mapped-image 0.2.0 → 0.3.0

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
@@ -46,6 +46,10 @@ Clicking a cell toggles its selection (filled red) and reports the cell's row nu
46
46
  | `onSelectedImageIndexChange` | `(index: number) => void` | Fires whenever the active image changes. |
47
47
  | `renderImageSelector` | `(props: ImageSelectorProps) => ReactNode` | Optional override for the image-switcher UI rendered over the map. Defaults to a row of buttons; pass your own to fully customize it, or build your own selector entirely outside this component using the controlled `selectedImageIndex`/`onSelectedImageIndexChange` props instead. |
48
48
  | `maxBoundsPadding` | `number` | Extra pannable margin, in pixels, beyond the image edges. Defaults to `50`. |
49
+ | `zoomMultiplier` | `number` | Scales the default fit-to-container zoom. `<1` zooms out, `>1` zooms in. Defaults to `1`. |
50
+ | `weightsEditable` | `boolean` | When `true`, draggable handles appear on every internal row/column boundary, letting the user resize relative weights with the mouse. Defaults to `false`. |
51
+ | `onColumnWeightsChange` | `(weights: number[]) => void` | Fires once a column-weight drag is released, with the full resolved weight array for the currently displayed image. |
52
+ | `onRowWeightsChange` | `(weights: number[]) => void` | Same as `onColumnWeightsChange`, for row-weight drags. |
49
53
 
50
54
  ### `ImageConfig`
51
55
 
@@ -59,10 +63,11 @@ Clicking a cell toggles its selection (filled red) and reports the cell's row nu
59
63
  | `rowWeights` | `Record<number, number>` | Same as `columnWeights`, but for row heights. |
60
64
 
61
65
  > Both `rows`/`columns` must be greater than `0`.
66
+ > `columnWeights`/`rowWeights` indices follow the array order used internally (column index `0` = leftmost; row index `0` = the row nearest the *bottom* of the image, not the top, since that's the underlying coordinate system's origin) — keep this in mind if you see row weights affecting the opposite row from what you expected.
62
67
 
63
68
  ## Building an editor
64
69
 
65
- This package intentionally exposes `selectedImageIndex`/`onSelectedImageIndexChange` and a controllable `selectedCells`/`onSelectedCellsChange` so a separate editor app (outside this package) can drive the grid entirely from its own state — e.g. to add/remove rows and columns, adjust `columnWeights`/`rowWeights`, and persist the resulting `ImageConfig[]` plus selection as a JSON config to reload later. There's no editor UI in this package itself.
70
+ This package intentionally exposes `selectedImageIndex`/`onSelectedImageIndexChange` and a controllable `selectedCells`/`onSelectedCellsChange` so a separate editor app (outside this package) can drive the grid entirely from its own state — e.g. to add/remove rows and columns, persist the resulting `ImageConfig[]` plus selection as a JSON config to reload later, and build its own row/column-count controls. For weight editing specifically, set `weightsEditable` and read the result from `onColumnWeightsChange`/`onRowWeightsChange` to update your own copy of `ImageConfig.columnWeights`/`rowWeights` (drags are previewed locally for responsiveness and only reported once released). There's no other editor UI in this package itself.
66
71
 
67
72
  ## Development
68
73
 
@@ -30,6 +30,14 @@ type MappedImageProps = {
30
30
  renderImageSelector?: (props: ImageSelectorProps) => ReactNode;
31
31
  /** Extra pannable margin (in pixels) beyond the image edges. Defaults to 50. */
32
32
  maxBoundsPadding?: number;
33
+ /** Scales the default fit-to-container zoom. <1 zooms out, >1 zooms in. Defaults to 1. */
34
+ zoomMultiplier?: number;
35
+ /** When true, draggable handles appear between rows/columns to resize their relative weights via mouse. Defaults to false. */
36
+ weightsEditable?: boolean;
37
+ /** Fires once a column-weight drag is released, with the full resolved weight array for the current image. */
38
+ onColumnWeightsChange?: (weights: number[]) => void;
39
+ /** Fires once a row-weight drag is released, with the full resolved weight array for the current image. */
40
+ onRowWeightsChange?: (weights: number[]) => void;
33
41
  };
34
42
  type ImageLayerProps = {
35
43
  image: ImageConfig;
@@ -40,6 +48,10 @@ type ImageLayerProps = {
40
48
  alt: string;
41
49
  selectedCells: Set<string>;
42
50
  maxBoundsPadding?: number;
51
+ zoomMultiplier?: number;
52
+ weightsEditable?: boolean;
53
+ onColumnWeightsChange?: (weights: number[]) => void;
54
+ onRowWeightsChange?: (weights: number[]) => void;
43
55
  onCellClick: GridProps["onCellClick"];
44
56
  };
45
57
  type GridProps = {
@@ -50,6 +62,9 @@ type GridProps = {
50
62
  columnWeights: number[];
51
63
  imgBounds: LatLngTuple;
52
64
  selectedCells: Set<string>;
65
+ weightsEditable?: boolean;
66
+ onColumnWeightsChange?: (weights: number[]) => void;
67
+ onRowWeightsChange?: (weights: number[]) => void;
53
68
  onCellClick?: (props: ICellClickProps) => void;
54
69
  };
55
70
  interface ICellClickProps {
@@ -1,5 +1,5 @@
1
1
  // MappedImage.tsx
2
- import { useState as useState2 } from "react";
2
+ import { useState as useState3 } from "react";
3
3
  import "leaflet/dist/leaflet.css";
4
4
 
5
5
  // #style-inject:#style-inject
@@ -25,7 +25,7 @@ function styleInject(css, { insertAt } = {}) {
25
25
  }
26
26
 
27
27
  // MappedImage.css
28
- styleInject(".grid-header-label-icon {\n background: none;\n border: none;\n}\n.grid-header-label {\n position: absolute;\n display: block;\n font-weight: bold;\n white-space: nowrap;\n}\n.grid-header-label--column {\n transform: translate(-50%, -100%);\n}\n.grid-header-label--row {\n transform: translate(-100%, -50%);\n}\n");
28
+ styleInject(".grid-header-label-icon {\n background: none;\n border: none;\n}\n.grid-header-label {\n position: absolute;\n display: block;\n font-weight: bold;\n white-space: nowrap;\n}\n.grid-header-label--column {\n transform: translate(-50%, -100%);\n}\n.grid-header-label--row {\n transform: translate(-100%, -50%);\n}\n.grid-weight-handle--column {\n cursor: col-resize;\n}\n.grid-weight-handle--row {\n cursor: row-resize;\n}\n.grid-weight-handle:hover {\n stroke-opacity: 0.6;\n}\n");
29
29
 
30
30
  // components/DefaultImageSelector.tsx
31
31
  import { jsx } from "react/jsx-runtime";
@@ -62,11 +62,12 @@ var DefaultImageSelector = ({ images, selectedIndex, onSelect }) => /* @__PURE__
62
62
  );
63
63
 
64
64
  // components/ImageLayer.tsx
65
- import { useEffect, useState } from "react";
65
+ import { useEffect as useEffect2, useState as useState2 } from "react";
66
66
  import { CRS } from "leaflet";
67
67
  import { MapContainer, ImageOverlay } from "react-leaflet";
68
68
 
69
69
  // components/Grid.tsx
70
+ import { useState } from "react";
70
71
  import { Rectangle, Marker } from "react-leaflet";
71
72
 
72
73
  // helpers/weights.ts
@@ -76,6 +77,16 @@ var weightOffsets = (weights) => {
76
77
  weights.forEach((weight) => offsets.push(offsets[offsets.length - 1] + weight));
77
78
  return offsets;
78
79
  };
80
+ var MIN_WEIGHT = 0.2;
81
+ var applyWeightDelta = (weights, index, delta) => {
82
+ const a = weights[index];
83
+ const b = weights[index + 1];
84
+ const clampedDelta = Math.max(-(a - MIN_WEIGHT), Math.min(b - MIN_WEIGHT, delta));
85
+ const next = [...weights];
86
+ next[index] = a + clampedDelta;
87
+ next[index + 1] = b - clampedDelta;
88
+ return next;
89
+ };
79
90
 
80
91
  // helpers/headerIcon.ts
81
92
  import { divIcon } from "leaflet";
@@ -104,8 +115,53 @@ var bindCellAccessibility = (instance, label, selected, onActivate) => {
104
115
  };
105
116
  };
106
117
 
107
- // components/Grid.tsx
118
+ // components/WeightHandle.tsx
119
+ import { useRef } from "react";
120
+ import { Polyline, useMap } from "react-leaflet";
108
121
  import { jsx as jsx2 } from "react/jsx-runtime";
122
+ var WeightHandle = ({ axis, boundary, length, onDrag, onDragEnd }) => {
123
+ const map = useMap();
124
+ const dragStartRef = useRef(null);
125
+ const positions = axis === "column" ? [[0, boundary], [length, boundary]] : [[boundary, 0], [boundary, length]];
126
+ const handleMouseDown = (event) => {
127
+ event.originalEvent.preventDefault();
128
+ map.dragging.disable();
129
+ dragStartRef.current = axis === "column" ? event.latlng.lng : event.latlng.lat;
130
+ const handleMouseMove = (moveEvent) => {
131
+ if (dragStartRef.current === null) {
132
+ return;
133
+ }
134
+ const latlng = map.mouseEventToLatLng(moveEvent);
135
+ const current = axis === "column" ? latlng.lng : latlng.lat;
136
+ onDrag(current - dragStartRef.current);
137
+ };
138
+ const handleMouseUp = () => {
139
+ dragStartRef.current = null;
140
+ map.dragging.enable();
141
+ onDragEnd();
142
+ window.removeEventListener("mousemove", handleMouseMove);
143
+ window.removeEventListener("mouseup", handleMouseUp);
144
+ };
145
+ window.addEventListener("mousemove", handleMouseMove);
146
+ window.addEventListener("mouseup", handleMouseUp);
147
+ };
148
+ return /* @__PURE__ */ jsx2(
149
+ Polyline,
150
+ {
151
+ positions,
152
+ pathOptions: {
153
+ color: "#3388ff",
154
+ weight: 6,
155
+ opacity: 0.15,
156
+ className: `grid-weight-handle grid-weight-handle--${axis}`
157
+ },
158
+ eventHandlers: { mousedown: handleMouseDown }
159
+ }
160
+ );
161
+ };
162
+
163
+ // components/Grid.tsx
164
+ import { jsx as jsx3 } from "react/jsx-runtime";
109
165
  var Grid = ({
110
166
  rows,
111
167
  rowOffset,
@@ -114,14 +170,20 @@ var Grid = ({
114
170
  columnWeights,
115
171
  imgBounds,
116
172
  selectedCells,
173
+ weightsEditable,
174
+ onColumnWeightsChange,
175
+ onRowWeightsChange,
117
176
  onCellClick
118
177
  }) => {
178
+ const [dragState, setDragState] = useState(null);
119
179
  const [height, width] = imgBounds;
120
- const colOffsets = weightOffsets(columnWeights);
180
+ const displayColumnWeights = dragState?.axis === "column" ? applyWeightDelta(columnWeights, dragState.index, dragState.deltaWeight) : columnWeights;
181
+ const displayRowWeights = dragState?.axis === "row" ? applyWeightDelta(rowWeights, dragState.index, dragState.deltaWeight) : rowWeights;
182
+ const colOffsets = weightOffsets(displayColumnWeights);
121
183
  const pxPerColWeightUnit = width / colOffsets[colOffsets.length - 1];
122
184
  const colLeft = (colCount) => colOffsets[colCount] * pxPerColWeightUnit;
123
185
  const colRight = (colCount) => colOffsets[colCount + 1] * pxPerColWeightUnit;
124
- const rowOffsets = weightOffsets(rowWeights);
186
+ const rowOffsets = weightOffsets(displayRowWeights);
125
187
  const pxPerRowWeightUnit = height / rowOffsets[rowOffsets.length - 1];
126
188
  const rowBottom = (rowCount) => rowOffsets[rowCount] * pxPerRowWeightUnit;
127
189
  const rowTop = (rowCount) => rowOffsets[rowCount + 1] * pxPerRowWeightUnit;
@@ -135,7 +197,7 @@ var Grid = ({
135
197
  const cellId = `${rowLabel},${columnLabel}`;
136
198
  const selected = selectedCells.has(cellId);
137
199
  const activate = () => onCellClick?.({ col: columnLabel, row: rowLabel });
138
- return /* @__PURE__ */ jsx2(
200
+ return /* @__PURE__ */ jsx3(
139
201
  Rectangle,
140
202
  {
141
203
  ref: (instance) => bindCellAccessibility(instance, `${columnLabel}${rowLabel}`, selected, activate),
@@ -153,7 +215,7 @@ var Grid = ({
153
215
  );
154
216
  })
155
217
  );
156
- const columnHeaders = columnLabels.map((columnLabel, colCount) => /* @__PURE__ */ jsx2(
218
+ const columnHeaders = columnLabels.map((columnLabel, colCount) => /* @__PURE__ */ jsx3(
157
219
  Marker,
158
220
  {
159
221
  position: [height, (colLeft(colCount) + colRight(colCount)) / 2],
@@ -164,7 +226,7 @@ var Grid = ({
164
226
  ));
165
227
  const rowHeaders = Array.from({ length: rows }).map((_, rowCount) => {
166
228
  const rowLabel = rowOffset + (rows - rowCount);
167
- return /* @__PURE__ */ jsx2(
229
+ return /* @__PURE__ */ jsx3(
168
230
  Marker,
169
231
  {
170
232
  position: [(rowBottom(rowCount) + rowTop(rowCount)) / 2, -7],
@@ -174,7 +236,50 @@ var Grid = ({
174
236
  `row-${rowLabel}`
175
237
  );
176
238
  });
177
- return [...cells, columnHeaders, rowHeaders];
239
+ const columnHandles = weightsEditable ? columnLabels.slice(0, -1).map((_, index) => /* @__PURE__ */ jsx3(
240
+ WeightHandle,
241
+ {
242
+ axis: "column",
243
+ boundary: colRight(index),
244
+ length: height,
245
+ onDrag: (deltaUnits) => setDragState({ axis: "column", index, deltaWeight: deltaUnits / pxPerColWeightUnit }),
246
+ onDragEnd: () => {
247
+ onColumnWeightsChange?.(displayColumnWeights);
248
+ setDragState(null);
249
+ }
250
+ },
251
+ `col-handle-${index}`
252
+ )) : [];
253
+ const rowHandles = weightsEditable ? Array.from({ length: rows - 1 }).map((_, index) => /* @__PURE__ */ jsx3(
254
+ WeightHandle,
255
+ {
256
+ axis: "row",
257
+ boundary: rowTop(index),
258
+ length: width,
259
+ onDrag: (deltaUnits) => setDragState({ axis: "row", index, deltaWeight: deltaUnits / pxPerRowWeightUnit }),
260
+ onDragEnd: () => {
261
+ onRowWeightsChange?.(displayRowWeights);
262
+ setDragState(null);
263
+ }
264
+ },
265
+ `row-handle-${index}`
266
+ )) : [];
267
+ return [...cells, columnHeaders, rowHeaders, columnHandles, rowHandles];
268
+ };
269
+
270
+ // components/ZoomController.tsx
271
+ import { useEffect, useRef as useRef2 } from "react";
272
+ import { useMap as useMap2 } from "react-leaflet";
273
+ var ZoomController = ({ zoomMultiplier }) => {
274
+ const map = useMap2();
275
+ const baseZoomRef = useRef2(null);
276
+ useEffect(() => {
277
+ if (baseZoomRef.current === null) {
278
+ baseZoomRef.current = map.getZoom();
279
+ }
280
+ map.setZoom(baseZoomRef.current + Math.log2(zoomMultiplier));
281
+ }, [map, zoomMultiplier]);
282
+ return null;
178
283
  };
179
284
 
180
285
  // helpers/imageBounds.ts
@@ -202,7 +307,7 @@ var toColumnLetter = (n) => {
202
307
  var colNames = (startIndex, count) => Array.from({ length: count }, (_, i) => toColumnLetter(startIndex + i + 1));
203
308
 
204
309
  // components/ImageLayer.tsx
205
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
310
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
206
311
  var ImageLayer = ({
207
312
  image,
208
313
  colOffset,
@@ -212,11 +317,15 @@ var ImageLayer = ({
212
317
  alt,
213
318
  selectedCells,
214
319
  maxBoundsPadding = 100,
320
+ zoomMultiplier = 1,
321
+ weightsEditable,
322
+ onColumnWeightsChange,
323
+ onRowWeightsChange,
215
324
  onCellClick
216
325
  }) => {
217
- const [imgBounds, setImgBounds] = useState(null);
218
- const [loadError, setLoadError] = useState(null);
219
- useEffect(() => {
326
+ const [imgBounds, setImgBounds] = useState2(null);
327
+ const [loadError, setLoadError] = useState2(null);
328
+ useEffect2(() => {
220
329
  imageBounds(image.src, width, height).then(setImgBounds).catch(setLoadError);
221
330
  }, [image.src, width, height]);
222
331
  if (loadError) {
@@ -245,7 +354,8 @@ var ImageLayer = ({
245
354
  style: { width, height },
246
355
  maxZoom: 2,
247
356
  children: [
248
- /* @__PURE__ */ jsx3(
357
+ /* @__PURE__ */ jsx4(ZoomController, { zoomMultiplier }),
358
+ /* @__PURE__ */ jsx4(
249
359
  Grid,
250
360
  {
251
361
  rows: image.rows,
@@ -255,17 +365,20 @@ var ImageLayer = ({
255
365
  columnWeights,
256
366
  imgBounds,
257
367
  selectedCells,
368
+ weightsEditable,
369
+ onColumnWeightsChange,
370
+ onRowWeightsChange,
258
371
  onCellClick
259
372
  }
260
373
  ),
261
- /* @__PURE__ */ jsx3(ImageOverlay, { url: image.src, bounds, alt })
374
+ /* @__PURE__ */ jsx4(ImageOverlay, { url: image.src, bounds, alt })
262
375
  ]
263
376
  }
264
377
  );
265
378
  };
266
379
 
267
380
  // MappedImage.tsx
268
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
381
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
269
382
  var MappedImage = (props) => {
270
383
  if (props.images.length === 0) {
271
384
  throw new Error("MappedImage requires at least one entry in `images`.");
@@ -276,12 +389,12 @@ var MappedImage = (props) => {
276
389
  `MappedImage: image "${invalidImage.name}" must have rows > 0 and columns > 0.`
277
390
  );
278
391
  }
279
- const [internalSelectedCells, setInternalSelectedCells] = useState2(
392
+ const [internalSelectedCells, setInternalSelectedCells] = useState3(
280
393
  props.selectedCells ?? /* @__PURE__ */ new Set()
281
394
  );
282
395
  const isSelectionControlled = props.selectedCells !== void 0;
283
396
  const selectedCells = isSelectionControlled ? props.selectedCells : internalSelectedCells;
284
- const [selectedImageIndex, setSelectedImageIndex] = useState2(
397
+ const [selectedImageIndex, setSelectedImageIndex] = useState3(
285
398
  props.selectedImageIndex ?? 0
286
399
  );
287
400
  const selectedImage = props.images[selectedImageIndex];
@@ -305,9 +418,9 @@ var MappedImage = (props) => {
305
418
  props.onSelectedCellsChange?.(next);
306
419
  props.onCellClick?.({ row, col });
307
420
  };
308
- const renderSelector = props.renderImageSelector ?? ((selectorProps) => /* @__PURE__ */ jsx4(DefaultImageSelector, { ...selectorProps }));
421
+ const renderSelector = props.renderImageSelector ?? ((selectorProps) => /* @__PURE__ */ jsx5(DefaultImageSelector, { ...selectorProps }));
309
422
  return /* @__PURE__ */ jsxs2("div", { style: { position: "relative", width: props.width, height: props.height }, children: [
310
- /* @__PURE__ */ jsx4(
423
+ /* @__PURE__ */ jsx5(
311
424
  ImageLayer,
312
425
  {
313
426
  image: selectedImage,
@@ -318,6 +431,10 @@ var MappedImage = (props) => {
318
431
  alt: props.alt,
319
432
  selectedCells,
320
433
  maxBoundsPadding: props.maxBoundsPadding,
434
+ zoomMultiplier: props.zoomMultiplier,
435
+ weightsEditable: props.weightsEditable,
436
+ onColumnWeightsChange: props.onColumnWeightsChange,
437
+ onRowWeightsChange: props.onRowWeightsChange,
321
438
  onCellClick: handleCellClick
322
439
  },
323
440
  selectedImage.src
@@ -1 +1 @@
1
- {"version":3,"sources":["../MappedImage.tsx","#style-inject:#style-inject","../MappedImage.css","../components/DefaultImageSelector.tsx","../components/ImageLayer.tsx","../components/Grid.tsx","../helpers/weights.ts","../helpers/headerIcon.ts","../helpers/cellAccessibility.ts","../helpers/imageBounds.ts","../helpers/colNames.ts"],"sourcesContent":["import { useState } from \"react\";\nimport \"leaflet/dist/leaflet.css\";\nimport \"./MappedImage.css\";\nimport type { ICellClickProps, ImageSelectorProps, MappedImageProps } from \"./types\";\nimport { DefaultImageSelector } from \"./components/DefaultImageSelector\";\nimport { ImageLayer } from \"./components/ImageLayer\";\n\nexport * from \"./types\";\n\nexport const MappedImage = (props: MappedImageProps) => {\n if (props.images.length === 0) {\n throw new Error(\"MappedImage requires at least one entry in `images`.\");\n }\n\n const invalidImage = props.images.find((image) => image.rows <= 0 || image.columns <= 0);\n if (invalidImage) {\n throw new Error(\n `MappedImage: image \"${invalidImage.name}\" must have rows > 0 and columns > 0.`,\n );\n }\n\n const [internalSelectedCells, setInternalSelectedCells] = useState<Set<string>>(\n props.selectedCells ?? new Set(),\n );\n const isSelectionControlled = props.selectedCells !== undefined;\n const selectedCells = isSelectionControlled ? props.selectedCells! : internalSelectedCells;\n\n const [selectedImageIndex, setSelectedImageIndex] = useState(\n props.selectedImageIndex ?? 0,\n );\n\n const selectedImage = props.images[selectedImageIndex];\n const colOffset = props.images\n .slice(0, selectedImageIndex)\n .reduce((sum, image) => sum + image.columns, 0);\n const rowOffset = props.images\n .slice(0, selectedImageIndex)\n .reduce((sum, image) => sum + image.rows, 0);\n\n const handleSelectImage = (index: number) => {\n setSelectedImageIndex(index);\n props.onSelectedImageIndexChange?.(index);\n };\n\n const handleCellClick = ({ row, col }: ICellClickProps) => {\n const cellId = `${row},${col}`;\n const next = new Set(selectedCells);\n if (next.has(cellId)) {\n next.delete(cellId);\n } else {\n next.add(cellId);\n }\n\n if (!isSelectionControlled) {\n setInternalSelectedCells(next);\n }\n props.onSelectedCellsChange?.(next);\n props.onCellClick?.({ row, col });\n };\n\n const renderSelector = props.renderImageSelector ?? (\n (selectorProps: ImageSelectorProps) => <DefaultImageSelector {...selectorProps} />\n );\n\n return (\n <div style={{ position: \"relative\", width: props.width, height: props.height }}>\n <ImageLayer\n key={selectedImage.src}\n image={selectedImage}\n colOffset={colOffset}\n rowOffset={rowOffset}\n width={props.width}\n height={props.height}\n alt={props.alt}\n selectedCells={selectedCells}\n maxBoundsPadding={props.maxBoundsPadding}\n onCellClick={handleCellClick}\n />\n {renderSelector({\n images: props.images,\n selectedIndex: selectedImageIndex,\n onSelect: handleSelectImage,\n })}\n </div>\n );\n};\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".grid-header-label-icon {\\n background: none;\\n border: none;\\n}\\n.grid-header-label {\\n position: absolute;\\n display: block;\\n font-weight: bold;\\n white-space: nowrap;\\n}\\n.grid-header-label--column {\\n transform: translate(-50%, -100%);\\n}\\n.grid-header-label--row {\\n transform: translate(-100%, -50%);\\n}\\n\")","import type { ImageSelectorProps } from \"../types\";\n\nexport const DefaultImageSelector = ({ images, selectedIndex, onSelect }: ImageSelectorProps) => (\n <div\n style={{\n position: \"absolute\",\n top: 10,\n right: 10,\n zIndex: 1000,\n display: \"flex\",\n gap: 8,\n }}\n >\n {images.map((image, index) => (\n <button\n key={image.name}\n type=\"button\"\n onClick={() => onSelect(index)}\n style={{\n padding: \"12px 20px\",\n fontSize: 16,\n fontWeight: index === selectedIndex ? \"bold\" : \"normal\",\n background: index === selectedIndex ? \"#3388ff\" : \"white\",\n color: index === selectedIndex ? \"white\" : \"black\",\n border: \"1px solid #3388ff\",\n borderRadius: 6,\n }}\n >\n {image.name}\n </button>\n ))}\n </div>\n);\n","import { useEffect, useState } from \"react\";\nimport { CRS, type LatLngBoundsExpression, type LatLngTuple } from \"leaflet\";\nimport { MapContainer, ImageOverlay } from \"react-leaflet\";\nimport type { ImageLayerProps } from \"../types\";\nimport { Grid } from \"./Grid\";\nimport { imageBounds } from \"../helpers/imageBounds\";\nimport { colNames } from \"../helpers/colNames\";\nimport { resolveWeights } from \"../helpers/weights\";\n\nexport const ImageLayer = ({\n image,\n colOffset,\n rowOffset,\n width,\n height,\n alt,\n selectedCells,\n maxBoundsPadding = 100,\n onCellClick,\n}: ImageLayerProps) => {\n const [imgBounds, setImgBounds] = useState<LatLngTuple | null>(null);\n const [loadError, setLoadError] = useState<Error | null>(null);\n\n useEffect(() => {\n imageBounds(image.src, width, height).then(setImgBounds).catch(setLoadError);\n }, [image.src, width, height]);\n\n if (loadError) {\n throw loadError;\n }\n\n if (!imgBounds) {\n return null;\n }\n\n const [imgHeight, imgWidth] = imgBounds;\n const bounds: LatLngBoundsExpression = [[0, 0], imgBounds];\n const maxBounds: LatLngBoundsExpression = [\n [-maxBoundsPadding , -maxBoundsPadding],\n [imgHeight + maxBoundsPadding, imgWidth + maxBoundsPadding],\n ];\n const columnLabels = colNames(colOffset, image.columns);\n const columnWeights = resolveWeights(image.columns, image.columnWeights);\n const rowWeights = resolveWeights(image.rows, image.rowWeights);\n\n return (\n <MapContainer\n crs={CRS.Simple}\n bounds={bounds}\n boundsOptions={{ padding: [50, 50] }}\n maxBounds={maxBounds}\n maxBoundsViscosity={0.5}\n style={{ width, height }}\n maxZoom={2}\n >\n <Grid\n rows={image.rows}\n rowOffset={rowOffset}\n rowWeights={rowWeights}\n columnLabels={columnLabels}\n columnWeights={columnWeights}\n imgBounds={imgBounds}\n selectedCells={selectedCells}\n onCellClick={onCellClick}\n />\n <ImageOverlay url={image.src} bounds={bounds} alt={alt} />\n </MapContainer>\n );\n};\n","import { Rectangle, Marker } from \"react-leaflet\";\nimport type { LatLngBoundsExpression } from \"leaflet\";\nimport type { GridProps } from \"../types\";\nimport { weightOffsets } from \"../helpers/weights\";\nimport { headerIcon } from \"../helpers/headerIcon\";\nimport { bindCellAccessibility } from \"../helpers/cellAccessibility\";\n\nexport const Grid = ({\n rows,\n rowOffset,\n rowWeights,\n columnLabels,\n columnWeights,\n imgBounds,\n selectedCells,\n onCellClick,\n}: GridProps) => {\n const [height, width] = imgBounds;\n\n const colOffsets = weightOffsets(columnWeights);\n const pxPerColWeightUnit = width / colOffsets[colOffsets.length - 1];\n const colLeft = (colCount: number) => colOffsets[colCount] * pxPerColWeightUnit;\n const colRight = (colCount: number) => colOffsets[colCount + 1] * pxPerColWeightUnit;\n\n const rowOffsets = weightOffsets(rowWeights);\n const pxPerRowWeightUnit = height / rowOffsets[rowOffsets.length - 1];\n const rowBottom = (rowCount: number) => rowOffsets[rowCount] * pxPerRowWeightUnit;\n const rowTop = (rowCount: number) => rowOffsets[rowCount + 1] * pxPerRowWeightUnit;\n\n const cells = Array.from({ length: rows }).map((_, rowCount) =>\n columnLabels.map((columnLabel, colCount) => {\n const bounds: LatLngBoundsExpression = [\n [rowTop(rowCount), colRight(colCount)],\n [rowBottom(rowCount), colLeft(colCount)],\n ];\n\n const rowLabel = rowOffset + (rows - rowCount);\n const cellId = `${rowLabel},${columnLabel}`;\n const selected = selectedCells.has(cellId);\n const activate = () => onCellClick?.({ col: columnLabel, row: rowLabel });\n\n return (\n <Rectangle\n key={cellId}\n ref={(instance) =>\n bindCellAccessibility(instance, `${columnLabel}${rowLabel}`, selected, activate)\n }\n bounds={bounds}\n pathOptions={{\n fillColor: selected ? \"red\" : \"transparent\",\n fillOpacity: selected ? 0.5 : 0,\n weight: 1.5,\n }}\n eventHandlers={{\n click: activate,\n }}\n />\n );\n }),\n );\n\n const columnHeaders = columnLabels.map((columnLabel, colCount) => (\n <Marker\n key={`col-${columnLabel}`}\n position={[height, (colLeft(colCount) + colRight(colCount)) / 2]}\n icon={headerIcon(columnLabel, \"grid-header-label--column\")}\n interactive={false}\n />\n ));\n\n const rowHeaders = Array.from({ length: rows }).map((_, rowCount) => {\n const rowLabel = rowOffset + (rows - rowCount);\n return (\n <Marker\n key={`row-${rowLabel}`}\n position={[(rowBottom(rowCount) + rowTop(rowCount)) / 2, -7]}\n icon={headerIcon(String(rowLabel), \"grid-header-label--row\")}\n interactive={false}\n />\n );\n });\n\n return [...cells, columnHeaders, rowHeaders];\n};\n","export const resolveWeights = (\n count: number,\n overrides: Record<number, number> = {},\n): number[] => Array.from({ length: count }, (_, i) => overrides[i] ?? 1);\n\n/** Cumulative leading edge (in weight units) of each index, plus the total at the end. */\nexport const weightOffsets = (weights: number[]): number[] => {\n const offsets = [0];\n weights.forEach((weight) => offsets.push(offsets[offsets.length - 1] + weight));\n return offsets;\n};\n","import { divIcon } from \"leaflet\";\n\nexport const headerIcon = (label: string, className: string) =>\n divIcon({\n html: `<span class=\"grid-header-label ${className}\">${label}</span>`,\n className: \"grid-header-label-icon\",\n iconSize: [0, 0],\n });\n","import type { Rectangle as LeafletRectangle } from \"leaflet\";\n\n// Path.getElement() returns the underlying SVG node but isn't declared in @types/leaflet, hence the cast.\nconst getPathElement = (instance: LeafletRectangle) =>\n (instance as unknown as { getElement(): SVGElement | undefined }).getElement();\n\nexport const bindCellAccessibility = (\n instance: LeafletRectangle | null,\n label: string,\n selected: boolean,\n onActivate: () => void,\n) => {\n const el = instance && getPathElement(instance);\n if (!el) {\n return;\n }\n\n el.setAttribute(\"role\", \"button\");\n el.setAttribute(\"tabindex\", \"0\");\n el.setAttribute(\"aria-label\", label);\n el.setAttribute(\"aria-pressed\", String(selected));\n el.onkeydown = (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onActivate();\n }\n };\n};\n","import type { LatLngTuple } from \"leaflet\";\r\n\r\nexport const imageBounds = (src: string, containerWidth: number, containerHeight: number): Promise<LatLngTuple> =>\r\n new Promise((resolve, reject) => {\r\n const img = new Image();\r\n img.src = src;\r\n img.onload = () => {\r\n const scale = Math.min(containerWidth / img.width, containerHeight / img.height);\r\n resolve([img.height * scale, img.width * scale]);\r\n };\r\n img.onerror = () => reject(new Error(`Failed to load image: ${src}`));\r\n });","const toColumnLetter = (n: number): string => {\n let result = \"\";\n let remaining = n;\n\n while (remaining > 0) {\n const remainder = (remaining - 1) % 26;\n result = String.fromCharCode(65 + remainder) + result;\n remaining = Math.floor((remaining - 1) / 26);\n }\n\n return result;\n};\n\nexport const colNames = (startIndex: number, count: number): string[] =>\n Array.from({ length: count }, (_, i) => toColumnLetter(startIndex + i + 1));\n"],"mappings":";AAAA,SAAS,YAAAA,iBAAgB;AACzB,OAAO;;;ACAkB,SAAR,YAA6B,KAAK,EAAE,SAAS,IAAI,CAAC,GAAG;AAC1D,MAAI,CAAC,OAAO,OAAO,aAAa,YAAa;AAE7C,QAAM,OAAO,SAAS,QAAQ,SAAS,qBAAqB,MAAM,EAAE,CAAC;AACrE,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,OAAO;AAEb,MAAI,aAAa,OAAO;AACtB,QAAI,KAAK,YAAY;AACnB,WAAK,aAAa,OAAO,KAAK,UAAU;AAAA,IAC1C,OAAO;AACL,WAAK,YAAY,KAAK;AAAA,IACxB;AAAA,EACF,OAAO;AACL,SAAK,YAAY,KAAK;AAAA,EACxB;AAEA,MAAI,MAAM,YAAY;AACpB,UAAM,WAAW,UAAU;AAAA,EAC7B,OAAO;AACL,UAAM,YAAY,SAAS,eAAe,GAAG,CAAC;AAAA,EAChD;AACF;;;ACvB8B,YAAY,kUAAkU;;;ACc1W;AAZL,IAAM,uBAAuB,CAAC,EAAE,QAAQ,eAAe,SAAS,MACnE;AAAA,EAAC;AAAA;AAAA,IACG,OAAO;AAAA,MACH,UAAU;AAAA,MACV,KAAK;AAAA,MACL,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,KAAK;AAAA,IACT;AAAA,IAEC,iBAAO,IAAI,CAAC,OAAO,UAChB;AAAA,MAAC;AAAA;AAAA,QAEG,MAAK;AAAA,QACL,SAAS,MAAM,SAAS,KAAK;AAAA,QAC7B,OAAO;AAAA,UACH,SAAS;AAAA,UACT,UAAU;AAAA,UACV,YAAY,UAAU,gBAAgB,SAAS;AAAA,UAC/C,YAAY,UAAU,gBAAgB,YAAY;AAAA,UAClD,OAAO,UAAU,gBAAgB,UAAU;AAAA,UAC3C,QAAQ;AAAA,UACR,cAAc;AAAA,QAClB;AAAA,QAEC,gBAAM;AAAA;AAAA,MAbF,MAAM;AAAA,IAcf,CACH;AAAA;AACL;;;AC/BJ,SAAS,WAAW,gBAAgB;AACpC,SAAS,WAA0D;AACnE,SAAS,cAAc,oBAAoB;;;ACF3C,SAAS,WAAW,cAAc;;;ACA3B,IAAM,iBAAiB,CAC1B,OACA,YAAoC,CAAC,MAC1B,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC;AAGjE,IAAM,gBAAgB,CAAC,YAAgC;AAC1D,QAAM,UAAU,CAAC,CAAC;AAClB,UAAQ,QAAQ,CAAC,WAAW,QAAQ,KAAK,QAAQ,QAAQ,SAAS,CAAC,IAAI,MAAM,CAAC;AAC9E,SAAO;AACX;;;ACVA,SAAS,eAAe;AAEjB,IAAM,aAAa,CAAC,OAAe,cACtC,QAAQ;AAAA,EACJ,MAAM,kCAAkC,SAAS,KAAK,KAAK;AAAA,EAC3D,WAAW;AAAA,EACX,UAAU,CAAC,GAAG,CAAC;AACnB,CAAC;;;ACJL,IAAM,iBAAiB,CAAC,aACnB,SAAiE,WAAW;AAE1E,IAAM,wBAAwB,CACjC,UACA,OACA,UACA,eACC;AACD,QAAM,KAAK,YAAY,eAAe,QAAQ;AAC9C,MAAI,CAAC,IAAI;AACL;AAAA,EACJ;AAEA,KAAG,aAAa,QAAQ,QAAQ;AAChC,KAAG,aAAa,YAAY,GAAG;AAC/B,KAAG,aAAa,cAAc,KAAK;AACnC,KAAG,aAAa,gBAAgB,OAAO,QAAQ,CAAC;AAChD,KAAG,YAAY,CAAC,MAAM;AAClB,QAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACpC,QAAE,eAAe;AACjB,iBAAW;AAAA,IACf;AAAA,EACJ;AACJ;;;AHegB,gBAAAC,YAAA;AAnCT,IAAM,OAAO,CAAC;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,MAAiB;AACb,QAAM,CAAC,QAAQ,KAAK,IAAI;AAExB,QAAM,aAAa,cAAc,aAAa;AAC9C,QAAM,qBAAqB,QAAQ,WAAW,WAAW,SAAS,CAAC;AACnE,QAAM,UAAU,CAAC,aAAqB,WAAW,QAAQ,IAAI;AAC7D,QAAM,WAAW,CAAC,aAAqB,WAAW,WAAW,CAAC,IAAI;AAElE,QAAM,aAAa,cAAc,UAAU;AAC3C,QAAM,qBAAqB,SAAS,WAAW,WAAW,SAAS,CAAC;AACpE,QAAM,YAAY,CAAC,aAAqB,WAAW,QAAQ,IAAI;AAC/D,QAAM,SAAS,CAAC,aAAqB,WAAW,WAAW,CAAC,IAAI;AAEhE,QAAM,QAAQ,MAAM,KAAK,EAAE,QAAQ,KAAK,CAAC,EAAE;AAAA,IAAI,CAAC,GAAG,aAC/C,aAAa,IAAI,CAAC,aAAa,aAAa;AACxC,YAAM,SAAiC;AAAA,QACnC,CAAC,OAAO,QAAQ,GAAG,SAAS,QAAQ,CAAC;AAAA,QACrC,CAAC,UAAU,QAAQ,GAAG,QAAQ,QAAQ,CAAC;AAAA,MAC3C;AAEA,YAAM,WAAW,aAAa,OAAO;AACrC,YAAM,SAAS,GAAG,QAAQ,IAAI,WAAW;AACzC,YAAM,WAAW,cAAc,IAAI,MAAM;AACzC,YAAM,WAAW,MAAM,cAAc,EAAE,KAAK,aAAa,KAAK,SAAS,CAAC;AAExE,aACI,gBAAAA;AAAA,QAAC;AAAA;AAAA,UAEG,KAAK,CAAC,aACF,sBAAsB,UAAU,GAAG,WAAW,GAAG,QAAQ,IAAI,UAAU,QAAQ;AAAA,UAEnF;AAAA,UACA,aAAa;AAAA,YACT,WAAW,WAAW,QAAQ;AAAA,YAC9B,aAAa,WAAW,MAAM;AAAA,YAC9B,QAAQ;AAAA,UACZ;AAAA,UACA,eAAe;AAAA,YACX,OAAO;AAAA,UACX;AAAA;AAAA,QAZK;AAAA,MAaT;AAAA,IAER,CAAC;AAAA,EACL;AAEA,QAAM,gBAAgB,aAAa,IAAI,CAAC,aAAa,aACjD,gBAAAA;AAAA,IAAC;AAAA;AAAA,MAEG,UAAU,CAAC,SAAS,QAAQ,QAAQ,IAAI,SAAS,QAAQ,KAAK,CAAC;AAAA,MAC/D,MAAM,WAAW,aAAa,2BAA2B;AAAA,MACzD,aAAa;AAAA;AAAA,IAHR,OAAO,WAAW;AAAA,EAI3B,CACH;AAED,QAAM,aAAa,MAAM,KAAK,EAAE,QAAQ,KAAK,CAAC,EAAE,IAAI,CAAC,GAAG,aAAa;AACjE,UAAM,WAAW,aAAa,OAAO;AACrC,WACI,gBAAAA;AAAA,MAAC;AAAA;AAAA,QAEG,UAAU,EAAE,UAAU,QAAQ,IAAI,OAAO,QAAQ,KAAK,GAAG,EAAE;AAAA,QAC3D,MAAM,WAAW,OAAO,QAAQ,GAAG,wBAAwB;AAAA,QAC3D,aAAa;AAAA;AAAA,MAHR,OAAO,QAAQ;AAAA,IAIxB;AAAA,EAER,CAAC;AAED,SAAO,CAAC,GAAG,OAAO,eAAe,UAAU;AAC/C;;;AIjFO,IAAM,cAAc,CAAC,KAAa,gBAAwB,oBAC7D,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC7B,QAAM,MAAM,IAAI,MAAM;AACtB,MAAI,MAAM;AACV,MAAI,SAAS,MAAM;AACf,UAAM,QAAQ,KAAK,IAAI,iBAAiB,IAAI,OAAO,kBAAkB,IAAI,MAAM;AAC/E,YAAQ,CAAC,IAAI,SAAS,OAAO,IAAI,QAAQ,KAAK,CAAC;AAAA,EACnD;AACA,MAAI,UAAU,MAAM,OAAO,IAAI,MAAM,yBAAyB,GAAG,EAAE,CAAC;AACxE,CAAC;;;ACXL,IAAM,iBAAiB,CAAC,MAAsB;AAC1C,MAAI,SAAS;AACb,MAAI,YAAY;AAEhB,SAAO,YAAY,GAAG;AAClB,UAAM,aAAa,YAAY,KAAK;AACpC,aAAS,OAAO,aAAa,KAAK,SAAS,IAAI;AAC/C,gBAAY,KAAK,OAAO,YAAY,KAAK,EAAE;AAAA,EAC/C;AAEA,SAAO;AACX;AAEO,IAAM,WAAW,CAAC,YAAoB,UACzC,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,MAAM,eAAe,aAAa,IAAI,CAAC,CAAC;;;ANgCtE,SASI,OAAAC,MATJ;AArCD,IAAM,aAAa,CAAC;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,mBAAmB;AAAA,EACnB;AACJ,MAAuB;AACnB,QAAM,CAAC,WAAW,YAAY,IAAI,SAA6B,IAAI;AACnE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAuB,IAAI;AAE7D,YAAU,MAAM;AACZ,gBAAY,MAAM,KAAK,OAAO,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY;AAAA,EAC/E,GAAG,CAAC,MAAM,KAAK,OAAO,MAAM,CAAC;AAE7B,MAAI,WAAW;AACX,UAAM;AAAA,EACV;AAEA,MAAI,CAAC,WAAW;AACZ,WAAO;AAAA,EACX;AAEA,QAAM,CAAC,WAAW,QAAQ,IAAI;AAC9B,QAAM,SAAiC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS;AACzD,QAAM,YAAoC;AAAA,IACtC,CAAC,CAAC,kBAAmB,CAAC,gBAAgB;AAAA,IACtC,CAAC,YAAY,kBAAkB,WAAW,gBAAgB;AAAA,EAC9D;AACA,QAAM,eAAe,SAAS,WAAW,MAAM,OAAO;AACtD,QAAM,gBAAgB,eAAe,MAAM,SAAS,MAAM,aAAa;AACvE,QAAM,aAAa,eAAe,MAAM,MAAM,MAAM,UAAU;AAE9D,SACI;AAAA,IAAC;AAAA;AAAA,MACG,KAAK,IAAI;AAAA,MACT;AAAA,MACA,eAAe,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE;AAAA,MACnC;AAAA,MACA,oBAAoB;AAAA,MACpB,OAAO,EAAE,OAAO,OAAO;AAAA,MACvB,SAAS;AAAA,MAET;AAAA,wBAAAA;AAAA,UAAC;AAAA;AAAA,YACG,MAAM,MAAM;AAAA,YACZ;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACJ;AAAA,QACA,gBAAAA,KAAC,gBAAa,KAAK,MAAM,KAAK,QAAgB,KAAU;AAAA;AAAA;AAAA,EAC5D;AAER;;;AJP+C,gBAAAC,MAIvC,QAAAC,aAJuC;AApDxC,IAAM,cAAc,CAAC,UAA4B;AACpD,MAAI,MAAM,OAAO,WAAW,GAAG;AAC3B,UAAM,IAAI,MAAM,sDAAsD;AAAA,EAC1E;AAEA,QAAM,eAAe,MAAM,OAAO,KAAK,CAAC,UAAU,MAAM,QAAQ,KAAK,MAAM,WAAW,CAAC;AACvF,MAAI,cAAc;AACd,UAAM,IAAI;AAAA,MACN,uBAAuB,aAAa,IAAI;AAAA,IAC5C;AAAA,EACJ;AAEA,QAAM,CAAC,uBAAuB,wBAAwB,IAAIC;AAAA,IACtD,MAAM,iBAAiB,oBAAI,IAAI;AAAA,EACnC;AACA,QAAM,wBAAwB,MAAM,kBAAkB;AACtD,QAAM,gBAAgB,wBAAwB,MAAM,gBAAiB;AAErE,QAAM,CAAC,oBAAoB,qBAAqB,IAAIA;AAAA,IAChD,MAAM,sBAAsB;AAAA,EAChC;AAEA,QAAM,gBAAgB,MAAM,OAAO,kBAAkB;AACrD,QAAM,YAAY,MAAM,OACnB,MAAM,GAAG,kBAAkB,EAC3B,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,SAAS,CAAC;AAClD,QAAM,YAAY,MAAM,OACnB,MAAM,GAAG,kBAAkB,EAC3B,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,MAAM,CAAC;AAE/C,QAAM,oBAAoB,CAAC,UAAkB;AACzC,0BAAsB,KAAK;AAC3B,UAAM,6BAA6B,KAAK;AAAA,EAC5C;AAEA,QAAM,kBAAkB,CAAC,EAAE,KAAK,IAAI,MAAuB;AACvD,UAAM,SAAS,GAAG,GAAG,IAAI,GAAG;AAC5B,UAAM,OAAO,IAAI,IAAI,aAAa;AAClC,QAAI,KAAK,IAAI,MAAM,GAAG;AAClB,WAAK,OAAO,MAAM;AAAA,IACtB,OAAO;AACH,WAAK,IAAI,MAAM;AAAA,IACnB;AAEA,QAAI,CAAC,uBAAuB;AACxB,+BAAyB,IAAI;AAAA,IACjC;AACA,UAAM,wBAAwB,IAAI;AAClC,UAAM,cAAc,EAAE,KAAK,IAAI,CAAC;AAAA,EACpC;AAEA,QAAM,iBAAiB,MAAM,wBACzB,CAAC,kBAAsC,gBAAAF,KAAC,wBAAsB,GAAG,eAAe;AAGpF,SACI,gBAAAC,MAAC,SAAI,OAAO,EAAE,UAAU,YAAY,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAO,GACzE;AAAA,oBAAAD;AAAA,MAAC;AAAA;AAAA,QAEG,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM;AAAA,QACd,KAAK,MAAM;AAAA,QACX;AAAA,QACA,kBAAkB,MAAM;AAAA,QACxB,aAAa;AAAA;AAAA,MATR,cAAc;AAAA,IAUvB;AAAA,IACC,eAAe;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,eAAe;AAAA,MACf,UAAU;AAAA,IACd,CAAC;AAAA,KACL;AAER;","names":["useState","jsx","jsx","jsx","jsxs","useState"]}
1
+ {"version":3,"sources":["../MappedImage.tsx","#style-inject:#style-inject","../MappedImage.css","../components/DefaultImageSelector.tsx","../components/ImageLayer.tsx","../components/Grid.tsx","../helpers/weights.ts","../helpers/headerIcon.ts","../helpers/cellAccessibility.ts","../components/WeightHandle.tsx","../components/ZoomController.tsx","../helpers/imageBounds.ts","../helpers/colNames.ts"],"sourcesContent":["import { useState } from \"react\";\nimport \"leaflet/dist/leaflet.css\";\nimport \"./MappedImage.css\";\nimport type { ICellClickProps, ImageSelectorProps, MappedImageProps } from \"./types\";\nimport { DefaultImageSelector } from \"./components/DefaultImageSelector\";\nimport { ImageLayer } from \"./components/ImageLayer\";\n\n// Type-only re-export; doesn't add a non-component export for fast-refresh purposes.\n// eslint-disable-next-line react-refresh/only-export-components\nexport * from \"./types\";\n\nexport const MappedImage = (props: MappedImageProps) => {\n if (props.images.length === 0) {\n throw new Error(\"MappedImage requires at least one entry in `images`.\");\n }\n\n const invalidImage = props.images.find((image) => image.rows <= 0 || image.columns <= 0);\n if (invalidImage) {\n throw new Error(\n `MappedImage: image \"${invalidImage.name}\" must have rows > 0 and columns > 0.`,\n );\n }\n\n const [internalSelectedCells, setInternalSelectedCells] = useState<Set<string>>(\n props.selectedCells ?? new Set(),\n );\n const isSelectionControlled = props.selectedCells !== undefined;\n const selectedCells = isSelectionControlled ? props.selectedCells! : internalSelectedCells;\n\n const [selectedImageIndex, setSelectedImageIndex] = useState(\n props.selectedImageIndex ?? 0,\n );\n\n const selectedImage = props.images[selectedImageIndex];\n const colOffset = props.images\n .slice(0, selectedImageIndex)\n .reduce((sum, image) => sum + image.columns, 0);\n const rowOffset = props.images\n .slice(0, selectedImageIndex)\n .reduce((sum, image) => sum + image.rows, 0);\n\n const handleSelectImage = (index: number) => {\n setSelectedImageIndex(index);\n props.onSelectedImageIndexChange?.(index);\n };\n\n const handleCellClick = ({ row, col }: ICellClickProps) => {\n const cellId = `${row},${col}`;\n const next = new Set(selectedCells);\n if (next.has(cellId)) {\n next.delete(cellId);\n } else {\n next.add(cellId);\n }\n\n if (!isSelectionControlled) {\n setInternalSelectedCells(next);\n }\n props.onSelectedCellsChange?.(next);\n props.onCellClick?.({ row, col });\n };\n\n const renderSelector = props.renderImageSelector ?? (\n (selectorProps: ImageSelectorProps) => <DefaultImageSelector {...selectorProps} />\n );\n\n return (\n <div style={{ position: \"relative\", width: props.width, height: props.height }}>\n <ImageLayer\n key={selectedImage.src}\n image={selectedImage}\n colOffset={colOffset}\n rowOffset={rowOffset}\n width={props.width}\n height={props.height}\n alt={props.alt}\n selectedCells={selectedCells}\n maxBoundsPadding={props.maxBoundsPadding}\n zoomMultiplier={props.zoomMultiplier}\n weightsEditable={props.weightsEditable}\n onColumnWeightsChange={props.onColumnWeightsChange}\n onRowWeightsChange={props.onRowWeightsChange}\n onCellClick={handleCellClick}\n />\n {renderSelector({\n images: props.images,\n selectedIndex: selectedImageIndex,\n onSelect: handleSelectImage,\n })}\n </div>\n );\n};\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".grid-header-label-icon {\\n background: none;\\n border: none;\\n}\\n.grid-header-label {\\n position: absolute;\\n display: block;\\n font-weight: bold;\\n white-space: nowrap;\\n}\\n.grid-header-label--column {\\n transform: translate(-50%, -100%);\\n}\\n.grid-header-label--row {\\n transform: translate(-100%, -50%);\\n}\\n.grid-weight-handle--column {\\n cursor: col-resize;\\n}\\n.grid-weight-handle--row {\\n cursor: row-resize;\\n}\\n.grid-weight-handle:hover {\\n stroke-opacity: 0.6;\\n}\\n\")","import type { ImageSelectorProps } from \"../types\";\n\nexport const DefaultImageSelector = ({ images, selectedIndex, onSelect }: ImageSelectorProps) => (\n <div\n style={{\n position: \"absolute\",\n top: 10,\n right: 10,\n zIndex: 1000,\n display: \"flex\",\n gap: 8,\n }}\n >\n {images.map((image, index) => (\n <button\n key={image.name}\n type=\"button\"\n onClick={() => onSelect(index)}\n style={{\n padding: \"12px 20px\",\n fontSize: 16,\n fontWeight: index === selectedIndex ? \"bold\" : \"normal\",\n background: index === selectedIndex ? \"#3388ff\" : \"white\",\n color: index === selectedIndex ? \"white\" : \"black\",\n border: \"1px solid #3388ff\",\n borderRadius: 6,\n }}\n >\n {image.name}\n </button>\n ))}\n </div>\n);\n","import { useEffect, useState } from \"react\";\nimport { CRS, type LatLngBoundsExpression, type LatLngTuple } from \"leaflet\";\nimport { MapContainer, ImageOverlay } from \"react-leaflet\";\nimport type { ImageLayerProps } from \"../types\";\nimport { Grid } from \"./Grid\";\nimport { ZoomController } from \"./ZoomController\";\nimport { imageBounds } from \"../helpers/imageBounds\";\nimport { colNames } from \"../helpers/colNames\";\nimport { resolveWeights } from \"../helpers/weights\";\n\nexport const ImageLayer = ({\n image,\n colOffset,\n rowOffset,\n width,\n height,\n alt,\n selectedCells,\n maxBoundsPadding = 100,\n zoomMultiplier = 1,\n weightsEditable,\n onColumnWeightsChange,\n onRowWeightsChange,\n onCellClick,\n}: ImageLayerProps) => {\n const [imgBounds, setImgBounds] = useState<LatLngTuple | null>(null);\n const [loadError, setLoadError] = useState<Error | null>(null);\n\n useEffect(() => {\n imageBounds(image.src, width, height).then(setImgBounds).catch(setLoadError);\n }, [image.src, width, height]);\n\n if (loadError) {\n throw loadError;\n }\n\n if (!imgBounds) {\n return null;\n }\n\n const [imgHeight, imgWidth] = imgBounds;\n const bounds: LatLngBoundsExpression = [[0, 0], imgBounds];\n const maxBounds: LatLngBoundsExpression = [\n [-maxBoundsPadding , -maxBoundsPadding],\n [imgHeight + maxBoundsPadding, imgWidth + maxBoundsPadding],\n ];\n const columnLabels = colNames(colOffset, image.columns);\n const columnWeights = resolveWeights(image.columns, image.columnWeights);\n const rowWeights = resolveWeights(image.rows, image.rowWeights);\n\n return (\n <MapContainer\n crs={CRS.Simple}\n bounds={bounds}\n boundsOptions={{ padding: [50, 50] }}\n maxBounds={maxBounds}\n maxBoundsViscosity={0.5}\n style={{ width, height }}\n maxZoom={2}\n >\n <ZoomController zoomMultiplier={zoomMultiplier} />\n <Grid\n rows={image.rows}\n rowOffset={rowOffset}\n rowWeights={rowWeights}\n columnLabels={columnLabels}\n columnWeights={columnWeights}\n imgBounds={imgBounds}\n selectedCells={selectedCells}\n weightsEditable={weightsEditable}\n onColumnWeightsChange={onColumnWeightsChange}\n onRowWeightsChange={onRowWeightsChange}\n onCellClick={onCellClick}\n />\n <ImageOverlay url={image.src} bounds={bounds} alt={alt} />\n </MapContainer>\n );\n};\n","import { useState } from \"react\";\nimport { Rectangle, Marker } from \"react-leaflet\";\nimport type { LatLngBoundsExpression } from \"leaflet\";\nimport type { GridProps } from \"../types\";\nimport { applyWeightDelta, weightOffsets } from \"../helpers/weights\";\nimport { headerIcon } from \"../helpers/headerIcon\";\nimport { bindCellAccessibility } from \"../helpers/cellAccessibility\";\nimport { WeightHandle } from \"./WeightHandle\";\n\ntype DragState = { axis: \"column\" | \"row\"; index: number; deltaWeight: number };\n\nexport const Grid = ({\n rows,\n rowOffset,\n rowWeights,\n columnLabels,\n columnWeights,\n imgBounds,\n selectedCells,\n weightsEditable,\n onColumnWeightsChange,\n onRowWeightsChange,\n onCellClick,\n}: GridProps) => {\n const [dragState, setDragState] = useState<DragState | null>(null);\n const [height, width] = imgBounds;\n\n const displayColumnWeights =\n dragState?.axis === \"column\"\n ? applyWeightDelta(columnWeights, dragState.index, dragState.deltaWeight)\n : columnWeights;\n const displayRowWeights =\n dragState?.axis === \"row\"\n ? applyWeightDelta(rowWeights, dragState.index, dragState.deltaWeight)\n : rowWeights;\n\n const colOffsets = weightOffsets(displayColumnWeights);\n const pxPerColWeightUnit = width / colOffsets[colOffsets.length - 1];\n const colLeft = (colCount: number) => colOffsets[colCount] * pxPerColWeightUnit;\n const colRight = (colCount: number) => colOffsets[colCount + 1] * pxPerColWeightUnit;\n\n const rowOffsets = weightOffsets(displayRowWeights);\n const pxPerRowWeightUnit = height / rowOffsets[rowOffsets.length - 1];\n const rowBottom = (rowCount: number) => rowOffsets[rowCount] * pxPerRowWeightUnit;\n const rowTop = (rowCount: number) => rowOffsets[rowCount + 1] * pxPerRowWeightUnit;\n\n const cells = Array.from({ length: rows }).map((_, rowCount) =>\n columnLabels.map((columnLabel, colCount) => {\n const bounds: LatLngBoundsExpression = [\n [rowTop(rowCount), colRight(colCount)],\n [rowBottom(rowCount), colLeft(colCount)],\n ];\n\n const rowLabel = rowOffset + (rows - rowCount);\n const cellId = `${rowLabel},${columnLabel}`;\n const selected = selectedCells.has(cellId);\n const activate = () => onCellClick?.({ col: columnLabel, row: rowLabel });\n\n return (\n <Rectangle\n key={cellId}\n ref={(instance) =>\n bindCellAccessibility(instance, `${columnLabel}${rowLabel}`, selected, activate)\n }\n bounds={bounds}\n pathOptions={{\n fillColor: selected ? \"red\" : \"transparent\",\n fillOpacity: selected ? 0.5 : 0,\n weight: 1.5,\n }}\n eventHandlers={{\n click: activate,\n }}\n />\n );\n }),\n );\n\n const columnHeaders = columnLabels.map((columnLabel, colCount) => (\n <Marker\n key={`col-${columnLabel}`}\n position={[height, (colLeft(colCount) + colRight(colCount)) / 2]}\n icon={headerIcon(columnLabel, \"grid-header-label--column\")}\n interactive={false}\n />\n ));\n\n const rowHeaders = Array.from({ length: rows }).map((_, rowCount) => {\n const rowLabel = rowOffset + (rows - rowCount);\n return (\n <Marker\n key={`row-${rowLabel}`}\n position={[(rowBottom(rowCount) + rowTop(rowCount)) / 2, -7]}\n icon={headerIcon(String(rowLabel), \"grid-header-label--row\")}\n interactive={false}\n />\n );\n });\n\n const columnHandles = weightsEditable\n ? columnLabels.slice(0, -1).map((_, index) => (\n <WeightHandle\n key={`col-handle-${index}`}\n axis=\"column\"\n boundary={colRight(index)}\n length={height}\n onDrag={(deltaUnits) =>\n setDragState({ axis: \"column\", index, deltaWeight: deltaUnits / pxPerColWeightUnit })\n }\n onDragEnd={() => {\n onColumnWeightsChange?.(displayColumnWeights);\n setDragState(null);\n }}\n />\n ))\n : [];\n\n const rowHandles = weightsEditable\n ? Array.from({ length: rows - 1 }).map((_, index) => (\n <WeightHandle\n key={`row-handle-${index}`}\n axis=\"row\"\n boundary={rowTop(index)}\n length={width}\n onDrag={(deltaUnits) =>\n setDragState({ axis: \"row\", index, deltaWeight: deltaUnits / pxPerRowWeightUnit })\n }\n onDragEnd={() => {\n onRowWeightsChange?.(displayRowWeights);\n setDragState(null);\n }}\n />\n ))\n : [];\n\n return [...cells, columnHeaders, rowHeaders, columnHandles, rowHandles];\n};\n","export const resolveWeights = (\n count: number,\n overrides: Record<number, number> = {},\n): number[] => Array.from({ length: count }, (_, i) => overrides[i] ?? 1);\n\n/** Cumulative leading edge (in weight units) of each index, plus the total at the end. */\nexport const weightOffsets = (weights: number[]): number[] => {\n const offsets = [0];\n weights.forEach((weight) => offsets.push(offsets[offsets.length - 1] + weight));\n return offsets;\n};\n\nconst MIN_WEIGHT = 0.2;\n\n/** Shifts weight from index+1 into index (or back, for a negative delta), clamped so neither drops below MIN_WEIGHT. Total weight is preserved. */\nexport const applyWeightDelta = (weights: number[], index: number, delta: number): number[] => {\n const a = weights[index];\n const b = weights[index + 1];\n const clampedDelta = Math.max(-(a - MIN_WEIGHT), Math.min(b - MIN_WEIGHT, delta));\n\n const next = [...weights];\n next[index] = a + clampedDelta;\n next[index + 1] = b - clampedDelta;\n return next;\n};\n","import { divIcon } from \"leaflet\";\n\nexport const headerIcon = (label: string, className: string) =>\n divIcon({\n html: `<span class=\"grid-header-label ${className}\">${label}</span>`,\n className: \"grid-header-label-icon\",\n iconSize: [0, 0],\n });\n","import type { Rectangle as LeafletRectangle } from \"leaflet\";\n\n// Path.getElement() returns the underlying SVG node but isn't declared in @types/leaflet, hence the cast.\nconst getPathElement = (instance: LeafletRectangle) =>\n (instance as unknown as { getElement(): SVGElement | undefined }).getElement();\n\nexport const bindCellAccessibility = (\n instance: LeafletRectangle | null,\n label: string,\n selected: boolean,\n onActivate: () => void,\n) => {\n const el = instance && getPathElement(instance);\n if (!el) {\n return;\n }\n\n el.setAttribute(\"role\", \"button\");\n el.setAttribute(\"tabindex\", \"0\");\n el.setAttribute(\"aria-label\", label);\n el.setAttribute(\"aria-pressed\", String(selected));\n el.onkeydown = (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onActivate();\n }\n };\n};\n","import { useRef } from \"react\";\nimport { Polyline, useMap } from \"react-leaflet\";\nimport type { LatLngExpression, LeafletMouseEvent } from \"leaflet\";\n\ntype WeightHandleProps = {\n axis: \"column\" | \"row\";\n /** Position along the cross-axis: lng for a column boundary, lat for a row boundary. */\n boundary: number;\n /** Full span of the line: image height for column boundaries, image width for row boundaries. */\n length: number;\n onDrag: (deltaUnits: number) => void;\n onDragEnd: () => void;\n};\n\nexport const WeightHandle = ({ axis, boundary, length, onDrag, onDragEnd }: WeightHandleProps) => {\n const map = useMap();\n const dragStartRef = useRef<number | null>(null);\n\n const positions: LatLngExpression[] =\n axis === \"column\" ? [[0, boundary], [length, boundary]] : [[boundary, 0], [boundary, length]];\n\n const handleMouseDown = (event: LeafletMouseEvent) => {\n event.originalEvent.preventDefault();\n map.dragging.disable();\n dragStartRef.current = axis === \"column\" ? event.latlng.lng : event.latlng.lat;\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n if (dragStartRef.current === null) {\n return;\n }\n const latlng = map.mouseEventToLatLng(moveEvent);\n const current = axis === \"column\" ? latlng.lng : latlng.lat;\n onDrag(current - dragStartRef.current);\n };\n\n const handleMouseUp = () => {\n dragStartRef.current = null;\n map.dragging.enable();\n onDragEnd();\n window.removeEventListener(\"mousemove\", handleMouseMove);\n window.removeEventListener(\"mouseup\", handleMouseUp);\n };\n\n window.addEventListener(\"mousemove\", handleMouseMove);\n window.addEventListener(\"mouseup\", handleMouseUp);\n };\n\n return (\n <Polyline\n positions={positions}\n pathOptions={{\n color: \"#3388ff\",\n weight: 6,\n opacity: 0.15,\n className: `grid-weight-handle grid-weight-handle--${axis}`,\n }}\n eventHandlers={{ mousedown: handleMouseDown }}\n />\n );\n};\n","import { useEffect, useRef } from \"react\";\nimport { useMap } from \"react-leaflet\";\n\ntype ZoomControllerProps = {\n zoomMultiplier: number;\n};\n\nexport const ZoomController = ({ zoomMultiplier }: ZoomControllerProps) => {\n const map = useMap();\n const baseZoomRef = useRef<number | null>(null);\n\n useEffect(() => {\n // Capture the zoom react-leaflet's fitBounds already settled on, before any multiplier is applied.\n if (baseZoomRef.current === null) {\n baseZoomRef.current = map.getZoom();\n }\n\n map.setZoom(baseZoomRef.current + Math.log2(zoomMultiplier));\n }, [map, zoomMultiplier]);\n\n return null;\n};\n","import type { LatLngTuple } from \"leaflet\";\r\n\r\nexport const imageBounds = (src: string, containerWidth: number, containerHeight: number): Promise<LatLngTuple> =>\r\n new Promise((resolve, reject) => {\r\n const img = new Image();\r\n img.src = src;\r\n img.onload = () => {\r\n const scale = Math.min(containerWidth / img.width, containerHeight / img.height);\r\n resolve([img.height * scale, img.width * scale]);\r\n };\r\n img.onerror = () => reject(new Error(`Failed to load image: ${src}`));\r\n });","const toColumnLetter = (n: number): string => {\n let result = \"\";\n let remaining = n;\n\n while (remaining > 0) {\n const remainder = (remaining - 1) % 26;\n result = String.fromCharCode(65 + remainder) + result;\n remaining = Math.floor((remaining - 1) / 26);\n }\n\n return result;\n};\n\nexport const colNames = (startIndex: number, count: number): string[] =>\n Array.from({ length: count }, (_, i) => toColumnLetter(startIndex + i + 1));\n"],"mappings":"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","names":["useState","useEffect","useState","jsx","jsx","useRef","useMap","jsx","useState","useEffect","jsx","jsxs","useState"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapped-image",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "A React component that overlays a clickable, weighted row/column grid on top of one or more images, built on react-leaflet.",