@aznabee/freehand-ui 0.1.7 → 0.2.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.
@@ -287,6 +287,10 @@ var DEFAULT_OPTIONS = {
287
287
  */
288
288
  zIndex: null
289
289
  };
290
+ var OVERLAY_CLASS = "doodle-ui-overlay";
291
+ function isOverlayNode(node) {
292
+ return node?.classList?.contains(OVERLAY_CLASS) ?? false;
293
+ }
290
294
  function resolveElement(target) {
291
295
  if (!target) return null;
292
296
  if (typeof target === "string") {
@@ -392,45 +396,21 @@ function resolveOverlayZIndex(element, override) {
392
396
  }
393
397
  return null;
394
398
  }
395
- var ITEM_PLACEMENT_PROPERTIES = [
396
- "order",
397
- "flexGrow",
398
- "flexShrink",
399
- "flexBasis",
400
- "alignSelf",
401
- "justifySelf",
402
- "gridColumn",
403
- "gridRow",
404
- "gridArea"
405
- ];
406
- function isInlineDisplay(computed) {
407
- return computed.display.startsWith("inline");
408
- }
409
399
  function insertOverlaySvg(svg, element) {
410
- const parent = element.parentNode;
411
- if (!parent) {
412
- document.body.appendChild(svg);
413
- return null;
414
- }
415
- const wrapper = document.createElement("div");
416
- wrapper.style.position = "relative";
417
- const computed = getComputedStyle(element);
418
- wrapper.style.display = isInlineDisplay(computed) ? "inline-block" : "block";
419
- for (const property of ITEM_PLACEMENT_PROPERTIES) {
420
- const value = computed[property];
421
- if (value) wrapper.style[property] = value;
422
- }
423
- parent.replaceChild(wrapper, element);
424
- wrapper.appendChild(element);
425
- wrapper.appendChild(svg);
426
- return wrapper;
427
- }
428
- function removeOverlaySvg(svg, wrapper, element) {
400
+ const setPosition = getComputedStyle(element).position === "static";
401
+ if (setPosition) {
402
+ element.style.position = "relative";
403
+ }
404
+ element.appendChild(svg);
405
+ return setPosition;
406
+ }
407
+ function removeOverlaySvg(svg, element, clearPosition) {
429
408
  if (svg.parentNode) {
430
409
  svg.parentNode.removeChild(svg);
431
410
  }
432
- if (!wrapper || !wrapper.parentNode) return;
433
- wrapper.parentNode.replaceChild(element, wrapper);
411
+ if (clearPosition) {
412
+ element.style.position = "";
413
+ }
434
414
  }
435
415
  function syncOverlayStacking(svg, element, zIndexOverride) {
436
416
  const zIndex = resolveOverlayZIndex(element, zIndexOverride);
@@ -457,7 +437,10 @@ function averageCornerRadius(element) {
457
437
  function detectBorderRadius(element) {
458
438
  const own = averageCornerRadius(element);
459
439
  if (own > 0) return own;
460
- const child = element.children.length === 1 ? element.firstElementChild : null;
440
+ const content = Array.from(element.children).filter(
441
+ (node) => !isOverlayNode(node)
442
+ );
443
+ const child = content.length === 1 ? content[0] : null;
461
444
  if (!child) return 0;
462
445
  const outer = element.getBoundingClientRect();
463
446
  const inner = child.getBoundingClientRect();
@@ -488,22 +471,6 @@ function relativeRect(outer, inner) {
488
471
  radius: inner.radius
489
472
  };
490
473
  }
491
- function unionRects(rects, margin = 0) {
492
- if (rects.length === 0) {
493
- return { x: 0, y: 0, width: 0, height: 0, radius: 0 };
494
- }
495
- const minX = Math.min(...rects.map((r) => r.x)) - margin;
496
- const minY = Math.min(...rects.map((r) => r.y)) - margin;
497
- const maxX = Math.max(...rects.map((r) => r.x + r.width)) + margin;
498
- const maxY = Math.max(...rects.map((r) => r.y + r.height)) + margin;
499
- return {
500
- x: minX,
501
- y: minY,
502
- width: maxX - minX,
503
- height: maxY - minY,
504
- radius: 0
505
- };
506
- }
507
474
  function inflateRect(rect, amount) {
508
475
  return {
509
476
  x: rect.x - amount,
@@ -585,8 +552,13 @@ function annotationAnchor(position, rect, offset = 12) {
585
552
  return point;
586
553
  }
587
554
 
588
- // src/renderer.js
555
+ // src/svg-dom.js
589
556
  var SVG_NS = "http://www.w3.org/2000/svg";
557
+ function createSvgElement(tag) {
558
+ return document.createElementNS(SVG_NS, tag);
559
+ }
560
+
561
+ // src/renderer.js
590
562
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
591
563
  var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", "Bradley Hand", "Segoe Script", "Comic Sans MS", cursive';
592
564
  var NOTE_FONT_SIZE = 19;
@@ -810,7 +782,7 @@ function createBorderBreaks(perimeter, config, random) {
810
782
  return breaks;
811
783
  }
812
784
  function createGroup(parent, x, y, rotation = 0) {
813
- const group = document.createElementNS(SVG_NS, "g");
785
+ const group = createSvgElement("g");
814
786
  group.setAttribute(
815
787
  "transform",
816
788
  `translate(${fmt(x)} ${fmt(y)}) rotate(${rotation.toFixed(1)})`
@@ -819,7 +791,7 @@ function createGroup(parent, x, y, rotation = 0) {
819
791
  return group;
820
792
  }
821
793
  function appendPath(svg, d, style) {
822
- const path = document.createElementNS(SVG_NS, "path");
794
+ const path = createSvgElement("path");
823
795
  path.setAttribute("d", d);
824
796
  path.setAttribute("fill", style.fill ?? "none");
825
797
  path.setAttribute("stroke", style.color);
@@ -834,7 +806,7 @@ function appendPath(svg, d, style) {
834
806
  return path;
835
807
  }
836
808
  function appendGlow(svg, radius, style) {
837
- const circle = document.createElementNS(SVG_NS, "circle");
809
+ const circle = createSvgElement("circle");
838
810
  circle.setAttribute("r", fmt(radius));
839
811
  circle.setAttribute("fill", style.color);
840
812
  circle.setAttribute("opacity", String(style.opacity));
@@ -1138,7 +1110,7 @@ function drawArrow(svg, rect, arrowOptions, style, seed, origin = null, context
1138
1110
  tipAngle = Math.atan2(end.y - c2y, end.x - c2x);
1139
1111
  }
1140
1112
  }
1141
- const group = document.createElementNS(SVG_NS, "g");
1113
+ const group = createSvgElement("g");
1142
1114
  svg.appendChild(group);
1143
1115
  const shaftPaths = [];
1144
1116
  if (ghost) {
@@ -1228,8 +1200,8 @@ function createNoteText(svg, text, style, options = {}) {
1228
1200
  const lines = wrapWords(text, options.wordsPerLine ?? DEFAULT_WORDS_PER_LINE);
1229
1201
  const lineHeight = fontSize * 1.05;
1230
1202
  const rtl = options.rtl ?? isRtlText(text);
1231
- const group = document.createElementNS(SVG_NS, "g");
1232
- const textEl = document.createElementNS(SVG_NS, "text");
1203
+ const group = createSvgElement("g");
1204
+ const textEl = createSvgElement("text");
1233
1205
  textEl.setAttribute("x", "0");
1234
1206
  textEl.setAttribute("y", "0");
1235
1207
  textEl.setAttribute("fill", style.color);
@@ -1248,7 +1220,7 @@ function createNoteText(svg, text, style, options = {}) {
1248
1220
  textEl.setAttributeNS(XML_NS, "xml:lang", options.locale);
1249
1221
  }
1250
1222
  const tspans = lines.map((line, index) => {
1251
- const tspan = document.createElementNS(SVG_NS, "tspan");
1223
+ const tspan = createSvgElement("tspan");
1252
1224
  tspan.textContent = line;
1253
1225
  tspan.setAttribute("x", "0");
1254
1226
  tspan.setAttribute("dy", index === 0 ? "0" : String(lineHeight));
@@ -1312,8 +1284,8 @@ function placeNoteText(note, box, style, seed, options = {}) {
1312
1284
  return note.group;
1313
1285
  }
1314
1286
  function createOverlaySvg(left, top, width, height) {
1315
- const svg = document.createElementNS(SVG_NS, "svg");
1316
- svg.setAttribute("class", "doodle-ui-overlay");
1287
+ const svg = createSvgElement("svg");
1288
+ svg.setAttribute("class", OVERLAY_CLASS);
1317
1289
  svg.setAttribute("xmlns", SVG_NS);
1318
1290
  svg.setAttribute("width", String(width));
1319
1291
  svg.setAttribute("height", String(height));
@@ -1855,13 +1827,27 @@ var instances = /* @__PURE__ */ new WeakMap();
1855
1827
  function now() {
1856
1828
  return typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
1857
1829
  }
1858
- function localBand(overlayRect, inset = 8) {
1830
+ function localBand(originLeft, inset = 8) {
1859
1831
  const width = window.innerWidth || document.documentElement.clientWidth || 0;
1860
1832
  return {
1861
- x: -overlayRect.x + inset,
1833
+ x: -originLeft + inset,
1862
1834
  width: Math.max(0, width - inset * 2)
1863
1835
  };
1864
1836
  }
1837
+ function paddingBox(element) {
1838
+ const rect = element.getBoundingClientRect();
1839
+ const style = getComputedStyle(element);
1840
+ const left = parseFloat(style.borderLeftWidth) || 0;
1841
+ const top = parseFloat(style.borderTopWidth) || 0;
1842
+ const right = parseFloat(style.borderRightWidth) || 0;
1843
+ const bottom = parseFloat(style.borderBottomWidth) || 0;
1844
+ return {
1845
+ left: rect.left + left,
1846
+ top: rect.top + top,
1847
+ width: Math.max(0, rect.width - left - right),
1848
+ height: Math.max(0, rect.height - top - bottom)
1849
+ };
1850
+ }
1865
1851
  var DoodleOverlay = class {
1866
1852
  /**
1867
1853
  * @param {Element} element
@@ -1873,7 +1859,7 @@ var DoodleOverlay = class {
1873
1859
  this.seed = Math.floor(Math.random() * 1e9);
1874
1860
  this.startedAt = now();
1875
1861
  this.svg = null;
1876
- this.wrapper = null;
1862
+ this.setPosition = false;
1877
1863
  this.resizeObserver = null;
1878
1864
  this.mutationObserver = null;
1879
1865
  this.pendingFrame = null;
@@ -1885,14 +1871,17 @@ var DoodleOverlay = class {
1885
1871
  }
1886
1872
  mount() {
1887
1873
  this.svg = createOverlaySvg(0, 0, 0, 0);
1888
- this.wrapper = insertOverlaySvg(this.svg, this.element);
1874
+ this.setPosition = insertOverlaySvg(this.svg, this.element);
1889
1875
  syncOverlayStacking(this.svg, this.element, this.options.zIndex);
1890
1876
  this.resizeObserver = new ResizeObserver(this.onResize);
1891
1877
  this.resizeObserver.observe(this.element);
1892
1878
  if (this.options.children) {
1893
1879
  this.observeChildren();
1894
1880
  }
1895
- this.mutationObserver = new MutationObserver(this.scheduleUpdate.bind(this));
1881
+ this.mutationObserver = new MutationObserver((records) => {
1882
+ const relevant = records.some((record) => !this.svg.contains(record.target));
1883
+ if (relevant) this.scheduleUpdate();
1884
+ });
1896
1885
  this.mutationObserver.observe(this.element, {
1897
1886
  childList: true,
1898
1887
  subtree: true,
@@ -1944,22 +1933,23 @@ var DoodleOverlay = class {
1944
1933
  if (!this.svg || !this.element.isConnected) return;
1945
1934
  const { padding, radius, color, strokeWidth, roughness, opacity } = this.options;
1946
1935
  const targets = this.getTargets();
1947
- const margin = this.overlayMargin();
1948
1936
  const elapsed = now() - this.startedAt;
1949
1937
  const absoluteRects = targets.map(
1950
1938
  (target) => getElementBounds(target, padding, radius)
1951
1939
  );
1952
- const overlayRect = unionRects(absoluteRects, margin);
1953
1940
  clearSvg(this.svg);
1954
1941
  syncOverlayStacking(this.svg, this.element, this.options.zIndex);
1955
- const origin = this.wrapper ? this.wrapper.getBoundingClientRect() : { left: 0, top: 0 };
1956
- this.svg.setAttribute("width", String(overlayRect.width));
1957
- this.svg.setAttribute("height", String(overlayRect.height));
1958
- this.svg.setAttribute("viewBox", `0 0 ${overlayRect.width} ${overlayRect.height}`);
1959
- this.svg.style.left = `${overlayRect.x - origin.left}px`;
1960
- this.svg.style.top = `${overlayRect.y - origin.top}px`;
1961
- this.svg.style.width = `${overlayRect.width}px`;
1962
- this.svg.style.height = `${overlayRect.height}px`;
1942
+ const box = paddingBox(this.element);
1943
+ const width = Math.max(1, box.width);
1944
+ const height = Math.max(1, box.height);
1945
+ const origin = { x: box.left, y: box.top };
1946
+ this.svg.setAttribute("width", String(width));
1947
+ this.svg.setAttribute("height", String(height));
1948
+ this.svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
1949
+ this.svg.style.left = "0px";
1950
+ this.svg.style.top = "0px";
1951
+ this.svg.style.width = `${width}px`;
1952
+ this.svg.style.height = `${height}px`;
1963
1953
  const strokeStyle = { color, strokeWidth, roughness, opacity };
1964
1954
  const noteStyle = {
1965
1955
  color,
@@ -1970,12 +1960,12 @@ var DoodleOverlay = class {
1970
1960
  };
1971
1961
  const noteGroups = [];
1972
1962
  const noteLayout = {
1973
- band: localBand(overlayRect),
1963
+ band: localBand(origin.x),
1974
1964
  gap: this.options.arrow ? 40 : 14
1975
1965
  };
1976
1966
  targets.forEach((target, index) => {
1977
1967
  const absolute = absoluteRects[index];
1978
- const local = relativeRect(overlayRect, absolute);
1968
+ const local = relativeRect(origin, absolute);
1979
1969
  const seed = this.seed + index * 31;
1980
1970
  const note = this.options.note && index === 0 ? renderAnnotation(
1981
1971
  this.svg,
@@ -2021,15 +2011,6 @@ var DoodleOverlay = class {
2021
2011
  this.svg.appendChild(group);
2022
2012
  }
2023
2013
  }
2024
- /**
2025
- * Room the overlay needs beyond the element for notes, arrows and decorations.
2026
- */
2027
- overlayMargin() {
2028
- const { note, arrow, decorations } = this.options;
2029
- if (!note && !arrow && !decorations) return 48;
2030
- const { text } = resolveLocalizedText(note?.text, note?.locale);
2031
- return Math.max(96, Math.min(240, 80 + text.length * 8));
2032
- }
2033
2014
  destroy() {
2034
2015
  if (this.pendingFrame != null) {
2035
2016
  cancelFrame(this.pendingFrame);
@@ -2052,10 +2033,9 @@ var DoodleOverlay = class {
2052
2033
  }
2053
2034
  this.listeners = [];
2054
2035
  if (this.svg) {
2055
- removeOverlaySvg(this.svg, this.wrapper, this.element);
2036
+ removeOverlaySvg(this.svg, this.element, this.setPosition);
2056
2037
  }
2057
2038
  this.svg = null;
2058
- this.wrapper = null;
2059
2039
  instances.delete(this.element);
2060
2040
  }
2061
2041
  };