@dcg-overseas/number-line 1.0.0-beta.1 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -210,12 +210,12 @@ function App() {
210
210
  | `canDelete` | `boolean` | 是否有可删除的选中项(笔迹或弧线) |
211
211
  | `canUndo` | `boolean` | 是否有可撤销的操作 |
212
212
  | `onTogglePen` | `() => void` | 切换画笔工具 |
213
- | `onToggleEraser` | `() => void` | 切换橡皮擦 |
214
- | `onDelete` | `() => void` | 删除当前选中 |
215
- | `clearAll` | `() => void` | 可撤销地清空全部笔迹、弧线、选择和分组边界粗刻度;连续清空不会增加空历史项 |
216
- | `onReset` | `() => void` | `clearAll` 的兼容别名,已弃用;新代码请使用 `clearAll` |
213
+ | `onToggleEraser` | `() => void` | 切换橡皮擦 |
214
+ | `onDelete` | `() => void` | 删除当前选中 |
215
+ | `clearAll` | `() => void` | 可撤销地清空全部笔迹、弧线、选择和分组边界粗刻度;连续清空不会增加空历史项 |
216
+ | `onReset` | `() => void` | `clearAll` 的兼容别名,已弃用;新代码请使用 `clearAll` |
217
217
  | `onUndo` | `() => void` | 撤销上一步操作 |
218
- | `showAllArcs` | `() => void` | 重新显示全部跳数弧线**及分组边界粗刻度**(清空删除记录)。即使参数未变、或刚 `clearAll` 隐藏后,也能强制把跳数显示出来,且不影响笔迹 |
218
+ | `showAllArcs` | `() => void` | 重新显示全部跳数弧线**及分组边界粗刻度**(清空删除记录)。即使参数未变、或刚 `clearAll` 隐藏后,也能强制把跳数显示出来,且不影响笔迹 |
219
219
 
220
220
  #### 缩放 API(仅 `enableZoom=true` 时有效)
221
221
  | 字段 | 类型 | 说明 |
@@ -235,7 +235,7 @@ function App() {
235
235
  import { NumberLineProvider, NumberLine, useNumberLineContext } from '@dcg-overseas/number-line'
236
236
 
237
237
  function Toolbar() {
238
- const { tool, canDelete, canUndo, onTogglePen, onToggleEraser, onDelete, clearAll, onUndo } =
238
+ const { tool, canDelete, canUndo, onTogglePen, onToggleEraser, onDelete, clearAll, onUndo } =
239
239
  useNumberLineContext()
240
240
 
241
241
  return (
@@ -243,7 +243,7 @@ function Toolbar() {
243
243
  <button data-active={tool === 'pen'} onClick={onTogglePen}>✏️ 画笔</button>
244
244
  <button data-active={tool === 'eraser'} onClick={onToggleEraser}>🩹 擦除</button>
245
245
  <button disabled={!canDelete} onClick={onDelete}>🗑 删除</button>
246
- <button onClick={clearAll}>♻️ 全部清除</button>
246
+ <button onClick={clearAll}>♻️ 全部清除</button>
247
247
  <button disabled={!canUndo} onClick={onUndo}>↶ 撤销</button>
248
248
  </div>
249
249
  )
@@ -305,9 +305,9 @@ import type {
305
305
 
306
306
  ### 操作级撤销
307
307
 
308
- 撤销栈记录每次操作发生前的完整状态快照,包括笔迹、已删除弧线、已选弧线和粗分组刻度状态。
309
- 画笔、橡皮擦、删除选中及 `clearAll` 都复用同一恢复流程,因此一次清空或批量删除可以完整还原。
310
- `min`、`max`、`groupSize`、`groupCount` 或 `tickStep` 改变时会执行不可撤销初始化并清空历史,防止把上一道题的数据恢复到新坐标范围。
308
+ 撤销栈记录每次操作发生前的完整状态快照,包括笔迹、已删除弧线、已选弧线和粗分组刻度状态。
309
+ 画笔、橡皮擦、删除选中及 `clearAll` 都复用同一恢复流程,因此一次清空或批量删除可以完整还原。
310
+ `min`、`max`、`groupSize`、`groupCount` 或 `tickStep` 改变时会执行不可撤销初始化并清空历史,防止把上一道题的数据恢复到新坐标范围。
311
311
 
312
312
  ---
313
313
 
package/dist/index.cjs CHANGED
@@ -223,6 +223,7 @@ function useNumberLineContext() {
223
223
  if (!ctx) throw new Error("Must be used inside <NumberLineProvider>");
224
224
  return ctx;
225
225
  }
226
+ const DEFAULT_DRAWING_STROKE_WIDTH = 2.5;
226
227
  function normalizeDomain(lo, hi) {
227
228
  const a = Number(lo);
228
229
  const b = Number(hi);
@@ -255,6 +256,7 @@ function NumberLineProvider({
255
256
  groupSize,
256
257
  groupCount,
257
258
  tickStep,
259
+ strokeWidth: configuredStrokeWidth,
258
260
  arcColors,
259
261
  showGroupTicks = true,
260
262
  showMajorTicks = true,
@@ -270,6 +272,7 @@ function NumberLineProvider({
270
272
  children
271
273
  }) {
272
274
  const { min: domainMin, max: domainMax } = normalizeDomain(min, max);
275
+ const strokeWidth = typeof configuredStrokeWidth === "number" && Number.isFinite(configuredStrokeWidth) && configuredStrokeWidth > 0 ? configuredStrokeWidth : DEFAULT_DRAWING_STROKE_WIDTH;
273
276
  const [selectedArcIndices, setSelectedArcIndices] = React.useState(/* @__PURE__ */ new Set());
274
277
  const [deletedArcIndices, setDeletedArcIndices] = React.useState(/* @__PURE__ */ new Set());
275
278
  const [groupTicksHidden, setGroupTicksHidden] = React.useState(false);
@@ -357,7 +360,6 @@ function NumberLineProvider({
357
360
  );
358
361
  const onPointerDown = React.useCallback(
359
362
  (e) => {
360
- e.currentTarget.focus();
361
363
  if (drawing.tool === "eraser") {
362
364
  if (eraseGesture.current) return;
363
365
  const coords2 = getLocalCoords(e);
@@ -552,6 +554,7 @@ function NumberLineProvider({
552
554
  groupSize,
553
555
  groupCount,
554
556
  tickStep,
557
+ strokeWidth,
555
558
  arcColors,
556
559
  showGroupTicks: showGroupTicks && !groupTicksHidden,
557
560
  showMajorTicks,
@@ -731,14 +734,15 @@ const AxisLayer = React.memo(function AxisLayer2({
731
734
  const renderAsMajor = !renderAsGroup && majorActive;
732
735
  const renderAsHalf = !renderAsGroup && !renderAsMajor && halfTickOffset > 0 && Math.abs(offset % step - halfTickOffset) < 1e-9;
733
736
  const showLabel = (renderAsGroup || renderAsMajor) && offset % labelStep === 0;
734
- const currentShowLabelStep = typeof getShowLabelStep !== "function" ? false : getShowLabelStep == null ? void 0 : getShowLabelStep({
737
+ const currentShowLabelStep = typeof getShowLabelStep === "function" ? getShowLabelStep({
735
738
  viewMin,
736
739
  viewMax,
737
740
  offset,
738
741
  labelStep,
739
742
  length,
740
743
  v
741
- });
744
+ }) : void 0;
745
+ const shouldShowLabel = currentShowLabelStep ?? (showLabel || showLabelStep);
742
746
  const tickH = renderAsGroup ? 7 : renderAsMajor ? 10 : renderAsHalf ? 8 : 6;
743
747
  const strokeW = renderAsGroup ? 4 : renderAsMajor ? 2 : renderAsHalf ? 2 : 1;
744
748
  const arcIndex = renderAsGroup ? offset > 0 ? offset / groupSize - 1 : offset === 0 ? 0 : -1 : -1;
@@ -756,7 +760,7 @@ const AxisLayer = React.memo(function AxisLayer2({
756
760
  strokeWidth: strokeW
757
761
  }
758
762
  ),
759
- (showLabel || showLabelStep || currentShowLabelStep) && /* @__PURE__ */ jsxRuntime.jsx(
763
+ shouldShowLabel && /* @__PURE__ */ jsxRuntime.jsx(
760
764
  "text",
761
765
  {
762
766
  x,
@@ -790,6 +794,7 @@ function AnimatedArc({
790
794
  max,
791
795
  hitCursor,
792
796
  shouldAnimate,
797
+ showLabel,
793
798
  onArcClick
794
799
  }) {
795
800
  const measureRef = React.useRef(null);
@@ -872,7 +877,7 @@ function AnimatedArc({
872
877
  pointerEvents: "none"
873
878
  }
874
879
  ),
875
- arcVisible && /* @__PURE__ */ jsxRuntime.jsx(
880
+ arcVisible && showLabel && /* @__PURE__ */ jsxRuntime.jsx(
876
881
  "path",
877
882
  {
878
883
  d: arcPath,
@@ -969,6 +974,8 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
969
974
  if (groupSize <= 0 || groupCount <= 0) return null;
970
975
  const shouldAnimate = groupSize > 0 && groupCount > 0;
971
976
  const toX = createValueToX(viewMin, viewMax, viewWidth, horizontalLayout);
977
+ const pixelsPerGroup = Math.abs(toX(min + groupSize) - toX(min));
978
+ const arcLabelInterval = Math.max(1, Math.ceil(36 / Math.max(1, pixelsPerGroup)));
972
979
  const arcs = [];
973
980
  for (let g = 0; g < groupCount; g++) {
974
981
  if (deletedArcIndices.has(g)) continue;
@@ -988,6 +995,7 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
988
995
  );
989
996
  const arcPath = buildArcPath({ x1, x2, y: axisY, rx, ry });
990
997
  const isSelected = selectedArcIndices.has(g);
998
+ const showLabel = arcLabelInterval === 1 || (g + 1) % arcLabelInterval === 0 || g === groupCount - 1;
991
999
  const hitCursor = tool === "none" ? "pointer" : "default";
992
1000
  arcs.push(
993
1001
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1005,6 +1013,7 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
1005
1013
  max,
1006
1014
  hitCursor,
1007
1015
  shouldAnimate,
1016
+ showLabel,
1008
1017
  onArcClick: (index, e) => {
1009
1018
  if (tool === "none") {
1010
1019
  e.stopPropagation();
@@ -1018,7 +1027,7 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
1018
1027
  }
1019
1028
  return /* @__PURE__ */ jsxRuntime.jsx("g", { className: "nl-arcs-layer", children: arcs });
1020
1029
  });
1021
- function DrawingLayer({ strokes, liveStroke, tool, width, height, eraseHoverIds, onStrokeClick }) {
1030
+ function DrawingLayer({ strokes, liveStroke, tool, width, height, strokeWidth, eraseHoverIds, onStrokeClick }) {
1022
1031
  return /* @__PURE__ */ jsxRuntime.jsxs("g", { className: "nl-drawing-layer", transform: `scale(${width} ${height})`, children: [
1023
1032
  strokes.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
1024
1033
  (s.selected || tool === "eraser" && eraseHoverIds.has(s.id)) && /* @__PURE__ */ jsxRuntime.jsx(
@@ -1060,7 +1069,7 @@ function DrawingLayer({ strokes, liveStroke, tool, width, height, eraseHoverIds,
1060
1069
  d: s.d,
1061
1070
  fill: "none",
1062
1071
  stroke: "#7c3aed",
1063
- strokeWidth: 2.5,
1072
+ strokeWidth,
1064
1073
  strokeLinecap: "round",
1065
1074
  strokeLinejoin: "round",
1066
1075
  vectorEffect: "non-scaling-stroke",
@@ -1074,7 +1083,7 @@ function DrawingLayer({ strokes, liveStroke, tool, width, height, eraseHoverIds,
1074
1083
  d: liveStroke.d,
1075
1084
  fill: "none",
1076
1085
  stroke: "#7c3aed",
1077
- strokeWidth: 2.5,
1086
+ strokeWidth,
1078
1087
  strokeLinecap: "round",
1079
1088
  strokeLinejoin: "round",
1080
1089
  vectorEffect: "non-scaling-stroke",
@@ -1099,6 +1108,7 @@ function NumberLine({ className }) {
1099
1108
  groupSize,
1100
1109
  groupCount,
1101
1110
  tickStep,
1111
+ strokeWidth,
1102
1112
  arcColors,
1103
1113
  showGroupTicks,
1104
1114
  showMajorTicks,
@@ -1281,7 +1291,11 @@ function NumberLine({ className }) {
1281
1291
  display: "block",
1282
1292
  cursor: svgCursor,
1283
1293
  touchAction: "none",
1284
- overflow: "visible"
1294
+ overflow: "visible",
1295
+ // 数轴自身带有键盘快捷键,所以 SVG 需要保留 tabIndex;但各浏览器会在鼠标、
1296
+ // 触摸和键盘聚焦时都绘制一圈与教学内容无关的边框。该工具使用按钮状态表达
1297
+ // 可操作性,因此统一移除原生 outline,避免它被误认为数轴的选中标记。
1298
+ outline: "none"
1285
1299
  },
1286
1300
  onPointerDown: handlePointerDown,
1287
1301
  onPointerMove: handlePointerMove,
@@ -1346,6 +1360,7 @@ function NumberLine({ className }) {
1346
1360
  tool,
1347
1361
  width: w,
1348
1362
  height: h,
1363
+ strokeWidth,
1349
1364
  eraseHoverIds,
1350
1365
  onStrokeClick
1351
1366
  }
package/dist/index.d.ts CHANGED
@@ -15,6 +15,8 @@ export declare interface NumberLineContextValue {
15
15
  groupSize: number;
16
16
  groupCount: number;
17
17
  tickStep: number;
18
+ /** 用户自由绘制笔迹的固定视觉线宽,单位为 CSS 像素。 */
19
+ strokeWidth: number;
18
20
  arcColors?: string[];
19
21
  showGroupTicks: boolean;
20
22
  showMajorTicks: boolean;
@@ -60,6 +62,8 @@ export declare interface NumberLineProps {
60
62
  groupSize: number;
61
63
  groupCount: number;
62
64
  tickStep: number;
65
+ /** 用户自由绘制笔迹的固定视觉线宽,单位为 CSS 像素;默认 2.5。 */
66
+ strokeWidth?: number;
63
67
  arcColors?: string[];
64
68
  /** show group boundary ticks (tallest). Default: true */
65
69
  showGroupTicks?: boolean;
@@ -80,10 +84,14 @@ export declare interface NumberLineProps {
80
84
  viewMax?: number;
81
85
  /** fired whenever internal viewport changes (uncontrolled mode) */
82
86
  onViewChange?: (viewMin: number, viewMax: number) => void;
87
+ /**
88
+ * 自定义主刻度标签可见性。提供该回调后,其返回值会覆盖组件的自动标签密度,
89
+ * 适用于小窗等需要按实际可用宽度降低标签数量的场景。
90
+ */
83
91
  getShowLabelStep?: (params: unknown) => boolean;
84
92
  }
85
93
 
86
- export declare function NumberLineProvider({ min, max, groupSize, groupCount, tickStep, arcColors, showGroupTicks, showMajorTicks, showMinorTicks, enableZoom, minZoom, maxZoom, showLabelStep, viewMin: controlledViewMin, viewMax: controlledViewMax, getShowLabelStep, onViewChange, children, }: NumberLineProviderProps): JSX_2.Element;
94
+ export declare function NumberLineProvider({ min, max, groupSize, groupCount, tickStep, strokeWidth: configuredStrokeWidth, arcColors, showGroupTicks, showMajorTicks, showMinorTicks, enableZoom, minZoom, maxZoom, showLabelStep, viewMin: controlledViewMin, viewMax: controlledViewMax, getShowLabelStep, onViewChange, children, }: NumberLineProviderProps): JSX_2.Element;
87
95
 
88
96
  declare interface NumberLineProviderProps extends NumberLineProps {
89
97
  children: default_2.ReactNode;
package/dist/index.js CHANGED
@@ -221,6 +221,7 @@ function useNumberLineContext() {
221
221
  if (!ctx) throw new Error("Must be used inside <NumberLineProvider>");
222
222
  return ctx;
223
223
  }
224
+ const DEFAULT_DRAWING_STROKE_WIDTH = 2.5;
224
225
  function normalizeDomain(lo, hi) {
225
226
  const a = Number(lo);
226
227
  const b = Number(hi);
@@ -253,6 +254,7 @@ function NumberLineProvider({
253
254
  groupSize,
254
255
  groupCount,
255
256
  tickStep,
257
+ strokeWidth: configuredStrokeWidth,
256
258
  arcColors,
257
259
  showGroupTicks = true,
258
260
  showMajorTicks = true,
@@ -268,6 +270,7 @@ function NumberLineProvider({
268
270
  children
269
271
  }) {
270
272
  const { min: domainMin, max: domainMax } = normalizeDomain(min, max);
273
+ const strokeWidth = typeof configuredStrokeWidth === "number" && Number.isFinite(configuredStrokeWidth) && configuredStrokeWidth > 0 ? configuredStrokeWidth : DEFAULT_DRAWING_STROKE_WIDTH;
271
274
  const [selectedArcIndices, setSelectedArcIndices] = useState(/* @__PURE__ */ new Set());
272
275
  const [deletedArcIndices, setDeletedArcIndices] = useState(/* @__PURE__ */ new Set());
273
276
  const [groupTicksHidden, setGroupTicksHidden] = useState(false);
@@ -355,7 +358,6 @@ function NumberLineProvider({
355
358
  );
356
359
  const onPointerDown = useCallback(
357
360
  (e) => {
358
- e.currentTarget.focus();
359
361
  if (drawing.tool === "eraser") {
360
362
  if (eraseGesture.current) return;
361
363
  const coords2 = getLocalCoords(e);
@@ -550,6 +552,7 @@ function NumberLineProvider({
550
552
  groupSize,
551
553
  groupCount,
552
554
  tickStep,
555
+ strokeWidth,
553
556
  arcColors,
554
557
  showGroupTicks: showGroupTicks && !groupTicksHidden,
555
558
  showMajorTicks,
@@ -729,14 +732,15 @@ const AxisLayer = React.memo(function AxisLayer2({
729
732
  const renderAsMajor = !renderAsGroup && majorActive;
730
733
  const renderAsHalf = !renderAsGroup && !renderAsMajor && halfTickOffset > 0 && Math.abs(offset % step - halfTickOffset) < 1e-9;
731
734
  const showLabel = (renderAsGroup || renderAsMajor) && offset % labelStep === 0;
732
- const currentShowLabelStep = typeof getShowLabelStep !== "function" ? false : getShowLabelStep == null ? void 0 : getShowLabelStep({
735
+ const currentShowLabelStep = typeof getShowLabelStep === "function" ? getShowLabelStep({
733
736
  viewMin,
734
737
  viewMax,
735
738
  offset,
736
739
  labelStep,
737
740
  length,
738
741
  v
739
- });
742
+ }) : void 0;
743
+ const shouldShowLabel = currentShowLabelStep ?? (showLabel || showLabelStep);
740
744
  const tickH = renderAsGroup ? 7 : renderAsMajor ? 10 : renderAsHalf ? 8 : 6;
741
745
  const strokeW = renderAsGroup ? 4 : renderAsMajor ? 2 : renderAsHalf ? 2 : 1;
742
746
  const arcIndex = renderAsGroup ? offset > 0 ? offset / groupSize - 1 : offset === 0 ? 0 : -1 : -1;
@@ -754,7 +758,7 @@ const AxisLayer = React.memo(function AxisLayer2({
754
758
  strokeWidth: strokeW
755
759
  }
756
760
  ),
757
- (showLabel || showLabelStep || currentShowLabelStep) && /* @__PURE__ */ jsx(
761
+ shouldShowLabel && /* @__PURE__ */ jsx(
758
762
  "text",
759
763
  {
760
764
  x,
@@ -788,6 +792,7 @@ function AnimatedArc({
788
792
  max,
789
793
  hitCursor,
790
794
  shouldAnimate,
795
+ showLabel,
791
796
  onArcClick
792
797
  }) {
793
798
  const measureRef = useRef(null);
@@ -870,7 +875,7 @@ function AnimatedArc({
870
875
  pointerEvents: "none"
871
876
  }
872
877
  ),
873
- arcVisible && /* @__PURE__ */ jsx(
878
+ arcVisible && showLabel && /* @__PURE__ */ jsx(
874
879
  "path",
875
880
  {
876
881
  d: arcPath,
@@ -967,6 +972,8 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
967
972
  if (groupSize <= 0 || groupCount <= 0) return null;
968
973
  const shouldAnimate = groupSize > 0 && groupCount > 0;
969
974
  const toX = createValueToX(viewMin, viewMax, viewWidth, horizontalLayout);
975
+ const pixelsPerGroup = Math.abs(toX(min + groupSize) - toX(min));
976
+ const arcLabelInterval = Math.max(1, Math.ceil(36 / Math.max(1, pixelsPerGroup)));
970
977
  const arcs = [];
971
978
  for (let g = 0; g < groupCount; g++) {
972
979
  if (deletedArcIndices.has(g)) continue;
@@ -986,6 +993,7 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
986
993
  );
987
994
  const arcPath = buildArcPath({ x1, x2, y: axisY, rx, ry });
988
995
  const isSelected = selectedArcIndices.has(g);
996
+ const showLabel = arcLabelInterval === 1 || (g + 1) % arcLabelInterval === 0 || g === groupCount - 1;
989
997
  const hitCursor = tool === "none" ? "pointer" : "default";
990
998
  arcs.push(
991
999
  /* @__PURE__ */ jsx(
@@ -1003,6 +1011,7 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
1003
1011
  max,
1004
1012
  hitCursor,
1005
1013
  shouldAnimate,
1014
+ showLabel,
1006
1015
  onArcClick: (index, e) => {
1007
1016
  if (tool === "none") {
1008
1017
  e.stopPropagation();
@@ -1016,7 +1025,7 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
1016
1025
  }
1017
1026
  return /* @__PURE__ */ jsx("g", { className: "nl-arcs-layer", children: arcs });
1018
1027
  });
1019
- function DrawingLayer({ strokes, liveStroke, tool, width, height, eraseHoverIds, onStrokeClick }) {
1028
+ function DrawingLayer({ strokes, liveStroke, tool, width, height, strokeWidth, eraseHoverIds, onStrokeClick }) {
1020
1029
  return /* @__PURE__ */ jsxs("g", { className: "nl-drawing-layer", transform: `scale(${width} ${height})`, children: [
1021
1030
  strokes.map((s) => /* @__PURE__ */ jsxs("g", { children: [
1022
1031
  (s.selected || tool === "eraser" && eraseHoverIds.has(s.id)) && /* @__PURE__ */ jsx(
@@ -1058,7 +1067,7 @@ function DrawingLayer({ strokes, liveStroke, tool, width, height, eraseHoverIds,
1058
1067
  d: s.d,
1059
1068
  fill: "none",
1060
1069
  stroke: "#7c3aed",
1061
- strokeWidth: 2.5,
1070
+ strokeWidth,
1062
1071
  strokeLinecap: "round",
1063
1072
  strokeLinejoin: "round",
1064
1073
  vectorEffect: "non-scaling-stroke",
@@ -1072,7 +1081,7 @@ function DrawingLayer({ strokes, liveStroke, tool, width, height, eraseHoverIds,
1072
1081
  d: liveStroke.d,
1073
1082
  fill: "none",
1074
1083
  stroke: "#7c3aed",
1075
- strokeWidth: 2.5,
1084
+ strokeWidth,
1076
1085
  strokeLinecap: "round",
1077
1086
  strokeLinejoin: "round",
1078
1087
  vectorEffect: "non-scaling-stroke",
@@ -1097,6 +1106,7 @@ function NumberLine({ className }) {
1097
1106
  groupSize,
1098
1107
  groupCount,
1099
1108
  tickStep,
1109
+ strokeWidth,
1100
1110
  arcColors,
1101
1111
  showGroupTicks,
1102
1112
  showMajorTicks,
@@ -1279,7 +1289,11 @@ function NumberLine({ className }) {
1279
1289
  display: "block",
1280
1290
  cursor: svgCursor,
1281
1291
  touchAction: "none",
1282
- overflow: "visible"
1292
+ overflow: "visible",
1293
+ // 数轴自身带有键盘快捷键,所以 SVG 需要保留 tabIndex;但各浏览器会在鼠标、
1294
+ // 触摸和键盘聚焦时都绘制一圈与教学内容无关的边框。该工具使用按钮状态表达
1295
+ // 可操作性,因此统一移除原生 outline,避免它被误认为数轴的选中标记。
1296
+ outline: "none"
1283
1297
  },
1284
1298
  onPointerDown: handlePointerDown,
1285
1299
  onPointerMove: handlePointerMove,
@@ -1344,6 +1358,7 @@ function NumberLine({ className }) {
1344
1358
  tool,
1345
1359
  width: w,
1346
1360
  height: h,
1361
+ strokeWidth,
1347
1362
  eraseHoverIds,
1348
1363
  onStrokeClick
1349
1364
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcg-overseas/number-line",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "description": "Interactive number line component with group arcs and freehand drawing",
5
5
  "type": "module",
6
6
  "license": "MIT",