@chancestv/tv-focus 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -42
- package/dist/index.js +211 -256
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/ATTRIBUTION.md +11 -1
- package/src/engine/spatial-navigation.ts +210 -257
package/README.md
CHANGED
|
@@ -1,51 +1,15 @@
|
|
|
1
1
|
# @chancestv/tv-focus
|
|
2
2
|
|
|
3
|
-
面向低版本 WebView(Chromium 53+)的 Vue 3 TV
|
|
3
|
+
面向低版本 WebView(Chromium 53+)的 Vue 3 TV 端空间导航焦点系统。遥控器方向键按屏幕几何位置在元素间移动焦点,支持 section 分区、模态层栈、记忆焦点。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> 业务层通常不直接使用本包,而是通过 [`@chancestv/tv-ui`](https://www.npmjs.com/package/@chancestv/tv-ui) 的组件使用。
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 文档
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
pnpm add @chancestv/tv-focus vue
|
|
11
|
-
```
|
|
9
|
+
完整文档、安装、用法、API 与在线 Demo 见 GitHub 仓库(npm 页不再维护文档,以仓库为准):
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
import { setupFocus, nativeKeyAdapter } from '@chancestv/tv-focus'
|
|
17
|
-
|
|
18
|
-
// 应用入口调用一次
|
|
19
|
-
setupFocus({ defaults: { rememberSource: true } })
|
|
20
|
-
// 把 OTT 原生遥控器按键透传为合成 keydown/keyup
|
|
21
|
-
nativeKeyAdapter('your-native-key-event-name')
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
```vue
|
|
25
|
-
<script setup lang="ts">
|
|
26
|
-
import { FocusSection, Focusable } from '@chancestv/tv-focus'
|
|
27
|
-
</script>
|
|
28
|
-
|
|
29
|
-
<template>
|
|
30
|
-
<FocusSection id="home">
|
|
31
|
-
<Focusable v-slot="{ focused }">
|
|
32
|
-
<div :class="{ active: focused }">item</div>
|
|
33
|
-
</Focusable>
|
|
34
|
-
</FocusSection>
|
|
35
|
-
</template>
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## 导出
|
|
39
|
-
|
|
40
|
-
- 初始化:`setupFocus` / `nativeKeyAdapter` / `SpatialNavigation`
|
|
41
|
-
- 组件:`Focusable` / `FocusSection` / `FocusLayer`
|
|
42
|
-
- 组合式:`useFocusable` / `useFocusSection` / `useKeepAliveFocus`
|
|
43
|
-
- 工具:`hasOpenLayer`
|
|
44
|
-
- 类型:`Direction` / `Restrict` / `EnterTo` / `LeaveFor` / `SectionConfig` 等
|
|
45
|
-
|
|
46
|
-
## 兼容目标
|
|
47
|
-
|
|
48
|
-
产物语法降级到 `chrome53`。
|
|
11
|
+
- 仓库与文档:https://github.com/danweiyuancircle/chances-tv-kit
|
|
12
|
+
- 在线 Demo(遥控器方向键操作):https://danweiyuancircle.github.io/chances-tv-kit/
|
|
49
13
|
|
|
50
14
|
## License
|
|
51
15
|
|
package/dist/index.js
CHANGED
|
@@ -64,278 +64,172 @@ function getRect(elem) {
|
|
|
64
64
|
rect.center.top = rect.center.bottom = rect.center.y;
|
|
65
65
|
return rect;
|
|
66
66
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
if (center.y < targetRect.top) {
|
|
81
|
-
y = 0;
|
|
82
|
-
} else if (center.y <= targetRect.bottom) {
|
|
83
|
-
y = 1;
|
|
84
|
-
} else {
|
|
85
|
-
y = 2;
|
|
86
|
-
}
|
|
87
|
-
groupId = y * 3 + x;
|
|
88
|
-
groups[groupId].push(rect);
|
|
89
|
-
if ([0, 2, 6, 8].indexOf(groupId) !== -1) {
|
|
90
|
-
var threshold = straightOverlapThreshold;
|
|
91
|
-
if (rect.left <= targetRect.right - targetRect.width * threshold) {
|
|
92
|
-
if (groupId === 2) {
|
|
93
|
-
groups[1].push(rect);
|
|
94
|
-
} else if (groupId === 8) {
|
|
95
|
-
groups[7].push(rect);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (rect.right >= targetRect.left + targetRect.width * threshold) {
|
|
99
|
-
if (groupId === 0) {
|
|
100
|
-
groups[1].push(rect);
|
|
101
|
-
} else if (groupId === 6) {
|
|
102
|
-
groups[7].push(rect);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (rect.top <= targetRect.bottom - targetRect.height * threshold) {
|
|
106
|
-
if (groupId === 6) {
|
|
107
|
-
groups[3].push(rect);
|
|
108
|
-
} else if (groupId === 8) {
|
|
109
|
-
groups[5].push(rect);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (rect.bottom >= targetRect.top + targetRect.height * threshold) {
|
|
113
|
-
if (groupId === 0) {
|
|
114
|
-
groups[3].push(rect);
|
|
115
|
-
} else if (groupId === 2) {
|
|
116
|
-
groups[5].push(rect);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
67
|
+
function snMajorAxisDistanceRaw(direction, source, dest) {
|
|
68
|
+
switch (direction) {
|
|
69
|
+
case "left":
|
|
70
|
+
return source.left - dest.right;
|
|
71
|
+
case "right":
|
|
72
|
+
return dest.left - source.right;
|
|
73
|
+
case "up":
|
|
74
|
+
return source.top - dest.bottom;
|
|
75
|
+
case "down":
|
|
76
|
+
return dest.top - source.bottom;
|
|
77
|
+
default:
|
|
78
|
+
return 0;
|
|
120
79
|
}
|
|
121
|
-
return groups;
|
|
122
80
|
}
|
|
123
|
-
function
|
|
124
|
-
return
|
|
125
|
-
nearPlumbLineIsBetter: function(rect) {
|
|
126
|
-
var d;
|
|
127
|
-
if (rect.center.x < targetRect.center.x) {
|
|
128
|
-
d = targetRect.center.x - rect.right;
|
|
129
|
-
} else {
|
|
130
|
-
d = rect.left - targetRect.center.x;
|
|
131
|
-
}
|
|
132
|
-
return d < 0 ? 0 : d;
|
|
133
|
-
},
|
|
134
|
-
nearHorizonIsBetter: function(rect) {
|
|
135
|
-
var d;
|
|
136
|
-
if (rect.center.y < targetRect.center.y) {
|
|
137
|
-
d = targetRect.center.y - rect.bottom;
|
|
138
|
-
} else {
|
|
139
|
-
d = rect.top - targetRect.center.y;
|
|
140
|
-
}
|
|
141
|
-
return d < 0 ? 0 : d;
|
|
142
|
-
},
|
|
143
|
-
nearTargetLeftIsBetter: function(rect) {
|
|
144
|
-
var d;
|
|
145
|
-
if (rect.center.x < targetRect.center.x) {
|
|
146
|
-
d = targetRect.left - rect.right;
|
|
147
|
-
} else {
|
|
148
|
-
d = rect.left - targetRect.left;
|
|
149
|
-
}
|
|
150
|
-
return d < 0 ? 0 : d;
|
|
151
|
-
},
|
|
152
|
-
nearTargetTopIsBetter: function(rect) {
|
|
153
|
-
var d;
|
|
154
|
-
if (rect.center.y < targetRect.center.y) {
|
|
155
|
-
d = targetRect.top - rect.bottom;
|
|
156
|
-
} else {
|
|
157
|
-
d = rect.top - targetRect.top;
|
|
158
|
-
}
|
|
159
|
-
return d < 0 ? 0 : d;
|
|
160
|
-
},
|
|
161
|
-
topIsBetter: function(rect) {
|
|
162
|
-
return rect.top;
|
|
163
|
-
},
|
|
164
|
-
bottomIsBetter: function(rect) {
|
|
165
|
-
return -1 * rect.bottom;
|
|
166
|
-
},
|
|
167
|
-
leftIsBetter: function(rect) {
|
|
168
|
-
return rect.left;
|
|
169
|
-
},
|
|
170
|
-
rightIsBetter: function(rect) {
|
|
171
|
-
return -1 * rect.right;
|
|
172
|
-
}
|
|
173
|
-
};
|
|
81
|
+
function snMajorAxisDistance(direction, source, dest) {
|
|
82
|
+
return Math.max(0, snMajorAxisDistanceRaw(direction, source, dest));
|
|
174
83
|
}
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
84
|
+
function snMajorAxisDistanceToFarEdgeRaw(direction, source, dest) {
|
|
85
|
+
switch (direction) {
|
|
86
|
+
case "left":
|
|
87
|
+
return source.left - dest.left;
|
|
88
|
+
case "right":
|
|
89
|
+
return dest.right - source.right;
|
|
90
|
+
case "up":
|
|
91
|
+
return source.top - dest.top;
|
|
92
|
+
case "down":
|
|
93
|
+
return dest.bottom - source.bottom;
|
|
94
|
+
default:
|
|
95
|
+
return 0;
|
|
182
96
|
}
|
|
183
|
-
|
|
184
|
-
|
|
97
|
+
}
|
|
98
|
+
function snMajorAxisDistanceToFarEdge(direction, source, dest) {
|
|
99
|
+
return Math.max(1, snMajorAxisDistanceToFarEdgeRaw(direction, source, dest));
|
|
100
|
+
}
|
|
101
|
+
function snMinorAxisDistance(direction, source, dest) {
|
|
102
|
+
switch (direction) {
|
|
103
|
+
case "left":
|
|
104
|
+
case "right":
|
|
105
|
+
return Math.abs(source.center.y - dest.center.y);
|
|
106
|
+
case "up":
|
|
107
|
+
case "down":
|
|
108
|
+
return Math.abs(source.center.x - dest.center.x);
|
|
109
|
+
default:
|
|
110
|
+
return 0;
|
|
185
111
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
112
|
+
}
|
|
113
|
+
function snGetWeightedDistanceFor(major, minor) {
|
|
114
|
+
return 13 * major * major + minor * minor;
|
|
115
|
+
}
|
|
116
|
+
function snIsToDirectionOf(direction, source, dest) {
|
|
117
|
+
switch (direction) {
|
|
118
|
+
case "left":
|
|
119
|
+
return source.left >= dest.right;
|
|
120
|
+
case "right":
|
|
121
|
+
return source.right <= dest.left;
|
|
122
|
+
case "up":
|
|
123
|
+
return source.top >= dest.bottom;
|
|
124
|
+
case "down":
|
|
125
|
+
return source.bottom <= dest.top;
|
|
126
|
+
default:
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function snIsCandidate(source, dest, direction) {
|
|
131
|
+
switch (direction) {
|
|
132
|
+
case "left":
|
|
133
|
+
return (source.right > dest.right || source.left >= dest.right) && source.left > dest.left;
|
|
134
|
+
case "right":
|
|
135
|
+
return (source.left < dest.left || source.right <= dest.left) && source.right < dest.right;
|
|
136
|
+
case "up":
|
|
137
|
+
return (source.bottom > dest.bottom || source.top >= dest.bottom) && source.top > dest.top;
|
|
138
|
+
case "down":
|
|
139
|
+
return (source.top < dest.top || source.bottom <= dest.top) && source.bottom < dest.bottom;
|
|
140
|
+
default:
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function snBeamsOverlap(direction, rect1, rect2) {
|
|
145
|
+
switch (direction) {
|
|
146
|
+
case "left":
|
|
147
|
+
case "right":
|
|
148
|
+
return rect2.bottom >= rect1.top && rect2.top <= rect1.bottom;
|
|
149
|
+
case "up":
|
|
150
|
+
case "down":
|
|
151
|
+
return rect2.right >= rect1.left && rect2.left <= rect1.right;
|
|
152
|
+
default:
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function snBeamBeats(direction, source, rect1, rect2) {
|
|
157
|
+
var r1In = snBeamsOverlap(direction, source, rect1);
|
|
158
|
+
var r2In = snBeamsOverlap(direction, source, rect2);
|
|
159
|
+
if (r2In || !r1In) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (!snIsToDirectionOf(direction, source, rect2)) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
if (direction === "left" || direction === "right") {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return snMajorAxisDistance(direction, source, rect1) < snMajorAxisDistanceToFarEdge(direction, source, rect2);
|
|
169
|
+
}
|
|
170
|
+
function snIsBetterCandidate(direction, source, rect1, rect2) {
|
|
171
|
+
if (!snIsCandidate(source, rect1, direction)) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (!snIsCandidate(source, rect2, direction)) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
if (snBeamBeats(direction, source, rect1, rect2)) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
if (snBeamBeats(direction, source, rect2, rect1)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
return snGetWeightedDistanceFor(
|
|
184
|
+
snMajorAxisDistance(direction, source, rect1),
|
|
185
|
+
snMinorAxisDistance(direction, source, rect1)
|
|
186
|
+
) < snGetWeightedDistanceFor(
|
|
187
|
+
snMajorAxisDistance(direction, source, rect2),
|
|
188
|
+
snMinorAxisDistance(direction, source, rect2)
|
|
189
|
+
);
|
|
198
190
|
}
|
|
199
191
|
function navigate(target, direction, candidates, config, preferNearest) {
|
|
200
192
|
if (!target || !direction || !candidates || !candidates.length) {
|
|
201
193
|
return null;
|
|
202
194
|
}
|
|
195
|
+
var targetRect = getRect(target);
|
|
196
|
+
if (!targetRect) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
203
199
|
var rects = [];
|
|
204
200
|
for (var i = 0; i < candidates.length; i++) {
|
|
205
|
-
var
|
|
206
|
-
if (
|
|
207
|
-
|
|
201
|
+
var r = getRect(candidates[i]);
|
|
202
|
+
if (!r) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (!snIsCandidate(targetRect, r, direction)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (config.straightOnly && !snBeamsOverlap(direction, targetRect, r)) {
|
|
209
|
+
continue;
|
|
208
210
|
}
|
|
211
|
+
rects.push(r);
|
|
209
212
|
}
|
|
210
213
|
if (!rects.length) {
|
|
211
214
|
return null;
|
|
212
215
|
}
|
|
213
|
-
var
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
var distanceFunction = generateDistanceFunction(targetRect);
|
|
218
|
-
var groups = partition(
|
|
219
|
-
rects,
|
|
220
|
-
targetRect,
|
|
221
|
-
config.straightOverlapThreshold
|
|
222
|
-
);
|
|
223
|
-
var internalGroups = partition(
|
|
224
|
-
groups[4],
|
|
225
|
-
targetRect.center,
|
|
226
|
-
config.straightOverlapThreshold
|
|
227
|
-
);
|
|
228
|
-
var priorities;
|
|
229
|
-
var df = distanceFunction;
|
|
230
|
-
var internalGroup, straightGroup, diagonalGroups;
|
|
231
|
-
var internalDist, straightDist, diagonalDist, mergedDist;
|
|
232
|
-
switch (direction) {
|
|
233
|
-
case "left":
|
|
234
|
-
internalGroup = internalGroups[0].concat(internalGroups[3]).concat(internalGroups[6]);
|
|
235
|
-
straightGroup = groups[3];
|
|
236
|
-
diagonalGroups = groups[0].concat(groups[6]);
|
|
237
|
-
internalDist = [df.nearPlumbLineIsBetter, df.topIsBetter];
|
|
238
|
-
straightDist = [df.nearPlumbLineIsBetter, df.topIsBetter];
|
|
239
|
-
diagonalDist = [
|
|
240
|
-
df.nearHorizonIsBetter,
|
|
241
|
-
df.rightIsBetter,
|
|
242
|
-
df.nearTargetTopIsBetter
|
|
243
|
-
];
|
|
244
|
-
mergedDist = [
|
|
245
|
-
df.nearPlumbLineIsBetter,
|
|
246
|
-
df.nearHorizonIsBetter,
|
|
247
|
-
df.topIsBetter
|
|
248
|
-
];
|
|
249
|
-
break;
|
|
250
|
-
case "right":
|
|
251
|
-
internalGroup = internalGroups[2].concat(internalGroups[5]).concat(internalGroups[8]);
|
|
252
|
-
straightGroup = groups[5];
|
|
253
|
-
diagonalGroups = groups[2].concat(groups[8]);
|
|
254
|
-
internalDist = [df.nearPlumbLineIsBetter, df.topIsBetter];
|
|
255
|
-
straightDist = [df.nearPlumbLineIsBetter, df.topIsBetter];
|
|
256
|
-
diagonalDist = [
|
|
257
|
-
df.nearHorizonIsBetter,
|
|
258
|
-
df.leftIsBetter,
|
|
259
|
-
df.nearTargetTopIsBetter
|
|
260
|
-
];
|
|
261
|
-
mergedDist = [
|
|
262
|
-
df.nearPlumbLineIsBetter,
|
|
263
|
-
df.nearHorizonIsBetter,
|
|
264
|
-
df.topIsBetter
|
|
265
|
-
];
|
|
266
|
-
break;
|
|
267
|
-
case "up":
|
|
268
|
-
internalGroup = internalGroups[0].concat(internalGroups[1]).concat(internalGroups[2]);
|
|
269
|
-
straightGroup = groups[1];
|
|
270
|
-
diagonalGroups = groups[0].concat(groups[2]);
|
|
271
|
-
internalDist = [df.nearHorizonIsBetter, df.leftIsBetter];
|
|
272
|
-
straightDist = [df.nearHorizonIsBetter, df.leftIsBetter];
|
|
273
|
-
diagonalDist = [
|
|
274
|
-
df.nearPlumbLineIsBetter,
|
|
275
|
-
df.bottomIsBetter,
|
|
276
|
-
df.nearTargetLeftIsBetter
|
|
277
|
-
];
|
|
278
|
-
mergedDist = [
|
|
279
|
-
df.nearHorizonIsBetter,
|
|
280
|
-
df.nearPlumbLineIsBetter,
|
|
281
|
-
df.leftIsBetter
|
|
282
|
-
];
|
|
283
|
-
break;
|
|
284
|
-
case "down":
|
|
285
|
-
internalGroup = internalGroups[6].concat(internalGroups[7]).concat(internalGroups[8]);
|
|
286
|
-
straightGroup = groups[7];
|
|
287
|
-
diagonalGroups = groups[6].concat(groups[8]);
|
|
288
|
-
internalDist = [df.nearHorizonIsBetter, df.leftIsBetter];
|
|
289
|
-
straightDist = [df.nearHorizonIsBetter, df.leftIsBetter];
|
|
290
|
-
diagonalDist = [
|
|
291
|
-
df.nearPlumbLineIsBetter,
|
|
292
|
-
df.topIsBetter,
|
|
293
|
-
df.nearTargetLeftIsBetter
|
|
294
|
-
];
|
|
295
|
-
mergedDist = [
|
|
296
|
-
df.nearHorizonIsBetter,
|
|
297
|
-
df.nearPlumbLineIsBetter,
|
|
298
|
-
df.leftIsBetter
|
|
299
|
-
];
|
|
300
|
-
break;
|
|
301
|
-
default:
|
|
302
|
-
return null;
|
|
303
|
-
}
|
|
304
|
-
if (preferNearest) {
|
|
305
|
-
priorities = [
|
|
306
|
-
{ group: internalGroup, distance: internalDist },
|
|
307
|
-
{
|
|
308
|
-
group: config.straightOnly ? straightGroup : straightGroup.concat(diagonalGroups),
|
|
309
|
-
distance: mergedDist
|
|
310
|
-
}
|
|
311
|
-
];
|
|
312
|
-
} else {
|
|
313
|
-
priorities = [
|
|
314
|
-
{ group: internalGroup, distance: internalDist },
|
|
315
|
-
{ group: straightGroup, distance: straightDist },
|
|
316
|
-
{ group: diagonalGroups, distance: diagonalDist }
|
|
317
|
-
];
|
|
318
|
-
if (config.straightOnly) {
|
|
319
|
-
priorities.pop();
|
|
216
|
+
var best = rects[0];
|
|
217
|
+
for (var k = 1; k < rects.length; k++) {
|
|
218
|
+
if (snIsBetterCandidate(direction, targetRect, rects[k], best)) {
|
|
219
|
+
best = rects[k];
|
|
320
220
|
}
|
|
321
221
|
}
|
|
322
|
-
var destGroup = prioritize(priorities);
|
|
323
|
-
if (!destGroup) {
|
|
324
|
-
return null;
|
|
325
|
-
}
|
|
326
|
-
var dest = null;
|
|
327
222
|
if (config.rememberSource && config.previous && config.previous.destination === target && config.previous.reverse === direction) {
|
|
328
|
-
for (var j = 0; j <
|
|
329
|
-
if (
|
|
330
|
-
|
|
223
|
+
for (var j = 0; j < rects.length; j++) {
|
|
224
|
+
if (rects[j].element === config.previous.target) {
|
|
225
|
+
if (!snIsBetterCandidate(direction, targetRect, best, rects[j])) {
|
|
226
|
+
return rects[j].element;
|
|
227
|
+
}
|
|
331
228
|
break;
|
|
332
229
|
}
|
|
333
230
|
}
|
|
334
231
|
}
|
|
335
|
-
|
|
336
|
-
dest = destGroup[0].element;
|
|
337
|
-
}
|
|
338
|
-
return dest;
|
|
232
|
+
return best.element;
|
|
339
233
|
}
|
|
340
234
|
function generateId() {
|
|
341
235
|
var id;
|
|
@@ -615,7 +509,7 @@ function getScrollScope(elem) {
|
|
|
615
509
|
}
|
|
616
510
|
function navigateWithinScrollScope(target, direction, candidates, config, preferNearest) {
|
|
617
511
|
if (!candidates || candidates.length < 2) {
|
|
618
|
-
return navigate(target, direction, candidates, config
|
|
512
|
+
return navigate(target, direction, candidates, config);
|
|
619
513
|
}
|
|
620
514
|
var targetScope = getScrollScope(target);
|
|
621
515
|
var inScope = [];
|
|
@@ -628,9 +522,60 @@ function navigateWithinScrollScope(target, direction, candidates, config, prefer
|
|
|
628
522
|
}
|
|
629
523
|
}
|
|
630
524
|
if (inScope.length && outScope.length) {
|
|
631
|
-
return navigate(target, direction, inScope, config
|
|
525
|
+
return navigate(target, direction, inScope, config) || navigate(target, direction, outScope, config);
|
|
526
|
+
}
|
|
527
|
+
return navigate(target, direction, candidates, config);
|
|
528
|
+
}
|
|
529
|
+
function filterCandidatesByDirection(source, direction, sectionNavMap, excludeSectionId) {
|
|
530
|
+
var srcRect = getRect(source);
|
|
531
|
+
var kept = [];
|
|
532
|
+
for (var id in sectionNavMap) {
|
|
533
|
+
if (id === excludeSectionId) {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
var elems = sectionNavMap[id];
|
|
537
|
+
if (!elems || !elems.length) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
var left = Infinity, top = Infinity, right = -Infinity, bottom = -Infinity;
|
|
541
|
+
var rects = [];
|
|
542
|
+
for (var i = 0; i < elems.length; i++) {
|
|
543
|
+
var r = getRect(elems[i]);
|
|
544
|
+
if (!r) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
rects.push(r);
|
|
548
|
+
if (r.left < left) {
|
|
549
|
+
left = r.left;
|
|
550
|
+
}
|
|
551
|
+
if (r.top < top) {
|
|
552
|
+
top = r.top;
|
|
553
|
+
}
|
|
554
|
+
if (r.right > right) {
|
|
555
|
+
right = r.right;
|
|
556
|
+
}
|
|
557
|
+
if (r.bottom > bottom) {
|
|
558
|
+
bottom = r.bottom;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (!rects.length) {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
var unionRect = {
|
|
565
|
+
left,
|
|
566
|
+
top,
|
|
567
|
+
right,
|
|
568
|
+
bottom,
|
|
569
|
+
width: right - left,
|
|
570
|
+
height: bottom - top
|
|
571
|
+
};
|
|
572
|
+
if (snIsCandidate(srcRect, unionRect, direction)) {
|
|
573
|
+
for (var j = 0; j < rects.length; j++) {
|
|
574
|
+
kept.push(rects[j].element);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
632
577
|
}
|
|
633
|
-
return
|
|
578
|
+
return kept;
|
|
634
579
|
}
|
|
635
580
|
function focusNext(direction, currentFocusedElement, currentSectionId) {
|
|
636
581
|
var extSelector = currentFocusedElement.getAttribute("data-sn-" + direction);
|
|
@@ -658,12 +603,23 @@ function focusNext(direction, currentFocusedElement, currentSectionId) {
|
|
|
658
603
|
config
|
|
659
604
|
);
|
|
660
605
|
if (!next && config.restrict == "self-first") {
|
|
606
|
+
var prunedCandidates = filterCandidatesByDirection(
|
|
607
|
+
currentFocusedElement,
|
|
608
|
+
direction,
|
|
609
|
+
sectionNavigableElements,
|
|
610
|
+
currentSectionId
|
|
611
|
+
);
|
|
612
|
+
if (!prunedCandidates.length) {
|
|
613
|
+
prunedCandidates = exclude(
|
|
614
|
+
allNavigableElements,
|
|
615
|
+
currentSectionNavigableElements
|
|
616
|
+
);
|
|
617
|
+
}
|
|
661
618
|
next = navigateWithinScrollScope(
|
|
662
619
|
currentFocusedElement,
|
|
663
620
|
direction,
|
|
664
|
-
|
|
665
|
-
config
|
|
666
|
-
true
|
|
621
|
+
prunedCandidates,
|
|
622
|
+
config
|
|
667
623
|
);
|
|
668
624
|
}
|
|
669
625
|
} else {
|
|
@@ -671,8 +627,7 @@ function focusNext(direction, currentFocusedElement, currentSectionId) {
|
|
|
671
627
|
currentFocusedElement,
|
|
672
628
|
direction,
|
|
673
629
|
exclude(allNavigableElements, currentFocusedElement),
|
|
674
|
-
config
|
|
675
|
-
false
|
|
630
|
+
config
|
|
676
631
|
);
|
|
677
632
|
}
|
|
678
633
|
if (next) {
|