@zumer/snapdom 2.0.0-dev.0 → 2.0.0-dev.2

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/snapdom.js CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.2.0.0-dev.0
4
+ * v.2.0.0-dev.2
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -374,19 +374,6 @@
374
374
  }
375
375
 
376
376
  // src/utils/image.js
377
- function createBackground(baseCanvas, backgroundColor) {
378
- if (!backgroundColor || !baseCanvas.width || !baseCanvas.height) {
379
- return baseCanvas;
380
- }
381
- const temp = document.createElement("canvas");
382
- temp.width = baseCanvas.width;
383
- temp.height = baseCanvas.height;
384
- const ctx = temp.getContext("2d");
385
- ctx.fillStyle = backgroundColor;
386
- ctx.fillRect(0, 0, temp.width, temp.height);
387
- ctx.drawImage(baseCanvas, 0, 0);
388
- return temp;
389
- }
390
377
  function inlineSingleBackgroundEntry(_0) {
391
378
  return __async(this, arguments, function* (entry, options = {}) {
392
379
  const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
@@ -664,11 +651,20 @@
664
651
  }
665
652
  }
666
653
  function isSafari() {
667
- const ua = typeof navigator !== "undefined" && navigator.userAgent ? navigator.userAgent : "";
668
- const isSafariUA = /^((?!chrome|android).)*safari/i.test(ua);
669
- const isUIWebView = /AppleWebKit/i.test(ua) && /Mobile/i.test(ua) && !/Safari/i.test(ua);
670
- const isWeChatUA = /(MicroMessenger|wxwork|WeCom|WindowsWechat|MacWechat)/i.test(ua);
671
- return isSafariUA || isUIWebView || isWeChatUA;
654
+ if (typeof navigator === "undefined") return false;
655
+ const ua = navigator.userAgent || "";
656
+ const uaLower = ua.toLowerCase();
657
+ const isSafariUA = uaLower.includes("safari") && !uaLower.includes("chrome") && !uaLower.includes("crios") && // Chrome on iOS
658
+ !uaLower.includes("fxios") && // Firefox on iOS
659
+ !uaLower.includes("android");
660
+ const isWebKit = /applewebkit/i.test(ua);
661
+ const isMobile = /mobile/i.test(ua);
662
+ const missingSafariToken = !/safari/i.test(ua);
663
+ const isUIWebView = isWebKit && isMobile && missingSafariToken;
664
+ const isWeChatUA = /(micromessenger|wxwork|wecom|windowswechat|macwechat)/i.test(ua);
665
+ const isBaiduUA = /(baiduboxapp|baidubrowser|baidusearch|baiduboxlite)/i.test(uaLower);
666
+ const isIOSWebKit = /ipad|iphone|ipod/.test(uaLower) && isWebKit;
667
+ return isSafariUA || isUIWebView || isWeChatUA || isBaiduUA || isIOSWebKit;
672
668
  }
673
669
 
674
670
  // src/modules/styles.js
@@ -714,6 +710,22 @@
714
710
  }
715
711
  out[prop] = val;
716
712
  }
713
+ const EXTRA_TEXT_DECORATION_PROPS = [
714
+ "text-decoration-line",
715
+ "text-decoration-color",
716
+ "text-decoration-style",
717
+ "text-decoration-thickness",
718
+ "text-underline-offset",
719
+ "text-decoration-skip-ink"
720
+ ];
721
+ for (const prop of EXTRA_TEXT_DECORATION_PROPS) {
722
+ if (out[prop]) continue;
723
+ try {
724
+ const v = style.getPropertyValue(prop);
725
+ if (v) out[prop] = v;
726
+ } catch (e) {
727
+ }
728
+ }
717
729
  if (options.embedFonts) {
718
730
  const EXTRA_FONT_PROPS = [
719
731
  "font-feature-settings",
@@ -1482,17 +1494,19 @@
1482
1494
  /layui/i,
1483
1495
  /lucide/i
1484
1496
  ];
1497
+ var ICON_FONT_URLS = Object.assign({
1498
+ materialIconsFilled: "https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2",
1499
+ materialIconsOutlined: "https://fonts.gstatic.com/s/materialiconsoutlined/v110/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUcel5euIg.woff2",
1500
+ materialIconsRound: "https://fonts.gstatic.com/s/materialiconsround/v109/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmPq_HTTw.woff2",
1501
+ materialIconsSharp: "https://fonts.gstatic.com/s/materialiconssharp/v110/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvRImcycg.woff2"
1502
+ }, typeof window !== "undefined" && window.__SNAPDOM_ICON_FONTS__ || {});
1485
1503
  var userIconFonts = [];
1486
1504
  function extendIconFonts(fonts) {
1487
1505
  const list = Array.isArray(fonts) ? fonts : [fonts];
1488
1506
  for (const f of list) {
1489
- if (f instanceof RegExp) {
1490
- userIconFonts.push(f);
1491
- } else if (typeof f === "string") {
1492
- userIconFonts.push(new RegExp(f, "i"));
1493
- } else {
1494
- console.warn("[snapdom] Ignored invalid iconFont value:", f);
1495
- }
1507
+ if (f instanceof RegExp) userIconFonts.push(f);
1508
+ else if (typeof f === "string") userIconFonts.push(new RegExp(f, "i"));
1509
+ else console.warn("[snapdom] Ignored invalid iconFont value:", f);
1496
1510
  }
1497
1511
  }
1498
1512
  function isIconFont2(input) {
@@ -1504,6 +1518,187 @@
1504
1518
  if (/icon/i.test(text) || /glyph/i.test(text) || /symbols/i.test(text) || /feather/i.test(text) || /fontawesome/i.test(text)) return true;
1505
1519
  return false;
1506
1520
  }
1521
+ function isMaterialFamily(family = "") {
1522
+ const s = String(family).toLowerCase();
1523
+ return /\bmaterial\s*icons\b/.test(s) || /\bmaterial\s*symbols\b/.test(s);
1524
+ }
1525
+ var loadedCanvasFamilies = /* @__PURE__ */ new Map();
1526
+ function parseAxes(variation = "") {
1527
+ const out = /* @__PURE__ */ Object.create(null);
1528
+ const v = String(variation || "");
1529
+ const rx = /['"]?\s*([A-Za-z]{3,4})\s*['"]?\s*([+-]?\d+(?:\.\d+)?)\s*/g;
1530
+ let m;
1531
+ while (m = rx.exec(v)) out[m[1].toUpperCase()] = Number(m[2]);
1532
+ return out;
1533
+ }
1534
+ function ensureLigatureCanvasFont(cssFamily, className, axes) {
1535
+ return __async(this, null, function* () {
1536
+ var _a;
1537
+ const fam = String(cssFamily || "");
1538
+ const lowerFam = fam.toLowerCase();
1539
+ const cls = String(className || "").toLowerCase();
1540
+ if (/\bmaterial\s*icons\b/.test(lowerFam) && !/\bsymbols\b/.test(lowerFam)) {
1541
+ return { familyForMeasure: fam, familyForCanvas: fam };
1542
+ }
1543
+ const isSymbols = /\bmaterial\s*symbols\b/.test(lowerFam);
1544
+ if (!isSymbols) {
1545
+ return { familyForMeasure: fam, familyForCanvas: fam };
1546
+ }
1547
+ const FILL = axes && ((_a = axes.FILL) != null ? _a : axes.fill);
1548
+ let style = "outlined";
1549
+ if (/\brounded\b/.test(cls) || /\bround\b/.test(cls)) style = "rounded";
1550
+ else if (/\bsharp\b/.test(cls)) style = "sharp";
1551
+ else if (/\boutlined\b/.test(cls)) style = "outlined";
1552
+ const filled = FILL === 1;
1553
+ let pick = null;
1554
+ if (filled) {
1555
+ if (style === "outlined" && ICON_FONT_URLS.materialIconsFilled) {
1556
+ pick = { url: ICON_FONT_URLS.materialIconsFilled, alias: "snapdom-mi-filled" };
1557
+ } else if (style === "rounded" && ICON_FONT_URLS.materialIconsRound) {
1558
+ pick = { url: ICON_FONT_URLS.materialIconsRound, alias: "snapdom-mi-round" };
1559
+ } else if (style === "sharp" && ICON_FONT_URLS.materialIconsSharp) {
1560
+ pick = { url: ICON_FONT_URLS.materialIconsSharp, alias: "snapdom-mi-sharp" };
1561
+ }
1562
+ }
1563
+ if (!pick) {
1564
+ return { familyForMeasure: fam, familyForCanvas: fam };
1565
+ }
1566
+ if (!loadedCanvasFamilies.has(pick.alias)) {
1567
+ try {
1568
+ const ff = new FontFace(pick.alias, `url(${pick.url})`, { style: "normal", weight: "400" });
1569
+ document.fonts.add(ff);
1570
+ yield ff.load();
1571
+ loadedCanvasFamilies.set(pick.alias, true);
1572
+ } catch (e) {
1573
+ return { familyForMeasure: fam, familyForCanvas: fam };
1574
+ }
1575
+ }
1576
+ const quoted = `"${pick.alias}"`;
1577
+ return { familyForMeasure: quoted, familyForCanvas: quoted };
1578
+ });
1579
+ }
1580
+ function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
1581
+ return __async(this, null, function* () {
1582
+ try {
1583
+ yield Promise.all([
1584
+ document.fonts.load(`400 ${px}px "${String(family).replace(/["']/g, "")}"`),
1585
+ document.fonts.ready
1586
+ ]);
1587
+ } catch (e) {
1588
+ }
1589
+ });
1590
+ }
1591
+ function resolvePaintColor(cs) {
1592
+ var _a, _b;
1593
+ let fill = ((_a = cs.getPropertyValue("-webkit-text-fill-color")) == null ? void 0 : _a.trim()) || "";
1594
+ const isTransparent = /^transparent$/i.test(fill) || /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
1595
+ if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor") return fill;
1596
+ const c = (_b = cs.color) == null ? void 0 : _b.trim();
1597
+ return c && c !== "inherit" ? c : "#000";
1598
+ }
1599
+ function materialIconToImage(_0) {
1600
+ return __async(this, arguments, function* (ligatureText, {
1601
+ family = "Material Icons",
1602
+ weight = "normal",
1603
+ fontSize = 32,
1604
+ color = "#000",
1605
+ variation = "",
1606
+ className = ""
1607
+ } = {}) {
1608
+ const fam = String(family || "").replace(/^['"]+|['"]+$/g, "");
1609
+ const dpr = window.devicePixelRatio || 1;
1610
+ const axes = parseAxes(variation);
1611
+ const { familyForMeasure, familyForCanvas } = yield ensureLigatureCanvasFont(fam, className, axes);
1612
+ yield ensureMaterialFontsReady(familyForCanvas.replace(/^["']+|["']+$/g, ""), fontSize);
1613
+ const span = document.createElement("span");
1614
+ span.textContent = ligatureText;
1615
+ span.style.position = "absolute";
1616
+ span.style.visibility = "hidden";
1617
+ span.style.left = "-99999px";
1618
+ span.style.whiteSpace = "nowrap";
1619
+ span.style.fontFamily = familyForMeasure;
1620
+ span.style.fontWeight = String(weight || "normal");
1621
+ span.style.fontSize = `${fontSize}px`;
1622
+ span.style.lineHeight = "1";
1623
+ span.style.margin = "0";
1624
+ span.style.padding = "0";
1625
+ span.style.fontFeatureSettings = "'liga' 1";
1626
+ span.style.fontVariantLigatures = "normal";
1627
+ span.style.color = color;
1628
+ document.body.appendChild(span);
1629
+ const rect = span.getBoundingClientRect();
1630
+ const width = Math.max(1, Math.ceil(rect.width));
1631
+ const height = Math.max(1, Math.ceil(rect.height));
1632
+ document.body.removeChild(span);
1633
+ const canvas = document.createElement("canvas");
1634
+ canvas.width = width * dpr;
1635
+ canvas.height = height * dpr;
1636
+ const ctx = canvas.getContext("2d");
1637
+ ctx.scale(dpr, dpr);
1638
+ ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px ${familyForCanvas}`;
1639
+ ctx.textAlign = "left";
1640
+ ctx.textBaseline = "top";
1641
+ ctx.fillStyle = color;
1642
+ try {
1643
+ ctx.fontKerning = "normal";
1644
+ } catch (e) {
1645
+ }
1646
+ ctx.fillText(ligatureText, 0, 0);
1647
+ return {
1648
+ dataUrl: canvas.toDataURL(),
1649
+ width,
1650
+ height
1651
+ };
1652
+ });
1653
+ }
1654
+ function ligatureIconToImage(cloneRoot, sourceRoot) {
1655
+ return __async(this, null, function* () {
1656
+ if (!(cloneRoot instanceof Element)) return 0;
1657
+ const selector = '.material-icons, [class*="material-symbols"]';
1658
+ const cloneNodes = Array.from(
1659
+ cloneRoot.querySelectorAll(selector)
1660
+ ).filter((n) => n && n.textContent && n.textContent.trim());
1661
+ if (cloneNodes.length === 0) return 0;
1662
+ const sourceNodes = sourceRoot instanceof Element ? Array.from(sourceRoot.querySelectorAll(selector)).filter((n) => n && n.textContent && n.textContent.trim()) : [];
1663
+ let replaced = 0;
1664
+ for (let i = 0; i < cloneNodes.length; i++) {
1665
+ const el = cloneNodes[i];
1666
+ const src = sourceNodes[i] || null;
1667
+ try {
1668
+ const cs = src ? getComputedStyle(src) : getComputedStyle(el);
1669
+ const family = cs.fontFamily || "Material Icons";
1670
+ if (!isMaterialFamily(family)) continue;
1671
+ const text = (src || el).textContent.trim();
1672
+ if (!text) continue;
1673
+ const size = parseInt(cs.fontSize, 10) || 24;
1674
+ const weight = cs.fontWeight && cs.fontWeight !== "normal" ? cs.fontWeight : "normal";
1675
+ const color = resolvePaintColor(cs);
1676
+ const variation = cs.fontVariationSettings && cs.fontVariationSettings !== "normal" ? cs.fontVariationSettings : "";
1677
+ const className = (src || el).className || "";
1678
+ const { dataUrl, width, height } = yield materialIconToImage(text, {
1679
+ family,
1680
+ weight,
1681
+ fontSize: size,
1682
+ color,
1683
+ variation,
1684
+ className
1685
+ });
1686
+ el.textContent = "";
1687
+ const img = el.ownerDocument.createElement("img");
1688
+ img.src = dataUrl;
1689
+ img.alt = text;
1690
+ img.style.height = `${size}px`;
1691
+ img.style.width = `${Math.max(1, Math.round(width / height * size))}px`;
1692
+ img.style.objectFit = "contain";
1693
+ img.style.verticalAlign = getComputedStyle(el).verticalAlign || "baseline";
1694
+ el.appendChild(img);
1695
+ replaced++;
1696
+ } catch (e) {
1697
+ }
1698
+ }
1699
+ return replaced;
1700
+ });
1701
+ }
1507
1702
 
1508
1703
  // src/modules/fonts.js
1509
1704
  function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
@@ -1864,7 +2059,7 @@ ${imported}
1864
2059
  const styleSpec = (((_c = block.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _c[1]) || "normal").trim();
1865
2060
  const stretchSpec = (((_d = block.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _d[1]) || "100%").trim();
1866
2061
  const urange = (((_e = block.match(/unicode-range:\s*([^;]+);/i)) == null ? void 0 : _e[1]) || "").trim();
1867
- const srcRaw = (((_f = block.match(/src\s*:\s*([^;]+);/i)) == null ? void 0 : _f[1]) || "").trim();
2062
+ const srcRaw = (((_f = block.match(/src\s*:\s*([^;}]+)[;}]/i)) == null ? void 0 : _f[1]) || "").trim();
1868
2063
  const urls = extractSrcUrls(srcRaw, location.href);
1869
2064
  const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
1870
2065
  const key = [
@@ -2076,7 +2271,7 @@ ${imported}
2076
2271
  const styleSpec = (((_d = face.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _d[1]) || "normal").trim();
2077
2272
  const stretchSpec = (((_e = face.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _e[1]) || "100%").trim();
2078
2273
  const urange = (((_f = face.match(/unicode-range:\s*([^;]+);/i)) == null ? void 0 : _f[1]) || "").trim();
2079
- const srcRaw = (((_g = face.match(/src\s*:\s*([^;]+);/i)) == null ? void 0 : _g[1]) || "").trim();
2274
+ const srcRaw = (((_g = face.match(/src\s*:\s*([^;}]+)[;}]/i)) == null ? void 0 : _g[1]) || "").trim();
2080
2275
  const srcUrls = extractSrcUrls(srcRaw, link.href);
2081
2276
  if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
2082
2277
  const ranges = parseUnicodeRange(urange);
@@ -3558,13 +3753,58 @@ ${imported}
3558
3753
  } catch (e) {
3559
3754
  }
3560
3755
  }
3756
+ function removeAllComments(root) {
3757
+ const it = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
3758
+ const toRemove = [];
3759
+ while (it.nextNode()) toRemove.push(it.currentNode);
3760
+ for (const n of toRemove) n.remove();
3761
+ }
3762
+ function sanitizeAttributesForXHTML(root, opts = {}) {
3763
+ const { stripFrameworkDirectives = true } = opts;
3764
+ const ALLOWED_PREFIXES = /* @__PURE__ */ new Set(["xml", "xlink"]);
3765
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
3766
+ while (walker.nextNode()) {
3767
+ const el = walker.currentNode;
3768
+ for (const attr of Array.from(el.attributes)) {
3769
+ const name = attr.name;
3770
+ if (name.includes("@")) {
3771
+ el.removeAttribute(name);
3772
+ continue;
3773
+ }
3774
+ if (name.includes(":")) {
3775
+ const prefix = name.split(":", 1)[0];
3776
+ if (!ALLOWED_PREFIXES.has(prefix)) {
3777
+ el.removeAttribute(name);
3778
+ continue;
3779
+ }
3780
+ }
3781
+ if (!stripFrameworkDirectives) continue;
3782
+ if (name.startsWith("x-") || // Alpine
3783
+ name.startsWith("v-") || // Vue
3784
+ name.startsWith(":") || // Vue/Alpine shorthand
3785
+ name.startsWith("on:") || // Svelte
3786
+ name.startsWith("bind:") || // Svelte
3787
+ name.startsWith("let:") || // Svelte
3788
+ name.startsWith("class:")) {
3789
+ el.removeAttribute(name);
3790
+ continue;
3791
+ }
3792
+ }
3793
+ }
3794
+ }
3795
+ function sanitizeCloneForXHTML(root, opts = {}) {
3796
+ if (!root) return;
3797
+ sanitizeAttributesForXHTML(root, opts);
3798
+ removeAllComments(root);
3799
+ }
3561
3800
  function captureDOM(element, options) {
3562
3801
  return __async(this, null, function* () {
3802
+ var _a;
3563
3803
  if (!element) throw new Error("Element cannot be null or undefined");
3564
3804
  applyCachePolicy(options.cache);
3565
3805
  const fast = options.fast;
3566
- const straighten = !!options.straighten;
3567
- const noShadows = !!options.noShadows;
3806
+ const outerTransforms = options.outerTransforms !== false;
3807
+ const outerShadows = !!options.outerShadows;
3568
3808
  let state = { element, options, plugins: options.plugins };
3569
3809
  let clone, classCSS, styleCache;
3570
3810
  let fontsCSS = "";
@@ -3577,17 +3817,29 @@ ${imported}
3577
3817
  const undoClamp = lineClamp(state.element);
3578
3818
  try {
3579
3819
  ({ clone, classCSS, styleCache } = yield prepareClone(state.element, state.options));
3580
- if (straighten && clone) {
3581
- rootTransform2D = normalizeRootTransforms(state.element, state.clone);
3820
+ if (!outerTransforms && clone) {
3821
+ rootTransform2D = normalizeRootTransforms(state.element, clone);
3582
3822
  }
3583
- if (noShadows && clone) {
3584
- stripRootShadows(state.element, state.clone);
3823
+ if (!outerShadows && clone) {
3824
+ stripRootShadows(state.element, clone);
3585
3825
  }
3586
3826
  } finally {
3587
3827
  undoClamp();
3588
3828
  }
3589
3829
  state = __spreadValues({ clone, classCSS, styleCache }, state);
3590
3830
  yield runHook("afterClone", state);
3831
+ sanitizeCloneForXHTML(state.clone);
3832
+ if (((_a = state.options) == null ? void 0 : _a.excludeMode) === "remove") {
3833
+ try {
3834
+ shrinkAutoSizeBoxes(state.element, state.clone, state.styleCache);
3835
+ } catch (e) {
3836
+ console.warn("[snapdom] shrink pass failed:", e);
3837
+ }
3838
+ }
3839
+ try {
3840
+ yield ligatureIconToImage(state.clone, state.element);
3841
+ } catch (e) {
3842
+ }
3591
3843
  yield new Promise((resolve) => {
3592
3844
  idle(() => __async(null, null, function* () {
3593
3845
  yield inlineImages(state.clone, state.options);
@@ -3639,10 +3891,18 @@ ${imported}
3639
3891
  yield runHook("beforeRender", state);
3640
3892
  yield new Promise((resolve) => {
3641
3893
  idle(() => {
3894
+ var _a2;
3642
3895
  const csEl = getComputedStyle(state.element);
3643
3896
  const rect = state.element.getBoundingClientRect();
3644
- const w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
3645
- const h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
3897
+ let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
3898
+ let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
3899
+ if (((_a2 = state.options) == null ? void 0 : _a2.excludeMode) === "remove") {
3900
+ const hEst = estimateKeptHeight(state.element, state.options);
3901
+ const EPS = 1;
3902
+ if (Number.isFinite(hEst) && hEst > 0) {
3903
+ h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
3904
+ }
3905
+ }
3646
3906
  const coerceNum = (v, def = NaN) => {
3647
3907
  const n = typeof v === "string" ? parseFloat(v) : v;
3648
3908
  return Number.isFinite(n) ? n : def;
@@ -3667,7 +3927,7 @@ ${imported}
3667
3927
  h = h0;
3668
3928
  }
3669
3929
  let minX = 0, minY = 0, maxX = w0, maxY = h0;
3670
- if (straighten && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
3930
+ if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
3671
3931
  const M2 = {
3672
3932
  a: rootTransform2D.a,
3673
3933
  b: rootTransform2D.b || 0,
@@ -3682,7 +3942,7 @@ ${imported}
3682
3942
  maxX = limitDecimals(bb2.maxX);
3683
3943
  maxY = limitDecimals(bb2.maxY);
3684
3944
  } else {
3685
- const useTFBBox = !straighten && hasTFBBox(state.element);
3945
+ const useTFBBox = outerTransforms && hasTFBBox(state.element);
3686
3946
  if (useTFBBox) {
3687
3947
  const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
3688
3948
  const ind2 = readIndividualTransforms(state.element);
@@ -3705,7 +3965,7 @@ ${imported}
3705
3965
  const bleedBlur = parseFilterBlur(csEl);
3706
3966
  const bleedOutline = parseOutline(csEl);
3707
3967
  const drop = parseFilterDropShadows(csEl);
3708
- const bleed = noShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
3968
+ const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
3709
3969
  top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
3710
3970
  right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
3711
3971
  bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
@@ -3723,7 +3983,7 @@ ${imported}
3723
3983
  const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
3724
3984
  const svgNS = "http://www.w3.org/2000/svg";
3725
3985
  const basePad = isSafari() ? 1 : 0;
3726
- const extraPad = straighten ? 1 : 0;
3986
+ const extraPad = !outerTransforms ? 1 : 0;
3727
3987
  const pad = limitDecimals(basePad + extraPad);
3728
3988
  const fo = document.createElementNS(svgNS, "foreignObject");
3729
3989
  const vbMinX = limitDecimals(minX);
@@ -3766,6 +4026,58 @@ ${imported}
3766
4026
  return state.dataURL;
3767
4027
  });
3768
4028
  }
4029
+ function authorHasExplicitSize(el) {
4030
+ var _a;
4031
+ try {
4032
+ const s = ((_a = el.getAttribute) == null ? void 0 : _a.call(el, "style")) || "";
4033
+ return /\b(height|width|block-size|inline-size)\s*:/.test(s);
4034
+ } catch (e) {
4035
+ return false;
4036
+ }
4037
+ }
4038
+ function isReplacedElement(el) {
4039
+ return el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLIFrameElement || el instanceof SVGElement || el instanceof HTMLObjectElement || el instanceof HTMLEmbedElement;
4040
+ }
4041
+ function shouldShrinkBox(srcEl, cs) {
4042
+ if (!(srcEl instanceof Element)) return false;
4043
+ if (authorHasExplicitSize(srcEl)) return false;
4044
+ if (isReplacedElement(srcEl)) return false;
4045
+ const pos = cs.position;
4046
+ if (pos === "absolute" || pos === "fixed" || pos === "sticky") return false;
4047
+ const disp = cs.display || "";
4048
+ if (disp.includes("flex") || disp.includes("grid") || disp.startsWith("table")) return false;
4049
+ if (cs.transform && cs.transform !== "none") return false;
4050
+ return true;
4051
+ }
4052
+ function shrinkAutoSizeBoxes(sourceRoot, cloneRoot, styleCache = /* @__PURE__ */ new Map()) {
4053
+ function walk(src, cln) {
4054
+ if (!(src instanceof Element) || !(cln instanceof Element)) return;
4055
+ const lostKids = src.childElementCount > cln.childElementCount;
4056
+ const cs = styleCache.get(src) || getComputedStyle(src);
4057
+ if (!styleCache.has(src)) styleCache.set(src, cs);
4058
+ if (lostKids && shouldShrinkBox(src, cs)) {
4059
+ if (!cln.style.height) cln.style.height = "auto";
4060
+ if (!cln.style.width) cln.style.width = "auto";
4061
+ cln.style.removeProperty("block-size");
4062
+ cln.style.removeProperty("inline-size");
4063
+ if (!cln.style.minHeight) cln.style.minHeight = "0";
4064
+ if (!cln.style.minWidth) cln.style.minWidth = "0";
4065
+ if (!cln.style.maxHeight) cln.style.maxHeight = "none";
4066
+ if (!cln.style.maxWidth) cln.style.maxWidth = "none";
4067
+ const oy = cs.overflowY || cs.overflowBlock || "visible";
4068
+ const ox = cs.overflowX || cs.overflowInline || "visible";
4069
+ if (oy !== "visible" || ox !== "visible") {
4070
+ cln.style.overflow = "visible";
4071
+ }
4072
+ }
4073
+ const sKids = Array.from(src.children);
4074
+ const cKids = Array.from(cln.children);
4075
+ for (let i = 0; i < Math.min(sKids.length, cKids.length); i++) {
4076
+ walk(sKids[i], cKids[i]);
4077
+ }
4078
+ }
4079
+ walk(sourceRoot, cloneRoot);
4080
+ }
3769
4081
  var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * __pow(10, n)) / __pow(10, n) : v;
3770
4082
  function parseFilterDropShadows(cs) {
3771
4083
  var _a;
@@ -4035,6 +4347,52 @@ ${imported}
4035
4347
  host.removeChild(tmp);
4036
4348
  return M;
4037
4349
  }
4350
+ function isInNormalFlow(el) {
4351
+ const cs = getComputedStyle(el);
4352
+ if (cs.display === "none") return false;
4353
+ if (cs.position === "absolute" || cs.position === "fixed" || cs.position === "sticky") return false;
4354
+ if ((cs.cssFloat || cs.float || "none") !== "none") return false;
4355
+ if (cs.transform && cs.transform !== "none") return false;
4356
+ return true;
4357
+ }
4358
+ function willBeExcluded(el, options) {
4359
+ if (!(el instanceof Element)) return false;
4360
+ if (el.getAttribute("data-capture") === "exclude" && (options == null ? void 0 : options.excludeMode) === "remove") return true;
4361
+ if (Array.isArray(options == null ? void 0 : options.exclude)) {
4362
+ for (const sel of options.exclude) {
4363
+ try {
4364
+ if (el.matches(sel)) return options.excludeMode === "remove";
4365
+ } catch (e) {
4366
+ }
4367
+ }
4368
+ }
4369
+ return false;
4370
+ }
4371
+ function estimateKeptHeight(container, options) {
4372
+ const csC = getComputedStyle(container);
4373
+ const rC = container.getBoundingClientRect();
4374
+ let minTop = Infinity;
4375
+ let maxBottom = -Infinity;
4376
+ let found = false;
4377
+ const kids = Array.from(container.children);
4378
+ for (const k of kids) {
4379
+ if (willBeExcluded(k, options)) continue;
4380
+ if (!isInNormalFlow(k)) continue;
4381
+ const rk = k.getBoundingClientRect();
4382
+ const top = rk.top - rC.top;
4383
+ const bottom = rk.bottom - rC.top;
4384
+ if (bottom <= top) continue;
4385
+ if (top < minTop) minTop = top;
4386
+ if (bottom > maxBottom) maxBottom = bottom;
4387
+ found = true;
4388
+ }
4389
+ const contentSpan = found ? Math.max(0, maxBottom - minTop) : 0;
4390
+ const bt = parseFloat(csC.borderTopWidth) || 0;
4391
+ const bb = parseFloat(csC.borderBottomWidth) || 0;
4392
+ const pt = parseFloat(csC.paddingTop) || 0;
4393
+ const pb = parseFloat(csC.paddingBottom) || 0;
4394
+ return bt + bb + pt + pb + contentSpan;
4395
+ }
4038
4396
 
4039
4397
  // src/core/context.js
4040
4398
  function normalizeCachePolicy(v) {
@@ -4085,8 +4443,8 @@ ${imported}
4085
4443
  backgroundColor: (_q = options.backgroundColor) != null ? _q : ["jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
4086
4444
  filename: (_r = options.filename) != null ? _r : "snapDOM",
4087
4445
  // NEW flags (user-friendly)
4088
- straighten: (_s = options.straighten) != null ? _s : false,
4089
- noShadows: (_t = options.noShadows) != null ? _t : false
4446
+ outerTransforms: (_s = options.outerTransforms) != null ? _s : true,
4447
+ outerShadows: (_t = options.outerShadows) != null ? _t : false
4090
4448
  // Plugins (reservado)
4091
4449
  // plugins: normalizePlugins(...),
4092
4450
  };
@@ -4193,7 +4551,7 @@ ${imported}
4193
4551
  }
4194
4552
  function toCanvas(url, options) {
4195
4553
  return __async(this, null, function* () {
4196
- let { width: optW, height: optH, scale = 1, dpr = 1, meta = {} } = options;
4554
+ let { width: optW, height: optH, scale = 1, dpr = 1, meta = {}, backgroundColor } = options;
4197
4555
  url = maybeConvertBoxShadowForSafari(url);
4198
4556
  const img = new Image();
4199
4557
  img.loading = "eager";
@@ -4232,6 +4590,12 @@ ${imported}
4232
4590
  canvas.style.height = `${outH}px`;
4233
4591
  const ctx = canvas.getContext("2d");
4234
4592
  if (dpr !== 1) ctx.scale(dpr, dpr);
4593
+ if (backgroundColor) {
4594
+ ctx.save();
4595
+ ctx.fillStyle = backgroundColor;
4596
+ ctx.fillRect(0, 0, outW, outH);
4597
+ ctx.restore();
4598
+ }
4235
4599
  ctx.drawImage(img, 0, 0, outW, outH);
4236
4600
  return canvas;
4237
4601
  });
@@ -4241,12 +4605,11 @@ ${imported}
4241
4605
  function rasterize(url, options) {
4242
4606
  return __async(this, null, function* () {
4243
4607
  const canvas = yield toCanvas(url, options);
4244
- const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
4245
4608
  const img = new Image();
4246
- img.src = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
4609
+ img.src = canvas.toDataURL(`image/${options.format}`, options.quality);
4247
4610
  yield img.decode();
4248
- img.style.width = `${finalCanvas.width / options.dpr}px`;
4249
- img.style.height = `${finalCanvas.height / options.dpr}px`;
4611
+ img.style.width = `${canvas.width / options.dpr}px`;
4612
+ img.style.height = `${canvas.height / options.dpr}px`;
4250
4613
  return img;
4251
4614
  });
4252
4615
  }
@@ -4310,13 +4673,8 @@ ${imported}
4310
4673
  return new Blob([svgText], { type: "image/svg+xml" });
4311
4674
  }
4312
4675
  const canvas = yield toCanvas(url, options);
4313
- const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
4314
4676
  return new Promise(
4315
- (resolve) => finalCanvas.toBlob(
4316
- (blob) => resolve(blob),
4317
- `image/${type}`,
4318
- options.quality
4319
- )
4677
+ (resolve) => canvas.toBlob((blob) => resolve(blob), `image/${type}`, options.quality)
4320
4678
  );
4321
4679
  });
4322
4680
  }
@@ -4336,9 +4694,8 @@ ${imported}
4336
4694
  return;
4337
4695
  }
4338
4696
  const canvas = yield toCanvas(url, options);
4339
- const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
4340
4697
  const a = document.createElement("a");
4341
- a.href = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
4698
+ a.href = canvas.toDataURL(`image/${options.format}`, options.quality);
4342
4699
  a.download = options.filename;
4343
4700
  a.click();
4344
4701
  });