@krx3d/tizentubekrx 1.15.3 → 1.15.7

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.
@@ -0,0 +1,2285 @@
1
+ const CONFIG_KEY = 'ytaf-configuration';
2
+ const defaultConfig = {
3
+ enableAdBlock: true,
4
+ enableSponsorBlock: true,
5
+ sponsorBlockManualSkips: ['intro', 'outro', 'filler'],
6
+ enableSponsorBlockSponsor: true,
7
+ enableSponsorBlockIntro: true,
8
+ enableSponsorBlockOutro: true,
9
+ enableSponsorBlockInteraction: true,
10
+ enableSponsorBlockSelfPromo: true,
11
+ enableSponsorBlockPreview: true,
12
+ enableSponsorBlockMusicOfftopic: true,
13
+ enableSponsorBlockFiller: false,
14
+ enableSponsorBlockHighlight: true,
15
+ videoSpeed: 1,
16
+ preferredVideoQuality: 'auto',
17
+ enableDeArrow: true,
18
+ enableDeArrowThumbnails: false,
19
+ focusContainerColor: '#0f0f0f',
20
+ routeColor: '#0f0f0f',
21
+ enableFixedUI: (window.h5vcc && window.h5vcc.tizentube) ? false : true,
22
+ enableHqThumbnails: true,
23
+ enableChapters: true,
24
+ enableLongPress: true,
25
+ enableShorts: false,
26
+ dontCheckUpdateUntil: 0,
27
+ enableWhoIsWatchingMenu: false,
28
+ enableShowUserLanguage: true,
29
+ enableShowOtherLanguages: false,
30
+ showWelcomeToast: false,
31
+ enablePreviousNextButtons: true,
32
+ enableSuperThanksButton: false,
33
+ enableSpeedControlsButton: true,
34
+ enablePatchingVideoPlayer: true,
35
+ enablePreviews: false,
36
+ enableHideWatchedVideos: true,
37
+ hideWatchedVideosThreshold: 10,
38
+ hideWatchedVideosPages: ['home', 'music', 'gaming', 'subscriptions', 'more'],
39
+ enableHideWatchedInPlaylists: true,
40
+ enablePlaylistContinueButton: true,
41
+ enableHideEndScreenCards: false,
42
+ enableYouThereRenderer: false,
43
+ lastAnnouncementCheck: 0,
44
+ enableScreenDimming: true,
45
+ dimmingTimeout: 60,
46
+ dimmingOpacity: 0.5,
47
+ enablePaidPromotionOverlay: false,
48
+ speedSettingsIncrement: 0.25,
49
+ videoPreferredCodec: 'any',
50
+ launchToOnStartup: null,
51
+ disabledSidebarContents: ['TROPHY', 'NEWS', 'YOUTUBE_MUSIC', 'BROADCAST', 'CLAPPERBOARD', 'LIVE', 'GAMING', 'TAB_MORE'],
52
+ enableUpdater: true
53
+ };
54
+
55
+ let localConfig;
56
+
57
+ try {
58
+ localConfig = JSON.parse(window.localStorage[CONFIG_KEY]);
59
+ } catch (err) {
60
+ console.warn('Config read failed:', err);
61
+ localConfig = defaultConfig;
62
+ }
63
+
64
+ export function configRead(key) {
65
+ if (localConfig[key] === undefined) {
66
+ console.warn('Populating key', key, 'with default value', defaultConfig[key]);
67
+ localConfig[key] = defaultConfig[key];
68
+ }
69
+
70
+ return localConfig[key];
71
+ }
72
+
73
+ export function configWrite(key, value) {
74
+ console.info('Setting key', key, 'to', value);
75
+ localConfig[key] = value;
76
+ window.localStorage[CONFIG_KEY] = JSON.stringify(localConfig);
77
+ configChangeEmitter.dispatchEvent(new CustomEvent('configChange', { detail: { key, value } }));
78
+ }
79
+
80
+ export const configChangeEmitter = {
81
+ listeners: {},
82
+ addEventListener(type, callback) {
83
+ if (!this.listeners[type]) this.listeners[type] = [];
84
+ this.listeners[type].push(callback);
85
+ },
86
+ removeEventListener(type, callback) {
87
+ if (!this.listeners[type]) return;
88
+ this.listeners[type] = this.listeners[type].filter(cb => cb !== callback);
89
+ },
90
+ dispatchEvent(event) {
91
+ const type = event.type;
92
+ if (!this.listeners[type]) return;
93
+ this.listeners[type].forEach(cb => {
94
+ try {
95
+ cb.call(this, event)
96
+ } catch (_) {};
97
+ });
98
+ }
99
+ };
100
+ //
101
+ // https://raw.githubusercontent.com/Financial-Times/polyfill-library/c25c30e4463bef60fba1213ecb697f3e3f253d7b/polyfills/DOMRect/polyfill.js
102
+ // License: MIT
103
+ //
104
+
105
+ (function (global) {
106
+ function number(v) {
107
+ return v === undefined ? 0 : Number(v);
108
+ }
109
+
110
+ function different(u, v) {
111
+ return u !== v && !(isNaN(u) && isNaN(v));
112
+ }
113
+
114
+ function DOMRect(xArg, yArg, wArg, hArg) {
115
+ var x, y, width, height, left, right, top, bottom;
116
+
117
+ x = number(xArg);
118
+ y = number(yArg);
119
+ width = number(wArg);
120
+ height = number(hArg);
121
+
122
+ Object.defineProperties(this, {
123
+ x: {
124
+ get: function () { return x; },
125
+ set: function (newX) {
126
+ if (different(x, newX)) {
127
+ x = newX;
128
+ left = right = undefined;
129
+ }
130
+ },
131
+ enumerable: true
132
+ },
133
+ y: {
134
+ get: function () { return y; },
135
+ set: function (newY) {
136
+ if (different(y, newY)) {
137
+ y = newY;
138
+ top = bottom = undefined;
139
+ }
140
+ },
141
+ enumerable: true
142
+ },
143
+ width: {
144
+ get: function () { return width; },
145
+ set: function (newWidth) {
146
+ if (different(width, newWidth)) {
147
+ width = newWidth;
148
+ left = right = undefined;
149
+ }
150
+ },
151
+ enumerable: true
152
+ },
153
+ height: {
154
+ get: function () { return height; },
155
+ set: function (newHeight) {
156
+ if (different(height, newHeight)) {
157
+ height = newHeight;
158
+ top = bottom = undefined;
159
+ }
160
+ },
161
+ enumerable: true
162
+ },
163
+ left: {
164
+ get: function () {
165
+ if (left === undefined) {
166
+ left = x + Math.min(0, width);
167
+ }
168
+ return left;
169
+ },
170
+ enumerable: true
171
+ },
172
+ right: {
173
+ get: function () {
174
+ if (right === undefined) {
175
+ right = x + Math.max(0, width);
176
+ }
177
+ return right;
178
+ },
179
+ enumerable: true
180
+ },
181
+ top: {
182
+ get: function () {
183
+ if (top === undefined) {
184
+ top = y + Math.min(0, height);
185
+ }
186
+ return top;
187
+ },
188
+ enumerable: true
189
+ },
190
+ bottom: {
191
+ get: function () {
192
+ if (bottom === undefined) {
193
+ bottom = y + Math.max(0, height);
194
+ }
195
+ return bottom;
196
+ },
197
+ enumerable: true
198
+ }
199
+ });
200
+ }
201
+
202
+ global.DOMRect = DOMRect;
203
+ }(self));
204
+ import { configWrite, configRead } from './config.js';
205
+ import { enablePip } from './features/pictureInPicture.js';
206
+ import modernUI, { optionShow } from './ui/settings.js';
207
+ import { speedSettings } from './ui/speedUI.js';
208
+ import { showToast, buttonItem } from './ui/ytUI.js';
209
+ import checkForUpdates from './features/updater.js';
210
+
211
+ export default function resolveCommand(cmd, _) {
212
+ // resolveCommand function is pretty OP, it can do from opening modals, changing client settings and way more.
213
+ // Because the client might change, we should find it first.
214
+
215
+ for (const key in window._yttv) {
216
+ if (window._yttv[key] && window._yttv[key].instance && window._yttv[key].instance.resolveCommand) {
217
+ return window._yttv[key].instance.resolveCommand(cmd, _);
218
+ }
219
+ }
220
+ }
221
+
222
+ export function findFunction(funcName) {
223
+ for (const key in window._yttv) {
224
+ if (window._yttv[key] && window._yttv[key][funcName] && typeof window._yttv[key][funcName] === 'function') {
225
+ return window._yttv[key][funcName];
226
+ }
227
+ }
228
+ }
229
+
230
+ // Patch resolveCommand to be able to change TizenTube settings
231
+
232
+ export function patchResolveCommand() {
233
+ for (const key in window._yttv) {
234
+ if (window._yttv[key] && window._yttv[key].instance && window._yttv[key].instance.resolveCommand) {
235
+
236
+ const ogResolve = window._yttv[key].instance.resolveCommand;
237
+ window._yttv[key].instance.resolveCommand = function (cmd, _) {
238
+ if (cmd.setClientSettingEndpoint) {
239
+ // Command to change client settings. Use TizenTube configuration to change settings.
240
+ for (const settings of cmd.setClientSettingEndpoint.settingDatas) {
241
+ if (!settings.clientSettingEnum.item.includes('_')) {
242
+ for (const setting of cmd.setClientSettingEndpoint.settingDatas) {
243
+ const valName = Object.keys(setting).find(key => key.includes('Value'));
244
+ const value = valName === 'intValue' ? Number(setting[valName]) : setting[valName];
245
+ if (valName === 'arrayValue') {
246
+ const arr = configRead(setting.clientSettingEnum.item);
247
+ if (arr.includes(value)) {
248
+ arr.splice(arr.indexOf(value), 1);
249
+ } else {
250
+ arr.push(value);
251
+ }
252
+ configWrite(setting.clientSettingEnum.item, arr);
253
+ } else configWrite(setting.clientSettingEnum.item, value);
254
+ }
255
+ } else if (settings.clientSettingEnum.item === 'I18N_LANGUAGE') {
256
+ const lang = settings.stringValue;
257
+ const date = new Date();
258
+ date.setFullYear(date.getFullYear() + 10);
259
+ document.cookie = `PREF=hl=${lang}; expires=${date.toUTCString()};`;
260
+ resolveCommand({
261
+ signalAction: {
262
+ signal: 'RELOAD_PAGE'
263
+ }
264
+ });
265
+ return true;
266
+ }
267
+ }
268
+ } else if (cmd.customAction) {
269
+ customAction(cmd.customAction.action, cmd.customAction.parameters);
270
+ return true;
271
+ } else if (cmd?.signalAction?.customAction) {
272
+ customAction(cmd.signalAction.customAction.action, cmd.signalAction.customAction.parameters);
273
+ return true;
274
+ } else if (cmd?.showEngagementPanelEndpoint?.customAction) {
275
+ customAction(cmd.showEngagementPanelEndpoint.customAction.action, cmd.showEngagementPanelEndpoint.customAction.parameters);
276
+ return true;
277
+ } else if (cmd?.playlistEditEndpoint?.customAction) {
278
+ customAction(cmd.playlistEditEndpoint.customAction.action, cmd.playlistEditEndpoint.customAction.parameters);
279
+ return true;
280
+ } else if (cmd?.openPopupAction?.uniqueId === 'playback-settings') {
281
+ // Patch the playback settings popup to use TizenTube speed settings
282
+ const items = cmd.openPopupAction.popup.overlaySectionRenderer.overlay.overlayTwoPanelRenderer.actionPanel.overlayPanelRenderer.content.overlayPanelItemListRenderer.items;
283
+ for (const item of items) {
284
+ if (item?.compactLinkRenderer?.icon?.iconType === 'SLOW_MOTION_VIDEO') {
285
+ item.compactLinkRenderer.subtitle && (item.compactLinkRenderer.subtitle.simpleText = 'with TizenTube');
286
+ item.compactLinkRenderer.serviceEndpoint = {
287
+ clickTrackingParams: "null",
288
+ signalAction: {
289
+ customAction: {
290
+ action: 'TT_SPEED_SETTINGS_SHOW',
291
+ parameters: []
292
+ }
293
+ }
294
+ };
295
+ }
296
+ }
297
+
298
+ cmd.openPopupAction.popup.overlaySectionRenderer.overlay.overlayTwoPanelRenderer.actionPanel.overlayPanelRenderer.content.overlayPanelItemListRenderer.items.splice(2, 0,
299
+ buttonItem(
300
+ { title: 'Mini Player' },
301
+ { icon: 'CLEAR_COOKIES' }, [
302
+ {
303
+ customAction: {
304
+ action: 'ENTER_PIP'
305
+ }
306
+ }
307
+ ])
308
+ );
309
+ } else if (cmd?.watchEndpoint?.videoId) {
310
+ window.isPipPlaying = false;
311
+ const ytlrPlayerContainer = document.querySelector('ytlr-player-container');
312
+ ytlrPlayerContainer.style.removeProperty('z-index');
313
+ }
314
+
315
+ return ogResolve.call(this, cmd, _);
316
+ }
317
+ }
318
+ }
319
+ }
320
+
321
+ function customAction(action, parameters) {
322
+ switch (action) {
323
+ case 'SETTINGS_UPDATE':
324
+ modernUI(true, parameters);
325
+ break;
326
+ case 'OPTIONS_SHOW':
327
+ optionShow(parameters, parameters.update);
328
+ break;
329
+ case 'SKIP':
330
+ const kE = document.createEvent('Event');
331
+ kE.initEvent('keydown', true, true);
332
+ kE.keyCode = 27;
333
+ kE.which = 27;
334
+ document.dispatchEvent(kE);
335
+
336
+ document.querySelector('video').currentTime = parameters.time;
337
+ break;
338
+ case 'TT_SETTINGS_SHOW':
339
+ modernUI();
340
+ break;
341
+ case 'TT_SPEED_SETTINGS_SHOW':
342
+ speedSettings();
343
+ break;
344
+ case 'UPDATE_REMIND_LATER':
345
+ configWrite('dontCheckUpdateUntil', parameters);
346
+ break;
347
+ case 'UPDATE_DOWNLOAD':
348
+ window.h5vcc.tizentube.InstallAppFromURL(parameters);
349
+ showToast('TizenTube Update', 'Downloading update, please wait...');
350
+ break;
351
+ case 'SET_PLAYER_SPEED':
352
+ const speed = Number(parameters);
353
+ document.querySelector('video').playbackRate = speed;
354
+ break;
355
+ case 'ENTER_PIP':
356
+ enablePip();
357
+ break;
358
+ case 'SHOW_TOAST':
359
+ showToast('TizenTube', parameters);
360
+ break;
361
+ case 'ADD_TO_QUEUE':
362
+ window.queuedVideos.videos.push(parameters);
363
+ showToast('TizenTube', 'Video added to queue.');
364
+ break;
365
+ case 'CLEAR_QUEUE':
366
+ window.queuedVideos.videos = [];
367
+ showToast('TizenTube', 'Video queue cleared.');
368
+ break;
369
+ case 'CHECK_FOR_UPDATES':
370
+ checkForUpdates(true);
371
+ break;
372
+ }
373
+ }import { string } from 'rollup-plugin-string';
374
+ import terser from '@rollup/plugin-terser';
375
+ import getBabelOutputPlugin from '@rollup/plugin-babel';
376
+ import { nodeResolve } from '@rollup/plugin-node-resolve';
377
+ import commonjs from '@rollup/plugin-commonjs';
378
+
379
+ export default {
380
+ input: "userScript.js",
381
+ output: { file: "../dist/userScript.js", format: "iife" },
382
+ plugins: [
383
+ string({
384
+ include: "**/*.css",
385
+ }),
386
+ nodeResolve({
387
+ browser: true,
388
+ preferBuiltins: false,
389
+ }),
390
+ commonjs({
391
+ include: [/node_modules/, /mods/],
392
+ transformMixedEsModules: true,
393
+ }),
394
+ getBabelOutputPlugin({
395
+ babelHelpers: 'bundled',
396
+ presets: [
397
+ ['@babel/preset-env', {
398
+ targets: 'Chrome 47',
399
+ }],
400
+ ],
401
+ }),
402
+ terser({
403
+ ecma: '5',
404
+ mangle: true,
405
+ }),
406
+ ]
407
+ };//
408
+ // https://raw.githubusercontent.com/WICG/spatial-navigation/183f0146b6741007e46fa64ab0950447defdf8af/polyfill/spatial-navigation-polyfill.js
409
+ // License: MIT
410
+ //
411
+
412
+ /* Spatial Navigation Polyfill
413
+ *
414
+ * It follows W3C official specification
415
+ * https://drafts.csswg.org/css-nav-1/
416
+ *
417
+ * Copyright (c) 2018-2019 LG Electronics Inc.
418
+ * https://github.com/WICG/spatial-navigation/polyfill
419
+ *
420
+ * Licensed under the MIT license (MIT)
421
+ */
422
+
423
+ (function () {
424
+
425
+ // The polyfill must not be executed, if it's already enabled via browser engine or browser extensions.
426
+ if ('navigate' in window) {
427
+ return;
428
+ }
429
+
430
+ const ARROW_KEY_CODE = {37: 'left', 38: 'up', 39: 'right', 40: 'down'};
431
+ const TAB_KEY_CODE = 9;
432
+ let mapOfBoundRect = null;
433
+ let startingPoint = null; // Saves spatial navigation starting point
434
+ let savedSearchOrigin = {element: null, rect: null}; // Saves previous search origin
435
+ let searchOriginRect = null; // Rect of current search origin
436
+
437
+ /**
438
+ * Initiate the spatial navigation features of the polyfill.
439
+ * @function initiateSpatialNavigation
440
+ */
441
+ function initiateSpatialNavigation() {
442
+ /*
443
+ * Bind the standards APIs to be exposed to the window object for authors
444
+ */
445
+ window.navigate = navigate;
446
+ window.Element.prototype.spatialNavigationSearch = spatialNavigationSearch;
447
+ window.Element.prototype.focusableAreas = focusableAreas;
448
+ window.Element.prototype.getSpatialNavigationContainer = getSpatialNavigationContainer;
449
+
450
+ /*
451
+ * CSS.registerProperty() from the Properties and Values API
452
+ * Reference: https://drafts.css-houdini.org/css-properties-values-api/#the-registerproperty-function
453
+ */
454
+ if (window.CSS && CSS.registerProperty) {
455
+ if (window.getComputedStyle(document.documentElement).getPropertyValue('--spatial-navigation-contain') === '') {
456
+ CSS.registerProperty({
457
+ name: '--spatial-navigation-contain',
458
+ syntax: 'auto | contain',
459
+ inherits: false,
460
+ initialValue: 'auto'
461
+ });
462
+ }
463
+
464
+ if (window.getComputedStyle(document.documentElement).getPropertyValue('--spatial-navigation-action') === '') {
465
+ CSS.registerProperty({
466
+ name: '--spatial-navigation-action',
467
+ syntax: 'auto | focus | scroll',
468
+ inherits: false,
469
+ initialValue: 'auto'
470
+ });
471
+ }
472
+
473
+ if (window.getComputedStyle(document.documentElement).getPropertyValue('--spatial-navigation-function') === '') {
474
+ CSS.registerProperty({
475
+ name: '--spatial-navigation-function',
476
+ syntax: 'normal | grid',
477
+ inherits: false,
478
+ initialValue: 'normal'
479
+ });
480
+ }
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Add event handlers for the spatial navigation behavior.
486
+ * This function defines which input methods trigger the spatial navigation behavior.
487
+ * @function spatialNavigationHandler
488
+ */
489
+ function spatialNavigationHandler() {
490
+ /*
491
+ * keydown EventListener :
492
+ * If arrow key pressed, get the next focusing element and send it to focusing controller
493
+ */
494
+ window.addEventListener('keydown', (e) => {
495
+ const currentKeyMode = (parent && parent.__spatialNavigation__.keyMode) || window.__spatialNavigation__.keyMode;
496
+ const eventTarget = document.activeElement;
497
+ const dir = ARROW_KEY_CODE[e.keyCode];
498
+
499
+ if (e.keyCode === TAB_KEY_CODE) {
500
+ startingPoint = null;
501
+ }
502
+
503
+ if (!currentKeyMode ||
504
+ (currentKeyMode === 'NONE') ||
505
+ ((currentKeyMode === 'SHIFTARROW') && !e.shiftKey) ||
506
+ ((currentKeyMode === 'ARROW') && e.shiftKey))
507
+ return;
508
+
509
+ if (!e.defaultPrevented) {
510
+ let focusNavigableArrowKey = {left: true, up: true, right: true, down: true};
511
+
512
+ // Edge case (text input, area) : Don't move focus, just navigate cursor in text area
513
+ if ((eventTarget.nodeName === 'INPUT') || eventTarget.nodeName === 'TEXTAREA') {
514
+ focusNavigableArrowKey = handlingEditableElement(e);
515
+ }
516
+
517
+ if (focusNavigableArrowKey[dir]) {
518
+ e.preventDefault();
519
+ mapOfBoundRect = new Map();
520
+
521
+ navigate(dir);
522
+
523
+ mapOfBoundRect = null;
524
+ startingPoint = null;
525
+ }
526
+ }
527
+ });
528
+
529
+ /*
530
+ * mouseup EventListener :
531
+ * If the mouse click a point in the page, the point will be the starting point.
532
+ * NOTE: Let UA set the spatial navigation starting point based on click
533
+ */
534
+ document.addEventListener('mouseup', (e) => {
535
+ startingPoint = {x: e.clientX, y: e.clientY};
536
+ });
537
+
538
+ /*
539
+ * focusin EventListener :
540
+ * When the element get the focus, save it and its DOMRect for resetting the search origin
541
+ * if it disappears.
542
+ */
543
+ window.addEventListener('focusin', (e) => {
544
+ if (e.target !== window) {
545
+ savedSearchOrigin.element = e.target;
546
+ savedSearchOrigin.rect = e.target.getBoundingClientRect();
547
+ }
548
+ });
549
+ }
550
+
551
+ /**
552
+ * Enable the author to trigger spatial navigation programmatically, as if the user had done so manually.
553
+ * @see {@link https://drafts.csswg.org/css-nav-1/#dom-window-navigate}
554
+ * @function navigate
555
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
556
+ */
557
+ function navigate(dir) {
558
+ // spatial navigation steps
559
+
560
+ // 1
561
+ const searchOrigin = findSearchOrigin();
562
+ let eventTarget = searchOrigin;
563
+
564
+ let elementFromPosition = null;
565
+
566
+ // 2 Optional step, UA defined starting point
567
+ if (startingPoint) {
568
+ // if there is a starting point, set eventTarget as the element from position for getting the spatnav container
569
+ elementFromPosition = document.elementFromPoint(startingPoint.x, startingPoint.y);
570
+
571
+ // Use starting point if the starting point isn't inside the focusable element (but not container)
572
+ // * Starting point is meaningfull when:
573
+ // 1) starting point is inside the spatnav container
574
+ // 2) starting point is inside the non-focusable element
575
+ if (elementFromPosition === null) {
576
+ elementFromPosition = document.body;
577
+ }
578
+ if (isFocusable(elementFromPosition) && !isContainer(elementFromPosition)) {
579
+ startingPoint = null;
580
+ } else if (isContainer(elementFromPosition)) {
581
+ eventTarget = elementFromPosition;
582
+ } else {
583
+ eventTarget = elementFromPosition.getSpatialNavigationContainer();
584
+ }
585
+ }
586
+
587
+ // 4
588
+ if (eventTarget === document || eventTarget === document.documentElement) {
589
+ eventTarget = document.body || document.documentElement;
590
+ }
591
+
592
+ // 5
593
+ // At this point, spatialNavigationSearch can be applied.
594
+ // If startingPoint is either a scroll container or the document,
595
+ // find the best candidate within startingPoint
596
+ let container = null;
597
+ if ((isContainer(eventTarget) || eventTarget.nodeName === 'BODY') && !(eventTarget.nodeName === 'INPUT')) {
598
+ if (eventTarget.nodeName === 'IFRAME') {
599
+ eventTarget = eventTarget.contentDocument.documentElement;
600
+ }
601
+ container = eventTarget;
602
+ let bestInsideCandidate = null;
603
+
604
+ // 5-2
605
+ if ((document.activeElement === searchOrigin) ||
606
+ (document.activeElement === document.body) && (searchOrigin === document.documentElement)) {
607
+ if (getCSSSpatNavAction(eventTarget) === 'scroll') {
608
+ if (scrollingController(eventTarget, dir)) return;
609
+ } else if (getCSSSpatNavAction(eventTarget) === 'focus') {
610
+ bestInsideCandidate = eventTarget.spatialNavigationSearch(dir, {container: eventTarget, candidates: getSpatialNavigationCandidates(eventTarget, {mode: 'all'})});
611
+ if (focusingController(bestInsideCandidate, dir)) return;
612
+ } else if (getCSSSpatNavAction(eventTarget) === 'auto') {
613
+ bestInsideCandidate = eventTarget.spatialNavigationSearch(dir, {container: eventTarget});
614
+ if (focusingController(bestInsideCandidate, dir) || scrollingController(eventTarget, dir)) return;
615
+ }
616
+ } else {
617
+ // when the previous search origin became offscreen
618
+ container = container.getSpatialNavigationContainer();
619
+ }
620
+ }
621
+
622
+ // 6
623
+ // Let container be the nearest ancestor of eventTarget
624
+ container = eventTarget.getSpatialNavigationContainer();
625
+ let parentContainer = (container.parentElement) ? container.getSpatialNavigationContainer() : null;
626
+
627
+ // When the container is the viewport of a browsing context
628
+ if (!parentContainer && ( window.location !== window.parent.location)) {
629
+ parentContainer = window.parent.document.documentElement;
630
+ }
631
+
632
+ if (getCSSSpatNavAction(container) === 'scroll') {
633
+ if (scrollingController(container, dir)) return;
634
+ } else if (getCSSSpatNavAction(container) === 'focus') {
635
+ navigateChain(eventTarget, container, parentContainer, dir, 'all');
636
+ } else if (getCSSSpatNavAction(container) === 'auto') {
637
+ navigateChain(eventTarget, container, parentContainer, dir, 'visible');
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Move the focus to the best candidate or do nothing.
643
+ * @function focusingController
644
+ * @param bestCandidate {Node} - The best candidate of the spatial navigation
645
+ * @param dir {SpatialNavigationDirection}- The directional information for the spatial navigation (e.g. LRUD)
646
+ * @returns {boolean}
647
+ */
648
+ function focusingController(bestCandidate, dir) {
649
+ // 10 & 11
650
+ // When bestCandidate is found
651
+ if (bestCandidate) {
652
+ // When bestCandidate is a focusable element and not a container : move focus
653
+ /*
654
+ * [event] navbeforefocus : Fired before spatial or sequential navigation changes the focus.
655
+ */
656
+ if (!createSpatNavEvents('beforefocus', bestCandidate, null, dir))
657
+ return true;
658
+
659
+ const container = bestCandidate.getSpatialNavigationContainer();
660
+
661
+ if ((container !== window) && (getCSSSpatNavAction(container) === 'focus')) {
662
+ bestCandidate.focus();
663
+ } else {
664
+ bestCandidate.focus({preventScroll: true});
665
+ }
666
+
667
+ startingPoint = null;
668
+ return true;
669
+ }
670
+
671
+ // When bestCandidate is not found within the scrollport of a container: Nothing
672
+ return false;
673
+ }
674
+
675
+ /**
676
+ * Directionally scroll the scrollable spatial navigation container if it can be manually scrolled more.
677
+ * @function scrollingController
678
+ * @param container {Node} - The spatial navigation container which can scroll
679
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
680
+ * @returns {boolean}
681
+ */
682
+ function scrollingController(container, dir) {
683
+
684
+ // If there is any scrollable area among parent elements and it can be manually scrolled, scroll the document
685
+ if (isScrollable(container, dir) && !isScrollBoundary(container, dir)) {
686
+ moveScroll(container, dir);
687
+ return true;
688
+ }
689
+
690
+ // If the spatnav container is document and it can be scrolled, scroll the document
691
+ if (!container.parentElement && !isHTMLScrollBoundary(container, dir)) {
692
+ moveScroll(container.ownerDocument.documentElement, dir);
693
+ return true;
694
+ }
695
+ return false;
696
+ }
697
+
698
+ /**
699
+ * Find the candidates within a spatial navigation container include delegable container.
700
+ * This function does not search inside delegable container or focusable container.
701
+ * In other words, this return candidates set is not included focusable elements inside delegable container or focusable container.
702
+ *
703
+ * @function getSpatialNavigationCandidates
704
+ * @param container {Node} - The spatial navigation container
705
+ * @param option {FocusableAreasOptions} - 'mode' attribute takes 'visible' or 'all' for searching the boundary of focusable elements.
706
+ * Default value is 'visible'.
707
+ * @returns {sequence<Node>} candidate elements within the container
708
+ */
709
+ function getSpatialNavigationCandidates (container, option = {mode: 'visible'}) {
710
+ let candidates = [];
711
+
712
+ if (container.childElementCount > 0) {
713
+ if (!container.parentElement) {
714
+ container = container.getElementsByTagName('body')[0] || document.body;
715
+ }
716
+ const children = container.children;
717
+ for (const elem of children) {
718
+ if (isDelegableContainer(elem)) {
719
+ candidates.push(elem);
720
+ } else if (isFocusable(elem)) {
721
+ candidates.push(elem);
722
+
723
+ if (!isContainer(elem) && elem.childElementCount) {
724
+ candidates = candidates.concat(getSpatialNavigationCandidates(elem, {mode: 'all'}));
725
+ }
726
+ } else if (elem.childElementCount) {
727
+ candidates = candidates.concat(getSpatialNavigationCandidates(elem, {mode: 'all'}));
728
+ }
729
+ }
730
+ }
731
+ return (option.mode === 'all') ? candidates : candidates.filter(isVisible);
732
+ }
733
+
734
+ /**
735
+ * Find the candidates among focusable elements within a spatial navigation container from the search origin (currently focused element)
736
+ * depending on the directional information.
737
+ * @function getFilteredSpatialNavigationCandidates
738
+ * @param element {Node} - The currently focused element which is defined as 'search origin' in the spec
739
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
740
+ * @param candidates {sequence<Node>} - The candidates for spatial navigation without the directional information
741
+ * @param container {Node} - The spatial navigation container
742
+ * @returns {Node} The candidates for spatial navigation considering the directional information
743
+ */
744
+ function getFilteredSpatialNavigationCandidates (element, dir, candidates, container) {
745
+ const targetElement = element;
746
+ // Removed below line due to a bug. (iframe body rect is sometime weird.)
747
+ // const targetElement = (element.nodeName === 'IFRAME') ? element.contentDocument.body : element;
748
+ // If the container is unknown, get the closest container from the element
749
+ container = container || targetElement.getSpatialNavigationContainer();
750
+
751
+ // If the candidates is unknown, find candidates
752
+ // 5-1
753
+ candidates = (!candidates || candidates.length <= 0) ? getSpatialNavigationCandidates(container) : candidates;
754
+ return filteredCandidates(targetElement, candidates, dir, container);
755
+ }
756
+
757
+ /**
758
+ * Find the best candidate among the candidates within the container from the search origin (currently focused element)
759
+ * @see {@link https://drafts.csswg.org/css-nav-1/#dom-element-spatialnavigationsearch}
760
+ * @function spatialNavigationSearch
761
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
762
+ * @param candidates {sequence<Node>} - The candidates for spatial navigation
763
+ * @param container {Node} - The spatial navigation container
764
+ * @returns {Node} The best candidate which will gain the focus
765
+ */
766
+ function spatialNavigationSearch (dir, args) {
767
+ const targetElement = this;
768
+ let internalCandidates = [];
769
+ let externalCandidates = [];
770
+ let insideOverlappedCandidates = getOverlappedCandidates(targetElement);
771
+ let bestTarget;
772
+
773
+ // Set default parameter value
774
+ if (!args)
775
+ args = {};
776
+
777
+ const defaultContainer = targetElement.getSpatialNavigationContainer();
778
+ let defaultCandidates = getSpatialNavigationCandidates(defaultContainer);
779
+ const container = args.container || defaultContainer;
780
+ if (args.container && (defaultContainer.contains(args.container))) {
781
+ defaultCandidates = defaultCandidates.concat(getSpatialNavigationCandidates(container));
782
+ }
783
+ const candidates = (args.candidates && args.candidates.length > 0) ?
784
+ args.candidates.filter((candidate) => container.contains(candidate)) :
785
+ defaultCandidates.filter((candidate) => container.contains(candidate) && (container !== candidate));
786
+
787
+ // Find the best candidate
788
+ // 5
789
+ // If startingPoint is either a scroll container or the document,
790
+ // find the best candidate within startingPoint
791
+ if (candidates && candidates.length > 0) {
792
+
793
+ // Divide internal or external candidates
794
+ candidates.forEach(candidate => {
795
+ if (candidate !== targetElement) {
796
+ (targetElement.contains(candidate) && targetElement !== candidate ? internalCandidates : externalCandidates).push(candidate);
797
+ }
798
+ });
799
+
800
+ // include overlapped element to the internalCandidates
801
+ let fullyOverlapped = insideOverlappedCandidates.filter(candidate => !internalCandidates.includes(candidate));
802
+ let overlappedContainer = candidates.filter(candidate => (isContainer(candidate) && isEntirelyVisible(targetElement, candidate)));
803
+ let overlappedByParent = overlappedContainer.map((elm) => elm.focusableAreas()).flat().filter(candidate => candidate !== targetElement);
804
+
805
+ internalCandidates = internalCandidates.concat(fullyOverlapped).filter((candidate) => container.contains(candidate));
806
+ externalCandidates = externalCandidates.concat(overlappedByParent).filter((candidate) => container.contains(candidate));
807
+
808
+ // Filter external Candidates
809
+ if (externalCandidates.length > 0) {
810
+ externalCandidates = getFilteredSpatialNavigationCandidates(targetElement, dir, externalCandidates, container);
811
+ }
812
+
813
+ // If there isn't search origin element but search orgin rect exist (search origin isn't in the layout case)
814
+ if (searchOriginRect) {
815
+ bestTarget = selectBestCandidate(targetElement, getFilteredSpatialNavigationCandidates(targetElement, dir, internalCandidates, container), dir);
816
+ }
817
+
818
+ if ((internalCandidates && internalCandidates.length > 0) && !(targetElement.nodeName === 'INPUT')) {
819
+ bestTarget = selectBestCandidateFromEdge(targetElement, internalCandidates, dir);
820
+ }
821
+
822
+ bestTarget = bestTarget || selectBestCandidate(targetElement, externalCandidates, dir);
823
+
824
+ if (bestTarget && isDelegableContainer(bestTarget)) {
825
+ // if best target is delegable container, then find descendants candidate inside delegable container.
826
+ const innerTarget = getSpatialNavigationCandidates(bestTarget, {mode: 'all'});
827
+ const descendantsBest = innerTarget.length > 0 ? targetElement.spatialNavigationSearch(dir, {candidates: innerTarget, container: bestTarget}) : null;
828
+ if (descendantsBest) {
829
+ bestTarget = descendantsBest;
830
+ } else if (!isFocusable(bestTarget)) {
831
+ // if there is no target inside bestTarget and delegable container is not focusable,
832
+ // then try to find another best target without curren best target.
833
+ candidates.splice(candidates.indexOf(bestTarget), 1);
834
+ bestTarget = candidates.length ? targetElement.spatialNavigationSearch(dir, {candidates: candidates, container: container}) : null;
835
+ }
836
+ }
837
+ return bestTarget;
838
+ }
839
+
840
+ return null;
841
+ }
842
+
843
+ /**
844
+ * Get the filtered candidate among candidates.
845
+ * @see {@link https://drafts.csswg.org/css-nav-1/#select-the-best-candidate}
846
+ * @function filteredCandidates
847
+ * @param currentElm {Node} - The currently focused element which is defined as 'search origin' in the spec
848
+ * @param candidates {sequence<Node>} - The candidates for spatial navigation
849
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
850
+ * @param container {Node} - The spatial navigation container
851
+ * @returns {sequence<Node>} The filtered candidates which are not the search origin and not in the given spatial navigation direction from the search origin
852
+ */
853
+ // TODO: Need to fix filtering the candidates with more clean code
854
+ function filteredCandidates(currentElm, candidates, dir, container) {
855
+ const originalContainer = currentElm.getSpatialNavigationContainer();
856
+ let eventTargetRect;
857
+
858
+ // If D(dir) is null, let candidates be the same as visibles
859
+ if (dir === undefined)
860
+ return candidates;
861
+
862
+ // Offscreen handling when originalContainer is not <HTML>
863
+ if (originalContainer.parentElement && container !== originalContainer && !isVisible(currentElm)) {
864
+ eventTargetRect = getBoundingClientRect(originalContainer);
865
+ } else {
866
+ eventTargetRect = searchOriginRect || getBoundingClientRect(currentElm);
867
+ }
868
+
869
+ /*
870
+ * Else, let candidates be the subset of the elements in visibles
871
+ * whose principal box’s geometric center is within the closed half plane
872
+ * whose boundary goes through the geometric center of starting point and is perpendicular to D.
873
+ */
874
+ if ((isContainer(currentElm) || currentElm.nodeName === 'BODY') && !(currentElm.nodeName === 'INPUT')) {
875
+ return candidates.filter(candidate => {
876
+ const candidateRect = getBoundingClientRect(candidate);
877
+ return container.contains(candidate) &&
878
+ ((currentElm.contains(candidate) && isInside(eventTargetRect, candidateRect) && candidate !== currentElm) ||
879
+ isOutside(candidateRect, eventTargetRect, dir));
880
+ });
881
+ } else {
882
+ return candidates.filter(candidate => {
883
+ const candidateRect = getBoundingClientRect(candidate);
884
+ const candidateBody = (candidate.nodeName === 'IFRAME') ? candidate.contentDocument.body : null;
885
+ return container.contains(candidate) &&
886
+ candidate !== currentElm && candidateBody !== currentElm &&
887
+ isOutside(candidateRect, eventTargetRect, dir) &&
888
+ !isInside(eventTargetRect, candidateRect);
889
+ });
890
+ }
891
+ }
892
+
893
+ /**
894
+ * Select the best candidate among given candidates.
895
+ * @see {@link https://drafts.csswg.org/css-nav-1/#select-the-best-candidate}
896
+ * @function selectBestCandidate
897
+ * @param currentElm {Node} - The currently focused element which is defined as 'search origin' in the spec
898
+ * @param candidates {sequence<Node>} - The candidates for spatial navigation
899
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
900
+ * @returns {Node} The best candidate which will gain the focus
901
+ */
902
+ function selectBestCandidate(currentElm, candidates, dir) {
903
+ const container = currentElm.getSpatialNavigationContainer();
904
+ const spatialNavigationFunction = getComputedStyle(container).getPropertyValue('--spatial-navigation-function');
905
+ const currentTargetRect = searchOriginRect || getBoundingClientRect(currentElm);
906
+ let distanceFunction;
907
+ let alignedCandidates;
908
+
909
+ switch (spatialNavigationFunction) {
910
+ case 'grid':
911
+ alignedCandidates = candidates.filter(elm => isAligned(currentTargetRect, getBoundingClientRect(elm), dir));
912
+ if (alignedCandidates.length > 0) {
913
+ candidates = alignedCandidates;
914
+ }
915
+ distanceFunction = getAbsoluteDistance;
916
+ break;
917
+ default:
918
+ distanceFunction = getDistance;
919
+ break;
920
+ }
921
+ return getClosestElement(currentElm, candidates, dir, distanceFunction);
922
+ }
923
+
924
+ /**
925
+ * Select the best candidate among candidates by finding the closet candidate from the edge of the currently focused element (search origin).
926
+ * @see {@link https://drafts.csswg.org/css-nav-1/#select-the-best-candidate (Step 5)}
927
+ * @function selectBestCandidateFromEdge
928
+ * @param currentElm {Node} - The currently focused element which is defined as 'search origin' in the spec
929
+ * @param candidates {sequence<Node>} - The candidates for spatial navigation
930
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
931
+ * @returns {Node} The best candidate which will gain the focus
932
+ */
933
+ function selectBestCandidateFromEdge(currentElm, candidates, dir) {
934
+ if (startingPoint)
935
+ return getClosestElement(currentElm, candidates, dir, getDistanceFromPoint);
936
+ else
937
+ return getClosestElement(currentElm, candidates, dir, getInnerDistance);
938
+ }
939
+
940
+ /**
941
+ * Select the closest candidate from the currently focused element (search origin) among candidates by using the distance function.
942
+ * @function getClosestElement
943
+ * @param currentElm {Node} - The currently focused element which is defined as 'search origin' in the spec
944
+ * @param candidates {sequence<Node>} - The candidates for spatial navigation
945
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
946
+ * @param distanceFunction {function} - The distance function which measures the distance from the search origin to each candidate
947
+ * @returns {Node} The candidate which is the closest one from the search origin
948
+ */
949
+ function getClosestElement(currentElm, candidates, dir, distanceFunction) {
950
+ let eventTargetRect = null;
951
+ if (( window.location !== window.parent.location ) && (currentElm.nodeName === 'BODY' || currentElm.nodeName === 'HTML')) {
952
+ // If the eventTarget is iframe, then get rect of it based on its containing document
953
+ // Set the iframe's position as (0,0) because the rects of elements inside the iframe don't know the real iframe's position.
954
+ eventTargetRect = window.frameElement.getBoundingClientRect();
955
+ eventTargetRect.x = 0;
956
+ eventTargetRect.y = 0;
957
+ } else {
958
+ eventTargetRect = searchOriginRect || currentElm.getBoundingClientRect();
959
+ }
960
+
961
+ let minDistance = Number.POSITIVE_INFINITY;
962
+ let minDistanceElements = [];
963
+
964
+ if (candidates) {
965
+ for (let i = 0; i < candidates.length; i++) {
966
+ const distance = distanceFunction(eventTargetRect, getBoundingClientRect(candidates[i]), dir);
967
+
968
+ // If the same distance, the candidate will be selected in the DOM order
969
+ if (distance < minDistance) {
970
+ minDistance = distance;
971
+ minDistanceElements = [candidates[i]];
972
+ } else if (distance === minDistance) {
973
+ minDistanceElements.push(candidates[i]);
974
+ }
975
+ }
976
+ }
977
+ if (minDistanceElements.length === 0)
978
+ return null;
979
+
980
+ return (minDistanceElements.length > 1 && distanceFunction === getAbsoluteDistance) ?
981
+ getClosestElement(currentElm, minDistanceElements, dir, getEuclideanDistance) : minDistanceElements[0];
982
+ }
983
+
984
+ /**
985
+ * Get container of an element.
986
+ * @see {@link https://drafts.csswg.org/css-nav-1/#dom-element-getspatialnavigationcontainer}
987
+ * @module Element
988
+ * @function getSpatialNavigationContainer
989
+ * @returns {Node} The spatial navigation container
990
+ */
991
+ function getSpatialNavigationContainer() {
992
+ let container = this;
993
+
994
+ do {
995
+ if (!container.parentElement) {
996
+ if (window.location !== window.parent.location) {
997
+ container = window.parent.document.documentElement;
998
+ } else {
999
+ container = window.document.documentElement;
1000
+ }
1001
+ break;
1002
+ } else {
1003
+ container = container.parentElement;
1004
+ }
1005
+ } while (!isContainer(container));
1006
+ return container;
1007
+ }
1008
+
1009
+ /**
1010
+ * Get nearest scroll container of an element.
1011
+ * @function getScrollContainer
1012
+ * @param Element
1013
+ * @returns {Node} The spatial navigation container
1014
+ */
1015
+ function getScrollContainer(element) {
1016
+ let scrollContainer = element;
1017
+
1018
+ do {
1019
+ if (!scrollContainer.parentElement) {
1020
+ if (window.location !== window.parent.location) {
1021
+ scrollContainer = window.parent.document.documentElement;
1022
+ } else {
1023
+ scrollContainer = window.document.documentElement;
1024
+ }
1025
+ break;
1026
+ } else {
1027
+ scrollContainer = scrollContainer.parentElement;
1028
+ }
1029
+ } while (!isScrollContainer(scrollContainer) || !isVisible(scrollContainer));
1030
+
1031
+ if (scrollContainer === document || scrollContainer === document.documentElement) {
1032
+ scrollContainer = window;
1033
+ }
1034
+
1035
+ return scrollContainer;
1036
+ }
1037
+
1038
+ /**
1039
+ * Find focusable elements within the spatial navigation container.
1040
+ * @see {@link https://drafts.csswg.org/css-nav-1/#dom-element-focusableareas}
1041
+ * @function focusableAreas
1042
+ * @param option {FocusableAreasOptions} - 'mode' attribute takes 'visible' or 'all' for searching the boundary of focusable elements.
1043
+ * Default value is 'visible'.
1044
+ * @returns {sequence<Node>} All focusable elements or only visible focusable elements within the container
1045
+ */
1046
+ function focusableAreas(option = {mode: 'visible'}) {
1047
+ const container = this.parentElement ? this : document.body;
1048
+ const focusables = Array.prototype.filter.call(container.getElementsByTagName('*'), isFocusable);
1049
+ return (option.mode === 'all') ? focusables : focusables.filter(isVisible);
1050
+ }
1051
+
1052
+ /**
1053
+ * Create the NavigationEvent: navbeforefocus, navnotarget
1054
+ * @see {@link https://drafts.csswg.org/css-nav-1/#events-navigationevent}
1055
+ * @function createSpatNavEvents
1056
+ * @param option {string} - Type of the navigation event (beforefocus, notarget)
1057
+ * @param element {Node} - The target element of the event
1058
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1059
+ */
1060
+ function createSpatNavEvents(eventType, containerElement, currentElement, direction) {
1061
+ if (['beforefocus', 'notarget'].includes(eventType)) {
1062
+ const data = {
1063
+ causedTarget: currentElement,
1064
+ dir: direction
1065
+ };
1066
+ const triggeredEvent = new CustomEvent('nav' + eventType, {bubbles: true, cancelable: true, detail: data});
1067
+ return containerElement.dispatchEvent(triggeredEvent);
1068
+ }
1069
+ }
1070
+
1071
+ /**
1072
+ * Get the value of the CSS custom property of the element
1073
+ * @function readCssVar
1074
+ * @param element {Node}
1075
+ * @param varName {string} - The name of the css custom property without '--'
1076
+ * @returns {string} The value of the css custom property
1077
+ */
1078
+ function readCssVar(element, varName) {
1079
+ // 20210606 fix getPropertyValue returning null ~inf
1080
+ return (element.style.getPropertyValue(`--${varName}`) || '').trim();
1081
+ }
1082
+
1083
+ /**
1084
+ * Decide whether or not the 'contain' value is given to 'spatial-navigation-contain' css property of an element
1085
+ * @function isCSSSpatNavContain
1086
+ * @param element {Node}
1087
+ * @returns {boolean}
1088
+ */
1089
+ function isCSSSpatNavContain(element) {
1090
+ return readCssVar(element, 'spatial-navigation-contain') === 'contain';
1091
+ }
1092
+
1093
+ /**
1094
+ * Return the value of 'spatial-navigation-action' css property of an element
1095
+ * @function getCSSSpatNavAction
1096
+ * @param element {Node} - would be the spatial navigation container
1097
+ * @returns {string} auto | focus | scroll
1098
+ */
1099
+ function getCSSSpatNavAction(element) {
1100
+ return readCssVar(element, 'spatial-navigation-action') || 'auto';
1101
+ }
1102
+
1103
+ /**
1104
+ * Only move the focus with spatial navigation. Manually scrolling isn't available.
1105
+ * @function navigateChain
1106
+ * @param eventTarget {Node} - currently focused element
1107
+ * @param container {SpatialNavigationContainer} - container
1108
+ * @param parentContainer {SpatialNavigationContainer} - parent container
1109
+ * @param option - visible || all
1110
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1111
+ */
1112
+ function navigateChain(eventTarget, container, parentContainer, dir, option) {
1113
+ let currentOption = {candidates: getSpatialNavigationCandidates(container, {mode: option}), container};
1114
+
1115
+ while (parentContainer) {
1116
+ if (focusingController(eventTarget.spatialNavigationSearch(dir, currentOption), dir)) {
1117
+ return;
1118
+ } else {
1119
+ if ((option === 'visible') && scrollingController(container, dir)) return;
1120
+ else {
1121
+ if (!createSpatNavEvents('notarget', container, eventTarget, dir)) return;
1122
+
1123
+ // find the container
1124
+ if (container === document || container === document.documentElement) {
1125
+ if ( window.location !== window.parent.location ) {
1126
+ // The page is in an iframe. eventTarget needs to be reset because the position of the element in the iframe
1127
+ eventTarget = window.frameElement;
1128
+ container = eventTarget.ownerDocument.documentElement;
1129
+ }
1130
+ } else {
1131
+ container = parentContainer;
1132
+ }
1133
+ currentOption = {candidates: getSpatialNavigationCandidates(container, {mode: option}), container};
1134
+ let nextContainer = container.getSpatialNavigationContainer();
1135
+
1136
+ if (nextContainer !== container) {
1137
+ parentContainer = nextContainer;
1138
+ } else {
1139
+ parentContainer = null;
1140
+ }
1141
+ }
1142
+ }
1143
+ }
1144
+
1145
+ currentOption = {candidates: getSpatialNavigationCandidates(container, {mode: option}), container};
1146
+
1147
+ // Behavior after 'navnotarget' - Getting out from the current spatnav container
1148
+ if ((!parentContainer && container) && focusingController(eventTarget.spatialNavigationSearch(dir, currentOption), dir)) return;
1149
+
1150
+ if (!createSpatNavEvents('notarget', currentOption.container, eventTarget, dir)) return;
1151
+
1152
+ if ((getCSSSpatNavAction(container) === 'auto') && (option === 'visible')) {
1153
+ if (scrollingController(container, dir)) return;
1154
+ }
1155
+ }
1156
+
1157
+ /**
1158
+ * Find search origin
1159
+ * @see {@link https://drafts.csswg.org/css-nav-1/#nav}
1160
+ * @function findSearchOrigin
1161
+ * @returns {Node} The search origin for the spatial navigation
1162
+ */
1163
+ function findSearchOrigin() {
1164
+ let searchOrigin = document.activeElement;
1165
+
1166
+ if (!searchOrigin || (searchOrigin === document.body && !document.querySelector(':focus'))) {
1167
+ // When the previous search origin lost its focus by blur: (1) disable attribute (2) visibility: hidden
1168
+ if (savedSearchOrigin.element && (searchOrigin !== savedSearchOrigin.element)) {
1169
+ const elementStyle = window.getComputedStyle(savedSearchOrigin.element, null);
1170
+ const invisibleStyle = ['hidden', 'collapse'];
1171
+
1172
+ if (savedSearchOrigin.element.disabled || invisibleStyle.includes(elementStyle.getPropertyValue('visibility'))) {
1173
+ searchOrigin = savedSearchOrigin.element;
1174
+ return searchOrigin;
1175
+ }
1176
+ }
1177
+ searchOrigin = document.documentElement;
1178
+ }
1179
+ // When the previous search origin lost its focus by blur: (1) display:none () element size turned into zero
1180
+ if (savedSearchOrigin.element &&
1181
+ ((getBoundingClientRect(savedSearchOrigin.element).height === 0) || (getBoundingClientRect(savedSearchOrigin.element).width === 0))) {
1182
+ searchOriginRect = savedSearchOrigin.rect;
1183
+ }
1184
+
1185
+ if (!isVisibleInScroller(searchOrigin)) {
1186
+ const scroller = getScrollContainer(searchOrigin);
1187
+ if (scroller && ((scroller === window) || (getCSSSpatNavAction(scroller) === 'auto')))
1188
+ return scroller;
1189
+ }
1190
+ return searchOrigin;
1191
+ }
1192
+
1193
+ /**
1194
+ * Move the scroll of an element depending on the given spatial navigation directrion
1195
+ * (Assume that User Agent defined distance is '40px')
1196
+ * @see {@link https://drafts.csswg.org/css-nav-1/#directionally-scroll-an-element}
1197
+ * @function moveScroll
1198
+ * @param element {Node} - The scrollable element
1199
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1200
+ * @param offset {Number} - The explicit amount of offset for scrolling. Default value is 0.
1201
+ */
1202
+ function moveScroll(element, dir, offset = 0) {
1203
+ if (element) {
1204
+ switch (dir) {
1205
+ case 'left': element.scrollLeft -= (40 + offset); break;
1206
+ case 'right': element.scrollLeft += (40 + offset); break;
1207
+ case 'up': element.scrollTop -= (40 + offset); break;
1208
+ case 'down': element.scrollTop += (40 + offset); break;
1209
+ }
1210
+ }
1211
+ }
1212
+
1213
+ /**
1214
+ * Decide whether an element is container or not.
1215
+ * @function isContainer
1216
+ * @param element {Node} element
1217
+ * @returns {boolean}
1218
+ */
1219
+ function isContainer(element) {
1220
+ return (!element.parentElement) ||
1221
+ (element.nodeName === 'IFRAME') ||
1222
+ (isScrollContainer(element)) ||
1223
+ (isCSSSpatNavContain(element));
1224
+ }
1225
+
1226
+ /**
1227
+ * Decide whether an element is delegable container or not.
1228
+ * NOTE: THIS IS NON-NORMATIVE API.
1229
+ * @function isDelegableContainer
1230
+ * @param element {Node} element
1231
+ * @returns {boolean}
1232
+ */
1233
+ function isDelegableContainer(element) {
1234
+ return readCssVar(element, 'spatial-navigation-contain') === 'delegable';
1235
+ }
1236
+
1237
+ /**
1238
+ * Decide whether an element is a scrollable container or not.
1239
+ * @see {@link https://drafts.csswg.org/css-overflow-3/#scroll-container}
1240
+ * @function isScrollContainer
1241
+ * @param element {Node}
1242
+ * @returns {boolean}
1243
+ */
1244
+ function isScrollContainer(element) {
1245
+ const elementStyle = window.getComputedStyle(element, null);
1246
+ const overflowX = elementStyle.getPropertyValue('overflow-x');
1247
+ const overflowY = elementStyle.getPropertyValue('overflow-y');
1248
+
1249
+ return ((overflowX !== 'visible' && overflowX !== 'clip' && isOverflow(element, 'left')) ||
1250
+ (overflowY !== 'visible' && overflowY !== 'clip' && isOverflow(element, 'down'))) ?
1251
+ true : false;
1252
+ }
1253
+
1254
+ /**
1255
+ * Decide whether this element is scrollable or not.
1256
+ * NOTE: If the value of 'overflow' is given to either 'visible', 'clip', or 'hidden', the element isn't scrollable.
1257
+ * If the value is 'hidden', the element can be only programmically scrollable. (https://drafts.csswg.org/css-overflow-3/#valdef-overflow-hidden)
1258
+ * @function isScrollable
1259
+ * @param element {Node}
1260
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1261
+ * @returns {boolean}
1262
+ */
1263
+ function isScrollable(element, dir) { // element, dir
1264
+ if (element && typeof element === 'object') {
1265
+ if (dir && typeof dir === 'string') { // parameter: dir, element
1266
+ if (isOverflow(element, dir)) {
1267
+ // style property
1268
+ const elementStyle = window.getComputedStyle(element, null);
1269
+ const overflowX = elementStyle.getPropertyValue('overflow-x');
1270
+ const overflowY = elementStyle.getPropertyValue('overflow-y');
1271
+
1272
+ switch (dir) {
1273
+ case 'left':
1274
+ /* falls through */
1275
+ case 'right':
1276
+ return (overflowX !== 'visible' && overflowX !== 'clip' && overflowX !== 'hidden');
1277
+ case 'up':
1278
+ /* falls through */
1279
+ case 'down':
1280
+ return (overflowY !== 'visible' && overflowY !== 'clip' && overflowY !== 'hidden');
1281
+ }
1282
+ }
1283
+ return false;
1284
+ } else { // parameter: element
1285
+ return (element.nodeName === 'HTML' || element.nodeName === 'BODY') ||
1286
+ (isScrollContainer(element) && isOverflow(element));
1287
+ }
1288
+ }
1289
+ }
1290
+
1291
+ /**
1292
+ * Decide whether an element is overflow or not.
1293
+ * @function isOverflow
1294
+ * @param element {Node}
1295
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1296
+ * @returns {boolean}
1297
+ */
1298
+ function isOverflow(element, dir) {
1299
+ if (element && typeof element === 'object') {
1300
+ if (dir && typeof dir === 'string') { // parameter: element, dir
1301
+ switch (dir) {
1302
+ case 'left':
1303
+ /* falls through */
1304
+ case 'right':
1305
+ return (element.scrollWidth > element.clientWidth);
1306
+ case 'up':
1307
+ /* falls through */
1308
+ case 'down':
1309
+ return (element.scrollHeight > element.clientHeight);
1310
+ }
1311
+ } else { // parameter: element
1312
+ return (element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight);
1313
+ }
1314
+ return false;
1315
+ }
1316
+ }
1317
+
1318
+ /**
1319
+ * Decide whether the scrollbar of the browsing context reaches to the end or not.
1320
+ * @function isHTMLScrollBoundary
1321
+ * @param element {Node} - The top browsing context
1322
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1323
+ * @returns {boolean}
1324
+ */
1325
+ function isHTMLScrollBoundary(element, dir) {
1326
+ let result = false;
1327
+ switch (dir) {
1328
+ case 'left':
1329
+ result = element.scrollLeft === 0;
1330
+ break;
1331
+ case 'right':
1332
+ result = (element.scrollWidth - element.scrollLeft - element.clientWidth) === 0;
1333
+ break;
1334
+ case 'up':
1335
+ result = element.scrollTop === 0;
1336
+ break;
1337
+ case 'down':
1338
+ result = (element.scrollHeight - element.scrollTop - element.clientHeight) === 0;
1339
+ break;
1340
+ }
1341
+ return result;
1342
+ }
1343
+
1344
+ /**
1345
+ * Decide whether the scrollbar of an element reaches to the end or not.
1346
+ * @function isScrollBoundary
1347
+ * @param element {Node}
1348
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1349
+ * @returns {boolean}
1350
+ */
1351
+ function isScrollBoundary(element, dir) {
1352
+ if (isScrollable(element, dir)) {
1353
+ const winScrollY = element.scrollTop;
1354
+ const winScrollX = element.scrollLeft;
1355
+
1356
+ const height = element.scrollHeight - element.clientHeight;
1357
+ const width = element.scrollWidth - element.clientWidth;
1358
+
1359
+ switch (dir) {
1360
+ case 'left': return (winScrollX === 0);
1361
+ case 'right': return (Math.abs(winScrollX - width) <= 1);
1362
+ case 'up': return (winScrollY === 0);
1363
+ case 'down': return (Math.abs(winScrollY - height) <= 1);
1364
+ }
1365
+ }
1366
+ return false;
1367
+ }
1368
+
1369
+ /**
1370
+ * Decide whether an element is inside the scorller viewport or not
1371
+ *
1372
+ * @function isVisibleInScroller
1373
+ * @param element {Node}
1374
+ * @returns {boolean}
1375
+ */
1376
+ function isVisibleInScroller(element) {
1377
+ const elementRect = element.getBoundingClientRect();
1378
+ let nearestScroller = getScrollContainer(element);
1379
+
1380
+ let scrollerRect = null;
1381
+ if (nearestScroller !== window) {
1382
+ scrollerRect = getBoundingClientRect(nearestScroller);
1383
+ } else {
1384
+ scrollerRect = new DOMRect(0, 0, window.innerWidth, window.innerHeight);
1385
+ }
1386
+
1387
+ if (isInside(scrollerRect, elementRect, 'left') && isInside(scrollerRect, elementRect, 'down'))
1388
+ return true;
1389
+ else
1390
+ return false;
1391
+ }
1392
+
1393
+ /**
1394
+ * Decide whether an element is focusable for spatial navigation.
1395
+ * 1. If element is the browsing context (document, iframe), then it's focusable,
1396
+ * 2. If the element is scrollable container (regardless of scrollable axis), then it's focusable,
1397
+ * 3. The value of tabIndex >= 0, then it's focusable,
1398
+ * 4. If the element is disabled, it isn't focusable,
1399
+ * 5. If the element is expressly inert, it isn't focusable,
1400
+ * 6. Whether the element is being rendered or not.
1401
+ *
1402
+ * @function isFocusable
1403
+ * @param element {Node}
1404
+ * @returns {boolean}
1405
+ *
1406
+ * @see {@link https://html.spec.whatwg.org/multipage/interaction.html#focusable-area}
1407
+ */
1408
+ function isFocusable(element) {
1409
+ if ((element.tabIndex < 0) || isAtagWithoutHref(element) || isActuallyDisabled(element) || isExpresslyInert(element) || !isBeingRendered(element))
1410
+ return false;
1411
+ else if ((!element.parentElement) || (isScrollable(element) && isOverflow(element)) || (element.tabIndex >= 0))
1412
+ return true;
1413
+ }
1414
+
1415
+ /**
1416
+ * Decide whether an element is a tag without href attribute or not.
1417
+ *
1418
+ * @function isAtagWithoutHref
1419
+ * @param element {Node}
1420
+ * @returns {boolean}
1421
+ */
1422
+ function isAtagWithoutHref(element) {
1423
+ return (element.tagName === 'A' && element.getAttribute('href') === null && element.getAttribute('tabIndex') === null);
1424
+ }
1425
+
1426
+ /**
1427
+ * Decide whether an element is actually disabled or not.
1428
+ *
1429
+ * @function isActuallyDisabled
1430
+ * @param element {Node}
1431
+ * @returns {boolean}
1432
+ *
1433
+ * @see {@link https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled}
1434
+ */
1435
+ function isActuallyDisabled(element) {
1436
+ if (['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTGROUP', 'OPTION', 'FIELDSET'].includes(element.tagName))
1437
+ return (element.disabled);
1438
+ else
1439
+ return false;
1440
+ }
1441
+
1442
+ /**
1443
+ * Decide whether the element is expressly inert or not.
1444
+ * @see {@link https://html.spec.whatwg.org/multipage/interaction.html#expressly-inert}
1445
+ * @function isExpresslyInert
1446
+ * @param element {Node}
1447
+ * @returns {boolean}
1448
+ */
1449
+ function isExpresslyInert(element) {
1450
+ return ((element.inert) && (!element.ownerDocument.documentElement.inert));
1451
+ }
1452
+
1453
+ /**
1454
+ * Decide whether the element is being rendered or not.
1455
+ * 1. If an element has the style as "visibility: hidden | collapse" or "display: none", it is not being rendered.
1456
+ * 2. If an element has the style as "opacity: 0", it is not being rendered.(that is, invisible).
1457
+ * 3. If width and height of an element are explicitly set to 0, it is not being rendered.
1458
+ * 4. If a parent element is hidden, an element itself is not being rendered.
1459
+ * (CSS visibility property and display property are inherited.)
1460
+ * @see {@link https://html.spec.whatwg.org/multipage/rendering.html#being-rendered}
1461
+ * @function isBeingRendered
1462
+ * @param element {Node}
1463
+ * @returns {boolean}
1464
+ */
1465
+ function isBeingRendered(element) {
1466
+ if (!isVisibleStyleProperty(element.parentElement))
1467
+ return false;
1468
+ if (!isVisibleStyleProperty(element) || (element.style.opacity === '0') ||
1469
+ (window.getComputedStyle(element).height === '0px' || window.getComputedStyle(element).width === '0px'))
1470
+ return false;
1471
+ return true;
1472
+ }
1473
+
1474
+ /**
1475
+ * Decide whether this element is partially or completely visible to user agent.
1476
+ * @function isVisible
1477
+ * @param element {Node}
1478
+ * @returns {boolean}
1479
+ */
1480
+ function isVisible(element) {
1481
+ return (!element.parentElement) || (isVisibleStyleProperty(element) && hitTest(element));
1482
+ }
1483
+
1484
+ /**
1485
+ * Decide whether this element is completely visible in this viewport for the arrow direction.
1486
+ * @function isEntirelyVisible
1487
+ * @param element {Node}
1488
+ * @returns {boolean}
1489
+ */
1490
+ function isEntirelyVisible(element, container) {
1491
+ const rect = getBoundingClientRect(element);
1492
+ const containerElm = container || element.getSpatialNavigationContainer();
1493
+ const containerRect = getBoundingClientRect(containerElm);
1494
+
1495
+ // FIXME: when element is bigger than container?
1496
+ const entirelyVisible = !((rect.left < containerRect.left) ||
1497
+ (rect.right > containerRect.right) ||
1498
+ (rect.top < containerRect.top) ||
1499
+ (rect.bottom > containerRect.bottom));
1500
+
1501
+ return entirelyVisible;
1502
+ }
1503
+
1504
+ /**
1505
+ * Decide the style property of this element is specified whether it's visible or not.
1506
+ * @function isVisibleStyleProperty
1507
+ * @param element {CSSStyleDeclaration}
1508
+ * @returns {boolean}
1509
+ */
1510
+ function isVisibleStyleProperty(element) {
1511
+ const elementStyle = window.getComputedStyle(element, null);
1512
+ const thisVisibility = elementStyle.getPropertyValue('visibility');
1513
+ const thisDisplay = elementStyle.getPropertyValue('display');
1514
+ const invisibleStyle = ['hidden', 'collapse'];
1515
+
1516
+ return (thisDisplay !== 'none' && !invisibleStyle.includes(thisVisibility));
1517
+ }
1518
+
1519
+ /**
1520
+ * Decide whether this element is entirely or partially visible within the viewport.
1521
+ * @function hitTest
1522
+ * @param element {Node}
1523
+ * @returns {boolean}
1524
+ */
1525
+ function hitTest(element) {
1526
+ const elementRect = getBoundingClientRect(element);
1527
+ if (element.nodeName !== 'IFRAME' && (elementRect.top < 0 || elementRect.left < 0 ||
1528
+ elementRect.top > element.ownerDocument.documentElement.clientHeight || elementRect.left >element.ownerDocument.documentElement.clientWidth))
1529
+ return false;
1530
+
1531
+ let offsetX = parseInt(element.offsetWidth) / 10;
1532
+ let offsetY = parseInt(element.offsetHeight) / 10;
1533
+
1534
+ offsetX = isNaN(offsetX) ? 1 : offsetX;
1535
+ offsetY = isNaN(offsetY) ? 1 : offsetY;
1536
+
1537
+ const hitTestPoint = {
1538
+ // For performance, just using the three point(middle, leftTop, rightBottom) of the element for hit testing
1539
+ middle: [(elementRect.left + elementRect.right) / 2, (elementRect.top + elementRect.bottom) / 2],
1540
+ leftTop: [elementRect.left + offsetX, elementRect.top + offsetY],
1541
+ rightBottom: [elementRect.right - offsetX, elementRect.bottom - offsetY]
1542
+ };
1543
+
1544
+ for(const point in hitTestPoint) {
1545
+ const elemFromPoint = element.ownerDocument.elementFromPoint(...hitTestPoint[point]);
1546
+ if (element === elemFromPoint || element.contains(elemFromPoint)) {
1547
+ return true;
1548
+ }
1549
+ }
1550
+ return false;
1551
+ }
1552
+
1553
+ /**
1554
+ * Decide whether a child element is entirely or partially Included within container visually.
1555
+ * @function isInside
1556
+ * @param containerRect {DOMRect}
1557
+ * @param childRect {DOMRect}
1558
+ * @returns {boolean}
1559
+ */
1560
+ function isInside(containerRect, childRect) {
1561
+ const rightEdgeCheck = (containerRect.left <= childRect.right && containerRect.right >= childRect.right);
1562
+ const leftEdgeCheck = (containerRect.left <= childRect.left && containerRect.right >= childRect.left);
1563
+ const topEdgeCheck = (containerRect.top <= childRect.top && containerRect.bottom >= childRect.top);
1564
+ const bottomEdgeCheck = (containerRect.top <= childRect.bottom && containerRect.bottom >= childRect.bottom);
1565
+ return (rightEdgeCheck || leftEdgeCheck) && (topEdgeCheck || bottomEdgeCheck);
1566
+ }
1567
+
1568
+ /**
1569
+ * Decide whether this element is entirely or partially visible within the viewport.
1570
+ * Note: rect1 is outside of rect2 for the dir
1571
+ * @function isOutside
1572
+ * @param rect1 {DOMRect}
1573
+ * @param rect2 {DOMRect}
1574
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1575
+ * @returns {boolean}
1576
+ */
1577
+ function isOutside(rect1, rect2, dir) {
1578
+ switch (dir) {
1579
+ case 'left':
1580
+ return isRightSide(rect2, rect1);
1581
+ case 'right':
1582
+ return isRightSide(rect1, rect2);
1583
+ case 'up':
1584
+ return isBelow(rect2, rect1);
1585
+ case 'down':
1586
+ return isBelow(rect1, rect2);
1587
+ default:
1588
+ return false;
1589
+ }
1590
+ }
1591
+
1592
+ /* rect1 is right of rect2 */
1593
+ function isRightSide(rect1, rect2) {
1594
+ return rect1.left >= rect2.right || (rect1.left >= rect2.left && rect1.right > rect2.right && rect1.bottom > rect2.top && rect1.top < rect2.bottom);
1595
+ }
1596
+
1597
+ /* rect1 is below of rect2 */
1598
+ function isBelow(rect1, rect2) {
1599
+ return rect1.top >= rect2.bottom || (rect1.top >= rect2.top && rect1.bottom > rect2.bottom && rect1.left < rect2.right && rect1.right > rect2.left);
1600
+ }
1601
+
1602
+ /* rect1 is completely aligned or partially aligned for the direction */
1603
+ function isAligned(rect1, rect2, dir) {
1604
+ switch (dir) {
1605
+ case 'left' :
1606
+ /* falls through */
1607
+ case 'right' :
1608
+ return rect1.bottom > rect2.top && rect1.top < rect2.bottom;
1609
+ case 'up' :
1610
+ /* falls through */
1611
+ case 'down' :
1612
+ return rect1.right > rect2.left && rect1.left < rect2.right;
1613
+ default:
1614
+ return false;
1615
+ }
1616
+ }
1617
+
1618
+ /**
1619
+ * Get distance between the search origin and a candidate element along the direction when candidate element is inside the search origin.
1620
+ * @see {@link https://drafts.csswg.org/css-nav-1/#find-the-shortest-distance}
1621
+ * @function getDistanceFromPoint
1622
+ * @param point {Point} - The search origin
1623
+ * @param element {DOMRect} - A candidate element
1624
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1625
+ * @returns {Number} The euclidian distance between the spatial navigation container and an element inside it
1626
+ */
1627
+ function getDistanceFromPoint(point, element, dir) {
1628
+ point = startingPoint;
1629
+ // Get exit point, entry point -> {x: '', y: ''};
1630
+ const points = getEntryAndExitPoints(dir, point, element);
1631
+
1632
+ // Find the points P1 inside the border box of starting point and P2 inside the border box of candidate
1633
+ // that minimize the distance between these two points
1634
+ const P1 = Math.abs(points.entryPoint.x - points.exitPoint.x);
1635
+ const P2 = Math.abs(points.entryPoint.y - points.exitPoint.y);
1636
+
1637
+ // The result is euclidian distance between P1 and P2.
1638
+ return Math.sqrt(Math.pow(P1, 2) + Math.pow(P2, 2));
1639
+ }
1640
+
1641
+ /**
1642
+ * Get distance between the search origin and a candidate element along the direction when candidate element is inside the search origin.
1643
+ * @see {@link https://drafts.csswg.org/css-nav-1/#find-the-shortest-distance}
1644
+ * @function getInnerDistance
1645
+ * @param rect1 {DOMRect} - The search origin
1646
+ * @param rect2 {DOMRect} - A candidate element
1647
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1648
+ * @returns {Number} The euclidean distance between the spatial navigation container and an element inside it
1649
+ */
1650
+ function getInnerDistance(rect1, rect2, dir) {
1651
+ const baseEdgeForEachDirection = {left: 'right', right: 'left', up: 'bottom', down: 'top'};
1652
+ const baseEdge = baseEdgeForEachDirection[dir];
1653
+
1654
+ return Math.abs(rect1[baseEdge] - rect2[baseEdge]);
1655
+ }
1656
+
1657
+ /**
1658
+ * Get the distance between the search origin and a candidate element considering the direction.
1659
+ * @see {@link https://drafts.csswg.org/css-nav-1/#calculating-the-distance}
1660
+ * @function getDistance
1661
+ * @param searchOrigin {DOMRect | Point} - The search origin
1662
+ * @param candidateRect {DOMRect} - A candidate element
1663
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1664
+ * @returns {Number} The distance scoring between two elements
1665
+ */
1666
+ function getDistance(searchOrigin, candidateRect, dir) {
1667
+ const kOrthogonalWeightForLeftRight = 30;
1668
+ const kOrthogonalWeightForUpDown = 2;
1669
+
1670
+ let orthogonalBias = 0;
1671
+ let alignBias = 0;
1672
+ const alignWeight = 5.0;
1673
+
1674
+ // Get exit point, entry point -> {x: '', y: ''};
1675
+ const points = getEntryAndExitPoints(dir, searchOrigin, candidateRect);
1676
+
1677
+ // Find the points P1 inside the border box of starting point and P2 inside the border box of candidate
1678
+ // that minimize the distance between these two points
1679
+ const P1 = Math.abs(points.entryPoint.x - points.exitPoint.x);
1680
+ const P2 = Math.abs(points.entryPoint.y - points.exitPoint.y);
1681
+
1682
+ // A: The euclidean distance between P1 and P2.
1683
+ const A = Math.sqrt(Math.pow(P1, 2) + Math.pow(P2, 2));
1684
+ let B, C;
1685
+
1686
+ // B: The absolute distance in the direction which is orthogonal to dir between P1 and P2, or 0 if dir is null.
1687
+ // C: The intersection edges between a candidate and the starting point.
1688
+
1689
+ // D: The square root of the area of intersection between the border boxes of candidate and starting point
1690
+ const intersectionRect = getIntersectionRect(searchOrigin, candidateRect);
1691
+ const D = intersectionRect.area;
1692
+
1693
+ switch (dir) {
1694
+ case 'left':
1695
+ /* falls through */
1696
+ case 'right' :
1697
+ // If two elements are aligned, add align bias
1698
+ // else, add orthogonal bias
1699
+ if (isAligned(searchOrigin, candidateRect, dir))
1700
+ alignBias = Math.min(intersectionRect.height / searchOrigin.height , 1);
1701
+ else
1702
+ orthogonalBias = (searchOrigin.height / 2);
1703
+
1704
+ B = (P2 + orthogonalBias) * kOrthogonalWeightForLeftRight;
1705
+ C = alignWeight * alignBias;
1706
+ break;
1707
+
1708
+ case 'up' :
1709
+ /* falls through */
1710
+ case 'down' :
1711
+ // If two elements are aligned, add align bias
1712
+ // else, add orthogonal bias
1713
+ if (isAligned(searchOrigin, candidateRect, dir))
1714
+ alignBias = Math.min(intersectionRect.width / searchOrigin.width , 1);
1715
+ else
1716
+ orthogonalBias = (searchOrigin.width / 2);
1717
+
1718
+ B = (P1 + orthogonalBias) * kOrthogonalWeightForUpDown;
1719
+ C = alignWeight * alignBias;
1720
+ break;
1721
+
1722
+ default:
1723
+ B = 0;
1724
+ C = 0;
1725
+ break;
1726
+ }
1727
+
1728
+ return (A + B - C - D);
1729
+ }
1730
+
1731
+ /**
1732
+ * Get the euclidean distance between the search origin and a candidate element considering the direction.
1733
+ * @function getEuclideanDistance
1734
+ * @param rect1 {DOMRect} - The search origin
1735
+ * @param rect2 {DOMRect} - A candidate element
1736
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1737
+ * @returns {Number} The distance scoring between two elements
1738
+ */
1739
+ function getEuclideanDistance(rect1, rect2, dir) {
1740
+ // Get exit point, entry point
1741
+ const points = getEntryAndExitPoints(dir, rect1, rect2);
1742
+
1743
+ // Find the points P1 inside the border box of starting point and P2 inside the border box of candidate
1744
+ // that minimize the distance between these two points
1745
+ const P1 = Math.abs(points.entryPoint.x - points.exitPoint.x);
1746
+ const P2 = Math.abs(points.entryPoint.y - points.exitPoint.y);
1747
+
1748
+ // Return the euclidean distance between P1 and P2.
1749
+ return Math.sqrt(Math.pow(P1, 2) + Math.pow(P2, 2));
1750
+ }
1751
+
1752
+ /**
1753
+ * Get the absolute distance between the search origin and a candidate element considering the direction.
1754
+ * @function getAbsoluteDistance
1755
+ * @param rect1 {DOMRect} - The search origin
1756
+ * @param rect2 {DOMRect} - A candidate element
1757
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD)
1758
+ * @returns {Number} The distance scoring between two elements
1759
+ */
1760
+ function getAbsoluteDistance(rect1, rect2, dir) {
1761
+ // Get exit point, entry point
1762
+ const points = getEntryAndExitPoints(dir, rect1, rect2);
1763
+
1764
+ // Return the absolute distance in the dir direction between P1 and P.
1765
+ return ((dir === 'left') || (dir === 'right')) ?
1766
+ Math.abs(points.entryPoint.x - points.exitPoint.x) : Math.abs(points.entryPoint.y - points.exitPoint.y);
1767
+ }
1768
+
1769
+ /**
1770
+ * Get entry point and exit point of two elements considering the direction.
1771
+ * @function getEntryAndExitPoints
1772
+ * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD). Default value for dir is 'down'.
1773
+ * @param searchOrigin {DOMRect | Point} - The search origin which contains the exit point
1774
+ * @param candidateRect {DOMRect} - One of candidates which contains the entry point
1775
+ * @returns {Points} The exit point from the search origin and the entry point from a candidate
1776
+ */
1777
+ function getEntryAndExitPoints(dir = 'down', searchOrigin, candidateRect) {
1778
+ /**
1779
+ * User type definition for Point
1780
+ * @typeof {Object} Points
1781
+ * @property {Point} Points.entryPoint
1782
+ * @property {Point} Points.exitPoint
1783
+ */
1784
+ const points = {entryPoint: {x: 0, y: 0}, exitPoint:{x: 0, y: 0}};
1785
+
1786
+ if (startingPoint) {
1787
+ points.exitPoint = searchOrigin;
1788
+
1789
+ switch (dir) {
1790
+ case 'left':
1791
+ points.entryPoint.x = candidateRect.right;
1792
+ break;
1793
+ case 'up':
1794
+ points.entryPoint.y = candidateRect.bottom;
1795
+ break;
1796
+ case 'right':
1797
+ points.entryPoint.x = candidateRect.left;
1798
+ break;
1799
+ case 'down':
1800
+ points.entryPoint.y = candidateRect.top;
1801
+ break;
1802
+ }
1803
+
1804
+ // Set orthogonal direction
1805
+ switch (dir) {
1806
+ case 'left':
1807
+ case 'right':
1808
+ if (startingPoint.y <= candidateRect.top) {
1809
+ points.entryPoint.y = candidateRect.top;
1810
+ } else if (startingPoint.y < candidateRect.bottom) {
1811
+ points.entryPoint.y = startingPoint.y;
1812
+ } else {
1813
+ points.entryPoint.y = candidateRect.bottom;
1814
+ }
1815
+ break;
1816
+
1817
+ case 'up':
1818
+ case 'down':
1819
+ if (startingPoint.x <= candidateRect.left) {
1820
+ points.entryPoint.x = candidateRect.left;
1821
+ } else if (startingPoint.x < candidateRect.right) {
1822
+ points.entryPoint.x = startingPoint.x;
1823
+ } else {
1824
+ points.entryPoint.x = candidateRect.right;
1825
+ }
1826
+ break;
1827
+ }
1828
+ }
1829
+ else {
1830
+ // Set direction
1831
+ switch (dir) {
1832
+ case 'left':
1833
+ points.exitPoint.x = searchOrigin.left;
1834
+ points.entryPoint.x = (candidateRect.right < searchOrigin.left) ? candidateRect.right : searchOrigin.left;
1835
+ break;
1836
+ case 'up':
1837
+ points.exitPoint.y = searchOrigin.top;
1838
+ points.entryPoint.y = (candidateRect.bottom < searchOrigin.top) ? candidateRect.bottom : searchOrigin.top;
1839
+ break;
1840
+ case 'right':
1841
+ points.exitPoint.x = searchOrigin.right;
1842
+ points.entryPoint.x = (candidateRect.left > searchOrigin.right) ? candidateRect.left : searchOrigin.right;
1843
+ break;
1844
+ case 'down':
1845
+ points.exitPoint.y = searchOrigin.bottom;
1846
+ points.entryPoint.y = (candidateRect.top > searchOrigin.bottom) ? candidateRect.top : searchOrigin.bottom;
1847
+ break;
1848
+ }
1849
+
1850
+ // Set orthogonal direction
1851
+ switch (dir) {
1852
+ case 'left':
1853
+ case 'right':
1854
+ if (isBelow(searchOrigin, candidateRect)) {
1855
+ points.exitPoint.y = searchOrigin.top;
1856
+ points.entryPoint.y = (candidateRect.bottom < searchOrigin.top) ? candidateRect.bottom : searchOrigin.top;
1857
+ } else if (isBelow(candidateRect, searchOrigin)) {
1858
+ points.exitPoint.y = searchOrigin.bottom;
1859
+ points.entryPoint.y = (candidateRect.top > searchOrigin.bottom) ? candidateRect.top : searchOrigin.bottom;
1860
+ } else {
1861
+ points.exitPoint.y = Math.max(searchOrigin.top, candidateRect.top);
1862
+ points.entryPoint.y = points.exitPoint.y;
1863
+ }
1864
+ break;
1865
+
1866
+ case 'up':
1867
+ case 'down':
1868
+ if (isRightSide(searchOrigin, candidateRect)) {
1869
+ points.exitPoint.x = searchOrigin.left;
1870
+ points.entryPoint.x = (candidateRect.right < searchOrigin.left) ? candidateRect.right : searchOrigin.left;
1871
+ } else if (isRightSide(candidateRect, searchOrigin)) {
1872
+ points.exitPoint.x = searchOrigin.right;
1873
+ points.entryPoint.x = (candidateRect.left > searchOrigin.right) ? candidateRect.left : searchOrigin.right;
1874
+ } else {
1875
+ points.exitPoint.x = Math.max(searchOrigin.left, candidateRect.left);
1876
+ points.entryPoint.x = points.exitPoint.x;
1877
+ }
1878
+ break;
1879
+ }
1880
+ }
1881
+
1882
+ return points;
1883
+ }
1884
+
1885
+ /**
1886
+ * Find focusable elements within the container
1887
+ * @see {@link https://drafts.csswg.org/css-nav-1/#find-the-shortest-distance}
1888
+ * @function getIntersectionRect
1889
+ * @param rect1 {DOMRect} - The search origin which contains the exit point
1890
+ * @param rect2 {DOMRect} - One of candidates which contains the entry point
1891
+ * @returns {IntersectionArea} The intersection area between two elements.
1892
+ *
1893
+ * @typeof {Object} IntersectionArea
1894
+ * @property {Number} IntersectionArea.width
1895
+ * @property {Number} IntersectionArea.height
1896
+ */
1897
+ function getIntersectionRect(rect1, rect2) {
1898
+ const intersection_rect = {width: 0, height: 0, area: 0};
1899
+
1900
+ const new_location = [Math.max(rect1.left, rect2.left), Math.max(rect1.top, rect2.top)];
1901
+ const new_max_point = [Math.min(rect1.right, rect2.right), Math.min(rect1.bottom, rect2.bottom)];
1902
+
1903
+ intersection_rect.width = Math.abs(new_location[0] - new_max_point[0]);
1904
+ intersection_rect.height = Math.abs(new_location[1] - new_max_point[1]);
1905
+
1906
+ if (!(new_location[0] >= new_max_point[0] || new_location[1] >= new_max_point[1])) {
1907
+ // intersecting-cases
1908
+ intersection_rect.area = Math.sqrt(intersection_rect.width * intersection_rect.height);
1909
+ }
1910
+
1911
+ return intersection_rect;
1912
+ }
1913
+
1914
+ /**
1915
+ * Handle the spatial navigation behavior for HTMLInputElement, HTMLTextAreaElement
1916
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input|HTMLInputElement (MDN)}
1917
+ * @function handlingEditableElement
1918
+ * @param e {Event} - keydownEvent
1919
+ * @returns {boolean}
1920
+ */
1921
+ function handlingEditableElement(e) {
1922
+ const SPINNABLE_INPUT_TYPES = ['email', 'date', 'month', 'number', 'time', 'week'],
1923
+ TEXT_INPUT_TYPES = ['password', 'text', 'search', 'tel', 'url', null];
1924
+ const eventTarget = document.activeElement;
1925
+ const focusNavigableArrowKey = {left: false, up: false, right: false, down: false};
1926
+
1927
+ const dir = ARROW_KEY_CODE[e.keyCode];
1928
+ if (dir === undefined) {
1929
+ return focusNavigableArrowKey;
1930
+ }
1931
+
1932
+ if (SPINNABLE_INPUT_TYPES.includes(eventTarget.getAttribute('type')) &&
1933
+ (dir === 'up' || dir === 'down')) {
1934
+ focusNavigableArrowKey[dir] = true;
1935
+ } else if (TEXT_INPUT_TYPES.includes(eventTarget.getAttribute('type')) || eventTarget.nodeName === 'TEXTAREA') {
1936
+ // 20210606 fix selectionStart unavailable on checkboxes ~inf
1937
+ const startPosition = eventTarget.selectionStart;
1938
+ const endPosition = eventTarget.selectionEnd;
1939
+ if (startPosition === endPosition) { // if there isn't any selected text
1940
+ if (startPosition === 0) {
1941
+ focusNavigableArrowKey.left = true;
1942
+ focusNavigableArrowKey.up = true;
1943
+ }
1944
+ if (endPosition === eventTarget.value.length) {
1945
+ focusNavigableArrowKey.right = true;
1946
+ focusNavigableArrowKey.down = true;
1947
+ }
1948
+ }
1949
+ } else { // HTMLDataListElement, HTMLSelectElement, HTMLOptGroup
1950
+ focusNavigableArrowKey[dir] = true;
1951
+ }
1952
+
1953
+ return focusNavigableArrowKey;
1954
+ }
1955
+
1956
+ /**
1957
+ * Get the DOMRect of an element
1958
+ * @function getBoundingClientRect
1959
+ * @param {Node} element
1960
+ * @returns {DOMRect}
1961
+ */
1962
+ function getBoundingClientRect(element) {
1963
+ // memoization
1964
+ let rect = mapOfBoundRect && mapOfBoundRect.get(element);
1965
+ if (!rect) {
1966
+ const boundingClientRect = element.getBoundingClientRect();
1967
+ rect = {
1968
+ top: Number(boundingClientRect.top.toFixed(2)),
1969
+ right: Number(boundingClientRect.right.toFixed(2)),
1970
+ bottom: Number(boundingClientRect.bottom.toFixed(2)),
1971
+ left: Number(boundingClientRect.left.toFixed(2)),
1972
+ width: Number(boundingClientRect.width.toFixed(2)),
1973
+ height: Number(boundingClientRect.height.toFixed(2))
1974
+ };
1975
+ mapOfBoundRect && mapOfBoundRect.set(element, rect);
1976
+ }
1977
+ return rect;
1978
+ }
1979
+
1980
+ /**
1981
+ * Get the candidates which is fully inside the target element in visual
1982
+ * @param {Node} targetElement
1983
+ * @returns {sequence<Node>} overlappedCandidates
1984
+ */
1985
+ function getOverlappedCandidates(targetElement) {
1986
+ const container = targetElement.getSpatialNavigationContainer();
1987
+ const candidates = container.focusableAreas();
1988
+ const overlappedCandidates = [];
1989
+
1990
+ candidates.forEach(element => {
1991
+ if ((targetElement !== element) && isEntirelyVisible(element, targetElement)) {
1992
+ overlappedCandidates.push(element);
1993
+ }
1994
+ });
1995
+
1996
+ return overlappedCandidates;
1997
+ }
1998
+
1999
+ /**
2000
+ * Get the list of the experimental APIs
2001
+ * @function getExperimentalAPI
2002
+ */
2003
+ function getExperimentalAPI() {
2004
+ function canScroll(container, dir) {
2005
+ return (isScrollable(container, dir) && !isScrollBoundary(container, dir)) ||
2006
+ (!container.parentElement && !isHTMLScrollBoundary(container, dir));
2007
+ }
2008
+
2009
+ function findTarget(findCandidate, element, dir, option) {
2010
+ let eventTarget = element;
2011
+ let bestNextTarget = null;
2012
+
2013
+ // 4
2014
+ if (eventTarget === document || eventTarget === document.documentElement) {
2015
+ eventTarget = document.body || document.documentElement;
2016
+ }
2017
+
2018
+ // 5
2019
+ // At this point, spatialNavigationSearch can be applied.
2020
+ // If startingPoint is either a scroll container or the document,
2021
+ // find the best candidate within startingPoint
2022
+ if ((isContainer(eventTarget) || eventTarget.nodeName === 'BODY') && !(eventTarget.nodeName === 'INPUT')) {
2023
+ if (eventTarget.nodeName === 'IFRAME')
2024
+ eventTarget = eventTarget.contentDocument.body;
2025
+
2026
+ const candidates = getSpatialNavigationCandidates(eventTarget, option);
2027
+
2028
+ // 5-2
2029
+ if (Array.isArray(candidates) && candidates.length > 0) {
2030
+ return findCandidate ? getFilteredSpatialNavigationCandidates(eventTarget, dir, candidates) : eventTarget.spatialNavigationSearch(dir, {candidates});
2031
+ }
2032
+ if (canScroll(eventTarget, dir)) {
2033
+ return findCandidate ? [] : eventTarget;
2034
+ }
2035
+ }
2036
+
2037
+ // 6
2038
+ // Let container be the nearest ancestor of eventTarget
2039
+ let container = eventTarget.getSpatialNavigationContainer();
2040
+ let parentContainer = (container.parentElement) ? container.getSpatialNavigationContainer() : null;
2041
+
2042
+ // When the container is the viewport of a browsing context
2043
+ if (!parentContainer && ( window.location !== window.parent.location)) {
2044
+ parentContainer = window.parent.document.documentElement;
2045
+ }
2046
+
2047
+ // 7
2048
+ while (parentContainer) {
2049
+ const candidates = filteredCandidates(eventTarget, getSpatialNavigationCandidates(container, option), dir, container);
2050
+
2051
+ if (Array.isArray(candidates) && candidates.length > 0) {
2052
+ bestNextTarget = eventTarget.spatialNavigationSearch(dir, {candidates, container});
2053
+ if (bestNextTarget) {
2054
+ return findCandidate ? candidates : bestNextTarget;
2055
+ }
2056
+ }
2057
+
2058
+ // If there isn't any candidate and the best candidate among candidate:
2059
+ // 1) Scroll or 2) Find candidates of the ancestor container
2060
+ // 8 - if
2061
+ else if (canScroll(container, dir)) {
2062
+ return findCandidate ? [] : eventTarget;
2063
+ } else if (container === document || container === document.documentElement) {
2064
+ container = window.document.documentElement;
2065
+
2066
+ // The page is in an iframe
2067
+ if ( window.location !== window.parent.location ) {
2068
+ // eventTarget needs to be reset because the position of the element in the IFRAME
2069
+ // is unuseful when the focus moves out of the iframe
2070
+ eventTarget = window.frameElement;
2071
+ container = window.parent.document.documentElement;
2072
+ if (container.parentElement)
2073
+ parentContainer = container.getSpatialNavigationContainer();
2074
+ else {
2075
+ parentContainer = null;
2076
+ break;
2077
+ }
2078
+ }
2079
+ } else {
2080
+ // avoiding when spatnav container with tabindex=-1
2081
+ if (isFocusable(container)) {
2082
+ eventTarget = container;
2083
+ }
2084
+
2085
+ container = parentContainer;
2086
+ if (container.parentElement)
2087
+ parentContainer = container.getSpatialNavigationContainer();
2088
+ else {
2089
+ parentContainer = null;
2090
+ break;
2091
+ }
2092
+ }
2093
+ }
2094
+
2095
+ if (!parentContainer && container) {
2096
+ // Getting out from the current spatnav container
2097
+ const candidates = filteredCandidates(eventTarget, getSpatialNavigationCandidates(container, option), dir, container);
2098
+
2099
+ // 9
2100
+ if (Array.isArray(candidates) && candidates.length > 0) {
2101
+ bestNextTarget = eventTarget.spatialNavigationSearch(dir, {candidates, container});
2102
+ if (bestNextTarget) {
2103
+ return findCandidate ? candidates : bestNextTarget;
2104
+ }
2105
+ }
2106
+ }
2107
+
2108
+ if (canScroll(container, dir)) {
2109
+ bestNextTarget = eventTarget;
2110
+ return bestNextTarget;
2111
+ }
2112
+ }
2113
+
2114
+ return {
2115
+ isContainer,
2116
+ isScrollContainer,
2117
+ isVisibleInScroller,
2118
+ findCandidates: findTarget.bind(null, true),
2119
+ findNextTarget: findTarget.bind(null, false),
2120
+ getDistanceFromTarget: (element, candidateElement, dir) => {
2121
+ if ((isContainer(element) || element.nodeName === 'BODY') && !(element.nodeName === 'INPUT')) {
2122
+ if (getSpatialNavigationCandidates(element).includes(candidateElement)) {
2123
+ return getInnerDistance(getBoundingClientRect(element), getBoundingClientRect(candidateElement), dir);
2124
+ }
2125
+ }
2126
+ return getDistance(getBoundingClientRect(element), getBoundingClientRect(candidateElement), dir);
2127
+ }
2128
+ };
2129
+ }
2130
+
2131
+ /**
2132
+ * Makes to use the experimental APIs.
2133
+ * @function enableExperimentalAPIs
2134
+ * @param option {boolean} - If it is true, the experimental APIs can be used or it cannot.
2135
+ */
2136
+ function enableExperimentalAPIs (option) {
2137
+ const currentKeyMode = window.__spatialNavigation__ && window.__spatialNavigation__.keyMode;
2138
+ window.__spatialNavigation__ = (option === false) ? getInitialAPIs() : Object.assign(getInitialAPIs(), getExperimentalAPI());
2139
+ window.__spatialNavigation__.keyMode = currentKeyMode;
2140
+ Object.seal(window.__spatialNavigation__);
2141
+ }
2142
+
2143
+ /**
2144
+ * Set the environment for using the spatial navigation polyfill.
2145
+ * @function getInitialAPIs
2146
+ */
2147
+ function getInitialAPIs() {
2148
+ return {
2149
+ enableExperimentalAPIs,
2150
+ get keyMode() { return this._keymode ? this._keymode : 'ARROW'; },
2151
+ set keyMode(mode) { this._keymode = (['SHIFTARROW', 'ARROW', 'NONE'].includes(mode)) ? mode : 'ARROW'; },
2152
+ setStartingPoint: function (x, y) {startingPoint = (x && y) ? {x, y} : null;}
2153
+ };
2154
+ }
2155
+
2156
+ initiateSpatialNavigation();
2157
+ enableExperimentalAPIs(false);
2158
+
2159
+ window.addEventListener('load', () => {
2160
+ spatialNavigationHandler();
2161
+ });
2162
+ })();
2163
+ // The tiny-sha256 module, edited to export itself.
2164
+ var sha256 = function sha256(ascii) {
2165
+ function rightRotate(value, amount) {
2166
+ return (value >>> amount) | (value << (32 - amount));
2167
+ };
2168
+
2169
+ var mathPow = Math.pow;
2170
+ var maxWord = mathPow(2, 32);
2171
+ var lengthProperty = 'length';
2172
+ var i, j; // Used as a counter across the whole file
2173
+ var result = '';
2174
+
2175
+ var words = [];
2176
+ var asciiBitLength = ascii[lengthProperty] * 8;
2177
+
2178
+ //* caching results is optional - remove/add slash from front of this line to toggle
2179
+ // Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes
2180
+ // (we actually calculate the first 64, but extra values are just ignored)
2181
+ var hash = sha256.h = sha256.h || [];
2182
+ // Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes
2183
+ var k = sha256.k = sha256.k || [];
2184
+ var primeCounter = k[lengthProperty];
2185
+ /*/
2186
+ var hash = [], k = [];
2187
+ var primeCounter = 0;
2188
+ //*/
2189
+
2190
+ var isComposite = {};
2191
+ for (var candidate = 2; primeCounter < 64; candidate++) {
2192
+ if (!isComposite[candidate]) {
2193
+ for (i = 0; i < 313; i += candidate) {
2194
+ isComposite[i] = candidate;
2195
+ }
2196
+ hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0;
2197
+ k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;
2198
+ }
2199
+ }
2200
+
2201
+ ascii += '\x80'; // Append '1' bit (plus zero padding)
2202
+ while (ascii[lengthProperty] % 64 - 56) ascii += '\x00'; // More zero padding
2203
+ for (i = 0; i < ascii[lengthProperty]; i++) {
2204
+ j = ascii.charCodeAt(i);
2205
+ if (j >> 8) return; // ASCII check: only accept characters in range 0-255
2206
+ words[i >> 2] |= j << ((3 - i) % 4) * 8;
2207
+ }
2208
+ words[words[lengthProperty]] = ((asciiBitLength / maxWord) | 0);
2209
+ words[words[lengthProperty]] = (asciiBitLength)
2210
+
2211
+ // process each chunk
2212
+ for (j = 0; j < words[lengthProperty];) {
2213
+ var w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration
2214
+ var oldHash = hash;
2215
+ // This is now the "working hash", often labelled as variables a...g
2216
+ // (we have to truncate as well, otherwise extra entries at the end accumulate
2217
+ hash = hash.slice(0, 8);
2218
+
2219
+ for (i = 0; i < 64; i++) {
2220
+ var i2 = i + j;
2221
+ // Expand the message into 64 words
2222
+ // Used below if
2223
+ var w15 = w[i - 15], w2 = w[i - 2];
2224
+
2225
+ // Iterate
2226
+ var a = hash[0], e = hash[4];
2227
+ var temp1 = hash[7]
2228
+ + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1
2229
+ + ((e & hash[5]) ^ ((~e) & hash[6])) // ch
2230
+ + k[i]
2231
+ // Expand the message schedule if needed
2232
+ + (w[i] = (i < 16) ? w[i] : (
2233
+ w[i - 16]
2234
+ + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) // s0
2235
+ + w[i - 7]
2236
+ + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10)) // s1
2237
+ ) | 0
2238
+ );
2239
+ // This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble
2240
+ var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0
2241
+ + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); // maj
2242
+
2243
+ hash = [(temp1 + temp2) | 0].concat(hash); // We don't bother trimming off the extra ones, they're harmless as long as we're truncating when we do the slice()
2244
+ hash[4] = (hash[4] + temp1) | 0;
2245
+ }
2246
+
2247
+ for (i = 0; i < 8; i++) {
2248
+ hash[i] = (hash[i] + oldHash[i]) | 0;
2249
+ }
2250
+ }
2251
+
2252
+ for (i = 0; i < 8; i++) {
2253
+ for (j = 3; j + 1; j--) {
2254
+ var b = (hash[i] >> (j * 8)) & 255;
2255
+ result += ((b < 16) ? 0 : '') + b.toString(16);
2256
+ }
2257
+ }
2258
+ return result;
2259
+ };
2260
+
2261
+ export default sha256;
2262
+ import "./features/userAgentSpoofing.js";
2263
+ import "whatwg-fetch";
2264
+ import 'core-js/proposals/object-getownpropertydescriptors';
2265
+ import '@formatjs/intl-getcanonicallocales/polyfill.iife'
2266
+ import '@formatjs/intl-locale/polyfill.iife'
2267
+ import '@formatjs/intl-displaynames/polyfill.iife'
2268
+ import '@formatjs/intl-displaynames/locale-data/en';
2269
+
2270
+ import "./domrect-polyfill";
2271
+ import "./features/adblock.js";
2272
+ import "./features/sponsorblock.js";
2273
+ import "./ui/ui.js";
2274
+ import "./ui/speedUI.js";
2275
+ import "./ui/theme.js";
2276
+ import "./ui/settings.js";
2277
+ import "./ui/disableWhosWatching.js";
2278
+ import "./features/moreSubtitles.js";
2279
+ import "./features/updater.js";
2280
+ import "./features/pictureInPicture.js";
2281
+ import "./features/preferredVideoQuality.js";
2282
+ import "./features/videoQueuing.js";
2283
+ import "./features/enableFeatures.js";
2284
+ import "./ui/customUI.js";
2285
+ import "./ui/customGuideAction.js";