@zerohive/hive-viewer 2.0.3 → 2.0.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.
package/dist/index.cjs CHANGED
@@ -159,7 +159,8 @@ function normalizeSignaturePlacement(placement) {
159
159
  }
160
160
 
161
161
  // src/utils/signatureImage.ts
162
- var processedSignatureCache = /* @__PURE__ */ new Map();
162
+ var signatureAlphaSourceCache = /* @__PURE__ */ new Map();
163
+ var renderedSignatureCache = /* @__PURE__ */ new Map();
163
164
  function ensureSignatureSource(source) {
164
165
  const trimmedSource = source.trim();
165
166
  if (trimmedSource.startsWith("data:") || trimmedSource.startsWith("blob:") || trimmedSource.startsWith("http:") || trimmedSource.startsWith("https:")) {
@@ -207,6 +208,12 @@ function colorDistance(leftRed, leftGreen, leftBlue, rightRed, rightGreen, right
207
208
  deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue
208
209
  );
209
210
  }
211
+ function computeLuminance(red, green, blue) {
212
+ return red * 0.2126 + green * 0.7152 + blue * 0.0722;
213
+ }
214
+ function clamp01(value) {
215
+ return Math.min(1, Math.max(0, value));
216
+ }
210
217
  function hasMeaningfulTransparency(pixels) {
211
218
  let translucentPixels = 0;
212
219
  const totalPixels = pixels.length / 4;
@@ -218,7 +225,10 @@ function hasMeaningfulTransparency(pixels) {
218
225
  return translucentPixels > Math.max(12, totalPixels * 4e-3);
219
226
  }
220
227
  function detectUniformBackground(pixels, width, height) {
221
- const sampleRadius = Math.max(2, Math.min(12, Math.floor(Math.min(width, height) / 12)));
228
+ const sampleRadius = Math.max(
229
+ 2,
230
+ Math.min(12, Math.floor(Math.min(width, height) / 12))
231
+ );
222
232
  const sampleOrigins = [
223
233
  [0, 0],
224
234
  [Math.max(0, width - sampleRadius), 0],
@@ -282,56 +292,98 @@ function detectUniformBackground(pixels, width, height) {
282
292
  }, 0);
283
293
  return maxDistance <= 26 ? background : null;
284
294
  }
285
- function createTintedSignatureFromAlpha(pixels, width, height, inkColor) {
286
- const canvas = createCanvas(width, height);
287
- const context = canvas.getContext("2d");
288
- if (!context) {
289
- return null;
295
+ function measureAlphaBounds(alphaMask, width, height, alphaThreshold = 8) {
296
+ let minX = width;
297
+ let minY = height;
298
+ let maxX = -1;
299
+ let maxY = -1;
300
+ for (let y = 0; y < height; y += 1) {
301
+ for (let x = 0; x < width; x += 1) {
302
+ const alpha = alphaMask[y * width + x];
303
+ if (alpha <= alphaThreshold) {
304
+ continue;
305
+ }
306
+ minX = Math.min(minX, x);
307
+ minY = Math.min(minY, y);
308
+ maxX = Math.max(maxX, x);
309
+ maxY = Math.max(maxY, y);
310
+ }
290
311
  }
291
- const output = context.createImageData(width, height);
292
- for (let offset = 0; offset < pixels.length; offset += 4) {
293
- output.data[offset] = inkColor[0];
294
- output.data[offset + 1] = inkColor[1];
295
- output.data[offset + 2] = inkColor[2];
296
- output.data[offset + 3] = pixels[offset + 3];
312
+ if (maxX < minX || maxY < minY) {
313
+ return { sx: 0, sy: 0, sw: width, sh: height };
297
314
  }
298
- context.putImageData(output, 0, 0);
299
- return canvas;
315
+ const padding = Math.max(6, Math.round(Math.max(width, height) * 0.04));
316
+ const sx = Math.max(0, minX - padding);
317
+ const sy = Math.max(0, minY - padding);
318
+ const sw = Math.min(width - sx, maxX - minX + 1 + padding * 2);
319
+ const sh = Math.min(height - sy, maxY - minY + 1 + padding * 2);
320
+ return { sx, sy, sw, sh };
300
321
  }
301
- function createTintedSignatureFromSolidBackground(pixels, width, height, background, inkColor) {
302
- const canvas = createCanvas(width, height);
303
- const context = canvas.getContext("2d");
304
- if (!context) {
305
- return null;
322
+ function createAlphaMaskFromTransparency(pixels) {
323
+ const alphaMask = new Uint8ClampedArray(pixels.length / 4);
324
+ for (let pixelIndex = 0; pixelIndex < alphaMask.length; pixelIndex += 1) {
325
+ alphaMask[pixelIndex] = pixels[pixelIndex * 4 + 3];
306
326
  }
307
- const output = context.createImageData(width, height);
327
+ return alphaMask;
328
+ }
329
+ function createAlphaMaskFromSolidBackground(pixels, width, height, background) {
330
+ const alphaMask = new Uint8ClampedArray(width * height);
308
331
  let foregroundPixels = 0;
309
- const totalPixels = width * height;
310
- for (let offset = 0; offset < pixels.length; offset += 4) {
332
+ let maxAlpha = 0;
333
+ const backgroundLuminance = computeLuminance(
334
+ background[0],
335
+ background[1],
336
+ background[2]
337
+ );
338
+ for (let pixelIndex = 0; pixelIndex < alphaMask.length; pixelIndex += 1) {
339
+ const offset = pixelIndex * 4;
340
+ const red = pixels[offset];
341
+ const green = pixels[offset + 1];
342
+ const blue = pixels[offset + 2];
311
343
  const alpha = pixels[offset + 3] / 255;
312
344
  const distance = colorDistance(
313
- pixels[offset],
314
- pixels[offset + 1],
315
- pixels[offset + 2],
345
+ red,
346
+ green,
347
+ blue,
316
348
  background[0],
317
349
  background[1],
318
350
  background[2]
319
351
  );
320
- const normalizedStrength = Math.max(0, Math.min(1, (distance - 14) / 120));
321
- const outputAlpha = Math.round(normalizedStrength * alpha * 255);
322
- output.data[offset] = inkColor[0];
323
- output.data[offset + 1] = inkColor[1];
324
- output.data[offset + 2] = inkColor[2];
325
- output.data[offset + 3] = outputAlpha;
352
+ const channelDelta = Math.max(
353
+ Math.abs(red - background[0]),
354
+ Math.abs(green - background[1]),
355
+ Math.abs(blue - background[2])
356
+ );
357
+ const luminance = computeLuminance(red, green, blue);
358
+ const luminanceDelta = Math.abs(luminance - backgroundLuminance);
359
+ const strength = clamp01(
360
+ Math.max(distance / 120, channelDelta / 90, luminanceDelta / 72)
361
+ );
362
+ const sharpened = strength <= 0.06 ? 0 : Math.pow(strength, 0.68);
363
+ const outputAlpha = Math.round(sharpened * alpha * 255);
364
+ alphaMask[pixelIndex] = outputAlpha;
326
365
  if (outputAlpha > 10) {
327
366
  foregroundPixels += 1;
367
+ maxAlpha = Math.max(maxAlpha, outputAlpha);
328
368
  }
329
369
  }
330
- if (foregroundPixels < Math.max(24, totalPixels * 15e-4) || foregroundPixels > totalPixels * 0.42) {
370
+ const totalPixels = width * height;
371
+ if (foregroundPixels < Math.max(24, totalPixels * 15e-4) || foregroundPixels > totalPixels * 0.42 || maxAlpha === 0) {
331
372
  return null;
332
373
  }
333
- context.putImageData(output, 0, 0);
334
- return canvas;
374
+ const normalizeScale = 255 / maxAlpha;
375
+ for (let pixelIndex = 0; pixelIndex < alphaMask.length; pixelIndex += 1) {
376
+ const alpha = alphaMask[pixelIndex];
377
+ if (alpha === 0) {
378
+ continue;
379
+ }
380
+ const normalized = Math.min(
381
+ 255,
382
+ Math.round(Math.pow(alpha * normalizeScale / 255, 0.92) * 255)
383
+ );
384
+ alphaMask[pixelIndex] = normalized <= 10 ? 0 : normalized;
385
+ }
386
+ return alphaMask;
335
387
  }
336
388
  function parseInkColor(inkColor) {
337
389
  const hex = SIGNATURE_INK_COLOR_VALUES[inkColor];
@@ -341,62 +393,148 @@ function parseInkColor(inkColor) {
341
393
  Number.parseInt(hex.slice(5, 7), 16)
342
394
  ];
343
395
  }
344
- async function getRenderableSignatureImage(source, inkColor) {
396
+ function createTintedSignatureCanvas(alphaSource, inkColor) {
397
+ if (!alphaSource.alphaMask) {
398
+ return null;
399
+ }
400
+ const { bounds } = alphaSource;
401
+ const canvas = createCanvas(bounds.sw, bounds.sh);
402
+ const context = canvas.getContext("2d");
403
+ if (!context) {
404
+ return null;
405
+ }
406
+ const output = context.createImageData(bounds.sw, bounds.sh);
407
+ const ink = parseInkColor(inkColor);
408
+ for (let y = 0; y < bounds.sh; y += 1) {
409
+ for (let x = 0; x < bounds.sw; x += 1) {
410
+ const sourceIndex = (bounds.sy + y) * alphaSource.width + bounds.sx + x;
411
+ const targetIndex = (y * bounds.sw + x) * 4;
412
+ output.data[targetIndex] = ink[0];
413
+ output.data[targetIndex + 1] = ink[1];
414
+ output.data[targetIndex + 2] = ink[2];
415
+ output.data[targetIndex + 3] = alphaSource.alphaMask[sourceIndex];
416
+ }
417
+ }
418
+ context.putImageData(output, 0, 0);
419
+ return canvas;
420
+ }
421
+ async function getSignatureAlphaSource(source) {
345
422
  const resolvedSource = ensureSignatureSource(source);
346
- const normalizedInkColor = normalizeSignatureInkColor(inkColor);
347
- const cacheKey = `${normalizedInkColor}::${resolvedSource}`;
348
- const cached = processedSignatureCache.get(cacheKey);
423
+ const cached = signatureAlphaSourceCache.get(resolvedSource);
349
424
  if (cached) {
350
425
  return cached;
351
426
  }
352
427
  const pending = (async () => {
353
428
  const image = await loadSignatureImage(resolvedSource);
429
+ const fallbackBounds = {
430
+ sx: 0,
431
+ sy: 0,
432
+ sw: image.width,
433
+ sh: image.height
434
+ };
354
435
  const canvas = createCanvas(image.width, image.height);
355
436
  const context = canvas.getContext("2d");
356
437
  if (!context) {
357
- return resolvedSource;
438
+ return {
439
+ source: resolvedSource,
440
+ width: image.width,
441
+ height: image.height,
442
+ alphaMask: null,
443
+ bounds: fallbackBounds
444
+ };
358
445
  }
359
446
  context.drawImage(image, 0, 0, image.width, image.height);
360
447
  let imageData;
361
448
  try {
362
449
  imageData = context.getImageData(0, 0, canvas.width, canvas.height);
363
450
  } catch {
364
- return resolvedSource;
451
+ return {
452
+ source: resolvedSource,
453
+ width: image.width,
454
+ height: image.height,
455
+ alphaMask: null,
456
+ bounds: fallbackBounds
457
+ };
365
458
  }
366
- const inkRgb = parseInkColor(normalizedInkColor);
367
- const transparentRender = hasMeaningfulTransparency(imageData.data) ? createTintedSignatureFromAlpha(
368
- imageData.data,
369
- canvas.width,
370
- canvas.height,
371
- inkRgb
372
- ) : null;
373
- if (transparentRender) {
374
- return transparentRender.toDataURL("image/png");
459
+ const alphaMask = hasMeaningfulTransparency(imageData.data) ? createAlphaMaskFromTransparency(imageData.data) : (() => {
460
+ const background = detectUniformBackground(
461
+ imageData.data,
462
+ canvas.width,
463
+ canvas.height
464
+ );
465
+ return background ? createAlphaMaskFromSolidBackground(
466
+ imageData.data,
467
+ canvas.width,
468
+ canvas.height,
469
+ background
470
+ ) : null;
471
+ })();
472
+ if (!alphaMask) {
473
+ return {
474
+ source: resolvedSource,
475
+ width: image.width,
476
+ height: image.height,
477
+ alphaMask: null,
478
+ bounds: fallbackBounds
479
+ };
375
480
  }
376
- const background = detectUniformBackground(
377
- imageData.data,
378
- canvas.width,
379
- canvas.height
380
- );
381
- if (!background) {
382
- return resolvedSource;
481
+ return {
482
+ source: resolvedSource,
483
+ width: image.width,
484
+ height: image.height,
485
+ alphaMask,
486
+ bounds: measureAlphaBounds(alphaMask, image.width, image.height)
487
+ };
488
+ })().catch(async () => {
489
+ const image = await loadSignatureImage(resolvedSource);
490
+ return {
491
+ source: resolvedSource,
492
+ width: image.width,
493
+ height: image.height,
494
+ alphaMask: null,
495
+ bounds: { sx: 0, sy: 0, sw: image.width, sh: image.height }
496
+ };
497
+ });
498
+ signatureAlphaSourceCache.set(resolvedSource, pending);
499
+ return pending;
500
+ }
501
+ async function getSignatureRenderMetrics(source) {
502
+ const alphaSource = await getSignatureAlphaSource(source);
503
+ const width = alphaSource.alphaMask ? alphaSource.bounds.sw : alphaSource.width;
504
+ const height = alphaSource.alphaMask ? alphaSource.bounds.sh : alphaSource.height;
505
+ return {
506
+ width,
507
+ height,
508
+ aspectRatio: width > 0 && height > 0 ? width / height : 3.1,
509
+ canTint: Boolean(alphaSource.alphaMask)
510
+ };
511
+ }
512
+ async function getRenderableSignatureImage(source, inkColor) {
513
+ const resolvedSource = ensureSignatureSource(source);
514
+ const normalizedInkColor = normalizeSignatureInkColor(inkColor);
515
+ const cacheKey = `${normalizedInkColor}::${resolvedSource}`;
516
+ const cached = renderedSignatureCache.get(cacheKey);
517
+ if (cached) {
518
+ return cached;
519
+ }
520
+ const pending = (async () => {
521
+ const alphaSource = await getSignatureAlphaSource(resolvedSource);
522
+ if (!alphaSource.alphaMask) {
523
+ return alphaSource.source;
383
524
  }
384
- const extractedRender = createTintedSignatureFromSolidBackground(
385
- imageData.data,
386
- canvas.width,
387
- canvas.height,
388
- background,
389
- inkRgb
525
+ const tintedCanvas = createTintedSignatureCanvas(
526
+ alphaSource,
527
+ normalizedInkColor
390
528
  );
391
- return extractedRender ? extractedRender.toDataURL("image/png") : resolvedSource;
529
+ return tintedCanvas ? tintedCanvas.toDataURL("image/png") : alphaSource.source;
392
530
  })().catch(() => resolvedSource);
393
- processedSignatureCache.set(cacheKey, pending);
531
+ renderedSignatureCache.set(cacheKey, pending);
394
532
  return pending;
395
533
  }
396
534
 
397
535
  // src/components/RenderableSignatureImage.tsx
398
536
  var import_jsx_runtime = require("react/jsx-runtime");
399
- function RenderableSignatureImage(props) {
537
+ var RenderableSignatureImage = (0, import_react.memo)(function RenderableSignatureImage2(props) {
400
538
  const { signatureImageUrl, inkColor, ...imageProps } = props;
401
539
  const [resolvedSrc, setResolvedSrc] = (0, import_react.useState)(signatureImageUrl);
402
540
  (0, import_react.useEffect)(() => {
@@ -415,25 +553,54 @@ function RenderableSignatureImage(props) {
415
553
  };
416
554
  }, [inkColor, signatureImageUrl]);
417
555
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { ...imageProps, src: resolvedSrc, decoding: "async", draggable: false });
418
- }
556
+ });
557
+ RenderableSignatureImage.displayName = "RenderableSignatureImage";
419
558
 
420
559
  // src/components/SignatureOverlay.tsx
421
560
  var import_jsx_runtime2 = require("react/jsx-runtime");
561
+ var DEFAULT_SIGNATURE_ASPECT_RATIO = 3.1;
422
562
  function clamp(value, min, max) {
423
563
  return Math.min(max, Math.max(min, value));
424
564
  }
425
- function getDefaultPlacementSize(kind) {
565
+ function clampAspectRatio(value) {
566
+ return clamp(value, 1.8, 6.2);
567
+ }
568
+ function getDefaultSurfaceAspectRatio(kind) {
426
569
  switch (kind) {
427
- case "sheet":
428
- return { width: 0.3, height: 0.16 };
429
570
  case "slide":
430
- return { width: 0.34, height: 0.18 };
571
+ return 16 / 9;
572
+ case "sheet":
573
+ return 1.9;
431
574
  case "image":
432
- return { width: 0.36, height: 0.18 };
575
+ return 1;
433
576
  default:
434
- return { width: 0.34, height: 0.16 };
577
+ return 816 / 1056;
435
578
  }
436
579
  }
580
+ function getSignatureCardAspectRatio(aspectRatio = DEFAULT_SIGNATURE_ASPECT_RATIO) {
581
+ return clamp(aspectRatio * 0.78, 1.35, 4.8);
582
+ }
583
+ function getDefaultPlacementSize(kind, aspectRatio = DEFAULT_SIGNATURE_ASPECT_RATIO, surfaceAspectRatio = getDefaultSurfaceAspectRatio(kind)) {
584
+ const effectiveAspectRatio = getSignatureCardAspectRatio(
585
+ clampAspectRatio(aspectRatio)
586
+ );
587
+ const effectiveSurfaceAspectRatio = clamp(surfaceAspectRatio, 0.45, 2.8);
588
+ const heightTarget = kind === "sheet" ? 0.11 : kind === "slide" ? 0.135 : kind === "image" ? 0.14 : 0.115;
589
+ const widthMin = kind === "sheet" ? 0.2 : kind === "slide" ? 0.22 : kind === "image" ? 0.24 : 0.22;
590
+ const widthMax = kind === "sheet" ? 0.38 : kind === "slide" ? 0.44 : kind === "image" ? 0.48 : 0.4;
591
+ const heightMax = kind === "sheet" ? 0.18 : kind === "slide" ? 0.2 : kind === "image" ? 0.22 : 0.19;
592
+ const width = clamp(
593
+ heightTarget * effectiveAspectRatio / effectiveSurfaceAspectRatio,
594
+ widthMin,
595
+ widthMax
596
+ );
597
+ const height = clamp(
598
+ width * effectiveSurfaceAspectRatio / effectiveAspectRatio,
599
+ 0.075,
600
+ heightMax
601
+ );
602
+ return { width, height };
603
+ }
437
604
  function getDefaultAnnotationSize(kind) {
438
605
  switch (kind) {
439
606
  case "sheet":
@@ -461,11 +628,13 @@ function SignatureOverlay(props) {
461
628
  placements,
462
629
  annotations,
463
630
  pendingSignature,
631
+ pendingSignatureColor,
464
632
  pendingAnnotation,
465
633
  activePlacementId,
466
634
  activeAnnotationId,
467
635
  placeHint,
468
636
  annotationHint,
637
+ cancelPlacementLabel,
469
638
  annotationPlaceholder,
470
639
  signatureAltLabel,
471
640
  signatureAltByLabel,
@@ -485,10 +654,21 @@ function SignatureOverlay(props) {
485
654
  onRemovePlacement,
486
655
  onRemoveAnnotation,
487
656
  onSelectPlacement,
488
- onSelectAnnotation
657
+ onSelectAnnotation,
658
+ onCancelPlacementMode
489
659
  } = props;
490
660
  const layerRef = (0, import_react2.useRef)(null);
491
661
  const [dragState, setDragState] = (0, import_react2.useState)(null);
662
+ const [dragPreview, setDragPreview] = (0, import_react2.useState)(null);
663
+ const [placementPreview, setPlacementPreview] = (0, import_react2.useState)(
664
+ null
665
+ );
666
+ const [pendingSignatureAspectRatio, setPendingSignatureAspectRatio] = (0, import_react2.useState)(
667
+ DEFAULT_SIGNATURE_ASPECT_RATIO
668
+ );
669
+ const [surfaceAspectRatio, setSurfaceAspectRatio] = (0, import_react2.useState)(
670
+ () => getDefaultSurfaceAspectRatio(surfaceKind)
671
+ );
492
672
  const visiblePlacements = (0, import_react2.useMemo)(
493
673
  () => placements.filter((placement) => placement.surfaceKey === surfaceKey),
494
674
  [placements, surfaceKey]
@@ -502,6 +682,105 @@ function SignatureOverlay(props) {
502
682
  [visibleAnnotations]
503
683
  );
504
684
  const captureMode = pendingSignature ? "signature" : pendingAnnotation ? "annotation" : null;
685
+ const defaultSignatureSize = (0, import_react2.useMemo)(
686
+ () => getDefaultPlacementSize(
687
+ surfaceKind,
688
+ pendingSignatureAspectRatio,
689
+ surfaceAspectRatio
690
+ ),
691
+ [pendingSignatureAspectRatio, surfaceAspectRatio, surfaceKind]
692
+ );
693
+ const defaultAnnotationSize = (0, import_react2.useMemo)(
694
+ () => getDefaultAnnotationSize(surfaceKind),
695
+ [surfaceKind]
696
+ );
697
+ const previewGeometry = (0, import_react2.useMemo)(() => {
698
+ if (!placementPreview || !captureMode) {
699
+ return null;
700
+ }
701
+ const size = captureMode === "signature" ? defaultSignatureSize : defaultAnnotationSize;
702
+ return {
703
+ width: size.width,
704
+ height: size.height,
705
+ x: clamp(placementPreview.x - size.width / 2, 0, 1 - size.width),
706
+ y: clamp(placementPreview.y - size.height / 2, 0, 1 - size.height)
707
+ };
708
+ }, [
709
+ captureMode,
710
+ defaultAnnotationSize,
711
+ defaultSignatureSize,
712
+ placementPreview
713
+ ]);
714
+ (0, import_react2.useEffect)(() => {
715
+ const element = layerRef.current;
716
+ if (!element) {
717
+ return;
718
+ }
719
+ const syncSurfaceAspectRatio = () => {
720
+ const rect = element.getBoundingClientRect();
721
+ if (rect.width <= 0 || rect.height <= 0) {
722
+ return;
723
+ }
724
+ const nextAspectRatio = rect.width / rect.height;
725
+ setSurfaceAspectRatio(
726
+ (current) => Math.abs(current - nextAspectRatio) < 1e-3 ? current : nextAspectRatio
727
+ );
728
+ };
729
+ syncSurfaceAspectRatio();
730
+ if (typeof ResizeObserver === "undefined") {
731
+ return;
732
+ }
733
+ const observer = new ResizeObserver(() => {
734
+ syncSurfaceAspectRatio();
735
+ });
736
+ observer.observe(element);
737
+ return () => {
738
+ observer.disconnect();
739
+ };
740
+ }, [surfaceKey, surfaceKind]);
741
+ (0, import_react2.useEffect)(() => {
742
+ if (!pendingSignature?.signatureImageUrl) {
743
+ setPendingSignatureAspectRatio(DEFAULT_SIGNATURE_ASPECT_RATIO);
744
+ return;
745
+ }
746
+ let cancelled = false;
747
+ void getSignatureRenderMetrics(pendingSignature.signatureImageUrl).then((metrics) => {
748
+ if (!cancelled) {
749
+ setPendingSignatureAspectRatio(
750
+ clampAspectRatio(metrics.aspectRatio || DEFAULT_SIGNATURE_ASPECT_RATIO)
751
+ );
752
+ }
753
+ }).catch(() => {
754
+ if (!cancelled) {
755
+ setPendingSignatureAspectRatio(DEFAULT_SIGNATURE_ASPECT_RATIO);
756
+ }
757
+ });
758
+ return () => {
759
+ cancelled = true;
760
+ };
761
+ }, [pendingSignature?.signatureImageUrl]);
762
+ (0, import_react2.useEffect)(() => {
763
+ if (!captureMode) {
764
+ setPlacementPreview(null);
765
+ return;
766
+ }
767
+ const handleKeyDown = (event) => {
768
+ if (event.key !== "Escape") {
769
+ return;
770
+ }
771
+ event.preventDefault();
772
+ onCancelPlacementMode();
773
+ };
774
+ window.addEventListener("keydown", handleKeyDown);
775
+ return () => {
776
+ window.removeEventListener("keydown", handleKeyDown);
777
+ };
778
+ }, [captureMode, onCancelPlacementMode]);
779
+ (0, import_react2.useEffect)(() => {
780
+ if (!dragState) {
781
+ setDragPreview(null);
782
+ }
783
+ }, [dragState]);
505
784
  (0, import_react2.useEffect)(() => {
506
785
  if (captureMode || dragState) {
507
786
  return;
@@ -540,11 +819,12 @@ function SignatureOverlay(props) {
540
819
  0,
541
820
  1 - dragState.originHeight
542
821
  );
543
- if (dragState.targetType === "signature") {
544
- onUpdatePlacement(dragState.id, { x: nextX, y: nextY });
545
- return;
546
- }
547
- onUpdateAnnotation(dragState.id, { x: nextX, y: nextY });
822
+ setDragPreview({
823
+ x: nextX,
824
+ y: nextY,
825
+ width: dragState.originWidth,
826
+ height: dragState.originHeight
827
+ });
548
828
  return;
549
829
  }
550
830
  if (dragState.targetType === "signature") {
@@ -558,7 +838,9 @@ function SignatureOverlay(props) {
558
838
  0.035,
559
839
  1 - dragState.originY
560
840
  );
561
- onUpdatePlacement(dragState.id, {
841
+ setDragPreview({
842
+ x: dragState.originX,
843
+ y: dragState.originY,
562
844
  width: nextWidth2,
563
845
  height: nextHeight2
564
846
  });
@@ -574,12 +856,22 @@ function SignatureOverlay(props) {
574
856
  0.08,
575
857
  1 - dragState.originY
576
858
  );
577
- onUpdateAnnotation(dragState.id, {
859
+ setDragPreview({
860
+ x: dragState.originX,
861
+ y: dragState.originY,
578
862
  width: nextWidth,
579
863
  height: nextHeight
580
864
  });
581
865
  };
582
866
  const handlePointerUp = () => {
867
+ if (dragPreview) {
868
+ if (dragState.targetType === "signature") {
869
+ onUpdatePlacement(dragState.id, dragPreview);
870
+ } else {
871
+ onUpdateAnnotation(dragState.id, dragPreview);
872
+ }
873
+ }
874
+ setDragPreview(null);
583
875
  setDragState(null);
584
876
  };
585
877
  window.addEventListener("pointermove", handlePointerMove);
@@ -588,7 +880,7 @@ function SignatureOverlay(props) {
588
880
  window.removeEventListener("pointermove", handlePointerMove);
589
881
  window.removeEventListener("pointerup", handlePointerUp);
590
882
  };
591
- }, [dragState, onUpdateAnnotation, onUpdatePlacement]);
883
+ }, [dragPreview, dragState, onUpdateAnnotation, onUpdatePlacement]);
592
884
  const handlePlace = (event) => {
593
885
  if (!layerRef.current || !captureMode) {
594
886
  return;
@@ -597,7 +889,7 @@ function SignatureOverlay(props) {
597
889
  const clickX = (event.clientX - rect.left) / rect.width;
598
890
  const clickY = (event.clientY - rect.top) / rect.height;
599
891
  if (captureMode === "signature" && pendingSignature) {
600
- const { width, height } = getDefaultPlacementSize(surfaceKind);
892
+ const { width, height } = defaultSignatureSize;
601
893
  onPlaceSignature({
602
894
  signature: pendingSignature,
603
895
  surfaceKey,
@@ -613,7 +905,7 @@ function SignatureOverlay(props) {
613
905
  return;
614
906
  }
615
907
  if (captureMode === "annotation") {
616
- const { width, height } = getDefaultAnnotationSize(surfaceKind);
908
+ const { width, height } = defaultAnnotationSize;
617
909
  onPlaceAnnotation({
618
910
  surfaceKey,
619
911
  surfaceKind,
@@ -628,6 +920,16 @@ function SignatureOverlay(props) {
628
920
  });
629
921
  }
630
922
  };
923
+ const updatePlacementPreview = (clientX, clientY) => {
924
+ if (!layerRef.current) {
925
+ return;
926
+ }
927
+ const rect = layerRef.current.getBoundingClientRect();
928
+ setPlacementPreview({
929
+ x: clamp((clientX - rect.left) / rect.width, 0, 1),
930
+ y: clamp((clientY - rect.top) / rect.height, 0, 1)
931
+ });
932
+ };
631
933
  const beginDrag = (event, target, mode) => {
632
934
  if (!layerRef.current) {
633
935
  return;
@@ -637,6 +939,7 @@ function SignatureOverlay(props) {
637
939
  if (target.kind === "signature") {
638
940
  onSelectPlacement(target.item.id);
639
941
  onSelectAnnotation(null);
942
+ setDragPreview(null);
640
943
  setDragState({
641
944
  id: target.item.id,
642
945
  targetType: "signature",
@@ -654,6 +957,7 @@ function SignatureOverlay(props) {
654
957
  }
655
958
  onSelectAnnotation(target.item.id);
656
959
  onSelectPlacement(null);
960
+ setDragPreview(null);
657
961
  setDragState({
658
962
  id: target.item.id,
659
963
  targetType: "annotation",
@@ -667,9 +971,110 @@ function SignatureOverlay(props) {
667
971
  rect: layerRef.current.getBoundingClientRect()
668
972
  });
669
973
  };
974
+ const getSignatureGeometry = (placement) => dragState?.targetType === "signature" && dragState.id === placement.id && dragPreview ? dragPreview : {
975
+ x: placement.x,
976
+ y: placement.y,
977
+ width: placement.width,
978
+ height: placement.height
979
+ };
980
+ const getAnnotationGeometry = (annotation) => dragState?.targetType === "annotation" && dragState.id === annotation.id && dragPreview ? dragPreview : {
981
+ x: annotation.x,
982
+ y: annotation.y,
983
+ width: annotation.width,
984
+ height: annotation.height
985
+ };
670
986
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: layerRef, className: "hv-signature-overlay", children: [
671
- captureMode && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-signature-overlay-capture", onClick: handlePlace, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-signature-overlay-hint", children: captureMode === "annotation" ? annotationHint : placeHint }) }),
987
+ captureMode && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
988
+ "div",
989
+ {
990
+ className: "hv-signature-overlay-capture",
991
+ onClick: handlePlace,
992
+ onPointerEnter: (event) => updatePlacementPreview(event.clientX, event.clientY),
993
+ onPointerMove: (event) => updatePlacementPreview(event.clientX, event.clientY),
994
+ onPointerLeave: () => setPlacementPreview(null),
995
+ children: [
996
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
997
+ "div",
998
+ {
999
+ className: "hv-signature-overlay-banner",
1000
+ onPointerDown: (event) => {
1001
+ event.stopPropagation();
1002
+ },
1003
+ onClick: (event) => {
1004
+ event.stopPropagation();
1005
+ },
1006
+ children: [
1007
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-signature-overlay-banner-copy", children: [
1008
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { className: "hv-signature-overlay-title", children: captureMode === "annotation" ? annotationTitle : signatureAltLabel }),
1009
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hv-signature-overlay-hint", children: captureMode === "annotation" ? annotationHint : placeHint })
1010
+ ] }),
1011
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1012
+ "button",
1013
+ {
1014
+ type: "button",
1015
+ className: "hv-signature-overlay-cancel",
1016
+ onPointerDown: (event) => {
1017
+ event.stopPropagation();
1018
+ },
1019
+ onClick: (event) => {
1020
+ event.stopPropagation();
1021
+ onCancelPlacementMode();
1022
+ },
1023
+ children: cancelPlacementLabel
1024
+ }
1025
+ )
1026
+ ]
1027
+ }
1028
+ ),
1029
+ captureMode === "signature" && previewGeometry && pendingSignature && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1030
+ "div",
1031
+ {
1032
+ className: "hv-signature-stamp hv-signature-ghost",
1033
+ style: {
1034
+ left: `${previewGeometry.x * 100}%`,
1035
+ top: `${previewGeometry.y * 100}%`,
1036
+ width: `${previewGeometry.width * 100}%`,
1037
+ height: `${previewGeometry.height * 100}%`
1038
+ },
1039
+ children: [
1040
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-signature-image-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1041
+ RenderableSignatureImage,
1042
+ {
1043
+ signatureImageUrl: pendingSignature.signatureImageUrl,
1044
+ inkColor: pendingSignatureColor,
1045
+ alt: signatureAltLabel,
1046
+ className: "hv-signature-image"
1047
+ }
1048
+ ) }),
1049
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-signature-meta", children: [
1050
+ pendingSignature.signedBy?.trim() && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hv-signature-meta-name", children: pendingSignature.signedBy.trim() }),
1051
+ pendingSignature.jobTitle?.trim() && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hv-signature-meta-jobtitle", children: pendingSignature.jobTitle.trim() }),
1052
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hv-signature-meta-date", children: normalizeSignatureDate(pendingSignature.dateSigned) })
1053
+ ] })
1054
+ ]
1055
+ }
1056
+ ),
1057
+ captureMode === "annotation" && previewGeometry && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1058
+ "div",
1059
+ {
1060
+ className: "hv-annotation-card hv-annotation-ghost",
1061
+ style: {
1062
+ left: `${previewGeometry.x * 100}%`,
1063
+ top: `${previewGeometry.y * 100}%`,
1064
+ width: `${previewGeometry.width * 100}%`,
1065
+ height: `${previewGeometry.height * 100}%`
1066
+ },
1067
+ children: [
1068
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-annotation-header", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-annotation-header-copy", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hv-annotation-title", children: annotationTitle }) }) }),
1069
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-annotation-body", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-annotation-preview empty", children: annotationPlaceholder }) })
1070
+ ]
1071
+ }
1072
+ )
1073
+ ]
1074
+ }
1075
+ ),
672
1076
  visiblePlacements.map((placement) => {
1077
+ const placementGeometry = getSignatureGeometry(placement);
673
1078
  const isActive = placement.id === activePlacementId;
674
1079
  const hasLinkedAnnotation = linkedAnnotationIds.has(placement.id);
675
1080
  const signer = placement.signature.signedBy?.trim();
@@ -681,20 +1086,12 @@ function SignatureOverlay(props) {
681
1086
  {
682
1087
  className: `hv-signature-stamp ${isActive ? "active" : ""}`,
683
1088
  style: {
684
- left: `${placement.x * 100}%`,
685
- top: `${placement.y * 100}%`,
686
- width: `${placement.width * 100}%`,
687
- height: `${placement.height * 100}%`
688
- },
689
- onPointerDown: (event) => {
690
- if (!isActive) {
691
- event.stopPropagation();
692
- onSelectPlacement(placement.id);
693
- onSelectAnnotation(null);
694
- return;
695
- }
696
- beginDrag(event, { kind: "signature", item: placement }, "move");
1089
+ left: `${placementGeometry.x * 100}%`,
1090
+ top: `${placementGeometry.y * 100}%`,
1091
+ width: `${placementGeometry.width * 100}%`,
1092
+ height: `${placementGeometry.height * 100}%`
697
1093
  },
1094
+ onPointerDown: (event) => beginDrag(event, { kind: "signature", item: placement }, "move"),
698
1095
  onClick: (event) => {
699
1096
  event.stopPropagation();
700
1097
  onSelectPlacement(placement.id);
@@ -776,6 +1173,7 @@ function SignatureOverlay(props) {
776
1173
  );
777
1174
  }),
778
1175
  visibleAnnotations.map((annotation) => {
1176
+ const annotationGeometry = getAnnotationGeometry(annotation);
779
1177
  const isActive = annotation.id === activeAnnotationId;
780
1178
  const isLinked = Boolean(annotation.linkedSignaturePlacementId);
781
1179
  const hasText = annotation.text.trim().length > 0;
@@ -786,8 +1184,8 @@ function SignatureOverlay(props) {
786
1184
  type: "button",
787
1185
  className: `hv-annotation-chip ${isLinked ? "linked" : ""} ${hasText ? "" : "empty"}`,
788
1186
  style: {
789
- left: `${annotation.x * 100}%`,
790
- top: `${annotation.y * 100}%`
1187
+ left: `${annotationGeometry.x * 100}%`,
1188
+ top: `${annotationGeometry.y * 100}%`
791
1189
  },
792
1190
  "aria-label": hasText ? annotation.text : openAnnotationLabel,
793
1191
  title: hasText ? annotation.text : openAnnotationLabel,
@@ -806,10 +1204,10 @@ function SignatureOverlay(props) {
806
1204
  {
807
1205
  className: `hv-annotation-card ${isActive ? "active" : ""} ${isLinked ? "linked" : ""}`,
808
1206
  style: {
809
- left: `${annotation.x * 100}%`,
810
- top: `${annotation.y * 100}%`,
811
- width: `${annotation.width * 100}%`,
812
- height: `${annotation.height * 100}%`
1207
+ left: `${annotationGeometry.x * 100}%`,
1208
+ top: `${annotationGeometry.y * 100}%`,
1209
+ width: `${annotationGeometry.width * 100}%`,
1210
+ height: `${annotationGeometry.height * 100}%`
813
1211
  },
814
1212
  onClick: (event) => {
815
1213
  event.stopPropagation();
@@ -5222,11 +5620,13 @@ function RichTextEditor(props) {
5222
5620
  placements: props.signatureOverlay.placements,
5223
5621
  annotations: props.signatureOverlay.annotations,
5224
5622
  pendingSignature: props.signatureOverlay.pendingSignature,
5623
+ pendingSignatureColor: props.signatureOverlay.pendingSignatureColor,
5225
5624
  pendingAnnotation: props.signatureOverlay.pendingAnnotation,
5226
5625
  activePlacementId: props.signatureOverlay.activePlacementId,
5227
5626
  activeAnnotationId: props.signatureOverlay.activeAnnotationId,
5228
5627
  placeHint: props.signatureOverlay.placeHint,
5229
5628
  annotationHint: props.signatureOverlay.annotationHint,
5629
+ cancelPlacementLabel: props.signatureOverlay.cancelPlacementLabel,
5230
5630
  annotationPlaceholder: props.signatureOverlay.annotationPlaceholder,
5231
5631
  signatureAltLabel: props.signatureOverlay.signatureAltLabel,
5232
5632
  signatureAltByLabel: props.signatureOverlay.signatureAltByLabel,
@@ -5246,7 +5646,8 @@ function RichTextEditor(props) {
5246
5646
  onRemovePlacement: props.signatureOverlay.onRemovePlacement,
5247
5647
  onRemoveAnnotation: props.signatureOverlay.onRemoveAnnotation,
5248
5648
  onSelectPlacement: props.signatureOverlay.onSelectPlacement,
5249
- onSelectAnnotation: props.signatureOverlay.onSelectAnnotation
5649
+ onSelectAnnotation: props.signatureOverlay.onSelectAnnotation,
5650
+ onCancelPlacementMode: props.signatureOverlay.onCancelPlacementMode
5250
5651
  }
5251
5652
  )
5252
5653
  ]
@@ -5285,11 +5686,13 @@ function RichTextEditor(props) {
5285
5686
  placements: props.signatureOverlay.placements,
5286
5687
  annotations: props.signatureOverlay.annotations,
5287
5688
  pendingSignature: props.signatureOverlay.pendingSignature,
5689
+ pendingSignatureColor: props.signatureOverlay.pendingSignatureColor,
5288
5690
  pendingAnnotation: props.signatureOverlay.pendingAnnotation,
5289
5691
  activePlacementId: props.signatureOverlay.activePlacementId,
5290
5692
  activeAnnotationId: props.signatureOverlay.activeAnnotationId,
5291
5693
  placeHint: props.signatureOverlay.placeHint,
5292
5694
  annotationHint: props.signatureOverlay.annotationHint,
5695
+ cancelPlacementLabel: props.signatureOverlay.cancelPlacementLabel,
5293
5696
  annotationPlaceholder: props.signatureOverlay.annotationPlaceholder,
5294
5697
  signatureAltLabel: props.signatureOverlay.signatureAltLabel,
5295
5698
  signatureAltByLabel: props.signatureOverlay.signatureAltByLabel,
@@ -5309,7 +5712,8 @@ function RichTextEditor(props) {
5309
5712
  onRemovePlacement: props.signatureOverlay.onRemovePlacement,
5310
5713
  onRemoveAnnotation: props.signatureOverlay.onRemoveAnnotation,
5311
5714
  onSelectPlacement: props.signatureOverlay.onSelectPlacement,
5312
- onSelectAnnotation: props.signatureOverlay.onSelectAnnotation
5715
+ onSelectAnnotation: props.signatureOverlay.onSelectAnnotation,
5716
+ onCancelPlacementMode: props.signatureOverlay.onCancelPlacementMode
5313
5717
  }
5314
5718
  )
5315
5719
  ] }) })
@@ -5356,11 +5760,13 @@ function RichTextEditor(props) {
5356
5760
  placements: props.signatureOverlay.placements,
5357
5761
  annotations: props.signatureOverlay.annotations,
5358
5762
  pendingSignature: props.signatureOverlay.pendingSignature,
5763
+ pendingSignatureColor: props.signatureOverlay.pendingSignatureColor,
5359
5764
  pendingAnnotation: props.signatureOverlay.pendingAnnotation,
5360
5765
  activePlacementId: props.signatureOverlay.activePlacementId,
5361
5766
  activeAnnotationId: props.signatureOverlay.activeAnnotationId,
5362
5767
  placeHint: props.signatureOverlay.placeHint,
5363
5768
  annotationHint: props.signatureOverlay.annotationHint,
5769
+ cancelPlacementLabel: props.signatureOverlay.cancelPlacementLabel,
5364
5770
  annotationPlaceholder: props.signatureOverlay.annotationPlaceholder,
5365
5771
  signatureAltLabel: props.signatureOverlay.signatureAltLabel,
5366
5772
  signatureAltByLabel: props.signatureOverlay.signatureAltByLabel,
@@ -5380,7 +5786,8 @@ function RichTextEditor(props) {
5380
5786
  onRemovePlacement: props.signatureOverlay.onRemovePlacement,
5381
5787
  onRemoveAnnotation: props.signatureOverlay.onRemoveAnnotation,
5382
5788
  onSelectPlacement: props.signatureOverlay.onSelectPlacement,
5383
- onSelectAnnotation: props.signatureOverlay.onSelectAnnotation
5789
+ onSelectAnnotation: props.signatureOverlay.onSelectAnnotation,
5790
+ onCancelPlacementMode: props.signatureOverlay.onCancelPlacementMode
5384
5791
  }
5385
5792
  )
5386
5793
  ]
@@ -5926,11 +6333,13 @@ function RichTextEditor(props) {
5926
6333
  placements: props.signatureOverlay.placements,
5927
6334
  annotations: props.signatureOverlay.annotations,
5928
6335
  pendingSignature: props.signatureOverlay.pendingSignature,
6336
+ pendingSignatureColor: props.signatureOverlay.pendingSignatureColor,
5929
6337
  pendingAnnotation: props.signatureOverlay.pendingAnnotation,
5930
6338
  activePlacementId: props.signatureOverlay.activePlacementId,
5931
6339
  activeAnnotationId: props.signatureOverlay.activeAnnotationId,
5932
6340
  placeHint: props.signatureOverlay.placeHint,
5933
6341
  annotationHint: props.signatureOverlay.annotationHint,
6342
+ cancelPlacementLabel: props.signatureOverlay.cancelPlacementLabel,
5934
6343
  annotationPlaceholder: props.signatureOverlay.annotationPlaceholder,
5935
6344
  signatureAltLabel: props.signatureOverlay.signatureAltLabel,
5936
6345
  signatureAltByLabel: props.signatureOverlay.signatureAltByLabel,
@@ -5950,7 +6359,8 @@ function RichTextEditor(props) {
5950
6359
  onRemovePlacement: props.signatureOverlay.onRemovePlacement,
5951
6360
  onRemoveAnnotation: props.signatureOverlay.onRemoveAnnotation,
5952
6361
  onSelectPlacement: props.signatureOverlay.onSelectPlacement,
5953
- onSelectAnnotation: props.signatureOverlay.onSelectAnnotation
6362
+ onSelectAnnotation: props.signatureOverlay.onSelectAnnotation,
6363
+ onCancelPlacementMode: props.signatureOverlay.onCancelPlacementMode
5954
6364
  }
5955
6365
  )
5956
6366
  ] }) })
@@ -6836,11 +7246,13 @@ function SpreadsheetEditor(props) {
6836
7246
  placements: props.signatureOverlay.placements,
6837
7247
  annotations: props.signatureOverlay.annotations,
6838
7248
  pendingSignature: props.signatureOverlay.pendingSignature,
7249
+ pendingSignatureColor: props.signatureOverlay.pendingSignatureColor,
6839
7250
  pendingAnnotation: props.signatureOverlay.pendingAnnotation,
6840
7251
  activePlacementId: props.signatureOverlay.activePlacementId,
6841
7252
  activeAnnotationId: props.signatureOverlay.activeAnnotationId,
6842
7253
  placeHint: props.signatureOverlay.placeHint,
6843
7254
  annotationHint: props.signatureOverlay.annotationHint,
7255
+ cancelPlacementLabel: props.signatureOverlay.cancelPlacementLabel,
6844
7256
  annotationPlaceholder: props.signatureOverlay.annotationPlaceholder,
6845
7257
  signatureAltLabel: props.signatureOverlay.signatureAltLabel,
6846
7258
  signatureAltByLabel: props.signatureOverlay.signatureAltByLabel,
@@ -6860,7 +7272,8 @@ function SpreadsheetEditor(props) {
6860
7272
  onRemovePlacement: props.signatureOverlay.onRemovePlacement,
6861
7273
  onRemoveAnnotation: props.signatureOverlay.onRemoveAnnotation,
6862
7274
  onSelectPlacement: props.signatureOverlay.onSelectPlacement,
6863
- onSelectAnnotation: props.signatureOverlay.onSelectAnnotation
7275
+ onSelectAnnotation: props.signatureOverlay.onSelectAnnotation,
7276
+ onCancelPlacementMode: props.signatureOverlay.onCancelPlacementMode
6864
7277
  }
6865
7278
  )
6866
7279
  ] }) })
@@ -6898,11 +7311,13 @@ function ImageRenderer(props) {
6898
7311
  placements: props.signatureOverlay.placements,
6899
7312
  annotations: props.signatureOverlay.annotations,
6900
7313
  pendingSignature: props.signatureOverlay.pendingSignature,
7314
+ pendingSignatureColor: props.signatureOverlay.pendingSignatureColor,
6901
7315
  pendingAnnotation: props.signatureOverlay.pendingAnnotation,
6902
7316
  activePlacementId: props.signatureOverlay.activePlacementId,
6903
7317
  activeAnnotationId: props.signatureOverlay.activeAnnotationId,
6904
7318
  placeHint: props.signatureOverlay.placeHint,
6905
7319
  annotationHint: props.signatureOverlay.annotationHint,
7320
+ cancelPlacementLabel: props.signatureOverlay.cancelPlacementLabel,
6906
7321
  annotationPlaceholder: props.signatureOverlay.annotationPlaceholder,
6907
7322
  signatureAltLabel: props.signatureOverlay.signatureAltLabel,
6908
7323
  signatureAltByLabel: props.signatureOverlay.signatureAltByLabel,
@@ -6922,7 +7337,8 @@ function ImageRenderer(props) {
6922
7337
  onRemovePlacement: props.signatureOverlay.onRemovePlacement,
6923
7338
  onRemoveAnnotation: props.signatureOverlay.onRemoveAnnotation,
6924
7339
  onSelectPlacement: props.signatureOverlay.onSelectPlacement,
6925
- onSelectAnnotation: props.signatureOverlay.onSelectAnnotation
7340
+ onSelectAnnotation: props.signatureOverlay.onSelectAnnotation,
7341
+ onCancelPlacementMode: props.signatureOverlay.onCancelPlacementMode
6926
7342
  }
6927
7343
  )
6928
7344
  ] }) });
@@ -7108,11 +7524,13 @@ function PdfPage({
7108
7524
  placements: signatureOverlay.placements,
7109
7525
  annotations: signatureOverlay.annotations,
7110
7526
  pendingSignature: signatureOverlay.pendingSignature,
7527
+ pendingSignatureColor: signatureOverlay.pendingSignatureColor,
7111
7528
  pendingAnnotation: signatureOverlay.pendingAnnotation,
7112
7529
  activePlacementId: signatureOverlay.activePlacementId,
7113
7530
  activeAnnotationId: signatureOverlay.activeAnnotationId,
7114
7531
  placeHint: signatureOverlay.placeHint,
7115
7532
  annotationHint: signatureOverlay.annotationHint,
7533
+ cancelPlacementLabel: signatureOverlay.cancelPlacementLabel,
7116
7534
  annotationPlaceholder: signatureOverlay.annotationPlaceholder,
7117
7535
  signatureAltLabel: signatureOverlay.signatureAltLabel,
7118
7536
  signatureAltByLabel: signatureOverlay.signatureAltByLabel,
@@ -7132,7 +7550,8 @@ function PdfPage({
7132
7550
  onRemovePlacement: signatureOverlay.onRemovePlacement,
7133
7551
  onRemoveAnnotation: signatureOverlay.onRemoveAnnotation,
7134
7552
  onSelectPlacement: signatureOverlay.onSelectPlacement,
7135
- onSelectAnnotation: signatureOverlay.onSelectAnnotation
7553
+ onSelectAnnotation: signatureOverlay.onSelectAnnotation,
7554
+ onCancelPlacementMode: signatureOverlay.onCancelPlacementMode
7136
7555
  }
7137
7556
  )
7138
7557
  ]
@@ -7631,11 +8050,13 @@ function PptxRenderer(props) {
7631
8050
  placements: props.signatureOverlay.placements,
7632
8051
  annotations: props.signatureOverlay.annotations,
7633
8052
  pendingSignature: props.signatureOverlay.pendingSignature,
8053
+ pendingSignatureColor: props.signatureOverlay.pendingSignatureColor,
7634
8054
  pendingAnnotation: props.signatureOverlay.pendingAnnotation,
7635
8055
  activePlacementId: props.signatureOverlay.activePlacementId,
7636
8056
  activeAnnotationId: props.signatureOverlay.activeAnnotationId,
7637
8057
  placeHint: props.signatureOverlay.placeHint,
7638
8058
  annotationHint: props.signatureOverlay.annotationHint,
8059
+ cancelPlacementLabel: props.signatureOverlay.cancelPlacementLabel,
7639
8060
  annotationPlaceholder: props.signatureOverlay.annotationPlaceholder,
7640
8061
  signatureAltLabel: props.signatureOverlay.signatureAltLabel,
7641
8062
  signatureAltByLabel: props.signatureOverlay.signatureAltByLabel,
@@ -7655,7 +8076,8 @@ function PptxRenderer(props) {
7655
8076
  onRemovePlacement: props.signatureOverlay.onRemovePlacement,
7656
8077
  onRemoveAnnotation: props.signatureOverlay.onRemoveAnnotation,
7657
8078
  onSelectPlacement: props.signatureOverlay.onSelectPlacement,
7658
- onSelectAnnotation: props.signatureOverlay.onSelectAnnotation
8079
+ onSelectAnnotation: props.signatureOverlay.onSelectAnnotation,
8080
+ onCancelPlacementMode: props.signatureOverlay.onCancelPlacementMode
7659
8081
  }
7660
8082
  )
7661
8083
  ]
@@ -7693,13 +8115,13 @@ var defaultLocale = {
7693
8115
  "signatures.title": "Signatures",
7694
8116
  "signatures.empty": "No signatures",
7695
8117
  "signatures.new": "New Signature",
7696
- "signatures.ready": "Ready to place",
8118
+ "signatures.ready": "Placement mode active",
7697
8119
  "signatures.cancelPlacement": "Cancel placement",
7698
8120
  "signatures.drawTitle": "Draw Signature",
7699
8121
  "signatures.drawHelp": "Sign above using your mouse or finger",
7700
8122
  "signatures.clear": "Clear",
7701
8123
  "signatures.createAndUse": "Create & Use",
7702
- "signatures.placeHint": "Click on the document surface to place the signature.",
8124
+ "signatures.placeHint": "Move your pointer to preview the signature, click to place it, then drag to reposition. Press Esc to cancel.",
7703
8125
  "signatures.alt": "Signature",
7704
8126
  "signatures.altBy": "Signature by",
7705
8127
  "signatures.noteIndicator": "Note",
@@ -7709,7 +8131,7 @@ var defaultLocale = {
7709
8131
  "signatures.color.blue": "Blue",
7710
8132
  "signatures.color.red": "Red",
7711
8133
  "signatures.color.green": "Green",
7712
- "annotations.placeHint": "Click on the document surface to place an annotation.",
8134
+ "annotations.placeHint": "Move your pointer to preview the note, click to place it, then drag to reposition. Press Esc to cancel.",
7713
8135
  "annotations.placeholder": "Add instruction or review note...",
7714
8136
  "annotations.title": "Annotation",
7715
8137
  "annotations.linkedTitle": "Signature Note",
@@ -8719,6 +9141,7 @@ function getFileNameHint(fileUrl, fileName) {
8719
9141
  function DocumentViewer(props) {
8720
9142
  const mode = props.mode ?? "view";
8721
9143
  const theme = props.theme ?? "light";
9144
+ const externalLoading = props.loading ?? false;
8722
9145
  const locale = (0, import_react10.useMemo)(
8723
9146
  () => ({ ...defaultLocale, ...props.locale }),
8724
9147
  [props.locale]
@@ -8868,6 +9291,12 @@ function DocumentViewer(props) {
8868
9291
  let active = true;
8869
9292
  let cleanupSource;
8870
9293
  const loadFile = async () => {
9294
+ if (externalLoading) {
9295
+ setLoading(true);
9296
+ setError("");
9297
+ setResolved(null);
9298
+ return;
9299
+ }
8871
9300
  setLoading(true);
8872
9301
  setError("");
8873
9302
  setResolved(null);
@@ -8913,6 +9342,7 @@ function DocumentViewer(props) {
8913
9342
  cleanupSource?.();
8914
9343
  };
8915
9344
  }, [
9345
+ externalLoading,
8916
9346
  hasIncomingSource,
8917
9347
  isRequestedCreateUnsupported,
8918
9348
  mode,
@@ -9074,12 +9504,17 @@ function DocumentViewer(props) {
9074
9504
  };
9075
9505
  const handleSignatureSelect = (signature) => {
9076
9506
  setSelectedSignature(normalizeSignature(signature));
9077
- setSelectedSignatureColor("black");
9078
9507
  setIsPlacingAnnotation(false);
9079
9508
  setActivePlacementId(null);
9080
9509
  setActiveAnnotationId(null);
9081
9510
  props.onSign?.(normalizeSignature(signature));
9082
9511
  };
9512
+ const handleCancelPlacementMode = () => {
9513
+ setSelectedSignature(null);
9514
+ setIsPlacingAnnotation(false);
9515
+ setActivePlacementId(null);
9516
+ setActiveAnnotationId(null);
9517
+ };
9083
9518
  const handlePlaceSignature = (placement) => {
9084
9519
  const nextPlacement = normalizeSignaturePlacement({
9085
9520
  id: createPlacementId(),
@@ -9098,8 +9533,7 @@ function DocumentViewer(props) {
9098
9533
  });
9099
9534
  updatePlacements((prev) => [...prev, nextPlacement]);
9100
9535
  setSelectedSignature(null);
9101
- setSelectedSignatureColor("black");
9102
- setActivePlacementId(null);
9536
+ setActivePlacementId(nextPlacement.id);
9103
9537
  setActiveAnnotationId(null);
9104
9538
  };
9105
9539
  const handlePlaceAnnotation = (annotation) => {
@@ -9149,11 +9583,13 @@ function DocumentViewer(props) {
9149
9583
  placements,
9150
9584
  annotations,
9151
9585
  pendingSignature: selectedSignature,
9586
+ pendingSignatureColor: selectedSignature ? selectedSignatureColor : void 0,
9152
9587
  pendingAnnotation: isPlacingAnnotation,
9153
9588
  activePlacementId,
9154
9589
  activeAnnotationId,
9155
9590
  placeHint: locale["signatures.placeHint"],
9156
9591
  annotationHint: locale["annotations.placeHint"],
9592
+ cancelPlacementLabel: locale["signatures.cancelPlacement"],
9157
9593
  annotationPlaceholder: locale["annotations.placeholder"],
9158
9594
  signatureAltLabel: locale["signatures.alt"],
9159
9595
  signatureAltByLabel: locale["signatures.altBy"],
@@ -9180,15 +9616,20 @@ function DocumentViewer(props) {
9180
9616
  onSelectPlacement: (id) => {
9181
9617
  setActivePlacementId(id);
9182
9618
  if (id) {
9619
+ setSelectedSignature(null);
9620
+ setIsPlacingAnnotation(false);
9183
9621
  setActiveAnnotationId(null);
9184
9622
  }
9185
9623
  },
9186
9624
  onSelectAnnotation: (id) => {
9187
9625
  setActiveAnnotationId(id);
9188
9626
  if (id) {
9627
+ setSelectedSignature(null);
9628
+ setIsPlacingAnnotation(false);
9189
9629
  setActivePlacementId(null);
9190
9630
  }
9191
- }
9631
+ },
9632
+ onCancelPlacementMode: handleCancelPlacementMode
9192
9633
  };
9193
9634
  const saveReady = (0, import_react10.useMemo)(() => {
9194
9635
  if (!resolved) {