one-design-next 0.0.29 → 0.0.31
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/dist/composer/index.js +24 -14
- package/dist/date-picker/index.js +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/skill-slot/index.d.ts +16 -10
- package/dist/skill-slot/index.js +170 -15
- package/dist/skill-slot/style/index.css +10 -4
- package/package.json +1 -1
package/dist/composer/index.js
CHANGED
|
@@ -115,6 +115,8 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
115
115
|
_useState8 = _slicedToArray(_useState7, 2),
|
|
116
116
|
triggerActiveIndex = _useState8[0],
|
|
117
117
|
setTriggerActiveIndex = _useState8[1];
|
|
118
|
+
/** trigger menu 的命令式句柄:导航语义在 SkillSlot 内,composer 只转发 ↑↓。 */
|
|
119
|
+
var triggerSkillSlotRef = useRef(null);
|
|
118
120
|
/** lite 模式下由单行升格到多行布局;粘性:只在 text 清空时复位 */
|
|
119
121
|
var _useState9 = useState(false),
|
|
120
122
|
_useState10 = _slicedToArray(_useState9, 2),
|
|
@@ -481,11 +483,16 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
481
483
|
* `@` 触发本轮不接(mention 数据源待业务接入),onTriggerChange 拿到也不弹。 */
|
|
482
484
|
var triggerMenuVisible = !!(triggerInfo && triggerInfo.trigger === '/' && skills && skills.length > 0);
|
|
483
485
|
|
|
484
|
-
/* query 变化或浮层关闭 →
|
|
486
|
+
/* query 变化或浮层关闭 → 高亮项重置到「最上方的可用项」(跳过 disabled)。
|
|
485
487
|
* 用 query 而不是 triggerInfo 整体当依赖:rect 变化(光标移位)不应该 reset。 */
|
|
486
488
|
useEffect(function () {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
+
var _ref3;
|
|
490
|
+
var list = (_ref3 = filteredSkills !== null && filteredSkills !== void 0 ? filteredSkills : skills) !== null && _ref3 !== void 0 ? _ref3 : [];
|
|
491
|
+
var first = list.findIndex(function (s) {
|
|
492
|
+
return !s.disabled;
|
|
493
|
+
});
|
|
494
|
+
setTriggerActiveIndex(first === -1 ? 0 : first);
|
|
495
|
+
}, [filteredSkills, skills, triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.query, triggerInfo == null]);
|
|
489
496
|
|
|
490
497
|
/* editor 在 trigger 激活时把 ↑↓Enter Esc Tab 丢过来。返回 true = editor 别管了。
|
|
491
498
|
* - ↑↓ 改高亮 index(夹在 [0, max] 内)
|
|
@@ -493,25 +500,25 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
493
500
|
* - Escape 关 menu,焦点保留在 editor */
|
|
494
501
|
var handleTriggerKeyDown = useCallback(function (e) {
|
|
495
502
|
if (!triggerMenuVisible) return false;
|
|
496
|
-
|
|
497
|
-
|
|
503
|
+
|
|
504
|
+
/* ↑↓ 导航完全委托给 SkillSlot(唯一事实源:跳过 disabled、夹边、滚动进视口),
|
|
505
|
+
* composer 焦点留在 editor,仅把按键转发进去。 */
|
|
498
506
|
if (e.key === 'ArrowDown') {
|
|
507
|
+
var _triggerSkillSlotRef$;
|
|
499
508
|
e.preventDefault();
|
|
500
|
-
|
|
501
|
-
return Math.min(i + 1, max);
|
|
502
|
-
});
|
|
509
|
+
(_triggerSkillSlotRef$ = triggerSkillSlotRef.current) === null || _triggerSkillSlotRef$ === void 0 || _triggerSkillSlotRef$.moveActive('down');
|
|
503
510
|
return true;
|
|
504
511
|
}
|
|
505
512
|
if (e.key === 'ArrowUp') {
|
|
513
|
+
var _triggerSkillSlotRef$2;
|
|
506
514
|
e.preventDefault();
|
|
507
|
-
|
|
508
|
-
return Math.max(i - 1, 0);
|
|
509
|
-
});
|
|
515
|
+
(_triggerSkillSlotRef$2 = triggerSkillSlotRef.current) === null || _triggerSkillSlotRef$2 === void 0 || _triggerSkillSlotRef$2.moveActive('up');
|
|
510
516
|
return true;
|
|
511
517
|
}
|
|
512
518
|
if (e.key === 'Enter' && !e.shiftKey || e.key === 'Tab') {
|
|
519
|
+
var _triggerSkillSlotRef$3, _triggerSkillSlotRef$4;
|
|
513
520
|
e.preventDefault();
|
|
514
|
-
var _skill =
|
|
521
|
+
var _skill = (_triggerSkillSlotRef$3 = (_triggerSkillSlotRef$4 = triggerSkillSlotRef.current) === null || _triggerSkillSlotRef$4 === void 0 ? void 0 : _triggerSkillSlotRef$4.getActiveSkill()) !== null && _triggerSkillSlotRef$3 !== void 0 ? _triggerSkillSlotRef$3 : filteredSkills === null || filteredSkills === void 0 ? void 0 : filteredSkills[triggerActiveIndex];
|
|
515
522
|
if (_skill) handleSelectSkill(_skill, 'trigger');
|
|
516
523
|
return true;
|
|
517
524
|
}
|
|
@@ -532,7 +539,9 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
532
539
|
var el = triggerAnchorRef.current;
|
|
533
540
|
if (!el || !triggerInfo) return;
|
|
534
541
|
el.style.left = "".concat(triggerInfo.rect.left, "px");
|
|
535
|
-
|
|
542
|
+
/* 锚到 caret 行顶部,配合 placement=topLeft 让 menu 浮在输入内容上方,
|
|
543
|
+
* 不遮挡正在输入的文字(空间不足时 Popover flip 会自动回落到下方)。 */
|
|
544
|
+
el.style.top = "".concat(triggerInfo.rect.top, "px");
|
|
536
545
|
}, [triggerInfo]);
|
|
537
546
|
|
|
538
547
|
/* ---- Derived ---- */
|
|
@@ -825,7 +834,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
825
834
|
var triggerMenu = skills && skills.length > 0 ? /*#__PURE__*/React.createElement(Popover, {
|
|
826
835
|
trigger: "click",
|
|
827
836
|
arrowed: false,
|
|
828
|
-
placement: "
|
|
837
|
+
placement: "topLeft",
|
|
829
838
|
offset: 4,
|
|
830
839
|
visible: triggerMenuVisible,
|
|
831
840
|
onVisibleChange: function onVisibleChange(v) {
|
|
@@ -837,6 +846,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
837
846
|
},
|
|
838
847
|
popupClassName: "odn-composer-skill-popup",
|
|
839
848
|
popup: /*#__PURE__*/React.createElement(SkillSlot, {
|
|
849
|
+
ref: triggerSkillSlotRef,
|
|
840
850
|
variant: "menu",
|
|
841
851
|
skills: filteredSkills !== null && filteredSkills !== void 0 ? filteredSkills : [],
|
|
842
852
|
activeIndex: triggerActiveIndex,
|
|
@@ -130,8 +130,9 @@ var DatePicker = function DatePicker(props) {
|
|
|
130
130
|
if (isSameDay(maxDateProp, thisQuarterEnd)) {
|
|
131
131
|
return thisQuarterEnd;
|
|
132
132
|
}
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
// 上一季度末 = 当前季度起始月的前一天,避免直接 setMonth(-3) 时
|
|
134
|
+
// 保留 day 数导致 Q2→Q1(30 vs 31) 或 Q4→Q3(31 溢出 Oct 1) 等错误
|
|
135
|
+
var prevQuarterEnd = new Date(thisQuarterEnd.getFullYear(), thisQuarterEnd.getMonth() - 2, 0);
|
|
135
136
|
return prevQuarterEnd;
|
|
136
137
|
}
|
|
137
138
|
if (picker === 'month') {
|
package/dist/index.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ export { default as Invocation, InvocationParamPopover, type InvocationProps, ty
|
|
|
64
64
|
export { type ComposerParamPanelContext } from './composer/param-panel';
|
|
65
65
|
export { default as Mention, type MentionProps } from './mention';
|
|
66
66
|
export { default as ChatItem, type ChatItemProps } from './chat-item';
|
|
67
|
-
export { default as SkillSlot, type SkillSlotProps } from './skill-slot';
|
|
67
|
+
export { default as SkillSlot, type SkillSlotHandle, type SkillSlotProps } from './skill-slot';
|
|
68
68
|
export { default as ErrorBlock, type ErrorBlockProps } from './error-block';
|
|
69
69
|
export { default as Fab, type FabProps } from './fab';
|
|
70
70
|
export { default as MessageImage, type MessageImageProps, type MessageImageItem } from './message-image';
|
|
@@ -8,22 +8,28 @@ export interface SkillSlotProps {
|
|
|
8
8
|
value?: string | null;
|
|
9
9
|
onSelect?: (skill: SkillItem) => void;
|
|
10
10
|
/**
|
|
11
|
-
* 受控的"键盘高亮"项 index(区别于 `value` 的"业务选中"
|
|
12
|
-
* 仅 menu 变体响应。鼠标 hover 与键盘高亮共享同一套视觉(HoverFill),
|
|
13
|
-
* 鼠标交互时优先级高于键盘;鼠标离开后键盘 activeIndex 才接管视觉。
|
|
11
|
+
* 受控的"键盘高亮"项 index(区别于 `value` 的"业务选中"语义)。仅 menu 变体响应。
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* 键盘导航语义(↑↓/Home/End 移动、跳过 disabled、夹边、滚动进视口)由 SkillSlot
|
|
14
|
+
* 自己负责,是唯一事实源。两种驱动方式:
|
|
15
|
+
* - 不传 `activeIndex` → 非受控:menu 容器自身可聚焦,直接响应键盘。
|
|
16
|
+
* - 传入 `activeIndex` → 受控:业务持有高亮 state(如 composer 焦点留在 editor,
|
|
17
|
+
* 通过 `ref` 调用 `moveActive` 转发 ↑↓),SkillSlot 通过 `onActiveIndexChange`
|
|
18
|
+
* 回写。鼠标 hover 也走同一套 index,保证鼠标/键盘指向同一项。
|
|
19
19
|
*/
|
|
20
20
|
activeIndex?: number;
|
|
21
21
|
onActiveIndexChange?: (index: number) => void;
|
|
22
22
|
className?: string;
|
|
23
23
|
style?: React.CSSProperties;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
export
|
|
27
|
-
|
|
25
|
+
/** 命令式句柄:供"焦点不在 menu 上"的宿主(如 composer 的 contenteditable)转发键盘。 */
|
|
26
|
+
export interface SkillSlotHandle {
|
|
27
|
+
/** 按方向移动高亮项,自动跳过 disabled、夹边。 */
|
|
28
|
+
moveActive: (dir: 'up' | 'down' | 'home' | 'end') => void;
|
|
29
|
+
/** 当前高亮项(无则 null)。 */
|
|
30
|
+
getActiveSkill: () => SkillItem | null;
|
|
31
|
+
/** 当前高亮 index(无则 -1)。 */
|
|
32
|
+
getActiveIndex: () => number;
|
|
28
33
|
}
|
|
34
|
+
export declare const SkillSlot: import("react").ForwardRefExoticComponent<SkillSlotProps & import("react").RefAttributes<SkillSlotHandle>>;
|
|
29
35
|
export default SkillSlot;
|
package/dist/skill-slot/index.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
2
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
3
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
4
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
5
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
6
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
7
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
1
8
|
import HoverFill from "../hover-fill";
|
|
2
9
|
import Icon from "../icon";
|
|
3
10
|
import Tooltip from "../tooltip";
|
|
4
11
|
import "./style";
|
|
5
|
-
|
|
12
|
+
|
|
13
|
+
/* 给 listbox / option 生成稳定且唯一的 id 前缀(aria-activedescendant 需要)。
|
|
14
|
+
* 模块级自增 + useState 初始化,保证每个实例 mount 期间 id 稳定。 */
|
|
15
|
+
var skillSlotIdSeq = 0;
|
|
16
|
+
|
|
17
|
+
/** 命令式句柄:供"焦点不在 menu 上"的宿主(如 composer 的 contenteditable)转发键盘。 */
|
|
18
|
+
|
|
19
|
+
export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref, ref) {
|
|
6
20
|
var skills = _ref.skills,
|
|
7
21
|
_ref$variant = _ref.variant,
|
|
8
22
|
variant = _ref$variant === void 0 ? 'bar' : _ref$variant,
|
|
@@ -20,18 +34,151 @@ export function SkillSlot(_ref) {
|
|
|
20
34
|
* activeIndex → highlighted 转移过来,鼠标走后 highlighted 接力。
|
|
21
35
|
*
|
|
22
36
|
* 颜色用 HoverFill 默认 hoverColor 同值(rgba(33,34,38,0.05)),过渡也用
|
|
23
|
-
* 颜色 transition,视觉看起来一致。
|
|
37
|
+
* 颜色 transition,视觉看起来一致。
|
|
38
|
+
*
|
|
39
|
+
* 键盘导航(仅 menu):
|
|
40
|
+
* - 受控:业务传 `activeIndex`(如 composer 把 editor 的 ↑↓ 转发进来),
|
|
41
|
+
* 组件只反映高亮 + 回调 `onActiveIndexChange`,自身不维护状态。
|
|
42
|
+
* - 非受控:未传 `activeIndex` 时,menu 容器自身可聚焦(tabIndex),内部
|
|
43
|
+
* 维护高亮 index,监听 ↑↓ / Home / End 移动、Enter / Space 选中,
|
|
44
|
+
* 并自动跳过 disabled 项、把高亮项滚动进视口。 */
|
|
45
|
+
|
|
46
|
+
var _useState = useState(function () {
|
|
47
|
+
return "odn-skill-slot-".concat(++skillSlotIdSeq);
|
|
48
|
+
}),
|
|
49
|
+
_useState2 = _slicedToArray(_useState, 1),
|
|
50
|
+
listboxId = _useState2[0];
|
|
51
|
+
var _useState3 = useState(-1),
|
|
52
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
53
|
+
internalActiveIndex = _useState4[0],
|
|
54
|
+
setInternalActiveIndex = _useState4[1];
|
|
55
|
+
var isControlled = activeIndex != null;
|
|
56
|
+
var currentIndex = isControlled ? activeIndex : internalActiveIndex;
|
|
57
|
+
var itemRefs = useRef([]);
|
|
58
|
+
|
|
59
|
+
/* disabled 与 active 互斥:已选中项不算 disabled(与渲染逻辑保持一致)。 */
|
|
60
|
+
var isItemDisabled = function isItemDisabled(skill) {
|
|
61
|
+
return !!skill.disabled && value !== skill.id;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/* 高亮项变化时滚动进视口(menu 列表可能超出浮层高度)。 */
|
|
65
|
+
useEffect(function () {
|
|
66
|
+
var _itemRefs$current$cur;
|
|
67
|
+
if (variant !== 'menu' || currentIndex < 0) return;
|
|
68
|
+
(_itemRefs$current$cur = itemRefs.current[currentIndex]) === null || _itemRefs$current$cur === void 0 || _itemRefs$current$cur.scrollIntoView({
|
|
69
|
+
block: 'nearest'
|
|
70
|
+
});
|
|
71
|
+
}, [variant, currentIndex]);
|
|
72
|
+
var applyActiveIndex = function applyActiveIndex(next) {
|
|
73
|
+
if (isControlled) onActiveIndexChange === null || onActiveIndexChange === void 0 || onActiveIndexChange(next);else setInternalActiveIndex(next);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/* 从 start 起按 dir 方向找第一个非 disabled 项;找不到返回 -1(不环绕,夹边)。 */
|
|
77
|
+
var findEnabled = function findEnabled(start, dir) {
|
|
78
|
+
for (var i = start; i >= 0 && i < skills.length; i += dir) {
|
|
79
|
+
if (!isItemDisabled(skills[i])) return i;
|
|
80
|
+
}
|
|
81
|
+
return -1;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/* 唯一的导航语义入口:按方向算出下一个可用项并应用。键盘事件与命令式 ref 共用。 */
|
|
85
|
+
var step = function step(dir) {
|
|
86
|
+
if (skills.length === 0) return;
|
|
87
|
+
var next = -1;
|
|
88
|
+
switch (dir) {
|
|
89
|
+
case 'down':
|
|
90
|
+
next = findEnabled(currentIndex < 0 ? 0 : currentIndex + 1, 1);
|
|
91
|
+
break;
|
|
92
|
+
case 'up':
|
|
93
|
+
next = findEnabled(currentIndex < 0 ? skills.length - 1 : currentIndex - 1, -1);
|
|
94
|
+
break;
|
|
95
|
+
case 'home':
|
|
96
|
+
next = findEnabled(0, 1);
|
|
97
|
+
break;
|
|
98
|
+
case 'end':
|
|
99
|
+
next = findEnabled(skills.length - 1, -1);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
if (next !== -1) applyActiveIndex(next);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/* 命令式句柄:让焦点不在 menu 上的宿主(composer)把 ↑↓ 转发进来,
|
|
106
|
+
* 导航/选中语义仍只有 SkillSlot 这一份。 */
|
|
107
|
+
useImperativeHandle(ref, function () {
|
|
108
|
+
return {
|
|
109
|
+
moveActive: step,
|
|
110
|
+
getActiveSkill: function getActiveSkill() {
|
|
111
|
+
return currentIndex >= 0 && currentIndex < skills.length ? skills[currentIndex] : null;
|
|
112
|
+
},
|
|
113
|
+
getActiveIndex: function getActiveIndex() {
|
|
114
|
+
return currentIndex;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
// currentIndex / skills 变化时句柄需读到最新值
|
|
119
|
+
[currentIndex, skills]);
|
|
120
|
+
var handleMenuKeyDown = function handleMenuKeyDown(e) {
|
|
121
|
+
if (skills.length === 0) return;
|
|
122
|
+
switch (e.key) {
|
|
123
|
+
case 'ArrowDown':
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
step('down');
|
|
126
|
+
break;
|
|
127
|
+
case 'ArrowUp':
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
step('up');
|
|
130
|
+
break;
|
|
131
|
+
case 'Home':
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
step('home');
|
|
134
|
+
break;
|
|
135
|
+
case 'End':
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
step('end');
|
|
138
|
+
break;
|
|
139
|
+
case 'Enter':
|
|
140
|
+
case ' ':
|
|
141
|
+
{
|
|
142
|
+
if (currentIndex >= 0 && currentIndex < skills.length && !isItemDisabled(skills[currentIndex])) {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
onSelect === null || onSelect === void 0 || onSelect(skills[currentIndex]);
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
default:
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/* 非受控时获得焦点 → 默认高亮第一个可用项。 */
|
|
154
|
+
var handleMenuFocus = function handleMenuFocus() {
|
|
155
|
+
if (isControlled || internalActiveIndex >= 0) return;
|
|
156
|
+
var next = findEnabled(0, 1);
|
|
157
|
+
if (next !== -1) setInternalActiveIndex(next);
|
|
158
|
+
};
|
|
24
159
|
|
|
160
|
+
/* 非受控时焦点离开整个 menu → 清掉高亮,避免失焦后残留。 */
|
|
161
|
+
var handleMenuBlur = function handleMenuBlur(e) {
|
|
162
|
+
if (isControlled) return;
|
|
163
|
+
if (e.currentTarget.contains(e.relatedTarget)) return;
|
|
164
|
+
setInternalActiveIndex(-1);
|
|
165
|
+
};
|
|
25
166
|
if (skills.length === 0) return null;
|
|
26
167
|
return /*#__PURE__*/React.createElement("div", {
|
|
27
168
|
"data-odn-skill-slot": true,
|
|
28
169
|
"data-odn-skill-slot-variant": variant,
|
|
29
|
-
|
|
170
|
+
id: variant === 'menu' ? listboxId : undefined,
|
|
171
|
+
role: variant === 'menu' ? 'listbox' : undefined,
|
|
172
|
+
tabIndex: variant === 'menu' ? 0 : undefined,
|
|
173
|
+
"aria-activedescendant": variant === 'menu' && currentIndex >= 0 ? "".concat(listboxId, "-opt-").concat(currentIndex) : undefined,
|
|
174
|
+
onKeyDown: variant === 'menu' ? handleMenuKeyDown : undefined,
|
|
175
|
+
onFocus: variant === 'menu' ? handleMenuFocus : undefined,
|
|
176
|
+
onBlur: variant === 'menu' ? handleMenuBlur : undefined,
|
|
30
177
|
className: className,
|
|
31
178
|
style: style
|
|
32
179
|
}, skills.map(function (skill, index) {
|
|
33
180
|
var active = value === skill.id;
|
|
34
|
-
var highlighted =
|
|
181
|
+
var highlighted = currentIndex === index;
|
|
35
182
|
/* disabled 与 active 互斥时,active 优先(已选中不应再渲染禁用样态)。
|
|
36
183
|
* onSelect 通过 button[disabled] 自然短路;HoverFill 也走 disabled 关掉 hover 反馈。 */
|
|
37
184
|
var disabled = !!skill.disabled && !active;
|
|
@@ -61,20 +208,23 @@ export function SkillSlot(_ref) {
|
|
|
61
208
|
}, skill.description));
|
|
62
209
|
}
|
|
63
210
|
if (variant === 'menu') {
|
|
64
|
-
/* HoverFill 包 button
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* 视觉只有 HoverFill 一套,鼠标键盘视觉本质完全相同。 */
|
|
211
|
+
/* HoverFill 包 button:鼠标 hover 视觉由 HoverFill 命令式控制;
|
|
212
|
+
* 键盘高亮走 button 上的 data-odn-skill-slot-item-highlighted ::before 层。
|
|
213
|
+
* 两层互斥(hover 时不画 ::before),颜色同值,视觉一致。 */
|
|
68
214
|
return /*#__PURE__*/React.createElement(HoverFill, {
|
|
69
215
|
key: skill.id,
|
|
70
216
|
"data-odn-skill-slot-item-wrap": true,
|
|
71
217
|
bgClassName: "odn-skill-slot-item-bg",
|
|
72
218
|
disabled: active || disabled,
|
|
73
219
|
onMouseEnter: function onMouseEnter() {
|
|
74
|
-
/*
|
|
75
|
-
* 避免鼠标移开后键盘 active 还指向另一项造成"鬼影高亮"。
|
|
76
|
-
|
|
77
|
-
|
|
220
|
+
/* 鼠标进入:把高亮 index 同步过来,让鼠标/键盘两条轨道始终指向同一项,
|
|
221
|
+
* 避免鼠标移开后键盘 active 还指向另一项造成"鬼影高亮"。
|
|
222
|
+
* 受控走 onActiveIndexChange,非受控更新内部 state。 */
|
|
223
|
+
if (disabled) return;
|
|
224
|
+
if (isControlled) {
|
|
225
|
+
if (onActiveIndexChange && activeIndex !== index) onActiveIndexChange(index);
|
|
226
|
+
} else if (internalActiveIndex !== index) {
|
|
227
|
+
setInternalActiveIndex(index);
|
|
78
228
|
}
|
|
79
229
|
}
|
|
80
230
|
/* 阻止 contenteditable 在 mousedown 时 blur,否则点 menu item 的瞬间
|
|
@@ -84,12 +234,17 @@ export function SkillSlot(_ref) {
|
|
|
84
234
|
}
|
|
85
235
|
}, /*#__PURE__*/React.createElement("button", {
|
|
86
236
|
type: "button",
|
|
237
|
+
id: "".concat(listboxId, "-opt-").concat(index),
|
|
238
|
+
ref: function ref(el) {
|
|
239
|
+
itemRefs.current[index] = el;
|
|
240
|
+
},
|
|
87
241
|
"data-odn-skill-slot-item": true,
|
|
88
242
|
"data-odn-skill-slot-item-active": active || undefined,
|
|
89
243
|
"data-odn-skill-slot-item-highlighted": highlighted || undefined,
|
|
90
244
|
"data-odn-skill-slot-item-disabled": disabled || undefined,
|
|
91
|
-
role:
|
|
92
|
-
"aria-selected":
|
|
245
|
+
role: "option",
|
|
246
|
+
"aria-selected": highlighted,
|
|
247
|
+
tabIndex: -1,
|
|
93
248
|
disabled: disabled,
|
|
94
249
|
"aria-disabled": disabled || undefined,
|
|
95
250
|
onClick: function onClick() {
|
|
@@ -134,6 +289,6 @@ export function SkillSlot(_ref) {
|
|
|
134
289
|
}
|
|
135
290
|
return barButton;
|
|
136
291
|
}));
|
|
137
|
-
}
|
|
292
|
+
});
|
|
138
293
|
SkillSlot.displayName = 'SkillSlot';
|
|
139
294
|
export default SkillSlot;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.odn-skill-slot-item-bg {
|
|
19
|
-
border-radius:
|
|
19
|
+
border-radius: 0;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/* ---- variant: menu ---- */
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
display: flex;
|
|
25
25
|
flex-direction: column;
|
|
26
26
|
padding: 4px 0;
|
|
27
|
+
/* 容器走 aria-activedescendant 模式(焦点留在 listbox,高亮项由 ::before 指示),
|
|
28
|
+
* 故去掉容器自身的焦点描边,避免与高亮视觉重复。 */
|
|
29
|
+
}
|
|
30
|
+
[data-odn-skill-slot][data-odn-skill-slot-variant=menu]:focus, [data-odn-skill-slot][data-odn-skill-slot-variant=menu]:focus-visible {
|
|
31
|
+
outline: none;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
[data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item-wrap] {
|
|
@@ -44,7 +49,8 @@
|
|
|
44
49
|
}
|
|
45
50
|
[data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item] > [data-odn-icon] {
|
|
46
51
|
flex-shrink: 0;
|
|
47
|
-
|
|
52
|
+
/* 与标签首行(line-height 22px)的视觉中线对齐:(22 - 14) / 2 = 4px */
|
|
53
|
+
margin-top: 4px;
|
|
48
54
|
}
|
|
49
55
|
[data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item] {
|
|
50
56
|
/* 键盘高亮:独立视觉层,颜色与 HoverFill 默认 hoverColor 完全一致,过渡用
|
|
@@ -63,7 +69,7 @@
|
|
|
63
69
|
position: absolute;
|
|
64
70
|
inset: 0;
|
|
65
71
|
background: rgba(33, 34, 38, 0.05);
|
|
66
|
-
border-radius:
|
|
72
|
+
border-radius: 0;
|
|
67
73
|
transition: background-color 0.18s ease;
|
|
68
74
|
pointer-events: none;
|
|
69
75
|
z-index: -1;
|
|
@@ -192,7 +198,7 @@
|
|
|
192
198
|
[data-odn-skill-slot-item-content] {
|
|
193
199
|
display: flex;
|
|
194
200
|
flex-direction: column;
|
|
195
|
-
gap:
|
|
201
|
+
gap: 2px;
|
|
196
202
|
min-width: 0;
|
|
197
203
|
flex: 1;
|
|
198
204
|
}
|