@dcg-overseas/number-line 1.0.0-beta.1 → 1.0.0-beta.2
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 +10 -10
- package/dist/index.cjs +15 -6
- package/dist/index.d.ts +4 -0
- package/dist/index.js +15 -6
- package/package.json +1 -1
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
|
@@ -357,7 +357,6 @@ function NumberLineProvider({
|
|
|
357
357
|
);
|
|
358
358
|
const onPointerDown = React.useCallback(
|
|
359
359
|
(e) => {
|
|
360
|
-
e.currentTarget.focus();
|
|
361
360
|
if (drawing.tool === "eraser") {
|
|
362
361
|
if (eraseGesture.current) return;
|
|
363
362
|
const coords2 = getLocalCoords(e);
|
|
@@ -731,14 +730,15 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
731
730
|
const renderAsMajor = !renderAsGroup && majorActive;
|
|
732
731
|
const renderAsHalf = !renderAsGroup && !renderAsMajor && halfTickOffset > 0 && Math.abs(offset % step - halfTickOffset) < 1e-9;
|
|
733
732
|
const showLabel = (renderAsGroup || renderAsMajor) && offset % labelStep === 0;
|
|
734
|
-
const currentShowLabelStep = typeof getShowLabelStep
|
|
733
|
+
const currentShowLabelStep = typeof getShowLabelStep === "function" ? getShowLabelStep({
|
|
735
734
|
viewMin,
|
|
736
735
|
viewMax,
|
|
737
736
|
offset,
|
|
738
737
|
labelStep,
|
|
739
738
|
length,
|
|
740
739
|
v
|
|
741
|
-
});
|
|
740
|
+
}) : void 0;
|
|
741
|
+
const shouldShowLabel = currentShowLabelStep ?? (showLabel || showLabelStep);
|
|
742
742
|
const tickH = renderAsGroup ? 7 : renderAsMajor ? 10 : renderAsHalf ? 8 : 6;
|
|
743
743
|
const strokeW = renderAsGroup ? 4 : renderAsMajor ? 2 : renderAsHalf ? 2 : 1;
|
|
744
744
|
const arcIndex = renderAsGroup ? offset > 0 ? offset / groupSize - 1 : offset === 0 ? 0 : -1 : -1;
|
|
@@ -756,7 +756,7 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
756
756
|
strokeWidth: strokeW
|
|
757
757
|
}
|
|
758
758
|
),
|
|
759
|
-
|
|
759
|
+
shouldShowLabel && /* @__PURE__ */ jsxRuntime.jsx(
|
|
760
760
|
"text",
|
|
761
761
|
{
|
|
762
762
|
x,
|
|
@@ -790,6 +790,7 @@ function AnimatedArc({
|
|
|
790
790
|
max,
|
|
791
791
|
hitCursor,
|
|
792
792
|
shouldAnimate,
|
|
793
|
+
showLabel,
|
|
793
794
|
onArcClick
|
|
794
795
|
}) {
|
|
795
796
|
const measureRef = React.useRef(null);
|
|
@@ -872,7 +873,7 @@ function AnimatedArc({
|
|
|
872
873
|
pointerEvents: "none"
|
|
873
874
|
}
|
|
874
875
|
),
|
|
875
|
-
arcVisible && /* @__PURE__ */ jsxRuntime.jsx(
|
|
876
|
+
arcVisible && showLabel && /* @__PURE__ */ jsxRuntime.jsx(
|
|
876
877
|
"path",
|
|
877
878
|
{
|
|
878
879
|
d: arcPath,
|
|
@@ -969,6 +970,8 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
|
|
|
969
970
|
if (groupSize <= 0 || groupCount <= 0) return null;
|
|
970
971
|
const shouldAnimate = groupSize > 0 && groupCount > 0;
|
|
971
972
|
const toX = createValueToX(viewMin, viewMax, viewWidth, horizontalLayout);
|
|
973
|
+
const pixelsPerGroup = Math.abs(toX(min + groupSize) - toX(min));
|
|
974
|
+
const arcLabelInterval = Math.max(1, Math.ceil(36 / Math.max(1, pixelsPerGroup)));
|
|
972
975
|
const arcs = [];
|
|
973
976
|
for (let g = 0; g < groupCount; g++) {
|
|
974
977
|
if (deletedArcIndices.has(g)) continue;
|
|
@@ -988,6 +991,7 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
|
|
|
988
991
|
);
|
|
989
992
|
const arcPath = buildArcPath({ x1, x2, y: axisY, rx, ry });
|
|
990
993
|
const isSelected = selectedArcIndices.has(g);
|
|
994
|
+
const showLabel = arcLabelInterval === 1 || (g + 1) % arcLabelInterval === 0 || g === groupCount - 1;
|
|
991
995
|
const hitCursor = tool === "none" ? "pointer" : "default";
|
|
992
996
|
arcs.push(
|
|
993
997
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1005,6 +1009,7 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
|
|
|
1005
1009
|
max,
|
|
1006
1010
|
hitCursor,
|
|
1007
1011
|
shouldAnimate,
|
|
1012
|
+
showLabel,
|
|
1008
1013
|
onArcClick: (index, e) => {
|
|
1009
1014
|
if (tool === "none") {
|
|
1010
1015
|
e.stopPropagation();
|
|
@@ -1281,7 +1286,11 @@ function NumberLine({ className }) {
|
|
|
1281
1286
|
display: "block",
|
|
1282
1287
|
cursor: svgCursor,
|
|
1283
1288
|
touchAction: "none",
|
|
1284
|
-
overflow: "visible"
|
|
1289
|
+
overflow: "visible",
|
|
1290
|
+
// 数轴自身带有键盘快捷键,所以 SVG 需要保留 tabIndex;但各浏览器会在鼠标、
|
|
1291
|
+
// 触摸和键盘聚焦时都绘制一圈与教学内容无关的边框。该工具使用按钮状态表达
|
|
1292
|
+
// 可操作性,因此统一移除原生 outline,避免它被误认为数轴的选中标记。
|
|
1293
|
+
outline: "none"
|
|
1285
1294
|
},
|
|
1286
1295
|
onPointerDown: handlePointerDown,
|
|
1287
1296
|
onPointerMove: handlePointerMove,
|
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,10 @@ export declare interface NumberLineProps {
|
|
|
80
80
|
viewMax?: number;
|
|
81
81
|
/** fired whenever internal viewport changes (uncontrolled mode) */
|
|
82
82
|
onViewChange?: (viewMin: number, viewMax: number) => void;
|
|
83
|
+
/**
|
|
84
|
+
* 自定义主刻度标签可见性。提供该回调后,其返回值会覆盖组件的自动标签密度,
|
|
85
|
+
* 适用于小窗等需要按实际可用宽度降低标签数量的场景。
|
|
86
|
+
*/
|
|
83
87
|
getShowLabelStep?: (params: unknown) => boolean;
|
|
84
88
|
}
|
|
85
89
|
|
package/dist/index.js
CHANGED
|
@@ -355,7 +355,6 @@ function NumberLineProvider({
|
|
|
355
355
|
);
|
|
356
356
|
const onPointerDown = useCallback(
|
|
357
357
|
(e) => {
|
|
358
|
-
e.currentTarget.focus();
|
|
359
358
|
if (drawing.tool === "eraser") {
|
|
360
359
|
if (eraseGesture.current) return;
|
|
361
360
|
const coords2 = getLocalCoords(e);
|
|
@@ -729,14 +728,15 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
729
728
|
const renderAsMajor = !renderAsGroup && majorActive;
|
|
730
729
|
const renderAsHalf = !renderAsGroup && !renderAsMajor && halfTickOffset > 0 && Math.abs(offset % step - halfTickOffset) < 1e-9;
|
|
731
730
|
const showLabel = (renderAsGroup || renderAsMajor) && offset % labelStep === 0;
|
|
732
|
-
const currentShowLabelStep = typeof getShowLabelStep
|
|
731
|
+
const currentShowLabelStep = typeof getShowLabelStep === "function" ? getShowLabelStep({
|
|
733
732
|
viewMin,
|
|
734
733
|
viewMax,
|
|
735
734
|
offset,
|
|
736
735
|
labelStep,
|
|
737
736
|
length,
|
|
738
737
|
v
|
|
739
|
-
});
|
|
738
|
+
}) : void 0;
|
|
739
|
+
const shouldShowLabel = currentShowLabelStep ?? (showLabel || showLabelStep);
|
|
740
740
|
const tickH = renderAsGroup ? 7 : renderAsMajor ? 10 : renderAsHalf ? 8 : 6;
|
|
741
741
|
const strokeW = renderAsGroup ? 4 : renderAsMajor ? 2 : renderAsHalf ? 2 : 1;
|
|
742
742
|
const arcIndex = renderAsGroup ? offset > 0 ? offset / groupSize - 1 : offset === 0 ? 0 : -1 : -1;
|
|
@@ -754,7 +754,7 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
754
754
|
strokeWidth: strokeW
|
|
755
755
|
}
|
|
756
756
|
),
|
|
757
|
-
|
|
757
|
+
shouldShowLabel && /* @__PURE__ */ jsx(
|
|
758
758
|
"text",
|
|
759
759
|
{
|
|
760
760
|
x,
|
|
@@ -788,6 +788,7 @@ function AnimatedArc({
|
|
|
788
788
|
max,
|
|
789
789
|
hitCursor,
|
|
790
790
|
shouldAnimate,
|
|
791
|
+
showLabel,
|
|
791
792
|
onArcClick
|
|
792
793
|
}) {
|
|
793
794
|
const measureRef = useRef(null);
|
|
@@ -870,7 +871,7 @@ function AnimatedArc({
|
|
|
870
871
|
pointerEvents: "none"
|
|
871
872
|
}
|
|
872
873
|
),
|
|
873
|
-
arcVisible && /* @__PURE__ */ jsx(
|
|
874
|
+
arcVisible && showLabel && /* @__PURE__ */ jsx(
|
|
874
875
|
"path",
|
|
875
876
|
{
|
|
876
877
|
d: arcPath,
|
|
@@ -967,6 +968,8 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
|
|
|
967
968
|
if (groupSize <= 0 || groupCount <= 0) return null;
|
|
968
969
|
const shouldAnimate = groupSize > 0 && groupCount > 0;
|
|
969
970
|
const toX = createValueToX(viewMin, viewMax, viewWidth, horizontalLayout);
|
|
971
|
+
const pixelsPerGroup = Math.abs(toX(min + groupSize) - toX(min));
|
|
972
|
+
const arcLabelInterval = Math.max(1, Math.ceil(36 / Math.max(1, pixelsPerGroup)));
|
|
970
973
|
const arcs = [];
|
|
971
974
|
for (let g = 0; g < groupCount; g++) {
|
|
972
975
|
if (deletedArcIndices.has(g)) continue;
|
|
@@ -986,6 +989,7 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
|
|
|
986
989
|
);
|
|
987
990
|
const arcPath = buildArcPath({ x1, x2, y: axisY, rx, ry });
|
|
988
991
|
const isSelected = selectedArcIndices.has(g);
|
|
992
|
+
const showLabel = arcLabelInterval === 1 || (g + 1) % arcLabelInterval === 0 || g === groupCount - 1;
|
|
989
993
|
const hitCursor = tool === "none" ? "pointer" : "default";
|
|
990
994
|
arcs.push(
|
|
991
995
|
/* @__PURE__ */ jsx(
|
|
@@ -1003,6 +1007,7 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
|
|
|
1003
1007
|
max,
|
|
1004
1008
|
hitCursor,
|
|
1005
1009
|
shouldAnimate,
|
|
1010
|
+
showLabel,
|
|
1006
1011
|
onArcClick: (index, e) => {
|
|
1007
1012
|
if (tool === "none") {
|
|
1008
1013
|
e.stopPropagation();
|
|
@@ -1279,7 +1284,11 @@ function NumberLine({ className }) {
|
|
|
1279
1284
|
display: "block",
|
|
1280
1285
|
cursor: svgCursor,
|
|
1281
1286
|
touchAction: "none",
|
|
1282
|
-
overflow: "visible"
|
|
1287
|
+
overflow: "visible",
|
|
1288
|
+
// 数轴自身带有键盘快捷键,所以 SVG 需要保留 tabIndex;但各浏览器会在鼠标、
|
|
1289
|
+
// 触摸和键盘聚焦时都绘制一圈与教学内容无关的边框。该工具使用按钮状态表达
|
|
1290
|
+
// 可操作性,因此统一移除原生 outline,避免它被误认为数轴的选中标记。
|
|
1291
|
+
outline: "none"
|
|
1283
1292
|
},
|
|
1284
1293
|
onPointerDown: handlePointerDown,
|
|
1285
1294
|
onPointerMove: handlePointerMove,
|