@markdy/core 1.0.10 → 1.0.12

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/index.d.ts CHANGED
@@ -6,6 +6,66 @@ type LayoutDirection = "LR" | "RL" | "TB" | "BT";
6
6
  type EdgeKind = "request" | "response" | "event" | "dependency";
7
7
  type DiagramType = "architecture" | "flowchart" | "tree" | "state" | "sequence" | "constellation" | "loop" | "flywheel" | "medallion" | "quadrant" | "swimlane" | "pyramid" | "radar" | "timeline" | "gantt" | "venn" | "layers" | "nested";
8
8
  type NodeShape = "card" | "rounded" | "diamond" | "circle" | "pill" | "terminal" | "container";
9
+ type PlayerProgress = "none" | "bar" | "boundary";
10
+ /** When and how fast the timeline runs. */
11
+ type PlayerPlaybackConfig = {
12
+ autoplay?: boolean;
13
+ loop?: boolean;
14
+ rate?: number;
15
+ };
16
+ /** Which toolbar affordances are mounted. Declaring the group opts in. */
17
+ type PlayerControlsConfig = {
18
+ play?: boolean;
19
+ restart?: boolean;
20
+ prevBeat?: boolean;
21
+ nextBeat?: boolean;
22
+ seek?: boolean;
23
+ speed?: boolean;
24
+ fit?: boolean;
25
+ resetView?: boolean;
26
+ fullscreen?: boolean;
27
+ svg?: boolean;
28
+ share?: boolean;
29
+ /** Speed multipliers offered by the speed buttons. */
30
+ speeds?: number[];
31
+ };
32
+ /** What pointer and key input do. Declaring the group opts in. */
33
+ type PlayerInteractionConfig = {
34
+ zoom?: boolean;
35
+ pan?: boolean;
36
+ clickToPlay?: boolean;
37
+ doubleClickToReset?: boolean;
38
+ /** Window-level shortcuts; opt-in because they capture space and arrows. */
39
+ keyboard?: boolean;
40
+ };
41
+ /** Non-interactive decoration drawn around the scene. */
42
+ type PlayerChromeConfig = {
43
+ badge?: boolean;
44
+ progress?: PlayerProgress;
45
+ progressColor?: string;
46
+ };
47
+ type PlayerConfig = {
48
+ playback?: PlayerPlaybackConfig;
49
+ controls?: PlayerControlsConfig;
50
+ interaction?: PlayerInteractionConfig;
51
+ chrome?: PlayerChromeConfig;
52
+ };
53
+ /** Fully defaulted player behavior produced by `resolvePlayer`. `enabled` is
54
+ * derived: a group is on when at least one of its affordances is on. */
55
+ type ResolvedPlayer = {
56
+ playback: Required<PlayerPlaybackConfig>;
57
+ controls: Required<PlayerControlsConfig> & {
58
+ enabled: boolean;
59
+ };
60
+ interaction: Required<PlayerInteractionConfig> & {
61
+ enabled: boolean;
62
+ };
63
+ chrome: {
64
+ badge: boolean;
65
+ progress: PlayerProgress;
66
+ progressColor?: string;
67
+ };
68
+ };
9
69
  type SceneMeta = {
10
70
  title?: string;
11
71
  width: number;
@@ -16,19 +76,21 @@ type SceneMeta = {
16
76
  duration?: number;
17
77
  /** Opt-in diagram mode; defaults to architecture. */
18
78
  type?: DiagramType;
19
- /** Optional custom progress bar color or gradient. Defaults to rainbow. */
79
+ /** Playback, controls, interaction, and chrome behavior. Source of truth. */
80
+ player?: PlayerConfig;
81
+ /** @deprecated Mirror of `player.chrome.progressColor`. */
20
82
  progressColor?: string;
21
- /** Enable playback and view reset controls toolbar. */
83
+ /** @deprecated Mirror of `player.controls.enabled`. */
22
84
  controls?: boolean;
23
- /** Enable wheel zoom and drag pan. */
85
+ /** @deprecated Mirror of `player.interaction.enabled`. */
24
86
  interactiveViewport?: boolean;
25
- /** Autoplay timeline on load. */
87
+ /** @deprecated Mirror of `player.playback.autoplay`. */
26
88
  autoplay?: boolean;
27
- /** Loop playback when reaching the end. */
89
+ /** @deprecated Mirror of `player.playback.loop`. */
28
90
  loop?: boolean;
29
- /** Show copyright badge. */
91
+ /** @deprecated Mirror of `player.chrome.badge`. */
30
92
  copyright?: boolean;
31
- /** Default playback speed multiplier. */
93
+ /** @deprecated Mirror of `player.playback.rate`. */
32
94
  playbackRate?: number;
33
95
  };
34
96
  type NodeDecl = {
@@ -326,6 +388,36 @@ declare function parse(source: string, opts?: ParseOptions): DiagramAST;
326
388
  declare function compile(ast: DiagramAST): RenderPlan;
327
389
  declare function parseAndCompile(source: string): ParseResult;
328
390
 
391
+ /**
392
+ * Single source of truth for player configuration: schema, aliases, parsing,
393
+ * and default resolution. Hosts (DOM renderer, Astro, MDX, CLI) resolve
394
+ * behavior through `resolvePlayer` instead of re-deriving defaults.
395
+ */
396
+
397
+ /** Groups own their own alias table, so `speed` can mean rate at the player
398
+ * root and the speed buttons inside `controls:`. */
399
+ type PlayerScope = "player" | "playback" | "controls" | "interaction" | "chrome";
400
+ declare const PLAYER_GROUPS: readonly ["playback", "controls", "interaction", "chrome"];
401
+ /** Keys usable as `scene` props and top-level directives. */
402
+ declare const PLAYER_FLAT_KEYS: string[];
403
+ /**
404
+ * Applies one `key value` pair into `config`. Returns an error message when the
405
+ * key is unknown or the value does not fit the setting type.
406
+ */
407
+ declare function applyPlayerSetting(config: PlayerConfig, scope: PlayerScope, key: string, rawValue: string): string | undefined;
408
+ /** Host-supplied overrides always win over script configuration. */
409
+ type PlayerOverrides = {
410
+ autoplay?: boolean;
411
+ loop?: boolean;
412
+ playbackRate?: number;
413
+ copyright?: boolean;
414
+ controls?: boolean;
415
+ interactiveViewport?: boolean;
416
+ progress?: PlayerProgress;
417
+ progressColor?: string;
418
+ };
419
+ declare function resolvePlayer(config?: PlayerConfig, overrides?: PlayerOverrides): ResolvedPlayer;
420
+
329
421
  declare const THEMES: Record<string, ThemeTokens>;
330
422
  declare function resolveTheme(name: string): ThemeTokens;
331
423
 
@@ -537,4 +629,4 @@ declare function resolveOutputPreset(name: string): OutputPreset;
537
629
  /** List all available preset names. */
538
630
  declare function listOutputPresets(): string[];
539
631
 
540
- export { ARCH_RULE_PRESETS, type AnnotationDecl, type ArchRuleType, type ArchitecturePreset, type ArchitectureRule, type ArchitectureViolation, BEAT_CUE_KEYWORDS, type BeatDecl, type BeatRange, type Box, CUE_ALIASES, type CardinalPort, type Cue, DIAGRAM_TYPES, type Diagnostic, type DiagramAST, type DiagramDiffResult, type DiagramType, type DiffChangeType, EDGE_OPERATORS, type EdgeDecl, type EdgeDiff, type EdgeKind, type EdgeSelector, type FlowSegment, type GroupBoundary, type GroupDecl, type LayoutDirection, type MarkdyConfig, NODE_ALIASES, NODE_KINDS, type NodeDecl, type NodeDiff, type NodeSelector, type NodeShape, OUTPUT_PRESETS, type OutputPreset, ParseError, type ParseOptions, type ParseResult, type PatternDecl, type Point, type PositionedNode, type RenderPlan, type RepairPromptBundle, type RoutedEdge, type RoutedPath, type RuleSeverity, SCENE_KEYS, type SceneMeta, type SemanticProfile, type SequenceActivation, type SequenceMessage, type StyleDecl, TECHNICAL_NODE_KINDS, TECHNICAL_NODE_TYPES, THEMES, type ThemeGeneratorOptions, type ThemeTokens, type TimedCue, type TreeBus, VISUAL_PRIMITIVE_TYPES, analyzeAndBuildRepairPrompt, canonicalNodeKind, classifyTechnology, compile, compilePlan, compressMarkdyToUrlHash, decompressMarkdyFromUrlHash, diffDiagramASTs, generateThemeFromBrand, getBoxPortPosition, humanizeId, listOutputPresets, nodeRole, parse, parseAndCompile, resolveArchitectureConfig, resolveOutputPreset, resolveTheme, routeOrthogonalEdge, selectOptimalPorts, validateArchitecture };
632
+ export { ARCH_RULE_PRESETS, type AnnotationDecl, type ArchRuleType, type ArchitecturePreset, type ArchitectureRule, type ArchitectureViolation, BEAT_CUE_KEYWORDS, type BeatDecl, type BeatRange, type Box, CUE_ALIASES, type CardinalPort, type Cue, DIAGRAM_TYPES, type Diagnostic, type DiagramAST, type DiagramDiffResult, type DiagramType, type DiffChangeType, EDGE_OPERATORS, type EdgeDecl, type EdgeDiff, type EdgeKind, type EdgeSelector, type FlowSegment, type GroupBoundary, type GroupDecl, type LayoutDirection, type MarkdyConfig, NODE_ALIASES, NODE_KINDS, type NodeDecl, type NodeDiff, type NodeSelector, type NodeShape, OUTPUT_PRESETS, type OutputPreset, PLAYER_FLAT_KEYS, PLAYER_GROUPS, ParseError, type ParseOptions, type ParseResult, type PatternDecl, type PlayerChromeConfig, type PlayerConfig, type PlayerControlsConfig, type PlayerInteractionConfig, type PlayerOverrides, type PlayerPlaybackConfig, type PlayerProgress, type PlayerScope, type Point, type PositionedNode, type RenderPlan, type RepairPromptBundle, type ResolvedPlayer, type RoutedEdge, type RoutedPath, type RuleSeverity, SCENE_KEYS, type SceneMeta, type SemanticProfile, type SequenceActivation, type SequenceMessage, type StyleDecl, TECHNICAL_NODE_KINDS, TECHNICAL_NODE_TYPES, THEMES, type ThemeGeneratorOptions, type ThemeTokens, type TimedCue, type TreeBus, VISUAL_PRIMITIVE_TYPES, analyzeAndBuildRepairPrompt, applyPlayerSetting, canonicalNodeKind, classifyTechnology, compile, compilePlan, compressMarkdyToUrlHash, decompressMarkdyFromUrlHash, diffDiagramASTs, generateThemeFromBrand, getBoxPortPosition, humanizeId, listOutputPresets, nodeRole, parse, parseAndCompile, resolveArchitectureConfig, resolveOutputPreset, resolvePlayer, resolveTheme, routeOrthogonalEdge, selectOptimalPorts, validateArchitecture };
package/dist/index.js CHANGED
@@ -378,6 +378,270 @@ var TECHNICAL_NODE_KINDS = {
378
378
  lock: "distributed"
379
379
  };
380
380
 
381
+ // src/player.ts
382
+ function parseBooleanToken(raw) {
383
+ if (typeof raw === "boolean") return raw;
384
+ const s = String(raw).trim().toLowerCase();
385
+ if (["true", "on", "yes", "1"].includes(s)) return true;
386
+ if (["false", "off", "no", "0"].includes(s)) return false;
387
+ return void 0;
388
+ }
389
+ var CONTROL_KEYS = [
390
+ "play",
391
+ "restart",
392
+ "prevBeat",
393
+ "nextBeat",
394
+ "seek",
395
+ "speed",
396
+ "fit",
397
+ "resetView",
398
+ "fullscreen",
399
+ "svg",
400
+ "share"
401
+ ];
402
+ var INTERACTION_KEYS = ["zoom", "pan", "doubleClickToReset"];
403
+ var PLAYBACK = {
404
+ autoplay: { group: "playback", key: "autoplay", type: "boolean" },
405
+ loop: { group: "playback", key: "loop", type: "boolean" },
406
+ rate: { group: "playback", key: "rate", type: "rate" },
407
+ speed: { group: "playback", key: "rate", type: "rate" },
408
+ playbackRate: { group: "playback", key: "rate", type: "rate" },
409
+ playback_rate: { group: "playback", key: "rate", type: "rate" }
410
+ };
411
+ var CONTROLS = {
412
+ controls: { group: "controls", key: "*", type: "group" },
413
+ play: { group: "controls", key: "play", type: "boolean" },
414
+ playButton: { group: "controls", key: "play", type: "boolean" },
415
+ play_button: { group: "controls", key: "play", type: "boolean" },
416
+ restart: { group: "controls", key: "restart", type: "boolean" },
417
+ restartButton: { group: "controls", key: "restart", type: "boolean" },
418
+ restart_button: { group: "controls", key: "restart", type: "boolean" },
419
+ prevBeat: { group: "controls", key: "prevBeat", type: "boolean" },
420
+ prev_beat: { group: "controls", key: "prevBeat", type: "boolean" },
421
+ nextBeat: { group: "controls", key: "nextBeat", type: "boolean" },
422
+ next_beat: { group: "controls", key: "nextBeat", type: "boolean" },
423
+ speeds: { group: "controls", key: "speeds", type: "rates" },
424
+ speedOptions: { group: "controls", key: "speeds", type: "rates" },
425
+ speed_options: { group: "controls", key: "speeds", type: "rates" },
426
+ seek: { group: "controls", key: "seek", type: "boolean" },
427
+ seekBar: { group: "controls", key: "seek", type: "boolean" },
428
+ seek_bar: { group: "controls", key: "seek", type: "boolean" },
429
+ speed: { group: "controls", key: "speed", type: "boolean" },
430
+ speedControls: { group: "controls", key: "speed", type: "boolean" },
431
+ speed_controls: { group: "controls", key: "speed", type: "boolean" },
432
+ fit: { group: "controls", key: "fit", type: "boolean" },
433
+ fitView: { group: "controls", key: "fit", type: "boolean" },
434
+ fit_view: { group: "controls", key: "fit", type: "boolean" },
435
+ fitViewButton: { group: "controls", key: "fit", type: "boolean" },
436
+ fit_view_button: { group: "controls", key: "fit", type: "boolean" },
437
+ resetView: { group: "controls", key: "resetView", type: "boolean" },
438
+ reset_view: { group: "controls", key: "resetView", type: "boolean" },
439
+ resetViewButton: { group: "controls", key: "resetView", type: "boolean" },
440
+ reset_view_button: { group: "controls", key: "resetView", type: "boolean" },
441
+ fullscreen: { group: "controls", key: "fullscreen", type: "boolean" },
442
+ fullScreen: { group: "controls", key: "fullscreen", type: "boolean" },
443
+ full_screen: { group: "controls", key: "fullscreen", type: "boolean" },
444
+ fullscreenButton: { group: "controls", key: "fullscreen", type: "boolean" },
445
+ fullscreen_button: { group: "controls", key: "fullscreen", type: "boolean" },
446
+ svg: { group: "controls", key: "svg", type: "boolean" },
447
+ exportSvg: { group: "controls", key: "svg", type: "boolean" },
448
+ export_svg: { group: "controls", key: "svg", type: "boolean" },
449
+ share: { group: "controls", key: "share", type: "boolean" },
450
+ shareLink: { group: "controls", key: "share", type: "boolean" },
451
+ share_link: { group: "controls", key: "share", type: "boolean" }
452
+ };
453
+ var INTERACTION = {
454
+ interactive: { group: "interaction", key: "*", type: "group" },
455
+ interactiveViewport: { group: "interaction", key: "*", type: "group" },
456
+ interactive_viewport: { group: "interaction", key: "*", type: "group" },
457
+ zoom: { group: "interaction", key: "zoom", type: "boolean" },
458
+ allowZoom: { group: "interaction", key: "zoom", type: "boolean" },
459
+ allow_zoom: { group: "interaction", key: "zoom", type: "boolean" },
460
+ pan: { group: "interaction", key: "pan", type: "boolean" },
461
+ allowPan: { group: "interaction", key: "pan", type: "boolean" },
462
+ allow_pan: { group: "interaction", key: "pan", type: "boolean" },
463
+ clickToPlay: { group: "interaction", key: "clickToPlay", type: "boolean" },
464
+ click_to_play: { group: "interaction", key: "clickToPlay", type: "boolean" },
465
+ keyboard: { group: "interaction", key: "keyboard", type: "boolean" },
466
+ shortcuts: { group: "interaction", key: "keyboard", type: "boolean" },
467
+ doubleClickToReset: { group: "interaction", key: "doubleClickToReset", type: "boolean" },
468
+ double_click_to_reset: { group: "interaction", key: "doubleClickToReset", type: "boolean" }
469
+ };
470
+ var CHROME = {
471
+ badge: { group: "chrome", key: "badge", type: "boolean" },
472
+ copyright: { group: "chrome", key: "badge", type: "boolean" },
473
+ progress: { group: "chrome", key: "progress", type: "progress" },
474
+ progressBar: { group: "chrome", key: "progress", type: "progress" },
475
+ progress_bar: { group: "chrome", key: "progress", type: "progress" },
476
+ sceneBoundaryProgress: { group: "chrome", key: "progress", type: "progress" },
477
+ progressColor: { group: "chrome", key: "progressColor", type: "color" },
478
+ progress_color: { group: "chrome", key: "progressColor", type: "color" },
479
+ progressBarColor: { group: "chrome", key: "progressColor", type: "color" },
480
+ progress_bar_color: { group: "chrome", key: "progressColor", type: "color" }
481
+ };
482
+ var FLAT = {
483
+ ...PLAYBACK,
484
+ ...CHROME,
485
+ controls: CONTROLS.controls,
486
+ playButton: CONTROLS.playButton,
487
+ play_button: CONTROLS.play_button,
488
+ restartButton: CONTROLS.restartButton,
489
+ restart_button: CONTROLS.restart_button,
490
+ seek: CONTROLS.seek,
491
+ seekBar: CONTROLS.seekBar,
492
+ seek_bar: CONTROLS.seek_bar,
493
+ speedControls: CONTROLS.speedControls,
494
+ speed_controls: CONTROLS.speed_controls,
495
+ speeds: CONTROLS.speeds,
496
+ speedOptions: CONTROLS.speedOptions,
497
+ speed_options: CONTROLS.speed_options,
498
+ prevBeat: CONTROLS.prevBeat,
499
+ prev_beat: CONTROLS.prev_beat,
500
+ nextBeat: CONTROLS.nextBeat,
501
+ next_beat: CONTROLS.next_beat,
502
+ keyboard: INTERACTION.keyboard,
503
+ shortcuts: INTERACTION.shortcuts,
504
+ fitView: CONTROLS.fitView,
505
+ fit_view: CONTROLS.fit_view,
506
+ fitViewButton: CONTROLS.fitViewButton,
507
+ fit_view_button: CONTROLS.fit_view_button,
508
+ resetViewButton: CONTROLS.resetViewButton,
509
+ reset_view_button: CONTROLS.reset_view_button,
510
+ fullscreen: CONTROLS.fullscreen,
511
+ fullScreen: CONTROLS.fullScreen,
512
+ full_screen: CONTROLS.full_screen,
513
+ fullscreenButton: CONTROLS.fullscreenButton,
514
+ fullscreen_button: CONTROLS.fullscreen_button,
515
+ exportSvg: CONTROLS.exportSvg,
516
+ export_svg: CONTROLS.export_svg,
517
+ shareLink: CONTROLS.shareLink,
518
+ share_link: CONTROLS.share_link,
519
+ interactive: INTERACTION.interactive,
520
+ interactiveViewport: INTERACTION.interactiveViewport,
521
+ interactive_viewport: INTERACTION.interactive_viewport,
522
+ allowZoom: INTERACTION.allowZoom,
523
+ allow_zoom: INTERACTION.allow_zoom,
524
+ allowPan: INTERACTION.allowPan,
525
+ allow_pan: INTERACTION.allow_pan,
526
+ clickToPlay: INTERACTION.clickToPlay,
527
+ click_to_play: INTERACTION.click_to_play,
528
+ doubleClickToReset: INTERACTION.doubleClickToReset,
529
+ double_click_to_reset: INTERACTION.double_click_to_reset
530
+ };
531
+ var SCOPES = {
532
+ player: FLAT,
533
+ playback: PLAYBACK,
534
+ controls: CONTROLS,
535
+ interaction: INTERACTION,
536
+ // `color` is unambiguous inside the block, but too generic at the root.
537
+ chrome: { ...CHROME, color: CHROME.progressColor }
538
+ };
539
+ var PLAYER_GROUPS = ["playback", "controls", "interaction", "chrome"];
540
+ var PLAYER_FLAT_KEYS = Object.keys(FLAT);
541
+ function applyPlayerSetting(config, scope, key, rawValue) {
542
+ const setting = SCOPES[scope][key];
543
+ if (!setting) return `unknown player property '${key}'`;
544
+ const value = unquote(rawValue).trim();
545
+ const group = config[setting.group] ??= {};
546
+ if (setting.type === "boolean" || setting.type === "group") {
547
+ const parsed = value ? parseBooleanToken(value) : true;
548
+ if (parsed === void 0) return `player property '${key}' expects true or false`;
549
+ if (setting.type === "boolean") group[setting.key] = parsed;
550
+ else for (const child of setting.group === "controls" ? CONTROL_KEYS : INTERACTION_KEYS) group[child] = parsed;
551
+ return void 0;
552
+ }
553
+ if (setting.type === "rate") {
554
+ const rate = Number(value);
555
+ if (!Number.isFinite(rate) || rate <= 0) return `player property '${key}' expects a positive number`;
556
+ group[setting.key] = rate;
557
+ return void 0;
558
+ }
559
+ if (setting.type === "rates") {
560
+ const rates = value.split(/[\s,]+/).filter(Boolean).map(Number);
561
+ if (!rates.length || rates.some((rate) => !Number.isFinite(rate) || rate <= 0)) {
562
+ return `player property '${key}' expects a list of positive numbers`;
563
+ }
564
+ group[setting.key] = rates;
565
+ return void 0;
566
+ }
567
+ if (setting.type === "color") {
568
+ if (value) group[setting.key] = value;
569
+ return void 0;
570
+ }
571
+ const mode = toProgressMode(value);
572
+ if (mode) group.progress = mode;
573
+ else if (value) group.progressColor = value;
574
+ return void 0;
575
+ }
576
+ function toProgressMode(value) {
577
+ const lower = value.toLowerCase();
578
+ if (lower === "none" || lower === "bar" || lower === "boundary") return lower;
579
+ const bool = parseBooleanToken(lower);
580
+ if (bool === true) return "boundary";
581
+ if (bool === false) return "none";
582
+ return void 0;
583
+ }
584
+ function unquote(raw) {
585
+ const value = raw.trim();
586
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
587
+ return value.slice(1, -1);
588
+ }
589
+ return value;
590
+ }
591
+ function resolvePlayer(config = {}, overrides = {}) {
592
+ const playback = config.playback ?? {};
593
+ const interaction = config.interaction ?? {};
594
+ const chrome = config.chrome ?? {};
595
+ const controlsOn = overrides.controls !== false && (overrides.controls === true || config.controls !== void 0);
596
+ const controls = {
597
+ play: controlsOn && (config.controls?.play ?? true),
598
+ restart: controlsOn && (config.controls?.restart ?? true),
599
+ prevBeat: controlsOn && (config.controls?.prevBeat ?? true),
600
+ nextBeat: controlsOn && (config.controls?.nextBeat ?? true),
601
+ seek: controlsOn && (config.controls?.seek ?? true),
602
+ speed: controlsOn && (config.controls?.speed ?? true),
603
+ fit: controlsOn && (config.controls?.fit ?? true),
604
+ resetView: controlsOn && (config.controls?.resetView ?? true),
605
+ fullscreen: controlsOn && (config.controls?.fullscreen ?? true),
606
+ svg: controlsOn && (config.controls?.svg ?? true),
607
+ share: controlsOn && (config.controls?.share ?? true)
608
+ };
609
+ const interactionOn = overrides.interactiveViewport !== false && (overrides.interactiveViewport === true || config.interaction !== void 0 || overrides.controls === true);
610
+ const gestures = {
611
+ zoom: interactionOn && (interaction.zoom ?? true),
612
+ pan: interactionOn && (interaction.pan ?? true),
613
+ doubleClickToReset: interactionOn && (interaction.doubleClickToReset ?? true)
614
+ };
615
+ const interactionEnabled = gestures.zoom || gestures.pan || gestures.doubleClickToReset;
616
+ const resetView = controls.resetView && interactionEnabled;
617
+ const controlsEnabled = controls.play || controls.restart || controls.prevBeat || controls.nextBeat || controls.seek || controls.speed || controls.fit || controls.resetView || controls.fullscreen || controls.svg || controls.share || resetView;
618
+ const rate = overrides.playbackRate ?? playback.rate ?? 1;
619
+ return {
620
+ playback: {
621
+ autoplay: overrides.autoplay ?? playback.autoplay ?? true,
622
+ loop: overrides.loop ?? playback.loop ?? true,
623
+ rate: Number.isFinite(rate) && rate > 0 ? rate : 1
624
+ },
625
+ controls: {
626
+ ...controls,
627
+ resetView,
628
+ enabled: controlsEnabled,
629
+ speeds: config.controls?.speeds?.length ? config.controls.speeds : [0.5, 1, 2]
630
+ },
631
+ interaction: {
632
+ ...gestures,
633
+ enabled: interactionEnabled,
634
+ clickToPlay: interaction.clickToPlay ?? true,
635
+ keyboard: interaction.keyboard ?? false
636
+ },
637
+ chrome: {
638
+ badge: overrides.copyright ?? chrome.badge ?? true,
639
+ progress: overrides.progress ?? chrome.progress ?? "boundary",
640
+ progressColor: overrides.progressColor ?? chrome.progressColor
641
+ }
642
+ };
643
+ }
644
+
381
645
  // src/registry.ts
382
646
  var NODE_KINDS = /* @__PURE__ */ new Set([
383
647
  ...TECHNICAL_NODE_TYPES,
@@ -433,23 +697,7 @@ var SCENE_KEYS = /* @__PURE__ */ new Set([
433
697
  "direction",
434
698
  "layout",
435
699
  "type",
436
- "progressColor",
437
- "progressBarColor",
438
- "progress_color",
439
- "progress_bar_color",
440
- "progress",
441
- "progressBar",
442
- "sceneBoundaryProgress",
443
- "controls",
444
- "interactive",
445
- "interactiveViewport",
446
- "interactive_viewport",
447
- "autoplay",
448
- "loop",
449
- "copyright",
450
- "playbackRate",
451
- "playback_rate",
452
- "speed"
700
+ ...PLAYER_FLAT_KEYS
453
701
  ]);
454
702
  function nodeRole(kind) {
455
703
  const canonical = canonicalNodeKind(kind);
@@ -2357,14 +2605,30 @@ function readIndentedBody(blocks, startIdx, parentIndent) {
2357
2605
  }
2358
2606
  return { body, nextIdx: i };
2359
2607
  }
2360
- function parseBooleanToken(raw) {
2361
- if (typeof raw === "boolean") return raw;
2362
- const s = String(raw).trim().toLowerCase();
2363
- if (["true", "on", "yes", "1"].includes(s)) return true;
2364
- if (["false", "off", "no", "0"].includes(s)) return false;
2365
- return void 0;
2608
+ var PLAYER_KEYWORDS = [...PLAYER_FLAT_KEYS].sort((a, b) => b.length - a.length).join("|");
2609
+ var TOP_LEVEL_KEYWORDS_RE = new RegExp(
2610
+ `^(scene|player|layout|pattern|group|annotation|edge|beat|var|style|${PLAYER_KEYWORDS})\\b`
2611
+ );
2612
+ var PLAYER_DIRECTIVE_RE = new RegExp(`^(${PLAYER_KEYWORDS})\\b(?:\\s*[:=]?\\s*(.+))?$`);
2613
+ var PLAYER_GROUP_RE = /^(playback|controls|interaction|chrome)\s*:\s*$/;
2614
+ var PLAYER_SETTING_RE = /^(\w+)(?:\s*[:=]\s*|\s+)?(.*)$/;
2615
+ var PLAYER_FLAT_KEY_SET = new Set(PLAYER_FLAT_KEYS);
2616
+ function mirrorLegacySceneMeta(meta) {
2617
+ const player = meta.player;
2618
+ if (!player) return meta;
2619
+ const { playback, controls, interaction, chrome } = player;
2620
+ if (playback?.autoplay !== void 0) meta.autoplay = playback.autoplay;
2621
+ if (playback?.loop !== void 0) meta.loop = playback.loop;
2622
+ if (playback?.rate !== void 0) meta.playbackRate = playback.rate;
2623
+ if (controls) meta.controls = Object.values(controls).some(Boolean);
2624
+ if (interaction) {
2625
+ meta.interactiveViewport = Boolean(interaction.zoom ?? true) || Boolean(interaction.pan ?? true);
2626
+ }
2627
+ if (chrome?.badge !== void 0) meta.copyright = chrome.badge;
2628
+ if (chrome?.progress === "none") meta.progressColor = "none";
2629
+ else if (chrome?.progressColor !== void 0) meta.progressColor = chrome.progressColor;
2630
+ return meta;
2366
2631
  }
2367
- var TOP_LEVEL_KEYWORDS_RE = /^(scene|layout|pattern|group|annotation|edge|beat|var|style|controls|interactive|interactiveViewport|interactive_viewport|autoplay|loop|copyright|playbackRate|playback_rate|speed|progressColor|progressBarColor|progress_color|progress_bar_color|progress|progressBar)\b/;
2368
2632
  function isTopLevelStatement(line) {
2369
2633
  if (line === "}") return true;
2370
2634
  if (TOP_LEVEL_KEYWORDS_RE.test(line)) return true;
@@ -2617,6 +2881,35 @@ function parse(source, opts = {}) {
2617
2881
  const lineNo = block.line;
2618
2882
  const unsupportedMessage = unsupportedSyntaxMessage(line);
2619
2883
  if (unsupportedMessage) throw new ParseError(unsupportedMessage, lineNo);
2884
+ if (/^player\s*:\s*$/.test(line)) {
2885
+ const { body, nextIdx } = readIndentedBody(blocks, i + 1, block.indent);
2886
+ if (body.length === 0) throw new ParseError("player block requires at least one setting", lineNo);
2887
+ const player = meta.player ??= {};
2888
+ const applySetting = (scope, setting) => {
2889
+ const match = setting.text.match(PLAYER_SETTING_RE);
2890
+ const error = applyPlayerSetting(player, scope, match?.[1] ?? "", match?.[2] ?? "");
2891
+ if (error) diagnostics.push({ severity: "warning", message: error, line: setting.line });
2892
+ };
2893
+ let settingIdx = 0;
2894
+ while (settingIdx < body.length) {
2895
+ const setting = body[settingIdx];
2896
+ const groupMatch = setting.text.match(PLAYER_GROUP_RE);
2897
+ if (groupMatch) {
2898
+ const scope = groupMatch[1];
2899
+ const { body: childBody, nextIdx: childNextIdx } = readIndentedBody(body, settingIdx + 1, setting.indent);
2900
+ if (childBody.length === 0) {
2901
+ throw new ParseError(`player ${scope} block requires at least one setting`, setting.line);
2902
+ }
2903
+ for (const child of childBody) applySetting(scope, child);
2904
+ settingIdx = childNextIdx;
2905
+ continue;
2906
+ }
2907
+ applySetting("player", setting);
2908
+ settingIdx++;
2909
+ }
2910
+ i = nextIdx;
2911
+ continue;
2912
+ }
2620
2913
  if (line.startsWith("scene")) {
2621
2914
  const rest2 = line.slice(5).trim();
2622
2915
  if (line.endsWith("{")) {
@@ -2643,28 +2936,9 @@ function parse(source, opts = {}) {
2643
2936
  else if (k === "duration") meta.duration = Number(v);
2644
2937
  else if (k === "theme") meta.theme = String(v);
2645
2938
  else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
2646
- else if (k === "progressColor" || k === "progressBarColor" || k === "progress_color" || k === "progress_bar_color" || k === "progress") {
2647
- meta.progressColor = String(v);
2648
- } else if (k === "progressBar" || k === "sceneBoundaryProgress") {
2649
- const bool = parseBooleanToken(v);
2650
- if (bool !== void 0) {
2651
- if (!bool) meta.progressColor = "none";
2652
- } else {
2653
- meta.progressColor = String(v);
2654
- }
2655
- } else if (k === "controls") {
2656
- meta.controls = parseBooleanToken(v) ?? true;
2657
- } else if (k === "interactive" || k === "interactiveViewport" || k === "interactive_viewport") {
2658
- meta.interactiveViewport = parseBooleanToken(v) ?? true;
2659
- } else if (k === "autoplay") {
2660
- meta.autoplay = parseBooleanToken(v) ?? true;
2661
- } else if (k === "loop") {
2662
- meta.loop = parseBooleanToken(v) ?? true;
2663
- } else if (k === "copyright") {
2664
- meta.copyright = parseBooleanToken(v) ?? true;
2665
- } else if (k === "playbackRate" || k === "playback_rate" || k === "speed") {
2666
- const r = Number(v);
2667
- if (!Number.isNaN(r) && r > 0) meta.playbackRate = r;
2939
+ else if (PLAYER_FLAT_KEY_SET.has(k)) {
2940
+ const error = applyPlayerSetting(meta.player ??= {}, "player", k, String(v));
2941
+ if (error) diagnostics.push({ severity: "warning", message: error, line: lineNo });
2668
2942
  } else if (k === "type") {
2669
2943
  const t = String(v).toLowerCase();
2670
2944
  if (!DIAGRAM_TYPES.has(t)) {
@@ -2677,33 +2951,10 @@ function parse(source, opts = {}) {
2677
2951
  i++;
2678
2952
  continue;
2679
2953
  }
2680
- const directiveMatch = line.match(
2681
- /^(controls|interactive|interactiveViewport|interactive_viewport|autoplay|loop|copyright|playbackRate|playback_rate|speed|progressColor|progressBarColor|progress_color|progress_bar_color|progress|progressBar)\b(?:\s*[:=]?\s*(.+))?$/
2682
- );
2954
+ const directiveMatch = line.match(PLAYER_DIRECTIVE_RE);
2683
2955
  if (directiveMatch) {
2684
- const keyword = directiveMatch[1];
2685
- const rawVal = (directiveMatch[2] ?? "").trim();
2686
- const val = rawVal.startsWith('"') && rawVal.endsWith('"') || rawVal.startsWith("'") && rawVal.endsWith("'") ? rawVal.slice(1, -1) : rawVal;
2687
- if (keyword === "controls") {
2688
- meta.controls = val ? parseBooleanToken(val) ?? true : true;
2689
- } else if (keyword === "interactive" || keyword === "interactiveViewport" || keyword === "interactive_viewport") {
2690
- meta.interactiveViewport = val ? parseBooleanToken(val) ?? true : true;
2691
- } else if (keyword === "autoplay") {
2692
- meta.autoplay = val ? parseBooleanToken(val) ?? true : true;
2693
- } else if (keyword === "loop") {
2694
- meta.loop = val ? parseBooleanToken(val) ?? true : true;
2695
- } else if (keyword === "copyright") {
2696
- meta.copyright = val ? parseBooleanToken(val) ?? true : true;
2697
- } else if (keyword === "playbackRate" || keyword === "playback_rate" || keyword === "speed") {
2698
- const r = Number(val);
2699
- if (!Number.isNaN(r) && r > 0) meta.playbackRate = r;
2700
- } else if (keyword === "progressColor" || keyword === "progressBarColor" || keyword === "progress_color" || keyword === "progress_bar_color" || keyword === "progress") {
2701
- if (val) meta.progressColor = val;
2702
- } else if (keyword === "progressBar") {
2703
- const bool = parseBooleanToken(val);
2704
- if (bool === false) meta.progressColor = "none";
2705
- else if (val) meta.progressColor = val;
2706
- }
2956
+ const error = applyPlayerSetting(meta.player ??= {}, "player", directiveMatch[1], directiveMatch[2] ?? "");
2957
+ if (error) diagnostics.push({ severity: "warning", message: error, line: lineNo });
2707
2958
  i++;
2708
2959
  continue;
2709
2960
  }
@@ -2859,7 +3110,7 @@ function parse(source, opts = {}) {
2859
3110
  throw new ParseError(`unexpected statement`, lineNo);
2860
3111
  }
2861
3112
  const ast = {
2862
- meta: { ...meta, title: title || void 0 },
3113
+ meta: { ...mirrorLegacySceneMeta(meta), title: title || void 0 },
2863
3114
  styles,
2864
3115
  nodes,
2865
3116
  edges,
@@ -3947,6 +4198,8 @@ export {
3947
4198
  NODE_ALIASES,
3948
4199
  NODE_KINDS,
3949
4200
  OUTPUT_PRESETS,
4201
+ PLAYER_FLAT_KEYS,
4202
+ PLAYER_GROUPS,
3950
4203
  ParseError,
3951
4204
  SCENE_KEYS,
3952
4205
  TECHNICAL_NODE_KINDS,
@@ -3954,6 +4207,7 @@ export {
3954
4207
  THEMES,
3955
4208
  VISUAL_PRIMITIVE_TYPES,
3956
4209
  analyzeAndBuildRepairPrompt,
4210
+ applyPlayerSetting,
3957
4211
  canonicalNodeKind,
3958
4212
  classifyTechnology,
3959
4213
  compile,
@@ -3970,6 +4224,7 @@ export {
3970
4224
  parseAndCompile,
3971
4225
  resolveArchitectureConfig,
3972
4226
  resolveOutputPreset,
4227
+ resolvePlayer,
3973
4228
  resolveTheme,
3974
4229
  routeOrthogonalEdge,
3975
4230
  selectOptimalPorts,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/core",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Diagram-native MarkdyScript parser and compiler (auto-layout, edge routing, cue scheduling) — zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",