@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.js CHANGED
@@ -1653,6 +1653,22 @@ function parseHtmlAttrs(attrsString) {
1653
1653
  }
1654
1654
 
1655
1655
  // core/parser.ts
1656
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
1657
+ "area",
1658
+ "base",
1659
+ "br",
1660
+ "col",
1661
+ "embed",
1662
+ "hr",
1663
+ "img",
1664
+ "input",
1665
+ "link",
1666
+ "meta",
1667
+ "param",
1668
+ "source",
1669
+ "track",
1670
+ "wbr"
1671
+ ]);
1656
1672
  function parseMarkdown(markdown2) {
1657
1673
  if (!markdown2) return [];
1658
1674
  const lines = markdown2.replace(/\r\n/g, "\n").replace(/\r/g, "").split("\n");
@@ -1666,8 +1682,19 @@ function parseMarkdown(markdown2) {
1666
1682
  if (match = trimmed.match(/^(#{1,6})\s+(.+)$/)) {
1667
1683
  const level = match[1].length;
1668
1684
  const rawText = match[2];
1669
- const { text: text2, classes: classes2, id: customId } = extractAttributes(rawText);
1670
- const baseId = customId || generateId(text2.replace(/->|<-/g, ""));
1685
+ const { text: rawParsedText, classes: classes2, id: customId } = extractAttributes(rawText);
1686
+ let text2 = rawParsedText;
1687
+ let align;
1688
+ const alignCenter = text2.match(/^->\s*(.+?)\s*<-\s*$/);
1689
+ const alignRight = text2.match(/^->\s*(.+?)\s*->\s*$/);
1690
+ if (alignCenter) {
1691
+ text2 = alignCenter[1];
1692
+ align = "center";
1693
+ } else if (alignRight) {
1694
+ text2 = alignRight[1];
1695
+ align = "right";
1696
+ }
1697
+ const baseId = customId || generateId(text2);
1671
1698
  let id2 = baseId;
1672
1699
  let n = 1;
1673
1700
  while (usedIds.has(id2)) {
@@ -1675,7 +1702,7 @@ function parseMarkdown(markdown2) {
1675
1702
  id2 = `${baseId}-${n}`;
1676
1703
  }
1677
1704
  usedIds.add(id2);
1678
- result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0 });
1705
+ result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0, align });
1679
1706
  i++;
1680
1707
  continue;
1681
1708
  }
@@ -1851,29 +1878,13 @@ function parseMarkdown(markdown2) {
1851
1878
  let tagStartMatch = trimmed.match(/^<([a-zA-Z][\w-]*)/);
1852
1879
  if (tagStartMatch) {
1853
1880
  const tagName = tagStartMatch[1].toLowerCase();
1854
- const voidElements = /* @__PURE__ */ new Set([
1855
- "area",
1856
- "base",
1857
- "br",
1858
- "col",
1859
- "embed",
1860
- "hr",
1861
- "img",
1862
- "input",
1863
- "link",
1864
- "meta",
1865
- "param",
1866
- "source",
1867
- "track",
1868
- "wbr"
1869
- ]);
1870
1881
  const remainingText = lines.slice(i).join("\n");
1871
1882
  const openTagRegex = new RegExp(`^\\s*<${tagName}\\b([^>]*?)>`, "i");
1872
1883
  const openTagMatch = remainingText.match(openTagRegex);
1873
1884
  if (openTagMatch) {
1874
1885
  const fullOpenTag = openTagMatch[0];
1875
1886
  const attrs = openTagMatch[1].replace(/\s+/g, " ").trim();
1876
- const isSelfClosing = fullOpenTag.endsWith("/>") || voidElements.has(tagName);
1887
+ const isSelfClosing = fullOpenTag.endsWith("/>") || VOID_ELEMENTS.has(tagName);
1877
1888
  if (isSelfClosing) {
1878
1889
  const blockText = remainingText.substring(0, openTagMatch.index + fullOpenTag.length);
1879
1890
  const consumedLines = blockText.split("\n").length;
@@ -11047,6 +11058,7 @@ function createModal(title) {
11047
11058
  header.appendChild(titleEl);
11048
11059
  const closeBtn = document.createElement("button");
11049
11060
  closeBtn.className = "nr-modal__close";
11061
+ closeBtn.setAttribute("aria-label", "Close");
11050
11062
  closeBtn.appendChild(createIcon("close"));
11051
11063
  closeBtn.addEventListener("click", () => dialog.close());
11052
11064
  header.appendChild(closeBtn);
@@ -11267,11 +11279,89 @@ function parseInlinePart(part) {
11267
11279
  return document.createTextNode(part);
11268
11280
  }
11269
11281
 
11282
+ // vanilla/utils.ts
11283
+ var THEME_TOKENS = /* @__PURE__ */ new Set([
11284
+ "primary",
11285
+ "secondary",
11286
+ "accent",
11287
+ "neutral",
11288
+ "info",
11289
+ "success",
11290
+ "warning",
11291
+ "error"
11292
+ ]);
11293
+ function isThemeToken(color) {
11294
+ return !!color && THEME_TOKENS.has(color);
11295
+ }
11296
+ function isArbitraryColor(value) {
11297
+ if (THEME_TOKENS.has(value)) return false;
11298
+ if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
11299
+ if (/^[a-zA-Z]+$/.test(value)) return true;
11300
+ return false;
11301
+ }
11302
+ function applyBaseProps(el, props) {
11303
+ if (props.class) {
11304
+ el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11305
+ }
11306
+ if (props.style) {
11307
+ const styles = parseCssString(props.style);
11308
+ for (const [key, value] of Object.entries(styles)) {
11309
+ el.style.setProperty(key, String(value));
11310
+ }
11311
+ }
11312
+ }
11313
+ function applyFloatStyle(el, float, width) {
11314
+ if (!float) return;
11315
+ if (float === "left" || float === "right") {
11316
+ el.style.float = float;
11317
+ if (!width) el.style.maxWidth = "50%";
11318
+ el.style.marginInlineStart = float === "right" ? "1rem" : "";
11319
+ el.style.marginInlineEnd = float === "left" ? "1rem" : "";
11320
+ } else if (float === "center") {
11321
+ el.style.marginInline = "auto";
11322
+ }
11323
+ }
11324
+ function applyColor(el, color, classSuffix) {
11325
+ if (!color) return "";
11326
+ if (isThemeToken(color)) {
11327
+ return ` ${classSuffix}--${color}`;
11328
+ }
11329
+ if (isArbitraryColor(color)) {
11330
+ el.style.background = color;
11331
+ el.style.color = "white";
11332
+ }
11333
+ return "";
11334
+ }
11335
+ function openModal(dialog) {
11336
+ if (!dialog.open) {
11337
+ document.body.appendChild(dialog);
11338
+ dialog.showModal();
11339
+ dialog.addEventListener("close", () => dialog.remove(), { once: true });
11340
+ }
11341
+ }
11342
+ var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11343
+ function applyAlignClass(el, baseClass, align) {
11344
+ if (align === "center") {
11345
+ el.classList.add(`${baseClass}--center`);
11346
+ } else if (align === "right") {
11347
+ el.classList.add(`${baseClass}--right`);
11348
+ }
11349
+ }
11350
+ function resolveIcon(value, fallback) {
11351
+ if (!value) return fallback;
11352
+ if (value === "none" || value === "off") return null;
11353
+ return value;
11354
+ }
11355
+ function parseIntProp(value, defaultValue) {
11356
+ if (!value) return defaultValue;
11357
+ const n = parseInt(value, 10);
11358
+ return Number.isNaN(n) ? defaultValue : n;
11359
+ }
11360
+
11270
11361
  // vanilla/directives/admonition.ts
11271
11362
  var admonitionDirective = ({ directiveType, props, renderSlot }) => {
11272
11363
  const el = createAdmonition(directiveType, props.title, props.icon);
11273
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11274
- if (props.style) el.setAttribute("style", props.style);
11364
+ applyBaseProps(el, props);
11275
11365
  const body = el.querySelector(".nr-admonition__body");
11276
11366
  if (body) {
11277
11367
  body.appendChild(renderSlot("default"));
@@ -11287,8 +11377,7 @@ var detailsDirective = ({ props, renderSlot }) => {
11287
11377
  props.icon,
11288
11378
  props.defaultOpen === "true"
11289
11379
  );
11290
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11291
- if (props.style) el.setAttribute("style", props.style);
11380
+ applyBaseProps(el, props);
11292
11381
  const body = el.querySelector(".nr-details__body");
11293
11382
  if (body) {
11294
11383
  body.appendChild(renderSlot("default"));
@@ -11301,14 +11390,17 @@ var details_default = detailsDirective;
11301
11390
  var modalDirective = ({ props, renderSlot }) => {
11302
11391
  const label = props.label || props.title || "Open";
11303
11392
  const modalTitle = props.title || "Modal";
11304
- const customClass = props.class || "";
11393
+ const align = props.align || "left";
11394
+ const iconName = resolveIcon(props.icon, "open_in_full");
11305
11395
  const wrapper = document.createElement("div");
11306
11396
  wrapper.className = "nr-modal-trigger";
11397
+ applyAlignClass(wrapper, "nr-modal-trigger", align);
11307
11398
  const btn = document.createElement("button");
11308
- btn.className = `nr-button nr-button--default`;
11309
- if (customClass) btn.classList.add(...customClass.split(/\s+/).filter(Boolean));
11310
- const icon = props.icon || "open_in_new";
11311
- if (icon) btn.appendChild(createIcon(icon));
11399
+ btn.className = "nr-button nr-button--default";
11400
+ btn.setAttribute("aria-haspopup", "dialog");
11401
+ applyColor(btn, props.color, "nr-button");
11402
+ applyBaseProps(btn, props);
11403
+ if (iconName) btn.appendChild(createIcon(iconName));
11312
11404
  btn.appendChild(document.createTextNode(label));
11313
11405
  const dialog = createModal(modalTitle);
11314
11406
  const body = dialog.querySelector(".nr-modal__body");
@@ -11318,15 +11410,7 @@ var modalDirective = ({ props, renderSlot }) => {
11318
11410
  prose.appendChild(renderSlot("default"));
11319
11411
  body.appendChild(prose);
11320
11412
  }
11321
- btn.addEventListener("click", () => {
11322
- if (!dialog.open) {
11323
- document.body.appendChild(dialog);
11324
- dialog.showModal();
11325
- dialog.addEventListener("close", () => {
11326
- dialog.remove();
11327
- }, { once: true });
11328
- }
11329
- });
11413
+ btn.addEventListener("click", () => openModal(dialog));
11330
11414
  wrapper.appendChild(btn);
11331
11415
  wrapper.appendChild(dialog);
11332
11416
  return wrapper;
@@ -11336,20 +11420,22 @@ var modal_default = modalDirective;
11336
11420
  // vanilla/directives/button.ts
11337
11421
  var buttonDirective = ({ props, renderSlot }) => {
11338
11422
  const url = props.url || props.href || "#";
11339
- const label = props.label;
11340
- const icon = props.icon || "near_me";
11423
+ const label = props.label || props.title;
11424
+ const iconName = resolveIcon(props.icon, "touch_app");
11341
11425
  const target = props.target || "_blank";
11342
- const customClass = props.class || "";
11426
+ const align = props.align || "left";
11343
11427
  const wrapper = document.createElement("div");
11344
11428
  wrapper.className = "nr-button-wrap";
11429
+ applyAlignClass(wrapper, "nr-button-wrap", align);
11345
11430
  if (label) {
11346
11431
  const a = document.createElement("a");
11347
11432
  a.href = url;
11348
11433
  a.target = target;
11349
- a.rel = "noopener noreferrer";
11434
+ if (target === "_blank") a.rel = "noopener noreferrer";
11350
11435
  a.className = "nr-button nr-button--default";
11351
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11352
- a.appendChild(createIcon(icon));
11436
+ applyColor(a, props.color, "nr-button");
11437
+ applyBaseProps(a, props);
11438
+ if (iconName) a.appendChild(createIcon(iconName));
11353
11439
  a.appendChild(document.createTextNode(label));
11354
11440
  wrapper.appendChild(a);
11355
11441
  return wrapper;
@@ -11359,17 +11445,20 @@ var buttonDirective = ({ props, renderSlot }) => {
11359
11445
  if (links.length > 0) {
11360
11446
  links.forEach((link) => {
11361
11447
  link.classList.add("nr-button", "nr-button--default");
11362
- if (customClass) link.classList.add(...customClass.split(/\s+/).filter(Boolean));
11448
+ applyColor(link, props.color, "nr-button");
11449
+ if (iconName) link.prepend(createIcon(iconName));
11450
+ applyBaseProps(link, props);
11363
11451
  });
11364
11452
  wrapper.appendChild(slotContent);
11365
11453
  } else {
11366
11454
  const a = document.createElement("a");
11367
11455
  a.href = url;
11368
11456
  a.target = target;
11369
- a.rel = "noopener noreferrer";
11457
+ if (target === "_blank") a.rel = "noopener noreferrer";
11370
11458
  a.className = "nr-button nr-button--default";
11371
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11372
- a.appendChild(createIcon(icon));
11459
+ applyColor(a, props.color, "nr-button");
11460
+ applyBaseProps(a, props);
11461
+ if (iconName) a.appendChild(createIcon(iconName));
11373
11462
  a.appendChild(slotContent);
11374
11463
  wrapper.appendChild(a);
11375
11464
  }
@@ -11391,12 +11480,9 @@ var cardDirective = ({
11391
11480
  const { isSingleCard } = options || {};
11392
11481
  const isModal = directiveType === "card-m";
11393
11482
  const isLink = directiveType === "card-b";
11394
- const inlineStyles = props.style ? parseCssString(props.style) : {};
11395
11483
  const card = document.createElement("div");
11396
11484
  card.className = `nr-card${isModal || isLink ? " nr-card--interactive" : ""} ${customClass}`.trim();
11397
- for (const [key, value] of Object.entries(inlineStyles)) {
11398
- card.style.setProperty(key, String(value));
11399
- }
11485
+ applyBaseProps(card, props);
11400
11486
  if (image) {
11401
11487
  const imgWrap = document.createElement("div");
11402
11488
  imgWrap.className = `nr-card__image${isSingleCard ? " nr-card__image--tall" : ""}`;
@@ -11475,13 +11561,7 @@ var cardDirective = ({
11475
11561
  prose.appendChild(renderSlot("content") || renderSlot("default"));
11476
11562
  modalBody.appendChild(prose);
11477
11563
  }
11478
- card.addEventListener("click", () => {
11479
- if (!dialog.open) {
11480
- document.body.appendChild(dialog);
11481
- dialog.showModal();
11482
- dialog.addEventListener("close", () => dialog.remove(), { once: true });
11483
- }
11484
- });
11564
+ card.addEventListener("click", () => openModal(dialog));
11485
11565
  const frag = document.createDocumentFragment();
11486
11566
  frag.appendChild(card);
11487
11567
  frag.appendChild(dialog);
@@ -11529,8 +11609,8 @@ var slideDirective = ({
11529
11609
  if (lines.length === 0) {
11530
11610
  return document.createDocumentFragment();
11531
11611
  }
11532
- const interval = parseInt(props.interval || "3000", 10);
11533
- const speed = parseInt(props.speed || "500", 10);
11612
+ const interval = parseIntProp(props.interval, 3e3);
11613
+ const speed = parseIntProp(props.speed, 500);
11534
11614
  const rawClass = props.class || "";
11535
11615
  const inlineStyle = props.style ? parseCssString(props.style) : {};
11536
11616
  const scopeClass = `sld-${++slideCounter}`;
@@ -11581,10 +11661,11 @@ var slideDirective = ({
11581
11661
  }
11582
11662
  });
11583
11663
  if (lines.length > 1) {
11584
- setInterval(() => {
11664
+ const id = setInterval(() => {
11585
11665
  current = (current + 1) % lines.length;
11586
11666
  track.style.transform = `translateY(${-current * maxH}px)`;
11587
11667
  }, interval);
11668
+ container.dataset.nrIntervalId = String(id);
11588
11669
  }
11589
11670
  return container;
11590
11671
  };
@@ -11594,8 +11675,7 @@ var slide_default = slideDirective;
11594
11675
  var keysDirective = ({ props, slots }) => {
11595
11676
  const wrap = document.createElement("div");
11596
11677
  wrap.className = "nr-keys";
11597
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11598
- if (props.style) wrap.setAttribute("style", props.style);
11678
+ applyBaseProps(wrap, props);
11599
11679
  const sizeClass = props.size ? ` nr-kbd--${props.size}` : "";
11600
11680
  const parts = (slots.default || "").split("+").map((p) => p.trim()).filter(Boolean);
11601
11681
  parts.forEach((part, i) => {
@@ -11619,8 +11699,7 @@ var accordionCounter = 0;
11619
11699
  var accordionItemDirective = ({ props, renderSlot }) => {
11620
11700
  const item = document.createElement("div");
11621
11701
  item.className = "nr-accordion__item";
11622
- if (props.class) item.classList.add(...props.class.split(/\s+/).filter(Boolean));
11623
- if (props.style) item.setAttribute("style", props.style);
11702
+ applyBaseProps(item, props);
11624
11703
  const input = document.createElement("input");
11625
11704
  input.type = "radio";
11626
11705
  input.className = "nr-accordion__input";
@@ -11639,8 +11718,7 @@ var accordionItemDirective = ({ props, renderSlot }) => {
11639
11718
  var accordionDirective = ({ props, renderSlot }) => {
11640
11719
  const wrap = document.createElement("div");
11641
11720
  wrap.className = "nr-accordion";
11642
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11643
- if (props.style) wrap.setAttribute("style", props.style);
11721
+ applyBaseProps(wrap, props);
11644
11722
  wrap.appendChild(renderSlot("default"));
11645
11723
  const mode = props.mode === "checkbox" ? "checkbox" : "radio";
11646
11724
  const group = `nr-acc-${++accordionCounter}`;
@@ -11653,7 +11731,6 @@ var accordionDirective = ({ props, renderSlot }) => {
11653
11731
  var accordion_default = accordionDirective;
11654
11732
 
11655
11733
  // vanilla/directives/carousel.ts
11656
- var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11657
11734
  var carouselDirective = ({ props, slots }) => {
11658
11735
  const images = [];
11659
11736
  const raw = slots.default || "";
@@ -11669,19 +11746,9 @@ var carouselDirective = ({ props, slots }) => {
11669
11746
  wrap.className = "nr-carousel";
11670
11747
  wrap.tabIndex = 0;
11671
11748
  wrap.setAttribute("aria-label", "Image carousel");
11672
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11673
- if (props.style) wrap.setAttribute("style", props.style);
11749
+ applyBaseProps(wrap, props);
11674
11750
  if (props.width) wrap.style.width = props.width;
11675
- if (props.float) {
11676
- if (props.float === "left" || props.float === "right") {
11677
- wrap.style.float = props.float;
11678
- if (!props.width) wrap.style.maxWidth = "50%";
11679
- wrap.style.marginInlineStart = props.float === "right" ? "1rem" : "";
11680
- wrap.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
11681
- } else if (props.float === "center") {
11682
- wrap.style.marginInline = "auto";
11683
- }
11684
- }
11751
+ applyFloatStyle(wrap, props.float, props.width);
11685
11752
  const viewport = document.createElement("div");
11686
11753
  viewport.className = "nr-carousel__viewport";
11687
11754
  if (props.height) viewport.style.height = props.height;
@@ -11751,11 +11818,10 @@ var DEFAULT_LABELS = ["days", "hours", "min", "sec"];
11751
11818
  var countdownDirective = ({ props }) => {
11752
11819
  const wrap = document.createElement("div");
11753
11820
  wrap.className = "nr-countdown";
11754
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11755
- if (props.style) wrap.setAttribute("style", props.style);
11821
+ applyBaseProps(wrap, props);
11756
11822
  const labelParts = (props.labels || "").split("|").map((s) => s.trim());
11757
11823
  const labels = DEFAULT_LABELS.map((label, i) => labelParts[i] || label);
11758
- const digits = parseInt(props.digits || "2", 10);
11824
+ const digits = parseIntProp(props.digits, 2);
11759
11825
  const targetTime = props.target ? new Date(props.target).getTime() : NaN;
11760
11826
  const hasTarget = !Number.isNaN(targetTime);
11761
11827
  const blocks = [];
@@ -11802,13 +11868,15 @@ var countdownDirective = ({ props }) => {
11802
11868
  blocks.push({ value });
11803
11869
  });
11804
11870
  render();
11805
- if (hasTarget) setInterval(render, 1e3);
11871
+ if (hasTarget) {
11872
+ const id = setInterval(render, 1e3);
11873
+ wrap.dataset.nrIntervalId = String(id);
11874
+ }
11806
11875
  return wrap;
11807
11876
  };
11808
11877
  var countdown_default = countdownDirective;
11809
11878
 
11810
11879
  // vanilla/directives/diff.ts
11811
- var IMG_RE2 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11812
11880
  var diffDirective = ({ props, slots }) => {
11813
11881
  let before = (props.before || "").split("#")[0].trim();
11814
11882
  let after = (props.after || "").split("#")[0].trim();
@@ -11816,8 +11884,8 @@ var diffDirective = ({ props, slots }) => {
11816
11884
  const urls = [];
11817
11885
  const raw = slots.default || "";
11818
11886
  let m;
11819
- IMG_RE2.lastIndex = 0;
11820
- while ((m = IMG_RE2.exec(raw)) !== null) {
11887
+ IMG_RE.lastIndex = 0;
11888
+ while ((m = IMG_RE.exec(raw)) !== null) {
11821
11889
  urls.push(m[2].split("#")[0].trim());
11822
11890
  }
11823
11891
  if (!before && urls.length > 0) before = urls[0];
@@ -11830,21 +11898,11 @@ var diffDirective = ({ props, slots }) => {
11830
11898
  figure.className = "nr-diff";
11831
11899
  figure.tabIndex = 0;
11832
11900
  figure.setAttribute("aria-label", "Image comparison slider");
11833
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
11834
- if (props.style) figure.setAttribute("style", props.style);
11901
+ applyBaseProps(figure, props);
11835
11902
  if (props.aspect) figure.style.aspectRatio = props.aspect;
11836
11903
  if (props.height) figure.style.height = props.height;
11837
11904
  if (props.width) figure.style.width = props.width;
11838
- if (props.float) {
11839
- if (props.float === "left" || props.float === "right") {
11840
- figure.style.float = props.float;
11841
- if (!props.width) figure.style.maxWidth = "50%";
11842
- figure.style.marginInlineStart = props.float === "right" ? "1rem" : "";
11843
- figure.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
11844
- } else if (props.float === "center") {
11845
- figure.style.marginInline = "auto";
11846
- }
11847
- }
11905
+ applyFloatStyle(figure, props.float, props.width);
11848
11906
  const beforeItem = document.createElement("div");
11849
11907
  beforeItem.className = "nr-diff__item nr-diff__item--before";
11850
11908
  beforeItem.setAttribute("role", "img");
@@ -11907,8 +11965,7 @@ var diff_default = diffDirective;
11907
11965
  var hover3dDirective = ({ props, renderSlot }) => {
11908
11966
  const container = document.createElement("div");
11909
11967
  container.className = "nr-hover-3d";
11910
- if (props.class) container.classList.add(...props.class.split(/\s+/).filter(Boolean));
11911
- if (props.style) container.setAttribute("style", props.style);
11968
+ applyBaseProps(container, props);
11912
11969
  const stage = document.createElement("div");
11913
11970
  stage.className = "nr-hover-3d__stage";
11914
11971
  stage.appendChild(renderSlot("default"));
@@ -11921,14 +11978,13 @@ var hover3dDirective = ({ props, renderSlot }) => {
11921
11978
  var hover3d_default = hover3dDirective;
11922
11979
 
11923
11980
  // vanilla/directives/hovergallery.ts
11924
- var IMG_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11925
11981
  var MAX_IMAGES = 10;
11926
11982
  var hovergalleryDirective = ({ props, slots }) => {
11927
11983
  const images = [];
11928
11984
  const raw = slots.default || "";
11929
11985
  let m;
11930
- IMG_RE3.lastIndex = 0;
11931
- while ((m = IMG_RE3.exec(raw)) !== null) {
11986
+ IMG_RE.lastIndex = 0;
11987
+ while ((m = IMG_RE.exec(raw)) !== null) {
11932
11988
  images.push({ src: m[2].split("#")[0].trim(), alt: m[1].trim() || "gallery image" });
11933
11989
  }
11934
11990
  if (images.length === 0) {
@@ -11937,9 +11993,8 @@ var hovergalleryDirective = ({ props, slots }) => {
11937
11993
  const count = Math.min(images.length, MAX_IMAGES);
11938
11994
  const figure = document.createElement("figure");
11939
11995
  figure.className = "nr-hover-gallery";
11996
+ applyBaseProps(figure, props);
11940
11997
  if (props.aspect) figure.style.aspectRatio = props.aspect;
11941
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
11942
- if (props.style) figure.setAttribute("style", (figure.getAttribute("style") || "") + ";" + props.style);
11943
11998
  const imgEls = [];
11944
11999
  for (let i = 0; i < count; i++) {
11945
12000
  const el = document.createElement("img");
@@ -11989,28 +12044,11 @@ var hovergalleryDirective = ({ props, slots }) => {
11989
12044
  var hovergallery_default = hovergalleryDirective;
11990
12045
 
11991
12046
  // vanilla/directives/chat.ts
11992
- var CHAT_THEME_TOKENS = /* @__PURE__ */ new Set([
11993
- "primary",
11994
- "secondary",
11995
- "accent",
11996
- "neutral",
11997
- "info",
11998
- "success",
11999
- "warning",
12000
- "error"
12001
- ]);
12002
- function isArbitraryColor(value) {
12003
- if (CHAT_THEME_TOKENS.has(value)) return false;
12004
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
12005
- if (/^[a-zA-Z]+$/.test(value)) return true;
12006
- return false;
12007
- }
12008
12047
  var chatItemDirective = ({ props, renderSlot }) => {
12009
12048
  const side = props.side === "end" ? "end" : "start";
12010
12049
  const wrap = document.createElement("div");
12011
12050
  wrap.className = `nr-chat nr-chat--${side}`;
12012
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
12013
- if (props.style) wrap.setAttribute("style", props.style);
12051
+ applyBaseProps(wrap, props);
12014
12052
  const header = document.createElement("div");
12015
12053
  header.className = "nr-chat__header";
12016
12054
  if (props.name) {
@@ -12035,14 +12073,8 @@ var chatItemDirective = ({ props, renderSlot }) => {
12035
12073
  avatar.appendChild(img);
12036
12074
  wrap.appendChild(avatar);
12037
12075
  }
12038
- const isThemeToken = CHAT_THEME_TOKENS.has(props.color || "");
12039
- const colorClass = isThemeToken ? ` nr-chat__bubble--${props.color}` : "";
12040
12076
  const bubble = document.createElement("div");
12041
- bubble.className = `nr-chat__bubble${colorClass}`;
12042
- if (props.color && isArbitraryColor(props.color) && !isThemeToken) {
12043
- bubble.style.background = props.color;
12044
- bubble.style.color = "white";
12045
- }
12077
+ bubble.className = `nr-chat__bubble${applyColor(bubble, props.color, "nr-chat__bubble")}`;
12046
12078
  bubble.appendChild(renderSlot("default"));
12047
12079
  wrap.appendChild(bubble);
12048
12080
  if (props.footer) {
@@ -12056,8 +12088,7 @@ var chatItemDirective = ({ props, renderSlot }) => {
12056
12088
  var chatDirective = ({ props, renderSlot }) => {
12057
12089
  const wrap = document.createElement("div");
12058
12090
  wrap.className = "nr-chat";
12059
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
12060
- if (props.style) wrap.setAttribute("style", props.style);
12091
+ applyBaseProps(wrap, props);
12061
12092
  wrap.appendChild(renderSlot("default"));
12062
12093
  return wrap;
12063
12094
  };
@@ -12093,8 +12124,7 @@ function bindEventProp(el, eventProp) {
12093
12124
  var richlistItemDirective = ({ props, renderSlot }) => {
12094
12125
  const li = document.createElement("li");
12095
12126
  li.className = "nr-richlist__item";
12096
- if (props.class) li.classList.add(...props.class.split(/\s+/).filter(Boolean));
12097
- if (props.style) li.setAttribute("style", props.style);
12127
+ applyBaseProps(li, props);
12098
12128
  if (props.image) {
12099
12129
  const thumb = document.createElement("div");
12100
12130
  thumb.className = "nr-richlist__thumb";
@@ -12156,36 +12186,20 @@ var richlistItemDirective = ({ props, renderSlot }) => {
12156
12186
  var richlistDirective = ({ props, renderSlot }) => {
12157
12187
  const ul = document.createElement("ul");
12158
12188
  ul.className = "nr-richlist";
12159
- if (props.class) ul.classList.add(...props.class.split(/\s+/).filter(Boolean));
12160
- if (props.style) ul.setAttribute("style", props.style);
12189
+ applyBaseProps(ul, props);
12161
12190
  ul.appendChild(renderSlot("default"));
12162
12191
  return ul;
12163
12192
  };
12164
12193
  var richlist_default = richlistDirective;
12165
12194
 
12166
12195
  // vanilla/directives/stat.ts
12167
- var STAT_THEME_TOKENS = /* @__PURE__ */ new Set([
12168
- "primary",
12169
- "secondary",
12170
- "info",
12171
- "success",
12172
- "warning",
12173
- "error"
12174
- ]);
12175
- function isArbitraryColor2(value) {
12176
- if (STAT_THEME_TOKENS.has(value)) return false;
12177
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
12178
- if (/^[a-zA-Z]+$/.test(value)) return true;
12179
- return false;
12180
- }
12181
12196
  var statDirective = ({ props }) => {
12182
- const isThemeToken = STAT_THEME_TOKENS.has(props.color || "");
12183
- const colorClass = isThemeToken ? ` nr-stat--${props.color}` : "";
12197
+ const statIsThemeToken = isThemeToken(props.color);
12198
+ const colorClass = statIsThemeToken ? ` nr-stat--${props.color}` : "";
12184
12199
  const stat = document.createElement("div");
12185
12200
  stat.className = `nr-stat${colorClass}`;
12186
- if (props.class) stat.classList.add(...props.class.split(/\s+/).filter(Boolean));
12187
- if (props.style) stat.setAttribute("style", props.style);
12188
- const useInlineColor = props.color && isArbitraryColor2(props.color) && !isThemeToken;
12201
+ applyBaseProps(stat, props);
12202
+ const useInlineColor = props.color && isArbitraryColor(props.color) && !statIsThemeToken;
12189
12203
  if (props.icon) {
12190
12204
  const figure = document.createElement("div");
12191
12205
  figure.className = "nr-stat__figure";
@@ -12278,9 +12292,12 @@ function renderHtmlString(html) {
12278
12292
  processedContent = processedContent.replace(
12279
12293
  /<style(?:\s+[^>]*)?>([\s\S]*?)<\/style>/gi,
12280
12294
  (_match, cssContent) => {
12295
+ const trimmed = cssContent.trim();
12296
+ const existing = document.head.querySelector("style[data-nr-global]");
12297
+ if (existing && existing.textContent === trimmed) return "";
12281
12298
  const styleEl = document.createElement("style");
12282
12299
  styleEl.setAttribute("data-nr-global", "");
12283
- styleEl.textContent = cssContent;
12300
+ styleEl.textContent = trimmed;
12284
12301
  document.head.appendChild(styleEl);
12285
12302
  return "";
12286
12303
  }
@@ -12350,19 +12367,14 @@ function renderElement(element, ctx, allElements) {
12350
12367
  switch (element.type) {
12351
12368
  case "header": {
12352
12369
  const tag = `h${element.level}`;
12353
- let text = element.text;
12354
- const alignCenter = text.match(/^->\s*(.+?)\s*<-\s*$/);
12355
- const alignRight = text.match(/^->\s*(.+?)\s*->\s*$/);
12356
- if (alignCenter) text = alignCenter[1];
12357
- else if (alignRight) text = alignRight[1];
12358
12370
  const h = document.createElement(tag);
12359
12371
  h.id = element.id;
12360
12372
  let cls = `md-h${element.level}`;
12361
- if (alignCenter) cls += " text-center";
12362
- if (alignRight) cls += " text-right";
12373
+ if (element.align === "center") cls += " text-center";
12374
+ else if (element.align === "right") cls += " text-right";
12363
12375
  if (element.classes) cls += ` ${element.classes}`;
12364
12376
  h.className = cls;
12365
- h.appendChild(renderInline(text));
12377
+ h.appendChild(renderInline(element.text));
12366
12378
  return h;
12367
12379
  }
12368
12380
  case "paragraph": {
@@ -12705,7 +12717,7 @@ var guideData = [
12705
12717
  "title": "Introducci\xF3n",
12706
12718
  "icon": "menu_book",
12707
12719
  "order": 1,
12708
- "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.'
12720
+ "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.'
12709
12721
  },
12710
12722
  {
12711
12723
  "id": "titulos",
@@ -12921,7 +12933,7 @@ var guideData = [
12921
12933
  "title": "Modal",
12922
12934
  "icon": "open_in_full",
12923
12935
  "order": 2,
12924
- "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.'
12936
+ "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.'
12925
12937
  },
12926
12938
  {
12927
12939
  "id": "button",
@@ -12929,7 +12941,7 @@ var guideData = [
12929
12941
  "title": "Button",
12930
12942
  "icon": "touch_app",
12931
12943
  "order": 3,
12932
- "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 |'
12944
+ "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"`) |'
12933
12945
  },
12934
12946
  {
12935
12947
  "id": "slide",
@@ -12946,6 +12958,14 @@ var guideData = [
12946
12958
  "icon": "code_off",
12947
12959
  "order": 1,
12948
12960
  "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.'
12961
+ },
12962
+ {
12963
+ "id": "wrapper-directives",
12964
+ "category": "Layout",
12965
+ "title": "Wrapper Directives",
12966
+ "icon": "crop_free",
12967
+ "order": 1,
12968
+ "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```'
12949
12969
  }
12950
12970
  ];
12951
12971