@markdy/core 0.8.5 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -77,6 +77,12 @@ type Cue = {
77
77
  zoom?: number;
78
78
  dur?: number;
79
79
  line: number;
80
+ } | {
81
+ kind: "frame";
82
+ targets: string[];
83
+ zoom?: number;
84
+ dur?: number;
85
+ line: number;
80
86
  } | {
81
87
  kind: "use";
82
88
  pattern: string;
@@ -163,7 +169,7 @@ type RoutedEdge = {
163
169
  type TimedCue = {
164
170
  start: number;
165
171
  duration: number;
166
- kind: "show" | "hide" | "flow" | "glow" | "focus";
172
+ kind: "show" | "hide" | "flow" | "glow" | "focus" | "frame";
167
173
  targets: string[];
168
174
  edgeId?: string;
169
175
  segments?: FlowSegment[];
package/dist/index.js CHANGED
@@ -349,7 +349,8 @@ var EDGE_OPERATORS = {
349
349
  "~>": "event",
350
350
  "--": "dependency"
351
351
  };
352
- var BEAT_CUE_KEYWORDS = /* @__PURE__ */ new Set(["show", "hide", "glow", "focus", "use"]);
352
+ var RESERVED_SELECTORS = /* @__PURE__ */ new Set(["$title", "$nodes", "$edges"]);
353
+ var BEAT_CUE_KEYWORDS = /* @__PURE__ */ new Set(["show", "hide", "glow", "focus", "frame", "use"]);
353
354
  var SCENE_KEYS = /* @__PURE__ */ new Set(["width", "height", "fps", "theme", "duration", "direction"]);
354
355
  function nodeRole(kind) {
355
356
  return TECHNICAL_NODE_KINDS[kind] ?? "compute";
@@ -391,6 +392,7 @@ var DEFAULTS = {
391
392
  flow: 0.55,
392
393
  glow: 0.45,
393
394
  focus: 0.6,
395
+ frame: 0.7,
394
396
  beatGap: 0.14,
395
397
  cueGap: 0.08,
396
398
  stagger: 0.06
@@ -575,7 +577,7 @@ function scheduleBeats(ast, edges) {
575
577
  }
576
578
  return;
577
579
  }
578
- if (cue.kind !== "show" && cue.kind !== "hide" && cue.kind !== "glow" && cue.kind !== "focus") return;
580
+ if (cue.kind !== "show" && cue.kind !== "hide" && cue.kind !== "glow" && cue.kind !== "focus" && cue.kind !== "frame") return;
579
581
  const targets = resolveTargets(cue.targets, ast, Object.fromEntries(Object.entries(ast.groups).map(([k, g]) => [k, g.members])));
580
582
  scheduled.push({
581
583
  start: t,
@@ -586,7 +588,7 @@ function scheduleBeats(ast, edges) {
586
588
  stagger: cue.kind === "show" ? cue.stagger ?? DEFAULTS.stagger : void 0,
587
589
  color: cue.kind === "glow" ? cue.color : void 0,
588
590
  strength: cue.kind === "glow" ? cue.strength : void 0,
589
- zoom: cue.kind === "focus" ? cue.zoom : void 0
591
+ zoom: cue.kind === "focus" || cue.kind === "frame" ? cue.zoom : void 0
590
592
  },
591
593
  beat: beat.name
592
594
  });
@@ -834,8 +836,7 @@ function splitTargetLabel(token, lineNo) {
834
836
  }
835
837
  function parseCueLine(line, lineNo) {
836
838
  const trimmed = line.trim();
837
- const propsMatch = trimmed.match(/\s+(?:dur|stagger|color|strength|zoom|after)=\S+/);
838
- const props = propsMatch ? parseProps(propsMatch[0]) : {};
839
+ const props = parseProps(trimmed);
839
840
  if (trimmed.includes(" & ")) {
840
841
  const parts = trimmed.split(/\s+&\s+/);
841
842
  return {
@@ -886,6 +887,18 @@ function parseCueLine(line, lineNo) {
886
887
  line: lineNo
887
888
  };
888
889
  }
890
+ if (keyword === "frame") {
891
+ const targetRaw = rest.join(" ").split(/\s+(?:zoom|dur)=/)[0];
892
+ const targets = splitTargets(targetRaw);
893
+ if (targets.length === 0) throw new ParseError(`expected frame target`, lineNo);
894
+ return {
895
+ kind: "frame",
896
+ targets,
897
+ zoom: typeof props.zoom === "number" ? props.zoom : void 0,
898
+ dur: typeof props.dur === "number" ? props.dur : void 0,
899
+ line: lineNo
900
+ };
901
+ }
889
902
  if (keyword === "use") {
890
903
  const call = rest.join(" ");
891
904
  const m = call.match(/^(\w+)\s*\((.*)\)\s*$/);
@@ -931,10 +944,9 @@ function substitutePatternCue(cue, params, args) {
931
944
  params.forEach((p, i) => {
932
945
  if (resolvedArgs[p] === void 0 && positional[i]) resolvedArgs[p] = positional[i];
933
946
  });
934
- delete resolvedArgs.__pos_0;
935
- delete resolvedArgs.__pos_1;
936
- delete resolvedArgs.__pos_2;
937
- delete resolvedArgs.__pos_3;
947
+ for (const key of Object.keys(resolvedArgs)) {
948
+ if (key.startsWith("__pos_")) delete resolvedArgs[key];
949
+ }
938
950
  const sub = (s) => {
939
951
  for (const p of params) {
940
952
  if (resolvedArgs[p]) s = s.replaceAll(`$${p}`, resolvedArgs[p]);
@@ -952,7 +964,7 @@ function substitutePatternCue(cue, params, args) {
952
964
  }))
953
965
  };
954
966
  }
955
- if (cue.kind === "show" || cue.kind === "hide" || cue.kind === "glow" || cue.kind === "focus") {
967
+ if (cue.kind === "show" || cue.kind === "hide" || cue.kind === "glow" || cue.kind === "focus" || cue.kind === "frame") {
956
968
  return { ...cue, targets: cue.targets.map(sub) };
957
969
  }
958
970
  if (cue.kind === "parallel") {
@@ -980,6 +992,67 @@ function readIndentedBody(blocks, startIdx, parentIndent) {
980
992
  }
981
993
  return { body, nextIdx: i };
982
994
  }
995
+ function pushWarning(diagnostics, seen, line, message) {
996
+ const key = `${line}:${message}`;
997
+ if (seen.has(key)) return;
998
+ seen.add(key);
999
+ diagnostics.push({ severity: "warning", message, line });
1000
+ }
1001
+ function visitCues(cues, visit) {
1002
+ for (const cue of cues) {
1003
+ visit(cue);
1004
+ if (cue.kind === "parallel") visitCues(cue.cues, visit);
1005
+ }
1006
+ }
1007
+ function validateReferences(ast) {
1008
+ const seen = /* @__PURE__ */ new Set();
1009
+ const hasNode = (id) => Boolean(ast.nodes[id]);
1010
+ const hasGroup = (id) => Boolean(ast.groups[id]);
1011
+ const isKnownTarget = (target) => {
1012
+ if (RESERVED_SELECTORS.has(target)) return true;
1013
+ if (target.startsWith("$")) return hasGroup(target.slice(1));
1014
+ return hasNode(target) || hasGroup(target);
1015
+ };
1016
+ for (const group of Object.values(ast.groups)) {
1017
+ for (const member of group.members) {
1018
+ if (!hasNode(member)) {
1019
+ pushWarning(ast.diagnostics, seen, group.line, `group '${group.id}' references unknown node '${member}'`);
1020
+ }
1021
+ }
1022
+ }
1023
+ for (const node of Object.values(ast.nodes)) {
1024
+ if (node.style && !ast.styles[node.style]) {
1025
+ pushWarning(ast.diagnostics, seen, node.line, `node '${node.id}' references unknown style '${node.style}'`);
1026
+ }
1027
+ }
1028
+ const validateFlowEndpoint = (line, endpoint) => {
1029
+ if (!hasNode(endpoint)) {
1030
+ pushWarning(ast.diagnostics, seen, line, `flow references unknown node '${endpoint}'`);
1031
+ }
1032
+ };
1033
+ for (const edge of ast.edges) {
1034
+ validateFlowEndpoint(edge.line, edge.from);
1035
+ validateFlowEndpoint(edge.line, edge.to);
1036
+ }
1037
+ for (const beat of ast.beats) {
1038
+ visitCues(beat.cues, (cue) => {
1039
+ if (cue.kind === "flow") {
1040
+ for (const segment of cue.segments) {
1041
+ validateFlowEndpoint(cue.line, segment.from);
1042
+ validateFlowEndpoint(cue.line, segment.to);
1043
+ }
1044
+ return;
1045
+ }
1046
+ if (cue.kind === "show" || cue.kind === "hide" || cue.kind === "glow" || cue.kind === "focus" || cue.kind === "frame") {
1047
+ for (const target of cue.targets) {
1048
+ if (!isKnownTarget(target)) {
1049
+ pushWarning(ast.diagnostics, seen, cue.line, `${cue.kind} references unknown target '${target}'`);
1050
+ }
1051
+ }
1052
+ }
1053
+ });
1054
+ }
1055
+ }
983
1056
  function parse(source, opts = {}) {
984
1057
  const lines = source.replace(/\r\n/g, "\n").split("\n");
985
1058
  const diagnostics = [];
@@ -1122,10 +1195,6 @@ function parse(source, opts = {}) {
1122
1195
  }
1123
1196
  throw new ParseError(`unexpected statement`, lineNo);
1124
1197
  }
1125
- const errors = diagnostics.filter((d) => d.severity === "error");
1126
- if (errors.length) {
1127
- throw new ParseError(errors[0].message, errors[0].line, errors[0].column);
1128
- }
1129
1198
  const ast = {
1130
1199
  meta: { ...meta, title: title || void 0 },
1131
1200
  styles,
@@ -1136,6 +1205,11 @@ function parse(source, opts = {}) {
1136
1205
  beats,
1137
1206
  diagnostics
1138
1207
  };
1208
+ validateReferences(ast);
1209
+ const errors = ast.diagnostics.filter((d) => d.severity === "error");
1210
+ if (errors.length) {
1211
+ throw new ParseError(errors[0].message, errors[0].line, errors[0].column);
1212
+ }
1139
1213
  if (!opts.parseOnly && Object.keys(nodes).length === 0 && beats.length === 0) {
1140
1214
  }
1141
1215
  return ast;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/core",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "MarkdyScript parser and diagram compiler (auto-layout, edge routing, cue scheduling) — zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",