@aznabee/freehand-ui 0.1.2 → 0.1.3

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/react.js CHANGED
@@ -46,9 +46,16 @@ function mergeOptions(options = {}) {
46
46
  if (merged.note && typeof merged.note === "string") {
47
47
  merged.note = { text: merged.note, position: "top-right" };
48
48
  }
49
- if (merged.arrow === true) {
50
- const from = merged.note && typeof merged.note === "object" && merged.note.position ? merged.note.position : "top-right";
51
- merged.arrow = { from, to: "edge", style: "curved" };
49
+ if (merged.arrow) {
50
+ const config = merged.arrow === true ? {} : merged.arrow;
51
+ merged.arrow = {
52
+ // "note" launches from the annotation, a position name from that side of
53
+ // the element. Normalised for every arrow, not just `arrow: true`, so an
54
+ // explicit `from` is honoured whether or not there is a note.
55
+ from: config.from ?? (merged.note ? "note" : "top-right"),
56
+ to: config.to ?? "edge",
57
+ style: config.style ?? "curved"
58
+ };
52
59
  }
53
60
  if (merged.decorations === true) {
54
61
  merged.decorations = { count: null, types: null };
@@ -193,10 +200,6 @@ function intersectionArea(a, b) {
193
200
  const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
194
201
  return w > 0 && h > 0 ? w * h : 0;
195
202
  }
196
- function outsideArea(box, bounds) {
197
- if (!bounds) return 0;
198
- return Math.max(0, box.width * box.height - intersectionArea(box, bounds));
199
- }
200
203
  function nearestPointOnRect(rect, point) {
201
204
  return {
202
205
  x: clamp(point.x, rect.x, rect.x + rect.width),
@@ -209,6 +212,17 @@ function distanceToRoundedRect(point, rect, radius = 0) {
209
212
  const cy = clamp(point.y, rect.y + r, rect.y + rect.height - r);
210
213
  return Math.hypot(point.x - cx, point.y - cy) - r;
211
214
  }
215
+ function pointOnBoxToward(box, target) {
216
+ const cx = box.x + box.width / 2;
217
+ const cy = box.y + box.height / 2;
218
+ const dx = target.x - cx;
219
+ const dy = target.y - cy;
220
+ if (dx === 0 && dy === 0) return { x: cx, y: cy };
221
+ const hw = box.width / 2 || 1e-3;
222
+ const hh = box.height / 2 || 1e-3;
223
+ const scale = 1 / Math.max(Math.abs(dx) / hw, Math.abs(dy) / hh);
224
+ return { x: cx + dx * scale, y: cy + dy * scale };
225
+ }
212
226
  function directionFromPosition(position) {
213
227
  return {
214
228
  x: position.includes("right") ? 1 : position.includes("left") ? -1 : 0,
@@ -572,14 +586,86 @@ function drawBorder(svg, rect, options, seed) {
572
586
  });
573
587
  }
574
588
  }
589
+ function cubicAt(p0, c1, c2, p3, t) {
590
+ const u = 1 - t;
591
+ const a = u * u * u;
592
+ const b = 3 * u * u * t;
593
+ const c = 3 * u * t * t;
594
+ const d = t * t * t;
595
+ return {
596
+ x: a * p0.x + b * c1.x + c * c2.x + d * p3.x,
597
+ y: a * p0.y + b * c1.y + c * c2.y + d * p3.y
598
+ };
599
+ }
600
+ function cubicTangentAt(p0, c1, c2, p3, t) {
601
+ const u = 1 - t;
602
+ const a = 3 * u * u;
603
+ const b = 6 * u * t;
604
+ const c = 3 * t * t;
605
+ return {
606
+ x: a * (c1.x - p0.x) + b * (c2.x - c1.x) + c * (p3.x - c2.x),
607
+ y: a * (c1.y - p0.y) + b * (c2.y - c1.y) + c * (p3.y - c2.y)
608
+ };
609
+ }
610
+ function loopedCurvePoints(start, c1, c2, end, radius, random) {
611
+ const at = (t) => cubicAt(start, c1, c2, end, t);
612
+ const tangentAt = (t) => normalize(cubicTangentAt(start, c1, c2, end, t));
613
+ const knot = 0.46 + random() * 0.12;
614
+ const points = [];
615
+ const lead = Math.max(2, Math.round(knot * 16));
616
+ for (let i = 0; i <= lead; i++) points.push(at(knot * i / lead));
617
+ const pivot = at(knot);
618
+ const heading = tangentAt(knot);
619
+ const normal = { x: -heading.y, y: heading.x };
620
+ const side = random() < 0.5 ? 1 : -1;
621
+ const centre = {
622
+ x: pivot.x + normal.x * radius * side,
623
+ y: pivot.y + normal.y * radius * side
624
+ };
625
+ const from = Math.atan2(pivot.y - centre.y, pivot.x - centre.x);
626
+ const turns = Math.PI * 2 * (0.97 + random() * 0.08);
627
+ const steps = 18;
628
+ for (let i = 1; i <= steps; i++) {
629
+ const progress = i / steps;
630
+ const angle = from + turns * progress * side;
631
+ const drift = radius * 0.55 * progress;
632
+ points.push({
633
+ x: centre.x + Math.cos(angle) * radius + heading.x * drift,
634
+ y: centre.y + Math.sin(angle) * radius + heading.y * drift
635
+ });
636
+ }
637
+ const tail = 14;
638
+ for (let i = 1; i <= tail; i++) {
639
+ points.push(at(knot + (1 - knot) * (i / tail)));
640
+ }
641
+ return points;
642
+ }
575
643
  function normalize(vector) {
576
644
  const length = Math.hypot(vector.x, vector.y);
577
645
  if (!length) return { x: 0, y: 0 };
578
646
  return { x: vector.x / length, y: vector.y / length };
579
647
  }
580
- function arrowStartFromNote(origin, target) {
648
+ var NOTE_CLEARANCE = 10;
649
+ var TIP_GAP = 11;
650
+ function arrowStartFromNote(origin, target, from) {
581
651
  const cx = origin.x + origin.width / 2;
582
652
  const cy = origin.y + origin.height / 2;
653
+ if (from && from !== "note") {
654
+ const point = pointFromPosition(from, origin);
655
+ const away = directionFromPosition(from);
656
+ if (!away.x && !away.y) {
657
+ const edge = pointOnBoxToward(origin, target);
658
+ const outward = normalize({ x: edge.x - cx, y: edge.y - cy });
659
+ return {
660
+ x: edge.x + outward.x * NOTE_CLEARANCE,
661
+ y: edge.y + outward.y * NOTE_CLEARANCE
662
+ };
663
+ }
664
+ return {
665
+ x: point.x + away.x * NOTE_CLEARANCE,
666
+ y: point.y + away.y * NOTE_CLEARANCE
667
+ };
668
+ }
583
669
  const dx = target.x - cx;
584
670
  const dy = target.y - cy;
585
671
  const hw = origin.width / 2 || 1e-3;
@@ -587,15 +673,16 @@ function arrowStartFromNote(origin, target) {
587
673
  const sx = Math.sign(dx) || 1;
588
674
  const sy = Math.sign(dy) || 1;
589
675
  if (Math.abs(dy) / hh >= Math.abs(dx) / hw) {
590
- return { x: cx - sx * hw * 0.72, y: cy + sy * (hh + 7) };
676
+ return { x: cx - sx * (hw + 10), y: cy + sy * (hh + 4) };
591
677
  }
592
- return { x: cx + sx * (hw + 7), y: cy + sy * hh * 0.2 };
678
+ return { x: cx + sx * (hw + 8), y: cy + sy * (hh + 6) };
593
679
  }
594
680
  function arrowEndpoints(rect, arrowOptions, random, origin) {
595
681
  const center = { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
682
+ const from = arrowOptions.from || "note";
596
683
  const anchored = Boolean(origin && origin.width > 0 && origin.height > 0);
597
- let start = anchored ? arrowStartFromNote(origin, center) : annotationAnchor(
598
- arrowOptions.from || "top-right",
684
+ let start = anchored ? arrowStartFromNote(origin, center, from) : annotationAnchor(
685
+ from === "note" ? "top-right" : from,
599
686
  rect,
600
687
  38 + random() * 12
601
688
  );
@@ -607,7 +694,7 @@ function arrowEndpoints(rect, arrowOptions, random, origin) {
607
694
  const near = nearestPointOnRect(rect, start);
608
695
  let outward = normalize({ x: start.x - near.x, y: start.y - near.y });
609
696
  if (!outward.x && !outward.y) outward = { x: 0, y: -1 };
610
- const gap = 6 + random() * 3;
697
+ const gap = TIP_GAP + random() * 5;
611
698
  end = { x: near.x + outward.x * gap, y: near.y + outward.y * gap };
612
699
  } else {
613
700
  end = pointFromPosition(toPos, rect);
@@ -620,6 +707,35 @@ function arrowEndpoints(rect, arrowOptions, random, origin) {
620
707
  }
621
708
  return { start, end };
622
709
  }
710
+ function drawArrowHead(svg, tip, angle, style, random, shaftLength = Infinity) {
711
+ const length = Math.min(9 + style.strokeWidth * 1.4, shaftLength * 0.3);
712
+ const spread = 0.46 + random() * 0.1;
713
+ for (const side of [-1, 1]) {
714
+ const armAngle = angle + spread * side;
715
+ const tail = {
716
+ x: tip.x - length * Math.cos(armAngle),
717
+ y: tip.y - length * Math.sin(armAngle)
718
+ };
719
+ appendPath(
720
+ svg,
721
+ roughLine(tail.x, tail.y, tip.x, tip.y, style.roughness * 0.5, random, {
722
+ steps: 2,
723
+ bow: 0.35 * side
724
+ }),
725
+ { ...style, dashed: false }
726
+ );
727
+ }
728
+ }
729
+ function boundsOf(points) {
730
+ const xs = points.map((p) => p.x);
731
+ const ys = points.map((p) => p.y);
732
+ return {
733
+ x: Math.min(...xs),
734
+ y: Math.min(...ys),
735
+ width: Math.max(...xs) - Math.min(...xs),
736
+ height: Math.max(...ys) - Math.min(...ys)
737
+ };
738
+ }
623
739
  function drawArrow(svg, rect, arrowOptions, style, seed, origin = null) {
624
740
  const random = createRandom(seed + 17);
625
741
  const { start, end } = arrowEndpoints(rect, arrowOptions, random, origin);
@@ -641,13 +757,33 @@ function drawArrow(svg, rect, arrowOptions, style, seed, origin = null) {
641
757
  x: rect.x + rect.width / 2 - mid.x,
642
758
  y: rect.y + rect.height / 2 - mid.y
643
759
  };
644
- const sign = perpendicular.x * towardCenter.x + perpendicular.y * towardCenter.y > 0 ? -1 : 1;
760
+ const lateral = perpendicular.x * towardCenter.x + perpendicular.y * towardCenter.y;
761
+ const sign = Math.abs(lateral) > distance * 0.12 ? lateral > 0 ? -1 : 1 : random() < 0.5 ? -1 : 1;
645
762
  const bow = Math.min(distance * (0.16 + random() * 0.08), 30) * sign;
646
763
  const c1x = start.x + dx * 0.28 + perpendicular.x * bow;
647
764
  const c1y = start.y + dy * 0.28 + perpendicular.y * bow;
648
765
  const c2x = start.x + dx * 0.72 + perpendicular.x * bow * 0.85;
649
766
  const c2y = start.y + dy * 0.72 + perpendicular.y * bow * 0.85;
650
767
  hull.push({ x: c1x, y: c1y }, { x: c2x, y: c2y });
768
+ if (arrowOptions.style === "looped") {
769
+ const radius = clamp(distance * 0.11, 7, 17);
770
+ const points = loopedCurvePoints(
771
+ start,
772
+ { x: c1x, y: c1y },
773
+ { x: c2x, y: c2y },
774
+ end,
775
+ radius,
776
+ random
777
+ );
778
+ shaft = catmullRomPath(points, false);
779
+ hull.push(...points);
780
+ const last = points[points.length - 1];
781
+ const prior = points[points.length - 2] ?? start;
782
+ tipAngle = Math.atan2(last.y - prior.y, last.x - prior.x);
783
+ appendPath(svg, shaft, { ...style, dashed });
784
+ drawArrowHead(svg, end, tipAngle, style, random, distance);
785
+ return { start, end, bounds: boundsOf(hull) };
786
+ }
651
787
  shaft = `M ${fmt(start.x)} ${fmt(start.y)} C ${fmt(c1x)} ${fmt(c1y)} ${fmt(c2x)} ${fmt(c2y)} ${fmt(end.x)} ${fmt(end.y)}`;
652
788
  if (style.roughness >= 1.1) {
653
789
  const drift = () => (random() - 0.5) * 1.4;
@@ -662,31 +798,8 @@ function drawArrow(svg, rect, arrowOptions, style, seed, origin = null) {
662
798
  tipAngle = Math.atan2(end.y - c2y, end.x - c2x);
663
799
  }
664
800
  appendPath(svg, shaft, { ...style, dashed });
665
- const headLength = 9 + style.strokeWidth * 1.4;
666
- const spread = 0.46 + random() * 0.1;
667
- for (const side of [-1, 1]) {
668
- const angle = tipAngle + spread * side;
669
- const tail = {
670
- x: end.x - headLength * Math.cos(angle),
671
- y: end.y - headLength * Math.sin(angle)
672
- };
673
- appendPath(
674
- svg,
675
- roughLine(tail.x, tail.y, end.x, end.y, style.roughness * 0.5, random, {
676
- steps: 2,
677
- bow: 0.35 * side
678
- }),
679
- { ...style, dashed: false }
680
- );
681
- }
682
- const xs = hull.map((p) => p.x);
683
- const ys = hull.map((p) => p.y);
684
- const bounds = {
685
- x: Math.min(...xs),
686
- y: Math.min(...ys),
687
- width: Math.max(...xs) - Math.min(...xs),
688
- height: Math.max(...ys) - Math.min(...ys)
689
- };
801
+ drawArrowHead(svg, end, tipAngle, style, random, distance);
802
+ const bounds = boundsOf(hull);
690
803
  return { start, end, bounds };
691
804
  }
692
805
  function drawUnderline(svg, x, y, width, style, random) {
@@ -724,11 +837,38 @@ function drawUnderline(svg, x, y, width, style, random) {
724
837
  }
725
838
  );
726
839
  }
727
- function createNoteText(svg, text, style) {
840
+ var DEFAULT_WORDS_PER_LINE = 2;
841
+ function wrapWords(text, perLine) {
842
+ const lines = [];
843
+ for (const paragraph of String(text).split(/\r?\n/)) {
844
+ const words = paragraph.trim().split(/\s+/).filter(Boolean);
845
+ if (!words.length) continue;
846
+ const size = Number.isFinite(perLine) && perLine >= 1 ? Math.round(perLine) : words.length;
847
+ for (let i = 0; i < words.length; i += size) {
848
+ lines.push(words.slice(i, i + size).join(" "));
849
+ }
850
+ }
851
+ return lines.length ? lines : [String(text)];
852
+ }
853
+ function measureWidth(element, content, fontSize) {
854
+ try {
855
+ const box = element.getBBox();
856
+ if (box && box.width > 0) return box.width;
857
+ } catch {
858
+ }
859
+ try {
860
+ const measured = element.getComputedTextLength();
861
+ if (Number.isFinite(measured) && measured > 0) return measured;
862
+ } catch {
863
+ }
864
+ return content.length * fontSize * 0.45;
865
+ }
866
+ function createNoteText(svg, text, style, options = {}) {
728
867
  const fontSize = style.fontSize ?? NOTE_FONT_SIZE;
868
+ const lines = wrapWords(text, options.wordsPerLine ?? DEFAULT_WORDS_PER_LINE);
869
+ const lineHeight = fontSize * 1.05;
729
870
  const group = document.createElementNS(SVG_NS, "g");
730
871
  const textEl = document.createElementNS(SVG_NS, "text");
731
- textEl.textContent = text;
732
872
  textEl.setAttribute("x", "0");
733
873
  textEl.setAttribute("y", "0");
734
874
  textEl.setAttribute("fill", style.color);
@@ -739,31 +879,43 @@ function createNoteText(svg, text, style) {
739
879
  textEl.setAttribute("letter-spacing", "0.4");
740
880
  textEl.setAttribute("text-anchor", "start");
741
881
  textEl.setAttribute("dominant-baseline", "auto");
882
+ const tspans = lines.map((line, index) => {
883
+ const tspan = document.createElementNS(SVG_NS, "tspan");
884
+ tspan.textContent = line;
885
+ tspan.setAttribute("x", "0");
886
+ tspan.setAttribute("dy", index === 0 ? "0" : String(lineHeight));
887
+ textEl.appendChild(tspan);
888
+ return tspan;
889
+ });
742
890
  group.appendChild(textEl);
743
891
  svg.appendChild(group);
744
- let width = 0;
745
- try {
746
- const box = textEl.getBBox();
747
- if (box && box.width > 0) width = box.width;
748
- } catch {
749
- }
750
- if (!width) {
751
- try {
752
- const measured = textEl.getComputedTextLength();
753
- if (Number.isFinite(measured) && measured > 0) width = measured;
754
- } catch {
755
- }
756
- }
757
- if (!width) width = text.length * fontSize * 0.45;
892
+ const lineWidths = tspans.map(
893
+ (tspan, index) => measureWidth(tspan, lines[index], fontSize)
894
+ );
895
+ const width = Math.max(...lineWidths);
758
896
  const ascent = fontSize * 0.74;
759
897
  const descent = fontSize * 0.26;
760
- return { group, textEl, width, ascent, descent, fontSize };
898
+ const height = (lines.length - 1) * lineHeight + ascent + descent;
899
+ return {
900
+ group,
901
+ textEl,
902
+ tspans,
903
+ lines,
904
+ lineWidths,
905
+ lineHeight,
906
+ width,
907
+ height,
908
+ ascent,
909
+ descent,
910
+ fontSize
911
+ };
761
912
  }
762
913
  function placeNoteText(note, box, style, seed, options = {}) {
763
914
  const random = createRandom(seed + 55);
764
915
  const tilt = options.tilt ?? (random() - 0.5) * 7;
765
916
  note.textEl.setAttribute("x", fmt(box.x));
766
917
  note.textEl.setAttribute("y", fmt(box.y + note.ascent));
918
+ for (const tspan of note.tspans) tspan.setAttribute("x", fmt(box.x));
767
919
  const cx = box.x + box.width / 2;
768
920
  const cy = box.y + box.height / 2;
769
921
  note.group.setAttribute(
@@ -771,11 +923,12 @@ function placeNoteText(note, box, style, seed, options = {}) {
771
923
  `rotate(${tilt.toFixed(2)} ${fmt(cx)} ${fmt(cy)})`
772
924
  );
773
925
  if (options.underline !== false) {
926
+ const lastWidth = note.lineWidths[note.lineWidths.length - 1] ?? box.width;
774
927
  drawUnderline(
775
928
  note.group,
776
929
  box.x + 1,
777
930
  box.y + box.height + 2,
778
- box.width - 2,
931
+ lastWidth - 2,
779
932
  {
780
933
  color: style.color,
781
934
  strokeWidth: style.strokeWidth || 1.4,
@@ -807,25 +960,6 @@ function createOverlaySvg(left, top, width, height) {
807
960
 
808
961
  // src/annotations.js
809
962
  var NOTE_GAP = 14;
810
- function mirror(position, axis) {
811
- if (axis === "x") {
812
- if (position.includes("right")) return position.replace("right", "left");
813
- if (position.includes("left")) return position.replace("left", "right");
814
- return position;
815
- }
816
- if (position.includes("top")) return position.replace("top", "bottom");
817
- if (position.includes("bottom")) return position.replace("bottom", "top");
818
- return position;
819
- }
820
- function candidatePositions(position) {
821
- const candidates = [
822
- position,
823
- mirror(position, "x"),
824
- mirror(position, "y"),
825
- mirror(mirror(position, "x"), "y")
826
- ];
827
- return candidates.filter((value, index) => candidates.indexOf(value) === index);
828
- }
829
963
  function boxForPosition(rect, position, size, gap) {
830
964
  const direction = directionFromPosition(position);
831
965
  const guard = inflateRect(rect, gap);
@@ -844,75 +978,67 @@ function boxForPosition(rect, position, size, gap) {
844
978
  }
845
979
  return { x, y, width: size.width, height: size.height };
846
980
  }
847
- function placeNote(rect, position, size, gap, viewport) {
848
- const guard = inflateRect(rect, gap * 0.7);
849
- let best = null;
850
- candidatePositions(position).forEach((candidate, index) => {
851
- const box = boxForPosition(rect, candidate, size, gap);
852
- const penalty = intersectionArea(box, guard) * 6 + outsideArea(box, viewport) + index * 0.5;
853
- if (!best || penalty < best.penalty) {
854
- best = { box, penalty, position: candidate };
855
- }
856
- });
857
- return best;
858
- }
859
- function clampToViewport(box, guard, viewport) {
860
- if (!viewport || viewport.width <= 0 || viewport.height <= 0) return box;
861
- const shifted = {
862
- ...box,
863
- x: clamp(
864
- box.x,
865
- viewport.x,
866
- Math.max(viewport.x, viewport.x + viewport.width - box.width)
867
- ),
868
- y: clamp(
869
- box.y,
870
- viewport.y,
871
- Math.max(viewport.y, viewport.y + viewport.height - box.height)
872
- )
873
- };
874
- if (intersectionArea(shifted, guard) > intersectionArea(box, guard)) {
875
- return box;
981
+ function limitAgainstElement(box, guard, dx) {
982
+ const level = box.y < guard.y + guard.height && box.y + box.height > guard.y;
983
+ if (!level) return dx;
984
+ if (dx < 0) {
985
+ const room2 = box.x - (guard.x + guard.width);
986
+ return room2 <= 0 ? 0 : -Math.min(-dx, room2);
876
987
  }
877
- return shifted;
988
+ const room = guard.x - (box.x + box.width);
989
+ return room <= 0 ? 0 : Math.min(dx, room);
990
+ }
991
+ function nudgeIntoBand(box, guard, band) {
992
+ if (!band || band.width <= 0) return box;
993
+ const overhangLeft = band.x - box.x;
994
+ const overhangRight = box.x + box.width - (band.x + band.width);
995
+ let dx = 0;
996
+ if (overhangLeft > 0) dx = overhangLeft;
997
+ else if (overhangRight > 0) dx = -overhangRight;
998
+ if (!dx) return box;
999
+ dx = limitAgainstElement(box, guard, dx);
1000
+ if (!dx) return box;
1001
+ return { ...box, x: box.x + dx };
878
1002
  }
879
1003
  function renderAnnotation(svg, rect, noteOptions, style, seed = 1, layout = {}) {
880
1004
  if (!noteOptions?.text) return null;
881
- const viewport = layout.viewport ?? null;
1005
+ const band = layout.band ?? null;
882
1006
  const position = isValidPosition(noteOptions.position) ? noteOptions.position : "top-right";
883
1007
  const random = createRandom(seed + 9);
884
- const note = createNoteText(svg, noteOptions.text, style);
1008
+ const note = createNoteText(svg, noteOptions.text, style, {
1009
+ wordsPerLine: noteOptions.wordsPerLine
1010
+ });
885
1011
  const tilt = (random() - 0.45) * 7;
886
1012
  const tiltPad = Math.abs(Math.sin(tilt * Math.PI / 180)) * note.width * 0.5;
887
1013
  const size = {
888
1014
  width: note.width,
889
- height: note.ascent + note.descent + (noteOptions.underline === false ? 0 : 5)
1015
+ height: note.height + (noteOptions.underline === false ? 0 : 5)
890
1016
  };
891
1017
  const gap = (layout.gap ?? NOTE_GAP) + random() * 6;
892
- const placed = placeNote(
1018
+ const placed = boxForPosition(
893
1019
  rect,
894
1020
  position,
895
1021
  { width: size.width, height: size.height + tiltPad },
896
- gap,
897
- viewport
1022
+ gap
898
1023
  );
1024
+ const shifted = {
1025
+ ...placed,
1026
+ x: placed.x + (Number(noteOptions.offset?.x) || 0),
1027
+ y: placed.y + (Number(noteOptions.offset?.y) || 0)
1028
+ };
899
1029
  const guard = inflateRect(rect, gap * 0.7);
900
- const outer = clampToViewport(
901
- { ...placed.box, height: size.height + tiltPad },
902
- guard,
903
- viewport
904
- );
1030
+ const outer = nudgeIntoBand(shifted, guard, band);
905
1031
  const box = {
906
1032
  x: outer.x,
907
1033
  y: outer.y + tiltPad / 2,
908
1034
  width: size.width,
909
- height: note.ascent + note.descent
1035
+ height: note.height
910
1036
  };
911
1037
  placeNoteText(note, box, style, seed, {
912
1038
  tilt,
913
1039
  underline: noteOptions.underline !== false
914
1040
  });
915
- return { group: note.group, box: inflateRect(outer, 4), position: placed.position };
1041
+ return { group: note.group, box: inflateRect(outer, 4), position };
916
1042
  }
917
1043
 
918
1044
  // src/decorations.js
@@ -1330,14 +1456,11 @@ function ensureHandwrittenFont() {
1330
1456
 
1331
1457
  // src/doodle.js
1332
1458
  var instances = /* @__PURE__ */ new WeakMap();
1333
- function localViewport(overlayRect, inset = 8) {
1459
+ function localBand(overlayRect, inset = 8) {
1334
1460
  const width = window.innerWidth || document.documentElement.clientWidth || 0;
1335
- const height = window.innerHeight || document.documentElement.clientHeight || 0;
1336
1461
  return {
1337
1462
  x: -overlayRect.x + inset,
1338
- y: -overlayRect.y + inset,
1339
- width: Math.max(0, width - inset * 2),
1340
- height: Math.max(0, height - inset * 2)
1463
+ width: Math.max(0, width - inset * 2)
1341
1464
  };
1342
1465
  }
1343
1466
  var DoodleOverlay = class {
@@ -1436,8 +1559,8 @@ var DoodleOverlay = class {
1436
1559
  };
1437
1560
  const noteGroups = [];
1438
1561
  const noteLayout = {
1439
- viewport: localViewport(overlayRect),
1440
- gap: this.options.arrow ? 32 : 14
1562
+ band: localBand(overlayRect),
1563
+ gap: this.options.arrow ? 40 : 14
1441
1564
  };
1442
1565
  targets.forEach((target, index) => {
1443
1566
  const absolute = absoluteRects[index];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aznabee/freehand-ui",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer",
5
5
  "type": "module",
6
6
  "main": "./dist/freehand-ui.cjs",