@chancestv/tv-focus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +27 -0
- package/README.md +52 -0
- package/dist/FocusLayer.vue.d.ts +33 -0
- package/dist/FocusSection.vue.d.ts +36 -0
- package/dist/Focusable.vue.d.ts +42 -0
- package/dist/core.d.ts +12 -0
- package/dist/engine/index.d.ts +5 -0
- package/dist/engine/spatial-navigation.d.ts +19 -0
- package/dist/engine/types.d.ts +72 -0
- package/dist/focus-layer-context.d.ts +10 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1351 -0
- package/dist/index.js.map +1 -0
- package/dist/keep-alive-bridge.d.ts +6 -0
- package/dist/key-source/native-event.d.ts +23 -0
- package/dist/layer-stack.d.ts +3 -0
- package/dist/section-registry.d.ts +8 -0
- package/dist/useFocusSection.d.ts +23 -0
- package/dist/useFocusable.d.ts +24 -0
- package/package.json +50 -0
- package/src/FocusLayer.vue +67 -0
- package/src/FocusSection.vue +38 -0
- package/src/Focusable.vue +41 -0
- package/src/core.ts +25 -0
- package/src/engine/ATTRIBUTION.md +37 -0
- package/src/engine/LICENSE +362 -0
- package/src/engine/index.ts +16 -0
- package/src/engine/spatial-navigation.ts +1249 -0
- package/src/engine/types.ts +85 -0
- package/src/focus-layer-context.ts +11 -0
- package/src/index.ts +34 -0
- package/src/keep-alive-bridge.ts +17 -0
- package/src/key-source/native-event.ts +124 -0
- package/src/layer-stack.ts +18 -0
- package/src/section-registry.ts +21 -0
- package/src/useFocusSection.ts +61 -0
- package/src/useFocusable.ts +82 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/engine/spatial-navigation.ts","../src/engine/index.ts","../src/core.ts","../src/key-source/native-event.ts","../src/section-registry.ts","../src/focus-layer-context.ts","../src/useFocusSection.ts","../src/useFocusable.ts","../src/keep-alive-bridge.ts","../src/layer-stack.ts","../src/Focusable.vue","../src/FocusSection.vue","../src/FocusLayer.vue"],"sourcesContent":["/*\n * A javascript-based implementation of Spatial Navigation.\n *\n * Original work: Copyright (c) 2022 Luke Chang.\n * https://github.com/luke-chang/js-spatial-navigation\n *\n * Modifications: fork 进 @shell/core/focus,TS 化、移除 jQuery 集成。\n * 详见 ATTRIBUTION.md。\n *\n * Licensed under the MPL 2.0.\n */\n// @ts-nocheck\n/* eslint-disable */\n\n// jQuery 集成已移除;保留 $ 作为常量 null,使原代码中 `if ($)` / `$ && ...` 分支天然短路\nvar $: any = null;\n\n /************************/\n /* Global Configuration */\n /************************/\n // Note: an <extSelector> can be one of following types:\n // - a valid selector string for \"querySelectorAll\" or jQuery (if it exists)\n // - a NodeList or an array containing DOM elements\n // - a single DOM element\n // - a jQuery object\n // - a string \"@<sectionId>\" to indicate the specified section\n // - a string \"@\" to indicate the default section\n var GlobalConfig = {\n selector: '', // can be a valid <extSelector> except \"@\" syntax.\n straightOnly: false,\n straightOverlapThreshold: 0.5,\n rememberSource: false,\n disabled: false,\n defaultElement: '', // <extSelector> except \"@\" syntax.\n enterTo: '', // '', 'last-focused', 'default-element'\n leaveFor: null, // {left: <extSelector>, right: <extSelector>,\n // up: <extSelector>, down: <extSelector>}\n restrict: 'self-first', // 'self-first', 'self-only', 'none'\n tabIndexIgnoreList:\n 'a, input, select, textarea, button, iframe, [contentEditable=true]',\n navigableFilter: null\n };\n\n /*********************/\n /* Constant Variable */\n /*********************/\n var KEYMAPPING = {\n '37': 'left',\n '38': 'up',\n '39': 'right',\n '40': 'down'\n };\n\n var REVERSE = {\n 'left': 'right',\n 'up': 'down',\n 'right': 'left',\n 'down': 'up'\n };\n\n var EVENT_PREFIX = 'sn:';\n var ID_POOL_PREFIX = 'section-';\n\n /********************/\n /* Private Variable */\n /********************/\n var _idPool = 0;\n var _ready = false;\n var _pause = false;\n var _sections = {};\n var _sectionCount = 0;\n var _defaultSectionId = '';\n var _lastSectionId = '';\n var _duringFocusChange = false;\n\n /************/\n /* Polyfill */\n /************/\n var elementMatchesSelector =\n Element.prototype.matches ||\n Element.prototype.matchesSelector ||\n Element.prototype.mozMatchesSelector ||\n Element.prototype.webkitMatchesSelector ||\n Element.prototype.msMatchesSelector ||\n Element.prototype.oMatchesSelector ||\n function (selector) {\n var matchedNodes =\n (this.parentNode || this.document).querySelectorAll(selector);\n return [].slice.call(matchedNodes).indexOf(this) >= 0;\n };\n\n /*****************/\n /* Core Function */\n /*****************/\n function getRect(elem) {\n var cr = elem.getBoundingClientRect();\n var rect = {\n left: cr.left,\n top: cr.top,\n right: cr.right,\n bottom: cr.bottom,\n width: cr.width,\n height: cr.height\n };\n rect.element = elem;\n rect.center = {\n x: rect.left + Math.floor(rect.width / 2),\n y: rect.top + Math.floor(rect.height / 2)\n };\n rect.center.left = rect.center.right = rect.center.x;\n rect.center.top = rect.center.bottom = rect.center.y;\n return rect;\n }\n\n function partition(rects, targetRect, straightOverlapThreshold) {\n var groups = [[], [], [], [], [], [], [], [], []];\n\n for (var i = 0; i < rects.length; i++) {\n var rect = rects[i];\n var center = rect.center;\n var x, y, groupId;\n\n if (center.x < targetRect.left) {\n x = 0;\n } else if (center.x <= targetRect.right) {\n x = 1;\n } else {\n x = 2;\n }\n\n if (center.y < targetRect.top) {\n y = 0;\n } else if (center.y <= targetRect.bottom) {\n y = 1;\n } else {\n y = 2;\n }\n\n groupId = y * 3 + x;\n groups[groupId].push(rect);\n\n if ([0, 2, 6, 8].indexOf(groupId) !== -1) {\n var threshold = straightOverlapThreshold;\n\n if (rect.left <= targetRect.right - targetRect.width * threshold) {\n if (groupId === 2) {\n groups[1].push(rect);\n } else if (groupId === 8) {\n groups[7].push(rect);\n }\n }\n\n if (rect.right >= targetRect.left + targetRect.width * threshold) {\n if (groupId === 0) {\n groups[1].push(rect);\n } else if (groupId === 6) {\n groups[7].push(rect);\n }\n }\n\n if (rect.top <= targetRect.bottom - targetRect.height * threshold) {\n if (groupId === 6) {\n groups[3].push(rect);\n } else if (groupId === 8) {\n groups[5].push(rect);\n }\n }\n\n if (rect.bottom >= targetRect.top + targetRect.height * threshold) {\n if (groupId === 0) {\n groups[3].push(rect);\n } else if (groupId === 2) {\n groups[5].push(rect);\n }\n }\n }\n }\n\n return groups;\n }\n\n function generateDistanceFunction(targetRect) {\n return {\n nearPlumbLineIsBetter: function(rect) {\n var d;\n if (rect.center.x < targetRect.center.x) {\n d = targetRect.center.x - rect.right;\n } else {\n d = rect.left - targetRect.center.x;\n }\n return d < 0 ? 0 : d;\n },\n nearHorizonIsBetter: function(rect) {\n var d;\n if (rect.center.y < targetRect.center.y) {\n d = targetRect.center.y - rect.bottom;\n } else {\n d = rect.top - targetRect.center.y;\n }\n return d < 0 ? 0 : d;\n },\n nearTargetLeftIsBetter: function(rect) {\n var d;\n if (rect.center.x < targetRect.center.x) {\n d = targetRect.left - rect.right;\n } else {\n d = rect.left - targetRect.left;\n }\n return d < 0 ? 0 : d;\n },\n nearTargetTopIsBetter: function(rect) {\n var d;\n if (rect.center.y < targetRect.center.y) {\n d = targetRect.top - rect.bottom;\n } else {\n d = rect.top - targetRect.top;\n }\n return d < 0 ? 0 : d;\n },\n topIsBetter: function(rect) {\n return rect.top;\n },\n bottomIsBetter: function(rect) {\n return -1 * rect.bottom;\n },\n leftIsBetter: function(rect) {\n return rect.left;\n },\n rightIsBetter: function(rect) {\n return -1 * rect.right;\n }\n };\n }\n\n function prioritize(priorities) {\n var destPriority = null;\n for (var i = 0; i < priorities.length; i++) {\n if (priorities[i].group.length) {\n destPriority = priorities[i];\n break;\n }\n }\n\n if (!destPriority) {\n return null;\n }\n\n var destDistance = destPriority.distance;\n\n destPriority.group.sort(function(a, b) {\n for (var i = 0; i < destDistance.length; i++) {\n var distance = destDistance[i];\n var delta = distance(a) - distance(b);\n if (delta) {\n return delta;\n }\n }\n return 0;\n });\n\n return destPriority.group;\n }\n\n // preferNearest=true:跨 section 查找,直线/斜向合并按距离竞争(就近原则)。\n // preferNearest=false(默认):section 内查找,保留上游分层(直线强于斜向)。\n function navigate(target, direction, candidates, config, preferNearest) {\n if (!target || !direction || !candidates || !candidates.length) {\n return null;\n }\n\n var rects = [];\n for (var i = 0; i < candidates.length; i++) {\n var rect = getRect(candidates[i]);\n if (rect) {\n rects.push(rect);\n }\n }\n if (!rects.length) {\n return null;\n }\n\n var targetRect = getRect(target);\n if (!targetRect) {\n return null;\n }\n\n var distanceFunction = generateDistanceFunction(targetRect);\n\n var groups = partition(\n rects,\n targetRect,\n config.straightOverlapThreshold\n );\n\n var internalGroups = partition(\n groups[4],\n targetRect.center,\n config.straightOverlapThreshold\n );\n\n var priorities;\n var df = distanceFunction;\n var internalGroup, straightGroup, diagonalGroups;\n var internalDist, straightDist, diagonalDist, mergedDist;\n\n switch (direction) {\n case 'left':\n internalGroup = internalGroups[0].concat(internalGroups[3])\n .concat(internalGroups[6]);\n straightGroup = groups[3];\n diagonalGroups = groups[0].concat(groups[6]);\n internalDist = [df.nearPlumbLineIsBetter, df.topIsBetter];\n straightDist = [df.nearPlumbLineIsBetter, df.topIsBetter];\n diagonalDist = [df.nearHorizonIsBetter, df.rightIsBetter,\n df.nearTargetTopIsBetter];\n // 合并时主轴=水平最近,次轴=垂直对齐\n mergedDist = [df.nearPlumbLineIsBetter, df.nearHorizonIsBetter,\n df.topIsBetter];\n break;\n case 'right':\n internalGroup = internalGroups[2].concat(internalGroups[5])\n .concat(internalGroups[8]);\n straightGroup = groups[5];\n diagonalGroups = groups[2].concat(groups[8]);\n internalDist = [df.nearPlumbLineIsBetter, df.topIsBetter];\n straightDist = [df.nearPlumbLineIsBetter, df.topIsBetter];\n diagonalDist = [df.nearHorizonIsBetter, df.leftIsBetter,\n df.nearTargetTopIsBetter];\n mergedDist = [df.nearPlumbLineIsBetter, df.nearHorizonIsBetter,\n df.topIsBetter];\n break;\n case 'up':\n internalGroup = internalGroups[0].concat(internalGroups[1])\n .concat(internalGroups[2]);\n straightGroup = groups[1];\n diagonalGroups = groups[0].concat(groups[2]);\n internalDist = [df.nearHorizonIsBetter, df.leftIsBetter];\n straightDist = [df.nearHorizonIsBetter, df.leftIsBetter];\n diagonalDist = [df.nearPlumbLineIsBetter, df.bottomIsBetter,\n df.nearTargetLeftIsBetter];\n // 合并时主轴=垂直最近,次轴=水平对齐\n mergedDist = [df.nearHorizonIsBetter, df.nearPlumbLineIsBetter,\n df.leftIsBetter];\n break;\n case 'down':\n internalGroup = internalGroups[6].concat(internalGroups[7])\n .concat(internalGroups[8]);\n straightGroup = groups[7];\n diagonalGroups = groups[6].concat(groups[8]);\n internalDist = [df.nearHorizonIsBetter, df.leftIsBetter];\n straightDist = [df.nearHorizonIsBetter, df.leftIsBetter];\n diagonalDist = [df.nearPlumbLineIsBetter, df.topIsBetter,\n df.nearTargetLeftIsBetter];\n mergedDist = [df.nearHorizonIsBetter, df.nearPlumbLineIsBetter,\n df.leftIsBetter];\n break;\n default:\n return null;\n }\n\n if (preferNearest) {\n // 跨 section:直线与斜向候选合并为一层按距离竞争,最近一行/一列胜出,\n // 修复「远处同列元素抢焦点」(如按下从按钮行越过相邻行跳到远处对齐元素)。\n priorities = [\n { group: internalGroup, distance: internalDist },\n {\n group: config.straightOnly\n ? straightGroup\n : straightGroup.concat(diagonalGroups),\n distance: mergedDist\n }\n ];\n } else {\n // section 内:保留上游分层(直线候选强于斜向),保证同行/同列相邻项优先,\n // 不被「不同行/不同列但像素更近」的元素抢走(如变宽卡片墙里同行相邻卡)。\n priorities = [\n { group: internalGroup, distance: internalDist },\n { group: straightGroup, distance: straightDist },\n { group: diagonalGroups, distance: diagonalDist }\n ];\n if (config.straightOnly) {\n priorities.pop();\n }\n }\n\n var destGroup = prioritize(priorities);\n if (!destGroup) {\n return null;\n }\n\n var dest = null;\n if (config.rememberSource &&\n config.previous &&\n config.previous.destination === target &&\n config.previous.reverse === direction) {\n for (var j = 0; j < destGroup.length; j++) {\n if (destGroup[j].element === config.previous.target) {\n dest = destGroup[j].element;\n break;\n }\n }\n }\n\n if (!dest) {\n dest = destGroup[0].element;\n }\n\n return dest;\n }\n\n /********************/\n /* Private Function */\n /********************/\n function generateId() {\n var id;\n while(true) {\n id = ID_POOL_PREFIX + String(++_idPool);\n if (!_sections[id]) {\n break;\n }\n }\n return id;\n }\n\n function parseSelector(selector) {\n var result = [];\n try {\n if (selector) {\n if ($) {\n result = $(selector).get();\n } else if (typeof selector === 'string') {\n result = [].slice.call(document.querySelectorAll(selector));\n } else if (typeof selector === 'object' && selector.length) {\n result = [].slice.call(selector);\n } else if (typeof selector === 'object' && selector.nodeType === 1) {\n result = [selector];\n }\n }\n } catch (err) {\n console.error(err);\n }\n return result;\n }\n\n function matchSelector(elem, selector) {\n if ($) {\n return $(elem).is(selector);\n } else if (typeof selector === 'string') {\n return elementMatchesSelector.call(elem, selector);\n } else if (typeof selector === 'object' && selector.length) {\n return selector.indexOf(elem) >= 0;\n } else if (typeof selector === 'object' && selector.nodeType === 1) {\n return elem === selector;\n }\n return false;\n }\n\n function getCurrentFocusedElement() {\n var activeElement = document.activeElement;\n if (activeElement && activeElement !== document.body) {\n return activeElement;\n }\n }\n\n function extend(out) {\n out = out || {};\n for (var i = 1; i < arguments.length; i++) {\n if (!arguments[i]) {\n continue;\n }\n for (var key in arguments[i]) {\n if (arguments[i].hasOwnProperty(key) &&\n arguments[i][key] !== undefined) {\n out[key] = arguments[i][key];\n }\n }\n }\n return out;\n }\n\n function exclude(elemList, excludedElem) {\n if (!Array.isArray(excludedElem)) {\n excludedElem = [excludedElem];\n }\n for (var i = 0, index; i < excludedElem.length; i++) {\n index = elemList.indexOf(excludedElem[i]);\n if (index >= 0) {\n elemList.splice(index, 1);\n }\n }\n return elemList;\n }\n\n function isNavigable(elem, sectionId, verifySectionSelector) {\n if (! elem || !sectionId ||\n !_sections[sectionId] || _sections[sectionId].disabled) {\n return false;\n }\n if ((elem.offsetWidth <= 0 && elem.offsetHeight <= 0) ||\n elem.hasAttribute('disabled')) {\n return false;\n }\n if (verifySectionSelector &&\n !matchSelector(elem, _sections[sectionId].selector)) {\n return false;\n }\n if (typeof _sections[sectionId].navigableFilter === 'function') {\n if (_sections[sectionId].navigableFilter(elem, sectionId) === false) {\n return false;\n }\n } else if (typeof GlobalConfig.navigableFilter === 'function') {\n if (GlobalConfig.navigableFilter(elem, sectionId) === false) {\n return false;\n }\n }\n return true;\n }\n\n function getSectionId(elem) {\n for (var id in _sections) {\n if (!_sections[id].disabled &&\n matchSelector(elem, _sections[id].selector)) {\n return id;\n }\n }\n }\n\n function getSectionNavigableElements(sectionId) {\n return parseSelector(_sections[sectionId].selector).filter(function(elem) {\n return isNavigable(elem, sectionId);\n });\n }\n\n function getSectionDefaultElement(sectionId) {\n var defaultElement = parseSelector(_sections[sectionId].defaultElement).find(function(elem) {\n return isNavigable(elem, sectionId, true);\n });\n if (!defaultElement) {\n return null;\n }\n return defaultElement;\n }\n\n function getSectionLastFocusedElement(sectionId) {\n var lastFocusedElement = _sections[sectionId].lastFocusedElement;\n if (!isNavigable(lastFocusedElement, sectionId, true)) {\n return null;\n }\n return lastFocusedElement;\n }\n\n function fireEvent(elem, type, details, cancelable) {\n if (arguments.length < 4) {\n cancelable = true;\n }\n var evt = document.createEvent('CustomEvent');\n evt.initCustomEvent(EVENT_PREFIX + type, true, cancelable, details);\n return elem.dispatchEvent(evt);\n }\n\n function focusElement(elem, sectionId, direction) {\n if (!elem) {\n return false;\n }\n\n var currentFocusedElement = getCurrentFocusedElement();\n\n var silentFocus = function() {\n if (currentFocusedElement) {\n currentFocusedElement.blur();\n }\n elem.focus();\n focusChanged(elem, sectionId);\n };\n\n if (_duringFocusChange) {\n silentFocus();\n return true;\n }\n\n _duringFocusChange = true;\n\n if (_pause) {\n silentFocus();\n _duringFocusChange = false;\n return true;\n }\n\n if (currentFocusedElement) {\n var unfocusProperties = {\n nextElement: elem,\n nextSectionId: sectionId,\n direction: direction,\n native: false\n };\n if (!fireEvent(currentFocusedElement, 'willunfocus', unfocusProperties)) {\n _duringFocusChange = false;\n return false;\n }\n currentFocusedElement.blur();\n fireEvent(currentFocusedElement, 'unfocused', unfocusProperties, false);\n }\n\n var focusProperties = {\n previousElement: currentFocusedElement,\n sectionId: sectionId,\n direction: direction,\n native: false\n };\n if (!fireEvent(elem, 'willfocus', focusProperties)) {\n _duringFocusChange = false;\n return false;\n }\n elem.focus();\n fireEvent(elem, 'focused', focusProperties, false);\n\n _duringFocusChange = false;\n\n focusChanged(elem, sectionId);\n return true;\n }\n\n function focusChanged(elem, sectionId) {\n if (!sectionId) {\n sectionId = getSectionId(elem);\n }\n if (sectionId) {\n _sections[sectionId].lastFocusedElement = elem;\n _lastSectionId = sectionId;\n }\n }\n\n function focusExtendedSelector(selector, direction) {\n if (selector.charAt(0) == '@') {\n if (selector.length == 1) {\n return focusSection();\n } else {\n var sectionId = selector.substr(1);\n return focusSection(sectionId);\n }\n } else {\n var next = parseSelector(selector)[0];\n if (next) {\n var nextSectionId = getSectionId(next);\n if (isNavigable(next, nextSectionId)) {\n return focusElement(next, nextSectionId, direction);\n }\n }\n }\n return false;\n }\n\n function focusSection(sectionId) {\n var range = [];\n var addRange = function(id) {\n if (id && range.indexOf(id) < 0 &&\n _sections[id] && !_sections[id].disabled) {\n range.push(id);\n }\n };\n\n if (sectionId) {\n addRange(sectionId);\n } else {\n addRange(_defaultSectionId);\n addRange(_lastSectionId);\n Object.keys(_sections).map(addRange);\n }\n\n for (var i = 0; i < range.length; i++) {\n var id = range[i];\n var next;\n\n if (_sections[id].enterTo == 'last-focused') {\n next = getSectionLastFocusedElement(id) ||\n getSectionDefaultElement(id) ||\n getSectionNavigableElements(id)[0];\n } else {\n next = getSectionDefaultElement(id) ||\n getSectionLastFocusedElement(id) ||\n getSectionNavigableElements(id)[0];\n }\n\n if (next) {\n return focusElement(next, id);\n }\n }\n\n return false;\n }\n\n function fireNavigatefailed(elem, direction) {\n fireEvent(elem, 'navigatefailed', {\n direction: direction\n }, false);\n }\n\n function gotoLeaveFor(sectionId, direction) {\n if (_sections[sectionId].leaveFor &&\n _sections[sectionId].leaveFor[direction] !== undefined) {\n var next = _sections[sectionId].leaveFor[direction];\n\n if (typeof next === 'string') {\n if (next === '') {\n return null;\n }\n return focusExtendedSelector(next, direction);\n }\n\n if ($ && next instanceof $) {\n next = next.get(0);\n }\n\n var nextSectionId = getSectionId(next);\n if (isNavigable(next, nextSectionId)) {\n return focusElement(next, nextSectionId, direction);\n }\n }\n return false;\n }\n\n // 元素 → 其最近「滚动/裁剪祖先」的缓存。容器关系在元素生命周期内稳定,\n // 低端盒子上每次按键都 walk + getComputedStyle 太贵,故 WeakMap 缓存(元素销毁自动回收)。\n var _scrollScopeCache = new WeakMap();\n\n // 从元素向上找第一个 overflow 非 visible 的祖先(auto/scroll/hidden/clip)。\n // 判断的是祖先而非元素/section 本身:5 个不可滚的子 section 会收敛到共同的可滚父/爷;\n // 容器外的固定页头(如返回)则落到别的祖先。无裁剪祖先时返回 null(全页同一平面)。\n function getScrollScope(elem) {\n if (_scrollScopeCache.has(elem)) {\n return _scrollScopeCache.get(elem);\n }\n var scope = null;\n var node = elem.parentElement;\n while (node && node !== document.documentElement) {\n var style = window.getComputedStyle(node);\n var ox = style.overflowX;\n var oy = style.overflowY;\n if (ox === 'auto' || ox === 'scroll' || ox === 'hidden' || ox === 'clip' ||\n oy === 'auto' || oy === 'scroll' || oy === 'hidden' || oy === 'clip') {\n scope = node;\n break;\n }\n node = node.parentElement;\n }\n _scrollScopeCache.set(elem, scope);\n return scope;\n }\n\n // 就近原则的「层级」延伸:跨 section 查找时,先只在与当前焦点同一滚动/裁剪容器内找\n // (哪怕候选已滚出视口),同容器该方向确实没有候选时,才允许跳到容器外的元素(如固定页头)。\n function navigateWithinScrollScope(target, direction, candidates, config, preferNearest) {\n if (!candidates || candidates.length < 2) {\n return navigate(target, direction, candidates, config, preferNearest);\n }\n var targetScope = getScrollScope(target);\n var inScope = [];\n var outScope = [];\n for (var i = 0; i < candidates.length; i++) {\n if (getScrollScope(candidates[i]) === targetScope) {\n inScope.push(candidates[i]);\n } else {\n outScope.push(candidates[i]);\n }\n }\n if (inScope.length && outScope.length) {\n return navigate(target, direction, inScope, config, preferNearest) ||\n navigate(target, direction, outScope, config, preferNearest);\n }\n return navigate(target, direction, candidates, config, preferNearest);\n }\n\n function focusNext(direction, currentFocusedElement, currentSectionId) {\n var extSelector =\n currentFocusedElement.getAttribute('data-sn-' + direction);\n if (typeof extSelector === 'string') {\n if (extSelector === '' ||\n !focusExtendedSelector(extSelector, direction)) {\n fireNavigatefailed(currentFocusedElement, direction);\n return false;\n }\n return true;\n }\n\n var sectionNavigableElements = {};\n var allNavigableElements = [];\n for (var id in _sections) {\n sectionNavigableElements[id] = getSectionNavigableElements(id);\n allNavigableElements =\n allNavigableElements.concat(sectionNavigableElements[id]);\n }\n\n var config = extend({}, GlobalConfig, _sections[currentSectionId]);\n var next;\n\n if (config.restrict == 'self-only' || config.restrict == 'self-first') {\n var currentSectionNavigableElements =\n sectionNavigableElements[currentSectionId];\n\n next = navigate(\n currentFocusedElement,\n direction,\n exclude(currentSectionNavigableElements, currentFocusedElement),\n config\n );\n\n if (!next && config.restrict == 'self-first') {\n // 离开本 section 跨区查找:就近原则 + 滚动容器感知\n next = navigateWithinScrollScope(\n currentFocusedElement,\n direction,\n exclude(allNavigableElements, currentSectionNavigableElements),\n config,\n true\n );\n }\n } else {\n // restrict:'none':全局单平面,保留上游分层(直线优先),仅叠加滚动容器感知\n next = navigateWithinScrollScope(\n currentFocusedElement,\n direction,\n exclude(allNavigableElements, currentFocusedElement),\n config,\n false\n );\n }\n\n if (next) {\n _sections[currentSectionId].previous = {\n target: currentFocusedElement,\n destination: next,\n reverse: REVERSE[direction]\n };\n\n var nextSectionId = getSectionId(next);\n\n if (currentSectionId != nextSectionId) {\n var result = gotoLeaveFor(currentSectionId, direction);\n if (result) {\n return true;\n } else if (result === null) {\n fireNavigatefailed(currentFocusedElement, direction);\n return false;\n }\n\n var enterToElement;\n switch (_sections[nextSectionId].enterTo) {\n case 'last-focused':\n enterToElement = getSectionLastFocusedElement(nextSectionId) ||\n getSectionDefaultElement(nextSectionId);\n break;\n case 'default-element':\n enterToElement = getSectionDefaultElement(nextSectionId);\n break;\n }\n if (enterToElement) {\n next = enterToElement;\n }\n }\n\n return focusElement(next, nextSectionId, direction);\n } else if (gotoLeaveFor(currentSectionId, direction)) {\n return true;\n }\n\n fireNavigatefailed(currentFocusedElement, direction);\n return false;\n }\n\n function onKeyDown(evt) {\n if (!_sectionCount || _pause ||\n evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) {\n return;\n }\n\n var currentFocusedElement;\n var preventDefault = function() {\n evt.preventDefault();\n evt.stopPropagation();\n return false;\n };\n\n var direction = KEYMAPPING[evt.keyCode];\n if (!direction) {\n if (evt.keyCode == 13) {\n currentFocusedElement = getCurrentFocusedElement();\n if (currentFocusedElement && getSectionId(currentFocusedElement)) {\n if (!fireEvent(currentFocusedElement, 'enter-down')) {\n return preventDefault();\n }\n }\n }\n return;\n }\n\n currentFocusedElement = getCurrentFocusedElement();\n\n if (!currentFocusedElement) {\n if (_lastSectionId) {\n currentFocusedElement = getSectionLastFocusedElement(_lastSectionId);\n }\n if (!currentFocusedElement) {\n focusSection();\n return preventDefault();\n }\n }\n\n var currentSectionId = getSectionId(currentFocusedElement);\n if (!currentSectionId) {\n return;\n }\n\n var willmoveProperties = {\n direction: direction,\n sectionId: currentSectionId,\n cause: 'keydown'\n };\n\n if (fireEvent(currentFocusedElement, 'willmove', willmoveProperties)) {\n focusNext(direction, currentFocusedElement, currentSectionId);\n }\n\n return preventDefault();\n }\n\n function onKeyUp(evt) {\n if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) {\n return;\n }\n if (!_pause && _sectionCount && evt.keyCode == 13) {\n var currentFocusedElement = getCurrentFocusedElement();\n if (currentFocusedElement && getSectionId(currentFocusedElement)) {\n if (!fireEvent(currentFocusedElement, 'enter-up')) {\n evt.preventDefault();\n evt.stopPropagation();\n }\n }\n }\n }\n\n function onFocus(evt) {\n var target = evt.target;\n if (target !== window && target !== document &&\n _sectionCount && !_duringFocusChange) {\n var sectionId = getSectionId(target);\n if (sectionId) {\n if (_pause) {\n focusChanged(target, sectionId);\n return;\n }\n\n var focusProperties = {\n sectionId: sectionId,\n native: true\n };\n\n if (!fireEvent(target, 'willfocus', focusProperties)) {\n _duringFocusChange = true;\n target.blur();\n _duringFocusChange = false;\n } else {\n fireEvent(target, 'focused', focusProperties, false);\n focusChanged(target, sectionId);\n }\n }\n }\n }\n\n function onBlur(evt) {\n var target = evt.target;\n if (target !== window && target !== document && !_pause &&\n _sectionCount && !_duringFocusChange && getSectionId(target)) {\n var unfocusProperties = {\n native: true\n };\n if (!fireEvent(target, 'willunfocus', unfocusProperties)) {\n _duringFocusChange = true;\n setTimeout(function() {\n target.focus();\n _duringFocusChange = false;\n });\n } else {\n fireEvent(target, 'unfocused', unfocusProperties, false);\n }\n }\n }\n\n /*******************/\n /* Public Function */\n /*******************/\n var SpatialNavigation = {\n init: function() {\n if (!_ready) {\n window.addEventListener('keydown', onKeyDown);\n window.addEventListener('keyup', onKeyUp);\n window.addEventListener('focus', onFocus, true);\n window.addEventListener('blur', onBlur, true);\n _ready = true;\n }\n },\n\n uninit: function() {\n window.removeEventListener('blur', onBlur, true);\n window.removeEventListener('focus', onFocus, true);\n window.removeEventListener('keyup', onKeyUp);\n window.removeEventListener('keydown', onKeyDown);\n SpatialNavigation.clear();\n _idPool = 0;\n _ready = false;\n },\n\n clear: function() {\n _sections = {};\n _sectionCount = 0;\n _defaultSectionId = '';\n _lastSectionId = '';\n _duringFocusChange = false;\n },\n\n // set(<config>);\n // set(<sectionId>, <config>);\n set: function() {\n var sectionId, config;\n\n if (typeof arguments[0] === 'object') {\n config = arguments[0];\n } else if (typeof arguments[0] === 'string' &&\n typeof arguments[1] === 'object') {\n sectionId = arguments[0];\n config = arguments[1];\n if (!_sections[sectionId]) {\n throw new Error('Section \"' + sectionId + '\" doesn\\'t exist!');\n }\n } else {\n return;\n }\n\n for (var key in config) {\n if (GlobalConfig[key] !== undefined) {\n if (sectionId) {\n _sections[sectionId][key] = config[key];\n } else if (config[key] !== undefined) {\n GlobalConfig[key] = config[key];\n }\n }\n }\n\n if (sectionId) {\n // remove \"undefined\" items\n _sections[sectionId] = extend({}, _sections[sectionId]);\n }\n },\n\n // add(<config>);\n // add(<sectionId>, <config>);\n add: function() {\n var sectionId;\n var config = {};\n\n if (typeof arguments[0] === 'object') {\n config = arguments[0];\n } else if (typeof arguments[0] === 'string' &&\n typeof arguments[1] === 'object') {\n sectionId = arguments[0];\n config = arguments[1];\n }\n\n if (!sectionId) {\n sectionId = (typeof config.id === 'string') ? config.id : generateId();\n }\n\n if (_sections[sectionId]) {\n throw new Error('Section \"' + sectionId + '\" has already existed!');\n }\n\n _sections[sectionId] = {};\n _sectionCount++;\n\n SpatialNavigation.set(sectionId, config);\n\n return sectionId;\n },\n\n remove: function(sectionId) {\n if (!sectionId || typeof sectionId !== 'string') {\n throw new Error('Please assign the \"sectionId\"!');\n }\n if (_sections[sectionId]) {\n _sections[sectionId] = undefined;\n _sections = extend({}, _sections);\n _sectionCount--;\n if (_lastSectionId === sectionId) {\n _lastSectionId = '';\n }\n return true;\n }\n return false;\n },\n\n disable: function(sectionId) {\n if (_sections[sectionId]) {\n _sections[sectionId].disabled = true;\n return true;\n }\n return false;\n },\n\n enable: function(sectionId) {\n if (_sections[sectionId]) {\n _sections[sectionId].disabled = false;\n return true;\n }\n return false;\n },\n\n pause: function() {\n _pause = true;\n },\n\n resume: function() {\n _pause = false;\n },\n\n // focus([silent])\n // focus(<sectionId>, [silent])\n // focus(<extSelector>, [silent])\n // Note: \"silent\" is optional and default to false\n focus: function(elem, silent) {\n var result = false;\n\n if (silent === undefined && typeof elem === 'boolean') {\n silent = elem;\n elem = undefined;\n }\n\n var autoPause = !_pause && silent;\n\n if (autoPause) {\n SpatialNavigation.pause();\n }\n\n if (!elem) {\n result = focusSection();\n } else {\n if (typeof elem === 'string') {\n if (_sections[elem]) {\n result = focusSection(elem);\n } else {\n result = focusExtendedSelector(elem);\n }\n } else {\n if ($ && elem instanceof $) {\n elem = elem.get(0);\n }\n\n var nextSectionId = getSectionId(elem);\n if (isNavigable(elem, nextSectionId)) {\n result = focusElement(elem, nextSectionId);\n }\n }\n }\n\n if (autoPause) {\n SpatialNavigation.resume();\n }\n\n return result;\n },\n\n // move(<direction>)\n // move(<direction>, <selector>)\n move: function(direction, selector) {\n direction = direction.toLowerCase();\n if (!REVERSE[direction]) {\n return false;\n }\n\n var elem = selector ?\n parseSelector(selector)[0] : getCurrentFocusedElement();\n if (!elem) {\n return false;\n }\n\n var sectionId = getSectionId(elem);\n if (!sectionId) {\n return false;\n }\n\n var willmoveProperties = {\n direction: direction,\n sectionId: sectionId,\n cause: 'api'\n };\n\n if (!fireEvent(elem, 'willmove', willmoveProperties)) {\n return false;\n }\n\n return focusNext(direction, elem, sectionId);\n },\n\n // makeFocusable()\n // makeFocusable(<sectionId>)\n makeFocusable: function(sectionId) {\n var doMakeFocusable = function(section) {\n var tabIndexIgnoreList = section.tabIndexIgnoreList !== undefined ?\n section.tabIndexIgnoreList : GlobalConfig.tabIndexIgnoreList;\n parseSelector(section.selector).forEach(function(elem) {\n if (!matchSelector(elem, tabIndexIgnoreList)) {\n if (!elem.getAttribute('tabindex')) {\n elem.setAttribute('tabindex', '-1');\n }\n }\n });\n };\n\n if (sectionId) {\n if (_sections[sectionId]) {\n doMakeFocusable(_sections[sectionId]);\n } else {\n throw new Error('Section \"' + sectionId + '\" doesn\\'t exist!');\n }\n } else {\n for (var id in _sections) {\n doMakeFocusable(_sections[id]);\n }\n }\n },\n\n setDefaultSection: function(sectionId) {\n if (!sectionId) {\n _defaultSectionId = '';\n } else if (!_sections[sectionId]) {\n throw new Error('Section \"' + sectionId + '\" doesn\\'t exist!');\n } else {\n _defaultSectionId = sectionId;\n }\n }\n };\n\n /**********************/\n /* ESM Export */\n /**********************/\n // 保留 window 挂载以兼容某些通过全局变量访问的场景\n if (typeof window !== 'undefined') {\n (window as any).SpatialNavigation = SpatialNavigation;\n }\n\nexport default SpatialNavigation;\n","/**\n * @shell/core/focus\n *\n * 框架无关的 TV 空间导航核心。\n *\n * 实现 fork 自 luke-chang/js-spatial-navigation (MPL 2.0),参见 ATTRIBUTION.md。\n */\nimport rawSpatialNavigation from './spatial-navigation'\nimport type { SpatialNavigationAPI } from './types'\n\n// spatial-navigation.ts 用了 @ts-nocheck,需要在出口显式声明类型给下游\nconst SpatialNavigation = rawSpatialNavigation as unknown as SpatialNavigationAPI\n\nexport default SpatialNavigation\nexport { SpatialNavigation }\nexport * from './types'\n","import SpatialNavigation from './engine'\nimport type { SectionConfig } from './engine'\n\nlet initialized = false\n\nexport interface SetupFocusOptions {\n /** 应用到 GlobalConfig 的默认配置(每个 section 可单独覆盖)*/\n defaults?: Partial<SectionConfig>\n}\n\n/**\n * 初始化全局 SpatialNavigation。多次调用幂等。\n *\n * 在应用启动(main.ts)调用一次即可。\n */\nexport function setupFocus(options: SetupFocusOptions = {}): void {\n if (initialized) return\n SpatialNavigation.init()\n if (options.defaults) {\n ;(SpatialNavigation as any).set(options.defaults)\n }\n initialized = true\n}\n\nexport { SpatialNavigation }\n","/**\n * 把 OTT 原生方向键事件(CustomEvent)转发为标准 keydown,\n * 让 @shell/core/focus 内置的 keydown 监听透明响应原生遥控器。\n *\n * 使用:在 main.ts 调一次即可:\n *\n * import { nativeKeyAdapter } from '@shell/core/focus'\n * nativeKeyAdapter('ott:native-keydown')\n *\n * 期望 CustomEvent.detail 形如 { key?, keyCode?, keyCodeString? } 之一。\n */\n\ninterface NativeKeyDetail {\n key?: string\n keyCode?: number\n keyCodeString?: string\n}\n\n// Android KeyEvent → DOM key 映射(覆盖最常见键)\nconst ANDROID_TO_KEY: Record<number, string> = {\n 19: 'ArrowUp',\n 20: 'ArrowDown',\n 21: 'ArrowLeft',\n 22: 'ArrowRight',\n 23: 'Enter',\n 66: 'Enter',\n 4: 'Escape',\n 67: 'Backspace',\n}\nconst KEY_STR_TO_KEY: Record<string, string> = {\n KEYCODE_DPAD_UP: 'ArrowUp',\n KEYCODE_DPAD_DOWN: 'ArrowDown',\n KEYCODE_DPAD_LEFT: 'ArrowLeft',\n KEYCODE_DPAD_RIGHT: 'ArrowRight',\n KEYCODE_DPAD_CENTER: 'Enter',\n KEYCODE_ENTER: 'Enter',\n KEYCODE_BACK: 'Escape',\n KEYCODE_DEL: 'Backspace',\n}\nconst KEY_TO_CODE: Record<string, number> = {\n ArrowUp: 38,\n ArrowDown: 40,\n ArrowLeft: 37,\n ArrowRight: 39,\n Enter: 13,\n Escape: 27,\n Backspace: 8,\n}\n\nfunction normalize(detail: NativeKeyDetail): { key: string; keyCode: number } | null {\n let key = detail.key\n if (!key && typeof detail.keyCodeString === 'string') {\n key = KEY_STR_TO_KEY[detail.keyCodeString]\n }\n if (!key && typeof detail.keyCode === 'number') {\n key = ANDROID_TO_KEY[detail.keyCode]\n }\n if (!key) return null\n const keyCode = KEY_TO_CODE[key] ?? 0\n return { key, keyCode }\n}\n\nlet attachedEventName: string | null = null\nlet handler: ((e: Event) => void) | null = null\n\n/**\n * 构造一个等价的 KeyboardEvent。\n *\n * 为什么不在构造器里传 keyCode/which:Blink 全版本(含 Chromium 53 / Chrome 66)的\n * KeyboardEvent 构造器都不读 KeyboardEventInit 里的 keyCode/which(它们是只读 legacy 属性,\n * 不在 init 字典内),构造产物 evt.keyCode 恒为 0。而 SpatialNavigation.onKeyDown/onKeyUp\n * 完全靠 evt.keyCode(37/38/39/40/13)路由方向键与确定键,keyCode=0 会导致全部失效。\n * 必须构造后用 Object.defineProperty 在实例上加 own 属性 shadow 掉原型 getter,强制写入 keyCode/which。\n */\nfunction makeKbEvent(type: 'keydown' | 'keyup', key: string, keyCode: number): KeyboardEvent {\n let evt: any\n try {\n evt = new KeyboardEvent(type, { key, bubbles: true, cancelable: true })\n } catch {\n // 极旧内核无 KeyboardEvent 构造器时兜底\n evt = document.createEvent('Event')\n evt.initEvent(type, true, true)\n Object.defineProperty(evt, 'key', { value: key, configurable: true })\n }\n Object.defineProperty(evt, 'keyCode', { value: keyCode, configurable: true })\n Object.defineProperty(evt, 'which', { value: keyCode, configurable: true })\n return evt as KeyboardEvent\n}\n\n/**\n * 注册原生事件名监听器(如 'ott:native-keydown'),把它转为 keydown + keyup 派发到 window。\n *\n * 为什么成对派发:Android 壳 dispatchKeyEvent 只在 ACTION_DOWN 透传 onNativeKeyDown,\n * 不发 ACTION_UP。但 focus-core 的 'enter-up' 事件挂在原生 keyup 上(spatial-navigation.ts onKeyUp),\n * EButton/useFocusable 又只监听 'sn:enter-up'。只派 keydown 会导致 OK 键完全失活。\n * 这里同步补一个 keyup,让 OTT 链路对外契约与浏览器键盘一致:\"按一次 = down+up 成对\"。\n *\n * 重复调用会先卸载上一个监听。\n */\nexport function nativeKeyAdapter(eventName: string): () => void {\n if (typeof window === 'undefined') return () => undefined\n // 卸载旧的\n if (attachedEventName && handler) {\n window.removeEventListener(attachedEventName, handler as EventListener)\n }\n attachedEventName = eventName\n handler = (e: Event) => {\n const detail = ((e as CustomEvent).detail || {}) as NativeKeyDetail\n const n = normalize(detail)\n if (!n) return\n window.dispatchEvent(makeKbEvent('keydown', n.key, n.keyCode))\n window.dispatchEvent(makeKbEvent('keyup', n.key, n.keyCode))\n }\n window.addEventListener(eventName, handler as EventListener)\n return () => {\n if (attachedEventName && handler) {\n window.removeEventListener(attachedEventName, handler as EventListener)\n attachedEventName = null\n handler = null\n }\n }\n}\n\nexport default nativeKeyAdapter\n","/**\n * 维护当前所有活跃 FocusSection 的 id 集合。\n * 上游 SpatialNavigation 未暴露 listSections,FocusLayer 进入/离开时需要批量 disable/enable\n * section,依赖此 registry 提供\"哪些 section 当前存在\"的信息。\n */\n\nconst activeSectionIds = new Set<string>()\n\nexport function registerSection(id: string): void {\n activeSectionIds.add(id)\n}\n\nexport function unregisterSection(id: string): void {\n activeSectionIds.delete(id)\n}\n\nexport function listSections(): string[] {\n const out: string[] = []\n activeSectionIds.forEach((id) => out.push(id))\n return out\n}\n","/**\n * FocusLayer 通过 provide 注入的上下文。\n * 内部 useFocusSection 通过 inject 拿到本对象,并把自己的 sectionId 注册进来,\n * 避免被 layer onMounted 时一并禁用。\n */\nexport interface FocusLayerContext {\n layerId: string\n registerInnerSection(sectionId: string): void\n}\n\nexport const FOCUS_LAYER_KEY: symbol = Symbol('dwy:focus-layer')\n","import { inject, onMounted, onUnmounted, provide } from 'vue'\nimport SpatialNavigation from './engine'\nimport type { Restrict, EnterTo, LeaveFor, ExtSelector } from './engine'\nimport { registerSection, unregisterSection } from './section-registry'\nimport { FOCUS_LAYER_KEY, type FocusLayerContext } from './focus-layer-context'\n\nlet sectionCounter = 0\n\nexport interface UseFocusSectionOptions {\n /** section id;未提供时自动生成 */\n id?: string\n /** 边界策略;默认 'self-first' */\n restrict?: Restrict\n /** 进入策略;默认 'last-focused' */\n enterTo?: EnterTo\n /** 跨方向跳转规则 */\n leaveFor?: LeaveFor | null\n /** 严格方向(无重叠时不跳)*/\n straightOnly?: boolean\n /** 离开后记忆来源焦点,默认 true */\n rememberSource?: boolean\n /** 进入 section 时默认聚焦的元素(CSS selector / Element) */\n defaultElement?: ExtSelector\n}\n\nexport interface FocusSectionContext {\n sectionId: string\n selectorAttr: string\n}\n\nexport const FOCUS_SECTION_KEY: symbol = Symbol('dwy:focus-section')\n\nexport function useFocusSection(options: UseFocusSectionOptions = {}): FocusSectionContext {\n const sectionId = options.id ?? `dwy-section-${++sectionCounter}`\n const selectorAttr = sectionId\n const enclosingLayer = inject<FocusLayerContext | null>(FOCUS_LAYER_KEY, null)\n\n onMounted(() => {\n ;(SpatialNavigation as any).add(sectionId, {\n selector: `[data-sn-section=\"${selectorAttr}\"]`,\n restrict: options.restrict ?? 'self-first',\n enterTo: options.enterTo ?? 'last-focused',\n leaveFor: options.leaveFor ?? null,\n straightOnly: options.straightOnly ?? false,\n rememberSource: options.rememberSource ?? true,\n defaultElement: options.defaultElement,\n })\n registerSection(sectionId)\n // 在 FocusLayer 内的 section 标记自己,避免被 layer 一并禁用\n if (enclosingLayer) enclosingLayer.registerInnerSection(sectionId)\n })\n\n onUnmounted(() => {\n ;(SpatialNavigation as any).remove(sectionId)\n unregisterSection(sectionId)\n })\n\n const ctx: FocusSectionContext = { sectionId, selectorAttr }\n provide(FOCUS_SECTION_KEY, ctx)\n return ctx\n}\n","import { getCurrentInstance, inject, onMounted, onUnmounted, ref, shallowRef } from 'vue'\nimport type { Ref } from 'vue'\nimport { FOCUS_SECTION_KEY, type FocusSectionContext } from './useFocusSection'\n\nexport interface UseFocusableOptions {\n /** 业务侧 key,会写到 data-focus-key,便于调试与脚本聚焦 */\n focusKey?: string\n /** Enter/OK 键回调 */\n onEnter?: () => void\n /** 获得焦点 */\n onFocus?: () => void\n /** 失去焦点 */\n onBlur?: () => void\n}\n\nexport interface UseFocusableResult {\n /** 绑定到 DOM 元素的 ref,必须接到 v-bind 或 ref=\"elRef\" 上 */\n elRef: Ref<HTMLElement | null>\n /** 响应式焦点状态 */\n focused: Ref<boolean>\n}\n\n/**\n * 把当前 DOM 元素注册为可聚焦项,自动归属到最近的 useFocusSection。\n *\n * 必须在 useFocusSection 提供的 provide 作用域内调用——\n * 通常表示组件树外层有 <FocusSection> 或调用过 useFocusSection。\n */\nexport function useFocusable(options: UseFocusableOptions = {}): UseFocusableResult {\n const elRef = shallowRef<HTMLElement | null>(null)\n const focused = ref(false)\n const ctx = inject<FocusSectionContext | null>(FOCUS_SECTION_KEY, null)\n\n function handleFocused() {\n focused.value = true\n if (options.onFocus) options.onFocus()\n }\n function handleUnfocused() {\n focused.value = false\n if (options.onBlur) options.onBlur()\n }\n function handleEnter() {\n if (options.onEnter) options.onEnter()\n }\n\n onMounted(() => {\n const el = elRef.value\n if (!el) {\n if (getCurrentInstance()) {\n console.warn('[dwy:focus] useFocusable 的 elRef 未绑定到任何元素')\n }\n return\n }\n if (ctx) {\n el.setAttribute('data-sn-section', ctx.selectorAttr)\n } else {\n console.warn('[dwy:focus] useFocusable 未找到外层 FocusSection;当前元素不会被纳入任何导航区域')\n }\n if (options.focusKey) {\n el.setAttribute('data-focus-key', options.focusKey)\n }\n if (el.tabIndex < 0 && !el.getAttribute('tabindex')) {\n el.setAttribute('tabindex', '-1')\n }\n el.addEventListener('sn:focused', handleFocused)\n el.addEventListener('sn:unfocused', handleUnfocused)\n el.addEventListener('sn:enter-up', handleEnter)\n // 注:子 Focusable 的 onMounted 早于父 FocusSection 的 onMounted,\n // 这里不再主动调用 makeFocusable —— tabindex 已在上一句自己加好,\n // SpatialNavigation 在 navigate 时会按 selector 重新 query 到本元素。\n })\n\n onUnmounted(() => {\n const el = elRef.value\n if (!el) return\n el.removeEventListener('sn:focused', handleFocused)\n el.removeEventListener('sn:unfocused', handleUnfocused)\n el.removeEventListener('sn:enter-up', handleEnter)\n })\n\n return { elRef, focused }\n}\n","import { getCurrentInstance, onActivated, onDeactivated } from 'vue'\nimport SpatialNavigation from './engine'\n\n/**\n * 在 Vue KeepAlive 缓存页面中使用:被切走时暂停 SpatialNavigation,被切回时恢复。\n *\n * 用法:在被 KeepAlive 包裹的 view 组件 setup 顶部调用一次。\n */\nexport function useKeepAliveFocus(): void {\n if (!getCurrentInstance()) return\n onActivated(() => {\n ;(SpatialNavigation as any).resume()\n })\n onDeactivated(() => {\n ;(SpatialNavigation as any).pause()\n })\n}\n","/**\n * 模态层计数器:FocusLayer 挂载 +1、卸载 -1。\n * 供 @shell/core 的 useBackButton 判断「当前有无打开的模态层」,\n * 有则把 Back 交给最上层弹层自己关,避免全局返回键误触发路由后退/退出。\n */\nlet openCount = 0\n\nexport function pushLayer(): void {\n openCount += 1\n}\n\nexport function popLayer(): void {\n openCount = Math.max(0, openCount - 1)\n}\n\nexport function hasOpenLayer(): boolean {\n return openCount > 0\n}\n","<template>\n <component\n :is=\"tag\"\n ref=\"elRef\"\n class=\"dwy-focusable\"\n :class=\"{ 'is-focused': focused }\"\n tabindex=\"-1\"\n >\n <slot :focused=\"focused\" />\n </component>\n</template>\n\n<script setup lang=\"ts\">\nimport { useFocusable } from './useFocusable'\n\ninterface Props {\n /** 业务侧 key,便于调试和脚本聚焦(写到 data-focus-key) */\n focusKey?: string\n /** 包装元素 tag,默认 div */\n tag?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n tag: 'div',\n})\n\nconst emit = defineEmits<{\n enter: []\n focus: []\n blur: []\n}>()\n\nconst { elRef, focused } = useFocusable({\n focusKey: props.focusKey,\n onEnter: () => emit('enter'),\n onFocus: () => emit('focus'),\n onBlur: () => emit('blur'),\n})\n\ndefineExpose({ elRef, focused })\n</script>\n","<template>\n <component :is=\"tag\" class=\"dwy-focus-section\" :data-sn-section-root=\"sectionId\">\n <slot :sectionId=\"sectionId\" />\n </component>\n</template>\n\n<script setup lang=\"ts\">\nimport { useFocusSection } from './useFocusSection'\nimport type { Restrict, EnterTo, LeaveFor } from './engine'\n\ninterface Props {\n id?: string\n restrict?: Restrict\n enterTo?: EnterTo\n leaveFor?: LeaveFor | null\n straightOnly?: boolean\n rememberSource?: boolean\n tag?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n restrict: 'self-first',\n enterTo: 'last-focused',\n leaveFor: null,\n straightOnly: false,\n rememberSource: true,\n tag: 'div',\n})\n\nconst { sectionId } = useFocusSection({\n id: props.id,\n restrict: props.restrict,\n enterTo: props.enterTo,\n leaveFor: props.leaveFor,\n straightOnly: props.straightOnly,\n rememberSource: props.rememberSource,\n})\n</script>\n","<template>\n <component :is=\"tag\" class=\"dwy-focus-layer\">\n <slot />\n </component>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, provide } from 'vue'\nimport SpatialNavigation from './engine'\nimport { listSections } from './section-registry'\nimport { FOCUS_LAYER_KEY, type FocusLayerContext } from './focus-layer-context'\nimport { pushLayer, popLayer } from './layer-stack'\n\n/**\n * 模态层:挂载时 disable layer 之外的所有 section、保存焦点;\n * 卸载时 enable 回来并恢复焦点。\n *\n * 通过 provide 暴露 FocusLayerContext,子树中的 useFocusSection 会\n * 把 sectionId 注册进来——避免子 section 被一并禁用。\n *\n * 子 section 的 mount 早于本组件 onMounted,因此 innerSectionIds 在\n * 我们 onMounted 时已经收集齐全。\n */\ninterface Props {\n id?: string\n tag?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), { tag: 'div' })\n\nconst innerSectionIds = new Set<string>()\nconst layerCtx: FocusLayerContext = {\n layerId: props.id ?? 'dwy-focus-layer',\n registerInnerSection(id: string) {\n innerSectionIds.add(id)\n },\n}\nprovide(FOCUS_LAYER_KEY, layerCtx)\n\nlet outerSections: string[] = []\nlet previousActiveElement: HTMLElement | null = null\n\nonMounted(() => {\n pushLayer()\n previousActiveElement = (document.activeElement as HTMLElement) ?? null\n outerSections = listSections().filter((id) => !innerSectionIds.has(id))\n outerSections.forEach((id) => {\n ;(SpatialNavigation as any).disable(id)\n })\n})\n\nonUnmounted(() => {\n popLayer()\n outerSections.forEach((id) => {\n ;(SpatialNavigation as any).enable(id)\n })\n outerSections = []\n if (previousActiveElement && typeof previousActiveElement.focus === 'function') {\n try {\n previousActiveElement.focus()\n } catch {\n /* ignore */\n }\n }\n previousActiveElement = null\n})\n</script>\n"],"names":["i","id","SpatialNavigation","rawSpatialNavigation","_a","_openBlock","_createBlock","_resolveDynamicComponent","_normalizeClass","_unref","_renderSlot"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把 OTT 原生方向键事件(CustomEvent)转发为标准 keydown,
|
|
3
|
+
* 让 @shell/core/focus 内置的 keydown 监听透明响应原生遥控器。
|
|
4
|
+
*
|
|
5
|
+
* 使用:在 main.ts 调一次即可:
|
|
6
|
+
*
|
|
7
|
+
* import { nativeKeyAdapter } from '@shell/core/focus'
|
|
8
|
+
* nativeKeyAdapter('ott:native-keydown')
|
|
9
|
+
*
|
|
10
|
+
* 期望 CustomEvent.detail 形如 { key?, keyCode?, keyCodeString? } 之一。
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* 注册原生事件名监听器(如 'ott:native-keydown'),把它转为 keydown + keyup 派发到 window。
|
|
14
|
+
*
|
|
15
|
+
* 为什么成对派发:Android 壳 dispatchKeyEvent 只在 ACTION_DOWN 透传 onNativeKeyDown,
|
|
16
|
+
* 不发 ACTION_UP。但 focus-core 的 'enter-up' 事件挂在原生 keyup 上(spatial-navigation.ts onKeyUp),
|
|
17
|
+
* EButton/useFocusable 又只监听 'sn:enter-up'。只派 keydown 会导致 OK 键完全失活。
|
|
18
|
+
* 这里同步补一个 keyup,让 OTT 链路对外契约与浏览器键盘一致:"按一次 = down+up 成对"。
|
|
19
|
+
*
|
|
20
|
+
* 重复调用会先卸载上一个监听。
|
|
21
|
+
*/
|
|
22
|
+
export declare function nativeKeyAdapter(eventName: string): () => void;
|
|
23
|
+
export default nativeKeyAdapter;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 维护当前所有活跃 FocusSection 的 id 集合。
|
|
3
|
+
* 上游 SpatialNavigation 未暴露 listSections,FocusLayer 进入/离开时需要批量 disable/enable
|
|
4
|
+
* section,依赖此 registry 提供"哪些 section 当前存在"的信息。
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerSection(id: string): void;
|
|
7
|
+
export declare function unregisterSection(id: string): void;
|
|
8
|
+
export declare function listSections(): string[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Restrict, EnterTo, LeaveFor, ExtSelector } from './engine';
|
|
2
|
+
export interface UseFocusSectionOptions {
|
|
3
|
+
/** section id;未提供时自动生成 */
|
|
4
|
+
id?: string;
|
|
5
|
+
/** 边界策略;默认 'self-first' */
|
|
6
|
+
restrict?: Restrict;
|
|
7
|
+
/** 进入策略;默认 'last-focused' */
|
|
8
|
+
enterTo?: EnterTo;
|
|
9
|
+
/** 跨方向跳转规则 */
|
|
10
|
+
leaveFor?: LeaveFor | null;
|
|
11
|
+
/** 严格方向(无重叠时不跳)*/
|
|
12
|
+
straightOnly?: boolean;
|
|
13
|
+
/** 离开后记忆来源焦点,默认 true */
|
|
14
|
+
rememberSource?: boolean;
|
|
15
|
+
/** 进入 section 时默认聚焦的元素(CSS selector / Element) */
|
|
16
|
+
defaultElement?: ExtSelector;
|
|
17
|
+
}
|
|
18
|
+
export interface FocusSectionContext {
|
|
19
|
+
sectionId: string;
|
|
20
|
+
selectorAttr: string;
|
|
21
|
+
}
|
|
22
|
+
export declare const FOCUS_SECTION_KEY: symbol;
|
|
23
|
+
export declare function useFocusSection(options?: UseFocusSectionOptions): FocusSectionContext;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
export interface UseFocusableOptions {
|
|
3
|
+
/** 业务侧 key,会写到 data-focus-key,便于调试与脚本聚焦 */
|
|
4
|
+
focusKey?: string;
|
|
5
|
+
/** Enter/OK 键回调 */
|
|
6
|
+
onEnter?: () => void;
|
|
7
|
+
/** 获得焦点 */
|
|
8
|
+
onFocus?: () => void;
|
|
9
|
+
/** 失去焦点 */
|
|
10
|
+
onBlur?: () => void;
|
|
11
|
+
}
|
|
12
|
+
export interface UseFocusableResult {
|
|
13
|
+
/** 绑定到 DOM 元素的 ref,必须接到 v-bind 或 ref="elRef" 上 */
|
|
14
|
+
elRef: Ref<HTMLElement | null>;
|
|
15
|
+
/** 响应式焦点状态 */
|
|
16
|
+
focused: Ref<boolean>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 把当前 DOM 元素注册为可聚焦项,自动归属到最近的 useFocusSection。
|
|
20
|
+
*
|
|
21
|
+
* 必须在 useFocusSection 提供的 provide 作用域内调用——
|
|
22
|
+
* 通常表示组件树外层有 <FocusSection> 或调用过 useFocusSection。
|
|
23
|
+
*/
|
|
24
|
+
export declare function useFocusable(options?: UseFocusableOptions): UseFocusableResult;
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chancestv/tv-focus",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TV spatial-navigation focus system for Vue 3, tuned for legacy WebView (Chromium 53+).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT AND MPL-2.0",
|
|
7
|
+
"author": "chances",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"tv",
|
|
10
|
+
"spatial-navigation",
|
|
11
|
+
"focus",
|
|
12
|
+
"vue3",
|
|
13
|
+
"remote-control",
|
|
14
|
+
"android-tv",
|
|
15
|
+
"webos",
|
|
16
|
+
"tizen",
|
|
17
|
+
"chromium-53"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"src",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"vue": "^3.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
|
39
|
+
"@vue/tsconfig": "^0.5.0",
|
|
40
|
+
"typescript": "^5.3.0",
|
|
41
|
+
"vite": "^5.0.0",
|
|
42
|
+
"vite-plugin-dts": "^4.2.0",
|
|
43
|
+
"vue": "^3.4.0",
|
|
44
|
+
"vue-tsc": "^2.0.8"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "vite build",
|
|
48
|
+
"typecheck": "vue-tsc --noEmit"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="tag" class="dwy-focus-layer">
|
|
3
|
+
<slot />
|
|
4
|
+
</component>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { onMounted, onUnmounted, provide } from 'vue'
|
|
9
|
+
import SpatialNavigation from './engine'
|
|
10
|
+
import { listSections } from './section-registry'
|
|
11
|
+
import { FOCUS_LAYER_KEY, type FocusLayerContext } from './focus-layer-context'
|
|
12
|
+
import { pushLayer, popLayer } from './layer-stack'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 模态层:挂载时 disable layer 之外的所有 section、保存焦点;
|
|
16
|
+
* 卸载时 enable 回来并恢复焦点。
|
|
17
|
+
*
|
|
18
|
+
* 通过 provide 暴露 FocusLayerContext,子树中的 useFocusSection 会
|
|
19
|
+
* 把 sectionId 注册进来——避免子 section 被一并禁用。
|
|
20
|
+
*
|
|
21
|
+
* 子 section 的 mount 早于本组件 onMounted,因此 innerSectionIds 在
|
|
22
|
+
* 我们 onMounted 时已经收集齐全。
|
|
23
|
+
*/
|
|
24
|
+
interface Props {
|
|
25
|
+
id?: string
|
|
26
|
+
tag?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const props = withDefaults(defineProps<Props>(), { tag: 'div' })
|
|
30
|
+
|
|
31
|
+
const innerSectionIds = new Set<string>()
|
|
32
|
+
const layerCtx: FocusLayerContext = {
|
|
33
|
+
layerId: props.id ?? 'dwy-focus-layer',
|
|
34
|
+
registerInnerSection(id: string) {
|
|
35
|
+
innerSectionIds.add(id)
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
provide(FOCUS_LAYER_KEY, layerCtx)
|
|
39
|
+
|
|
40
|
+
let outerSections: string[] = []
|
|
41
|
+
let previousActiveElement: HTMLElement | null = null
|
|
42
|
+
|
|
43
|
+
onMounted(() => {
|
|
44
|
+
pushLayer()
|
|
45
|
+
previousActiveElement = (document.activeElement as HTMLElement) ?? null
|
|
46
|
+
outerSections = listSections().filter((id) => !innerSectionIds.has(id))
|
|
47
|
+
outerSections.forEach((id) => {
|
|
48
|
+
;(SpatialNavigation as any).disable(id)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
onUnmounted(() => {
|
|
53
|
+
popLayer()
|
|
54
|
+
outerSections.forEach((id) => {
|
|
55
|
+
;(SpatialNavigation as any).enable(id)
|
|
56
|
+
})
|
|
57
|
+
outerSections = []
|
|
58
|
+
if (previousActiveElement && typeof previousActiveElement.focus === 'function') {
|
|
59
|
+
try {
|
|
60
|
+
previousActiveElement.focus()
|
|
61
|
+
} catch {
|
|
62
|
+
/* ignore */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
previousActiveElement = null
|
|
66
|
+
})
|
|
67
|
+
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="tag" class="dwy-focus-section" :data-sn-section-root="sectionId">
|
|
3
|
+
<slot :sectionId="sectionId" />
|
|
4
|
+
</component>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { useFocusSection } from './useFocusSection'
|
|
9
|
+
import type { Restrict, EnterTo, LeaveFor } from './engine'
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
id?: string
|
|
13
|
+
restrict?: Restrict
|
|
14
|
+
enterTo?: EnterTo
|
|
15
|
+
leaveFor?: LeaveFor | null
|
|
16
|
+
straightOnly?: boolean
|
|
17
|
+
rememberSource?: boolean
|
|
18
|
+
tag?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
22
|
+
restrict: 'self-first',
|
|
23
|
+
enterTo: 'last-focused',
|
|
24
|
+
leaveFor: null,
|
|
25
|
+
straightOnly: false,
|
|
26
|
+
rememberSource: true,
|
|
27
|
+
tag: 'div',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const { sectionId } = useFocusSection({
|
|
31
|
+
id: props.id,
|
|
32
|
+
restrict: props.restrict,
|
|
33
|
+
enterTo: props.enterTo,
|
|
34
|
+
leaveFor: props.leaveFor,
|
|
35
|
+
straightOnly: props.straightOnly,
|
|
36
|
+
rememberSource: props.rememberSource,
|
|
37
|
+
})
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="tag"
|
|
4
|
+
ref="elRef"
|
|
5
|
+
class="dwy-focusable"
|
|
6
|
+
:class="{ 'is-focused': focused }"
|
|
7
|
+
tabindex="-1"
|
|
8
|
+
>
|
|
9
|
+
<slot :focused="focused" />
|
|
10
|
+
</component>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { useFocusable } from './useFocusable'
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
/** 业务侧 key,便于调试和脚本聚焦(写到 data-focus-key) */
|
|
18
|
+
focusKey?: string
|
|
19
|
+
/** 包装元素 tag,默认 div */
|
|
20
|
+
tag?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
24
|
+
tag: 'div',
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const emit = defineEmits<{
|
|
28
|
+
enter: []
|
|
29
|
+
focus: []
|
|
30
|
+
blur: []
|
|
31
|
+
}>()
|
|
32
|
+
|
|
33
|
+
const { elRef, focused } = useFocusable({
|
|
34
|
+
focusKey: props.focusKey,
|
|
35
|
+
onEnter: () => emit('enter'),
|
|
36
|
+
onFocus: () => emit('focus'),
|
|
37
|
+
onBlur: () => emit('blur'),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
defineExpose({ elRef, focused })
|
|
41
|
+
</script>
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import SpatialNavigation from './engine'
|
|
2
|
+
import type { SectionConfig } from './engine'
|
|
3
|
+
|
|
4
|
+
let initialized = false
|
|
5
|
+
|
|
6
|
+
export interface SetupFocusOptions {
|
|
7
|
+
/** 应用到 GlobalConfig 的默认配置(每个 section 可单独覆盖)*/
|
|
8
|
+
defaults?: Partial<SectionConfig>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 初始化全局 SpatialNavigation。多次调用幂等。
|
|
13
|
+
*
|
|
14
|
+
* 在应用启动(main.ts)调用一次即可。
|
|
15
|
+
*/
|
|
16
|
+
export function setupFocus(options: SetupFocusOptions = {}): void {
|
|
17
|
+
if (initialized) return
|
|
18
|
+
SpatialNavigation.init()
|
|
19
|
+
if (options.defaults) {
|
|
20
|
+
;(SpatialNavigation as any).set(options.defaults)
|
|
21
|
+
}
|
|
22
|
+
initialized = true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { SpatialNavigation }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Attribution
|
|
2
|
+
|
|
3
|
+
`@shell/core/focus` 的核心导航实现 fork 自 [luke-chang/js-spatial-navigation](https://github.com/luke-chang/js-spatial-navigation),遵循 **MPL 2.0** 许可证。
|
|
4
|
+
|
|
5
|
+
## 上游
|
|
6
|
+
|
|
7
|
+
- 仓库:https://github.com/luke-chang/js-spatial-navigation
|
|
8
|
+
- 来源 commit:`b3539758b31d4d5df40de5da5942a4bb92b947bc`(2022-03-02 _Bump copyright year to 2022_)
|
|
9
|
+
- 原始版权:Copyright (c) 2022 Luke Chang
|
|
10
|
+
- 许可证:Mozilla Public License 2.0(见 [LICENSE](./LICENSE))
|
|
11
|
+
|
|
12
|
+
## fork 后的修改
|
|
13
|
+
|
|
14
|
+
1. **移除 IIFE 与 jQuery 集成**:原 `;(function($) { ... })(window.jQuery)` 包装改为 ESM 模块;末尾的 `$.SpatialNavigation` / `$.fn.SpatialNavigation` jQuery 入口删除。
|
|
15
|
+
2. **顶部声明 `var $ = null`**:保留原代码中 4 处 `if ($)` / `$ && next instanceof $` 分支结构不变(短路为 false),无 jQuery 依赖。
|
|
16
|
+
3. **ESM 导出**:`export default SpatialNavigation`,原 `window.SpatialNavigation = SpatialNavigation` 作为兼容性副作用保留。
|
|
17
|
+
4. **CommonJS 兼容**:原 `module.exports = SpatialNavigation` 由 tsup 双格式构建产物覆盖。
|
|
18
|
+
5. **TypeScript 化**:增加 `// @ts-nocheck`,跳过严格类型检查;公共 API 类型定义见 `src/types.ts`。
|
|
19
|
+
6. **构建目标 Chromium 53**:tsup `target: 'chrome53'`,禁用 ES2015+ 语法降级。
|
|
20
|
+
7. **文件命名**:`spatial_navigation.js` → `spatial-navigation.ts`。
|
|
21
|
+
|
|
22
|
+
主流程算法(`partition` / `prioritize` / `navigate` / section 边界策略)**未修改**。
|
|
23
|
+
|
|
24
|
+
## MPL 2.0 合规
|
|
25
|
+
|
|
26
|
+
- 保留原 LICENSE 全文于 [LICENSE](./LICENSE)
|
|
27
|
+
- 保留原作者署名于源文件头部
|
|
28
|
+
- 修改后的源文件 `src/spatial-navigation.ts` 继续以 MPL 2.0 形式可被访问(本仓库为本地 monorepo,源码可读)
|
|
29
|
+
- 产物 banner 自动注入版权声明
|
|
30
|
+
|
|
31
|
+
## 后续维护
|
|
32
|
+
|
|
33
|
+
修改要点应记录在本文件下方变更日志:
|
|
34
|
+
|
|
35
|
+
### 变更日志
|
|
36
|
+
|
|
37
|
+
- 2026-05-19:初始 fork,TS 化 + 移除 jQuery + ESM 化
|