@markdy/renderer-dom 1.0.17 → 1.0.19

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/README.md CHANGED
@@ -13,7 +13,7 @@ Web Animations API renderer for [MarkdyScript](../../docs/SYNTAX.md) scenes. Tra
13
13
  - **Seek-safe** — manual `currentTime` control enables reliable `seek()` in any direction
14
14
  - **Playback-rate controls** — set normalized timeline speed to slow down or speed up diagrams without rebuilding animations
15
15
  - **Interactive viewport** — opt into wheel zoom and drag pan while click-to-pause stays available
16
- - **Embed controls** — opt into a compact playback toolbar with restart, speed, and view reset controls
16
+ - **Embed controls** — opt into only the playback, timeline, viewport, export, and sharing controls a scene needs
17
17
  - **Semantic themes** — `paper`, `editorial`, `nebula`, `midnight`, `blueprint`, and `graphite`, with per-role node and edge colors
18
18
  - **Single dependency** — only `@markdy/core`
19
19
 
@@ -70,21 +70,21 @@ diagram.destroy(); // clean up DOM + cancel animations
70
70
  |---|---|---|---|
71
71
  | `container` | `HTMLElement` | *(required)* | DOM element to mount the scene into |
72
72
  | `code` | `string` | *(required)* | MarkdyScript source code |
73
- | `autoplay` | `boolean` | `plan.meta.autoplay ?? true` | Start playing immediately |
74
- | `loop` | `boolean` | `plan.meta.loop ?? true` | Loop the animation when it reaches the end |
75
- | `copyright` | `boolean` | `plan.meta.copyright ?? true` | Show a small "Powered by Markdy" badge below the animation |
73
+ | `autoplay` | `boolean` | `player.playback.autoplay ?? true` | Override whether playback starts immediately |
74
+ | `loop` | `boolean` | `player.playback.loop ?? true` | Override whether playback loops at the end |
75
+ | `copyright` | `boolean` | `player.chrome.badge ?? true` | Show the linked badge at the footer's right edge |
76
76
  | `progressBar` | `boolean \| string` | `true` | Deprecated compatibility flag for the scene-boundary progress bar |
77
- | `sceneBoundaryProgress` | `boolean \| string` | `true` | Show boundary progress bar, or pass a custom color/gradient string |
78
- | `progressColor` | `string` | `plan.meta.progressColor ?? "rainbow"` | Custom progress bar color (e.g. `"#3b82f6"`) or gradient (e.g. `"#ec4899, #8b5cf6"`) |
79
- | `playbackRate` | `number` | `plan.meta.playbackRate ?? 1` | Normalized timeline speed multiplier; `1` is Markdy's normal pace |
80
- | `interactiveViewport` | `boolean` | `controls \|\| player.interaction` | Enable wheel zoom and drag pan on the rendered viewport |
81
- | `controls` | `boolean` | `player.controls` | Show the footer toolbar (play, prev/next beat, restart, seek, speed, fit, reset view, SVG, share); also enables viewport interaction |
77
+ | `sceneBoundaryProgress` | `boolean \| string` | `player.chrome.progress` | Override boundary progress, or pass a custom color/gradient |
78
+ | `progressColor` | `string` | `player.chrome.color` or rainbow | Override the progress color or gradient |
79
+ | `playbackRate` | `number` | `player.playback.rate ?? 1` | Override the initial timeline multiplier |
80
+ | `interactiveViewport` | `boolean` | `player.interaction` | `true` supplies default gestures; `false` suppresses script gestures |
81
+ | `controls` | `boolean` | `player.controls` | `true` supplies legacy toolbar defaults; `false` suppresses script controls |
82
82
  | `shareUrl` | `string` | Markdy playground | Base URL used by the Share control when building `#code=` links |
83
83
  | `onWarning` | `(warning: Diagnostic) => void` | `console.warn` | Called for each soft parse warning |
84
84
  | `onTimeUpdate` | `(seconds: number, durationSeconds: number) => void` | — | Called whenever playback or seek changes the current time |
85
85
  | `onPlayStateChange` | `(playing: boolean) => void` | — | Called when playback starts or pauses |
86
86
 
87
- > **Note:** Directives declared inside the MarkdyScript code (e.g. `controls true`, `interactive true`, `progressColor "#3b82f6"`) apply automatically if the corresponding JavaScript option is omitted (`undefined`). Options passed to `createDiagram()` override the in-script declarations.
87
+ > **Note:** Prefer grouped `player:` configuration in MarkdyScript. Omitted host options preserve it; host `false` suppresses controls or interaction, while host `true` supplies legacy defaults for leaves the script does not set. A speed selector renders only with at least two distinct positive `speeds` values.
88
88
 
89
89
  ### `Diagram`
90
90
 
package/dist/index.d.ts CHANGED
@@ -26,11 +26,11 @@ interface DiagramOptions {
26
26
  progressBarColor?: string;
27
27
  /** Playback speed multiplier. Defaults to 1, where 1 is Markdy's normal pace. */
28
28
  playbackRate?: number;
29
- /** Enable wheel zoom and drag pan on the rendered viewport. Defaults to false. */
29
+ /** Host interaction override. `true` enables default gestures; `false` suppresses script interaction. */
30
30
  interactiveViewport?: boolean;
31
31
  /** Base URL for the Share control. Defaults to the Markdy playground. */
32
32
  shareUrl?: string;
33
- /** Show built-in playback and viewport controls. Also enables viewport interaction. Defaults to false. */
33
+ /** Host controls override. `true` enables the legacy default set; `false` suppresses script controls. */
34
34
  controls?: boolean;
35
35
  onWarning?: (warning: Diagnostic) => void;
36
36
  onTimeUpdate?: (seconds: number, durationSeconds: number) => void;
package/dist/index.js CHANGED
@@ -2511,6 +2511,7 @@ function ensureSceneStyles(doc) {
2511
2511
  justify-content: space-between;
2512
2512
  gap: 8px;
2513
2513
  padding: 6px 12px;
2514
+ color: var(--md-text, #f8fafc);
2514
2515
  background: var(--md-footer-bg, rgba(15, 23, 42, 0.85));
2515
2516
  backdrop-filter: blur(16px);
2516
2517
  -webkit-backdrop-filter: blur(16px);
@@ -2521,7 +2522,9 @@ function ensureSceneStyles(doc) {
2521
2522
  display: flex;
2522
2523
  align-items: center;
2523
2524
  justify-content: flex-start;
2524
- width: 100%;
2525
+ flex: 1 1 auto;
2526
+ width: auto;
2527
+ min-width: 0;
2525
2528
  gap: 8px;
2526
2529
  max-width: 100%;
2527
2530
  overflow-x: auto;
@@ -2555,7 +2558,6 @@ function ensureSceneStyles(doc) {
2555
2558
  align-items: center;
2556
2559
  gap: 4px;
2557
2560
  flex-shrink: 0;
2558
- margin-left: auto;
2559
2561
  }
2560
2562
  .markdy-control-divider {
2561
2563
  width: 1px;
@@ -2628,26 +2630,22 @@ function ensureSceneStyles(doc) {
2628
2630
  .markdy-badge {
2629
2631
  display: inline-flex;
2630
2632
  align-items: center;
2631
- gap: 4px;
2632
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
2633
- font-size: 11px;
2634
- font-weight: 600;
2633
+ text-align: right;
2634
+ font-family: system-ui, sans-serif;
2635
+ font-size: 10px;
2636
+ font-weight: 400;
2635
2637
  color: var(--md-control-text, #94a3b8);
2636
2638
  text-decoration: none;
2637
- padding: 3px 8px;
2638
- border-radius: 999px;
2639
- background: var(--md-control-bg, rgba(255, 255, 255, 0.05));
2640
- border: 1px solid var(--md-control-border, rgba(148, 163, 184, 0.18));
2639
+ padding: 0;
2640
+ opacity: 0.7;
2641
2641
  margin-left: auto;
2642
2642
  flex-shrink: 0;
2643
- transition: all 0.15s ease;
2643
+ transition: opacity 0.15s ease, color 0.15s ease;
2644
2644
  white-space: nowrap;
2645
2645
  }
2646
2646
  .markdy-badge:hover {
2647
- color: var(--accent, #10b981);
2648
- background: var(--accent-light, rgba(16, 185, 129, 0.12));
2649
- border-color: var(--accent, #10b981);
2650
- transform: translateY(-1px);
2647
+ opacity: 1;
2648
+ color: var(--md-text, #f8fafc);
2651
2649
  }
2652
2650
  .markdy-speed-group {
2653
2651
  display: inline-flex;
@@ -2680,57 +2678,6 @@ function ensureSceneStyles(doc) {
2680
2678
  color: #ffffff !important;
2681
2679
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25) !important;
2682
2680
  }
2683
- .markdy-control-seek {
2684
- appearance: none;
2685
- -webkit-appearance: none;
2686
- width: 100%;
2687
- height: 5px;
2688
- border-radius: 999px;
2689
- background: linear-gradient(
2690
- to right,
2691
- var(--accent, #10b981) 0%,
2692
- var(--accent, #10b981) var(--seek-pct, 0%),
2693
- rgba(148, 163, 184, 0.22) var(--seek-pct, 0%),
2694
- rgba(148, 163, 184, 0.22) 100%
2695
- );
2696
- outline: none;
2697
- cursor: pointer;
2698
- margin: 0;
2699
- transition: height 0.15s ease;
2700
- }
2701
- .markdy-control-seek:hover {
2702
- height: 7px;
2703
- }
2704
- .markdy-control-seek::-webkit-slider-thumb {
2705
- appearance: none;
2706
- -webkit-appearance: none;
2707
- width: 12px;
2708
- height: 12px;
2709
- border-radius: 50%;
2710
- background: #ffffff;
2711
- border: 2px solid var(--accent, #10b981);
2712
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
2713
- cursor: pointer;
2714
- transition: transform 0.15s ease, box-shadow 0.15s ease;
2715
- }
2716
- .markdy-control-seek:hover::-webkit-slider-thumb {
2717
- transform: scale(1.25);
2718
- box-shadow: 0 0 8px var(--accent-glow, rgba(16, 185, 129, 0.6));
2719
- }
2720
- .markdy-control-seek::-moz-range-thumb {
2721
- width: 12px;
2722
- height: 12px;
2723
- border-radius: 50%;
2724
- background: #ffffff;
2725
- border: 2px solid var(--accent, #10b981);
2726
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
2727
- cursor: pointer;
2728
- transition: transform 0.15s ease, box-shadow 0.15s ease;
2729
- }
2730
- .markdy-control-seek:hover::-moz-range-thumb {
2731
- transform: scale(1.25);
2732
- box-shadow: 0 0 8px var(--accent-glow, rgba(16, 185, 129, 0.6));
2733
- }
2734
2681
  .markdy-control-time {
2735
2682
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
2736
2683
  font-size: 10px;
@@ -2916,44 +2863,61 @@ function ensureSceneStyles(doc) {
2916
2863
  .markdy-control-seek::-webkit-slider-runnable-track {
2917
2864
  width: 100%;
2918
2865
  height: 4.5px;
2919
- background: rgba(148, 163, 184, 0.35);
2866
+ background: linear-gradient(
2867
+ to right,
2868
+ var(--accent, #10b981) 0%,
2869
+ var(--accent, #10b981) var(--seek-pct, 0%),
2870
+ rgba(148, 163, 184, 0.35) var(--seek-pct, 0%),
2871
+ rgba(148, 163, 184, 0.35) 100%
2872
+ );
2920
2873
  border-radius: 9999px;
2921
- transition: background 0.15s ease;
2922
- }
2923
- .markdy-control-seek:hover::-webkit-slider-runnable-track {
2924
- background: rgba(148, 163, 184, 0.55);
2925
2874
  }
2926
2875
  .markdy-control-seek::-webkit-slider-thumb {
2876
+ appearance: none;
2927
2877
  -webkit-appearance: none;
2928
2878
  height: 13px;
2929
2879
  width: 13px;
2930
2880
  border-radius: 50%;
2931
- background: #2563eb;
2881
+ background: var(--accent, #10b981);
2932
2882
  border: 2px solid #ffffff;
2933
2883
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
2934
2884
  margin-top: -4.25px;
2935
2885
  cursor: grab;
2936
- transition: transform 0.1s ease, background 0.15s ease;
2886
+ transition: transform 0.1s ease, box-shadow 0.15s ease;
2887
+ }
2888
+ .markdy-control-seek:hover::-webkit-slider-thumb {
2889
+ transform: scale(1.2);
2890
+ box-shadow: 0 0 8px var(--accent-glow, rgba(16, 185, 129, 0.6));
2937
2891
  }
2938
2892
  .markdy-control-seek:active::-webkit-slider-thumb {
2939
2893
  cursor: grabbing;
2940
2894
  transform: scale(1.2);
2941
- background: #1d4ed8;
2942
2895
  }
2943
2896
  .markdy-control-seek::-moz-range-track {
2944
2897
  width: 100%;
2945
2898
  height: 4.5px;
2946
- background: rgba(148, 163, 184, 0.35);
2899
+ background: linear-gradient(
2900
+ to right,
2901
+ var(--accent, #10b981) 0%,
2902
+ var(--accent, #10b981) var(--seek-pct, 0%),
2903
+ rgba(148, 163, 184, 0.35) var(--seek-pct, 0%),
2904
+ rgba(148, 163, 184, 0.35) 100%
2905
+ );
2947
2906
  border-radius: 9999px;
2948
2907
  }
2949
2908
  .markdy-control-seek::-moz-range-thumb {
2950
2909
  height: 13px;
2951
2910
  width: 13px;
2952
2911
  border-radius: 50%;
2953
- background: #2563eb;
2912
+ background: var(--accent, #10b981);
2954
2913
  border: 2px solid #ffffff;
2955
2914
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
2956
2915
  cursor: grab;
2916
+ transition: transform 0.1s ease, box-shadow 0.15s ease;
2917
+ }
2918
+ .markdy-control-seek:hover::-moz-range-thumb {
2919
+ transform: scale(1.2);
2920
+ box-shadow: 0 0 8px var(--accent-glow, rgba(16, 185, 129, 0.6));
2957
2921
  }
2958
2922
 
2959
2923
  [data-markdy-theme="midnight"] .markdy-controls button,
@@ -2974,9 +2938,9 @@ function ensureSceneStyles(doc) {
2974
2938
  [data-markdy-theme="nebula"] .markdy-controls button:hover:not([aria-pressed="true"]),
2975
2939
  :root[data-theme="dark"] .markdy-controls button:hover:not([aria-pressed="true"]),
2976
2940
  .theme-dark .markdy-controls button:hover:not([aria-pressed="true"]) {
2977
- background: rgba(51, 65, 85, 0.95);
2978
- color: #f8fafc;
2979
- border-color: #94a3b8;
2941
+ background: var(--md-control-hover-bg, rgba(51, 65, 85, 0.95));
2942
+ color: var(--md-control-hover-text, #f8fafc);
2943
+ border-color: var(--md-control-hover-border, #94a3b8);
2980
2944
  }
2981
2945
  [data-markdy-theme="midnight"] .markdy-controls button[aria-pressed="true"],
2982
2946
  [data-markdy-theme="blueprint"] .markdy-controls button[aria-pressed="true"],
@@ -2985,10 +2949,10 @@ function ensureSceneStyles(doc) {
2985
2949
  [data-markdy-theme="nebula"] .markdy-controls button[aria-pressed="true"],
2986
2950
  :root[data-theme="dark"] .markdy-controls button[aria-pressed="true"],
2987
2951
  .theme-dark .markdy-controls button[aria-pressed="true"] {
2988
- background: #3b82f6 !important;
2952
+ background: var(--accent, #3b82f6) !important;
2989
2953
  color: #ffffff !important;
2990
- border-color: #3b82f6 !important;
2991
- box-shadow: 0 0 10px rgba(59, 130, 246, 0.5) !important;
2954
+ border-color: var(--accent, #3b82f6) !important;
2955
+ box-shadow: 0 0 10px var(--accent-glow, rgba(59, 130, 246, 0.5)) !important;
2992
2956
  }
2993
2957
  .markdy-footer a {
2994
2958
  transition: opacity 0.15s ease, color 0.15s ease;
@@ -3082,41 +3046,56 @@ function ensureSceneStyles(doc) {
3082
3046
  `;
3083
3047
  doc.head.appendChild(style);
3084
3048
  }
3085
- function applyThemeToScene(scene, theme) {
3086
- scene.style.background = theme.canvas;
3087
- scene.style.color = theme.text;
3088
- scene.style.setProperty("--md-canvas", theme.canvas);
3089
- scene.style.setProperty("--md-surface", theme.surface);
3090
- scene.style.setProperty("--md-surface-raised", theme.surfaceRaised);
3091
- scene.style.setProperty("--md-border", theme.border);
3092
- scene.style.setProperty("--md-text", theme.text);
3093
- scene.style.setProperty("--md-text-muted", theme.textMuted);
3094
- scene.style.setProperty("--md-paper", theme.paper ?? theme.canvas);
3095
- scene.style.setProperty("--md-ink", theme.ink ?? theme.text);
3096
- scene.style.setProperty("--md-muted", theme.muted ?? theme.textMuted);
3097
- scene.style.setProperty("--md-rule", theme.rule ?? theme.border);
3098
- scene.style.setProperty("--md-grid-minor", theme.gridMinor);
3099
- scene.style.setProperty("--md-grid-major", theme.gridMajor);
3100
- scene.style.setProperty("--md-vignette", theme.vignette);
3101
- scene.style.setProperty("--md-accent", theme.accent);
3102
- if (theme.link) scene.style.setProperty("--md-link", theme.link);
3103
- if (theme.soft) scene.style.setProperty("--md-soft", theme.soft);
3104
- if (theme.accentTint) scene.style.setProperty("--md-accent-tint", theme.accentTint);
3105
- scene.style.setProperty("--md-node-surface", theme.nodeSurface ?? theme.surface);
3106
- scene.style.setProperty("--md-node-surface-raised", theme.nodeSurfaceRaised ?? theme.surfaceRaised);
3107
- scene.style.setProperty("--md-hairline", theme.hairline ?? `color-mix(in srgb, ${theme.border} 50%, transparent)`);
3108
- scene.style.setProperty("--md-shadow", theme.shadow ?? "rgba(2, 6, 23, 0.55)");
3109
- if (theme.fonts?.title) scene.style.setProperty("--md-font-title", theme.fonts.title);
3110
- if (theme.fonts?.nodeName) scene.style.setProperty("--md-font-node", theme.fonts.nodeName);
3111
- if (theme.fonts?.mono) scene.style.setProperty("--md-font-mono", theme.fonts.mono);
3112
- if (theme.radiusMd) scene.style.setProperty("--md-radius-md", `${theme.radiusMd}px`);
3049
+ function applyThemeVariables(element, theme) {
3050
+ element.style.setProperty("--md-canvas", theme.canvas);
3051
+ element.style.setProperty("--md-surface", theme.surface);
3052
+ element.style.setProperty("--md-surface-raised", theme.surfaceRaised);
3053
+ element.style.setProperty("--md-border", theme.border);
3054
+ element.style.setProperty("--md-text", theme.text);
3055
+ element.style.setProperty("--md-text-muted", theme.textMuted);
3056
+ element.style.setProperty("--md-paper", theme.paper ?? theme.canvas);
3057
+ element.style.setProperty("--md-ink", theme.ink ?? theme.text);
3058
+ element.style.setProperty("--md-muted", theme.muted ?? theme.textMuted);
3059
+ element.style.setProperty("--md-rule", theme.rule ?? theme.border);
3060
+ element.style.setProperty("--md-grid-minor", theme.gridMinor);
3061
+ element.style.setProperty("--md-grid-major", theme.gridMajor);
3062
+ element.style.setProperty("--md-vignette", theme.vignette);
3063
+ element.style.setProperty("--md-accent", theme.accent);
3064
+ element.style.setProperty("--accent", theme.accent);
3065
+ element.style.setProperty("--accent-glow", `color-mix(in srgb, ${theme.accent} 40%, transparent)`);
3066
+ element.style.setProperty("--accent-light", theme.accentTint ?? `color-mix(in srgb, ${theme.accent} 15%, transparent)`);
3067
+ if (theme.link) element.style.setProperty("--md-link", theme.link);
3068
+ if (theme.soft) element.style.setProperty("--md-soft", theme.soft);
3069
+ if (theme.accentTint) element.style.setProperty("--md-accent-tint", theme.accentTint);
3070
+ element.style.setProperty("--md-node-surface", theme.nodeSurface ?? theme.surface);
3071
+ element.style.setProperty("--md-node-surface-raised", theme.nodeSurfaceRaised ?? theme.surfaceRaised);
3072
+ element.style.setProperty("--md-hairline", theme.hairline ?? `color-mix(in srgb, ${theme.border} 50%, transparent)`);
3073
+ element.style.setProperty("--md-shadow", theme.shadow ?? "rgba(2, 6, 23, 0.55)");
3074
+ element.style.setProperty("--md-footer-bg", `color-mix(in srgb, ${theme.surface} 92%, transparent)`);
3075
+ element.style.setProperty("--md-footer-border", theme.border);
3076
+ element.style.setProperty("--md-control-bg", theme.surfaceRaised);
3077
+ element.style.setProperty("--md-control-border", theme.border);
3078
+ element.style.setProperty("--md-control-text", theme.textMuted);
3079
+ element.style.setProperty("--md-control-hover-bg", theme.canvas);
3080
+ element.style.setProperty("--md-control-hover-text", theme.text);
3081
+ element.style.setProperty("--md-control-hover-border", theme.accent);
3082
+ element.style.setProperty("--md-divider", theme.border);
3083
+ if (theme.fonts?.title) element.style.setProperty("--md-font-title", theme.fonts.title);
3084
+ if (theme.fonts?.nodeName) element.style.setProperty("--md-font-node", theme.fonts.nodeName);
3085
+ if (theme.fonts?.mono) element.style.setProperty("--md-font-mono", theme.fonts.mono);
3086
+ if (theme.radiusMd) element.style.setProperty("--md-radius-md", `${theme.radiusMd}px`);
3113
3087
  if (theme.spacing) {
3114
3088
  for (const [key, value] of Object.entries(theme.spacing)) {
3115
- scene.style.setProperty(`--md-space-${key}`, `${value}px`);
3089
+ element.style.setProperty(`--md-space-${key}`, `${value}px`);
3116
3090
  }
3117
3091
  }
3118
- scene.dataset.markdyTheme = theme.name;
3119
- if (theme.flatCards) scene.dataset.flat = "1";
3092
+ element.dataset.markdyTheme = theme.name;
3093
+ if (theme.flatCards) element.dataset.flat = "1";
3094
+ }
3095
+ function applyThemeToScene(scene, theme) {
3096
+ applyThemeVariables(scene, theme);
3097
+ scene.style.background = theme.canvas;
3098
+ scene.style.color = theme.text;
3120
3099
  }
3121
3100
 
3122
3101
  // src/diagram.ts
@@ -3287,11 +3266,12 @@ function createDiagram(opts) {
3287
3266
  if (footer) return footer;
3288
3267
  footer = document.createElement("div");
3289
3268
  footer.className = "markdy-footer";
3269
+ applyThemeVariables(footer, plan.theme);
3290
3270
  Object.assign(footer.style, {
3291
3271
  display: "flex",
3292
3272
  alignItems: "center",
3293
3273
  justifyContent: "space-between",
3294
- flexWrap: "wrap",
3274
+ flexWrap: "nowrap",
3295
3275
  gap: "6px",
3296
3276
  width: "100%",
3297
3277
  boxSizing: "border-box",
@@ -3309,9 +3289,12 @@ function createDiagram(opts) {
3309
3289
  badge.target = "_blank";
3310
3290
  badge.rel = "noopener noreferrer";
3311
3291
  badge.title = "Open and edit in Markdy Playground";
3312
- badge.style.marginLeft = "auto";
3313
- badge.innerHTML = `<span style="opacity:0.9">\u26A1</span> Markdy`;
3292
+ badge.textContent = "Powered by Markdy";
3314
3293
  ensureFooter().appendChild(badge);
3294
+ const badgeLink = badge;
3295
+ void compressMarkdyToUrlHash(code).then((hash) => {
3296
+ badgeLink.href = `${MARKDY_PLAYGROUND_URL}#code=${hash}`;
3297
+ }).catch(() => void 0);
3315
3298
  }
3316
3299
  ensureSceneStyles(document);
3317
3300
  ensureNodeStyles(document);
@@ -3540,6 +3523,7 @@ function createDiagram(opts) {
3540
3523
  let controlsSeekBar = null;
3541
3524
  let controlsTimeEl = null;
3542
3525
  let controlsFitButton = null;
3526
+ let closeCodePanel = null;
3543
3527
  let fitViewActive = false;
3544
3528
  const ICONS2 = {
3545
3529
  play: '<svg class="markdy-icon" width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>',
@@ -3759,8 +3743,9 @@ function createDiagram(opts) {
3759
3743
  for (const anim of allAnims) anim.cancel();
3760
3744
  resizeObserver?.disconnect();
3761
3745
  removeFullscreenListeners?.();
3746
+ closeCodePanel?.();
3747
+ closeCodePanel = null;
3762
3748
  if (progressEl?.parentNode === viewport) viewport.removeChild(progressEl);
3763
- if (badge?.parentNode) badge.parentNode.removeChild(badge);
3764
3749
  if (footer?.parentNode) footer.parentNode.removeChild(footer);
3765
3750
  if (viewport.parentNode === container) container.removeChild(viewport);
3766
3751
  }
@@ -3927,19 +3912,33 @@ function createDiagram(opts) {
3927
3912
  }
3928
3913
  function tintCode(raw) {
3929
3914
  return raw.split("\n").map((line) => {
3930
- if (/^\s*\/\//.test(line)) {
3931
- return `<span class="t-comment">${escHtml(line)}</span>`;
3932
- }
3933
- line = line.replace(/(->|<-|~>|--|<~)/g, '<span class="t-edge">$1</span>');
3934
- line = line.replace(/"([^"]*)"/g, '"<span class="t-string">$1</span>"');
3935
- line = line.replace(
3936
- /^(\s*)(scene|beat|group|player|show|frame|glow|focus|layout|edge|annotation|start|end|decision|service|browser|gateway|database|cache|queue|worker|function|pod|user|client|hub|station|metric|mobile)\b/,
3937
- '$1<span class="t-keyword">$2</span>'
3915
+ if (/^\s*\/\//.test(line)) return `<span class="t-comment">${escHtml(line)}</span>`;
3916
+ const keyword = line.match(
3917
+ /^(\s*)(scene|beat|group|player|show|frame|glow|focus|layout|edge|annotation|start|end|decision|service|browser|gateway|database|cache|queue|worker|function|pod|user|client|hub|station|metric|mobile)\b/
3938
3918
  );
3939
- line = line.replace(/\b(\d[\d.]*(?:ms|px|s)?)\b/g, '<span class="t-number">$1</span>');
3940
- return line;
3919
+ const prefix = keyword ? `${escHtml(keyword[1])}<span class="t-keyword">${keyword[2]}</span>` : "";
3920
+ return prefix + tintInlineCode(line.slice(keyword?.[0].length ?? 0));
3941
3921
  }).join("\n");
3942
3922
  }
3923
+ function tintInlineCode(raw) {
3924
+ const tokenPattern = /"[^"]*"|->|<-|~>|--|<~|\b\d[\d.]*(?:ms|px|s)?\b/g;
3925
+ let output = "";
3926
+ let cursor = 0;
3927
+ for (const match of raw.matchAll(tokenPattern)) {
3928
+ const token = match[0];
3929
+ const index = match.index ?? 0;
3930
+ output += escHtml(raw.slice(cursor, index));
3931
+ if (token.startsWith('"')) {
3932
+ output += `"<span class="t-string">${escHtml(token.slice(1, -1))}</span>"`;
3933
+ } else if (/^\d/.test(token)) {
3934
+ output += `<span class="t-number">${token}</span>`;
3935
+ } else {
3936
+ output += `<span class="t-edge">${escHtml(token)}</span>`;
3937
+ }
3938
+ cursor = index + token.length;
3939
+ }
3940
+ return output + escHtml(raw.slice(cursor));
3941
+ }
3943
3942
  function escHtml(str) {
3944
3943
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3945
3944
  }
@@ -3949,6 +3948,7 @@ function createDiagram(opts) {
3949
3948
  button.className = "markdy-control-code";
3950
3949
  button.setAttribute("aria-haspopup", "dialog");
3951
3950
  button.addEventListener("click", () => {
3951
+ closeCodePanel?.();
3952
3952
  const overlay = document.createElement("div");
3953
3953
  overlay.className = "markdy-code-panel-overlay";
3954
3954
  overlay.setAttribute("role", "dialog");
@@ -4006,24 +4006,28 @@ function createDiagram(opts) {
4006
4006
  overlay.appendChild(panel);
4007
4007
  document.body.appendChild(overlay);
4008
4008
  closeBtn.focus();
4009
+ let closing = false;
4010
+ function removePanel() {
4011
+ document.removeEventListener("keydown", handleEscape);
4012
+ overlay.remove();
4013
+ if (closeCodePanel === removePanel) closeCodePanel = null;
4014
+ }
4009
4015
  function closePanel() {
4016
+ if (closing) return;
4017
+ closing = true;
4010
4018
  overlay.dataset.closing = "1";
4011
- overlay.addEventListener("animationend", () => overlay.remove(), { once: true });
4019
+ document.removeEventListener("keydown", handleEscape);
4020
+ overlay.addEventListener("animationend", removePanel, { once: true });
4012
4021
  }
4013
4022
  closeBtn.addEventListener("click", closePanel);
4014
4023
  overlay.addEventListener("click", (e) => {
4015
4024
  if (e.target === overlay) closePanel();
4016
4025
  });
4017
4026
  function handleEscape(e) {
4018
- if (e.key === "Escape") {
4019
- closePanel();
4020
- document.removeEventListener("keydown", handleEscape);
4021
- }
4027
+ if (e.key === "Escape") closePanel();
4022
4028
  }
4023
4029
  document.addEventListener("keydown", handleEscape);
4024
- overlay.addEventListener("animationend", () => {
4025
- if (overlay.dataset.closing === "1") document.removeEventListener("keydown", handleEscape);
4026
- }, { once: true });
4030
+ closeCodePanel = removePanel;
4027
4031
  });
4028
4032
  toolbar.appendChild(button);
4029
4033
  }
@@ -4230,7 +4234,7 @@ function createDiagram(opts) {
4230
4234
  mountCodeControl(toolsGroup);
4231
4235
  }
4232
4236
  if (toolsGroup.children.length > 0) toolbar.appendChild(toolsGroup);
4233
- ensureFooter().insertBefore(toolbar, badge ?? null);
4237
+ ensureFooter().insertBefore(toolbar, badge);
4234
4238
  syncControls();
4235
4239
  }
4236
4240
  viewport.style.cursor = interactiveViewport ? "grab" : "pointer";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "Browser renderer for diagram-native animated MarkdyScript architecture diagrams, built on the Web Animations API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,14 +45,14 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "html2canvas": "^1.4.1",
48
- "@markdy/core": "1.0.17"
48
+ "@markdy/core": "1.0.19"
49
49
  },
50
50
  "devDependencies": {
51
51
  "jsdom": "^29.1.1",
52
52
  "tsup": "^8.5.1",
53
53
  "typescript": "^5.9.3",
54
54
  "vitest": "^4.1.7",
55
- "@markdy/stdlib-systems": "1.0.17"
55
+ "@markdy/stdlib-systems": "1.0.19"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsup",