@reekon-tools/boldr-utils 1.19.3 → 1.19.5

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.
@@ -20,6 +20,7 @@ export interface AnnotationCanvasInnerProps {
20
20
  fractionalTolerance?: FractionalTolerance;
21
21
  decimalTolerance?: DecimalTolerance;
22
22
  resolveImageUrl?: (storagePath: string) => Promise<string>;
23
+ deferBackgroundToResolver?: boolean;
23
24
  pickMeasurement?: () => Promise<MeasurementRef | null>;
24
25
  requestTextInput?: RequestTextInput;
25
26
  renderBackground?: RenderCanvasBackground;
@@ -27,7 +27,7 @@ const DEFAULT_PAN_TRIGGERS = ['middleMouse', 'space'];
27
27
  // (SSR / the vitest environment), where 1 is the right answer.
28
28
  const devicePixelRatioOf = () => typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1;
29
29
  export const AnnotationCanvasInner = (props) => {
30
- const { resolveImageUrl, stampFontSource, stampValueFontSize = 14, gestures, width, height, style, activeToolId, tools, snapshotRef, renderBackground, } = props;
30
+ const { resolveImageUrl, deferBackgroundToResolver, stampFontSource, stampValueFontSize = 14, gestures, width, height, style, activeToolId, tools, snapshotRef, renderBackground, } = props;
31
31
  // Snapshot plumbing for thumbnail capture: hold the <Canvas> ref and publish
32
32
  // a capture fn on the consumer's snapshotRef. encodeToBytes() yields PNG.
33
33
  const canvasRef = useCanvasRef();
@@ -315,6 +315,7 @@ export const AnnotationCanvasInner = (props) => {
315
315
  worldTransform: state.worldTransform,
316
316
  viewport: state.viewport,
317
317
  resolveImageUrl,
318
+ deferBackgroundToResolver,
318
319
  backgroundContent,
319
320
  canvasRef,
320
321
  valueFont,
@@ -21,6 +21,7 @@ export interface AnnotationCanvasInnerProps {
21
21
  fractionalTolerance?: FractionalTolerance;
22
22
  decimalTolerance?: DecimalTolerance;
23
23
  resolveImageUrl?: (storagePath: string) => Promise<string>;
24
+ deferBackgroundToResolver?: boolean;
24
25
  pickMeasurement?: () => Promise<MeasurementRef | null>;
25
26
  requestTextInput?: RequestTextInput;
26
27
  renderBackground?: RenderCanvasBackground;
@@ -64,7 +64,7 @@ const LOUPE_CROSS_WIDTH_PX = 1.5;
64
64
  // is only re-synced once each gesture ends. The tool/tap path still hops to
65
65
  // JS (`runOnJS(true)`) since drawing/selection are React-state operations.
66
66
  export const AnnotationCanvasInner = (props) => {
67
- const { resolveImageUrl, stampFontSource, stampValueFontSize = 14, width, height, style, magnifierTopOffset = 0, renderBackground, } = props;
67
+ const { resolveImageUrl, deferBackgroundToResolver, stampFontSource, stampValueFontSize = 14, width, height, style, magnifierTopOffset = 0, renderBackground, } = props;
68
68
  const valueFont = useFont(stampFontSource, stampValueFontSize);
69
69
  // The animated-viewport implementation needs the Reanimated shared values,
70
70
  // which are created below AFTER the state hook — so the hook gets a stable
@@ -1949,6 +1949,7 @@ export const AnnotationCanvasInner = (props) => {
1949
1949
  effectiveCanvas: state.effectiveCanvas,
1950
1950
  worldTransform,
1951
1951
  resolveImageUrl,
1952
+ deferBackgroundToResolver,
1952
1953
  backgroundSkImages,
1953
1954
  // Shared with the loupe overlay below (it re-runs this same scene through a
1954
1955
  // magnifying transform), so the lens shows the substrate magnified instead
@@ -2045,7 +2046,7 @@ export const AnnotationCanvasInner = (props) => {
2045
2046
  // drawing to the canvas box by construction, but the stamp overlay is
2046
2047
  // plain RN views — without clipping, tiles panned past the edge of a
2047
2048
  // non-fullscreen canvas (e.g. a diagram strip) escape into surrounding UI.
2048
- _jsxs(GestureHandlerRootView, { style: [{ width, height, overflow: 'hidden' }, style], children: [backgroundLayers.map((layer) => (_jsx(BackgroundSkImageLoader, { layer: layer, resolveUrl: resolveImageUrl, onImage: onBackgroundSkImage }, layer.id))), _jsx(GestureDetector, { gesture: gesture, children: _jsx(View, { style: { width, height }, collapsable: false, children: AnnotationCanvasSkia(skiaProps) }) }), renderMeasurementStamp && (_jsx(View, { pointerEvents: "box-none", style: StyleSheet.absoluteFill, children: state.effectiveCanvas.placedMeasurements.map((placed) => (_jsx(MeasurementStampOverlayItem, { placed: placed, measurement: placed.measurementId
2049
+ _jsxs(GestureHandlerRootView, { style: [{ width, height, overflow: 'hidden' }, style], children: [backgroundLayers.map((layer) => (_jsx(BackgroundSkImageLoader, { layer: layer, resolveUrl: resolveImageUrl, deferToResolver: deferBackgroundToResolver, onImage: onBackgroundSkImage }, layer.id))), _jsx(GestureDetector, { gesture: gesture, children: _jsx(View, { style: { width, height }, collapsable: false, children: AnnotationCanvasSkia(skiaProps) }) }), renderMeasurementStamp && (_jsx(View, { pointerEvents: "box-none", style: StyleSheet.absoluteFill, children: state.effectiveCanvas.placedMeasurements.map((placed) => (_jsx(MeasurementStampOverlayItem, { placed: placed, measurement: placed.measurementId
2049
2050
  ? (state.measurementsById.get(placed.measurementId) ?? null)
2050
2051
  : (resolveStampMeasurement?.(placed) ?? null), selected: selection?.ids.includes(placed.id) ?? false, dragging: draggingId === placed.id, sliding: slidingId === placed.id, endpointDragging: epDragId === placed.id, rectResizing: rectDragId === placed.id, planeEdgeDragging: planeDrag != null && placed.planeId === planeDrag.planeId, zoomSnapshot: state.viewport.zoom, zoom: zoom, panX: panX, panY: panY, dragX: dragX, dragY: dragY, slideCtx: slideCtx, epCtx: epCtx, rectCtx: rectCtx, planeTileCtx: planeTileCtx, planeDragTX: planeDragTX, planeDragTY: planeDragTY, renderMeasurementStamp: renderMeasurementStamp, tileScaleFactor: state.tileScaleFactor, headerTileScaleFactor: state.headerTileScaleFactor, fileTileScaleFactor: state.fileTileScaleFactor, tileViewportScale: state.tileViewportScale, onStampPress: onMeasurementStampPress, onStampLongPress: onMeasurementStampLongPress, onRemove: onMeasurementStampRemove
2051
2052
  ? () => {
@@ -23,6 +23,7 @@ export interface AnnotationCanvasSkiaProps {
23
23
  };
24
24
  viewport?: ViewportState;
25
25
  resolveImageUrl?: (storagePath: string) => Promise<string>;
26
+ deferBackgroundToResolver?: boolean;
26
27
  backgroundSkImages?: Record<string, SkImage | null>;
27
28
  backgroundContent?: ReactNode;
28
29
  valueFont: SkFont | null;
@@ -105,5 +106,5 @@ export interface AnnotationCanvasSkiaProps {
105
106
  wrapContent?: (content: ReactNode) => ReactNode;
106
107
  canvasRef?: RefObject<CanvasRef | null>;
107
108
  }
108
- export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, backgroundContent, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, planeOutlineScale, measurementLineScale, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
109
+ export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, deferBackgroundToResolver, backgroundSkImages, backgroundContent, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, planeOutlineScale, measurementLineScale, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
109
110
  export {};
@@ -80,7 +80,7 @@ const SelectionBox = ({ bounds, isDragging, transform, }) => (_jsx(DraggableElem
80
80
  // since the function-call pattern works identically on native we use it
81
81
  // in both Inners for consistency. Don't add hooks here; this is a plain
82
82
  // JSX-returning helper, not a component.
83
- export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, backgroundContent, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, planeOutlineScale = 1, measurementLineScale = 1, customPreview, wrapContent, canvasRef, }) => {
83
+ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, deferBackgroundToResolver, backgroundSkImages, backgroundContent, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, planeOutlineScale = 1, measurementLineScale = 1, customPreview, wrapContent, canvasRef, }) => {
84
84
  // Doc rect a SCALE-mode plane grid covers: the first background layer (the
85
85
  // photo being calibrated), else the document rect. Pinned to layer 0 like
86
86
  // the tile-sizing reference — adding a second image must not re-span the
@@ -94,7 +94,7 @@ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTran
94
94
  width: effectiveCanvas.viewport.width,
95
95
  height: effectiveCanvas.viewport.height,
96
96
  };
97
- const world = (_jsxs(Group, { transform: worldTransform, children: [backgroundContent, backgroundLayersOf(effectiveCanvas.viewport).map((layer) => (_jsx(DraggableElement, { isDragging: layer.id === draggingId || layer.id === resizingId, transform: layer.id === resizingId ? resizeTransform : dragTransform, children: _jsx(BackgroundLayerElement, { layer: layer, resolveUrl: resolveImageUrl, skImage: backgroundSkImages?.[layer.id], viewport: viewport, screenWidth: width, screenHeight: height }) }, layer.id))), (effectiveCanvas.planes ?? []).map((plane) => {
97
+ const world = (_jsxs(Group, { transform: worldTransform, children: [backgroundContent, backgroundLayersOf(effectiveCanvas.viewport).map((layer) => (_jsx(DraggableElement, { isDragging: layer.id === draggingId || layer.id === resizingId, transform: layer.id === resizingId ? resizeTransform : dragTransform, children: _jsx(BackgroundLayerElement, { layer: layer, resolveUrl: resolveImageUrl, deferToResolver: deferBackgroundToResolver, skImage: backgroundSkImages?.[layer.id], viewport: viewport, screenWidth: width, screenHeight: height }) }, layer.id))), (effectiveCanvas.planes ?? []).map((plane) => {
98
98
  if (plane.id === planeDragId && planeDragPath) {
99
99
  const statics = plane.mode === 'perspective'
100
100
  ? plane.corners
@@ -1,19 +1,21 @@
1
1
  import { type SkImage } from '@shopify/react-native-skia';
2
2
  import type { AnnotationBackgroundImage, AnnotationBackgroundLayer } from '../../../types/annotation.js';
3
3
  import type { ViewportState } from '../viewport.js';
4
- export declare const useBackgroundSkImage: (image: AnnotationBackgroundImage | null | undefined, resolveUrl?: (path: string) => Promise<string>) => SkImage | null;
4
+ export declare const useBackgroundSkImage: (image: AnnotationBackgroundImage | null | undefined, resolveUrl?: (path: string) => Promise<string>, deferToResolver?: boolean) => SkImage | null;
5
5
  export interface BackgroundSkImageLoaderProps {
6
6
  layer: AnnotationBackgroundLayer;
7
7
  resolveUrl?: (path: string) => Promise<string>;
8
+ deferToResolver?: boolean;
8
9
  onImage: (id: string, image: SkImage | null) => void;
9
10
  }
10
- export declare const BackgroundSkImageLoader: ({ layer, resolveUrl, onImage, }: BackgroundSkImageLoaderProps) => null;
11
+ export declare const BackgroundSkImageLoader: ({ layer, resolveUrl, deferToResolver, onImage, }: BackgroundSkImageLoaderProps) => null;
11
12
  export interface BackgroundLayerElementProps {
12
13
  layer: AnnotationBackgroundLayer;
13
14
  resolveUrl?: (path: string) => Promise<string>;
15
+ deferToResolver?: boolean;
14
16
  skImage?: SkImage | null;
15
17
  viewport?: ViewportState;
16
18
  screenWidth?: number;
17
19
  screenHeight?: number;
18
20
  }
19
- export declare const BackgroundLayerElement: import("react").MemoExoticComponent<({ layer, resolveUrl, skImage: skImageProp, viewport, screenWidth, screenHeight, }: BackgroundLayerElementProps) => import("react/jsx-runtime").JSX.Element | null>;
21
+ export declare const BackgroundLayerElement: import("react").MemoExoticComponent<({ layer, resolveUrl, deferToResolver, skImage: skImageProp, viewport, screenWidth, screenHeight, }: BackgroundLayerElementProps) => import("react/jsx-runtime").JSX.Element | null>;
@@ -11,8 +11,8 @@ import { BackgroundSvgElement } from './BackgroundSvg.js';
11
11
  // re-decodes. Returns null until loaded, or when `image` is null/undefined.
12
12
  // Hooks run unconditionally regardless of `image`. Raster only — SVG
13
13
  // backgrounds self-load in BackgroundSvgElement (parsing an SVG is cheap).
14
- export const useBackgroundSkImage = (image, resolveUrl) => {
15
- const url = useBackgroundUrl(image, resolveUrl);
14
+ export const useBackgroundSkImage = (image, resolveUrl, deferToResolver) => {
15
+ const url = useBackgroundUrl(image, resolveUrl, deferToResolver);
16
16
  return useImage(url || null);
17
17
  };
18
18
  // Null-rendering decode host: one mounted instance per background layer, so a
@@ -20,10 +20,10 @@ export const useBackgroundSkImage = (image, resolveUrl) => {
20
20
  // mapping over hooks. The consumer (the native Inner) mounts one per layer,
21
21
  // accumulates the reported images into a Record keyed by layer id, and feeds
22
22
  // that to every canvas sharing the decode (main + magnifier loupe).
23
- export const BackgroundSkImageLoader = ({ layer, resolveUrl, onImage, }) => {
23
+ export const BackgroundSkImageLoader = ({ layer, resolveUrl, deferToResolver, onImage, }) => {
24
24
  // SVG layers never draw through an SkImage (BackgroundSvgElement owns its
25
25
  // own vector pipeline), so skip the decode and report null.
26
- const image = useBackgroundSkImage(layer.format === 'svg' ? null : layer, resolveUrl);
26
+ const image = useBackgroundSkImage(layer.format === 'svg' ? null : layer, resolveUrl, deferToResolver);
27
27
  // The callback rides a ref so an inline consumer closure doesn't re-report
28
28
  // (and re-render the collector) every render.
29
29
  const onImageRef = useRef(onImage);
@@ -38,12 +38,12 @@ export const BackgroundSkImageLoader = ({ layer, resolveUrl, onImage, }) => {
38
38
  // A component (not inlined into the scene helper) because each layer owns
39
39
  // per-image hooks — URL resolution and raster decode — and AnnotationCanvasSkia
40
40
  // is a hook-free plain function.
41
- export const BackgroundLayerElement = memo(({ layer, resolveUrl, skImage: skImageProp, viewport, screenWidth, screenHeight, }) => {
41
+ export const BackgroundLayerElement = memo(({ layer, resolveUrl, deferToResolver, skImage: skImageProp, viewport, screenWidth, screenHeight, }) => {
42
42
  const isSvg = layer.format === 'svg';
43
43
  // Self-load only when no pre-decoded image is provided; the hook still runs
44
44
  // unconditionally (with a null source, so it no-ops) to satisfy hook rules.
45
45
  const provided = skImageProp !== undefined;
46
- const loaded = useBackgroundSkImage(provided || isSvg ? null : layer, resolveUrl);
46
+ const loaded = useBackgroundSkImage(provided || isSvg ? null : layer, resolveUrl, deferToResolver);
47
47
  const skImage = provided ? skImageProp : loaded;
48
48
  const t = layer.transform;
49
49
  let content = null;
@@ -53,7 +53,7 @@ export const BackgroundLayerElement = memo(({ layer, resolveUrl, skImage: skImag
53
53
  // through the browser at viewport resolution. Both draw at intrinsic
54
54
  // size — the Group below owns the placement — but the web rasterizer
55
55
  // still needs the doc rect to crop against the visible region.
56
- content = (_jsx(BackgroundSvgElement, { image: layer, dims: backgroundLayerDocRect(layer), resolveUrl: resolveUrl, viewport: viewport, screenWidth: screenWidth, screenHeight: screenHeight }));
56
+ content = (_jsx(BackgroundSvgElement, { image: layer, dims: backgroundLayerDocRect(layer), resolveUrl: resolveUrl, deferToResolver: deferToResolver, viewport: viewport, screenWidth: screenWidth, screenHeight: screenHeight }));
57
57
  }
58
58
  else if (skImage) {
59
59
  content = (_jsx(Image, { image: skImage, x: 0, y: 0, width: layer.widthPx, height: layer.heightPx, fit: "fill" }));
@@ -5,6 +5,7 @@ export interface BackgroundSvgProps {
5
5
  image: AnnotationBackgroundImage;
6
6
  dims: RectPx;
7
7
  resolveUrl?: (path: string) => Promise<string>;
8
+ deferToResolver?: boolean;
8
9
  viewport?: ViewportState;
9
10
  screenWidth?: number;
10
11
  screenHeight?: number;
@@ -116,8 +116,8 @@ const useBackgroundSkSvg = (url) => {
116
116
  }, []);
117
117
  return svg;
118
118
  };
119
- const SkSvgBackground = ({ image, resolveUrl }) => {
120
- const url = useBackgroundUrl(image, resolveUrl);
119
+ const SkSvgBackground = ({ image, resolveUrl, deferToResolver, }) => {
120
+ const url = useBackgroundUrl(image, resolveUrl, deferToResolver);
121
121
  const svg = useBackgroundSkSvg(url);
122
122
  if (!svg)
123
123
  return null;
@@ -169,8 +169,8 @@ const loadSvgImage = (xml) => new Promise((resolve, reject) => {
169
169
  };
170
170
  img.src = objectUrl;
171
171
  });
172
- const BrowserSvgBackground = ({ image, dims, resolveUrl, viewport, screenWidth, screenHeight, }) => {
173
- const url = useBackgroundUrl(image, resolveUrl);
172
+ const BrowserSvgBackground = ({ image, dims, resolveUrl, deferToResolver, viewport, screenWidth, screenHeight, }) => {
173
+ const url = useBackgroundUrl(image, resolveUrl, deferToResolver);
174
174
  const [svgText, setSvgText] = useState(null);
175
175
  useEffect(() => {
176
176
  if (!url) {
@@ -1,2 +1,3 @@
1
1
  import type { AnnotationBackgroundImage } from '../../../types/annotation.js';
2
- export declare const useBackgroundUrl: (image: AnnotationBackgroundImage | null | undefined, resolveUrl?: (path: string) => Promise<string>) => string;
2
+ export declare const initialBackgroundUrl: (image: AnnotationBackgroundImage | null | undefined, resolveUrl: ((path: string) => Promise<string>) | undefined, deferToResolver: boolean | undefined) => string;
3
+ export declare const useBackgroundUrl: (image: AnnotationBackgroundImage | null | undefined, resolveUrl?: (path: string) => Promise<string>, deferToResolver?: boolean) => string;
@@ -2,8 +2,21 @@ import { useEffect, useRef, useState } from 'react';
2
2
  // Resolve a background image's (possibly expired) storage URL. Shared by the
3
3
  // raster loader (BackgroundLayerElement / BackgroundSkImageLoader) and both
4
4
  // SVG render paths (BackgroundSvg).
5
- export const useBackgroundUrl = (image, resolveUrl) => {
6
- const [url, setUrl] = useState(image?.downloadUrl ?? '');
5
+ //
6
+ // `deferToResolver` — start from '' instead of seeding the doc's stored
7
+ // `downloadUrl` while the resolver runs. A consumer whose resolver is
8
+ // LOCAL-FIRST (a cached copy on disk answers without the network) must set
9
+ // this: the seed is handed to the image loader on the very first render, so
10
+ // it starts a full-resolution download from Storage that the local answer
11
+ // then races — wasted bandwidth at best, and the loader keeps whichever
12
+ // result lands LAST, so the remote fetch can override the local decode.
13
+ // A consumer whose resolver is network-only (web/desktop re-resolving a
14
+ // possibly-expired URL) should leave it off: there the seed is a warm start —
15
+ // the resolver normally confirms the same URL, and the early fetch is the
16
+ // image download it would have started anyway.
17
+ export const initialBackgroundUrl = (image, resolveUrl, deferToResolver) => (deferToResolver && resolveUrl ? '' : (image?.downloadUrl ?? ''));
18
+ export const useBackgroundUrl = (image, resolveUrl, deferToResolver) => {
19
+ const [url, setUrl] = useState(() => initialBackgroundUrl(image, resolveUrl, deferToResolver));
7
20
  // Hold resolveUrl in a ref so re-resolution is keyed only on the image
8
21
  // identity, not the function's. Consumers commonly pass an inline closure
9
22
  // (a new reference each render); depending on it directly would re-run a
@@ -16,7 +16,7 @@ export declare const GROUP_HEADER_COLLAPSED_WIDTH: number;
16
16
  export declare const GROUP_HEADER_COLLAPSED_HEIGHT: number;
17
17
  export declare const groupHeaderBaseDims: (collapsed?: boolean) => StampTileDims;
18
18
  export declare const DEFAULT_TILE_SCALE = 1;
19
- export declare const TILE_SCALE_MIN = 0.4;
19
+ export declare const TILE_SCALE_MIN = 0.05;
20
20
  export declare const TILE_SCALE_MAX = 2;
21
21
  export declare const resolveTileScaleFactor: (canvas: Pick<AnnotationCanvasState, "tileScaleFactor" | "tileScaleFactorMobile">, platform: TileScalePlatform) => number;
22
22
  export declare const resolveHeaderTileScaleFactor: (canvas: Pick<AnnotationCanvasState, "tileScaleFactor" | "tileScaleFactorMobile" | "headerTileScaleFactor" | "headerTileScaleFactorMobile">, platform: TileScalePlatform) => number;
@@ -67,7 +67,13 @@ export const groupHeaderBaseDims = (collapsed) => collapsed
67
67
  // Web and mobile persist separate knobs (AnnotationCanvasState.tileScaleFactor
68
68
  // vs .tileScaleFactorMobile) — read them through resolveTileScaleFactor below.
69
69
  export const DEFAULT_TILE_SCALE = 1;
70
- export const TILE_SCALE_MIN = 0.4;
70
+ // Floor 0.05 ≈ a 5pt speck at the content-fit view — far under tap size,
71
+ // which is fine: the zoom multiplier composes, so pinching in restores the
72
+ // tap target. (Was 0.4; large dense drawings needed much smaller tiles.)
73
+ // NOTE: the slider's 0.05 commit rounding makes the first step off the floor
74
+ // a 2× jump — if that grates in practice, finer low-end rounding and/or a log
75
+ // track in the TileScaleSlider components is the follow-up.
76
+ export const TILE_SCALE_MIN = 0.05;
71
77
  export const TILE_SCALE_MAX = 2;
72
78
  // Resolve the document-wide tile-scale knob for a platform. Web and mobile
73
79
  // persist separate values because a scale calibrated against a desktop window
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.19.3",
3
+ "version": "1.19.5",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",