one-design-next 0.0.81 → 0.0.83

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.
@@ -23,7 +23,7 @@ export interface ClarifyDockSpec {
23
23
  answeredText?: string;
24
24
  /**
25
25
  * 单题选项上限(含「其他」),默认 {@link CLARIFY_DOCK_MAX_OPTIONS}。
26
- * 超过截断业务选项;有「其他」时优先保留末位。硬顶 26(AZ)。
26
+ * 超过截断业务选项;有「其他」时优先保留末位。硬顶 26(126)。
27
27
  */
28
28
  maxOptions?: number;
29
29
  }
@@ -215,16 +215,16 @@ var MULTI_UNSELECTED_ACTIVE_FILL = 'rgba(33, 34, 38, 0.08)';
215
215
  */
216
216
  export var CLARIFY_DOCK_MAX_OPTIONS = 5;
217
217
 
218
- /** 徽标字母硬顶 AZ,防止异常大值。 */
219
- var OPTION_LETTER_HARD_CAP = 26;
218
+ /** 徽标序号硬顶 126,防止异常大值。 */
219
+ var OPTION_INDEX_HARD_CAP = 26;
220
220
  function resolveMaxOptions(maxOptions) {
221
221
  var raw = maxOptions !== null && maxOptions !== void 0 ? maxOptions : CLARIFY_DOCK_MAX_OPTIONS;
222
222
  if (!Number.isFinite(raw)) return CLARIFY_DOCK_MAX_OPTIONS;
223
- return Math.max(1, Math.min(Math.floor(raw), OPTION_LETTER_HARD_CAP));
223
+ return Math.max(1, Math.min(Math.floor(raw), OPTION_INDEX_HARD_CAP));
224
224
  }
225
- function optionLetter(idx) {
226
- if (idx < 0 || idx >= OPTION_LETTER_HARD_CAP) return '';
227
- return String.fromCharCode(65 + idx);
225
+ function optionIndexLabel(idx) {
226
+ if (idx < 0 || idx >= OPTION_INDEX_HARD_CAP) return '';
227
+ return String(idx + 1);
228
228
  }
229
229
  export function ClarifyDockPanel(_ref2) {
230
230
  var msgId = _ref2.msgId,
@@ -248,24 +248,28 @@ export function ClarifyDockPanel(_ref2) {
248
248
  _useState10 = _slicedToArray(_useState9, 2),
249
249
  otherDraft = _useState10[0],
250
250
  setOtherDraft = _useState10[1];
251
- var _useState11 = useState(null),
251
+ var _useState11 = useState(''),
252
252
  _useState12 = _slicedToArray(_useState11, 2),
253
- dateDraft = _useState12[0],
254
- setDateDraft = _useState12[1];
253
+ textDraft = _useState12[0],
254
+ setTextDraft = _useState12[1];
255
255
  var _useState13 = useState(null),
256
256
  _useState14 = _slicedToArray(_useState13, 2),
257
- dateRangeDraft = _useState14[0],
258
- setDateRangeDraft = _useState14[1];
259
- /** AutoHeight 过渡结束后再递增,驱动选项延后 autofocus */
260
- var _useState15 = useState(0),
257
+ dateDraft = _useState14[0],
258
+ setDateDraft = _useState14[1];
259
+ var _useState15 = useState(null),
261
260
  _useState16 = _slicedToArray(_useState15, 2),
262
- inputFocusEpoch = _useState16[0],
263
- setInputFocusEpoch = _useState16[1];
261
+ dateRangeDraft = _useState16[0],
262
+ setDateRangeDraft = _useState16[1];
263
+ /** AutoHeight 过渡结束后再递增,驱动选项延后 autofocus */
264
+ var _useState17 = useState(0),
265
+ _useState18 = _slicedToArray(_useState17, 2),
266
+ inputFocusEpoch = _useState18[0],
267
+ setInputFocusEpoch = _useState18[1];
264
268
  var question = spec.questions[page];
265
269
  var answer = answers[page];
266
270
  var input = question.input;
267
271
 
268
- /* 切页:回显 other / date / date-range 草稿 */
272
+ /* 切页:回显 other / text / date / date-range 草稿 */
269
273
  useEffect(function () {
270
274
  if ((answer === null || answer === void 0 ? void 0 : answer.kind) === 'options' || (answer === null || answer === void 0 ? void 0 : answer.kind) === 'options-multi') {
271
275
  var _answer$otherText;
@@ -273,6 +277,11 @@ export function ClarifyDockPanel(_ref2) {
273
277
  } else {
274
278
  setOtherDraft('');
275
279
  }
280
+ if ((answer === null || answer === void 0 ? void 0 : answer.kind) === 'text') {
281
+ setTextDraft(answer.value);
282
+ } else {
283
+ setTextDraft('');
284
+ }
276
285
  if ((answer === null || answer === void 0 ? void 0 : answer.kind) === 'date') {
277
286
  setDateDraft(answer.value ? new Date("".concat(answer.value, "T00:00:00")) : null);
278
287
  } else {
@@ -309,6 +318,8 @@ export function ClarifyDockPanel(_ref2) {
309
318
  if (hasOther && otherDraft.trim().length === 0) return false;
310
319
  return hasRegular || hasOther;
311
320
  }
321
+ case 'text':
322
+ return answer.value.trim().length > 0;
312
323
  case 'date':
313
324
  return answer.value.trim().length > 0;
314
325
  case 'date-range':
@@ -360,6 +371,8 @@ export function ClarifyDockPanel(_ref2) {
360
371
  }
361
372
  return labels.join('、');
362
373
  }
374
+ case 'text':
375
+ return ans.value;
363
376
  case 'date':
364
377
  return ans.value;
365
378
  case 'date-range':
@@ -381,6 +394,12 @@ export function ClarifyDockPanel(_ref2) {
381
394
  otherText: otherDraft
382
395
  });
383
396
  }
397
+ if (a.kind === 'text') {
398
+ return {
399
+ kind: 'text',
400
+ value: textDraft
401
+ };
402
+ }
384
403
  if (a.kind === 'date' && dateDraft) {
385
404
  return {
386
405
  kind: 'date',
@@ -420,6 +439,9 @@ export function ClarifyDockPanel(_ref2) {
420
439
  data[q.key] = values;
421
440
  break;
422
441
  }
442
+ case 'text':
443
+ data[q.key] = a.value;
444
+ break;
423
445
  case 'date':
424
446
  data[q.key] = a.value;
425
447
  break;
@@ -483,6 +505,7 @@ export function ClarifyDockPanel(_ref2) {
483
505
  kind: 'skipped'
484
506
  };
485
507
  setOtherDraft('');
508
+ setTextDraft('');
486
509
  setDateDraft(null);
487
510
  setDateRangeDraft(null);
488
511
  /* 不按顺序跳过:任意一题跳过后若已全部有值(含已跳),直接提交 */
@@ -502,6 +525,7 @@ export function ClarifyDockPanel(_ref2) {
502
525
  if (!isSkipped) return;
503
526
  setAnswerForPage(undefined);
504
527
  setOtherDraft('');
528
+ setTextDraft('');
505
529
  setDateDraft(null);
506
530
  setDateRangeDraft(null);
507
531
  };
@@ -552,6 +576,7 @@ export function ClarifyDockPanel(_ref2) {
552
576
  answer: isSkipped ? undefined : answer,
553
577
  focusEpoch: inputFocusEpoch,
554
578
  otherDraft: otherDraft,
579
+ textDraft: textDraft,
555
580
  dateDraft: dateDraft,
556
581
  dateRangeDraft: dateRangeDraft,
557
582
  onOtherDraftChange: function onOtherDraftChange(v) {
@@ -570,6 +595,13 @@ export function ClarifyDockPanel(_ref2) {
570
595
  });
571
596
  }
572
597
  },
598
+ onTextChange: function onTextChange(v) {
599
+ setTextDraft(v);
600
+ setAnswerForPage(v.trim() ? {
601
+ kind: 'text',
602
+ value: v
603
+ } : undefined);
604
+ },
573
605
  onDateChange: function onDateChange(d) {
574
606
  setDateDraft(d);
575
607
  setAnswerForPage(d ? {
@@ -671,8 +703,8 @@ function withOtherOption(options, allowOther, maxOptions) {
671
703
 
672
704
  /**
673
705
  * 按 input.kind 适配录入组件:
674
- * options / options-multi → 选项列表;dateDatePicker;
675
- * date-range → DateRangePicker。自由文本走自然语言 + Composer,不在此渲染。
706
+ * options / options-multi → 选项列表;textInput 单行;
707
+ * date → DatePicker;date-range → DateRangePicker
676
708
  */
677
709
  function QuestionInput(_ref3) {
678
710
  var input = _ref3.input,
@@ -680,9 +712,11 @@ function QuestionInput(_ref3) {
680
712
  answer = _ref3.answer,
681
713
  focusEpoch = _ref3.focusEpoch,
682
714
  otherDraft = _ref3.otherDraft,
715
+ textDraft = _ref3.textDraft,
683
716
  dateDraft = _ref3.dateDraft,
684
717
  dateRangeDraft = _ref3.dateRangeDraft,
685
718
  onOtherDraftChange = _ref3.onOtherDraftChange,
719
+ onTextChange = _ref3.onTextChange,
686
720
  onDateChange = _ref3.onDateChange,
687
721
  onDateRangeChange = _ref3.onDateRangeChange,
688
722
  onPickSingle = _ref3.onPickSingle,
@@ -704,6 +738,17 @@ function QuestionInput(_ref3) {
704
738
  })
705
739
  );
706
740
  }
741
+ if (input.kind === 'text') {
742
+ return /*#__PURE__*/React.createElement("div", {
743
+ "data-odn-clarify-dock-input-row": true,
744
+ "data-kind": "text"
745
+ }, /*#__PURE__*/React.createElement(TextQuestionInput, {
746
+ value: textDraft,
747
+ placeholder: input.placeholder,
748
+ focusEpoch: focusEpoch,
749
+ onChange: onTextChange
750
+ }));
751
+ }
707
752
  if (input.kind === 'date') {
708
753
  return /*#__PURE__*/React.createElement("div", {
709
754
  "data-odn-clarify-dock-input-row": true
@@ -734,25 +779,57 @@ function QuestionInput(_ref3) {
734
779
  }
735
780
 
736
781
  /* ------------------------------------------------------------------ */
737
- /* OptionsList · A–E 徽标 + ↑↓ 框选(焦点隔离,不抢 Composer */
782
+ /* TextQuestionInput · 迷你单行输入(撑满宽度,13px */
738
783
  /* ------------------------------------------------------------------ */
739
784
 
740
- function OptionsList(_ref4) {
741
- var options = _ref4.options,
742
- multi = _ref4.multi,
743
- answer = _ref4.answer,
785
+ function TextQuestionInput(_ref4) {
786
+ var value = _ref4.value,
787
+ placeholder = _ref4.placeholder,
744
788
  focusEpoch = _ref4.focusEpoch,
745
- otherDraft = _ref4.otherDraft,
746
- otherPlaceholder = _ref4.otherPlaceholder,
747
- onOtherDraftChange = _ref4.onOtherDraftChange,
748
- onPickSingle = _ref4.onPickSingle,
749
- onToggleMulti = _ref4.onToggleMulti;
789
+ _onChange = _ref4.onChange;
790
+ var ref = useRef(null);
791
+ useEffect(function () {
792
+ if (focusEpoch <= 0) return;
793
+ var id = window.requestAnimationFrame(function () {
794
+ var _ref$current;
795
+ (_ref$current = ref.current) === null || _ref$current === void 0 || _ref$current.focus();
796
+ });
797
+ return function () {
798
+ return window.cancelAnimationFrame(id);
799
+ };
800
+ }, [focusEpoch]);
801
+ return /*#__PURE__*/React.createElement(Input, {
802
+ ref: ref,
803
+ value: value,
804
+ onChange: function onChange(_, next) {
805
+ return _onChange(next);
806
+ },
807
+ placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : '请输入…',
808
+ size: "small",
809
+ className: "w-full"
810
+ });
811
+ }
812
+
813
+ /* ------------------------------------------------------------------ */
814
+ /* OptionsList · 1–N 序号徽标 + ↑↓ 框选(焦点隔离,不抢 Composer) */
815
+ /* ------------------------------------------------------------------ */
816
+
817
+ function OptionsList(_ref5) {
818
+ var options = _ref5.options,
819
+ multi = _ref5.multi,
820
+ answer = _ref5.answer,
821
+ focusEpoch = _ref5.focusEpoch,
822
+ otherDraft = _ref5.otherDraft,
823
+ otherPlaceholder = _ref5.otherPlaceholder,
824
+ onOtherDraftChange = _ref5.onOtherDraftChange,
825
+ onPickSingle = _ref5.onPickSingle,
826
+ onToggleMulti = _ref5.onToggleMulti;
750
827
  var optionRefs = useRef([]);
751
828
  /** roving tabindex:当前键盘焦点项;与选中态解耦,避免重渲染丢焦 */
752
- var _useState17 = useState(0),
753
- _useState18 = _slicedToArray(_useState17, 2),
754
- focusedIdx = _useState18[0],
755
- setFocusedIdx = _useState18[1];
829
+ var _useState19 = useState(0),
830
+ _useState20 = _slicedToArray(_useState19, 2),
831
+ focusedIdx = _useState20[0],
832
+ setFocusedIdx = _useState20[1];
756
833
  /** 选中态导致「其他」编辑行等结构切换后,在 layout 再聚焦 */
757
834
  var pendingFocusIdxRef = useRef(null);
758
835
  var resolveSelectedIdx = function resolveSelectedIdx() {
@@ -832,7 +909,7 @@ function OptionsList(_ref4) {
832
909
  }, options.map(function (opt, idx) {
833
910
  var active = (answer === null || answer === void 0 ? void 0 : answer.kind) === 'options' ? answer.value === opt.value : (answer === null || answer === void 0 ? void 0 : answer.kind) === 'options-multi' ? answer.values.includes(opt.value) : false;
834
911
  var isOther = opt.value === OTHER_VALUE;
835
- var letter = optionLetter(idx);
912
+ var seq = optionIndexLabel(idx);
836
913
  if (isOther && active) {
837
914
  var editingRow = /*#__PURE__*/React.createElement("div", {
838
915
  "data-odn-clarify-dock-option": true,
@@ -854,10 +931,10 @@ function OptionsList(_ref4) {
854
931
  onToggleMulti(opt);
855
932
  }
856
933
  }
857
- }, letter) : /*#__PURE__*/React.createElement("span", {
934
+ }, seq) : /*#__PURE__*/React.createElement("span", {
858
935
  "data-odn-clarify-dock-option-letter": true,
859
936
  "aria-hidden": true
860
- }, letter), /*#__PURE__*/React.createElement(Input, {
937
+ }, seq), /*#__PURE__*/React.createElement(Input, {
861
938
  value: otherDraft,
862
939
  onChange: function onChange(_, value) {
863
940
  return onOtherDraftChange(value);
@@ -903,7 +980,7 @@ function OptionsList(_ref4) {
903
980
  var optionContent = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
904
981
  "data-odn-clarify-dock-option-letter": true,
905
982
  "aria-hidden": true
906
- }, letter), /*#__PURE__*/React.createElement("span", {
983
+ }, seq), /*#__PURE__*/React.createElement("span", {
907
984
  "data-odn-clarify-dock-option-label": true
908
985
  }, opt.label));
909
986
 
@@ -950,10 +1027,10 @@ function OptionsList(_ref4) {
950
1027
  * - recordAnswer 后 Panel 消失,Trigger 切 Approval 式锁态卡
951
1028
  */
952
1029
  export function useClarifyDockState(messages, sessionKey, resetVer) {
953
- var _useState19 = useState({}),
954
- _useState20 = _slicedToArray(_useState19, 2),
955
- answers = _useState20[0],
956
- setAnswers = _useState20[1];
1030
+ var _useState21 = useState({}),
1031
+ _useState22 = _slicedToArray(_useState21, 2),
1032
+ answers = _useState22[0],
1033
+ setAnswers = _useState22[1];
957
1034
 
958
1035
  /* 切换会话或点「重置」时清空已答标记,避免 Panel / Trigger 状态脱节 */
959
1036
  useEffect(function () {
@@ -142,16 +142,20 @@ div[data-odn-clarify-dock] [data-odn-clarify-dock-body] {
142
142
  [data-odn-clarify-dock-actions] [data-odn-pagination] {
143
143
  flex-shrink: 0;
144
144
  font-size: 12px;
145
+ font-family: inherit;
146
+ font-variant-numeric: normal;
145
147
  line-height: 1.5;
146
148
  color: var(--odn-color-black-9);
147
149
  }
148
150
 
149
151
  [data-odn-clarify-dock-actions] [data-odn-pagination-simple-page-value] {
152
+ font-family: inherit;
153
+ font-variant-numeric: normal;
150
154
  color: var(--odn-color-black-9);
151
155
  }
152
156
 
153
157
  /* ============================================================== */
154
- /* Options · AE 徽标 + 通栏灰底(不复用 Button 尺寸体系) */
158
+ /* Options · 1N 序号徽标 + 通栏灰底(不复用 Button 尺寸体系) */
155
159
  /* ============================================================== */
156
160
  [data-odn-clarify-dock] [data-odn-clarify-dock-options] {
157
161
  display: flex;
@@ -238,7 +242,8 @@ div[data-odn-clarify-dock] [data-odn-clarify-dock-body] {
238
242
  color: var(--odn-color-black-10);
239
243
  font-size: 11px;
240
244
  font-weight: 600;
241
- font-variant-numeric: tabular-nums;
245
+ font-family: inherit;
246
+ font-variant-numeric: normal;
242
247
  line-height: 1;
243
248
  }
244
249
 
@@ -288,7 +293,8 @@ div[data-odn-clarify-dock] [data-odn-clarify-dock-body] {
288
293
  line-height: 1;
289
294
  font-size: 11px;
290
295
  font-weight: 600;
291
- font-variant-numeric: tabular-nums;
296
+ font-family: inherit;
297
+ font-variant-numeric: normal;
292
298
  border: 1px solid var(--odn-color-brand-6);
293
299
  background: var(--odn-color-brand-6);
294
300
  color: var(--odn-color-bg-elevated);
@@ -542,6 +548,14 @@ div[data-odn-clarify-dock] [data-odn-clarify-dock-body] {
542
548
  flex: 0 0 auto;
543
549
  }
544
550
 
551
+ /* 纯文本题:迷你单行输入,默认撑满宽度,字号 13px,左右 padding 6px,圆角 4px */
552
+ [data-odn-clarify-dock-input-row][data-kind=text] {
553
+ width: 100%;
554
+ --odn-input-font-size-small: var(--odn-font-size-text-sm);
555
+ --odn-input-padding-inline-small: var(--odn-input-padding-block-medium);
556
+ --odn-input-border-radius-small: var(--odn-border-radius);
557
+ }
558
+
545
559
  div[data-odn-clarify-dock] [data-odn-date-picker-container]:not([data-odn-date-picker-container-range]) {
546
560
  display: inline-block;
547
561
  box-sizing: border-box;
@@ -17,6 +17,9 @@ export type ClarifyInput = {
17
17
  options: ClarifyOption[];
18
18
  allowOther?: boolean;
19
19
  otherPlaceholder?: string;
20
+ } | {
21
+ kind: 'text';
22
+ placeholder?: string;
20
23
  } | {
21
24
  kind: 'date';
22
25
  placeholder?: string;
@@ -12,7 +12,7 @@ export function toISODate(d) {
12
12
 
13
13
  /** 解析 Host 传入的日期边界;非法则忽略。 */
14
14
  export function parseClarifyBoundDate(value) {
15
- if (value == null) return undefined;
15
+ if (value === undefined || value === null) return undefined;
16
16
  if (value instanceof Date) {
17
17
  return Number.isNaN(value.getTime()) ? undefined : value;
18
18
  }
@@ -894,7 +894,9 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
894
894
  },
895
895
  popupClassName: "odn-composer-skill-popup",
896
896
  popup: /*#__PURE__*/React.createElement(SkillSlot, {
897
+ key: toolMenuOpen ? 'open' : 'closed',
897
898
  variant: "menu",
899
+ searchable: true,
898
900
  skills: skillGroups ? undefined : skills,
899
901
  skillGroups: skillGroups,
900
902
  showGroupTitles: showGroupTitles,
@@ -63,6 +63,11 @@ export interface SkillSlotProps {
63
63
  * 不传则保持默认布局宽度。
64
64
  */
65
65
  itemWidth?: number;
66
+ /**
67
+ * menu 变体:在列表顶部显示搜索框,按 label / description 即时过滤,命中字高亮。
68
+ * bar / card 忽略。本轮不做搜索框内的键盘导航。
69
+ */
70
+ searchable?: boolean;
66
71
  className?: string;
67
72
  style?: React.CSSProperties;
68
73
  }
@@ -4,8 +4,17 @@ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
4
4
  import { Fragment, forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
5
  import HoverFill from "../hover-fill";
6
6
  import Icon from "../icon";
7
+ import Input from "../input";
7
8
  import Popover from "../popover";
9
+ import { highlightQueryText } from "../stream-text/highlight-query-text";
8
10
  import "./style";
11
+ function skillMatchesQuery(skill, query) {
12
+ var _skill$description;
13
+ var q = query.trim().toLowerCase();
14
+ if (!q) return true;
15
+ if (skill.label.toLowerCase().includes(q)) return true;
16
+ return ((_skill$description = skill.description) !== null && _skill$description !== void 0 ? _skill$description : '').toLowerCase().includes(q);
17
+ }
9
18
 
10
19
  /* 给 listbox / option 生成稳定且唯一的 id 前缀(aria-activedescendant 需要)。
11
20
  * 模块级自增 + useState 初始化,保证每个实例 mount 期间 id 稳定。 */
@@ -16,7 +25,8 @@ var skillSlotIdSeq = 0;
16
25
  * 始终挂载 Popover(用 visible 关掉未截断态),避免条件挂载导致 ref/测量丢帧。
17
26
  */
18
27
  function SkillDescPopover(_ref) {
19
- var description = _ref.description;
28
+ var description = _ref.description,
29
+ highlightQuery = _ref.highlightQuery;
20
30
  var ref = useRef(null);
21
31
  var _useState = useState(false),
22
32
  _useState2 = _slicedToArray(_useState, 2),
@@ -59,11 +69,11 @@ function SkillDescPopover(_ref) {
59
69
  visible: truncated ? undefined : false,
60
70
  popup: /*#__PURE__*/React.createElement("span", {
61
71
  "data-odn-skill-slot-desc-full": true
62
- }, description)
72
+ }, highlightQueryText(description, highlightQuery))
63
73
  }, /*#__PURE__*/React.createElement("span", {
64
74
  ref: ref,
65
75
  "data-odn-skill-slot-item-desc": true
66
- }, description));
76
+ }, highlightQueryText(description, highlightQuery)));
67
77
  }
68
78
 
69
79
  /** 命令式句柄:供"焦点不在 menu 上"的宿主(如 composer 的 contenteditable)转发键盘。 */
@@ -82,15 +92,53 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
82
92
  menuMaxHeight = _ref2.menuMaxHeight,
83
93
  menuWidth = _ref2.menuWidth,
84
94
  itemWidth = _ref2.itemWidth,
95
+ _ref2$searchable = _ref2.searchable,
96
+ searchable = _ref2$searchable === void 0 ? false : _ref2$searchable,
85
97
  className = _ref2.className,
86
98
  style = _ref2.style;
87
- /* 优先使用 skillGroups,否则回退到 skills;两者都未提供则为空数组。 */
88
- var skills = useMemo(function () {
99
+ var searchableMenu = variant === 'menu' && searchable;
100
+ var _useState3 = useState(''),
101
+ _useState4 = _slicedToArray(_useState3, 2),
102
+ searchInput = _useState4[0],
103
+ setSearchInput = _useState4[1];
104
+ var _useState5 = useState(''),
105
+ _useState6 = _slicedToArray(_useState5, 2),
106
+ searchQuery = _useState6[0],
107
+ setSearchQuery = _useState6[1];
108
+ var composingRef = useRef(false);
109
+
110
+ /* 目录全集:过滤前的列表。目录为空才整棵不渲染。 */
111
+ var catalogSkills = useMemo(function () {
89
112
  if (skillGroups) return skillGroups.flatMap(function (g) {
90
113
  return g.skills;
91
114
  });
92
115
  return skillsProp !== null && skillsProp !== void 0 ? skillsProp : [];
93
116
  }, [skillGroups, skillsProp]);
117
+ var visibleGroups = useMemo(function () {
118
+ if (!skillGroups) return undefined;
119
+ if (!searchableMenu) return skillGroups;
120
+ return skillGroups.map(function (g) {
121
+ return {
122
+ groupName: g.groupName,
123
+ skills: g.skills.filter(function (s) {
124
+ return skillMatchesQuery(s, searchQuery);
125
+ })
126
+ };
127
+ }).filter(function (g) {
128
+ return g.skills.length > 0;
129
+ });
130
+ }, [skillGroups, searchableMenu, searchQuery]);
131
+
132
+ /* 优先使用过滤后的 skillGroups,否则回退到 skills。 */
133
+ var skills = useMemo(function () {
134
+ if (visibleGroups) return visibleGroups.flatMap(function (g) {
135
+ return g.skills;
136
+ });
137
+ if (!searchableMenu) return catalogSkills;
138
+ return catalogSkills.filter(function (s) {
139
+ return skillMatchesQuery(s, searchQuery);
140
+ });
141
+ }, [visibleGroups, catalogSkills, searchableMenu, searchQuery]);
94
142
  var showGroupTitles = showGroupTitlesProp !== null && showGroupTitlesProp !== void 0 ? showGroupTitlesProp : variant === 'menu';
95
143
  var groupedWithTitles = !!(skillGroups && showGroupTitles);
96
144
  /* menu 变体:HoverFill 完全只管鼠标交互(命令式状态机,不变);键盘 ↑↓
@@ -110,15 +158,15 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
110
158
  * 维护高亮 index,监听 ↑↓ / Home / End 移动、Enter / Space 选中,
111
159
  * 并自动跳过 disabled 项、把高亮项滚动进视口。 */
112
160
 
113
- var _useState3 = useState(function () {
161
+ var _useState7 = useState(function () {
114
162
  return "odn-skill-slot-".concat(++skillSlotIdSeq);
115
163
  }),
116
- _useState4 = _slicedToArray(_useState3, 1),
117
- listboxId = _useState4[0];
118
- var _useState5 = useState(-1),
119
- _useState6 = _slicedToArray(_useState5, 2),
120
- internalActiveIndex = _useState6[0],
121
- setInternalActiveIndex = _useState6[1];
164
+ _useState8 = _slicedToArray(_useState7, 1),
165
+ listboxId = _useState8[0];
166
+ var _useState9 = useState(-1),
167
+ _useState10 = _slicedToArray(_useState9, 2),
168
+ internalActiveIndex = _useState10[0],
169
+ setInternalActiveIndex = _useState10[1];
122
170
  var isControlled = activeIndex !== undefined && activeIndex !== null;
123
171
  var currentIndex = isControlled ? activeIndex : internalActiveIndex;
124
172
  var itemRefs = useRef([]);
@@ -230,7 +278,7 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
230
278
  if (e.currentTarget.contains(e.relatedTarget)) return;
231
279
  setInternalActiveIndex(-1);
232
280
  };
233
- if (skills.length === 0) return null;
281
+ if (catalogSkills.length === 0) return null;
234
282
 
235
283
  /* menu variant 有 scroll-wrap 时,滚动仅包裹技能列表,footer 固定在底部不参与滚动。 */
236
284
  var hasScrollWrap = variant === 'menu' && menuMaxHeight !== undefined && menuMaxHeight !== null;
@@ -279,8 +327,9 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
279
327
  "data-odn-skill-slot-item-content": true
280
328
  }, /*#__PURE__*/React.createElement("span", {
281
329
  "data-odn-skill-slot-item-label": true
282
- }, skill.label), skill.description ? /*#__PURE__*/React.createElement(SkillDescPopover, {
283
- description: skill.description
330
+ }, highlightQueryText(skill.label, searchableMenu ? searchQuery : undefined)), skill.description ? /*#__PURE__*/React.createElement(SkillDescPopover, {
331
+ description: skill.description,
332
+ highlightQuery: searchableMenu ? searchQuery : undefined
284
333
  }) : null)));
285
334
  };
286
335
 
@@ -365,7 +414,7 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
365
414
  var renderGroups = function renderGroups() {
366
415
  var flatIndex = 0;
367
416
  if (variant === 'menu') {
368
- return /*#__PURE__*/React.createElement(React.Fragment, null, skillGroups.map(function (group, groupIdx) {
417
+ return /*#__PURE__*/React.createElement(React.Fragment, null, visibleGroups.map(function (group, groupIdx) {
369
418
  return /*#__PURE__*/React.createElement(Fragment, {
370
419
  key: group.groupName || "_group_".concat(groupIdx)
371
420
  }, groupIdx > 0 ? /*#__PURE__*/React.createElement("div", {
@@ -378,7 +427,7 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
378
427
  }));
379
428
  }));
380
429
  }
381
- return /*#__PURE__*/React.createElement(React.Fragment, null, skillGroups.map(function (group, groupIdx) {
430
+ return /*#__PURE__*/React.createElement(React.Fragment, null, visibleGroups.map(function (group, groupIdx) {
382
431
  return /*#__PURE__*/React.createElement("div", {
383
432
  key: group.groupName || "_group_".concat(groupIdx),
384
433
  "data-odn-skill-slot-group": true
@@ -403,8 +452,41 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
403
452
 
404
453
  /* 有 skillGroups:始终按组遍历(保序);无标题时仍走分组 DOM,仅不渲染 header,
405
454
  * bar/card 用 group-items 承接原有 flex/grid,避免拍平后丢组边界。 */
406
- var listBody = skillGroups ? renderGroups() : renderFlatItems();
455
+ var listBody = visibleGroups ? renderGroups() : renderFlatItems();
407
456
  var manageFooter = renderManageFooter();
457
+ var searchBar = searchableMenu ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
458
+ "data-odn-skill-slot-search": true,
459
+ onCompositionStart: function onCompositionStart() {
460
+ composingRef.current = true;
461
+ },
462
+ onCompositionEnd: function onCompositionEnd(e) {
463
+ composingRef.current = false;
464
+ var value = e.target instanceof HTMLInputElement ? e.target.value : searchInput;
465
+ setSearchInput(value);
466
+ setSearchQuery(value);
467
+ }
468
+ }, /*#__PURE__*/React.createElement(Input, {
469
+ light: true,
470
+ size: "small",
471
+ allowClear: true,
472
+ placeholder: "\u641C\u7D22\u6280\u80FD\u540D\u79F0\u6216\u63CF\u8FF0",
473
+ value: searchInput,
474
+ onChange: function onChange(e, value) {
475
+ setSearchInput(value);
476
+ var composing = composingRef.current || e.nativeEvent.isComposing;
477
+ if (composing) return;
478
+ setSearchQuery(value);
479
+ },
480
+ leftElement: /*#__PURE__*/React.createElement(Icon, {
481
+ name: "search",
482
+ size: 14
483
+ })
484
+ })), /*#__PURE__*/React.createElement("div", {
485
+ "data-odn-skill-slot-divider": true
486
+ })) : null;
487
+ var emptyState = searchableMenu && skills.length === 0 ? /*#__PURE__*/React.createElement("div", {
488
+ "data-odn-skill-slot-empty": true
489
+ }, "\u672A\u627E\u5230\u76F8\u5173\u6280\u80FD") : null;
408
490
 
409
491
  /* 共享的属性容器:menu variant 的 aria / keyboard / 交互属性 */
410
492
  var listAttrs = variant === 'menu' ? {
@@ -421,29 +503,33 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
421
503
  } : {}), variant !== 'menu' && itemWidth !== undefined && itemWidth !== null ? {
422
504
  '--odn-skill-slot-item-width': "".concat(itemWidth, "px")
423
505
  } : {}), style);
424
- if (hasScrollWrap) {
425
- /* 滚动仅包裹技能列表,footer 固定在底部不参与滚动 */
426
- return /*#__PURE__*/React.createElement("div", _extends({
506
+ if (variant !== 'menu') {
507
+ return /*#__PURE__*/React.createElement("div", {
427
508
  "data-odn-skill-slot": true,
428
509
  "data-odn-skill-slot-variant": variant,
429
510
  "data-odn-skill-slot-grouped": groupedWithTitles || undefined,
430
511
  className: className,
431
512
  style: rootStyle
432
- }, listAttrs), /*#__PURE__*/React.createElement("div", {
433
- "data-odn-skill-slot-scroll-wrap": true,
434
- style: {
435
- maxHeight: menuMaxHeight,
436
- overflowY: 'auto'
437
- }
438
- }, listBody), manageFooter);
513
+ }, listBody);
439
514
  }
440
- return /*#__PURE__*/React.createElement("div", _extends({
515
+
516
+ /* listbox 挂在列表容器上,搜索框 / footer 不进 listbox。 */
517
+ var menuList = /*#__PURE__*/React.createElement("div", _extends({
518
+ "data-odn-skill-slot-list": true
519
+ }, listAttrs), skills.length > 0 ? listBody : emptyState);
520
+ return /*#__PURE__*/React.createElement("div", {
441
521
  "data-odn-skill-slot": true,
442
522
  "data-odn-skill-slot-variant": variant,
443
523
  "data-odn-skill-slot-grouped": groupedWithTitles || undefined,
444
524
  className: className,
445
525
  style: rootStyle
446
- }, listAttrs), listBody, manageFooter);
526
+ }, searchBar, hasScrollWrap ? /*#__PURE__*/React.createElement("div", {
527
+ "data-odn-skill-slot-scroll-wrap": true,
528
+ style: {
529
+ maxHeight: menuMaxHeight,
530
+ overflowY: 'auto'
531
+ }
532
+ }, menuList) : menuList, manageFooter);
447
533
  });
448
534
  SkillSlot.displayName = 'SkillSlot';
449
535
  export default SkillSlot;
@@ -41,6 +41,76 @@
41
41
  outline: none;
42
42
  }
43
43
 
44
+ [data-odn-skill-slot-list] {
45
+ display: flex;
46
+ flex-direction: column;
47
+ width: 100%;
48
+ }
49
+ [data-odn-skill-slot-list]:focus, [data-odn-skill-slot-list]:focus-visible {
50
+ outline: none;
51
+ }
52
+
53
+ /* 顶栏滤镜行:与底部「技能管理」同构(无描边、热区不拉通栏)。
54
+ * 左右 4px 与菜单上下 padding 对齐;选择器压过 Input 默认描边 / 聚焦蓝框。 */
55
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] {
56
+ flex-shrink: 0;
57
+ margin: 0 4px;
58
+ }
59
+
60
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input] {
61
+ display: flex;
62
+ width: 100%;
63
+ min-width: 0;
64
+ box-sizing: border-box;
65
+ align-items: center;
66
+ gap: 8px;
67
+ padding: 8px 12px;
68
+ font-size: 13px;
69
+ line-height: 22px;
70
+ color: var(--odn-color-black-12);
71
+ border: none;
72
+ border-radius: 4px;
73
+ background: transparent;
74
+ box-shadow: none;
75
+ outline: none;
76
+ transition: background-color 0.15s;
77
+ }
78
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input][data-odn-input-focused=true] {
79
+ border: none;
80
+ box-shadow: none;
81
+ outline: none;
82
+ background: transparent;
83
+ }
84
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input]:hover {
85
+ background: rgba(33, 34, 38, 0.05);
86
+ }
87
+
88
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input-left-element] {
89
+ color: var(--odn-color-black-9);
90
+ }
91
+
92
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-search-mark] {
93
+ background: transparent;
94
+ color: var(--odn-color-brand-6);
95
+ padding: 0;
96
+ margin: 0;
97
+ border: 0;
98
+ border-radius: 0;
99
+ font: inherit;
100
+ letter-spacing: inherit;
101
+ word-spacing: inherit;
102
+ line-height: inherit;
103
+ vertical-align: baseline;
104
+ }
105
+
106
+ [data-odn-skill-slot-empty] {
107
+ padding: 20px 12px;
108
+ font-size: 13px;
109
+ line-height: 20px;
110
+ color: var(--odn-color-black-8);
111
+ text-align: center;
112
+ }
113
+
44
114
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item-wrap] {
45
115
  display: block;
46
116
  }
@@ -171,9 +241,11 @@
171
241
  display: flex;
172
242
  align-items: center;
173
243
  justify-content: space-between;
174
- width: 100%;
244
+ box-sizing: border-box;
245
+ margin: 0 4px;
175
246
  padding: 8px 12px;
176
247
  border: none;
248
+ border-radius: 4px;
177
249
  background: transparent;
178
250
  font-size: 13px;
179
251
  color: var(--odn-color-black-9);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "one-design-next",
3
- "version": "0.0.81",
3
+ "version": "0.0.83",
4
4
  "description": "One Design Next from TAD@tencent.com",
5
5
  "license": "MIT",
6
6
  "type": "module",