remark-dgmo 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Diagrammo
3
+ Copyright (c) 2026 Demian Neidetcher
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/dist/client.d.ts CHANGED
@@ -18,7 +18,13 @@
18
18
  * invocation so SPA-style frameworks (Docusaurus) can re-run it after
19
19
  * route changes.
20
20
  *
21
- * In a non-browser environment (Node SSR), `bindDgmo()` is a no-op.
21
+ * In a non-browser environment, `bindDgmo()` is a no-op. We can't just check
22
+ * for `window`/`document`: some SSG renderers (Docusaurus's static export among
23
+ * them) evaluate client modules against a PARTIAL DOM that defines `window` and
24
+ * `document` but NOT the browser-only APIs this function relies on
25
+ * (`MutationObserver`, `requestAnimationFrame`, `SVGGraphicsElement.getBBox`).
26
+ * So we feature-detect those too and bail unless we're in a real browser —
27
+ * otherwise the module-level auto-init below throws during server render.
22
28
  */
23
29
  declare function bindDgmo(): void;
24
30
 
package/dist/client.js CHANGED
@@ -1,8 +1,11 @@
1
1
  // src/client.ts
2
2
  var clickHandlerBound = false;
3
3
  var themeObserverBound = false;
4
+ function isInteractiveBrowser() {
5
+ return typeof window !== "undefined" && typeof document !== "undefined" && typeof MutationObserver !== "undefined" && typeof requestAnimationFrame !== "undefined";
6
+ }
4
7
  function bindDgmo() {
5
- if (typeof window === "undefined" || typeof document === "undefined") return;
8
+ if (!isInteractiveBrowser()) return;
6
9
  if (!clickHandlerBound) {
7
10
  document.addEventListener("click", (e) => {
8
11
  void handleToolbarBtnClick(e);
@@ -83,7 +86,7 @@ function tightenViewBoxes() {
83
86
  }
84
87
  });
85
88
  }
86
- if (typeof window !== "undefined" && typeof document !== "undefined") {
89
+ if (isInteractiveBrowser()) {
87
90
  if (document.readyState === "loading") {
88
91
  document.addEventListener("DOMContentLoaded", bindDgmo);
89
92
  } else {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Framework-neutral client-side enhancement for diagrams emitted by\n * `remark-dgmo`:\n *\n * - Bind a delegated click handler for the showcase toolbar buttons:\n * `.dgmo-copy` copies the source string in `data-dgmo-source` to the\n * clipboard. `.dgmo-open` is an `<a href>` whose default-action\n * navigation is preserved — except when it lives inside a `<summary>`\n * (the collapsible toolbar case), where we have to manually navigate\n * because the same click's `preventDefault()` cancels both the\n * summary's toggle AND the anchor's nav.\n * - Tighten each diagram's `viewBox` to its actual content bounds via\n * `SVGGraphicsElement.getBBox()`, since SVG-export from the renderer\n * embeds a generous bounding box.\n *\n * `bindDgmo()` is safe to call multiple times: the click handler is bound\n * once-and-only-once (idempotent), and viewBox tightening is run every\n * invocation so SPA-style frameworks (Docusaurus) can re-run it after\n * route changes.\n *\n * In a non-browser environment (Node SSR), `bindDgmo()` is a no-op.\n */\n\nlet clickHandlerBound = false;\nlet themeObserverBound = false;\n\nexport function bindDgmo(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n\n if (!clickHandlerBound) {\n document.addEventListener('click', (e) => {\n void handleToolbarBtnClick(e);\n });\n clickHandlerBound = true;\n }\n tightenViewBoxes();\n\n // Color-mode toggles flip which dual-render wrapper is `display: none`.\n // The wrapper that was hidden at load couldn't be measured (getBBox\n // returns 0 on display:none subtrees), so its SVG keeps the full\n // un-tightened viewBox and looks tiny when it becomes visible. Watch\n // for the host's color-mode signal flipping and re-tighten then.\n //\n // Double-rAF defers the measurement until AFTER the browser has\n // finished the layout pass that the display-change triggered. Without\n // it, MutationObserver fires synchronously before layout and getBBox\n // still returns 0 on the freshly-visible element.\n if (!themeObserverBound) {\n const html = document.documentElement;\n const reTighten = () => {\n requestAnimationFrame(() => requestAnimationFrame(tightenViewBoxes));\n };\n new MutationObserver(reTighten).observe(html, {\n attributes: true,\n attributeFilter: ['data-theme', 'class'],\n });\n themeObserverBound = true;\n }\n}\n\nasync function handleToolbarBtnClick(e: Event): Promise<void> {\n const target = e.target as Element | null;\n if (!target || typeof target.closest !== 'function') return;\n const btn = target.closest('.dgmo-toolbar-btn') as HTMLElement | null;\n if (!btn) return;\n\n // The showcase toolbar IS the <summary> of a <details> disclosure, so a\n // click on any descendant would also toggle the disclosure unless we\n // cancel the default action. preventDefault here cancels the summary's\n // toggle — but it also cancels an anchor's navigation, so we have to\n // manually re-open the link below when the open-in-editor button is\n // nested inside a summary.\n const insideSummary = !!btn.closest('summary');\n if (insideSummary) e.preventDefault();\n\n if (btn.matches('button.dgmo-copy')) {\n const src = btn.dataset['dgmoSource'] ?? '';\n try {\n await navigator.clipboard.writeText(src);\n } catch {\n return;\n }\n btn.classList.add('dgmo-copy--success');\n setTimeout(() => btn.classList.remove('dgmo-copy--success'), 1500);\n return;\n }\n\n if (insideSummary && btn.matches('a.dgmo-open')) {\n const anchor = btn as HTMLAnchorElement;\n if (anchor.href) {\n window.open(\n anchor.href,\n anchor.target || '_blank',\n 'noopener,noreferrer'\n );\n }\n }\n}\n\nfunction tightenViewBoxes(): void {\n const WRAPPER_SELECTORS = '.dgmo-light, .dgmo-dark, .dgmo-svg';\n document.querySelectorAll(WRAPPER_SELECTORS).forEach((node) => {\n const wrapper = node as HTMLElement;\n const svg = wrapper.querySelector('svg') as SVGSVGElement | null;\n if (!svg) return;\n\n // `getBBox()` returns 0,0,0,0 on elements whose ancestor is `display: none`.\n // Under dual-render the inactive color-mode wrapper IS display:none at load,\n // so its SVG would stay un-tightened — and then look small after the user\n // toggles into it. Set the wrapper to inline-style `display: block`\n // synchronously, read getBBox, then restore. Modern browsers don't paint\n // between these synchronous DOM writes, so there's no visible flicker.\n const computed = window.getComputedStyle(wrapper);\n const wasHidden = computed.display === 'none';\n const savedInlineDisplay = wrapper.style.display;\n if (wasHidden) {\n wrapper.style.display = 'block';\n // Force a synchronous layout pass before getBBox so the freshly-\n // shown element actually has bounds. Without this, some browsers\n // still report 0,0,0,0 because they batch the display change for\n // the next frame.\n void wrapper.offsetHeight;\n }\n\n try {\n const bbox = (svg as unknown as SVGGraphicsElement).getBBox();\n if (bbox.width > 0 && bbox.height > 0) {\n const pad = 16;\n svg.setAttribute(\n 'viewBox',\n `${bbox.x - pad} ${bbox.y - pad} ${bbox.width + pad * 2} ${\n bbox.height + pad * 2\n }`\n );\n }\n } catch {\n // ignore: SVG not yet in the DOM, or getBBox unsupported\n }\n\n if (wasHidden) {\n wrapper.style.display = savedInlineDisplay;\n // Clearing the only inline property leaves `style=\"\"` on the\n // element in Chrome/Safari. React 19's post-hydration check reads\n // that empty attribute as a server-vs-client mismatch on the\n // wrapper div (the wrapper is React-owned; only its `innerHTML`\n // is dangerouslySetInnerHTML). Drop the empty attribute so the\n // wrapper round-trips back to its SSR shape.\n if (savedInlineDisplay === '' && wrapper.getAttribute('style') === '') {\n wrapper.removeAttribute('style');\n }\n }\n });\n}\n\n// Auto-init on initial load. Docusaurus-style SPA wrappers also re-call\n// bindDgmo on route changes; that's safe (the click handler is bound once,\n// viewBox tightening runs every time).\nif (typeof window !== 'undefined' && typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', bindDgmo);\n } else {\n bindDgmo();\n }\n}\n"],"mappings":";AAuBA,IAAI,oBAAoB;AACxB,IAAI,qBAAqB;AAElB,SAAS,WAAiB;AAC/B,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa,YAAa;AAEtE,MAAI,CAAC,mBAAmB;AACtB,aAAS,iBAAiB,SAAS,CAAC,MAAM;AACxC,WAAK,sBAAsB,CAAC;AAAA,IAC9B,CAAC;AACD,wBAAoB;AAAA,EACtB;AACA,mBAAiB;AAYjB,MAAI,CAAC,oBAAoB;AACvB,UAAM,OAAO,SAAS;AACtB,UAAM,YAAY,MAAM;AACtB,4BAAsB,MAAM,sBAAsB,gBAAgB,CAAC;AAAA,IACrE;AACA,QAAI,iBAAiB,SAAS,EAAE,QAAQ,MAAM;AAAA,MAC5C,YAAY;AAAA,MACZ,iBAAiB,CAAC,cAAc,OAAO;AAAA,IACzC,CAAC;AACD,yBAAqB;AAAA,EACvB;AACF;AAEA,eAAe,sBAAsB,GAAyB;AAC5D,QAAM,SAAS,EAAE;AACjB,MAAI,CAAC,UAAU,OAAO,OAAO,YAAY,WAAY;AACrD,QAAM,MAAM,OAAO,QAAQ,mBAAmB;AAC9C,MAAI,CAAC,IAAK;AAQV,QAAM,gBAAgB,CAAC,CAAC,IAAI,QAAQ,SAAS;AAC7C,MAAI,cAAe,GAAE,eAAe;AAEpC,MAAI,IAAI,QAAQ,kBAAkB,GAAG;AACnC,UAAM,MAAM,IAAI,QAAQ,YAAY,KAAK;AACzC,QAAI;AACF,YAAM,UAAU,UAAU,UAAU,GAAG;AAAA,IACzC,QAAQ;AACN;AAAA,IACF;AACA,QAAI,UAAU,IAAI,oBAAoB;AACtC,eAAW,MAAM,IAAI,UAAU,OAAO,oBAAoB,GAAG,IAAI;AACjE;AAAA,EACF;AAEA,MAAI,iBAAiB,IAAI,QAAQ,aAAa,GAAG;AAC/C,UAAM,SAAS;AACf,QAAI,OAAO,MAAM;AACf,aAAO;AAAA,QACL,OAAO;AAAA,QACP,OAAO,UAAU;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,mBAAyB;AAChC,QAAM,oBAAoB;AAC1B,WAAS,iBAAiB,iBAAiB,EAAE,QAAQ,CAAC,SAAS;AAC7D,UAAM,UAAU;AAChB,UAAM,MAAM,QAAQ,cAAc,KAAK;AACvC,QAAI,CAAC,IAAK;AAQV,UAAM,WAAW,OAAO,iBAAiB,OAAO;AAChD,UAAM,YAAY,SAAS,YAAY;AACvC,UAAM,qBAAqB,QAAQ,MAAM;AACzC,QAAI,WAAW;AACb,cAAQ,MAAM,UAAU;AAKxB,WAAK,QAAQ;AAAA,IACf;AAEA,QAAI;AACF,YAAM,OAAQ,IAAsC,QAAQ;AAC5D,UAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,GAAG;AACrC,cAAM,MAAM;AACZ,YAAI;AAAA,UACF;AAAA,UACA,GAAG,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,QAAQ,MAAM,CAAC,IACrD,KAAK,SAAS,MAAM,CACtB;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,QAAI,WAAW;AACb,cAAQ,MAAM,UAAU;AAOxB,UAAI,uBAAuB,MAAM,QAAQ,aAAa,OAAO,MAAM,IAAI;AACrE,gBAAQ,gBAAgB,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAKA,IAAI,OAAO,WAAW,eAAe,OAAO,aAAa,aAAa;AACpE,MAAI,SAAS,eAAe,WAAW;AACrC,aAAS,iBAAiB,oBAAoB,QAAQ;AAAA,EACxD,OAAO;AACL,aAAS;AAAA,EACX;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Framework-neutral client-side enhancement for diagrams emitted by\n * `remark-dgmo`:\n *\n * - Bind a delegated click handler for the showcase toolbar buttons:\n * `.dgmo-copy` copies the source string in `data-dgmo-source` to the\n * clipboard. `.dgmo-open` is an `<a href>` whose default-action\n * navigation is preserved — except when it lives inside a `<summary>`\n * (the collapsible toolbar case), where we have to manually navigate\n * because the same click's `preventDefault()` cancels both the\n * summary's toggle AND the anchor's nav.\n * - Tighten each diagram's `viewBox` to its actual content bounds via\n * `SVGGraphicsElement.getBBox()`, since SVG-export from the renderer\n * embeds a generous bounding box.\n *\n * `bindDgmo()` is safe to call multiple times: the click handler is bound\n * once-and-only-once (idempotent), and viewBox tightening is run every\n * invocation so SPA-style frameworks (Docusaurus) can re-run it after\n * route changes.\n *\n * In a non-browser environment, `bindDgmo()` is a no-op. We can't just check\n * for `window`/`document`: some SSG renderers (Docusaurus's static export among\n * them) evaluate client modules against a PARTIAL DOM that defines `window` and\n * `document` but NOT the browser-only APIs this function relies on\n * (`MutationObserver`, `requestAnimationFrame`, `SVGGraphicsElement.getBBox`).\n * So we feature-detect those too and bail unless we're in a real browser —\n * otherwise the module-level auto-init below throws during server render.\n */\n\nlet clickHandlerBound = false;\nlet themeObserverBound = false;\n\nfunction isInteractiveBrowser(): boolean {\n return (\n typeof window !== 'undefined' &&\n typeof document !== 'undefined' &&\n typeof MutationObserver !== 'undefined' &&\n typeof requestAnimationFrame !== 'undefined'\n );\n}\n\nexport function bindDgmo(): void {\n if (!isInteractiveBrowser()) return;\n\n if (!clickHandlerBound) {\n document.addEventListener('click', (e) => {\n void handleToolbarBtnClick(e);\n });\n clickHandlerBound = true;\n }\n tightenViewBoxes();\n\n // Color-mode toggles flip which dual-render wrapper is `display: none`.\n // The wrapper that was hidden at load couldn't be measured (getBBox\n // returns 0 on display:none subtrees), so its SVG keeps the full\n // un-tightened viewBox and looks tiny when it becomes visible. Watch\n // for the host's color-mode signal flipping and re-tighten then.\n //\n // Double-rAF defers the measurement until AFTER the browser has\n // finished the layout pass that the display-change triggered. Without\n // it, MutationObserver fires synchronously before layout and getBBox\n // still returns 0 on the freshly-visible element.\n if (!themeObserverBound) {\n const html = document.documentElement;\n const reTighten = () => {\n requestAnimationFrame(() => requestAnimationFrame(tightenViewBoxes));\n };\n new MutationObserver(reTighten).observe(html, {\n attributes: true,\n attributeFilter: ['data-theme', 'class'],\n });\n themeObserverBound = true;\n }\n}\n\nasync function handleToolbarBtnClick(e: Event): Promise<void> {\n const target = e.target as Element | null;\n if (!target || typeof target.closest !== 'function') return;\n const btn = target.closest('.dgmo-toolbar-btn') as HTMLElement | null;\n if (!btn) return;\n\n // The showcase toolbar IS the <summary> of a <details> disclosure, so a\n // click on any descendant would also toggle the disclosure unless we\n // cancel the default action. preventDefault here cancels the summary's\n // toggle — but it also cancels an anchor's navigation, so we have to\n // manually re-open the link below when the open-in-editor button is\n // nested inside a summary.\n const insideSummary = !!btn.closest('summary');\n if (insideSummary) e.preventDefault();\n\n if (btn.matches('button.dgmo-copy')) {\n const src = btn.dataset['dgmoSource'] ?? '';\n try {\n await navigator.clipboard.writeText(src);\n } catch {\n return;\n }\n btn.classList.add('dgmo-copy--success');\n setTimeout(() => btn.classList.remove('dgmo-copy--success'), 1500);\n return;\n }\n\n if (insideSummary && btn.matches('a.dgmo-open')) {\n const anchor = btn as HTMLAnchorElement;\n if (anchor.href) {\n window.open(\n anchor.href,\n anchor.target || '_blank',\n 'noopener,noreferrer'\n );\n }\n }\n}\n\nfunction tightenViewBoxes(): void {\n const WRAPPER_SELECTORS = '.dgmo-light, .dgmo-dark, .dgmo-svg';\n document.querySelectorAll(WRAPPER_SELECTORS).forEach((node) => {\n const wrapper = node as HTMLElement;\n const svg = wrapper.querySelector('svg') as SVGSVGElement | null;\n if (!svg) return;\n\n // `getBBox()` returns 0,0,0,0 on elements whose ancestor is `display: none`.\n // Under dual-render the inactive color-mode wrapper IS display:none at load,\n // so its SVG would stay un-tightened — and then look small after the user\n // toggles into it. Set the wrapper to inline-style `display: block`\n // synchronously, read getBBox, then restore. Modern browsers don't paint\n // between these synchronous DOM writes, so there's no visible flicker.\n const computed = window.getComputedStyle(wrapper);\n const wasHidden = computed.display === 'none';\n const savedInlineDisplay = wrapper.style.display;\n if (wasHidden) {\n wrapper.style.display = 'block';\n // Force a synchronous layout pass before getBBox so the freshly-\n // shown element actually has bounds. Without this, some browsers\n // still report 0,0,0,0 because they batch the display change for\n // the next frame.\n void wrapper.offsetHeight;\n }\n\n try {\n const bbox = (svg as unknown as SVGGraphicsElement).getBBox();\n if (bbox.width > 0 && bbox.height > 0) {\n const pad = 16;\n svg.setAttribute(\n 'viewBox',\n `${bbox.x - pad} ${bbox.y - pad} ${bbox.width + pad * 2} ${\n bbox.height + pad * 2\n }`\n );\n }\n } catch {\n // ignore: SVG not yet in the DOM, or getBBox unsupported\n }\n\n if (wasHidden) {\n wrapper.style.display = savedInlineDisplay;\n // Clearing the only inline property leaves `style=\"\"` on the\n // element in Chrome/Safari. React 19's post-hydration check reads\n // that empty attribute as a server-vs-client mismatch on the\n // wrapper div (the wrapper is React-owned; only its `innerHTML`\n // is dangerouslySetInnerHTML). Drop the empty attribute so the\n // wrapper round-trips back to its SSR shape.\n if (savedInlineDisplay === '' && wrapper.getAttribute('style') === '') {\n wrapper.removeAttribute('style');\n }\n }\n });\n}\n\n// Auto-init on initial load. Docusaurus-style SPA wrappers also re-call\n// bindDgmo on route changes; that's safe (the click handler is bound once,\n// viewBox tightening runs every time).\nif (isInteractiveBrowser()) {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', bindDgmo);\n } else {\n bindDgmo();\n }\n}\n"],"mappings":";AA6BA,IAAI,oBAAoB;AACxB,IAAI,qBAAqB;AAEzB,SAAS,uBAAgC;AACvC,SACE,OAAO,WAAW,eAClB,OAAO,aAAa,eACpB,OAAO,qBAAqB,eAC5B,OAAO,0BAA0B;AAErC;AAEO,SAAS,WAAiB;AAC/B,MAAI,CAAC,qBAAqB,EAAG;AAE7B,MAAI,CAAC,mBAAmB;AACtB,aAAS,iBAAiB,SAAS,CAAC,MAAM;AACxC,WAAK,sBAAsB,CAAC;AAAA,IAC9B,CAAC;AACD,wBAAoB;AAAA,EACtB;AACA,mBAAiB;AAYjB,MAAI,CAAC,oBAAoB;AACvB,UAAM,OAAO,SAAS;AACtB,UAAM,YAAY,MAAM;AACtB,4BAAsB,MAAM,sBAAsB,gBAAgB,CAAC;AAAA,IACrE;AACA,QAAI,iBAAiB,SAAS,EAAE,QAAQ,MAAM;AAAA,MAC5C,YAAY;AAAA,MACZ,iBAAiB,CAAC,cAAc,OAAO;AAAA,IACzC,CAAC;AACD,yBAAqB;AAAA,EACvB;AACF;AAEA,eAAe,sBAAsB,GAAyB;AAC5D,QAAM,SAAS,EAAE;AACjB,MAAI,CAAC,UAAU,OAAO,OAAO,YAAY,WAAY;AACrD,QAAM,MAAM,OAAO,QAAQ,mBAAmB;AAC9C,MAAI,CAAC,IAAK;AAQV,QAAM,gBAAgB,CAAC,CAAC,IAAI,QAAQ,SAAS;AAC7C,MAAI,cAAe,GAAE,eAAe;AAEpC,MAAI,IAAI,QAAQ,kBAAkB,GAAG;AACnC,UAAM,MAAM,IAAI,QAAQ,YAAY,KAAK;AACzC,QAAI;AACF,YAAM,UAAU,UAAU,UAAU,GAAG;AAAA,IACzC,QAAQ;AACN;AAAA,IACF;AACA,QAAI,UAAU,IAAI,oBAAoB;AACtC,eAAW,MAAM,IAAI,UAAU,OAAO,oBAAoB,GAAG,IAAI;AACjE;AAAA,EACF;AAEA,MAAI,iBAAiB,IAAI,QAAQ,aAAa,GAAG;AAC/C,UAAM,SAAS;AACf,QAAI,OAAO,MAAM;AACf,aAAO;AAAA,QACL,OAAO;AAAA,QACP,OAAO,UAAU;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,mBAAyB;AAChC,QAAM,oBAAoB;AAC1B,WAAS,iBAAiB,iBAAiB,EAAE,QAAQ,CAAC,SAAS;AAC7D,UAAM,UAAU;AAChB,UAAM,MAAM,QAAQ,cAAc,KAAK;AACvC,QAAI,CAAC,IAAK;AAQV,UAAM,WAAW,OAAO,iBAAiB,OAAO;AAChD,UAAM,YAAY,SAAS,YAAY;AACvC,UAAM,qBAAqB,QAAQ,MAAM;AACzC,QAAI,WAAW;AACb,cAAQ,MAAM,UAAU;AAKxB,WAAK,QAAQ;AAAA,IACf;AAEA,QAAI;AACF,YAAM,OAAQ,IAAsC,QAAQ;AAC5D,UAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,GAAG;AACrC,cAAM,MAAM;AACZ,YAAI;AAAA,UACF;AAAA,UACA,GAAG,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,QAAQ,MAAM,CAAC,IACrD,KAAK,SAAS,MAAM,CACtB;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,QAAI,WAAW;AACb,cAAQ,MAAM,UAAU;AAOxB,UAAI,uBAAuB,MAAM,QAAQ,aAAa,OAAO,MAAM,IAAI;AACrE,gBAAQ,gBAAgB,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAKA,IAAI,qBAAqB,GAAG;AAC1B,MAAI,SAAS,eAAe,WAAW;AACrC,aAAS,iBAAiB,oBAAoB,QAAQ;AAAA,EACxD,OAAO;AACL,aAAS;AAAA,EACX;AACF;","names":[]}
@@ -0,0 +1,96 @@
1
+ /* remark-dgmo — optional Nord page theme.
2
+ *
3
+ * A drop-in stylesheet that paints the whole page in the Nord palette
4
+ * (nordtheme.com) so the page chrome matches dgmo's default `nord` diagram
5
+ * palette. Framework-AGNOSTIC: it only
6
+ * defines the palette, a set of generic `--dgmo-*` semantic variables, and
7
+ * base body/diagram styling. To theme a framework's own chrome (Docusaurus
8
+ * Infima, Fumadocs UI, …), alias that framework's variables to the `--dgmo-*`
9
+ * ones in your own CSS, e.g.:
10
+ *
11
+ * :root { --ifm-background-color: var(--dgmo-bg); }
12
+ *
13
+ * Dark mode is keyed on BOTH `[data-theme="dark"]` (Docusaurus, Astro, …) and
14
+ * `.dark` (Fumadocs / next-themes), so it follows whichever convention your
15
+ * host uses — the same toggle that flips dgmo's dual-rendered diagrams.
16
+ */
17
+
18
+ :root {
19
+ /* Nord palette */
20
+ --nord0: #2e3440;
21
+ --nord1: #3b4252;
22
+ --nord2: #434c5e;
23
+ --nord3: #4c566a;
24
+ --nord4: #d8dee9;
25
+ --nord5: #e5e9f0;
26
+ --nord6: #eceff4;
27
+ --nord7: #8fbcbb;
28
+ --nord8: #88c0d0;
29
+ --nord9: #81a1c1;
30
+ --nord10: #5e81ac;
31
+ --nord11: #bf616a;
32
+ --nord12: #d08770;
33
+ --nord13: #ebcb8b;
34
+ --nord14: #a3be8c;
35
+ --nord15: #b48ead;
36
+
37
+ /* Generic semantic tokens (light) — alias these to your framework's vars */
38
+ --dgmo-bg: var(--nord6);
39
+ --dgmo-surface: #ffffff;
40
+ --dgmo-elevated: var(--nord5);
41
+ --dgmo-fg: var(--nord0);
42
+ --dgmo-muted: var(--nord3);
43
+ --dgmo-border: var(--nord4);
44
+ --dgmo-accent: var(--nord10);
45
+ --dgmo-accent-hover: var(--nord9);
46
+ }
47
+
48
+ [data-theme="dark"],
49
+ .dark {
50
+ --dgmo-bg: var(--nord0);
51
+ --dgmo-surface: var(--nord1);
52
+ --dgmo-elevated: var(--nord2);
53
+ --dgmo-fg: var(--nord6);
54
+ --dgmo-muted: var(--nord4);
55
+ --dgmo-border: var(--nord3);
56
+ --dgmo-accent: var(--nord8);
57
+ --dgmo-accent-hover: var(--nord7);
58
+ }
59
+
60
+ /* === Base page === */
61
+ body {
62
+ background: var(--dgmo-bg);
63
+ color: var(--dgmo-fg);
64
+ }
65
+
66
+ a {
67
+ color: var(--dgmo-accent);
68
+ }
69
+ a:hover {
70
+ color: var(--dgmo-accent-hover);
71
+ }
72
+
73
+ /* === Diagram cards ===
74
+ * Frame each diagram so it reads as a discrete card against the page. The
75
+ * SVG carries its own palette background, so the card adds only a border,
76
+ * radius, and a touch of surface padding. Targets the wrapper `<figure>`
77
+ * (`dgmo dgmo--diagram`); showcase cards already have `.dgmo-card`. */
78
+ .dgmo--diagram,
79
+ .dgmo-card {
80
+ background: var(--dgmo-surface);
81
+ border: 1px solid var(--dgmo-border);
82
+ border-radius: 0.6rem;
83
+ padding: 1rem;
84
+ margin: 1.25rem 0;
85
+ }
86
+ .dgmo-card {
87
+ padding: 0; /* its inner .dgmo-svg / toolbar handle spacing */
88
+ }
89
+
90
+ /* Caption under a diagram, when present */
91
+ .dgmo-caption {
92
+ color: var(--dgmo-muted);
93
+ font-size: 0.85em;
94
+ margin-top: 0.5rem;
95
+ text-align: center;
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remark-dgmo",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Remark plugin to render DGMO diagrams from fenced code blocks at build time. Framework-agnostic core shared by astro-dgmo, docusaurus-plugin-dgmo, and any unified pipeline.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -43,7 +43,8 @@
43
43
  "default": "./dist/remark-plugin.js"
44
44
  },
45
45
  "./client.js": "./dist/client.js",
46
- "./client.css": "./dist/client.css"
46
+ "./client.css": "./dist/client.css",
47
+ "./theme-nord.css": "./dist/theme-nord.css"
47
48
  },
48
49
  "files": [
49
50
  "dist",
@@ -55,8 +56,8 @@
55
56
  "node": ">=20.6"
56
57
  },
57
58
  "scripts": {
58
- "build": "tsup && cp styles/client.css dist/client.css",
59
- "postbuild": "test -f dist/client.css && test -f dist/client.js",
59
+ "build": "tsup && cp styles/client.css dist/client.css && cp styles/theme-nord.css dist/theme-nord.css",
60
+ "postbuild": "test -f dist/client.css && test -f dist/client.js && test -f dist/theme-nord.css",
60
61
  "dev": "tsup --watch",
61
62
  "typecheck": "tsc --noEmit",
62
63
  "test": "vitest run --coverage",
@@ -66,6 +67,9 @@
66
67
  "format": "prettier --write .",
67
68
  "format:check": "prettier --check .",
68
69
  "check:deadcode": "knip",
70
+ "check:duplication": "jscpd ./src",
71
+ "check:deps": "depcheck --ignores=''",
72
+ "check:all": "pnpm check:deadcode && pnpm check:duplication && pnpm check:deps",
69
73
  "prepublishOnly": "pnpm build && pnpm test"
70
74
  },
71
75
  "peerDependencies": {
@@ -86,7 +90,9 @@
86
90
  "@types/mdast": "^4.0.4",
87
91
  "@types/node": "^22.10.0",
88
92
  "@vitest/coverage-v8": "^4.1.6",
93
+ "depcheck": "^1.4.7",
89
94
  "eslint": "^10.4.0",
95
+ "jscpd": "^4.0.9",
90
96
  "knip": "^6.14.1",
91
97
  "postcss": "^8.4.49",
92
98
  "prettier": "^3.8.3",