@noirmd/previewer 2.1.4 → 2.1.6

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.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;
@@ -11069,6 +11080,7 @@ function createModal(title) {
11069
11080
  header.appendChild(titleEl);
11070
11081
  const closeBtn = document.createElement("button");
11071
11082
  closeBtn.className = "nr-modal__close";
11083
+ closeBtn.setAttribute("aria-label", "Close");
11072
11084
  closeBtn.appendChild(createIcon("close"));
11073
11085
  closeBtn.addEventListener("click", () => dialog.close());
11074
11086
  header.appendChild(closeBtn);
@@ -11289,11 +11301,89 @@ function parseInlinePart(part) {
11289
11301
  return document.createTextNode(part);
11290
11302
  }
11291
11303
 
11304
+ // vanilla/utils.ts
11305
+ var THEME_TOKENS = /* @__PURE__ */ new Set([
11306
+ "primary",
11307
+ "secondary",
11308
+ "accent",
11309
+ "neutral",
11310
+ "info",
11311
+ "success",
11312
+ "warning",
11313
+ "error"
11314
+ ]);
11315
+ function isThemeToken(color) {
11316
+ return !!color && THEME_TOKENS.has(color);
11317
+ }
11318
+ function isArbitraryColor(value) {
11319
+ if (THEME_TOKENS.has(value)) return false;
11320
+ if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
11321
+ if (/^[a-zA-Z]+$/.test(value)) return true;
11322
+ return false;
11323
+ }
11324
+ function applyBaseProps(el, props) {
11325
+ if (props.class) {
11326
+ el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11327
+ }
11328
+ if (props.style) {
11329
+ const styles = parseCssString(props.style);
11330
+ for (const [key, value] of Object.entries(styles)) {
11331
+ el.style.setProperty(key, String(value));
11332
+ }
11333
+ }
11334
+ }
11335
+ function applyFloatStyle(el, float, width) {
11336
+ if (!float) return;
11337
+ if (float === "left" || float === "right") {
11338
+ el.style.float = float;
11339
+ if (!width) el.style.maxWidth = "50%";
11340
+ el.style.marginInlineStart = float === "right" ? "1rem" : "";
11341
+ el.style.marginInlineEnd = float === "left" ? "1rem" : "";
11342
+ } else if (float === "center") {
11343
+ el.style.marginInline = "auto";
11344
+ }
11345
+ }
11346
+ function applyColor(el, color, classSuffix) {
11347
+ if (!color) return "";
11348
+ if (isThemeToken(color)) {
11349
+ return ` ${classSuffix}--${color}`;
11350
+ }
11351
+ if (isArbitraryColor(color)) {
11352
+ el.style.background = color;
11353
+ el.style.color = "white";
11354
+ }
11355
+ return "";
11356
+ }
11357
+ function openModal(dialog) {
11358
+ if (!dialog.open) {
11359
+ document.body.appendChild(dialog);
11360
+ dialog.showModal();
11361
+ dialog.addEventListener("close", () => dialog.remove(), { once: true });
11362
+ }
11363
+ }
11364
+ var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11365
+ function applyAlignClass(el, baseClass, align) {
11366
+ if (align === "center") {
11367
+ el.classList.add(`${baseClass}--center`);
11368
+ } else if (align === "right") {
11369
+ el.classList.add(`${baseClass}--right`);
11370
+ }
11371
+ }
11372
+ function resolveIcon(value, fallback) {
11373
+ if (!value) return fallback;
11374
+ if (value === "none" || value === "off") return null;
11375
+ return value;
11376
+ }
11377
+ function parseIntProp(value, defaultValue) {
11378
+ if (!value) return defaultValue;
11379
+ const n = parseInt(value, 10);
11380
+ return Number.isNaN(n) ? defaultValue : n;
11381
+ }
11382
+
11292
11383
  // vanilla/directives/admonition.ts
11293
11384
  var admonitionDirective = ({ directiveType, props, renderSlot }) => {
11294
11385
  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);
11386
+ applyBaseProps(el, props);
11297
11387
  const body = el.querySelector(".nr-admonition__body");
11298
11388
  if (body) {
11299
11389
  body.appendChild(renderSlot("default"));
@@ -11309,8 +11399,7 @@ var detailsDirective = ({ props, renderSlot }) => {
11309
11399
  props.icon,
11310
11400
  props.defaultOpen === "true"
11311
11401
  );
11312
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11313
- if (props.style) el.setAttribute("style", props.style);
11402
+ applyBaseProps(el, props);
11314
11403
  const body = el.querySelector(".nr-details__body");
11315
11404
  if (body) {
11316
11405
  body.appendChild(renderSlot("default"));
@@ -11323,14 +11412,17 @@ var details_default = detailsDirective;
11323
11412
  var modalDirective = ({ props, renderSlot }) => {
11324
11413
  const label = props.label || props.title || "Open";
11325
11414
  const modalTitle = props.title || "Modal";
11326
- const customClass = props.class || "";
11415
+ const align = props.align || "left";
11416
+ const iconName = resolveIcon(props.icon, "open_in_full");
11327
11417
  const wrapper = document.createElement("div");
11328
11418
  wrapper.className = "nr-modal-trigger";
11419
+ applyAlignClass(wrapper, "nr-modal-trigger", align);
11329
11420
  const btn = document.createElement("button");
11330
- btn.className = `nr-button nr-button--default`;
11331
- if (customClass) btn.classList.add(...customClass.split(/\s+/).filter(Boolean));
11332
- const icon = props.icon || "open_in_new";
11333
- if (icon) btn.appendChild(createIcon(icon));
11421
+ btn.className = "nr-button nr-button--default";
11422
+ btn.setAttribute("aria-haspopup", "dialog");
11423
+ applyColor(btn, props.color, "nr-button");
11424
+ applyBaseProps(btn, props);
11425
+ if (iconName) btn.appendChild(createIcon(iconName));
11334
11426
  btn.appendChild(document.createTextNode(label));
11335
11427
  const dialog = createModal(modalTitle);
11336
11428
  const body = dialog.querySelector(".nr-modal__body");
@@ -11340,15 +11432,7 @@ var modalDirective = ({ props, renderSlot }) => {
11340
11432
  prose.appendChild(renderSlot("default"));
11341
11433
  body.appendChild(prose);
11342
11434
  }
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
- });
11435
+ btn.addEventListener("click", () => openModal(dialog));
11352
11436
  wrapper.appendChild(btn);
11353
11437
  wrapper.appendChild(dialog);
11354
11438
  return wrapper;
@@ -11358,20 +11442,22 @@ var modal_default = modalDirective;
11358
11442
  // vanilla/directives/button.ts
11359
11443
  var buttonDirective = ({ props, renderSlot }) => {
11360
11444
  const url = props.url || props.href || "#";
11361
- const label = props.label;
11362
- const icon = props.icon || "near_me";
11445
+ const label = props.label || props.title;
11446
+ const iconName = resolveIcon(props.icon, "touch_app");
11363
11447
  const target = props.target || "_blank";
11364
- const customClass = props.class || "";
11448
+ const align = props.align || "left";
11365
11449
  const wrapper = document.createElement("div");
11366
11450
  wrapper.className = "nr-button-wrap";
11451
+ applyAlignClass(wrapper, "nr-button-wrap", align);
11367
11452
  if (label) {
11368
11453
  const a = document.createElement("a");
11369
11454
  a.href = url;
11370
11455
  a.target = target;
11371
- a.rel = "noopener noreferrer";
11456
+ if (target === "_blank") a.rel = "noopener noreferrer";
11372
11457
  a.className = "nr-button nr-button--default";
11373
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11374
- a.appendChild(createIcon(icon));
11458
+ applyColor(a, props.color, "nr-button");
11459
+ applyBaseProps(a, props);
11460
+ if (iconName) a.appendChild(createIcon(iconName));
11375
11461
  a.appendChild(document.createTextNode(label));
11376
11462
  wrapper.appendChild(a);
11377
11463
  return wrapper;
@@ -11381,17 +11467,20 @@ var buttonDirective = ({ props, renderSlot }) => {
11381
11467
  if (links.length > 0) {
11382
11468
  links.forEach((link) => {
11383
11469
  link.classList.add("nr-button", "nr-button--default");
11384
- if (customClass) link.classList.add(...customClass.split(/\s+/).filter(Boolean));
11470
+ applyColor(link, props.color, "nr-button");
11471
+ if (iconName) link.prepend(createIcon(iconName));
11472
+ applyBaseProps(link, props);
11385
11473
  });
11386
11474
  wrapper.appendChild(slotContent);
11387
11475
  } else {
11388
11476
  const a = document.createElement("a");
11389
11477
  a.href = url;
11390
11478
  a.target = target;
11391
- a.rel = "noopener noreferrer";
11479
+ if (target === "_blank") a.rel = "noopener noreferrer";
11392
11480
  a.className = "nr-button nr-button--default";
11393
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11394
- a.appendChild(createIcon(icon));
11481
+ applyColor(a, props.color, "nr-button");
11482
+ applyBaseProps(a, props);
11483
+ if (iconName) a.appendChild(createIcon(iconName));
11395
11484
  a.appendChild(slotContent);
11396
11485
  wrapper.appendChild(a);
11397
11486
  }
@@ -11413,12 +11502,9 @@ var cardDirective = ({
11413
11502
  const { isSingleCard } = options || {};
11414
11503
  const isModal = directiveType === "card-m";
11415
11504
  const isLink = directiveType === "card-b";
11416
- const inlineStyles = props.style ? parseCssString(props.style) : {};
11417
11505
  const card = document.createElement("div");
11418
11506
  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
- }
11507
+ applyBaseProps(card, props);
11422
11508
  if (image) {
11423
11509
  const imgWrap = document.createElement("div");
11424
11510
  imgWrap.className = `nr-card__image${isSingleCard ? " nr-card__image--tall" : ""}`;
@@ -11497,13 +11583,7 @@ var cardDirective = ({
11497
11583
  prose.appendChild(renderSlot("content") || renderSlot("default"));
11498
11584
  modalBody.appendChild(prose);
11499
11585
  }
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
- });
11586
+ card.addEventListener("click", () => openModal(dialog));
11507
11587
  const frag = document.createDocumentFragment();
11508
11588
  frag.appendChild(card);
11509
11589
  frag.appendChild(dialog);
@@ -11551,8 +11631,8 @@ var slideDirective = ({
11551
11631
  if (lines.length === 0) {
11552
11632
  return document.createDocumentFragment();
11553
11633
  }
11554
- const interval = parseInt(props.interval || "3000", 10);
11555
- const speed = parseInt(props.speed || "500", 10);
11634
+ const interval = parseIntProp(props.interval, 3e3);
11635
+ const speed = parseIntProp(props.speed, 500);
11556
11636
  const rawClass = props.class || "";
11557
11637
  const inlineStyle = props.style ? parseCssString(props.style) : {};
11558
11638
  const scopeClass = `sld-${++slideCounter}`;
@@ -11603,10 +11683,11 @@ var slideDirective = ({
11603
11683
  }
11604
11684
  });
11605
11685
  if (lines.length > 1) {
11606
- setInterval(() => {
11686
+ const id = setInterval(() => {
11607
11687
  current = (current + 1) % lines.length;
11608
11688
  track.style.transform = `translateY(${-current * maxH}px)`;
11609
11689
  }, interval);
11690
+ container.dataset.nrIntervalId = String(id);
11610
11691
  }
11611
11692
  return container;
11612
11693
  };
@@ -11616,8 +11697,7 @@ var slide_default = slideDirective;
11616
11697
  var keysDirective = ({ props, slots }) => {
11617
11698
  const wrap = document.createElement("div");
11618
11699
  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);
11700
+ applyBaseProps(wrap, props);
11621
11701
  const sizeClass = props.size ? ` nr-kbd--${props.size}` : "";
11622
11702
  const parts = (slots.default || "").split("+").map((p) => p.trim()).filter(Boolean);
11623
11703
  parts.forEach((part, i) => {
@@ -11641,8 +11721,7 @@ var accordionCounter = 0;
11641
11721
  var accordionItemDirective = ({ props, renderSlot }) => {
11642
11722
  const item = document.createElement("div");
11643
11723
  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);
11724
+ applyBaseProps(item, props);
11646
11725
  const input = document.createElement("input");
11647
11726
  input.type = "radio";
11648
11727
  input.className = "nr-accordion__input";
@@ -11661,8 +11740,7 @@ var accordionItemDirective = ({ props, renderSlot }) => {
11661
11740
  var accordionDirective = ({ props, renderSlot }) => {
11662
11741
  const wrap = document.createElement("div");
11663
11742
  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);
11743
+ applyBaseProps(wrap, props);
11666
11744
  wrap.appendChild(renderSlot("default"));
11667
11745
  const mode = props.mode === "checkbox" ? "checkbox" : "radio";
11668
11746
  const group = `nr-acc-${++accordionCounter}`;
@@ -11675,7 +11753,6 @@ var accordionDirective = ({ props, renderSlot }) => {
11675
11753
  var accordion_default = accordionDirective;
11676
11754
 
11677
11755
  // vanilla/directives/carousel.ts
11678
- var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11679
11756
  var carouselDirective = ({ props, slots }) => {
11680
11757
  const images = [];
11681
11758
  const raw = slots.default || "";
@@ -11691,19 +11768,9 @@ var carouselDirective = ({ props, slots }) => {
11691
11768
  wrap.className = "nr-carousel";
11692
11769
  wrap.tabIndex = 0;
11693
11770
  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);
11771
+ applyBaseProps(wrap, props);
11696
11772
  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
- }
11773
+ applyFloatStyle(wrap, props.float, props.width);
11707
11774
  const viewport = document.createElement("div");
11708
11775
  viewport.className = "nr-carousel__viewport";
11709
11776
  if (props.height) viewport.style.height = props.height;
@@ -11773,11 +11840,10 @@ var DEFAULT_LABELS = ["days", "hours", "min", "sec"];
11773
11840
  var countdownDirective = ({ props }) => {
11774
11841
  const wrap = document.createElement("div");
11775
11842
  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);
11843
+ applyBaseProps(wrap, props);
11778
11844
  const labelParts = (props.labels || "").split("|").map((s) => s.trim());
11779
11845
  const labels = DEFAULT_LABELS.map((label, i) => labelParts[i] || label);
11780
- const digits = parseInt(props.digits || "2", 10);
11846
+ const digits = parseIntProp(props.digits, 2);
11781
11847
  const targetTime = props.target ? new Date(props.target).getTime() : NaN;
11782
11848
  const hasTarget = !Number.isNaN(targetTime);
11783
11849
  const blocks = [];
@@ -11824,13 +11890,15 @@ var countdownDirective = ({ props }) => {
11824
11890
  blocks.push({ value });
11825
11891
  });
11826
11892
  render();
11827
- if (hasTarget) setInterval(render, 1e3);
11893
+ if (hasTarget) {
11894
+ const id = setInterval(render, 1e3);
11895
+ wrap.dataset.nrIntervalId = String(id);
11896
+ }
11828
11897
  return wrap;
11829
11898
  };
11830
11899
  var countdown_default = countdownDirective;
11831
11900
 
11832
11901
  // vanilla/directives/diff.ts
11833
- var IMG_RE2 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11834
11902
  var diffDirective = ({ props, slots }) => {
11835
11903
  let before = (props.before || "").split("#")[0].trim();
11836
11904
  let after = (props.after || "").split("#")[0].trim();
@@ -11838,8 +11906,8 @@ var diffDirective = ({ props, slots }) => {
11838
11906
  const urls = [];
11839
11907
  const raw = slots.default || "";
11840
11908
  let m;
11841
- IMG_RE2.lastIndex = 0;
11842
- while ((m = IMG_RE2.exec(raw)) !== null) {
11909
+ IMG_RE.lastIndex = 0;
11910
+ while ((m = IMG_RE.exec(raw)) !== null) {
11843
11911
  urls.push(m[2].split("#")[0].trim());
11844
11912
  }
11845
11913
  if (!before && urls.length > 0) before = urls[0];
@@ -11852,21 +11920,11 @@ var diffDirective = ({ props, slots }) => {
11852
11920
  figure.className = "nr-diff";
11853
11921
  figure.tabIndex = 0;
11854
11922
  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);
11923
+ applyBaseProps(figure, props);
11857
11924
  if (props.aspect) figure.style.aspectRatio = props.aspect;
11858
11925
  if (props.height) figure.style.height = props.height;
11859
11926
  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
- }
11927
+ applyFloatStyle(figure, props.float, props.width);
11870
11928
  const beforeItem = document.createElement("div");
11871
11929
  beforeItem.className = "nr-diff__item nr-diff__item--before";
11872
11930
  beforeItem.setAttribute("role", "img");
@@ -11929,8 +11987,7 @@ var diff_default = diffDirective;
11929
11987
  var hover3dDirective = ({ props, renderSlot }) => {
11930
11988
  const container = document.createElement("div");
11931
11989
  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);
11990
+ applyBaseProps(container, props);
11934
11991
  const stage = document.createElement("div");
11935
11992
  stage.className = "nr-hover-3d__stage";
11936
11993
  stage.appendChild(renderSlot("default"));
@@ -11943,14 +12000,13 @@ var hover3dDirective = ({ props, renderSlot }) => {
11943
12000
  var hover3d_default = hover3dDirective;
11944
12001
 
11945
12002
  // vanilla/directives/hovergallery.ts
11946
- var IMG_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11947
12003
  var MAX_IMAGES = 10;
11948
12004
  var hovergalleryDirective = ({ props, slots }) => {
11949
12005
  const images = [];
11950
12006
  const raw = slots.default || "";
11951
12007
  let m;
11952
- IMG_RE3.lastIndex = 0;
11953
- while ((m = IMG_RE3.exec(raw)) !== null) {
12008
+ IMG_RE.lastIndex = 0;
12009
+ while ((m = IMG_RE.exec(raw)) !== null) {
11954
12010
  images.push({ src: m[2].split("#")[0].trim(), alt: m[1].trim() || "gallery image" });
11955
12011
  }
11956
12012
  if (images.length === 0) {
@@ -11959,9 +12015,8 @@ var hovergalleryDirective = ({ props, slots }) => {
11959
12015
  const count = Math.min(images.length, MAX_IMAGES);
11960
12016
  const figure = document.createElement("figure");
11961
12017
  figure.className = "nr-hover-gallery";
12018
+ applyBaseProps(figure, props);
11962
12019
  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
12020
  const imgEls = [];
11966
12021
  for (let i = 0; i < count; i++) {
11967
12022
  const el = document.createElement("img");
@@ -12011,28 +12066,11 @@ var hovergalleryDirective = ({ props, slots }) => {
12011
12066
  var hovergallery_default = hovergalleryDirective;
12012
12067
 
12013
12068
  // 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
12069
  var chatItemDirective = ({ props, renderSlot }) => {
12031
12070
  const side = props.side === "end" ? "end" : "start";
12032
12071
  const wrap = document.createElement("div");
12033
12072
  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);
12073
+ applyBaseProps(wrap, props);
12036
12074
  const header = document.createElement("div");
12037
12075
  header.className = "nr-chat__header";
12038
12076
  if (props.name) {
@@ -12057,14 +12095,8 @@ var chatItemDirective = ({ props, renderSlot }) => {
12057
12095
  avatar.appendChild(img);
12058
12096
  wrap.appendChild(avatar);
12059
12097
  }
12060
- const isThemeToken = CHAT_THEME_TOKENS.has(props.color || "");
12061
- const colorClass = isThemeToken ? ` nr-chat__bubble--${props.color}` : "";
12062
12098
  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
- }
12099
+ bubble.className = `nr-chat__bubble${applyColor(bubble, props.color, "nr-chat__bubble")}`;
12068
12100
  bubble.appendChild(renderSlot("default"));
12069
12101
  wrap.appendChild(bubble);
12070
12102
  if (props.footer) {
@@ -12078,8 +12110,7 @@ var chatItemDirective = ({ props, renderSlot }) => {
12078
12110
  var chatDirective = ({ props, renderSlot }) => {
12079
12111
  const wrap = document.createElement("div");
12080
12112
  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);
12113
+ applyBaseProps(wrap, props);
12083
12114
  wrap.appendChild(renderSlot("default"));
12084
12115
  return wrap;
12085
12116
  };
@@ -12115,8 +12146,7 @@ function bindEventProp(el, eventProp) {
12115
12146
  var richlistItemDirective = ({ props, renderSlot }) => {
12116
12147
  const li = document.createElement("li");
12117
12148
  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);
12149
+ applyBaseProps(li, props);
12120
12150
  if (props.image) {
12121
12151
  const thumb = document.createElement("div");
12122
12152
  thumb.className = "nr-richlist__thumb";
@@ -12178,36 +12208,20 @@ var richlistItemDirective = ({ props, renderSlot }) => {
12178
12208
  var richlistDirective = ({ props, renderSlot }) => {
12179
12209
  const ul = document.createElement("ul");
12180
12210
  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);
12211
+ applyBaseProps(ul, props);
12183
12212
  ul.appendChild(renderSlot("default"));
12184
12213
  return ul;
12185
12214
  };
12186
12215
  var richlist_default = richlistDirective;
12187
12216
 
12188
12217
  // 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
12218
  var statDirective = ({ props }) => {
12204
- const isThemeToken = STAT_THEME_TOKENS.has(props.color || "");
12205
- const colorClass = isThemeToken ? ` nr-stat--${props.color}` : "";
12219
+ const statIsThemeToken = isThemeToken(props.color);
12220
+ const colorClass = statIsThemeToken ? ` nr-stat--${props.color}` : "";
12206
12221
  const stat = document.createElement("div");
12207
12222
  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;
12223
+ applyBaseProps(stat, props);
12224
+ const useInlineColor = props.color && isArbitraryColor(props.color) && !statIsThemeToken;
12211
12225
  if (props.icon) {
12212
12226
  const figure = document.createElement("div");
12213
12227
  figure.className = "nr-stat__figure";
@@ -12300,9 +12314,12 @@ function renderHtmlString(html) {
12300
12314
  processedContent = processedContent.replace(
12301
12315
  /<style(?:\s+[^>]*)?>([\s\S]*?)<\/style>/gi,
12302
12316
  (_match, cssContent) => {
12317
+ const trimmed = cssContent.trim();
12318
+ const existing = document.head.querySelector("style[data-nr-global]");
12319
+ if (existing && existing.textContent === trimmed) return "";
12303
12320
  const styleEl = document.createElement("style");
12304
12321
  styleEl.setAttribute("data-nr-global", "");
12305
- styleEl.textContent = cssContent;
12322
+ styleEl.textContent = trimmed;
12306
12323
  document.head.appendChild(styleEl);
12307
12324
  return "";
12308
12325
  }
@@ -12372,19 +12389,14 @@ function renderElement(element, ctx, allElements) {
12372
12389
  switch (element.type) {
12373
12390
  case "header": {
12374
12391
  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
12392
  const h = document.createElement(tag);
12381
12393
  h.id = element.id;
12382
12394
  let cls = `md-h${element.level}`;
12383
- if (alignCenter) cls += " text-center";
12384
- if (alignRight) cls += " text-right";
12395
+ if (element.align === "center") cls += " text-center";
12396
+ else if (element.align === "right") cls += " text-right";
12385
12397
  if (element.classes) cls += ` ${element.classes}`;
12386
12398
  h.className = cls;
12387
- h.appendChild(renderInline(text));
12399
+ h.appendChild(renderInline(element.text));
12388
12400
  return h;
12389
12401
  }
12390
12402
  case "paragraph": {
@@ -12727,7 +12739,7 @@ var guideData = [
12727
12739
  "title": "Introducci\xF3n",
12728
12740
  "icon": "menu_book",
12729
12741
  "order": 1,
12730
- "md": '# Introducci\xF3n a NoirMD\n\n**NoirMD** es un editor y motor de renderizado Markdown con extensiones propias: **admoniciones**, **componentes**, **directivas interactivas** y **markdown enriquecido**.\n\nEsta gu\xEDa est\xE1 escrita con el propio motor: cada directiva que ves aqu\xED es una muestra **viva** y funcional, no una captura.\n\n## C\xF3mo usar el editor\n\n| Elemento | Descripci\xF3n |\n| --- | --- |\n| Toolbar superior | Modo (editor / split / preview), guardar, copiar, imprimir, tema, gu\xEDa y configurar |\n| Panel izquierdo | Editor de c\xF3digo con resaltado de sintaxis |\n| Panel derecho | Preview en vivo (en modo split o preview) |\n| Atajo | `Ctrl+S` guarda el contenido |\n\n## Sintaxis de una directiva\n\nLas directivas se escriben con tres dos puntos `:::` y un nombre, opcionalmente con atributos entre llaves:\n\n```\n:::card {title="Mi tarjeta" icon="star"}\n\nContenido **markdown** aqu\xED dentro.\n\n:::\n```\n\nTodo lo que est\xE1 entre la apertura y el cierre `:::` se renderiza con el mismo motor, as\xED que puedes **anidar** directivas.\n\n## Cheatsheet r\xE1pido\n\n| Sintaxis | Resultado |\n| --- | --- |\n| `# T\xEDtulo` \u2192 `###### T\xEDtulo` | Encabezados |\n| `**negrita**` \xB7 `*cursiva*` \xB7 `~~tachado~~` | \xC9nfasis |\n| `` `c\xF3digo` `` | C\xF3digo inline |\n| `` ```js `` | Bloque de c\xF3digo con resaltado |\n| `[texto](url)` | Enlace |\n| `![alt](url)` | Imagen |\n| `![alt](url#left)` | Imagen flotante a la izquierda |\n| `==resaltado==` | Resaltado |\n| `%color%texto%%` | Texto de color |\n| `->centrado<-` | Texto centrado |\n| `!>spoiler<!` | Spoiler oculto |\n| `|[[icono]]|` | Icono Material |\n| `[TOC]` | \xCDndice de contenidos |\n| `:::note` `:::warning` `:::danger` `:::info` `:::greentext` | Admoniciones |\n| `:::card` `:::accordion` `:::carousel` `:::diff` `:::chat` `:::stat` `:::countdown` `:::keys` `:::hover-3d` `:::hover-gallery` `:::richlist` | Componentes |\n| `:::details` `:::modal` `:::button` `:::slide` | Interactivos |\n| `<style>` HTML inline | Bloques HTML (CSS global, HTML crudo) |\n\n## Organizaci\xF3n de la gu\xEDa\n\n- **Markdown** \u2014 sintaxis base y enriquecida (t\xEDtulos, \xE9nfasis, tablas, c\xF3digo, im\xE1genes, inline).\n- **Admoniciones** \u2014 cajas de aviso: nota, warning, danger, info y greentext.\n- **Componentes** \u2014 los 10 componentes de tarjeta, teclas, acorde\xF3n, carrusel, etc.\n- **Interactivos** \u2014 details, modal, botones y slides.\n- **Layout** \u2014 bloques HTML crudo: CSS global con `<style>` e HTML inline.\n\nCada p\xE1gina incluye: la sintaxis exacta, la tabla de props, un ejemplo en vivo y el c\xF3digo fuente para copiar.'
12742
+ "md": '# Introducci\xF3n a NoirMD\n\n**NoirMD** es un editor y motor de renderizado Markdown con extensiones propias: **admoniciones**, **componentes**, **directivas interactivas** y **markdown enriquecido**.\n\nEsta gu\xEDa est\xE1 escrita con el propio motor: cada directiva que ves aqu\xED es una muestra **viva** y funcional, no una captura.\n\n## C\xF3mo usar el editor\n\n| Elemento | Descripci\xF3n |\n| --- | --- |\n| Toolbar superior | Modo (editor / split / preview), guardar, copiar, imprimir, tema, gu\xEDa y configurar |\n| Panel izquierdo | Editor de c\xF3digo con resaltado de sintaxis |\n| Panel derecho | Preview en vivo (en modo split o preview) |\n| Atajo | `Ctrl+S` guarda el contenido |\n\n## Sintaxis de una directiva\n\nLas directivas se escriben con tres dos puntos `:::` y un nombre, opcionalmente con atributos entre llaves:\n\n```\n:::card {title="Mi tarjeta" icon="star"}\n\nContenido **markdown** aqu\xED dentro.\n\n:::\n```\n\nTodo lo que est\xE1 entre la apertura y el cierre `:::` se renderiza con el mismo motor, as\xED que puedes **anidar** directivas.\n\n## Cheatsheet r\xE1pido\n\n| Sintaxis | Resultado |\n| --- | --- |\n| `# T\xEDtulo` \u2192 `###### T\xEDtulo` | Encabezados |\n| `**negrita**` \xB7 `_cursiva_` \xB7 `~~tachado~~` | \xC9nfasis |\n| `` `c\xF3digo` `` | C\xF3digo inline |\n| `` ```js `` | Bloque de c\xF3digo con resaltado |\n| `[texto](url)` | Enlace |\n| `![alt](url)` | Imagen |\n| `![alt](url#left)` | Imagen flotante a la izquierda |\n| `==resaltado==` | Resaltado |\n| `%color%texto%%` | Texto de color |\n| `->centrado<-` | Texto centrado |\n| `!>spoiler<!` | Spoiler oculto |\n| `|[[icono]]|` | Icono Material |\n| `[TOC]` | \xCDndice de contenidos |\n| `:::note` `:::warning` `:::danger` `:::info` `:::greentext` | Admoniciones |\n| `:::card` `:::accordion` `:::carousel` `:::diff` `:::chat` `:::stat` `:::countdown` `:::keys` `:::hover-3d` `:::hover-gallery` `:::richlist` | Componentes |\n| `:::details` `:::modal` `:::button` `:::slide` | Interactivos |\n| `<style>` HTML inline | Bloques HTML (CSS global, HTML crudo) |\n\n## Organizaci\xF3n de la gu\xEDa\n\n- **Markdown** \u2014 sintaxis base y enriquecida (t\xEDtulos, \xE9nfasis, tablas, c\xF3digo, im\xE1genes, inline).\n- **Admoniciones** \u2014 cajas de aviso: nota, warning, danger, info y greentext.\n- **Componentes** \u2014 los 10 componentes de tarjeta, teclas, acorde\xF3n, carrusel, etc.\n- **Interactivos** \u2014 details, modal, botones y slides.\n- **Layout** \u2014 bloques HTML crudo: CSS global con `<style>` e HTML inline.\n\nCada p\xE1gina incluye: la sintaxis exacta, la tabla de props, un ejemplo en vivo y el c\xF3digo fuente para copiar.'
12731
12743
  },
12732
12744
  {
12733
12745
  "id": "titulos",
@@ -12943,7 +12955,7 @@ var guideData = [
12943
12955
  "title": "Modal",
12944
12956
  "icon": "open_in_full",
12945
12957
  "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.'
12958
+ "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## Sin icono\n\nPara ocultar el icono del bot\xF3n de apertura, usa `icon="none"`:\n\n```md\n:::modal {title="Sin icono" label="Abrir" icon="none"}\nContenido del modal.\n:::\n```\n\n:::modal {title="Sin icono" label="Abrir" icon="none"}\nContenido del modal.\n:::\n\n## Color\n\nEl bot\xF3n de apertura acepta tokens de tema (`primary`, `secondary`, `info`, `success`, `warning`, `error`) o colores CSS arbitrarios:\n\n```md\n:::modal {title="\xC9xito" label="Abrir" icon="check_circle" color="success"}\nAcci\xF3n completada correctamente.\n:::\n```\n\n:::modal {title="\xC9xito" label="Abrir" icon="check_circle" color="success"}\nAcci\xF3n completada correctamente.\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_full`). Usa `icon="none"` para ocultar |\n| `color` | token de tema o CSS | Color del bot\xF3n de apertura |\n| `align` | `left` / `center` / `right` | Alineaci\xF3n del bot\xF3n de apertura (default `left`) |\n| `class` | texto | Clases CSS adicionales (se aplican al bot\xF3n de apertura) |\n| `style` | CSS | Estilos inline (se aplican al bot\xF3n de apertura) |\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
12959
  },
12948
12960
  {
12949
12961
  "id": "button",
@@ -12951,7 +12963,7 @@ var guideData = [
12951
12963
  "title": "Button",
12952
12964
  "icon": "touch_app",
12953
12965
  "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 |'
12966
+ "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. El icono se a\xF1ade autom\xE1ticamente a cada enlace del contenido:\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## Con enlace en el contenido\n\nSi el contenido es un enlace markdown, el bot\xF3n usa el enlace del contenido y el icono se a\xF1ade al principio:\n\n```md\n:::button {icon="star"}\n[Descarga con FDM](https://example.com/download)\n:::\n```\n\n:::button {icon="star"}\n[Descarga con FDM](https://example.com/download)\n:::\n\n## Sin icono\n\nPara ocultar el icono, usa `icon="none"`:\n\n```md\n:::button {label="Sin icono" url="https://example.com" icon="none"}\n:::\n```\n\n:::button {label="Sin icono" url="https://example.com" icon="none"}\n:::\n\n## Color\n\nLos botones aceptan tokens de tema (`primary`, `secondary`, `info`, `success`, `warning`, `error`) o colores CSS arbitrarios (`red`, `#ff0000`, `rgb(255,0,0)`):\n\n```md\n:::button {label="\xC9xito" url="https://example.com" icon="check_circle" color="success"}\n:::\n```\n\n:::button {label="\xC9xito" url="https://example.com" icon="check_circle" color="success"}\n:::\n\n```md\n:::button {label="Rojo" url="https://example.com" icon="error" color="#e11d48"}\n:::\n```\n\n:::button {label="Rojo" url="https://example.com" icon="error" color="#e11d48"}\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 `touch_app`). Usa `icon="none"` para ocultar |\n| `target` | `_blank` / `_self` / ... | Destino del enlace (default `_blank`) |\n| `color` | token de tema o CSS | Color del bot\xF3n (ver colores soportados arriba) |\n| `align` | `left` / `center` / `right` | Alineaci\xF3n del bot\xF3n (default `left`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline (ej: `style="font-size:1rem"`) |'
12955
12967
  },
12956
12968
  {
12957
12969
  "id": "slide",
@@ -12968,6 +12980,14 @@ var guideData = [
12968
12980
  "icon": "code_off",
12969
12981
  "order": 1,
12970
12982
  "md": '# Bloques HTML\r\n\r\nNoirMD permite escribir **HTML crudo** directamente en el markdown. Seg\xFAn la etiqueta, el comportamiento var\xEDa.\r\n\r\n## CSS global con `<style>`\r\n\r\nEscribe un bloque `<style>` para inyectar CSS en el documento. El contenido se extrae autom\xE1ticamente y se inserta en el `<head>` del DOM.\r\n\r\n```md\r\n<style>\r\n.mi-clase {\r\n background: #f1f5f9;\r\n border-radius: 10px;\r\n padding: 1rem;\r\n}\r\n</style>\r\n```\r\n\r\n<style>\r\n.nr-demo-box {\r\n display: grid;\r\n grid-template-columns: 1fr 1fr;\r\n gap: 1rem;\r\n padding: 1rem;\r\n border-radius: 12px;\r\n background: color-mix(in srgb, var(--color-accent-primary, #0ea5e9) 10%, transparent);\r\n}\r\n.nr-demo-box > div {\r\n padding: 1rem;\r\n border-radius: 8px;\r\n background: var(--color-background-secondary-solid, #1e293b);\r\n}\r\n</style>\r\n\r\n<div class="nr-demo-box">\r\n<div>**A**</div>\r\n<div>**B**</div>\r\n</div>\r\n\r\n> El CSS se aplica al **documento renderizado completo**, no solo al bloque. Define clases una vez y \xFAsalas despu\xE9s en cualquier etiqueta HTML.\r\n\r\n## HTML inline\r\n\r\nCualquier etiqueta HTML escrita directamente en el markdown se renderiza sin procesar como markdown. El contenido dentro se preserva tal cual.\r\n\r\n```md\r\n<div style="text-align: center; padding: 1rem; border: 1px solid #334155; border-radius: 10px;">\r\n HTML escrito a mano funciona tal cual.\r\n</div>\r\n```\r\n\r\n<div style="text-align: center; padding: 1rem; border: 1px solid #334155; border-radius: 10px;">\r\n HTML escrito a mano funciona tal cual.\r\n</div>\r\n\r\n## Elementos interactivos nativos\r\n\r\n```md\r\n<details class="nr-details">\r\n <summary>Detalle nativo con <b>HTML</b></summary>\r\n <p>Los atributos, estilos y eventos se conservan intactos.</p>\r\n</details>\r\n```\r\n\r\n<details class="nr-details">\r\n <summary>Detalle nativo con <b>HTML</b></summary>\r\n <p>Los atributos, estilos y eventos se conservan intactos.</p>\r\n</details>\r\n\r\n## Scripts\r\n\r\nLos bloques `<script>` se ejecutan autom\xE1ticamente al renderizar:\r\n\r\n```md\r\n<script>\r\n console.log(\'Este script se ejecuta al renderizar\');\r\n</script>\r\n```\r\n\r\n> \u26A0\uFE0F Al ser HTML y scripts sin filtrar, \xFAsalos solo con contenido de confianza.\r\n\r\n## Cu\xE1ndo usar bloques HTML\r\n\r\n- Insertar embeds (`iframe`, `video`, widgets externos).\r\n- Estructuras que el markdown no cubre (layouts complejos, formularios nativos).\r\n- Inyectar CSS reutilizable con `<style>`.\r\n- Prototipar HTML antes de convertirlo a directiva.'
12983
+ },
12984
+ {
12985
+ "id": "wrapper-directives",
12986
+ "category": "Layout",
12987
+ "title": "Wrapper Directives",
12988
+ "icon": "crop_free",
12989
+ "order": 1,
12990
+ "md": '# Wrapper Directives\r\n\r\nLas directivas `:::div`, `:::style`, `:::custom` y `:::raw` son wrappers gen\xE9ricos para **envolver contenido** con clases, estilos y atributos personalizados.\r\n\r\n## `:::div` \u2014 Div gen\xE9rico\r\n\r\nEnvuelve contenido en un `<div>` con clases, id, estilos y atributos `data-*`.\r\n\r\n```md\r\n:::div {class="mi-clase" id="seccion" style="padding: 2rem; background: #f0f0f0"}\r\n\r\nContenido **markdown** aqu\xED.\r\n\r\n:::\r\n```\r\n\r\n:::div {class="mi-clase" id="seccion" style="padding: 2rem; background: #f0f0f0"}\r\n\r\nContenido **markdown** aqu\xED.\r\n\r\n:::\r\n\r\n## `:::style` \u2014 Inyectar CSS\r\n\r\nInyecta un bloque `<style>` global. \xDAtil para estilos que afectan m\xFAltiples componentes.\r\n\r\n```md\r\n:::style\r\n.nr-mi-clase { color: red; }\r\n:::\r\n```\r\n\r\n## `:::custom` \u2014 Elemento personalizado\r\n\r\nSimilar a `div`, pero permite crear cualquier elemento HTML.\r\n\r\n## `:::raw` \u2014 HTML crudo\r\n\r\nRenderiza contenido HTML sin procesar.\r\n\r\n## Props\r\n\r\n| Prop | Tipo | Descripci\xF3n |\r\n| --- | --- | --- |\r\n| `class` | texto | Clases CSS (soporta `.shorthand` tambi\xE9n) |\r\n| `id` | texto | ID del elemento |\r\n| `style` | CSS inline | Estilos inline (se aplica con `setProperty`, no sobreescribe otros estilos) |\r\n| `data-*` | texto | Cualquier atributo `data-*` se aplica al elemento |\r\n\r\n## Shorthands\r\n\r\n```md\r\n:::div {.mi-clase #mi-id}\r\n\r\nIgual que usar `class="mi-clase" id="mi-id"`.\r\n\r\n:::\r\n```'
12971
12991
  }
12972
12992
  ];
12973
12993