@pixerate/editor 0.164.0 → 0.166.0

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.
Files changed (146) hide show
  1. package/dist/agent/index.cjs +54 -39
  2. package/dist/agent/index.js +24 -9
  3. package/dist/animation/index.cjs +6 -5
  4. package/dist/animation/index.js +5 -4
  5. package/dist/animation-editor/index.cjs +65 -64
  6. package/dist/animation-editor/index.js +8 -7
  7. package/dist/bridge/cli.cjs +3 -3
  8. package/dist/bridge/cli.js +2 -2
  9. package/dist/bridge/index.cjs +3 -3
  10. package/dist/bridge/index.js +2 -2
  11. package/dist/canvas/index.cjs +89 -87
  12. package/dist/canvas/index.js +11 -9
  13. package/dist/chart/controller.d.cts +60 -2
  14. package/dist/chart/controller.d.ts +60 -2
  15. package/dist/chart/index.cjs +657 -163
  16. package/dist/chart/index.d.cts +3 -0
  17. package/dist/chart/index.d.ts +3 -0
  18. package/dist/chart/index.js +512 -18
  19. package/dist/chart/layout-families.d.cts +14 -0
  20. package/dist/chart/layout-families.d.ts +14 -0
  21. package/dist/chart/layout-shared.d.cts +5 -0
  22. package/dist/chart/layout-shared.d.ts +5 -0
  23. package/dist/chart/links.d.cts +34 -0
  24. package/dist/chart/links.d.ts +34 -0
  25. package/dist/chart/mermaid.d.cts +9 -6
  26. package/dist/chart/mermaid.d.ts +9 -6
  27. package/dist/chart/race.d.cts +26 -0
  28. package/dist/chart/race.d.ts +26 -0
  29. package/dist/chart/schemas.d.cts +5 -0
  30. package/dist/chart/schemas.d.ts +5 -0
  31. package/dist/chart/spec.d.cts +3 -2
  32. package/dist/chart/spec.d.ts +3 -2
  33. package/dist/chart/style.d.cts +33 -0
  34. package/dist/chart/style.d.ts +33 -0
  35. package/dist/chart/types.d.cts +28 -2
  36. package/dist/chart/types.d.ts +28 -2
  37. package/dist/{chunk-ZVNAMTFO.cjs → chunk-3FSUJIXN.cjs} +18 -174
  38. package/dist/{chunk-RQCMSEM5.cjs → chunk-4QUUHTGJ.cjs} +2 -2
  39. package/dist/{chunk-CIPIESX4.cjs → chunk-4ZC5QJE2.cjs} +3 -3
  40. package/dist/{chunk-S6X2QRVU.cjs → chunk-5UEFBM2M.cjs} +1 -138
  41. package/dist/{chunk-5FNGLV2D.js → chunk-5UNQDC3Q.js} +18 -3
  42. package/dist/{chunk-YSAGBIRW.cjs → chunk-6FYIFGBW.cjs} +16 -16
  43. package/dist/{chunk-QYZGBVNC.js → chunk-7272D3K2.js} +10 -8
  44. package/dist/{chunk-462WGGXX.cjs → chunk-AMX23CSN.cjs} +27 -16
  45. package/dist/{chunk-3VLOOO3G.js → chunk-BDIM6NVW.js} +1 -1
  46. package/dist/{chunk-R3P2M7HU.cjs → chunk-BPX4Z3N3.cjs} +355 -207
  47. package/dist/{chunk-D54JHQK2.cjs → chunk-CHLV7UFP.cjs} +51 -51
  48. package/dist/{chunk-OXERWBKE.js → chunk-DFCAR7AW.js} +3 -3
  49. package/dist/{chunk-GZ2FM3ZB.js → chunk-DKNEQX7T.js} +6 -162
  50. package/dist/{chunk-JCXMJUQO.cjs → chunk-EGZM52EO.cjs} +3 -3
  51. package/dist/{chunk-ES3UEVUT.cjs → chunk-EUIDTZNQ.cjs} +112 -110
  52. package/dist/{chunk-H3OEM3Q2.js → chunk-F6R67I3A.js} +1 -1
  53. package/dist/{chunk-3NEZJCBY.js → chunk-FR432ROA.js} +349 -201
  54. package/dist/chunk-HIDFL5LQ.js +484 -0
  55. package/dist/{chunk-22KLZZGH.cjs → chunk-HXOBOHH2.cjs} +8 -8
  56. package/dist/{chunk-MKTDELCD.js → chunk-I5MRJW7B.js} +1 -1
  57. package/dist/{chunk-SDHZG5TF.js → chunk-ILP77UD6.js} +160 -2
  58. package/dist/{chunk-Z4P2RBMJ.js → chunk-ITANXMZD.js} +177 -3
  59. package/dist/chunk-KGRZMHZB.js +204 -0
  60. package/dist/{chunk-SET6KJOP.js → chunk-LPIKXPBM.js} +1 -1
  61. package/dist/{chunk-BQWGN5WH.js → chunk-M2TPMYJE.js} +1 -1
  62. package/dist/{chunk-GTELFJRE.cjs → chunk-M5GLQQWG.cjs} +2 -2
  63. package/dist/{chunk-4VU4CP7R.js → chunk-MHLU6GRJ.js} +1 -138
  64. package/dist/{chunk-NZD2MZJC.cjs → chunk-MZFZSVUJ.cjs} +38 -23
  65. package/dist/{chunk-DAPQGA55.js → chunk-NQQDGGXY.js} +2 -2
  66. package/dist/chunk-O6OGNDND.js +164 -0
  67. package/dist/{chunk-PA3IMPPS.cjs → chunk-OVYQAYGH.cjs} +81 -81
  68. package/dist/{chunk-YXOW456B.js → chunk-PPEDZ5WR.js} +1 -1
  69. package/dist/{chunk-24M7VD6M.cjs → chunk-S4EIAIEW.cjs} +7 -7
  70. package/dist/{chunk-M4MIZPQI.cjs → chunk-SJ2CPTQC.cjs} +189 -15
  71. package/dist/{chunk-YGG7WDWT.cjs → chunk-SZWVMB3B.cjs} +24 -18
  72. package/dist/chunk-TFF2GTGN.cjs +153 -0
  73. package/dist/chunk-TTCJPX4U.cjs +204 -0
  74. package/dist/{chunk-SBQVEBSU.cjs → chunk-UUUTEVX6.cjs} +242 -68
  75. package/dist/{chunk-WI7QJA7A.cjs → chunk-UXETV2NC.cjs} +161 -3
  76. package/dist/{chunk-GKNEJ6EN.cjs → chunk-V5L2G22R.cjs} +321 -107
  77. package/dist/{chunk-TALQONZ5.js → chunk-VB2CY6EG.js} +3 -3
  78. package/dist/{chunk-QY4KUDMY.js → chunk-VRXXAXUT.js} +2 -2
  79. package/dist/chunk-VU6WQSMB.cjs +164 -0
  80. package/dist/{chunk-NHLYK3AX.js → chunk-XBQX76CT.js} +182 -8
  81. package/dist/{chunk-5AARIS3R.cjs → chunk-XCDGM7TD.cjs} +37 -37
  82. package/dist/{chunk-POR54B55.js → chunk-XNU45TYA.js} +7 -1
  83. package/dist/{chunk-EVXQNQND.js → chunk-XZB3TJ6K.js} +16 -5
  84. package/dist/chunk-YBAPT7NL.js +153 -0
  85. package/dist/{chunk-SZA42R4N.js → chunk-YUFTVNLX.js} +230 -16
  86. package/dist/chunk-Z2UQYMPS.cjs +484 -0
  87. package/dist/color/index.cjs +5 -3
  88. package/dist/color/index.js +12 -10
  89. package/dist/diagram/index.cjs +55 -243
  90. package/dist/diagram/index.js +12 -200
  91. package/dist/export/index.cjs +7 -6
  92. package/dist/export/index.js +6 -5
  93. package/dist/extensions/index.cjs +12 -9
  94. package/dist/extensions/index.js +11 -8
  95. package/dist/gradient/index.cjs +29 -27
  96. package/dist/gradient/index.js +6 -4
  97. package/dist/image-editor/index.cjs +644 -643
  98. package/dist/image-editor/index.js +7 -6
  99. package/dist/index.cjs +21 -18
  100. package/dist/index.js +14 -11
  101. package/dist/paint-editor/index.cjs +19 -18
  102. package/dist/paint-editor/index.js +7 -6
  103. package/dist/parameters/index.cjs +2 -2
  104. package/dist/parameters/index.js +1 -1
  105. package/dist/persistence/assetWrites.d.cts +22 -0
  106. package/dist/persistence/assetWrites.d.ts +22 -0
  107. package/dist/persistence/assets.d.cts +71 -0
  108. package/dist/persistence/assets.d.ts +71 -0
  109. package/dist/persistence/documentStore.d.cts +17 -1
  110. package/dist/persistence/documentStore.d.ts +17 -1
  111. package/dist/persistence/documentTools.d.cts +5 -1
  112. package/dist/persistence/documentTools.d.ts +5 -1
  113. package/dist/persistence/editorAutosave.d.cts +7 -0
  114. package/dist/persistence/editorAutosave.d.ts +7 -0
  115. package/dist/persistence/index.cjs +141 -21
  116. package/dist/persistence/index.js +130 -10
  117. package/dist/persistence/storage.d.cts +13 -1
  118. package/dist/persistence/storage.d.ts +13 -1
  119. package/dist/spreadsheet/index.cjs +3 -3
  120. package/dist/spreadsheet/index.js +2 -2
  121. package/dist/tokens/index.cjs +11 -3
  122. package/dist/tokens/index.d.cts +1 -0
  123. package/dist/tokens/index.d.ts +1 -0
  124. package/dist/tokens/index.js +10 -2
  125. package/dist/tokens/style.d.cts +36 -0
  126. package/dist/tokens/style.d.ts +36 -0
  127. package/dist/tools/index.cjs +15 -3
  128. package/dist/tools/index.d.cts +2 -0
  129. package/dist/tools/index.d.ts +2 -0
  130. package/dist/tools/index.js +14 -2
  131. package/dist/tools/style-tools.d.cts +19 -0
  132. package/dist/tools/style-tools.d.ts +19 -0
  133. package/dist/tools/style.d.cts +212 -0
  134. package/dist/tools/style.d.ts +212 -0
  135. package/dist/tools/view.d.cts +6 -0
  136. package/dist/tools/view.d.ts +6 -0
  137. package/dist/typography/index.cjs +8 -6
  138. package/dist/typography/index.js +5 -3
  139. package/dist/vector/index.cjs +953 -952
  140. package/dist/vector/index.js +7 -6
  141. package/dist/video-editor/index.cjs +8 -7
  142. package/dist/video-editor/index.js +6 -5
  143. package/package.json +1 -1
  144. package/skills/pixerate-chart/SKILL.md +20 -2
  145. package/dist/chunk-ERAGQL2N.cjs +0 -68
  146. package/dist/chunk-KMU6QJB7.js +0 -68
@@ -1,10 +1,14 @@
1
1
  import {
2
2
  defineEditorDocument
3
- } from "./chunk-NHLYK3AX.js";
3
+ } from "./chunk-XBQX76CT.js";
4
4
  import {
5
5
  hexToRgba,
6
6
  normalizeTokenColor
7
7
  } from "./chunk-2WPNAGHI.js";
8
+ import {
9
+ contrastRatio,
10
+ parseColor
11
+ } from "./chunk-MHLU6GRJ.js";
8
12
 
9
13
  // src/tokens/types.ts
10
14
  var TOKEN_TYPES = ["color", "number", "string", "boolean"];
@@ -1307,6 +1311,157 @@ function createTokensEditorDocument(store) {
1307
1311
  });
1308
1312
  }
1309
1313
 
1314
+ // src/tokens/style.ts
1315
+ function findVariable(state, ref) {
1316
+ return getTokenVariable(state, ref) ?? findTokenVariable(state, ref);
1317
+ }
1318
+ function createTokenStyleSource(store) {
1319
+ return {
1320
+ resolve({ token, mode }) {
1321
+ const state = store.getState();
1322
+ const variable = findVariable(state, token);
1323
+ if (!variable) return { ok: false, message: `there is no design token "${token}".` };
1324
+ let modes;
1325
+ if (mode !== void 0) {
1326
+ const collection = getTokenCollection(state, variable.collectionId);
1327
+ const found = findTokenMode(collection, mode);
1328
+ if (!found) return { ok: false, message: `"${collection.name}" has no mode "${mode}"; its modes are ${collection.modes.map((m) => `"${m.name}"`).join(", ")}.` };
1329
+ modes = { [collection.id]: found.id };
1330
+ }
1331
+ const resolved = resolveToken(state, variable.id, modes);
1332
+ return resolved.ok ? { ok: true, value: resolved.value } : { ok: false, message: `"${token}" does not resolve: ${resolved.message}` };
1333
+ },
1334
+ subscribe: (listener) => store.subscribe(() => listener())
1335
+ };
1336
+ }
1337
+ var profileOf = (profile) => profile;
1338
+ var requiredVariables = {
1339
+ id: "required-variables",
1340
+ description: `Every variable named in the profile's "required" list exists.`,
1341
+ run(state, profile) {
1342
+ const required = profileOf(profile).required;
1343
+ if (!required?.length) throw new Error('the profile has no "required" list.');
1344
+ return required.filter((name) => !findVariable(state, name)).map((name) => ({ severity: "error", message: `The token set has no variable "${name}"; add it (house style requires it).`, target: { variable: name } }));
1345
+ }
1346
+ };
1347
+ var namePattern = {
1348
+ id: "name-pattern",
1349
+ description: `Every variable name matches the profile's "namePattern" regular expression.`,
1350
+ run(state, profile) {
1351
+ const pattern = profileOf(profile).namePattern;
1352
+ if (!pattern) throw new Error('the profile has no "namePattern".');
1353
+ let re;
1354
+ try {
1355
+ re = new RegExp(pattern);
1356
+ } catch {
1357
+ throw new Error(`"namePattern" ${pattern} is not a valid regular expression.`);
1358
+ }
1359
+ return state.variables.filter((v) => !re.test(v.name)).map((v) => ({ severity: "warning", message: `Variable "${v.name}" does not match the naming pattern ${pattern}; rename it.`, target: { variableId: v.id } }));
1360
+ }
1361
+ };
1362
+ var requiredModes = {
1363
+ id: "required-modes",
1364
+ description: `Every collection has the modes named in the profile's "modes" list (e.g. Light and Dark).`,
1365
+ run(state, profile) {
1366
+ const modes = profileOf(profile).modes;
1367
+ if (!modes?.length) throw new Error('the profile has no "modes" list.');
1368
+ return state.collections.flatMap(
1369
+ (c) => modes.filter((m) => !findTokenMode(c, m)).map((m) => ({ severity: "error", message: `Collection "${c.name}" has no "${m}" mode; add it.`, target: { collectionId: c.id } }))
1370
+ );
1371
+ }
1372
+ };
1373
+ var contrastPairs = {
1374
+ id: "contrast-pairs",
1375
+ description: `Each foreground / background pair in "contrastPairs" reaches its contrast ratio (default 4.5) in every mode of the foreground's collection.`,
1376
+ run(state, profile) {
1377
+ const pairs = profileOf(profile).contrastPairs;
1378
+ if (!pairs?.length) throw new Error('the profile has no "contrastPairs".');
1379
+ const findings = [];
1380
+ for (const { fg, bg, ratio = 4.5 } of pairs) {
1381
+ const f = findVariable(state, fg);
1382
+ const b = findVariable(state, bg);
1383
+ if (!f || !b) {
1384
+ findings.push({ severity: "error", message: `Contrast pair ${fg} on ${bg} names a variable the token set lacks (${!f ? fg : bg}).`, target: { variable: !f ? fg : bg } });
1385
+ continue;
1386
+ }
1387
+ const collection = getTokenCollection(state, f.collectionId);
1388
+ for (const mode of collection.modes) {
1389
+ const modes = { [collection.id]: mode.id, [b.collectionId]: findTokenMode(getTokenCollection(state, b.collectionId), mode.name)?.id ?? mode.id };
1390
+ const fv = resolveToken(state, f.id, modes);
1391
+ const bv = resolveToken(state, b.id, modes);
1392
+ const fc = fv.ok && typeof fv.value === "string" ? parseColor(fv.value) : null;
1393
+ const bc = bv.ok && typeof bv.value === "string" ? parseColor(bv.value) : null;
1394
+ if (!fc || !bc) continue;
1395
+ const r = contrastRatio(fc, bc);
1396
+ if (r < ratio) {
1397
+ findings.push({
1398
+ severity: "error",
1399
+ message: `${f.name} on ${b.name} has contrast ${Math.round(r * 100) / 100}:1 in ${mode.name}; want at least ${ratio}:1, so darken or lighten one of them in that mode.`,
1400
+ target: { variableId: f.id, modeId: mode.id }
1401
+ });
1402
+ }
1403
+ }
1404
+ }
1405
+ return findings;
1406
+ }
1407
+ };
1408
+ var tokensStyleDomain = {
1409
+ id: "tokens",
1410
+ title: "Design tokens",
1411
+ description: "Rules for the token set itself: variables that must exist, a naming pattern, required modes and colour pairs that must reach a contrast ratio.",
1412
+ profileSchema: {
1413
+ type: "object",
1414
+ properties: {
1415
+ required: { type: "array", items: { type: "string", minLength: 1 }, description: 'Variable names that must exist, e.g. "brand/primary".' },
1416
+ namePattern: { type: "string", minLength: 1, description: 'Regular expression every variable name must match, e.g. "^[a-z0-9-]+(/[a-z0-9-]+)*$".' },
1417
+ modes: { type: "array", items: { type: "string", minLength: 1 }, description: 'Mode names every collection must have, e.g. ["Light", "Dark"].' },
1418
+ contrastPairs: {
1419
+ type: "array",
1420
+ items: {
1421
+ type: "object",
1422
+ properties: {
1423
+ fg: { type: "string", minLength: 1, description: "Foreground variable name or id." },
1424
+ bg: { type: "string", minLength: 1, description: "Background variable name or id." },
1425
+ ratio: { type: "number", minimum: 1, maximum: 21, description: "Smallest contrast ratio (default 4.5, WCAG AA for text)." }
1426
+ },
1427
+ required: ["fg", "bg"],
1428
+ additionalProperties: false
1429
+ },
1430
+ description: "Colour pairs checked in every mode."
1431
+ }
1432
+ },
1433
+ additionalProperties: false
1434
+ },
1435
+ checks: [requiredVariables, namePattern, requiredModes, contrastPairs]
1436
+ };
1437
+ function createTokensStyleToolProvider(store, styles, options = {}) {
1438
+ if (!styles.domains.some((d) => d.id === "tokens")) throw new Error('The style resolver has no "tokens" domain; add tokensStyleDomain to its domains.');
1439
+ const check = {
1440
+ name: "check_tokens_style",
1441
+ title: "Check token house style",
1442
+ description: 'Checks the design-token set against the house style (see get_style with domain "tokens"): runs the checks its profile turns on (required-variables, name-pattern, required-modes, contrast-pairs). Returns findings with a severity, a sentence saying how to fix it, a target (variable id or name, mode id, collection id) and the layer that asked for it; plus the checks that ran and the ones skipped with the reason. Fix findings with get_tokens_document / load_tokens_document, then check again.',
1443
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
1444
+ annotations: { title: "Check token house style", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1445
+ handler: async () => {
1446
+ const { style, ...report } = await styles.check("tokens", store.getState());
1447
+ return {
1448
+ ...report,
1449
+ ok: report.findings.length === 0,
1450
+ ...style.skills.length ? {} : { note: "No house style applies to design tokens." },
1451
+ ...style.issues.length ? { issues: style.issues } : {}
1452
+ };
1453
+ }
1454
+ };
1455
+ return {
1456
+ id: options.id ?? "tokens-style",
1457
+ namespace: options.namespace ?? "tokens",
1458
+ title: "Token house style",
1459
+ description: "Check the design-token set against the house style.",
1460
+ skill: "pixerate-tokens",
1461
+ tools: [check]
1462
+ };
1463
+ }
1464
+
1310
1465
  export {
1311
1466
  TOKEN_TYPES,
1312
1467
  TokenError,
@@ -1360,5 +1515,8 @@ export {
1360
1515
  exportW3CTokens,
1361
1516
  exportW3CTokensJson,
1362
1517
  importW3CTokens,
1363
- createTokensEditorDocument
1518
+ createTokensEditorDocument,
1519
+ createTokenStyleSource,
1520
+ tokensStyleDomain,
1521
+ createTokensStyleToolProvider
1364
1522
  };
@@ -12,7 +12,7 @@ import {
12
12
  chartTreeTotal,
13
13
  chartVariesColors,
14
14
  shadeChartColor
15
- } from "./chunk-3NEZJCBY.js";
15
+ } from "./chunk-FR432ROA.js";
16
16
  import {
17
17
  NumberFormat
18
18
  } from "./chunk-II2WIF5Z.js";
@@ -948,7 +948,7 @@ function drawCallouts(ctx, plot) {
948
948
  }
949
949
  if (unplaced) ctx.warn("annotations-skipped", `${unplaced} callout${unplaced === 1 ? " points" : "s point"} at a gap or a hidden value and ${unplaced === 1 ? "is" : "are"} not drawn.`);
950
950
  }
951
- var MUTED = /* @__PURE__ */ new Set(["bar", "line", "area", "point", "slice", "cell", "box", "whisker", "outlier", "connector", "trendline", "trendline-label", "error-bar", "data-label", "legend-swatch", "legend-label"]);
951
+ var MUTED = /* @__PURE__ */ new Set(["node", "bar", "line", "area", "point", "slice", "cell", "box", "whisker", "outlier", "connector", "trendline", "trendline-label", "error-bar", "data-label", "legend-swatch", "legend-label"]);
952
952
  function applyHighlight(ctx) {
953
953
  const { spec, marks } = ctx;
954
954
  if (!spec.highlight.length) return;
@@ -1677,6 +1677,38 @@ function trendlineLegendItems(spec, color) {
1677
1677
  );
1678
1678
  }
1679
1679
 
1680
+ // src/chart/race.ts
1681
+ function chartRaceValueAt(series, index) {
1682
+ for (let i = Math.min(index, series.values.length - 1); i >= 0; i--) {
1683
+ const v = series.values[i];
1684
+ if (v !== null && v !== void 0 && Number.isFinite(v)) return v;
1685
+ }
1686
+ return 0;
1687
+ }
1688
+ function chartRaceMoment(count, frame) {
1689
+ const last = Math.max(0, count - 1);
1690
+ const at = frame === null || frame === void 0 || !Number.isFinite(frame) ? last : Math.max(0, Math.min(last, frame));
1691
+ const from = Math.floor(at);
1692
+ const to = Math.min(last, from + 1);
1693
+ return { from, to, t: at - from, at };
1694
+ }
1695
+ function chartRaceRanking(spec, frame) {
1696
+ const visible = spec.series.filter((s) => !s.hidden);
1697
+ const { from, to, t } = chartRaceMoment(spec.categories.length, frame);
1698
+ const rankAt = (i) => {
1699
+ const order2 = visible.map((s, k) => ({ id: s.id, v: chartRaceValueAt(s, i), k })).sort((a, b) => b.v - a.v || a.k - b.k);
1700
+ return new Map(order2.map((o, r) => [o.id, r]));
1701
+ };
1702
+ const r0 = rankAt(from);
1703
+ const r1 = rankAt(to);
1704
+ const near = t < 0.5 ? r0 : r1;
1705
+ return visible.map((s) => {
1706
+ const a = chartRaceValueAt(s, from);
1707
+ const b = chartRaceValueAt(s, to);
1708
+ return { seriesId: s.id, name: s.name, value: a + (b - a) * t, position: r0.get(s.id) + (r1.get(s.id) - r0.get(s.id)) * t, rank: near.get(s.id) };
1709
+ }).sort((a, b) => a.position - b.position || b.value - a.value);
1710
+ }
1711
+
1680
1712
  // src/chart/layout-families.ts
1681
1713
  var sliceColor = (ctx, series, i) => series.pointColors?.[i] || chartSliceColor(ctx.spec, i);
1682
1714
  function layoutPie(ctx, area, series) {
@@ -2349,11 +2381,142 @@ function layoutSunburst(ctx, area, series) {
2349
2381
  }
2350
2382
  return { plot };
2351
2383
  }
2384
+ function layoutSankey(ctx, area, series) {
2385
+ const { spec, theme, marks, fs, measure } = ctx;
2386
+ const nodes = spec.sankeyNodes;
2387
+ const links = spec.links;
2388
+ const at = new Map(nodes.map((n, i) => [n, i]));
2389
+ const incoming = nodes.map(() => []);
2390
+ const outgoing = nodes.map(() => []);
2391
+ links.forEach((l, k) => {
2392
+ outgoing[at.get(l.source)].push(k);
2393
+ incoming[at.get(l.target)].push(k);
2394
+ });
2395
+ const column = nodes.map(() => -1);
2396
+ const depth = (i) => {
2397
+ if (column[i] >= 0) return column[i];
2398
+ column[i] = 0;
2399
+ column[i] = incoming[i].reduce((d, k) => Math.max(d, depth(at.get(links[k].source)) + 1), 0);
2400
+ return column[i];
2401
+ };
2402
+ nodes.forEach((_, i) => depth(i));
2403
+ const last = Math.max(...column);
2404
+ nodes.forEach((_, i) => {
2405
+ if (!outgoing[i].length) column[i] = last;
2406
+ });
2407
+ const value = series?.values.map((v) => v ?? 0) ?? nodes.map(() => 0);
2408
+ const nodeW = Math.max(6, Math.min(16, area.width * 0.02));
2409
+ const gap = last > 0 ? (area.width - nodeW) / last : 0;
2410
+ const xOf = (c) => last > 0 ? area.x + c * gap : area.x + area.width / 2 - nodeW / 2;
2411
+ const columns = Array.from({ length: last + 1 }, (_, c) => nodes.map((_2, i) => i).filter((i) => column[i] === c));
2412
+ const pad = Math.max(4, Math.min(fs * 1.2, area.height / Math.max(1, Math.max(...columns.map((c) => c.length))) / 3));
2413
+ const ky = Math.min(...columns.filter((c) => c.length).map((c) => (area.height - pad * (c.length - 1)) / Math.max(1e-9, c.reduce((t, i) => t + value[i], 0))));
2414
+ const top = nodes.map(() => 0);
2415
+ for (const col of columns) {
2416
+ const used = col.reduce((t, i) => t + value[i] * ky, 0) + pad * Math.max(0, col.length - 1);
2417
+ let y = area.y + (area.height - used) / 2;
2418
+ for (const i of col) {
2419
+ top[i] = y;
2420
+ y += value[i] * ky + pad;
2421
+ }
2422
+ }
2423
+ const color = (i) => series?.pointColors?.[i] || chartSliceColor(spec, i);
2424
+ const named = new Set(spec.highlight);
2425
+ const lit = nodes.map((n) => named.has(n));
2426
+ const dimLinks = lit.some(Boolean);
2427
+ const outOffset = nodes.map(() => 0);
2428
+ const inOffset = nodes.map(() => 0);
2429
+ const startY = /* @__PURE__ */ new Map();
2430
+ const endY = /* @__PURE__ */ new Map();
2431
+ nodes.forEach((_, i) => {
2432
+ for (const k of [...outgoing[i]].sort((a, b) => top[at.get(links[a].target)] - top[at.get(links[b].target)])) {
2433
+ startY.set(k, top[i] + outOffset[i]);
2434
+ outOffset[i] += links[k].value * ky;
2435
+ }
2436
+ for (const k of [...incoming[i]].sort((a, b) => top[at.get(links[a].source)] - top[at.get(links[b].source)])) {
2437
+ endY.set(k, top[i] + inOffset[i]);
2438
+ inOffset[i] += links[k].value * ky;
2439
+ }
2440
+ });
2441
+ const selectedNode = (i) => ctx.isSelected({ kind: "category", index: i }) || ctx.isSelected({ kind: "point", seriesId: series?.id ?? "", index: i });
2442
+ links.forEach((l, k) => {
2443
+ const s = at.get(l.source);
2444
+ const t = at.get(l.target);
2445
+ const w = l.value * ky;
2446
+ const x0 = xOf(column[s]) + nodeW;
2447
+ const x1 = xOf(column[t]);
2448
+ const y0 = startY.get(k);
2449
+ const y1 = endY.get(k);
2450
+ const xm = (x0 + x1) / 2;
2451
+ const d = `M${r2(x0)},${r2(y0)}C${r2(xm)},${r2(y0)} ${r2(xm)},${r2(y1)} ${r2(x1)},${r2(y1)}L${r2(x1)},${r2(y1 + w)}C${r2(xm)},${r2(y1 + w)} ${r2(xm)},${r2(y0 + w)} ${r2(x0)},${r2(y0 + w)}Z`;
2452
+ const fill = l.color ?? (spec.sankeyLinkColor === "gray" ? theme.mutedText : color(spec.sankeyLinkColor === "target" ? t : s));
2453
+ const emphasis = selectedNode(s) || selectedNode(t) ? 0.6 : 0.35;
2454
+ const opacity = dimLinks && !lit[s] && !lit[t] ? emphasis * 0.25 : emphasis;
2455
+ marks.push({ kind: "path", key: `link-${k}`, role: "link", d, fill, opacity });
2456
+ });
2457
+ const labelsOn = series?.dataLabels ?? spec.dataLabels;
2458
+ const format = series?.format ?? spec.yAxis.format;
2459
+ let hidden = 0;
2460
+ nodes.forEach((name, i) => {
2461
+ const x = xOf(column[i]);
2462
+ const h = Math.max(1, value[i] * ky);
2463
+ const target = { kind: "category", index: i };
2464
+ const selected = selectedNode(i);
2465
+ marks.push({ kind: "rect", key: `node-${i}`, role: "node", x, y: top[i], width: nodeW, height: h, fill: color(i), radius: 1, ...selected ? { stroke: theme.selection, strokeWidth: 2 } : {}, target, selected });
2466
+ const text = labelsOn ? `${name} ${formatChartNumber(value[i], format)}` : name;
2467
+ const room = (last > 0 ? gap : area.width / 2) - nodeW - 10;
2468
+ const shown = truncate(text, Math.max(fs * 2, room), fs, measure);
2469
+ if (shown !== text) hidden++;
2470
+ const right = column[i] < last || last === 0;
2471
+ marks.push({ kind: "text", key: `label-node-${i}`, role: "data-label", x: right ? x + nodeW + 5 : x - 5, y: top[i] + h / 2, text: shown, anchor: right ? "start" : "end", baseline: "middle", size: fs, weight: selected ? 600 : void 0, fill: selected ? theme.selection : theme.text, target, selected });
2472
+ });
2473
+ if (hidden) ctx.warn("labels-truncated", `${hidden} node label${hidden === 1 ? " was" : "s were"} shortened to fit between the columns.`);
2474
+ return { plot: area };
2475
+ }
2476
+ function layoutBarRace(ctx, area, frame) {
2477
+ const { spec, theme, marks, fs, measure } = ctx;
2478
+ const top = spec.raceTopN;
2479
+ const moment = chartRaceMoment(spec.categories.length, frame);
2480
+ const ranking = chartRaceRanking(spec, frame);
2481
+ const shown = ranking.filter((e) => e.position < top);
2482
+ const labelW = Math.min(area.width * 0.3, Math.max(0, ...ranking.map((e) => measure(e.name, fs)))) + 8;
2483
+ const tickH = spec.yAxis.show ? fs * 1.6 : 4;
2484
+ const plot = { x: area.x + labelW, y: area.y + tickH, width: Math.max(1, area.width - labelW - fs * 4), height: Math.max(1, area.height - tickH) };
2485
+ const lead = Math.max(1e-9, ...shown.map((e) => e.value));
2486
+ const max = spec.yAxis.max !== null && spec.yAxis.max > 0 ? spec.yAxis.max : lead * 1.08;
2487
+ const xOf = (v) => plot.x + Math.max(0, Math.min(max, v)) / max * plot.width;
2488
+ const ticks = niceScale(0, max, 5).ticks.filter((t) => t <= max);
2489
+ const axisTarget = { kind: "axis", axis: "y" };
2490
+ const format = spec.yAxis.format;
2491
+ ticks.forEach((t, k) => {
2492
+ if (spec.yAxis.gridlines) marks.push({ kind: "line", key: `grid-y-${k}`, role: "grid", x1: xOf(t), y1: plot.y, x2: xOf(t), y2: plot.y + plot.height, stroke: theme.grid, strokeWidth: 1 });
2493
+ if (spec.yAxis.show) marks.push({ kind: "text", key: `tick-y-${k}`, role: "tick-label", x: xOf(t), y: area.y, text: formatChartNumber(t, format, format.decimals ?? stepDecimals(ticks[1] - ticks[0] || 1)), anchor: "middle", baseline: "top", size: fs - 1, fill: theme.mutedText, target: axisTarget });
2494
+ });
2495
+ const label = spec.categories[moment.t < 0.5 ? moment.from : moment.to] ?? "";
2496
+ const timeSize = Math.min(theme.titleSize * 3, plot.height * 0.2, plot.width * 0.5 / Math.max(1, label.length * 0.6));
2497
+ marks.push({ kind: "text", key: "race-time", role: "race-time", x: plot.x + plot.width, y: plot.y + plot.height - 4, text: label, anchor: "end", baseline: "bottom", size: timeSize, weight: 700, fill: theme.mutedText, opacity: 0.55, target: { kind: "category", index: moment.t < 0.5 ? moment.from : moment.to } });
2498
+ const band = plot.height / top;
2499
+ const barH = Math.max(2, band * (1 - Math.min(0.8, spec.gapWidth)));
2500
+ const index = Math.round(moment.at);
2501
+ for (const e of shown) {
2502
+ const si = spec.series.findIndex((s2) => s2.id === e.seriesId);
2503
+ const s = spec.series[si];
2504
+ const y = plot.y + e.position * band + (band - barH) / 2;
2505
+ const target = { kind: "point", seriesId: e.seriesId, index };
2506
+ const selected = ctx.isSelected(target) || ctx.seriesSelected(e.seriesId);
2507
+ const width = Math.max(0, xOf(e.value) - plot.x);
2508
+ marks.push({ kind: "rect", key: `bar-${e.seriesId}`, role: "bar", x: plot.x, y, width, height: barH, fill: chartSeriesColor(spec, si), radius: Math.min(3, barH / 4), target, selected, ...selected ? { stroke: theme.selection, strokeWidth: 1.5 } : {} });
2509
+ marks.push({ kind: "text", key: `label-name-${e.seriesId}`, role: "tick-label", x: plot.x - 6, y: y + barH / 2, text: truncate(e.name, labelW - 8, fs, measure), anchor: "end", baseline: "middle", size: fs, fill: selected ? theme.selection : theme.text, target: { kind: "series", seriesId: e.seriesId } });
2510
+ if (s.dataLabels ?? true) marks.push({ kind: "text", key: `label-${e.seriesId}`, role: "data-label", x: plot.x + width + 5, y: y + barH / 2, text: formatChartNumber(e.value, s.format ?? format, (s.format ?? format).decimals ?? (Math.abs(e.value) >= 100 ? 0 : void 0)), anchor: "start", baseline: "middle", size: fs - 1, fill: theme.text, target });
2511
+ }
2512
+ if (ranking.length > top) ctx.warn("values-skipped", `The race shows the top ${top} of ${ranking.length} names at each moment (raceTopN).`);
2513
+ return { plot, valueScale: { axis: "x", domain: [0, max], range: [plot.x, plot.x + plot.width], ticks } };
2514
+ }
2352
2515
 
2353
2516
  // src/chart/layout.ts
2354
2517
  function legendItems(spec, ctx) {
2355
2518
  const family = chartFamily(spec);
2356
- if (spec.type === "funnel" || spec.type === "slope") return [];
2519
+ if (spec.type === "funnel" || spec.type === "slope" || spec.type === "sankey" || spec.type === "barrace") return [];
2357
2520
  if (chartSpecColorsByCategory(spec)) {
2358
2521
  const first = spec.series.find((s) => !s.hidden);
2359
2522
  const seats = (i) => Math.max(0, Math.round(first?.values[i] ?? 0));
@@ -2493,6 +2656,14 @@ function layoutChart(spec, options) {
2493
2656
  return finish(layoutFunnel(ctx, area, visible[0]));
2494
2657
  case "treemap":
2495
2658
  return finish(spec.tree.length ? layoutTreemapTree(ctx, area, spec.series[0]) : layoutTreemap(ctx, area, visible[0]));
2659
+ case "barrace":
2660
+ return finish(layoutBarRace(ctx, area, options.raceFrame));
2661
+ case "sankey":
2662
+ if (!spec.links.length) {
2663
+ ctx.warn("type-fallback", "A Sankey diagram needs flows (set_chart_links); it is drawn as a bar chart until then.");
2664
+ return finish(layoutCartesian(ctx, area, visible));
2665
+ }
2666
+ return finish(layoutSankey(ctx, area, spec.series[0]));
2496
2667
  case "sunburst":
2497
2668
  return finish(layoutSunburst(ctx, area, spec.tree.length ? spec.series[0] : visible[0]));
2498
2669
  case "slope":
@@ -2696,6 +2867,9 @@ export {
2696
2867
  MAX_CHART_DATA_LABELS,
2697
2868
  MAX_CHART_LINE_POINTS,
2698
2869
  MAX_CHART_MARKERS,
2870
+ chartRaceValueAt,
2871
+ chartRaceMoment,
2872
+ chartRaceRanking,
2699
2873
  layoutChart,
2700
2874
  chartValueAtPoint,
2701
2875
  chartXAtPoint,
@@ -0,0 +1,204 @@
1
+ import {
2
+ splitSource
3
+ } from "./chunk-O6OGNDND.js";
4
+
5
+ // src/diagram/sankey/index.ts
6
+ function parseSankey(source) {
7
+ const split = splitSource(source);
8
+ if (!/^\s*sankey(?:-beta)?\b/i.test(split.header)) {
9
+ return { error: "Not a sankey diagram: the first line should be `sankey-beta`." };
10
+ }
11
+ const firstLine = split.headerIndex + 2;
12
+ const kept = [];
13
+ split.bodyLines.forEach((text, i) => {
14
+ if (/^\s*%%/.test(text)) return;
15
+ kept.push({ text: text.trim(), line: firstLine + i });
16
+ });
17
+ const records = readRecords(kept);
18
+ if ("error" in records) return records;
19
+ const nodes = [];
20
+ const seen = /* @__PURE__ */ new Set();
21
+ const add = (name) => {
22
+ if (!seen.has(name)) {
23
+ seen.add(name);
24
+ nodes.push(name);
25
+ }
26
+ };
27
+ const links = [];
28
+ for (const { fields, line } of records) {
29
+ if (fields.length !== 3) {
30
+ return {
31
+ error: `Expected three fields (source,target,value), found ${fields.length}.`,
32
+ line
33
+ };
34
+ }
35
+ const [source2, target, raw] = fields;
36
+ const value = Number.parseFloat(raw);
37
+ if (!Number.isFinite(value)) {
38
+ return { error: `"${raw}" is not a number.`, line };
39
+ }
40
+ add(source2);
41
+ add(target);
42
+ links.push({ source: source2, target, value });
43
+ }
44
+ if (!links.length) return { error: "No links: add lines like `A,B,10`." };
45
+ return { data: { nodes, links } };
46
+ }
47
+ function readRecords(lines) {
48
+ const records = [];
49
+ let i = 0;
50
+ while (i < lines.length) {
51
+ const start = lines[i];
52
+ if (start.text === "") {
53
+ i++;
54
+ continue;
55
+ }
56
+ const fields = [];
57
+ let text = start.text;
58
+ let pos = 0;
59
+ let field = "";
60
+ let quoted = false;
61
+ for (; ; ) {
62
+ if (quoted) {
63
+ const close = text.indexOf('"', pos);
64
+ if (close < 0) {
65
+ field += text.slice(pos);
66
+ i++;
67
+ if (i >= lines.length)
68
+ return { error: "A quoted field is never closed.", line: start.line };
69
+ field += "\n";
70
+ text = lines[i].text;
71
+ pos = 0;
72
+ continue;
73
+ }
74
+ field += text.slice(pos, close);
75
+ if (text[close + 1] === '"') {
76
+ field += '"';
77
+ pos = close + 2;
78
+ continue;
79
+ }
80
+ quoted = false;
81
+ pos = close + 1;
82
+ const rest = /^[ \t]*/.exec(text.slice(pos))[0].length;
83
+ pos += rest;
84
+ if (pos < text.length && text[pos] !== ",") {
85
+ return { error: "Unexpected text after a quoted field.", line: lines[i].line };
86
+ }
87
+ continue;
88
+ }
89
+ if (pos >= text.length) {
90
+ fields.push(field.trim());
91
+ break;
92
+ }
93
+ const ch = text[pos];
94
+ if (ch === ",") {
95
+ fields.push(field.trim());
96
+ field = "";
97
+ pos++;
98
+ } else if (ch === '"' && field.trim() === "") {
99
+ quoted = true;
100
+ field = "";
101
+ pos++;
102
+ } else if (ch === '"') {
103
+ return { error: "A quote inside an unquoted field.", line: lines[i].line };
104
+ } else {
105
+ field += ch;
106
+ pos++;
107
+ }
108
+ }
109
+ records.push({ fields, line: start.line });
110
+ i++;
111
+ }
112
+ return records;
113
+ }
114
+
115
+ // src/diagram/treemap/index.ts
116
+ var NAME = String.raw`(?:"([^"]*)"|'([^']*)')`;
117
+ var CLASS = String.raw`(?:\s*:::\s*([A-Za-z_]\w*))?`;
118
+ var SECTION_RE = new RegExp(String.raw`^${NAME}${CLASS}$`);
119
+ var LEAF_RE = new RegExp(String.raw`^${NAME}\s*[:,]\s*([0-9_.,]+)${CLASS}$`);
120
+ function parseTreemap(source) {
121
+ const split = splitSource(source);
122
+ if (!/^\s*treemap(?:-beta)?\b/i.test(split.header)) {
123
+ return { error: "Not a treemap: the first line should be `treemap-beta`." };
124
+ }
125
+ const data = { items: [] };
126
+ const stack = [];
127
+ const firstLine = split.headerIndex + 2;
128
+ const lines = split.bodyLines;
129
+ for (let i = 0; i < lines.length; i++) {
130
+ const raw = lines[i];
131
+ const line = firstLine + i;
132
+ const indent = /^[ \t]*/.exec(raw)[0].length;
133
+ const text = stripComment(raw).trim();
134
+ if (!text) continue;
135
+ const accBlock = /^accDescr\s*\{(.*)$/.exec(text);
136
+ if (accBlock) {
137
+ let body = accBlock[1];
138
+ while (!body.includes("}") && i + 1 < lines.length) body += `
139
+ ${lines[++i]}`;
140
+ if (!body.includes("}")) return { error: "`accDescr {` is never closed.", line };
141
+ data.accDescr = body.slice(0, body.indexOf("}")).trim();
142
+ continue;
143
+ }
144
+ const acc = /^(accTitle|accDescr)\s*:(.*)$/.exec(text);
145
+ if (acc) {
146
+ data[acc[1]] = acc[2].trim();
147
+ continue;
148
+ }
149
+ const title = /^title(?:[ \t]+(.*))?$/.exec(text);
150
+ if (title) {
151
+ data.title = (title[1] ?? "").trim() || void 0;
152
+ continue;
153
+ }
154
+ if (/^classDef\s/.test(text)) continue;
155
+ let item;
156
+ const leaf = LEAF_RE.exec(text);
157
+ const section = leaf ? null : SECTION_RE.exec(text);
158
+ if (leaf) {
159
+ const value = Number.parseFloat(leaf[3].replace(/,/g, ""));
160
+ if (!Number.isFinite(value)) return { error: `"${leaf[3]}" is not a number.`, line };
161
+ item = { name: leaf[1] ?? leaf[2], value };
162
+ if (leaf[4]) item.className = leaf[4];
163
+ } else if (section) {
164
+ item = { name: section[1] ?? section[2], children: [] };
165
+ if (section[3]) item.className = section[3];
166
+ } else {
167
+ return {
168
+ error: /^["']/.test(text) ? 'Expected `"Name"` for a section or `"Name": value` for a leaf.' : 'Names must be in quotes, e.g. `"Sales": 120`.',
169
+ line
170
+ };
171
+ }
172
+ while (stack.length && stack[stack.length - 1].indent >= indent) stack.pop();
173
+ const parent = stack[stack.length - 1]?.item;
174
+ if (parent) parent.children.push(item);
175
+ else data.items.push(item);
176
+ if (item.children) stack.push({ item, indent });
177
+ }
178
+ if (!data.items.length) return { error: 'No items: add lines like `"Sales": 120`.' };
179
+ return { data };
180
+ }
181
+ function stripComment(line) {
182
+ let quote = null;
183
+ for (let i = 0; i < line.length; i++) {
184
+ const ch = line[i];
185
+ if (quote) {
186
+ if (ch === quote) quote = null;
187
+ } else if (ch === '"' || ch === "'") {
188
+ quote = ch;
189
+ } else if (ch === "%" && line[i + 1] === "%") {
190
+ return line.slice(0, i);
191
+ }
192
+ }
193
+ return line;
194
+ }
195
+ function treemapValue(item) {
196
+ if (!item.children) return item.value ?? 0;
197
+ return item.children.reduce((sum, child) => sum + treemapValue(child), 0);
198
+ }
199
+
200
+ export {
201
+ parseSankey,
202
+ parseTreemap,
203
+ treemapValue
204
+ };
@@ -51,7 +51,7 @@ import {
51
51
  import {
52
52
  oklabToRgb,
53
53
  rgbToOklab
54
- } from "./chunk-BQWGN5WH.js";
54
+ } from "./chunk-M2TPMYJE.js";
55
55
  import {
56
56
  hexToRgba,
57
57
  normalizeTokenColor,
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  oklabToRgb,
8
8
  rgbToOklab
9
- } from "./chunk-4VU4CP7R.js";
9
+ } from "./chunk-MHLU6GRJ.js";
10
10
 
11
11
  // src/animation/color.ts
12
12
  function rgbToOklab2(r, g, b, alpha = 1) {
@@ -4,7 +4,7 @@
4
4
  var _chunkDWJJI6RTcjs = require('./chunk-DWJJI6RT.cjs');
5
5
 
6
6
 
7
- var _chunkCIPIESX4cjs = require('./chunk-CIPIESX4.cjs');
7
+ var _chunk4ZC5QJE2cjs = require('./chunk-4ZC5QJE2.cjs');
8
8
 
9
9
  // src/animation/keyframes.ts
10
10
  var ANIMATION_TIME_EPSILON = 1e-6;
@@ -46,7 +46,7 @@ function interpolateAnimationValue(from, to, t, type = "number") {
46
46
  if (t <= 0) return from;
47
47
  if (t >= 1) return to;
48
48
  if (type === "color" || typeof from === "string" && typeof to === "string") {
49
- const mixed = _chunkCIPIESX4cjs.mixColorsOklab.call(void 0, from, to, t);
49
+ const mixed = _chunk4ZC5QJE2cjs.mixColorsOklab.call(void 0, from, to, t);
50
50
  if (mixed !== null) return mixed;
51
51
  }
52
52
  return from;