@noirmd/previewer 2.1.4 → 2.1.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/modal.css CHANGED
@@ -81,5 +81,11 @@
81
81
  }
82
82
 
83
83
  .nr-modal-trigger {
84
- display: inline-flex;
84
+ display: block;
85
+ }
86
+ .nr-modal-trigger--center {
87
+ text-align: center;
88
+ }
89
+ .nr-modal-trigger--right {
90
+ text-align: right;
85
91
  }
package/dist/react.cjs CHANGED
@@ -1675,6 +1675,22 @@ function parseHtmlAttrs(attrsString) {
1675
1675
  }
1676
1676
 
1677
1677
  // core/parser.ts
1678
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
1679
+ "area",
1680
+ "base",
1681
+ "br",
1682
+ "col",
1683
+ "embed",
1684
+ "hr",
1685
+ "img",
1686
+ "input",
1687
+ "link",
1688
+ "meta",
1689
+ "param",
1690
+ "source",
1691
+ "track",
1692
+ "wbr"
1693
+ ]);
1678
1694
  function parseMarkdown(markdown2) {
1679
1695
  if (!markdown2) return [];
1680
1696
  const lines = markdown2.replace(/\r\n/g, "\n").replace(/\r/g, "").split("\n");
@@ -1688,8 +1704,19 @@ function parseMarkdown(markdown2) {
1688
1704
  if (match = trimmed.match(/^(#{1,6})\s+(.+)$/)) {
1689
1705
  const level = match[1].length;
1690
1706
  const rawText = match[2];
1691
- const { text: text2, classes: classes2, id: customId } = extractAttributes(rawText);
1692
- const baseId = customId || generateId(text2.replace(/->|<-/g, ""));
1707
+ const { text: rawParsedText, classes: classes2, id: customId } = extractAttributes(rawText);
1708
+ let text2 = rawParsedText;
1709
+ let align;
1710
+ const alignCenter = text2.match(/^->\s*(.+?)\s*<-\s*$/);
1711
+ const alignRight = text2.match(/^->\s*(.+?)\s*->\s*$/);
1712
+ if (alignCenter) {
1713
+ text2 = alignCenter[1];
1714
+ align = "center";
1715
+ } else if (alignRight) {
1716
+ text2 = alignRight[1];
1717
+ align = "right";
1718
+ }
1719
+ const baseId = customId || generateId(text2);
1693
1720
  let id2 = baseId;
1694
1721
  let n = 1;
1695
1722
  while (usedIds.has(id2)) {
@@ -1697,7 +1724,7 @@ function parseMarkdown(markdown2) {
1697
1724
  id2 = `${baseId}-${n}`;
1698
1725
  }
1699
1726
  usedIds.add(id2);
1700
- result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0 });
1727
+ result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0, align });
1701
1728
  i++;
1702
1729
  continue;
1703
1730
  }
@@ -1873,29 +1900,13 @@ function parseMarkdown(markdown2) {
1873
1900
  let tagStartMatch = trimmed.match(/^<([a-zA-Z][\w-]*)/);
1874
1901
  if (tagStartMatch) {
1875
1902
  const tagName = tagStartMatch[1].toLowerCase();
1876
- const voidElements = /* @__PURE__ */ new Set([
1877
- "area",
1878
- "base",
1879
- "br",
1880
- "col",
1881
- "embed",
1882
- "hr",
1883
- "img",
1884
- "input",
1885
- "link",
1886
- "meta",
1887
- "param",
1888
- "source",
1889
- "track",
1890
- "wbr"
1891
- ]);
1892
1903
  const remainingText = lines.slice(i).join("\n");
1893
1904
  const openTagRegex = new RegExp(`^\\s*<${tagName}\\b([^>]*?)>`, "i");
1894
1905
  const openTagMatch = remainingText.match(openTagRegex);
1895
1906
  if (openTagMatch) {
1896
1907
  const fullOpenTag = openTagMatch[0];
1897
1908
  const attrs = openTagMatch[1].replace(/\s+/g, " ").trim();
1898
- const isSelfClosing = fullOpenTag.endsWith("/>") || voidElements.has(tagName);
1909
+ const isSelfClosing = fullOpenTag.endsWith("/>") || VOID_ELEMENTS.has(tagName);
1899
1910
  if (isSelfClosing) {
1900
1911
  const blockText = remainingText.substring(0, openTagMatch.index + fullOpenTag.length);
1901
1912
  const consumedLines = blockText.split("\n").length;
@@ -11289,11 +11300,77 @@ function parseInlinePart(part) {
11289
11300
  return document.createTextNode(part);
11290
11301
  }
11291
11302
 
11303
+ // vanilla/utils.ts
11304
+ var THEME_TOKENS = /* @__PURE__ */ new Set([
11305
+ "primary",
11306
+ "secondary",
11307
+ "accent",
11308
+ "neutral",
11309
+ "info",
11310
+ "success",
11311
+ "warning",
11312
+ "error"
11313
+ ]);
11314
+ function isThemeToken(color) {
11315
+ return !!color && THEME_TOKENS.has(color);
11316
+ }
11317
+ function isArbitraryColor(value) {
11318
+ if (THEME_TOKENS.has(value)) return false;
11319
+ if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
11320
+ if (/^[a-zA-Z]+$/.test(value)) return true;
11321
+ return false;
11322
+ }
11323
+ function applyBaseProps(el, props) {
11324
+ if (props.class) {
11325
+ el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11326
+ }
11327
+ if (props.style) {
11328
+ const styles = parseCssString(props.style);
11329
+ for (const [key, value] of Object.entries(styles)) {
11330
+ el.style.setProperty(key, String(value));
11331
+ }
11332
+ }
11333
+ }
11334
+ function applyFloatStyle(el, float, width) {
11335
+ if (!float) return;
11336
+ if (float === "left" || float === "right") {
11337
+ el.style.float = float;
11338
+ if (!width) el.style.maxWidth = "50%";
11339
+ el.style.marginInlineStart = float === "right" ? "1rem" : "";
11340
+ el.style.marginInlineEnd = float === "left" ? "1rem" : "";
11341
+ } else if (float === "center") {
11342
+ el.style.marginInline = "auto";
11343
+ }
11344
+ }
11345
+ function applyColor(el, color, classSuffix) {
11346
+ if (!color) return "";
11347
+ if (isThemeToken(color)) {
11348
+ return ` ${classSuffix}--${color}`;
11349
+ }
11350
+ if (isArbitraryColor(color)) {
11351
+ el.style.background = color;
11352
+ el.style.color = "white";
11353
+ }
11354
+ return "";
11355
+ }
11356
+ function openModal(dialog) {
11357
+ if (!dialog.open) {
11358
+ document.body.appendChild(dialog);
11359
+ dialog.showModal();
11360
+ dialog.addEventListener("close", () => dialog.remove(), { once: true });
11361
+ }
11362
+ }
11363
+ var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11364
+ function parseIntProp(value, defaultValue) {
11365
+ if (!value) return defaultValue;
11366
+ const n = parseInt(value, 10);
11367
+ return Number.isNaN(n) ? defaultValue : n;
11368
+ }
11369
+
11292
11370
  // vanilla/directives/admonition.ts
11293
11371
  var admonitionDirective = ({ directiveType, props, renderSlot }) => {
11294
11372
  const el = createAdmonition(directiveType, props.title, props.icon);
11295
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11296
- if (props.style) el.setAttribute("style", props.style);
11373
+ applyBaseProps(el, props);
11297
11374
  const body = el.querySelector(".nr-admonition__body");
11298
11375
  if (body) {
11299
11376
  body.appendChild(renderSlot("default"));
@@ -11309,8 +11386,7 @@ var detailsDirective = ({ props, renderSlot }) => {
11309
11386
  props.icon,
11310
11387
  props.defaultOpen === "true"
11311
11388
  );
11312
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11313
- if (props.style) el.setAttribute("style", props.style);
11389
+ applyBaseProps(el, props);
11314
11390
  const body = el.querySelector(".nr-details__body");
11315
11391
  if (body) {
11316
11392
  body.appendChild(renderSlot("default"));
@@ -11323,12 +11399,12 @@ var details_default = detailsDirective;
11323
11399
  var modalDirective = ({ props, renderSlot }) => {
11324
11400
  const label = props.label || props.title || "Open";
11325
11401
  const modalTitle = props.title || "Modal";
11326
- const customClass = props.class || "";
11402
+ const align = props.align || "left";
11327
11403
  const wrapper = document.createElement("div");
11328
- wrapper.className = "nr-modal-trigger";
11404
+ wrapper.className = `nr-modal-trigger${align === "center" ? " nr-modal-trigger--center" : align === "right" ? " nr-modal-trigger--right" : ""}`;
11329
11405
  const btn = document.createElement("button");
11330
11406
  btn.className = `nr-button nr-button--default`;
11331
- if (customClass) btn.classList.add(...customClass.split(/\s+/).filter(Boolean));
11407
+ applyBaseProps(btn, props);
11332
11408
  const icon = props.icon || "open_in_new";
11333
11409
  if (icon) btn.appendChild(createIcon(icon));
11334
11410
  btn.appendChild(document.createTextNode(label));
@@ -11340,15 +11416,7 @@ var modalDirective = ({ props, renderSlot }) => {
11340
11416
  prose.appendChild(renderSlot("default"));
11341
11417
  body.appendChild(prose);
11342
11418
  }
11343
- btn.addEventListener("click", () => {
11344
- if (!dialog.open) {
11345
- document.body.appendChild(dialog);
11346
- dialog.showModal();
11347
- dialog.addEventListener("close", () => {
11348
- dialog.remove();
11349
- }, { once: true });
11350
- }
11351
- });
11419
+ btn.addEventListener("click", () => openModal(dialog));
11352
11420
  wrapper.appendChild(btn);
11353
11421
  wrapper.appendChild(dialog);
11354
11422
  return wrapper;
@@ -11358,19 +11426,20 @@ var modal_default = modalDirective;
11358
11426
  // vanilla/directives/button.ts
11359
11427
  var buttonDirective = ({ props, renderSlot }) => {
11360
11428
  const url = props.url || props.href || "#";
11361
- const label = props.label;
11429
+ const label = props.label || props.title;
11362
11430
  const icon = props.icon || "near_me";
11363
11431
  const target = props.target || "_blank";
11364
11432
  const customClass = props.class || "";
11433
+ const align = props.align || "left";
11365
11434
  const wrapper = document.createElement("div");
11366
- wrapper.className = "nr-button-wrap";
11435
+ wrapper.className = `nr-button-wrap${align === "center" ? " nr-button-wrap--center" : align === "right" ? " nr-button-wrap--right" : ""}`;
11367
11436
  if (label) {
11368
11437
  const a = document.createElement("a");
11369
11438
  a.href = url;
11370
11439
  a.target = target;
11371
11440
  a.rel = "noopener noreferrer";
11372
11441
  a.className = "nr-button nr-button--default";
11373
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11442
+ applyBaseProps(a, props);
11374
11443
  a.appendChild(createIcon(icon));
11375
11444
  a.appendChild(document.createTextNode(label));
11376
11445
  wrapper.appendChild(a);
@@ -11381,7 +11450,7 @@ var buttonDirective = ({ props, renderSlot }) => {
11381
11450
  if (links.length > 0) {
11382
11451
  links.forEach((link) => {
11383
11452
  link.classList.add("nr-button", "nr-button--default");
11384
- if (customClass) link.classList.add(...customClass.split(/\s+/).filter(Boolean));
11453
+ applyBaseProps(link, props);
11385
11454
  });
11386
11455
  wrapper.appendChild(slotContent);
11387
11456
  } else {
@@ -11390,7 +11459,7 @@ var buttonDirective = ({ props, renderSlot }) => {
11390
11459
  a.target = target;
11391
11460
  a.rel = "noopener noreferrer";
11392
11461
  a.className = "nr-button nr-button--default";
11393
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11462
+ applyBaseProps(a, props);
11394
11463
  a.appendChild(createIcon(icon));
11395
11464
  a.appendChild(slotContent);
11396
11465
  wrapper.appendChild(a);
@@ -11413,12 +11482,9 @@ var cardDirective = ({
11413
11482
  const { isSingleCard } = options || {};
11414
11483
  const isModal = directiveType === "card-m";
11415
11484
  const isLink = directiveType === "card-b";
11416
- const inlineStyles = props.style ? parseCssString(props.style) : {};
11417
11485
  const card = document.createElement("div");
11418
11486
  card.className = `nr-card${isModal || isLink ? " nr-card--interactive" : ""} ${customClass}`.trim();
11419
- for (const [key, value] of Object.entries(inlineStyles)) {
11420
- card.style.setProperty(key, String(value));
11421
- }
11487
+ applyBaseProps(card, props);
11422
11488
  if (image) {
11423
11489
  const imgWrap = document.createElement("div");
11424
11490
  imgWrap.className = `nr-card__image${isSingleCard ? " nr-card__image--tall" : ""}`;
@@ -11497,13 +11563,7 @@ var cardDirective = ({
11497
11563
  prose.appendChild(renderSlot("content") || renderSlot("default"));
11498
11564
  modalBody.appendChild(prose);
11499
11565
  }
11500
- card.addEventListener("click", () => {
11501
- if (!dialog.open) {
11502
- document.body.appendChild(dialog);
11503
- dialog.showModal();
11504
- dialog.addEventListener("close", () => dialog.remove(), { once: true });
11505
- }
11506
- });
11566
+ card.addEventListener("click", () => openModal(dialog));
11507
11567
  const frag = document.createDocumentFragment();
11508
11568
  frag.appendChild(card);
11509
11569
  frag.appendChild(dialog);
@@ -11551,8 +11611,8 @@ var slideDirective = ({
11551
11611
  if (lines.length === 0) {
11552
11612
  return document.createDocumentFragment();
11553
11613
  }
11554
- const interval = parseInt(props.interval || "3000", 10);
11555
- const speed = parseInt(props.speed || "500", 10);
11614
+ const interval = parseIntProp(props.interval, 3e3);
11615
+ const speed = parseIntProp(props.speed, 500);
11556
11616
  const rawClass = props.class || "";
11557
11617
  const inlineStyle = props.style ? parseCssString(props.style) : {};
11558
11618
  const scopeClass = `sld-${++slideCounter}`;
@@ -11603,10 +11663,11 @@ var slideDirective = ({
11603
11663
  }
11604
11664
  });
11605
11665
  if (lines.length > 1) {
11606
- setInterval(() => {
11666
+ const id = setInterval(() => {
11607
11667
  current = (current + 1) % lines.length;
11608
11668
  track.style.transform = `translateY(${-current * maxH}px)`;
11609
11669
  }, interval);
11670
+ container.dataset.nrIntervalId = String(id);
11610
11671
  }
11611
11672
  return container;
11612
11673
  };
@@ -11616,8 +11677,7 @@ var slide_default = slideDirective;
11616
11677
  var keysDirective = ({ props, slots }) => {
11617
11678
  const wrap = document.createElement("div");
11618
11679
  wrap.className = "nr-keys";
11619
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11620
- if (props.style) wrap.setAttribute("style", props.style);
11680
+ applyBaseProps(wrap, props);
11621
11681
  const sizeClass = props.size ? ` nr-kbd--${props.size}` : "";
11622
11682
  const parts = (slots.default || "").split("+").map((p) => p.trim()).filter(Boolean);
11623
11683
  parts.forEach((part, i) => {
@@ -11641,8 +11701,7 @@ var accordionCounter = 0;
11641
11701
  var accordionItemDirective = ({ props, renderSlot }) => {
11642
11702
  const item = document.createElement("div");
11643
11703
  item.className = "nr-accordion__item";
11644
- if (props.class) item.classList.add(...props.class.split(/\s+/).filter(Boolean));
11645
- if (props.style) item.setAttribute("style", props.style);
11704
+ applyBaseProps(item, props);
11646
11705
  const input = document.createElement("input");
11647
11706
  input.type = "radio";
11648
11707
  input.className = "nr-accordion__input";
@@ -11661,8 +11720,7 @@ var accordionItemDirective = ({ props, renderSlot }) => {
11661
11720
  var accordionDirective = ({ props, renderSlot }) => {
11662
11721
  const wrap = document.createElement("div");
11663
11722
  wrap.className = "nr-accordion";
11664
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11665
- if (props.style) wrap.setAttribute("style", props.style);
11723
+ applyBaseProps(wrap, props);
11666
11724
  wrap.appendChild(renderSlot("default"));
11667
11725
  const mode = props.mode === "checkbox" ? "checkbox" : "radio";
11668
11726
  const group = `nr-acc-${++accordionCounter}`;
@@ -11675,7 +11733,6 @@ var accordionDirective = ({ props, renderSlot }) => {
11675
11733
  var accordion_default = accordionDirective;
11676
11734
 
11677
11735
  // vanilla/directives/carousel.ts
11678
- var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11679
11736
  var carouselDirective = ({ props, slots }) => {
11680
11737
  const images = [];
11681
11738
  const raw = slots.default || "";
@@ -11691,19 +11748,9 @@ var carouselDirective = ({ props, slots }) => {
11691
11748
  wrap.className = "nr-carousel";
11692
11749
  wrap.tabIndex = 0;
11693
11750
  wrap.setAttribute("aria-label", "Image carousel");
11694
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11695
- if (props.style) wrap.setAttribute("style", props.style);
11751
+ applyBaseProps(wrap, props);
11696
11752
  if (props.width) wrap.style.width = props.width;
11697
- if (props.float) {
11698
- if (props.float === "left" || props.float === "right") {
11699
- wrap.style.float = props.float;
11700
- if (!props.width) wrap.style.maxWidth = "50%";
11701
- wrap.style.marginInlineStart = props.float === "right" ? "1rem" : "";
11702
- wrap.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
11703
- } else if (props.float === "center") {
11704
- wrap.style.marginInline = "auto";
11705
- }
11706
- }
11753
+ applyFloatStyle(wrap, props.float, props.width);
11707
11754
  const viewport = document.createElement("div");
11708
11755
  viewport.className = "nr-carousel__viewport";
11709
11756
  if (props.height) viewport.style.height = props.height;
@@ -11773,11 +11820,10 @@ var DEFAULT_LABELS = ["days", "hours", "min", "sec"];
11773
11820
  var countdownDirective = ({ props }) => {
11774
11821
  const wrap = document.createElement("div");
11775
11822
  wrap.className = "nr-countdown";
11776
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11777
- if (props.style) wrap.setAttribute("style", props.style);
11823
+ applyBaseProps(wrap, props);
11778
11824
  const labelParts = (props.labels || "").split("|").map((s) => s.trim());
11779
11825
  const labels = DEFAULT_LABELS.map((label, i) => labelParts[i] || label);
11780
- const digits = parseInt(props.digits || "2", 10);
11826
+ const digits = parseIntProp(props.digits, 2);
11781
11827
  const targetTime = props.target ? new Date(props.target).getTime() : NaN;
11782
11828
  const hasTarget = !Number.isNaN(targetTime);
11783
11829
  const blocks = [];
@@ -11824,13 +11870,15 @@ var countdownDirective = ({ props }) => {
11824
11870
  blocks.push({ value });
11825
11871
  });
11826
11872
  render();
11827
- if (hasTarget) setInterval(render, 1e3);
11873
+ if (hasTarget) {
11874
+ const id = setInterval(render, 1e3);
11875
+ wrap.dataset.nrIntervalId = String(id);
11876
+ }
11828
11877
  return wrap;
11829
11878
  };
11830
11879
  var countdown_default = countdownDirective;
11831
11880
 
11832
11881
  // vanilla/directives/diff.ts
11833
- var IMG_RE2 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11834
11882
  var diffDirective = ({ props, slots }) => {
11835
11883
  let before = (props.before || "").split("#")[0].trim();
11836
11884
  let after = (props.after || "").split("#")[0].trim();
@@ -11838,8 +11886,8 @@ var diffDirective = ({ props, slots }) => {
11838
11886
  const urls = [];
11839
11887
  const raw = slots.default || "";
11840
11888
  let m;
11841
- IMG_RE2.lastIndex = 0;
11842
- while ((m = IMG_RE2.exec(raw)) !== null) {
11889
+ IMG_RE.lastIndex = 0;
11890
+ while ((m = IMG_RE.exec(raw)) !== null) {
11843
11891
  urls.push(m[2].split("#")[0].trim());
11844
11892
  }
11845
11893
  if (!before && urls.length > 0) before = urls[0];
@@ -11852,21 +11900,11 @@ var diffDirective = ({ props, slots }) => {
11852
11900
  figure.className = "nr-diff";
11853
11901
  figure.tabIndex = 0;
11854
11902
  figure.setAttribute("aria-label", "Image comparison slider");
11855
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
11856
- if (props.style) figure.setAttribute("style", props.style);
11903
+ applyBaseProps(figure, props);
11857
11904
  if (props.aspect) figure.style.aspectRatio = props.aspect;
11858
11905
  if (props.height) figure.style.height = props.height;
11859
11906
  if (props.width) figure.style.width = props.width;
11860
- if (props.float) {
11861
- if (props.float === "left" || props.float === "right") {
11862
- figure.style.float = props.float;
11863
- if (!props.width) figure.style.maxWidth = "50%";
11864
- figure.style.marginInlineStart = props.float === "right" ? "1rem" : "";
11865
- figure.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
11866
- } else if (props.float === "center") {
11867
- figure.style.marginInline = "auto";
11868
- }
11869
- }
11907
+ applyFloatStyle(figure, props.float, props.width);
11870
11908
  const beforeItem = document.createElement("div");
11871
11909
  beforeItem.className = "nr-diff__item nr-diff__item--before";
11872
11910
  beforeItem.setAttribute("role", "img");
@@ -11929,8 +11967,7 @@ var diff_default = diffDirective;
11929
11967
  var hover3dDirective = ({ props, renderSlot }) => {
11930
11968
  const container = document.createElement("div");
11931
11969
  container.className = "nr-hover-3d";
11932
- if (props.class) container.classList.add(...props.class.split(/\s+/).filter(Boolean));
11933
- if (props.style) container.setAttribute("style", props.style);
11970
+ applyBaseProps(container, props);
11934
11971
  const stage = document.createElement("div");
11935
11972
  stage.className = "nr-hover-3d__stage";
11936
11973
  stage.appendChild(renderSlot("default"));
@@ -11943,14 +11980,13 @@ var hover3dDirective = ({ props, renderSlot }) => {
11943
11980
  var hover3d_default = hover3dDirective;
11944
11981
 
11945
11982
  // vanilla/directives/hovergallery.ts
11946
- var IMG_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11947
11983
  var MAX_IMAGES = 10;
11948
11984
  var hovergalleryDirective = ({ props, slots }) => {
11949
11985
  const images = [];
11950
11986
  const raw = slots.default || "";
11951
11987
  let m;
11952
- IMG_RE3.lastIndex = 0;
11953
- while ((m = IMG_RE3.exec(raw)) !== null) {
11988
+ IMG_RE.lastIndex = 0;
11989
+ while ((m = IMG_RE.exec(raw)) !== null) {
11954
11990
  images.push({ src: m[2].split("#")[0].trim(), alt: m[1].trim() || "gallery image" });
11955
11991
  }
11956
11992
  if (images.length === 0) {
@@ -11959,9 +11995,8 @@ var hovergalleryDirective = ({ props, slots }) => {
11959
11995
  const count = Math.min(images.length, MAX_IMAGES);
11960
11996
  const figure = document.createElement("figure");
11961
11997
  figure.className = "nr-hover-gallery";
11998
+ applyBaseProps(figure, props);
11962
11999
  if (props.aspect) figure.style.aspectRatio = props.aspect;
11963
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
11964
- if (props.style) figure.setAttribute("style", (figure.getAttribute("style") || "") + ";" + props.style);
11965
12000
  const imgEls = [];
11966
12001
  for (let i = 0; i < count; i++) {
11967
12002
  const el = document.createElement("img");
@@ -12011,28 +12046,11 @@ var hovergalleryDirective = ({ props, slots }) => {
12011
12046
  var hovergallery_default = hovergalleryDirective;
12012
12047
 
12013
12048
  // vanilla/directives/chat.ts
12014
- var CHAT_THEME_TOKENS = /* @__PURE__ */ new Set([
12015
- "primary",
12016
- "secondary",
12017
- "accent",
12018
- "neutral",
12019
- "info",
12020
- "success",
12021
- "warning",
12022
- "error"
12023
- ]);
12024
- function isArbitraryColor(value) {
12025
- if (CHAT_THEME_TOKENS.has(value)) return false;
12026
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
12027
- if (/^[a-zA-Z]+$/.test(value)) return true;
12028
- return false;
12029
- }
12030
12049
  var chatItemDirective = ({ props, renderSlot }) => {
12031
12050
  const side = props.side === "end" ? "end" : "start";
12032
12051
  const wrap = document.createElement("div");
12033
12052
  wrap.className = `nr-chat nr-chat--${side}`;
12034
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
12035
- if (props.style) wrap.setAttribute("style", props.style);
12053
+ applyBaseProps(wrap, props);
12036
12054
  const header = document.createElement("div");
12037
12055
  header.className = "nr-chat__header";
12038
12056
  if (props.name) {
@@ -12057,14 +12075,8 @@ var chatItemDirective = ({ props, renderSlot }) => {
12057
12075
  avatar.appendChild(img);
12058
12076
  wrap.appendChild(avatar);
12059
12077
  }
12060
- const isThemeToken = CHAT_THEME_TOKENS.has(props.color || "");
12061
- const colorClass = isThemeToken ? ` nr-chat__bubble--${props.color}` : "";
12062
12078
  const bubble = document.createElement("div");
12063
- bubble.className = `nr-chat__bubble${colorClass}`;
12064
- if (props.color && isArbitraryColor(props.color) && !isThemeToken) {
12065
- bubble.style.background = props.color;
12066
- bubble.style.color = "white";
12067
- }
12079
+ bubble.className = `nr-chat__bubble${applyColor(bubble, props.color, "nr-chat__bubble")}`;
12068
12080
  bubble.appendChild(renderSlot("default"));
12069
12081
  wrap.appendChild(bubble);
12070
12082
  if (props.footer) {
@@ -12078,8 +12090,7 @@ var chatItemDirective = ({ props, renderSlot }) => {
12078
12090
  var chatDirective = ({ props, renderSlot }) => {
12079
12091
  const wrap = document.createElement("div");
12080
12092
  wrap.className = "nr-chat";
12081
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
12082
- if (props.style) wrap.setAttribute("style", props.style);
12093
+ applyBaseProps(wrap, props);
12083
12094
  wrap.appendChild(renderSlot("default"));
12084
12095
  return wrap;
12085
12096
  };
@@ -12115,8 +12126,7 @@ function bindEventProp(el, eventProp) {
12115
12126
  var richlistItemDirective = ({ props, renderSlot }) => {
12116
12127
  const li = document.createElement("li");
12117
12128
  li.className = "nr-richlist__item";
12118
- if (props.class) li.classList.add(...props.class.split(/\s+/).filter(Boolean));
12119
- if (props.style) li.setAttribute("style", props.style);
12129
+ applyBaseProps(li, props);
12120
12130
  if (props.image) {
12121
12131
  const thumb = document.createElement("div");
12122
12132
  thumb.className = "nr-richlist__thumb";
@@ -12178,36 +12188,20 @@ var richlistItemDirective = ({ props, renderSlot }) => {
12178
12188
  var richlistDirective = ({ props, renderSlot }) => {
12179
12189
  const ul = document.createElement("ul");
12180
12190
  ul.className = "nr-richlist";
12181
- if (props.class) ul.classList.add(...props.class.split(/\s+/).filter(Boolean));
12182
- if (props.style) ul.setAttribute("style", props.style);
12191
+ applyBaseProps(ul, props);
12183
12192
  ul.appendChild(renderSlot("default"));
12184
12193
  return ul;
12185
12194
  };
12186
12195
  var richlist_default = richlistDirective;
12187
12196
 
12188
12197
  // vanilla/directives/stat.ts
12189
- var STAT_THEME_TOKENS = /* @__PURE__ */ new Set([
12190
- "primary",
12191
- "secondary",
12192
- "info",
12193
- "success",
12194
- "warning",
12195
- "error"
12196
- ]);
12197
- function isArbitraryColor2(value) {
12198
- if (STAT_THEME_TOKENS.has(value)) return false;
12199
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
12200
- if (/^[a-zA-Z]+$/.test(value)) return true;
12201
- return false;
12202
- }
12203
12198
  var statDirective = ({ props }) => {
12204
- const isThemeToken = STAT_THEME_TOKENS.has(props.color || "");
12205
- const colorClass = isThemeToken ? ` nr-stat--${props.color}` : "";
12199
+ const statIsThemeToken = isThemeToken(props.color);
12200
+ const colorClass = statIsThemeToken ? ` nr-stat--${props.color}` : "";
12206
12201
  const stat = document.createElement("div");
12207
12202
  stat.className = `nr-stat${colorClass}`;
12208
- if (props.class) stat.classList.add(...props.class.split(/\s+/).filter(Boolean));
12209
- if (props.style) stat.setAttribute("style", props.style);
12210
- const useInlineColor = props.color && isArbitraryColor2(props.color) && !isThemeToken;
12203
+ applyBaseProps(stat, props);
12204
+ const useInlineColor = props.color && isArbitraryColor(props.color) && !statIsThemeToken;
12211
12205
  if (props.icon) {
12212
12206
  const figure = document.createElement("div");
12213
12207
  figure.className = "nr-stat__figure";
@@ -12300,9 +12294,12 @@ function renderHtmlString(html) {
12300
12294
  processedContent = processedContent.replace(
12301
12295
  /<style(?:\s+[^>]*)?>([\s\S]*?)<\/style>/gi,
12302
12296
  (_match, cssContent) => {
12297
+ const trimmed = cssContent.trim();
12298
+ const existing = document.head.querySelector("style[data-nr-global]");
12299
+ if (existing && existing.textContent === trimmed) return "";
12303
12300
  const styleEl = document.createElement("style");
12304
12301
  styleEl.setAttribute("data-nr-global", "");
12305
- styleEl.textContent = cssContent;
12302
+ styleEl.textContent = trimmed;
12306
12303
  document.head.appendChild(styleEl);
12307
12304
  return "";
12308
12305
  }
@@ -12372,19 +12369,14 @@ function renderElement(element, ctx, allElements) {
12372
12369
  switch (element.type) {
12373
12370
  case "header": {
12374
12371
  const tag = `h${element.level}`;
12375
- let text = element.text;
12376
- const alignCenter = text.match(/^->\s*(.+?)\s*<-\s*$/);
12377
- const alignRight = text.match(/^->\s*(.+?)\s*->\s*$/);
12378
- if (alignCenter) text = alignCenter[1];
12379
- else if (alignRight) text = alignRight[1];
12380
12372
  const h = document.createElement(tag);
12381
12373
  h.id = element.id;
12382
12374
  let cls = `md-h${element.level}`;
12383
- if (alignCenter) cls += " text-center";
12384
- if (alignRight) cls += " text-right";
12375
+ if (element.align === "center") cls += " text-center";
12376
+ else if (element.align === "right") cls += " text-right";
12385
12377
  if (element.classes) cls += ` ${element.classes}`;
12386
12378
  h.className = cls;
12387
- h.appendChild(renderInline(text));
12379
+ h.appendChild(renderInline(element.text));
12388
12380
  return h;
12389
12381
  }
12390
12382
  case "paragraph": {
@@ -12943,7 +12935,7 @@ var guideData = [
12943
12935
  "title": "Modal",
12944
12936
  "icon": "open_in_full",
12945
12937
  "order": 2,
12946
- "md": '# Modal\n\nLa directiva `:::modal` crea un **di\xE1logo modal** con su bot\xF3n de apertura.\n\n## Sintaxis\n\n```md\n:::modal {title="Confirmar borrado" label="Abrir modal" icon="delete"}\n\xBFSeguro que quieres borrar este documento? Esta acci\xF3n no se puede deshacer.\n\n| Acci\xF3n | Efecto |\n| --- | --- |\n| Aceptar | Borra el documento |\n| Cancelar | No hace nada |\n:::\n```\n\n:::modal {title="Confirmar borrado" label="Abrir modal" icon="delete"}\n\xBFSeguro que quieres borrar este documento? Esta acci\xF3n no se puede deshacer.\n\n| Acci\xF3n | Efecto |\n| --- | --- |\n| Aceptar | Borra el documento |\n| Cancelar | No hace nada |\n:::\n\n## Contenido enriquecido\n\n```md\n:::modal {title="Notas de la versi\xF3n" label="Ver novedades" icon="new_releases"}\n**v2.0** \u2014 cambios principales:\n\n- Nuevo componente `:::diff`\n- Gu\xEDa integrada en el editor\n- Especificidad CSS corregida en im\xE1genes\n:::\n```\n\n:::modal {title="Notas de la versi\xF3n" label="Ver novedades" icon="new_releases"}\n**v2.0** \u2014 cambios principales:\n\n- Nuevo componente `:::diff`\n- Gu\xEDa integrada en el editor\n- Especificidad CSS corregida en im\xE1genes\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | T\xEDtulo del modal |\n| `label` | texto | Texto del bot\xF3n de apertura (default: `title` o \xABOpen\xBB) |\n| `icon` | nombre Material | Icono del bot\xF3n (default `open_in_new`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Interacci\xF3n\n\n- **Bot\xF3n**: abre el modal (focus se mueve dentro).\n- **Overlay** o bot\xF3n **\xD7**: cierra.\n- **Esc**: cierra (en desktop).\n- `dialog` nativo \u2192 accesible por defecto, focus trapped y `inert` al fondo.'
12938
+ "md": '# Modal\n\nLa directiva `:::modal` crea un **di\xE1logo modal** con su bot\xF3n de apertura.\n\n## Sintaxis\n\n```md\n:::modal {title="Confirmar borrado" label="Abrir modal" icon="delete"}\n\xBFSeguro que quieres borrar este documento? Esta acci\xF3n no se puede deshacer.\n\n| Acci\xF3n | Efecto |\n| --- | --- |\n| Aceptar | Borra el documento |\n| Cancelar | No hace nada |\n:::\n```\n\n:::modal {title="Confirmar borrado" label="Abrir modal" icon="delete"}\n\xBFSeguro que quieres borrar este documento? Esta acci\xF3n no se puede deshacer.\n\n| Acci\xF3n | Efecto |\n| --- | --- |\n| Aceptar | Borra el documento |\n| Cancelar | No hace nada |\n:::\n\n## Contenido enriquecido\n\n```md\n:::modal {title="Notas de la versi\xF3n" label="Ver novedades" icon="new_releases"}\n**v2.0** \u2014 cambios principales:\n\n- Nuevo componente `:::diff`\n- Gu\xEDa integrada en el editor\n- Especificidad CSS corregida en im\xE1genes\n:::\n```\n\n:::modal {title="Notas de la versi\xF3n" label="Ver novedades" icon="new_releases"}\n**v2.0** \u2014 cambios principales:\n\n- Nuevo componente `:::diff`\n- Gu\xEDa integrada en el editor\n- Especificidad CSS corregida en im\xE1genes\n:::\n\n## Alineaci\xF3n del bot\xF3n\n\nEl bot\xF3n de apertura se alinea a la izquierda por defecto. Usa el prop `align` para cambiar la alineaci\xF3n:\n\n### Centrado\n\n```md\n:::modal {title="Centrado" label="Abrir" icon="open_in_full" align="center"}\nContenido del modal centrado.\n:::\n```\n\n:::modal {title="Centrado" label="Abrir" icon="open_in_full" align="center"}\nContenido del modal centrado.\n:::\n\n### Alineado a la derecha\n\n```md\n:::modal {title="Derecha" label="Abrir" icon="open_in_full" align="right"}\nContenido del modal alineado a la derecha.\n:::\n```\n\n:::modal {title="Derecha" label="Abrir" icon="open_in_full" align="right"}\nContenido del modal alineado a la derecha.\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | T\xEDtulo del modal |\n| `label` (o `title`) | texto | Texto del bot\xF3n de apertura (`title` funciona como alias, default: \xABOpen\xBB) |\n| `icon` | nombre Material | Icono del bot\xF3n (default `open_in_new`) |\n| `align` | `left` / `center` / `right` | Alineaci\xF3n del bot\xF3n de apertura (default `left`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Interacci\xF3n\n\n- **Bot\xF3n**: abre el modal (focus se mueve dentro).\n- **Overlay** o bot\xF3n **\xD7**: cierra.\n- **Esc**: cierra (en desktop).\n- `dialog` nativo \u2192 accesible por defecto, focus trapped y `inert` al fondo.'
12947
12939
  },
12948
12940
  {
12949
12941
  "id": "button",
@@ -12951,7 +12943,7 @@ var guideData = [
12951
12943
  "title": "Button",
12952
12944
  "icon": "touch_app",
12953
12945
  "order": 3,
12954
- "md": '# Button\n\nLa directiva `:::button` crea un **bot\xF3n con enlace** (se abre en pesta\xF1a nueva por defecto).\n\n## Sintaxis\n\n```md\n:::button {label="Documentaci\xF3n" url="https://example.com" icon="menu_book"}\n:::\n```\n\n:::button {label="Documentaci\xF3n" url="https://example.com" icon="menu_book"}\n:::\n\n## Variante con enlace interno\n\n```md\n:::button {label="Ir a la p\xE1gina de notas" url="#admonici\xF3n-nota" icon="sticky_note_2" target="_self"}\n:::\n```\n\n:::button {label="Ir a la p\xE1gina de notas" url="#admonici\xF3n-nota" icon="sticky_note_2" target="_self"}\n:::\n\n## Con contenido markdown\n\nSi el bloque contiene texto/enlaces, se renderizan dentro del bot\xF3n:\n\n```md\n:::button {label="Descargar" url="https://example.com/download" icon="download"}\nDescarga el **manual** en PDF\n:::\n```\n\n:::button {label="Descargar" url="https://example.com/download" icon="download"}\nDescarga el **manual** en PDF\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `label` | texto | Texto del bot\xF3n |\n| `url` (o `href`) | URL | Destino del enlace (default `#`) |\n| `icon` | nombre Material | Icono (default `near_me`) |\n| `target` | `_blank` / `_self` / ... | Destino del enlace (default `_blank`) |\n| `class` | texto | Clases CSS adicionales |'
12946
+ "md": '# Button\n\nLa directiva `:::button` crea un **bot\xF3n con enlace** (se abre en pesta\xF1a nueva por defecto).\n\n## Sintaxis\n\n```md\n:::button {label="Documentaci\xF3n" url="https://example.com" icon="menu_book"}\n:::\n```\n\n:::button {label="Documentaci\xF3n" url="https://example.com" icon="menu_book"}\n:::\n\n## Variante con enlace interno\n\n```md\n:::button {label="Ir a la p\xE1gina de notas" url="#admonici\xF3n-nota" icon="sticky_note_2" target="_self"}\n:::\n```\n\n:::button {label="Ir a la p\xE1gina de notas" url="#admonici\xF3n-nota" icon="sticky_note_2" target="_self"}\n:::\n\n## Con contenido markdown\n\nSi el bloque contiene texto/enlaces, se renderizan dentro del bot\xF3n:\n\n```md\n:::button {label="Descargar" url="https://example.com/download" icon="download"}\nDescarga el **manual** en PDF\n:::\n```\n\n:::button {label="Descargar" url="https://example.com/download" icon="download"}\nDescarga el **manual** en PDF\n:::\n\n## Alineaci\xF3n\n\nLos botones se alinean a la izquierda por defecto. Usa el prop `align` para cambiar la alineaci\xF3n:\n\n### Centrado\n\n```md\n:::button {label="Centrado" url="https://example.com" icon="center_focus_strong" align="center"}\n:::\n```\n\n:::button {label="Centrado" url="https://example.com" icon="center_focus_strong" align="center"}\n:::\n\n### Alineado a la derecha\n\n```md\n:::button {label="Derecha" url="https://example.com" icon="arrow_forward" align="right"}\n:::\n```\n\n:::button {label="Derecha" url="https://example.com" icon="arrow_forward" align="right"}\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `label` (o `title`) | texto | Texto del bot\xF3n (`title` funciona como alias por compatibilidad) |\n| `url` (o `href`) | URL | Destino del enlace (default `#`) |\n| `icon` | nombre Material | Icono (default `near_me`) |\n| `target` | `_blank` / `_self` / ... | Destino del enlace (default `_blank`) |\n| `align` | `left` / `center` / `right` | Alineaci\xF3n del bot\xF3n (default `left`) |\n| `class` | texto | Clases CSS adicionales |'
12955
12947
  },
12956
12948
  {
12957
12949
  "id": "slide",