one-design-next 0.0.86 → 0.0.87

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.
@@ -16,10 +16,11 @@ import { Message } from "../message";
16
16
  import Popover from "../popover";
17
17
  import SkillSlot from "../skill-slot";
18
18
  import Tooltip from "../tooltip";
19
- import { ComposerEditor } from "./editor";
19
+ import { COMPOSER_EDITOR_MAX_ROWS_PX, ComposerEditor } from "./editor";
20
20
  import { chipHasEditableParams, isMentionChip } from "./inline-ref";
21
21
  import { rawValueToSegments, segmentsHasContent } from "./segments";
22
22
  import "./style";
23
+ import { filterSkillsByQuery, resolveComposerSkillMenuWidth, shouldOpenSlashTriggerMenu } from "./utils";
23
24
  function createInvocationChipData(skill) {
24
25
  return {
25
26
  id: "skill-".concat(skill.id, "-").concat(Date.now().toString(36), "-").concat(Math.random().toString(36).slice(2, 6)),
@@ -96,7 +97,7 @@ var DEFAULT_TOOL_CONFIGS = {
96
97
  */
97
98
 
98
99
  export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
99
- var _skillSlotProps$menuW, _paramEdit$chipId;
100
+ var _filteredSkillGroups$, _filteredSkills$lengt, _paramEdit$chipId;
100
101
  var valueProp = _ref.value,
101
102
  _ref$defaultValue = _ref.defaultValue,
102
103
  defaultValue = _ref$defaultValue === void 0 ? '' : _ref$defaultValue,
@@ -150,7 +151,6 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
150
151
  var onManageSkills = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.onManageSkills;
151
152
  var skillManageFooter = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.skillManageFooter;
152
153
  var resolvedMenuMaxHeight = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.menuMaxHeight;
153
- var resolvedMenuWidth = (_skillSlotProps$menuW = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.menuWidth) !== null && _skillSlotProps$menuW !== void 0 ? _skillSlotProps$menuW : 260;
154
154
  var showGroupTitles = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.showGroupTitles;
155
155
  var _useControlledState = useControlledState(defaultValue, valueProp),
156
156
  _useControlledState2 = _slicedToArray(_useControlledState, 2),
@@ -192,7 +192,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
192
192
  _useState10 = _slicedToArray(_useState9, 2),
193
193
  expanded = _useState10[0],
194
194
  setExpanded = _useState10[1];
195
- /** full 模式下由右上角按钮控制的 50vh 展开态。 */
195
+ /** 右上角按钮:拉到当前生效上限(10 行盒高,矮屏被整框 50vh 截住)。 */
196
196
  var _useState11 = useState(false),
197
197
  _useState12 = _slicedToArray(_useState11, 2),
198
198
  fullExpanded = _useState12[0],
@@ -202,8 +202,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
202
202
  _useState14 = _slicedToArray(_useState13, 2),
203
203
  editorHeight = _useState14[0],
204
204
  setEditorHeight = _useState14[1];
205
- /** editor 内容是否真正达到 maxRows 触顶——配合 SCSS 控制 viewport overflow,
206
- * 阻止升格 transition / 换行未触顶时的 scrollbar 闪现 */
205
+ /** 内容已超过 10 行,或整框 50vh 把编辑器挤到放不下当前内容 */
207
206
  var _useState15 = useState(false),
208
207
  _useState16 = _slicedToArray(_useState15, 2),
209
208
  atMax = _useState16[0],
@@ -211,6 +210,35 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
211
210
  var fileInputRef = useRef(null);
212
211
  var editorRef = useRef(null);
213
212
  var boxRef = useRef(null);
213
+ var skillTriggerRef = useRef(null);
214
+ var triggerAnchorRef = useRef(null);
215
+ var _useState17 = useState(0),
216
+ _useState18 = _slicedToArray(_useState17, 2),
217
+ skillMenuCrossAxis = _useState18[0],
218
+ setSkillMenuCrossAxis = _useState18[1];
219
+ /** composer box 实宽;技能菜单默认比它略窄。显式 menuWidth 覆盖。 */
220
+ var _useState19 = useState(0),
221
+ _useState20 = _slicedToArray(_useState19, 2),
222
+ boxWidth = _useState20[0],
223
+ setBoxWidth = _useState20[1];
224
+ useLayoutEffect(function () {
225
+ var el = boxRef.current;
226
+ if (!el) return undefined;
227
+ var sync = function sync() {
228
+ var next = el.getBoundingClientRect().width;
229
+ setBoxWidth(function (prev) {
230
+ return prev === next ? prev : next;
231
+ });
232
+ };
233
+ sync();
234
+ if (typeof ResizeObserver === 'undefined') return undefined;
235
+ var ro = new ResizeObserver(sync);
236
+ ro.observe(el);
237
+ return function () {
238
+ return ro.disconnect();
239
+ };
240
+ }, []);
241
+ var resolvedMenuWidth = resolveComposerSkillMenuWidth(skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.menuWidth, boxWidth);
214
242
  var handleComposerMouseDown = useCallback(function (event) {
215
243
  var _editorRef$current;
216
244
  if (disabled || event.button !== 0) return;
@@ -226,10 +254,10 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
226
254
  preventScroll: true
227
255
  });
228
256
  }, [disabled]);
229
- var _useState17 = useState(null),
230
- _useState18 = _slicedToArray(_useState17, 2),
231
- paramEdit = _useState18[0],
232
- setParamEdit = _useState18[1];
257
+ var _useState21 = useState(null),
258
+ _useState22 = _slicedToArray(_useState21, 2),
259
+ paramEdit = _useState22[0],
260
+ setParamEdit = _useState22[1];
233
261
  var openParamPanel = useCallback(function (chipId) {
234
262
  if (!renderParamPanel) return;
235
263
  requestAnimationFrame(function () {
@@ -369,13 +397,19 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
369
397
  }
370
398
  }, [text, attachments.length, expanded]);
371
399
 
372
- // lite 回落到单行后同步退出手动 50vh 展开态。
400
+ // lite 回落到单行后同步退出手动展开态。
373
401
  useEffect(function () {
374
402
  if (variant === 'lite' && !expanded) {
375
403
  setFullExpanded(false);
376
404
  }
377
405
  }, [variant, expanded]);
378
406
 
407
+ // 内容已顶满(满 10 行或整框 50vh 溢出)时展开态没有可切换的高度。
408
+ var atHeightCap = atMax || editorHeight >= COMPOSER_EDITOR_MAX_ROWS_PX;
409
+ useEffect(function () {
410
+ if (atHeightCap) setFullExpanded(false);
411
+ }, [atHeightCap]);
412
+
379
413
  /* ---- Send ----
380
414
  * 内部 text 是 raw value(chip 序列化为零宽 marker)。
381
415
  * 对外只有一份契约:`onSend(segments, meta?)`
@@ -564,10 +598,10 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
564
598
  * 不夺回焦点时 Enter 会再次激活按钮而不是发送 */
565
599
  (_editorRef$current15 = editorRef.current) === null || _editorRef$current15 === void 0 || _editorRef$current15.focus();
566
600
  };
567
- var _useState19 = useState(false),
568
- _useState20 = _slicedToArray(_useState19, 2),
569
- fileDragOver = _useState20[0],
570
- setFileDragOver = _useState20[1];
601
+ var _useState23 = useState(false),
602
+ _useState24 = _slicedToArray(_useState23, 2),
603
+ fileDragOver = _useState24[0],
604
+ setFileDragOver = _useState24[1];
571
605
  var composerBoxFileDragAttrs = fileDragOver ? {
572
606
  'data-odn-composer-file-drag': ''
573
607
  } : {};
@@ -617,39 +651,37 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
617
651
  }, [openParamPanel]);
618
652
 
619
653
  /* trigger query 实时筛选 skills(substring match,case-insensitive)。
620
- * 没有 query / 没有 trigger → 返回原列表。 */
654
+ * trigger 已结束时返回空列表,避免 Popover 关闭动画那一帧把子节点切回全量目录。
655
+ * 依赖 query / trigger 而不是整个 triggerInfo:rect 变化不应重建列表。 */
656
+ var slashTriggerActive = (triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.trigger) === '/';
657
+ var slashTriggerQuery = slashTriggerActive ? triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.query : undefined;
621
658
  var filteredSkills = useMemo(function () {
622
- var _triggerInfo$query;
623
659
  if (!skills) return undefined;
624
- var q = triggerInfo === null || triggerInfo === void 0 || (_triggerInfo$query = triggerInfo.query) === null || _triggerInfo$query === void 0 ? void 0 : _triggerInfo$query.trim().toLowerCase();
625
- if (!q) return skills;
626
- return skills.filter(function (s) {
627
- return s.label.toLowerCase().includes(q);
628
- });
629
- }, [skills, triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.query]);
660
+ if (!slashTriggerActive) return [];
661
+ return filterSkillsByQuery(skills, slashTriggerQuery);
662
+ }, [skills, slashTriggerActive, slashTriggerQuery]);
630
663
 
631
664
  /* grouped 版本的 trigger query 筛选:按组分别过滤,保留非空组。 */
632
665
  var filteredSkillGroups = useMemo(function () {
633
- var _triggerInfo$query2;
634
666
  if (!skillGroups) return undefined;
635
- var q = triggerInfo === null || triggerInfo === void 0 || (_triggerInfo$query2 = triggerInfo.query) === null || _triggerInfo$query2 === void 0 ? void 0 : _triggerInfo$query2.trim().toLowerCase();
636
- if (!q) return skillGroups;
667
+ if (!slashTriggerActive) return [];
637
668
  return skillGroups.map(function (g) {
638
669
  return {
639
670
  groupName: g.groupName,
640
- skills: g.skills.filter(function (s) {
641
- return s.label.toLowerCase().includes(q);
642
- })
671
+ skills: filterSkillsByQuery(g.skills, slashTriggerQuery)
643
672
  };
644
673
  }).filter(function (g) {
645
674
  return g.skills.length > 0;
646
675
  });
647
- }, [skillGroups, triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.query]);
676
+ }, [skillGroups, slashTriggerActive, slashTriggerQuery]);
648
677
 
649
- /* trigger 浮层渲染条件:editor 处于 `/` 触发态 + 业务方传了 skills 或 skillGroups。
650
- * `@` 触发本轮不接(mention 数据源待业务接入),onTriggerChange 拿到也不弹。 */
678
+ /* trigger 浮层:`/` 触发态 + 目录非空 + 过滤后仍有命中。
679
+ * 无匹配时菜单关闭,Enter 不再被当成「选 skill」。`@` 本轮不接。 */
651
680
  var hasSkills = !!(skills && skills.length > 0) || !!(skillGroups && skillGroups.length > 0);
652
- var triggerMenuVisible = !!(triggerInfo && triggerInfo.trigger === '/' && hasSkills);
681
+ var triggerHitCount = skillGroups ? (_filteredSkillGroups$ = filteredSkillGroups === null || filteredSkillGroups === void 0 ? void 0 : filteredSkillGroups.reduce(function (n, g) {
682
+ return n + g.skills.length;
683
+ }, 0)) !== null && _filteredSkillGroups$ !== void 0 ? _filteredSkillGroups$ : 0 : (_filteredSkills$lengt = filteredSkills === null || filteredSkills === void 0 ? void 0 : filteredSkills.length) !== null && _filteredSkills$lengt !== void 0 ? _filteredSkills$lengt : 0;
684
+ var triggerMenuVisible = shouldOpenSlashTriggerMenu(triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.trigger, hasSkills ? triggerHitCount : 0);
653
685
 
654
686
  /* query 变化或浮层关闭 → 高亮项重置到「最上方的可用项」(跳过 disabled)。
655
687
  * 用 query 而不是 triggerInfo 整体当依赖:rect 变化(光标移位)不应该 reset。 */
@@ -691,7 +723,8 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
691
723
  var _skill = (_ref6 = (_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]) !== null && _ref6 !== void 0 ? _ref6 : filteredSkillGroups === null || filteredSkillGroups === void 0 ? void 0 : filteredSkillGroups.flatMap(function (g) {
692
724
  return g.skills;
693
725
  })[triggerActiveIndex];
694
- if (_skill) handleSelectSkill(_skill, 'trigger');
726
+ if (!_skill) return false;
727
+ handleSelectSkill(_skill, 'trigger');
695
728
  return true;
696
729
  }
697
730
  if (e.key === 'Escape') {
@@ -704,29 +737,6 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
704
737
  return false;
705
738
  }, [filteredSkills, filteredSkillGroups, handleSelectSkill, triggerActiveIndex, triggerMenuVisible]);
706
739
 
707
- /* trigger 浮 anchor:跟随 caret 的 0×0 隐形元素,给 Popover 当 children。
708
- * Popover 需要 DOM children 当 reference,没有"虚拟 anchor" API,只能这么搞。
709
- *
710
- * 使用 position: absolute 而非 fixed——Drawer 等容器的 transform 会创建
711
- * 新的 containing block,导致 fixed 定位失准。absolute 相对于最近 positioned
712
- * 祖先([data-odn-composer] 有 position:relative),通过减去 offsetParent
713
- * 的 viewport rect 得到正确的局部坐标。 */
714
- var triggerAnchorRef = useRef(null);
715
- useLayoutEffect(function () {
716
- var el = triggerAnchorRef.current;
717
- if (!el || !triggerInfo) return;
718
- var offsetParent = el.offsetParent;
719
- if (offsetParent) {
720
- var parentRect = offsetParent.getBoundingClientRect();
721
- el.style.left = "".concat(triggerInfo.rect.left - parentRect.left, "px");
722
- el.style.top = "".concat(triggerInfo.rect.top - parentRect.top, "px");
723
- } else {
724
- // fallback:没有 offsetParent(比如 display:none),用 viewport 坐标
725
- el.style.left = "".concat(triggerInfo.rect.left, "px");
726
- el.style.top = "".concat(triggerInfo.rect.top, "px");
727
- }
728
- }, [triggerInfo]);
729
-
730
740
  /* ---- Derived ---- */
731
741
  var hasUploadingAttachment = attachments.some(function (a) {
732
742
  var _a$status2;
@@ -740,6 +750,29 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
740
750
  var effectiveLabeled = isLite ? false : toolsLabeled;
741
751
  // lite 模式下仅允许一个工具按钮(数量在两态下保持恒定)
742
752
  var effectiveTools = isLite ? tools.slice(0, 1) : tools;
753
+ useLayoutEffect(function () {
754
+ var el = triggerAnchorRef.current;
755
+ var box = boxRef.current;
756
+ if (!el || !box || !triggerInfo) return;
757
+ var boxRect = box.getBoundingClientRect();
758
+ el.style.top = "".concat(triggerInfo.rect.top - boxRect.top, "px");
759
+ }, [triggerInfo]);
760
+ useLayoutEffect(function () {
761
+ var box = boxRef.current;
762
+ var trigger = skillTriggerRef.current;
763
+ if (!box || !trigger) {
764
+ setSkillMenuCrossAxis(function (prev) {
765
+ return prev === 0 ? prev : 0;
766
+ });
767
+ return;
768
+ }
769
+ var boxRect = box.getBoundingClientRect();
770
+ var triggerRect = trigger.getBoundingClientRect();
771
+ var next = Math.round(boxRect.left + boxRect.width / 2 - (triggerRect.left + triggerRect.width / 2));
772
+ setSkillMenuCrossAxis(function (prev) {
773
+ return prev === next ? prev : next;
774
+ });
775
+ }, [boxWidth, toolMenuOpen, effectiveLabeled, isLite, expanded, fullExpanded]);
743
776
 
744
777
  /* 统一解析 toolConfigs:
745
778
  * - key 未配置 → 整份默认
@@ -891,12 +924,17 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
891
924
  visible: toolMenuOpen ? false : undefined,
892
925
  transitionDuration: toolMenuOpen ? 0 : undefined
893
926
  }, /*#__PURE__*/React.createElement("div", {
927
+ ref: skillTriggerRef,
894
928
  "data-odn-composer-skill-trigger": true
895
929
  }, /*#__PURE__*/React.createElement(Popover, {
896
930
  trigger: "click",
897
931
  arrowed: false,
898
- placement: "topLeft",
899
- offset: 4,
932
+ placement: "top",
933
+ shift: false,
934
+ offset: {
935
+ mainAxis: 4,
936
+ crossAxis: skillMenuCrossAxis
937
+ },
900
938
  visible: !disabled && toolMenuOpen,
901
939
  onVisibleChange: function onVisibleChange(v) {
902
940
  if (disabled) return;
@@ -1089,7 +1127,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
1089
1127
  },
1090
1128
  onPressEnter: function onPressEnter(e) {
1091
1129
  if (e.nativeEvent.isComposing || e.keyCode === 229) return;
1092
- // trigger 浮层激活时,普通 Enter 留给浮层做选择;Shift+Enter 继续换行
1130
+ // trigger 浮层有可选中项时,普通 Enter 留给浮层做选择;Shift+Enter 继续换行
1093
1131
  if (triggerMenuVisible && !e.shiftKey) {
1094
1132
  e.preventDefault();
1095
1133
  return false;
@@ -1106,12 +1144,13 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
1106
1144
  }
1107
1145
  });
1108
1146
 
1109
- /* trigger 浮层:anchor 是 fixed 定位的 0×0 隐形 div,跟随 caret 处的 trigger rect。
1110
- * 共用 SkillSlot 渲染,过滤后的列表为空时仍显示 menu(empty 视觉由 SkillSlot 自己处理)。 */
1147
+ /* `/` 浮层:anchor 水平铺满 composer box、竖直跟 caret,菜单与输入框水平居中。
1148
+ * 共用 SkillSlot 渲染;过滤后无命中时 triggerMenuVisible=false,不挂菜单。 */
1111
1149
  var triggerMenu = hasSkills ? /*#__PURE__*/React.createElement(Popover, {
1112
1150
  trigger: "click",
1113
1151
  arrowed: false,
1114
- placement: "topLeft",
1152
+ placement: "top",
1153
+ shift: false,
1115
1154
  offset: 4,
1116
1155
  visible: triggerMenuVisible,
1117
1156
  onVisibleChange: function onVisibleChange(v) {
@@ -1143,7 +1182,9 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
1143
1182
  "data-odn-composer-trigger-anchor": true,
1144
1183
  style: {
1145
1184
  position: 'absolute',
1146
- width: 0,
1185
+ left: 0,
1186
+ right: 0,
1187
+ top: 0,
1147
1188
  height: 0,
1148
1189
  pointerEvents: 'none'
1149
1190
  },
@@ -1152,6 +1193,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
1152
1193
 
1153
1194
  // lite:有附件即强制升格(否则按钮会和附件/多行内容打架)
1154
1195
  var liteExpanded = isLite && !isInlineLite;
1196
+ var showExpandBtn = !isInlineLite && !atHeightCap;
1155
1197
  var fullExpandLabel = fullExpanded ? '收起输入框' : '展开输入框';
1156
1198
  return /*#__PURE__*/React.createElement("div", {
1157
1199
  "data-odn-composer": true,
@@ -1159,6 +1201,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
1159
1201
  "data-odn-composer-expanded": liteExpanded ? '' : undefined,
1160
1202
  "data-odn-composer-full-expanded": fullExpanded ? '' : undefined,
1161
1203
  "data-odn-composer-atmax": atMax ? '' : undefined,
1204
+ "data-odn-composer-height-cap": atHeightCap ? '' : undefined,
1162
1205
  "data-odn-composer-disabled": disabled || undefined,
1163
1206
  "data-odn-composer-spacing": spacing === 'drawer' ? 'drawer' : undefined,
1164
1207
  "aria-disabled": disabled || undefined,
@@ -1167,7 +1210,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
1167
1210
  onMouseDown: handleComposerMouseDown
1168
1211
  }, /*#__PURE__*/React.createElement("div", {
1169
1212
  "data-odn-composer-inner": true
1170
- }, hiddenFileInput, triggerMenu, paramPanelNode, /*#__PURE__*/React.createElement("div", _extends({
1213
+ }, hiddenFileInput, paramPanelNode, /*#__PURE__*/React.createElement("div", _extends({
1171
1214
  ref: boxRef,
1172
1215
  "data-odn-composer-box": true
1173
1216
  }, composerBoxFileDragAttrs, {
@@ -1175,7 +1218,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
1175
1218
  onDragOver: handleComposerDragOver,
1176
1219
  onDragLeave: handleComposerDragLeave,
1177
1220
  onDrop: handleComposerDrop
1178
- }), !isInlineLite && /*#__PURE__*/React.createElement(Tooltip, {
1221
+ }), triggerMenu, showExpandBtn && /*#__PURE__*/React.createElement(Tooltip, {
1179
1222
  popup: fullExpandLabel,
1180
1223
  placement: "top"
1181
1224
  }, /*#__PURE__*/React.createElement("button", {
@@ -31,6 +31,11 @@
31
31
  --odn-composer-expand-control-offset: 38px;
32
32
  }
33
33
 
34
+ /* 满 10 行或溢出后缩放按钮隐藏,右上角不必再给按钮留位。 */
35
+ [data-odn-composer][data-odn-composer-height-cap] {
36
+ --odn-composer-expand-control-offset: 6px;
37
+ }
38
+
34
39
  [data-odn-composer-box] {
35
40
  position: relative;
36
41
  display: flex;
@@ -168,7 +173,7 @@
168
173
  padding-bottom: 42px;
169
174
  }
170
175
 
171
- /* ─────────── scrollbar 显隐控制(lite 三重保险) ───────────
176
+ /* ─────────── scrollbar 显隐控制 ───────────
172
177
  * base-ui 的 ScrollArea autohide 默认在 root hover / scrolling 时会把
173
178
  * [data-hovering] / [data-scrolling] 标到 Scrollbar 上,opacity 切到 1,
174
179
  * 那条灰色 thumb 就显出来。这是 ScrollArea 的预设行为,对 chat composer
@@ -180,13 +185,13 @@
180
185
  * 3. thumb 背景透明(即便前两条都被压,看到的也是空 div)
181
186
  *
182
187
  * 触顶(atmax)时三条全部回滚,由 ScrollArea autohide 接管。 */
183
- [data-odn-composer][data-odn-composer-variant=lite] [data-odn-scroll-area-scrollbar] {
188
+ [data-odn-composer]:not([data-odn-composer-atmax]) [data-odn-scroll-area-scrollbar] {
184
189
  display: none !important;
185
190
  opacity: 0 !important;
186
191
  pointer-events: none !important;
187
192
  }
188
193
 
189
- [data-odn-composer][data-odn-composer-variant=lite] [data-odn-scroll-area-thumb] {
194
+ [data-odn-composer]:not([data-odn-composer-atmax]) [data-odn-scroll-area-thumb] {
190
195
  background-color: transparent !important;
191
196
  }
192
197
 
@@ -194,20 +199,20 @@
194
199
  * 不能用 `revert` —— revert 跳过整个 author origin,连 ODN ScrollArea
195
200
  * 的 `opacity: 0` / thumb `background-color: var(--odn-scrollbar-...)`
196
201
  * 都会被擦掉,结果是 thumb 元素存在但没颜色(看不到 bar)。 */
197
- [data-odn-composer][data-odn-composer-variant=lite][data-odn-composer-atmax] [data-odn-scroll-area-scrollbar] {
202
+ [data-odn-composer][data-odn-composer-atmax] [data-odn-scroll-area-scrollbar] {
198
203
  display: flex !important;
199
204
  opacity: 0 !important;
200
205
  pointer-events: none !important;
201
206
  transition: opacity 0.18s ease;
202
207
  }
203
208
 
204
- [data-odn-composer][data-odn-composer-variant=lite][data-odn-composer-atmax] [data-odn-scroll-area-scrollbar][data-hovering],
205
- [data-odn-composer][data-odn-composer-variant=lite][data-odn-composer-atmax] [data-odn-scroll-area-scrollbar][data-scrolling] {
209
+ [data-odn-composer][data-odn-composer-atmax] [data-odn-scroll-area-scrollbar][data-hovering],
210
+ [data-odn-composer][data-odn-composer-atmax] [data-odn-scroll-area-scrollbar][data-scrolling] {
206
211
  opacity: 1 !important;
207
212
  pointer-events: auto !important;
208
213
  }
209
214
 
210
- [data-odn-composer][data-odn-composer-variant=lite][data-odn-composer-atmax] [data-odn-scroll-area-thumb] {
215
+ [data-odn-composer][data-odn-composer-atmax] [data-odn-scroll-area-thumb] {
211
216
  background-color: var(--odn-scrollbar-background-color) !important;
212
217
  }
213
218
 
@@ -225,6 +230,7 @@
225
230
  flex: 1 1 auto;
226
231
  min-height: 0;
227
232
  overflow: hidden;
233
+ max-height: 242px;
228
234
  }
229
235
 
230
236
  [data-odn-composer] [data-odn-composer-editor] {
@@ -509,5 +515,6 @@ p[data-odn-composer-hint] {
509
515
 
510
516
  [data-odn-composer][data-odn-composer-full-expanded] [data-odn-composer-box] {
511
517
  box-sizing: border-box;
512
- min-height: 50vh;
518
+ /* 拉到当前上限:10 行盒高 302px(242 编辑器 + 60 chrome)与 50vh 取小。 */
519
+ min-height: min(302px, 50vh);
513
520
  }
@@ -101,7 +101,7 @@ export declare function isEditorEmpty(editor: HTMLElement, chipIdAttr?: string):
101
101
  * 之后空 text / chip 前 leading br 的清理逻辑不变(TreeWalker / querySelectorAll
102
102
  * 在 line div 内部一样工作)。
103
103
  */
104
- export declare function normalizeEditorDom(editor: HTMLElement, _chipIdAttr?: string): void;
104
+ export declare function normalizeEditorDom(editor: HTMLElement): void;
105
105
  export interface TriggerProbe {
106
106
  /** 触发字符 */
107
107
  trigger: string;
@@ -117,3 +117,15 @@ export interface TriggerProbe {
117
117
  export declare function probeTrigger(range: Range, triggers: readonly string[],
118
118
  /** caret 与触发字符之间允许的最大 query 字符数;默认 50 */
119
119
  maxQueryLen?: number): TriggerProbe | null;
120
+ /** 按 trigger query 做 label 子串匹配(忽略大小写)。query 为空则返回原列表。 */
121
+ export declare function filterSkillsByQuery<T extends {
122
+ label: string;
123
+ }>(items: T[], query?: string | null): T[];
124
+ /** `/` 技能菜单只在「有可选中的过滤结果」时打开;空匹配不应拦截 Enter。 */
125
+ export declare function shouldOpenSlashTriggerMenu(trigger: string | null | undefined, hitCount: number): boolean;
126
+ /** 技能菜单比 composer box 左右各缩 12px。 */
127
+ export declare const COMPOSER_SKILL_MENU_INSET_PX = 24;
128
+ /** box 尚未量到宽度时的兜底(与旧默认对齐)。 */
129
+ export declare const COMPOSER_SKILL_MENU_FALLBACK_WIDTH = 260;
130
+ /** 未显式传 menuWidth 时,菜单比 composer box 略窄。 */
131
+ export declare function resolveComposerSkillMenuWidth(explicit: number | undefined, boxWidth: number): number;
@@ -294,6 +294,11 @@ export function getPlainText(editor) {
294
294
  }
295
295
  return out;
296
296
  }
297
+ var kebab = function kebab(s) {
298
+ return s.replace(/[A-Z]/g, function (m) {
299
+ return "-".concat(m.toLowerCase());
300
+ });
301
+ };
297
302
 
298
303
  /**
299
304
  * editor 是否为完全空(无 chip + 文本剔除 ZWSP / 换行后为空)。
@@ -310,16 +315,6 @@ export function isEditorEmpty(editor) {
310
315
  // 仅过滤换行符(contenteditable 默认 <br> 单独存在时 textContent 是 '\n',视觉仍为空)
311
316
  return text.replace(/\n/g, '') === '';
312
317
  }
313
- var kebab = function kebab(s) {
314
- return s.replace(/[A-Z]/g, function (m) {
315
- return "-".concat(m.toLowerCase());
316
- });
317
- };
318
- var makeIsHost = function makeIsHost(chipIdAttr) {
319
- return function (el) {
320
- return el instanceof HTMLElement && el.dataset[chipIdAttr] !== undefined;
321
- };
322
- };
323
318
 
324
319
  /* ────────────────────────────────────────────────────────────────────
325
320
  * DOM 不变量 · chip 后 ZWSP 锚点 + 行首占位清理
@@ -338,7 +333,6 @@ var makeIsHost = function makeIsHost(chipIdAttr) {
338
333
  * 在 line div 内部一样工作)。
339
334
  */
340
335
  export function normalizeEditorDom(editor) {
341
- var _chipIdAttr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mentionId';
342
336
  ensureLineRoots(editor);
343
337
  var emptyTexts = [];
344
338
  var orphanAnchorTexts = [];
@@ -491,4 +485,32 @@ export function probeTrigger(range, triggers) {
491
485
  triggerOffset: triggerOffset,
492
486
  caretOffset: caret
493
487
  };
488
+ }
489
+
490
+ /** 按 trigger query 做 label 子串匹配(忽略大小写)。query 为空则返回原列表。 */
491
+ export function filterSkillsByQuery(items, query) {
492
+ var q = query === null || query === void 0 ? void 0 : query.trim().toLowerCase();
493
+ if (!q) return items;
494
+ return items.filter(function (s) {
495
+ return s.label.toLowerCase().includes(q);
496
+ });
497
+ }
498
+
499
+ /** `/` 技能菜单只在「有可选中的过滤结果」时打开;空匹配不应拦截 Enter。 */
500
+ export function shouldOpenSlashTriggerMenu(trigger, hitCount) {
501
+ return trigger === '/' && hitCount > 0;
502
+ }
503
+
504
+ /** 技能菜单比 composer box 左右各缩 12px。 */
505
+ export var COMPOSER_SKILL_MENU_INSET_PX = 24;
506
+ /** box 尚未量到宽度时的兜底(与旧默认对齐)。 */
507
+ export var COMPOSER_SKILL_MENU_FALLBACK_WIDTH = 260;
508
+
509
+ /** 未显式传 menuWidth 时,菜单比 composer box 略窄。 */
510
+ export function resolveComposerSkillMenuWidth(explicit, boxWidth) {
511
+ if (explicit !== undefined) return explicit;
512
+ if (boxWidth > COMPOSER_SKILL_MENU_INSET_PX) {
513
+ return Math.round(boxWidth - COMPOSER_SKILL_MENU_INSET_PX);
514
+ }
515
+ return COMPOSER_SKILL_MENU_FALLBACK_WIDTH;
494
516
  }
package/dist/index.d.ts CHANGED
@@ -58,7 +58,7 @@ export { APPROVAL_DEFAULT_ACTIONS_HINT, default as Approval, type ApprovalDecisi
58
58
  export { ARTIFACT_DEDICATED_FILE_ICON_TYPES, ARTIFACT_FILE_GLOW_COLORS, ARTIFACT_FILE_ICON_TYPES, default as Artifact, ArtifactFileIcon, getArtifactAction, getArtifactActionFromKind, getArtifactGlowColor, type ArtifactAction, type ArtifactActionKind, type ArtifactFileIconProps, type ArtifactFileIconSize, type ArtifactFileIconType, type ArtifactProps, type ArtifactSize, } from './artifact';
59
59
  export { default as Attachments, type AttachmentsProps } from './attachments';
60
60
  export { default as ChatItem, formatRelativeTime, type ChatItemProps, } from './chat-item';
61
- export { default as ChatList, matchChatListFilterTime, matchChatListFilterType, type ChatListFilterTime, type ChatListFilterType, type ChatListItem, type ChatListProps, } from './chat-list';
61
+ export { default as ChatList, matchChatListFilterStatus, matchChatListFilterTime, matchChatListFilterType, type ChatListFilterStatus, type ChatListFilterTime, type ChatListFilterType, type ChatListItem, type ChatListProps, } from './chat-list';
62
62
  export { CLARIFY_DOCK_MAX_OPTIONS, CLARIFY_DOCK_SKIPPED_LABEL, CLARIFY_DOCK_SKIPPED_VALUE, ClarifyDockPanel, ClarifyDockTrigger, clarifyDockLayoutId, parseClarifyBoundDate, toISODate, useClarifyDockState, type ClarifyDockAnswerItem, type ClarifyDockHostMessage, type ClarifyDockMessageState, type ClarifyDockPanelProps, type ClarifyDockQuestion, type ClarifyDockResolvePayload, type ClarifyDockSpec, type ClarifyDockState, type ClarifyDockTriggerProps, type ClarifyInput, type ClarifyOption, } from './clarify-dock';
63
63
  export { default as CodeBlock, type CodeBlockProps } from './code-block';
64
64
  export { default as Composer, type ComposerProps, type ComposerRef, type ComposerSkillSlotProps, type ComposerTool, type ComposerToolConfig, } from './composer';
package/dist/index.js CHANGED
@@ -64,7 +64,7 @@ export { APPROVAL_DEFAULT_ACTIONS_HINT, default as Approval } from "./approval";
64
64
  export { ARTIFACT_DEDICATED_FILE_ICON_TYPES, ARTIFACT_FILE_GLOW_COLORS, ARTIFACT_FILE_ICON_TYPES, default as Artifact, ArtifactFileIcon, getArtifactAction, getArtifactActionFromKind, getArtifactGlowColor } from "./artifact";
65
65
  export { default as Attachments } from "./attachments";
66
66
  export { default as ChatItem, formatRelativeTime } from "./chat-item";
67
- export { default as ChatList, matchChatListFilterTime, matchChatListFilterType } from "./chat-list";
67
+ export { default as ChatList, matchChatListFilterStatus, matchChatListFilterTime, matchChatListFilterType } from "./chat-list";
68
68
  export { CLARIFY_DOCK_MAX_OPTIONS, CLARIFY_DOCK_SKIPPED_LABEL, CLARIFY_DOCK_SKIPPED_VALUE, ClarifyDockPanel, ClarifyDockTrigger, clarifyDockLayoutId, parseClarifyBoundDate, toISODate, useClarifyDockState } from "./clarify-dock";
69
69
  export { default as CodeBlock } from "./code-block";
70
70
  export { default as Composer } from "./composer";
@@ -24,15 +24,16 @@ export interface SkillSlotProps {
24
24
  value?: string | null;
25
25
  onSelect?: (skill: SkillItem) => void;
26
26
  /**
27
- * 技能管理回调。提供后 menu 变体会在列表底部渲染按钮。
28
- * 按钮内容默认显示「技能管理」文字 + 右箭头图标。
27
+ * 技能管理回调。提供后 menu 变体渲染「技能管理」按钮:
28
+ * 有搜索栏时在搜索行最右侧 hug,无搜索时仍在列表底部。
29
+ * 按钮内容默认左侧管理 icon +「技能管理」文字。
29
30
  * (`skillManageFooter` 为非推荐逃生舱,见该 prop 注释。)
30
31
  */
31
32
  onManageSkills?: () => void;
32
33
  /**
33
34
  * 技能管理按钮的自定义内容(ReactNode)。
34
35
  * 仅在 `onManageSkills` 提供且 `variant='menu'` 时生效。
35
- * 不传时默认渲染「技能管理」文字 + 右箭头图标。
36
+ * 不传时默认渲染左侧管理 icon +「技能管理」文字。
36
37
  * 非推荐:徽标 / 待处理数等业务状态应留在技能管理,勿经此槽塞进选 skill 菜单。
37
38
  */
38
39
  skillManageFooter?: React.ReactNode;