@markdy/core 1.0.18 → 1.0.20

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
@@ -68,7 +68,8 @@ try {
68
68
  | `ParseError` | class | Error with `.line` number for diagnostics |
69
69
  | `DiagramAST` | type | Parsed scene: meta, nodes, edges, groups, patterns, beats |
70
70
  | `RenderPlan` | type | Positioned nodes, routed edges, group zones, sequence messages, timed cues, beat ranges |
71
- | `SceneMeta` | type | Scene configuration (width, height, fps, theme, direction, controls, interactive, progressColor, playbackRate, loop, autoplay, copyright) |
71
+ | `SceneMeta` | type | Scene configuration; `meta.player` is authoritative, with deprecated flat mirrors retained for compatibility |
72
+ | `PlayerConfig` / `resolvePlayer` | type / function | Grouped playback, controls, interaction, and chrome configuration with host resolution |
72
73
  | `NodeDecl` | type | Node declaration (kind, id, label, style) |
73
74
  | `EdgeDecl` | type | Edge declaration (kind, from, to, label) |
74
75
  | `BeatDecl` | type | Named beat with cues |
package/dist/index.d.ts CHANGED
@@ -409,7 +409,7 @@ declare const PLAYER_FLAT_KEYS: string[];
409
409
  * key is unknown or the value does not fit the setting type.
410
410
  */
411
411
  declare function applyPlayerSetting(config: PlayerConfig, scope: PlayerScope, key: string, rawValue: string): string | undefined;
412
- /** Host-supplied overrides always win over script configuration. */
412
+ /** Host behavior overrides. `false` gates a feature off; `true` supplies legacy defaults when script values are absent. */
413
413
  type PlayerOverrides = {
414
414
  autoplay?: boolean;
415
415
  loop?: boolean;
package/dist/index.js CHANGED
@@ -611,25 +611,26 @@ function unquote(raw) {
611
611
  }
612
612
  function resolvePlayer(config = {}, overrides = {}) {
613
613
  const playback = config.playback ?? {};
614
+ const configuredControls = config.controls ?? {};
614
615
  const interaction = config.interaction ?? {};
615
616
  const chrome = config.chrome ?? {};
616
- const controlsOn = overrides.controls !== false && (overrides.controls === true || config.controls !== void 0);
617
- const controls = {
618
- play: controlsOn && (config.controls?.play ?? true),
619
- restart: controlsOn && (config.controls?.restart ?? true),
620
- prevBeat: controlsOn && (config.controls?.prevBeat ?? false),
621
- nextBeat: controlsOn && (config.controls?.nextBeat ?? false),
622
- seek: controlsOn && (config.controls?.seek ?? true),
623
- speed: controlsOn && (config.controls?.speed ?? true),
624
- fit: controlsOn && (config.controls?.fit ?? true),
625
- resetView: controlsOn && (config.controls?.resetView ?? true),
626
- fullscreen: controlsOn && (config.controls?.fullscreen ?? true),
627
- svg: controlsOn && (config.controls?.svg ?? true),
628
- share: controlsOn && (config.controls?.share ?? true),
629
- // `code` is opt-in: off by default, on only when explicitly declared true.
630
- code: controlsOn && (config.controls?.code ?? false),
631
- // `theme` is opt-in: off by default, on only when explicitly declared true.
632
- theme: controlsOn && (config.controls?.theme ?? false)
617
+ const controlsAllowed = overrides.controls !== false;
618
+ const hostControlDefault = overrides.controls === true;
619
+ const resolveControl = (value, fallback = hostControlDefault) => controlsAllowed && (value ?? fallback);
620
+ const requestedControls = {
621
+ play: resolveControl(configuredControls.play),
622
+ restart: resolveControl(configuredControls.restart),
623
+ prevBeat: resolveControl(configuredControls.prevBeat, false),
624
+ nextBeat: resolveControl(configuredControls.nextBeat, false),
625
+ seek: resolveControl(configuredControls.seek),
626
+ speed: resolveControl(configuredControls.speed),
627
+ fit: resolveControl(configuredControls.fit),
628
+ resetView: resolveControl(configuredControls.resetView),
629
+ fullscreen: resolveControl(configuredControls.fullscreen),
630
+ svg: resolveControl(configuredControls.svg),
631
+ share: resolveControl(configuredControls.share),
632
+ code: resolveControl(configuredControls.code, false),
633
+ theme: resolveControl(configuredControls.theme, hostControlDefault)
633
634
  };
634
635
  const interactionOn = overrides.interactiveViewport !== false && (overrides.interactiveViewport === true || config.interaction !== void 0 || overrides.controls === true);
635
636
  const gestures = {
@@ -638,8 +639,16 @@ function resolvePlayer(config = {}, overrides = {}) {
638
639
  doubleClickToReset: interactionOn && (interaction.doubleClickToReset ?? true)
639
640
  };
640
641
  const interactionEnabled = gestures.zoom || gestures.pan || gestures.doubleClickToReset;
641
- const resetView = controls.resetView && interactionEnabled;
642
- const controlsEnabled = controls.play || controls.restart || controls.prevBeat || controls.nextBeat || controls.seek || controls.speed || controls.fit || controls.resetView || controls.fullscreen || controls.svg || controls.share || controls.code || controls.theme || resetView;
642
+ const configuredSpeeds = configuredControls.speeds?.length ? configuredControls.speeds : [0.25, 1];
643
+ const speeds = configuredSpeeds.filter(
644
+ (rate2, index) => Number.isFinite(rate2) && rate2 > 0 && configuredSpeeds.indexOf(rate2) === index
645
+ );
646
+ const controls = {
647
+ ...requestedControls,
648
+ speed: requestedControls.speed && speeds.length > 1,
649
+ resetView: requestedControls.resetView && interactionEnabled
650
+ };
651
+ const controlsEnabled = Object.values(controls).some(Boolean);
643
652
  const rate = overrides.playbackRate ?? playback.rate ?? 1;
644
653
  return {
645
654
  playback: {
@@ -649,9 +658,8 @@ function resolvePlayer(config = {}, overrides = {}) {
649
658
  },
650
659
  controls: {
651
660
  ...controls,
652
- resetView,
653
661
  enabled: controlsEnabled,
654
- speeds: config.controls?.speeds?.length ? config.controls.speeds : [0.25, 1]
662
+ speeds
655
663
  },
656
664
  interaction: {
657
665
  ...gestures,
@@ -887,12 +895,33 @@ function layoutRanked(ast, edges, opts) {
887
895
  nodeGroupIndex.set(memberId, gIdx);
888
896
  }
889
897
  });
890
- for (const ids of byRank.values()) {
898
+ const nodeDeclOrder = new Map(nodeIds.map((id, idx) => [id, idx]));
899
+ const incomingParents = /* @__PURE__ */ new Map();
900
+ for (const id of nodeIds) incomingParents.set(id, []);
901
+ for (const e of edges) {
902
+ if (e.kind !== "response" && !e.selfLoop && ast.nodes[e.from] && ast.nodes[e.to]) {
903
+ incomingParents.get(e.to)?.push(e.from);
904
+ }
905
+ }
906
+ const rankOrder = /* @__PURE__ */ new Map();
907
+ const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
908
+ for (const r of sortedRanks) {
909
+ const ids = byRank.get(r);
891
910
  ids.sort((a, b) => {
892
911
  const ga = nodeGroupIndex.has(a) ? nodeGroupIndex.get(a) : 9999;
893
912
  const gb = nodeGroupIndex.has(b) ? nodeGroupIndex.get(b) : 9999;
894
913
  if (ga !== gb) return ga - gb;
895
- return a.localeCompare(b);
914
+ const parentsA = (incomingParents.get(a) ?? []).filter((p) => rankOrder.has(p));
915
+ const parentsB = (incomingParents.get(b) ?? []).filter((p) => rankOrder.has(p));
916
+ if (parentsA.length > 0 && parentsB.length > 0) {
917
+ const avgA = parentsA.reduce((sum, p) => sum + (rankOrder.get(p) ?? 0), 0) / parentsA.length;
918
+ const avgB = parentsB.reduce((sum, p) => sum + (rankOrder.get(p) ?? 0), 0) / parentsB.length;
919
+ if (Math.abs(avgA - avgB) > 1e-3) return avgA - avgB;
920
+ }
921
+ return (nodeDeclOrder.get(a) ?? 0) - (nodeDeclOrder.get(b) ?? 0);
922
+ });
923
+ ids.forEach((id, idx) => {
924
+ rankOrder.set(id, idx);
896
925
  });
897
926
  }
898
927
  const isVertical = direction === "TB" || direction === "BT";
@@ -1673,7 +1702,7 @@ function layoutNodes(ast, edges) {
1673
1702
  const dtype = diagramType(ast);
1674
1703
  switch (dtype) {
1675
1704
  case "flowchart":
1676
- return layoutRanked(ast, edges, { forceVertical: true });
1705
+ return layoutRanked(ast, edges, { forceVertical: ast.meta.direction === "TB" || ast.meta.direction === "BT" });
1677
1706
  case "tree":
1678
1707
  return layoutTree(ast, edges.some((edge) => edge.structural) ? edges.filter((edge) => edge.structural) : edges);
1679
1708
  case "sequence":
@@ -2970,6 +2999,9 @@ function parse(source, opts = {}) {
2970
2999
  diagnostics.push({ severity: "warning", message: `unknown diagram type '${v}'`, line: lineNo });
2971
3000
  } else {
2972
3001
  meta.type = t;
3002
+ if (t === "flowchart" && !props.direction && !props.layout && !inlineLayout) {
3003
+ meta.direction = "TB";
3004
+ }
2973
3005
  }
2974
3006
  }
2975
3007
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/core",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
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",