one-design-next 0.0.72 → 0.0.74

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.
@@ -7,9 +7,11 @@ export interface AutoHeightProps extends React.HTMLAttributes<HTMLDivElement> {
7
7
  anchor?: 'top' | 'bottom';
8
8
  /**
9
9
  * 内容身份键(如翻页 page)。
10
- * 变化时走旧题渐隐 新题渐现;有高度差时同步高度。
10
+ * 变化时按等高 / 不等高走不同过渡。
11
11
  */
12
12
  contentKey?: React.Key;
13
+ /** 翻页过渡结束(或首次就绪)后回调;用于延后选项 autofocus。 */
14
+ onSettle?: () => void;
13
15
  }
14
16
  /**
15
17
  * 只动画「题面内容」的高度与透明度;卡面描边应由外层稳定承载,勿把整卡塞进来做 opacity。
@@ -18,4 +20,4 @@ export interface AutoHeightProps extends React.HTMLAttributes<HTMLDivElement> {
18
20
  * `readyRef` 钉死或把 outer 压成 0,重开后不再测量就会「只剩底栏」。
19
21
  * 因此:未就绪时忽略 0 高;已就绪时忽略「从正高塌到 0」;重现正高时再同步。
20
22
  */
21
- export declare function AutoHeight({ children, anchor, contentKey, style, ...props }: AutoHeightProps): React.JSX.Element;
23
+ export declare function AutoHeight({ children, anchor, contentKey, onSettle, style, ...props }: AutoHeightProps): React.JSX.Element;
@@ -3,20 +3,27 @@ import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
3
3
  import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
4
4
  import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
5
5
  import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
6
- var _excluded = ["children", "anchor", "contentKey", "style"];
6
+ var _excluded = ["children", "anchor", "contentKey", "onSettle", "style"];
7
7
  import React, { useLayoutEffect, useRef, useState } from 'react';
8
8
 
9
9
  /**
10
- * 翻页(contentKey 变化):
11
- * 旧题渐隐 → 新题渐现(不交叉,避免半透明叠字)
12
- * 有高度差时,高度与整段同起同止;等高则只做显隐。
10
+ * 翻页(contentKey 变化)分两套:
11
+ *
12
+ * 等高:同位短交叉淡(无空窗、不动高度、无遮罩底板)
13
+ * 不等高:高度同步 + 内容交叉淡;旧层用不透明底板防错位叠字
13
14
  */
14
- var FADE_OUT_MS = 140;
15
- var FADE_IN_MS = 180;
16
- var HEIGHT_MS = FADE_OUT_MS + FADE_IN_MS;
17
- var FADE_IN_DELAY_MS = FADE_OUT_MS;
18
- var TOTAL_MS = HEIGHT_MS;
15
+
16
+ /** 等高 · 交叉淡(旧/新同起同止,无空窗) */
17
+ var EQUAL_FADE_MS = 200;
18
+ var EQUAL_TOTAL_MS = EQUAL_FADE_MS;
19
+
20
+ /** 不等高 · 高度主导;内容交叉淡同样 0 延迟 */
21
+ var UNEQUAL_HEIGHT_MS = 320;
22
+ var UNEQUAL_FADE_MS = 200;
23
+ var UNEQUAL_TOTAL_MS = UNEQUAL_HEIGHT_MS;
19
24
  var HEIGHT_EPS = 0.5;
25
+ /** 几乎看不出的高度差并入等高,避免无意义高度动画 */
26
+ var EQUAL_HEIGHT_SLACK = 2;
20
27
  var EASING = 'ease-in-out';
21
28
  /**
22
29
  * 只动画「题面内容」的高度与透明度;卡面描边应由外层稳定承载,勿把整卡塞进来做 opacity。
@@ -30,6 +37,7 @@ export function AutoHeight(_ref) {
30
37
  _ref$anchor = _ref.anchor,
31
38
  anchor = _ref$anchor === void 0 ? 'top' : _ref$anchor,
32
39
  contentKey = _ref.contentKey,
40
+ onSettle = _ref.onSettle,
33
41
  style = _ref.style,
34
42
  props = _objectWithoutProperties(_ref, _excluded);
35
43
  var outerRef = useRef(null);
@@ -40,6 +48,8 @@ export function AutoHeight(_ref) {
40
48
  var heightRef = useRef(0);
41
49
  var prevKeyRef = useRef(contentKey);
42
50
  var prevChildrenRef = useRef(children);
51
+ var onSettleRef = useRef(onSettle);
52
+ onSettleRef.current = onSettle;
43
53
  var _useState = useState(null),
44
54
  _useState2 = _slicedToArray(_useState, 2),
45
55
  outgoing = _useState2[0],
@@ -59,7 +69,7 @@ export function AutoHeight(_ref) {
59
69
  outer.style.transition = '';
60
70
  outer.style.height = "".concat(start, "px");
61
71
  void outer.offsetHeight;
62
- outer.style.transition = "height ".concat(HEIGHT_MS, "ms ").concat(EASING);
72
+ outer.style.transition = "height ".concat(UNEQUAL_HEIGHT_MS, "ms ").concat(EASING);
63
73
  outer.style.height = "".concat(target, "px");
64
74
  };
65
75
 
@@ -67,6 +77,10 @@ export function AutoHeight(_ref) {
67
77
  var isCollapsedMeasure = function isCollapsedMeasure(h) {
68
78
  return h < HEIGHT_EPS;
69
79
  };
80
+ var prefersReducedMotion = function prefersReducedMotion() {
81
+ var _window$matchMedia, _window;
82
+ return typeof window !== 'undefined' && ((_window$matchMedia = (_window = window).matchMedia) === null || _window$matchMedia === void 0 ? void 0 : _window$matchMedia.call(_window, '(prefers-reduced-motion: reduce)').matches);
83
+ };
70
84
  useLayoutEffect(function () {
71
85
  var outer = outerRef.current;
72
86
  var current = currentRef.current;
@@ -86,9 +100,11 @@ export function AutoHeight(_ref) {
86
100
  var h = measure();
87
101
  /* 抽屉刚挂载时常量到 0:先不 ready,等 ResizeObserver 拿到正高 */
88
102
  if (!isCollapsedMeasure(h)) {
103
+ var _onSettleRef$current;
89
104
  outer.style.height = "".concat(h, "px");
90
105
  heightRef.current = h;
91
106
  readyRef.current = true;
107
+ (_onSettleRef$current = onSettleRef.current) === null || _onSettleRef$current === void 0 || _onSettleRef$current.call(onSettleRef);
92
108
  }
93
109
  prevKeyRef.current = contentKey;
94
110
  prevChildrenRef.current = children;
@@ -101,7 +117,6 @@ export function AutoHeight(_ref) {
101
117
  var oldChildren = prevChildrenRef.current;
102
118
  prevKeyRef.current = contentKey;
103
119
  prevChildrenRef.current = children;
104
- /* 等高 / 不等高都走渐隐渐现;高度差在 outgoing 阶段再处理 */
105
120
  setOutgoing(oldChildren);
106
121
  return function () {
107
122
  return outer.removeEventListener('transitionend', onTransitionEnd);
@@ -128,8 +143,7 @@ export function AutoHeight(_ref) {
128
143
  }, [children, contentKey, outgoing]);
129
144
 
130
145
  /**
131
- * 旧题先隐尽,再显新题——中间不交叉半透明,避免叠字鬼影。
132
- * 有高度差时高度整段同步;等高只做显隐。旧层用不透明底盖住下层。
146
+ * 按等高 / 不等高分流过渡;均避免「隐尽 → 空窗 → 再显」。
133
147
  */
134
148
  useLayoutEffect(function () {
135
149
  if (outgoing === null || outgoing === undefined) return;
@@ -139,7 +153,24 @@ export function AutoHeight(_ref) {
139
153
  if (!outer || !current) return;
140
154
  var start = heightRef.current;
141
155
  var target = current.getBoundingClientRect().height;
142
- var heightChanged = Math.abs(start - target) >= HEIGHT_EPS;
156
+ var heightChanged = Math.abs(start - target) >= EQUAL_HEIGHT_SLACK;
157
+ var reduced = prefersReducedMotion();
158
+ if (reduced || isCollapsedMeasure(target)) {
159
+ var _onSettleRef$current2;
160
+ finishHeight(outer, isCollapsedMeasure(target) ? start : target);
161
+ current.style.transition = '';
162
+ current.style.opacity = '1';
163
+ if (outEl) {
164
+ outEl.style.transition = '';
165
+ outEl.style.opacity = '';
166
+ outEl.style.visibility = '';
167
+ outEl.style.background = '';
168
+ }
169
+ setOutgoing(null);
170
+ animatingRef.current = false;
171
+ (_onSettleRef$current2 = onSettleRef.current) === null || _onSettleRef$current2 === void 0 || _onSettleRef$current2.call(onSettleRef);
172
+ return;
173
+ }
143
174
  animatingRef.current = true;
144
175
  outer.style.transition = '';
145
176
  outer.style.height = "".concat(start, "px");
@@ -148,38 +179,63 @@ export function AutoHeight(_ref) {
148
179
  if (outEl) {
149
180
  outEl.style.transition = 'none';
150
181
  outEl.style.opacity = '1';
182
+ outEl.style.visibility = '';
183
+ /* 仅不等高盖底板;等高交叉淡保持透明 */
184
+ outEl.style.background = heightChanged ? 'var(--odn-color-bg-elevated)' : 'transparent';
151
185
  }
152
186
  void outer.offsetHeight;
153
- if (heightChanged && !isCollapsedMeasure(target)) {
154
- outer.style.transition = "height ".concat(HEIGHT_MS, "ms ").concat(EASING);
155
- outer.style.height = "".concat(target, "px");
156
- }
157
- if (outEl) {
158
- outEl.style.transition = "opacity ".concat(FADE_OUT_MS, "ms ").concat(EASING);
159
- outEl.style.opacity = '0';
160
- }
161
- var fadeInTimer = window.setTimeout(function () {
187
+ var doneTimer = 0;
188
+ if (!heightChanged) {
189
+ /* —— 等高:同位交叉淡,无遮罩、不动高度 —— */
162
190
  if (outEl) {
163
- outEl.style.visibility = 'hidden';
191
+ outEl.style.transition = "opacity ".concat(EQUAL_FADE_MS, "ms ").concat(EASING);
192
+ outEl.style.opacity = '0';
164
193
  }
165
- current.style.transition = "opacity ".concat(FADE_IN_MS, "ms ").concat(EASING);
166
- current.style.opacity = '1';
167
- }, FADE_IN_DELAY_MS);
168
- var doneTimer = window.setTimeout(function () {
169
- setOutgoing(null);
170
- animatingRef.current = false;
171
- var h = current.getBoundingClientRect().height;
172
- if (!isCollapsedMeasure(h)) finishHeight(outer, h);
173
- current.style.transition = '';
194
+ current.style.transition = "opacity ".concat(EQUAL_FADE_MS, "ms ").concat(EASING);
174
195
  current.style.opacity = '1';
196
+ doneTimer = window.setTimeout(function () {
197
+ var _onSettleRef$current3;
198
+ setOutgoing(null);
199
+ animatingRef.current = false;
200
+ finishHeight(outer, target);
201
+ current.style.transition = '';
202
+ current.style.opacity = '1';
203
+ if (outEl) {
204
+ outEl.style.transition = '';
205
+ outEl.style.opacity = '';
206
+ outEl.style.visibility = '';
207
+ outEl.style.background = '';
208
+ }
209
+ (_onSettleRef$current3 = onSettleRef.current) === null || _onSettleRef$current3 === void 0 || _onSettleRef$current3.call(onSettleRef);
210
+ }, EQUAL_TOTAL_MS);
211
+ } else {
212
+ /* —— 不等高:高度全程同步 + 内容交叉淡(有底板) —— */
213
+ outer.style.transition = "height ".concat(UNEQUAL_HEIGHT_MS, "ms ").concat(EASING);
214
+ outer.style.height = "".concat(target, "px");
175
215
  if (outEl) {
176
- outEl.style.transition = '';
177
- outEl.style.opacity = '';
178
- outEl.style.visibility = '';
216
+ outEl.style.transition = "opacity ".concat(UNEQUAL_FADE_MS, "ms ").concat(EASING);
217
+ outEl.style.opacity = '0';
179
218
  }
180
- }, TOTAL_MS);
219
+ current.style.transition = "opacity ".concat(UNEQUAL_FADE_MS, "ms ").concat(EASING);
220
+ current.style.opacity = '1';
221
+ doneTimer = window.setTimeout(function () {
222
+ var _onSettleRef$current4;
223
+ setOutgoing(null);
224
+ animatingRef.current = false;
225
+ var h = current.getBoundingClientRect().height;
226
+ if (!isCollapsedMeasure(h)) finishHeight(outer, h);
227
+ current.style.transition = '';
228
+ current.style.opacity = '1';
229
+ if (outEl) {
230
+ outEl.style.transition = '';
231
+ outEl.style.opacity = '';
232
+ outEl.style.visibility = '';
233
+ outEl.style.background = '';
234
+ }
235
+ (_onSettleRef$current4 = onSettleRef.current) === null || _onSettleRef$current4 === void 0 || _onSettleRef$current4.call(onSettleRef);
236
+ }, UNEQUAL_TOTAL_MS);
237
+ }
181
238
  return function () {
182
- window.clearTimeout(fadeInTimer);
183
239
  window.clearTimeout(doneTimer);
184
240
  };
185
241
  }, [outgoing]);
@@ -199,9 +255,11 @@ export function AutoHeight(_ref) {
199
255
  if (animatingRef.current || outgoing !== null && outgoing !== undefined) return;
200
256
  var target = current.getBoundingClientRect().height;
201
257
  if (!readyRef.current) {
258
+ var _onSettleRef$current5;
202
259
  if (isCollapsedMeasure(target)) return;
203
260
  finishHeight(outer, target);
204
261
  readyRef.current = true;
262
+ (_onSettleRef$current5 = onSettleRef.current) === null || _onSettleRef$current5 === void 0 || _onSettleRef$current5.call(onSettleRef);
205
263
  return;
206
264
  }
207
265
 
@@ -241,7 +299,7 @@ export function AutoHeight(_ref) {
241
299
  position: 'absolute',
242
300
  left: 0,
243
301
  right: 0
244
- }, pin, 0), "pointerEvents", 'none'), "background", 'var(--odn-color-bg-elevated)'), "zIndex", 1)
302
+ }, pin, 0), "pointerEvents", 'none'), "background", 'transparent'), "zIndex", 1)
245
303
  }, outgoing) : null, /*#__PURE__*/React.createElement("div", {
246
304
  ref: currentRef,
247
305
  "data-odn-clarify-auto-height-inner": true
@@ -1,10 +1,10 @@
1
1
  import * as React from 'react';
2
- import type { ClarifyInput } from './types';
3
2
  import './style';
3
+ import type { ClarifyInput } from './types';
4
4
  /** @deprecated 仅兼容旧 Host;上移 morph 已改为 FLIP,不再需要 layoutId / LayoutGroup。 */
5
5
  export declare function clarifyDockLayoutId(msgId: string): string;
6
- export type { ClarifyInput, ClarifyOption } from './types';
7
6
  export { parseClarifyBoundDate, toISODate } from './types';
7
+ export type { ClarifyInput, ClarifyOption } from './types';
8
8
  export interface ClarifyDockQuestion {
9
9
  key: string;
10
10
  /** 题面文案。 */
@@ -14,8 +14,8 @@ import Input from "../input";
14
14
  import Pagination from "../pagination";
15
15
  import { AutoHeight } from "./auto-height";
16
16
  import { discardClarifyDockMorph, stashClarifyDockMorph, takeClarifyDockMorph } from "./morph-handoff";
17
- import { parseClarifyBoundDate, toISODate } from "./types";
18
17
  import "./style";
18
+ import { parseClarifyBoundDate, toISODate } from "./types";
19
19
 
20
20
  /** @deprecated 仅兼容旧 Host;上移 morph 已改为 FLIP,不再需要 layoutId / LayoutGroup。 */
21
21
  export function clarifyDockLayoutId(msgId) {
@@ -138,8 +138,9 @@ export function ClarifyDockTrigger(_ref) {
138
138
  };
139
139
  }, [msgId, morphDisabled, reduceMotion, items]);
140
140
  if (!items || items.length === 0) return null;
141
- var multi = items.length > 1;
142
- var mainLabel = multi ? "\u5DF2\u786E\u8BA4 ".concat(items.length, " \u9898") : formatLockedLine(items[0].label, items[0].answer);
141
+
142
+ /* 单题 / 多题共用骨架:折叠短摘要 + 展开小票(对齐 Approval) */
143
+ var mainLabel = "\u5DF2\u786E\u8BA4 ".concat(items.length, " \u9898");
143
144
  var handleToggle = function handleToggle() {
144
145
  if (expanded) {
145
146
  setExpanded(false);
@@ -169,7 +170,7 @@ export function ClarifyDockTrigger(_ref) {
169
170
  "data-odn-clarify-dock-locked-icon": true
170
171
  }), /*#__PURE__*/React.createElement("span", {
171
172
  "data-odn-clarify-dock-locked-main": true
172
- }, mainLabel)), multi ? /*#__PURE__*/React.createElement("button", {
173
+ }, mainLabel)), /*#__PURE__*/React.createElement("button", {
173
174
  type: "button",
174
175
  "data-odn-clarify-dock-locked-toggle": true,
175
176
  "aria-expanded": expanded,
@@ -178,7 +179,7 @@ export function ClarifyDockTrigger(_ref) {
178
179
  name: "chevron-down",
179
180
  size: 14,
180
181
  "data-odn-clarify-dock-locked-chevron": true
181
- })) : null)), multi ? /*#__PURE__*/React.createElement("div", {
182
+ })))), /*#__PURE__*/React.createElement("div", {
182
183
  "data-odn-clarify-dock-locked-panel": true,
183
184
  "data-open": expanded ? 'true' : 'false',
184
185
  "data-collapsed-settled": collapsedSettled ? '' : undefined,
@@ -192,7 +193,7 @@ export function ClarifyDockTrigger(_ref) {
192
193
  key: item.key,
193
194
  "data-odn-clarify-dock-locked-line": true
194
195
  }, formatLockedLine(item.label, item.answer));
195
- })))) : null));
196
+ }))))));
196
197
  }
197
198
 
198
199
  /* ================================================================== */
@@ -255,6 +256,11 @@ export function ClarifyDockPanel(_ref2) {
255
256
  _useState14 = _slicedToArray(_useState13, 2),
256
257
  dateRangeDraft = _useState14[0],
257
258
  setDateRangeDraft = _useState14[1];
259
+ /** AutoHeight 过渡结束后再递增,驱动选项延后 autofocus */
260
+ var _useState15 = useState(0),
261
+ _useState16 = _slicedToArray(_useState15, 2),
262
+ inputFocusEpoch = _useState16[0],
263
+ setInputFocusEpoch = _useState16[1];
258
264
  var question = spec.questions[page];
259
265
  var answer = answers[page];
260
266
  var input = question.input;
@@ -317,7 +323,7 @@ export function ClarifyDockPanel(_ref2) {
317
323
  var canConfirmAll = useMemo(function () {
318
324
  if (!canAdvance) return false;
319
325
  return answers.every(function (a, i) {
320
- return i === page ? true : a != null;
326
+ return i === page ? true : a !== undefined;
321
327
  });
322
328
  }, [answers, canAdvance, page]);
323
329
  var formatAnswer = function formatAnswer(q, ans) {
@@ -456,10 +462,10 @@ export function ClarifyDockPanel(_ref2) {
456
462
  setAnswers(synced);
457
463
  if (isLastPage) {
458
464
  if (!synced.every(function (a) {
459
- return a != null;
465
+ return a !== undefined;
460
466
  })) {
461
467
  var firstIncomplete = synced.findIndex(function (a) {
462
- return a == null;
468
+ return a === undefined;
463
469
  });
464
470
  if (firstIncomplete >= 0) goToPage(firstIncomplete, synced);
465
471
  return;
@@ -481,14 +487,14 @@ export function ClarifyDockPanel(_ref2) {
481
487
  setDateRangeDraft(null);
482
488
  /* 不按顺序跳过:任意一题跳过后若已全部有值(含已跳),直接提交 */
483
489
  if (next.every(function (a) {
484
- return a != null;
490
+ return a !== undefined;
485
491
  })) {
486
492
  setAnswers(next);
487
493
  submit(next);
488
494
  return;
489
495
  }
490
496
  var firstIncomplete = next.findIndex(function (a) {
491
- return a == null;
497
+ return a === undefined;
492
498
  });
493
499
  goToPage(firstIncomplete >= 0 ? firstIncomplete : page, next);
494
500
  };
@@ -510,7 +516,12 @@ export function ClarifyDockPanel(_ref2) {
510
516
  "aria-label": "\u6F84\u6E05\u95EE\u9898 ".concat(page + 1, " / ").concat(total)
511
517
  }, /*#__PURE__*/React.createElement(AutoHeight, {
512
518
  anchor: "bottom",
513
- contentKey: page
519
+ contentKey: page,
520
+ onSettle: function onSettle() {
521
+ return setInputFocusEpoch(function (n) {
522
+ return n + 1;
523
+ });
524
+ }
514
525
  }, /*#__PURE__*/React.createElement("div", {
515
526
  "data-odn-clarify-dock-body": true
516
527
  }, spec.title ? /*#__PURE__*/React.createElement("div", {
@@ -525,9 +536,11 @@ export function ClarifyDockPanel(_ref2) {
525
536
  "data-odn-clarify-dock-q-row": true
526
537
  }, /*#__PURE__*/React.createElement("div", {
527
538
  "data-odn-clarify-dock-q": true
528
- }, question.label, input.kind === 'options-multi' ? /*#__PURE__*/React.createElement("span", {
539
+ }, question.label), input.kind === 'options' ? /*#__PURE__*/React.createElement("span", {
529
540
  "data-odn-clarify-dock-q-hint": true
530
- }, "\uFF08\u53EF\u591A\u9009\uFF09") : null)), /*#__PURE__*/React.createElement("div", {
541
+ }, "\u5355\u9009") : input.kind === 'options-multi' ? /*#__PURE__*/React.createElement("span", {
542
+ "data-odn-clarify-dock-q-hint": true
543
+ }, "\u53EF\u591A\u9009") : null), /*#__PURE__*/React.createElement("div", {
531
544
  "data-odn-clarify-dock-input-shell": true,
532
545
  "data-skipped": isSkipped ? '' : undefined
533
546
  }, /*#__PURE__*/React.createElement("div", {
@@ -537,6 +550,7 @@ export function ClarifyDockPanel(_ref2) {
537
550
  input: input,
538
551
  maxOptions: resolveMaxOptions(spec.maxOptions),
539
552
  answer: isSkipped ? undefined : answer,
553
+ focusEpoch: inputFocusEpoch,
540
554
  otherDraft: otherDraft,
541
555
  dateDraft: dateDraft,
542
556
  dateRangeDraft: dateRangeDraft,
@@ -664,6 +678,7 @@ function QuestionInput(_ref3) {
664
678
  var input = _ref3.input,
665
679
  maxOptions = _ref3.maxOptions,
666
680
  answer = _ref3.answer,
681
+ focusEpoch = _ref3.focusEpoch,
667
682
  otherDraft = _ref3.otherDraft,
668
683
  dateDraft = _ref3.dateDraft,
669
684
  dateRangeDraft = _ref3.dateRangeDraft,
@@ -673,16 +688,21 @@ function QuestionInput(_ref3) {
673
688
  onPickSingle = _ref3.onPickSingle,
674
689
  onToggleMulti = _ref3.onToggleMulti;
675
690
  if (input.kind === 'options' || input.kind === 'options-multi') {
676
- return /*#__PURE__*/React.createElement(OptionsList, {
677
- options: withOtherOption(input.options, input.allowOther, maxOptions),
678
- multi: input.kind === 'options-multi',
679
- answer: answer,
680
- otherDraft: otherDraft,
681
- otherPlaceholder: input.otherPlaceholder,
682
- onOtherDraftChange: onOtherDraftChange,
683
- onPickSingle: onPickSingle,
684
- onToggleMulti: onToggleMulti
685
- });
691
+ return (
692
+ /*#__PURE__*/
693
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- 同文件后置实现
694
+ React.createElement(OptionsList, {
695
+ options: withOtherOption(input.options, input.allowOther, maxOptions),
696
+ multi: input.kind === 'options-multi',
697
+ answer: answer,
698
+ focusEpoch: focusEpoch,
699
+ otherDraft: otherDraft,
700
+ otherPlaceholder: input.otherPlaceholder,
701
+ onOtherDraftChange: onOtherDraftChange,
702
+ onPickSingle: onPickSingle,
703
+ onToggleMulti: onToggleMulti
704
+ })
705
+ );
686
706
  }
687
707
  if (input.kind === 'date') {
688
708
  return /*#__PURE__*/React.createElement("div", {
@@ -721,24 +741,34 @@ function OptionsList(_ref4) {
721
741
  var options = _ref4.options,
722
742
  multi = _ref4.multi,
723
743
  answer = _ref4.answer,
744
+ focusEpoch = _ref4.focusEpoch,
724
745
  otherDraft = _ref4.otherDraft,
725
746
  otherPlaceholder = _ref4.otherPlaceholder,
726
747
  onOtherDraftChange = _ref4.onOtherDraftChange,
727
748
  onPickSingle = _ref4.onPickSingle,
728
749
  onToggleMulti = _ref4.onToggleMulti;
729
750
  var optionRefs = useRef([]);
730
- var optionKey = options.map(function (o) {
731
- return o.value;
732
- }).join('|');
733
-
734
- /* 切题时把焦点放到首项/已选项,便于立刻 ↑↓ */
735
- useEffect(function () {
751
+ /** roving tabindex:当前键盘焦点项;与选中态解耦,避免重渲染丢焦 */
752
+ var _useState17 = useState(0),
753
+ _useState18 = _slicedToArray(_useState17, 2),
754
+ focusedIdx = _useState18[0],
755
+ setFocusedIdx = _useState18[1];
756
+ /** 选中态导致「其他」编辑行等结构切换后,在 layout 再聚焦 */
757
+ var pendingFocusIdxRef = useRef(null);
758
+ var resolveSelectedIdx = function resolveSelectedIdx() {
736
759
  var selectedIdx = options.findIndex(function (o) {
737
760
  if ((answer === null || answer === void 0 ? void 0 : answer.kind) === 'options') return answer.value === o.value;
738
761
  if ((answer === null || answer === void 0 ? void 0 : answer.kind) === 'options-multi') return answer.values.includes(o.value);
739
762
  return false;
740
763
  });
741
- var focusIdx = selectedIdx >= 0 ? selectedIdx : 0;
764
+ return selectedIdx >= 0 ? selectedIdx : 0;
765
+ };
766
+
767
+ /* 等 AutoHeight 过渡 settle 后再聚焦,避免渐现中途冒出焦点环 */
768
+ useEffect(function () {
769
+ if (focusEpoch <= 0) return;
770
+ var focusIdx = resolveSelectedIdx();
771
+ setFocusedIdx(focusIdx);
742
772
  var id = window.requestAnimationFrame(function () {
743
773
  var _optionRefs$current$f;
744
774
  (_optionRefs$current$f = optionRefs.current[focusIdx]) === null || _optionRefs$current$f === void 0 || _optionRefs$current$f.focus();
@@ -746,29 +776,45 @@ function OptionsList(_ref4) {
746
776
  return function () {
747
777
  return window.cancelAnimationFrame(id);
748
778
  };
749
- // eslint-disable-next-line react-hooks/exhaustive-deps -- 只在选项集切换时重聚焦
750
- }, [optionKey]);
751
- var focusAt = function focusAt(idx) {
779
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- 仅在 settle 后聚焦当前选项集
780
+ }, [focusEpoch]);
781
+ useLayoutEffect(function () {
752
782
  var _optionRefs$current$n;
753
- var next = Math.max(0, Math.min(options.length - 1, idx));
783
+ var pending = pendingFocusIdxRef.current;
784
+ if (pending === null) return;
785
+ pendingFocusIdxRef.current = null;
786
+ var next = Math.max(0, Math.min(options.length - 1, pending));
787
+ setFocusedIdx(next);
754
788
  (_optionRefs$current$n = optionRefs.current[next]) === null || _optionRefs$current$n === void 0 || _optionRefs$current$n.focus();
789
+ });
790
+ var focusAt = function focusAt(idx) {
791
+ var _optionRefs$current$n2;
792
+ var next = Math.max(0, Math.min(options.length - 1, idx));
793
+ setFocusedIdx(next);
794
+ (_optionRefs$current$n2 = optionRefs.current[next]) === null || _optionRefs$current$n2 === void 0 || _optionRefs$current$n2.focus();
795
+ };
796
+ var moveTo = function moveTo(idx) {
797
+ var next = Math.max(0, Math.min(options.length - 1, idx));
798
+ if (!multi) {
799
+ /* 单选会改 answer,可能切到「其他」编辑行导致 button 卸载;延后到 layout 再聚焦 */
800
+ pendingFocusIdxRef.current = next;
801
+ onPickSingle(options[next]);
802
+ return;
803
+ }
804
+ focusAt(next);
755
805
  };
756
806
  var handleOptionKeyDown = function handleOptionKeyDown(idx, opt) {
757
807
  return function (e) {
758
808
  if (e.key === 'ArrowDown') {
759
809
  e.preventDefault();
760
810
  e.stopPropagation();
761
- var next = (idx + 1) % options.length;
762
- focusAt(next);
763
- if (!multi) onPickSingle(options[next]);
811
+ moveTo((idx + 1) % options.length);
764
812
  return;
765
813
  }
766
814
  if (e.key === 'ArrowUp') {
767
815
  e.preventDefault();
768
816
  e.stopPropagation();
769
- var _next = (idx - 1 + options.length) % options.length;
770
- focusAt(_next);
771
- if (!multi) onPickSingle(options[_next]);
817
+ moveTo((idx - 1 + options.length) % options.length);
772
818
  return;
773
819
  }
774
820
  if (e.key === 'Enter' || e.key === ' ') {
@@ -824,11 +870,13 @@ function OptionsList(_ref4) {
824
870
  });
825
871
  var next = e.key === 'ArrowDown' ? (otherIdx + 1) % options.length : (otherIdx - 1 + options.length) % options.length;
826
872
  if (!multi) {
873
+ pendingFocusIdxRef.current = next;
827
874
  onPickSingle(options[next]);
875
+ } else {
876
+ requestAnimationFrame(function () {
877
+ return focusAt(next);
878
+ });
828
879
  }
829
- requestAnimationFrame(function () {
830
- return focusAt(next);
831
- });
832
880
  }
833
881
  if (multi && e.key === 'Escape') {
834
882
  e.preventDefault();
@@ -859,30 +907,16 @@ function OptionsList(_ref4) {
859
907
  "data-odn-clarify-dock-option-label": true
860
908
  }, opt.label));
861
909
 
862
- /* 不用 data-odn-button:避免 Button small 固定 height 把通栏选项撑坏 */
863
- if (active && !multi) {
864
- return /*#__PURE__*/React.createElement("button", {
865
- key: opt.value,
866
- type: "button",
867
- ref: function ref(el) {
868
- optionRefs.current[idx] = el;
869
- },
870
- role: "radio",
871
- "aria-checked": true,
872
- tabIndex: idx === 0 ? 0 : -1,
873
- onClick: function onClick() {
874
- return onPickSingle(opt);
875
- },
876
- onKeyDown: handleOptionKeyDown(idx, opt),
877
- "data-odn-clarify-dock-option": true,
878
- "data-active": true,
879
- "data-other": isOther || undefined
880
- }, optionContent);
881
- }
910
+ /*
911
+ * 单选 / 多选统一 HoverFill + button 结构。
912
+ * 旧版单选已选项改裸 button,↑↓ 选中时 DOM 重挂载 → 焦点丢失、按键失灵。
913
+ * 单选已选:HoverFill 色透明,选中态仍由 data-active CSS 绘制。
914
+ */
915
+ var singleActive = Boolean(active && !multi);
882
916
  return /*#__PURE__*/React.createElement(HoverFill, {
883
917
  key: opt.value,
884
- hoverColor: multi ? active ? MULTI_SELECTED_HOVER_FILL : MULTI_UNSELECTED_HOVER_FILL : MULTI_UNSELECTED_HOVER_FILL,
885
- activeColor: multi ? active ? MULTI_SELECTED_HOVER_FILL : MULTI_UNSELECTED_ACTIVE_FILL : MULTI_UNSELECTED_ACTIVE_FILL,
918
+ hoverColor: singleActive ? 'transparent' : multi ? active ? MULTI_SELECTED_HOVER_FILL : MULTI_UNSELECTED_HOVER_FILL : MULTI_UNSELECTED_HOVER_FILL,
919
+ activeColor: singleActive ? 'transparent' : multi ? active ? MULTI_SELECTED_HOVER_FILL : MULTI_UNSELECTED_ACTIVE_FILL : MULTI_UNSELECTED_ACTIVE_FILL,
886
920
  "data-odn-clarify-dock-option-fill": true
887
921
  }, /*#__PURE__*/React.createElement("button", {
888
922
  type: "button",
@@ -891,7 +925,10 @@ function OptionsList(_ref4) {
891
925
  },
892
926
  role: multi ? 'checkbox' : 'radio',
893
927
  "aria-checked": active,
894
- tabIndex: idx === 0 ? 0 : -1,
928
+ tabIndex: focusedIdx === idx ? 0 : -1,
929
+ onFocus: function onFocus() {
930
+ return setFocusedIdx(idx);
931
+ },
895
932
  onClick: function onClick() {
896
933
  return multi ? onToggleMulti(opt) : onPickSingle(opt);
897
934
  },
@@ -913,10 +950,10 @@ function OptionsList(_ref4) {
913
950
  * - recordAnswer 后 Panel 消失,Trigger 切 Approval 式锁态卡
914
951
  */
915
952
  export function useClarifyDockState(messages, sessionKey, resetVer) {
916
- var _useState15 = useState({}),
917
- _useState16 = _slicedToArray(_useState15, 2),
918
- answers = _useState16[0],
919
- setAnswers = _useState16[1];
953
+ var _useState19 = useState({}),
954
+ _useState20 = _slicedToArray(_useState19, 2),
955
+ answers = _useState20[0],
956
+ setAnswers = _useState20[1];
920
957
 
921
958
  /* 切换会话或点「重置」时清空已答标记,避免 Panel / Trigger 状态脱节 */
922
959
  useEffect(function () {
@@ -85,10 +85,13 @@ div[data-odn-clarify-dock] [data-odn-clarify-dock-body] {
85
85
  [data-odn-clarify-dock-q-row] {
86
86
  display: flex;
87
87
  align-items: baseline;
88
+ justify-content: space-between;
89
+ gap: 12px;
88
90
  min-width: 0;
89
91
  }
90
92
 
91
93
  [data-odn-clarify-dock] [data-odn-clarify-dock-q] {
94
+ flex: 1;
92
95
  min-width: 0;
93
96
  font-size: 14px;
94
97
  font-weight: 400;
@@ -97,13 +100,11 @@ div[data-odn-clarify-dock] [data-odn-clarify-dock-body] {
97
100
  margin: 0;
98
101
  }
99
102
 
100
- /* 多选题干后缀(与题干同字号、同色);负 margin 收紧与问号间的全角留白 */
101
103
  [data-odn-clarify-dock-q-hint] {
102
- font-size: inherit;
103
- line-height: inherit;
104
- font-weight: inherit;
105
- color: var(--odn-color-black-12);
106
- margin-left: -0.6em;
104
+ flex-shrink: 0;
105
+ font-size: 12px;
106
+ line-height: 1.5;
107
+ color: var(--odn-color-black-8);
107
108
  }
108
109
 
109
110
  /* ---------- 底栏 · 对齐 approval-actions(含顶部分割线) ---------- */
@@ -206,14 +207,9 @@ div[data-odn-clarify-dock] [data-odn-clarify-dock-body] {
206
207
  }
207
208
  [data-odn-clarify-dock] [data-odn-clarify-dock-option]:focus-visible {
208
209
  outline: none;
209
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--odn-color-black-8) 20%, transparent);
210
- }
211
-
212
- /* 单选未包 HoverFill 时,灰底直接画在 button 上 */
213
- [data-odn-clarify-dock] [data-odn-clarify-dock-options] > [data-odn-clarify-dock-option] {
214
- background: var(--odn-color-black-2);
215
210
  }
216
211
 
212
+ /* 单选已选态由 data-active 绘制;选项均包在 HoverFill 内 */
217
213
  [data-odn-clarify-dock] [data-odn-clarify-dock-option][data-active] {
218
214
  background: color-mix(in srgb, var(--odn-color-brand-6) 12%, transparent);
219
215
  color: var(--odn-color-brand-7);
@@ -228,6 +228,12 @@ export interface ComposerProps {
228
228
  * @default image/*,.pdf,...,.7z
229
229
  */
230
230
  accept?: string;
231
+ /**
232
+ * 自定义"格式不支持"提示文案。接收被拒绝的文件扩展名(不含前导点,已大写),
233
+ * 返回用于 Message.warning 的字符串。
234
+ * @default (ext) => `不支持「${ext}」格式`
235
+ */
236
+ fileRejectMessage?: (ext: string) => string;
231
237
  /**
232
238
  * 参数面板内容渲染。提供后 invocation chip 可点击打开面板;
233
239
  * `SkillItem.initialState='pending'` 插入后会自动打开。
@@ -137,6 +137,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
137
137
  maxTotalSize = _ref.maxTotalSize,
138
138
  _ref$accept = _ref.accept,
139
139
  accept = _ref$accept === void 0 ? DEFAULT_ACCEPT : _ref$accept,
140
+ fileRejectMessage = _ref.fileRejectMessage,
140
141
  renderParamPanel = _ref.renderParamPanel,
141
142
  onToolClick = _ref.onToolClick,
142
143
  className = _ref.className,
@@ -500,7 +501,8 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
500
501
  _iterator.f();
501
502
  }
502
503
  if (unsupportedExt) {
503
- Message.warning("\u4E0D\u652F\u6301\u300C".concat(unsupportedExt.replace(/^\./, '').toUpperCase(), "\u300D\u683C\u5F0F"));
504
+ var _ext = unsupportedExt.replace(/^\./, '').toUpperCase();
505
+ Message.warning(fileRejectMessage ? fileRejectMessage(_ext) : "\u4E0D\u652F\u6301\u300C".concat(_ext, "\u300D\u683C\u5F0F"));
504
506
  }
505
507
  if (oversizedName) {
506
508
  Message.warning("\u300C".concat(oversizedName, "\u300D\u8D85\u8FC7 ").concat(formatSizeMb(maxFileSize), "\uFF0C\u8BF7\u538B\u7F29\u540E\u91CD\u8BD5"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "one-design-next",
3
- "version": "0.0.72",
3
+ "version": "0.0.74",
4
4
  "description": "One Design Next from TAD@tencent.com",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,9 +41,10 @@
41
41
  "test": "vitest",
42
42
  "test:a11y": "echo 'axe-core smoke run(待 P5 接入)'",
43
43
  "test:changed": "vitest run --changed",
44
- "test:ci": "vitest run",
44
+ "test:ci": "vitest run --exclude '**/visual.test.tsx'",
45
+ "test:visual": "vitest run '**/visual.test.tsx'",
45
46
  "test:visual:docker": "bash scripts/visual-baseline-docker.sh",
46
- "test:visual:update": "UPDATE_VISUAL=1 vitest run",
47
+ "test:visual:update": "UPDATE_VISUAL=1 vitest run '**/visual.test.tsx'",
47
48
  "test:visual:update:docker": "bash scripts/visual-baseline-docker.sh update",
48
49
  "typecheck": "tsc -p tsconfig.ci.json"
49
50
  },