@dcg-overseas/number-line 0.1.5 → 0.1.7

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/dist/index.cjs CHANGED
@@ -234,6 +234,19 @@ function useNumberLineContext() {
234
234
  if (!ctx) throw new Error("Must be used inside <NumberLineProvider>");
235
235
  return ctx;
236
236
  }
237
+ function normalizeDomain(lo, hi) {
238
+ const a = Number(lo);
239
+ const b = Number(hi);
240
+ const x = Math.min(a, b);
241
+ const y = Math.max(a, b);
242
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
243
+ return { min: 0, max: 1 };
244
+ }
245
+ if (y <= x) {
246
+ return { min: x, max: x + 1 };
247
+ }
248
+ return { min: x, max: y };
249
+ }
237
250
  function NumberLineProvider({
238
251
  min,
239
252
  max,
@@ -252,14 +265,15 @@ function NumberLineProvider({
252
265
  onViewChange,
253
266
  children
254
267
  }) {
268
+ const { min: domainMin, max: domainMax } = normalizeDomain(min, max);
255
269
  const [selectedArcIndices, setSelectedArcIndices] = React.useState(/* @__PURE__ */ new Set());
256
270
  const [deletedArcIndices, setDeletedArcIndices] = React.useState(/* @__PURE__ */ new Set());
257
271
  const { ref: svgRef, width: containerWidth, height: containerHeight } = useContainerSize();
258
272
  const history = useHistory();
259
273
  const drawing = useDrawing();
260
274
  const { viewMin, viewMax, zoomAt, panByFraction, resetView } = useZoom({
261
- dataMin: min,
262
- dataMax: max,
275
+ dataMin: domainMin,
276
+ dataMax: domainMax,
263
277
  minZoom,
264
278
  maxZoom,
265
279
  controlledMin: controlledViewMin,
@@ -271,7 +285,7 @@ function NumberLineProvider({
271
285
  React.useEffect(() => {
272
286
  setDeletedArcIndices(/* @__PURE__ */ new Set());
273
287
  setSelectedArcIndices(/* @__PURE__ */ new Set());
274
- }, [min, max, groupSize, groupCount]);
288
+ }, [domainMin, domainMax, groupSize, groupCount]);
275
289
  const getLocalCoords = React.useCallback(
276
290
  (e) => {
277
291
  const svg = svgRef.current;
@@ -428,8 +442,8 @@ function NumberLineProvider({
428
442
  containerWidth,
429
443
  containerHeight,
430
444
  svgCursor,
431
- min,
432
- max,
445
+ min: domainMin,
446
+ max: domainMax,
433
447
  groupSize,
434
448
  groupCount,
435
449
  tickStep,
@@ -774,9 +788,11 @@ function DrawingLayer({ strokes, liveStroke, tool, width, height, onStrokeClick
774
788
  )
775
789
  ] });
776
790
  }
777
- const PAD_LEFT = 30;
778
- const PAD_RIGHT = 40;
779
- const AXIS_BOTTOM_OFFSET = 50;
791
+ const PAD_LEFT_LEGACY = 30;
792
+ const PAD_RIGHT_LEGACY = 40;
793
+ const PAD_X = (PAD_LEFT_LEGACY + PAD_RIGHT_LEGACY) / 2;
794
+ const LABEL_SPACE_BELOW_AXIS = 42;
795
+ const LABEL_ABOVE_AXIS_SLOP = 32;
780
796
  const ARC_TOP_PADDING = 16;
781
797
  const MIN_ARC_HEIGHT = 24;
782
798
  function NumberLine({ className }) {
@@ -813,7 +829,17 @@ function NumberLine({ className }) {
813
829
  } = useNumberLineContext();
814
830
  const w = containerWidth;
815
831
  const h = containerHeight;
816
- const axisY = Math.max(MIN_ARC_HEIGHT + ARC_TOP_PADDING, h - AXIS_BOTTOM_OFFSET);
832
+ const range = Math.max(viewMax - viewMin, 1e-6);
833
+ const usable = Math.max(w - 2 * PAD_X, 1);
834
+ let ryTypical = MIN_ARC_HEIGHT * 0.5;
835
+ if (groupSize > 0 && groupCount > 0) {
836
+ const rx = usable * groupSize / range / 2;
837
+ ryTypical = Math.min(rx * 0.7, h * 0.48);
838
+ }
839
+ const minAxisY = MIN_ARC_HEIGHT + ARC_TOP_PADDING;
840
+ const maxAxisY = h - LABEL_SPACE_BELOW_AXIS;
841
+ const axisYCentered = (h + ryTypical + LABEL_ABOVE_AXIS_SLOP - LABEL_SPACE_BELOW_AXIS) / 2;
842
+ const axisY = Math.min(maxAxisY, Math.max(minAxisY, axisYCentered));
817
843
  const arcMaxHeight = Math.max(MIN_ARC_HEIGHT, axisY - ARC_TOP_PADDING);
818
844
  const [isZoomActive, setIsZoomActive] = React.useState(false);
819
845
  const [isPanning, setIsPanning] = React.useState(false);
@@ -980,8 +1006,8 @@ function NumberLine({ className }) {
980
1006
  viewMin,
981
1007
  viewMax,
982
1008
  axisY,
983
- padLeft: PAD_LEFT,
984
- padRight: PAD_RIGHT,
1009
+ padLeft: PAD_X,
1010
+ padRight: PAD_X,
985
1011
  viewWidth: w,
986
1012
  containerWidth: w,
987
1013
  groupSize,
@@ -1002,8 +1028,8 @@ function NumberLine({ className }) {
1002
1028
  groupSize,
1003
1029
  groupCount,
1004
1030
  axisY,
1005
- padLeft: PAD_LEFT,
1006
- padRight: PAD_RIGHT,
1031
+ padLeft: PAD_X,
1032
+ padRight: PAD_X,
1007
1033
  viewWidth: w,
1008
1034
  arcMaxHeight,
1009
1035
  tool,
package/dist/index.js CHANGED
@@ -232,6 +232,19 @@ function useNumberLineContext() {
232
232
  if (!ctx) throw new Error("Must be used inside <NumberLineProvider>");
233
233
  return ctx;
234
234
  }
235
+ function normalizeDomain(lo, hi) {
236
+ const a = Number(lo);
237
+ const b = Number(hi);
238
+ const x = Math.min(a, b);
239
+ const y = Math.max(a, b);
240
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
241
+ return { min: 0, max: 1 };
242
+ }
243
+ if (y <= x) {
244
+ return { min: x, max: x + 1 };
245
+ }
246
+ return { min: x, max: y };
247
+ }
235
248
  function NumberLineProvider({
236
249
  min,
237
250
  max,
@@ -250,14 +263,15 @@ function NumberLineProvider({
250
263
  onViewChange,
251
264
  children
252
265
  }) {
266
+ const { min: domainMin, max: domainMax } = normalizeDomain(min, max);
253
267
  const [selectedArcIndices, setSelectedArcIndices] = useState(/* @__PURE__ */ new Set());
254
268
  const [deletedArcIndices, setDeletedArcIndices] = useState(/* @__PURE__ */ new Set());
255
269
  const { ref: svgRef, width: containerWidth, height: containerHeight } = useContainerSize();
256
270
  const history = useHistory();
257
271
  const drawing = useDrawing();
258
272
  const { viewMin, viewMax, zoomAt, panByFraction, resetView } = useZoom({
259
- dataMin: min,
260
- dataMax: max,
273
+ dataMin: domainMin,
274
+ dataMax: domainMax,
261
275
  minZoom,
262
276
  maxZoom,
263
277
  controlledMin: controlledViewMin,
@@ -269,7 +283,7 @@ function NumberLineProvider({
269
283
  useEffect(() => {
270
284
  setDeletedArcIndices(/* @__PURE__ */ new Set());
271
285
  setSelectedArcIndices(/* @__PURE__ */ new Set());
272
- }, [min, max, groupSize, groupCount]);
286
+ }, [domainMin, domainMax, groupSize, groupCount]);
273
287
  const getLocalCoords = useCallback(
274
288
  (e) => {
275
289
  const svg = svgRef.current;
@@ -426,8 +440,8 @@ function NumberLineProvider({
426
440
  containerWidth,
427
441
  containerHeight,
428
442
  svgCursor,
429
- min,
430
- max,
443
+ min: domainMin,
444
+ max: domainMax,
431
445
  groupSize,
432
446
  groupCount,
433
447
  tickStep,
@@ -772,9 +786,11 @@ function DrawingLayer({ strokes, liveStroke, tool, width, height, onStrokeClick
772
786
  )
773
787
  ] });
774
788
  }
775
- const PAD_LEFT = 30;
776
- const PAD_RIGHT = 40;
777
- const AXIS_BOTTOM_OFFSET = 50;
789
+ const PAD_LEFT_LEGACY = 30;
790
+ const PAD_RIGHT_LEGACY = 40;
791
+ const PAD_X = (PAD_LEFT_LEGACY + PAD_RIGHT_LEGACY) / 2;
792
+ const LABEL_SPACE_BELOW_AXIS = 42;
793
+ const LABEL_ABOVE_AXIS_SLOP = 32;
778
794
  const ARC_TOP_PADDING = 16;
779
795
  const MIN_ARC_HEIGHT = 24;
780
796
  function NumberLine({ className }) {
@@ -811,7 +827,17 @@ function NumberLine({ className }) {
811
827
  } = useNumberLineContext();
812
828
  const w = containerWidth;
813
829
  const h = containerHeight;
814
- const axisY = Math.max(MIN_ARC_HEIGHT + ARC_TOP_PADDING, h - AXIS_BOTTOM_OFFSET);
830
+ const range = Math.max(viewMax - viewMin, 1e-6);
831
+ const usable = Math.max(w - 2 * PAD_X, 1);
832
+ let ryTypical = MIN_ARC_HEIGHT * 0.5;
833
+ if (groupSize > 0 && groupCount > 0) {
834
+ const rx = usable * groupSize / range / 2;
835
+ ryTypical = Math.min(rx * 0.7, h * 0.48);
836
+ }
837
+ const minAxisY = MIN_ARC_HEIGHT + ARC_TOP_PADDING;
838
+ const maxAxisY = h - LABEL_SPACE_BELOW_AXIS;
839
+ const axisYCentered = (h + ryTypical + LABEL_ABOVE_AXIS_SLOP - LABEL_SPACE_BELOW_AXIS) / 2;
840
+ const axisY = Math.min(maxAxisY, Math.max(minAxisY, axisYCentered));
815
841
  const arcMaxHeight = Math.max(MIN_ARC_HEIGHT, axisY - ARC_TOP_PADDING);
816
842
  const [isZoomActive, setIsZoomActive] = useState(false);
817
843
  const [isPanning, setIsPanning] = useState(false);
@@ -978,8 +1004,8 @@ function NumberLine({ className }) {
978
1004
  viewMin,
979
1005
  viewMax,
980
1006
  axisY,
981
- padLeft: PAD_LEFT,
982
- padRight: PAD_RIGHT,
1007
+ padLeft: PAD_X,
1008
+ padRight: PAD_X,
983
1009
  viewWidth: w,
984
1010
  containerWidth: w,
985
1011
  groupSize,
@@ -1000,8 +1026,8 @@ function NumberLine({ className }) {
1000
1026
  groupSize,
1001
1027
  groupCount,
1002
1028
  axisY,
1003
- padLeft: PAD_LEFT,
1004
- padRight: PAD_RIGHT,
1029
+ padLeft: PAD_X,
1030
+ padRight: PAD_X,
1005
1031
  viewWidth: w,
1006
1032
  arcMaxHeight,
1007
1033
  tool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcg-overseas/number-line",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Interactive number line component with group arcs and freehand drawing",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,6 +23,12 @@
23
23
  "dist"
24
24
  ],
25
25
  "sideEffects": false,
26
+ "scripts": {
27
+ "build": "vite build",
28
+ "build:watch": "vite build --watch",
29
+ "typecheck": "tsc --noEmit",
30
+ "clean": "rm -rf dist *.tsbuildinfo"
31
+ },
26
32
  "peerDependencies": {
27
33
  "react": "^18.0.0",
28
34
  "react-dom": "^18.0.0"
@@ -37,11 +43,5 @@
37
43
  "publishConfig": {
38
44
  "access": "public",
39
45
  "registry": "https://registry.npmjs.org/"
40
- },
41
- "scripts": {
42
- "build": "vite build",
43
- "build:watch": "vite build --watch",
44
- "typecheck": "tsc --noEmit",
45
- "clean": "rm -rf dist *.tsbuildinfo"
46
46
  }
47
- }
47
+ }