@cardenelabs/cdl 0.78.0 → 0.80.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,57 @@ CDL (Chainome Diagram Language) の主要変更履歴。
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.80.0] - 2026-09-27
9
+
10
+ ### Fixed
11
+
12
+ - **押せる箱を指すと押せる形のカーソルが出るようになった** (#869 / #896)
13
+
14
+ `relations: hover` を書いた図の箱は押せる形になるが、指しても `pointer` の cursor が
15
+ 出なかった。 読み上げには `role="button"` で「押せる」 と伝わる一方、見た目には出ない =
16
+ 読み手は触れて光るだけの箱だと読む。
17
+
18
+ 原因は同じ要素に `style` を 2 回渡していたこと。 React は後の値で前を丸ごと置き換える
19
+ ため、押せる箱に渡した `cursor: pointer` が後ろの `transition` に消されていた。
20
+
21
+ | 項目 | 直す前 | 直した後 |
22
+ |---|---|---|
23
+ | 押せる箱の `style` | `transition` だけ | `transition` と `cursor: pointer` |
24
+ | 押せない箱の `style` | `transition` だけ | 変えない |
25
+
26
+ `style` を 1 箇所でまとめて作り、要素へ 1 回だけ渡す形にした。 条件つきで広げる側には
27
+ 置かない = そこに残すと、後ろに `style` を 1 つ足しただけでまた黙って消える。
28
+
29
+ 押せない箱の cursor は親から継承したままにする (編集画面では掴む形になる)。
30
+
31
+ ## [0.79.0] - 2026-09-27
32
+
33
+ ### Fixed
34
+
35
+ - **円弧を含む線の外接矩形が図の左上まで伸びるのを直した** (#872 / #895)
36
+
37
+ 道筋を読む所が **命令ごとの引数の数を見ていなかった**。 文字を全部消して数を 2 つずつ
38
+ 組にしていたため、円弧 (`A`) の引数 7 個 = 奇数で組にする位置が 1 つずれ、その後ろの点が
39
+ 全部ずれる。 道筋のどこにも無い `(11,11)` と `(0,0)` が点として入っていた。
40
+
41
+ | 何を | 値 |
42
+ |---|---|
43
+ | 道筋の実際の範囲 | x 105..507 / y 278..628 |
44
+ | 直す前の読み方が出す範囲 | x 0..691 / y 0..472 |
45
+
46
+ 画面で 358px 離れている名札と線が「重なっている」 と知らされる。 逆向きにも効いており、
47
+ 矩形が実際より大きいぶん近接の押し出しが誤った方向に働いていた。
48
+
49
+ **壊れていたのは 3 箇所**。 同じ読み方が散っており、道筋を組み直す所は円弧の `A` を命令と
50
+ して認識できず、前の命令の引数に混ざって `NaN` を書き出していた (円弧を持つ経路を
51
+ 近接の解消で動かすと道筋そのものが壊れる)。
52
+
53
+ 命令ごとの引数の数を 1 箇所に持ち、3 つの関数がそれを使う形にした。
54
+
55
+ 外接矩形は円弧の膨らみを足す。 円弧は終点しか座標を持たないが実際の線は弦から膨らむので
56
+ (経路が経路を跨ぐ飛び越しは半径 11 の半円)、終点だけで矩形を作ると本当の重なりを
57
+ 見落とす側に倒れる。
58
+
8
59
  ## [0.78.0] - 2026-09-26
9
60
 
10
61
  ### Fixed
@@ -3228,7 +3279,9 @@ Initial OSS release.
3228
3279
  使わない = annotated tag を push しても GitHub Release は作られず、 Release を作っていない版で
3229
3280
  行き止まりになる。
3230
3281
 
3231
- [Unreleased]: https://github.com/cardene777/cdl/compare/v0.78.0...HEAD
3282
+ [Unreleased]: https://github.com/cardene777/cdl/compare/v0.80.0...HEAD
3283
+ [0.80.0]: https://github.com/cardene777/cdl/compare/v0.79.0...v0.80.0
3284
+ [0.79.0]: https://github.com/cardene777/cdl/compare/v0.78.0...v0.79.0
3232
3285
  [0.78.0]: https://github.com/cardene777/cdl/compare/v0.77.0...v0.78.0
3233
3286
  [0.77.0]: https://github.com/cardene777/cdl/compare/v0.76.0...v0.77.0
3234
3287
  [0.76.0]: https://github.com/cardene777/cdl/compare/v0.75.0...v0.76.0
package/dist/index.cjs CHANGED
@@ -12798,72 +12798,169 @@ function collectBBoxes(lanes, nodes, edges) {
12798
12798
  });
12799
12799
  }
12800
12800
  for (const e of edges) {
12801
- const points = extractPathPoints(e.d);
12802
- if (points.length === 0) continue;
12803
- const xs = points.map((p) => p.x);
12804
- const ys = points.map((p) => p.y);
12805
- const minX = Math.min(...xs);
12806
- const maxX = Math.max(...xs);
12807
- const minY = Math.min(...ys);
12808
- const maxY = Math.max(...ys);
12801
+ const box = pathBBox(e.d);
12802
+ if (!box) continue;
12809
12803
  out.push({
12810
12804
  kind: "edge-path",
12811
12805
  id: e.id,
12812
- x: minX - 2,
12813
- y: minY - 2,
12814
- w: maxX - minX + 4,
12815
- h: maxY - minY + 4
12806
+ x: box.minX - 2,
12807
+ y: box.minY - 2,
12808
+ w: box.maxX - box.minX + 4,
12809
+ h: box.maxY - box.minY + 4
12816
12810
  });
12817
12811
  }
12818
12812
  return out;
12819
12813
  }
12814
+ var PATH_ARITY = {
12815
+ M: 2,
12816
+ L: 2,
12817
+ H: 1,
12818
+ V: 1,
12819
+ C: 6,
12820
+ S: 4,
12821
+ Q: 4,
12822
+ T: 2,
12823
+ A: 7,
12824
+ Z: 0
12825
+ };
12826
+ function isRelative(cmd) {
12827
+ return cmd >= "a" && cmd <= "z";
12828
+ }
12829
+ function coordSlots(cmd) {
12830
+ switch (cmd.toUpperCase()) {
12831
+ case "M":
12832
+ case "L":
12833
+ case "T":
12834
+ return [[0, 1]];
12835
+ case "H":
12836
+ return [[0, -1]];
12837
+ case "V":
12838
+ return [[-1, 0]];
12839
+ case "Q":
12840
+ case "S":
12841
+ return [[0, 1], [2, 3]];
12842
+ case "C":
12843
+ return [[0, 1], [2, 3], [4, 5]];
12844
+ case "A":
12845
+ return [[5, 6]];
12846
+ default:
12847
+ return [];
12848
+ }
12849
+ }
12850
+ function parsePathCommands(d) {
12851
+ const out = [];
12852
+ const commandRegex = /([MmLlHhVvCcSsQqTtAaZz])([^MmLlHhVvCcSsQqTtAaZz]*)/g;
12853
+ let m;
12854
+ while ((m = commandRegex.exec(d)) !== null) {
12855
+ const cmd = m[1];
12856
+ const arity = PATH_ARITY[cmd.toUpperCase()] ?? 0;
12857
+ if (arity === 0) {
12858
+ out.push({ cmd, args: [] });
12859
+ continue;
12860
+ }
12861
+ const nums = (m[2] ?? "").trim().split(/[\s,]+/).filter(Boolean).map(parseFloat);
12862
+ for (let i = 0; i + arity <= nums.length; i += arity) {
12863
+ const args = nums.slice(i, i + arity);
12864
+ if (!args.every(Number.isFinite)) break;
12865
+ const \u7E70\u308A\u8FD4\u3057 = i > 0;
12866
+ const c = \u7E70\u308A\u8FD4\u3057 && cmd === "M" ? "L" : \u7E70\u308A\u8FD4\u3057 && cmd === "m" ? "l" : cmd;
12867
+ out.push({ cmd: c, args });
12868
+ }
12869
+ }
12870
+ return out;
12871
+ }
12820
12872
  function extractPathPoints(d) {
12821
12873
  const out = [];
12822
- const tokens = d.replace(/[A-Za-z]/g, " ").trim().split(/[\s,]+/).filter(Boolean);
12823
- for (let i = 0; i + 1 < tokens.length; i += 2) {
12824
- const x = parseFloat(tokens[i]);
12825
- const y = parseFloat(tokens[i + 1]);
12826
- if (Number.isFinite(x) && Number.isFinite(y)) {
12874
+ let cur = { x: 0, y: 0 };
12875
+ let started = false;
12876
+ for (const { cmd, args } of parsePathCommands(d)) {
12877
+ const \u76F8\u5BFE = started && isRelative(cmd);
12878
+ const \u57FA\u70B9 = cur;
12879
+ for (const [xi, yi] of coordSlots(cmd)) {
12880
+ const x = xi >= 0 ? \u76F8\u5BFE ? \u57FA\u70B9.x + args[xi] : args[xi] : \u57FA\u70B9.x;
12881
+ const y = yi >= 0 ? \u76F8\u5BFE ? \u57FA\u70B9.y + args[yi] : args[yi] : \u57FA\u70B9.y;
12827
12882
  out.push({ x, y });
12883
+ cur = { x, y };
12828
12884
  }
12885
+ started = true;
12829
12886
  }
12830
12887
  return out;
12831
12888
  }
12889
+ function pathBBox(d) {
12890
+ let minX = Infinity;
12891
+ let minY = Infinity;
12892
+ let maxX = -Infinity;
12893
+ let maxY = -Infinity;
12894
+ let \u70B9\u3042\u308A = false;
12895
+ const \u53D6\u308B = (x, y) => {
12896
+ minX = Math.min(minX, x);
12897
+ minY = Math.min(minY, y);
12898
+ maxX = Math.max(maxX, x);
12899
+ maxY = Math.max(maxY, y);
12900
+ \u70B9\u3042\u308A = true;
12901
+ };
12902
+ let cur = { x: 0, y: 0 };
12903
+ let started = false;
12904
+ for (const { cmd, args } of parsePathCommands(d)) {
12905
+ const \u76F8\u5BFE = started && isRelative(cmd);
12906
+ const \u57FA\u70B9 = cur;
12907
+ for (const [xi, yi] of coordSlots(cmd)) {
12908
+ const x = xi >= 0 ? \u76F8\u5BFE ? \u57FA\u70B9.x + args[xi] : args[xi] : \u57FA\u70B9.x;
12909
+ const y = yi >= 0 ? \u76F8\u5BFE ? \u57FA\u70B9.y + args[yi] : args[yi] : \u57FA\u70B9.y;
12910
+ \u53D6\u308B(x, y);
12911
+ cur = { x, y };
12912
+ }
12913
+ if (cmd.toUpperCase() === "A") {
12914
+ const r = Math.max(Math.abs(args[0]), Math.abs(args[1]));
12915
+ const \u5F26 = Math.hypot(cur.x - \u57FA\u70B9.x, cur.y - \u57FA\u70B9.y);
12916
+ const \u534A\u5F26 = Math.min(\u5F26 / 2, r);
12917
+ const \u4F59 = Math.sqrt(Math.max(0, r * r - \u534A\u5F26 * \u534A\u5F26));
12918
+ const \u81A8\u3089\u307F = args[3] !== 0 ? r + \u4F59 : r - \u4F59;
12919
+ \u53D6\u308B(Math.min(\u57FA\u70B9.x, cur.x) - \u81A8\u3089\u307F, Math.min(\u57FA\u70B9.y, cur.y) - \u81A8\u3089\u307F);
12920
+ \u53D6\u308B(Math.max(\u57FA\u70B9.x, cur.x) + \u81A8\u3089\u307F, Math.max(\u57FA\u70B9.y, cur.y) + \u81A8\u3089\u307F);
12921
+ }
12922
+ started = true;
12923
+ }
12924
+ return \u70B9\u3042\u308A ? { minX, minY, maxX, maxY } : null;
12925
+ }
12832
12926
  function rebuildPathD(d, map) {
12833
- const commandRegex = /([MLCQZmlcqz])([^MLCQZmlcqz]*)/g;
12834
12927
  let out = "";
12835
12928
  let idx = 0;
12836
- let m;
12837
- while ((m = commandRegex.exec(d)) !== null) {
12838
- const cmd = m[1];
12839
- const argsStr = (m[2] ?? "").trim();
12840
- if (argsStr === "" || cmd === "Z" || cmd === "z") {
12929
+ let cur = { x: 0, y: 0 };
12930
+ let started = false;
12931
+ for (const { cmd, args } of parsePathCommands(d)) {
12932
+ const slots = coordSlots(cmd);
12933
+ if (slots.length === 0) {
12841
12934
  out += cmd + " ";
12935
+ started = true;
12842
12936
  continue;
12843
12937
  }
12844
- const args = argsStr.split(/[\s,]+/).filter(Boolean).map(parseFloat);
12845
- const translated = [];
12846
- for (let i = 0; i + 1 < args.length; i += 2) {
12847
- const [nx, ny] = map(args[i], args[i + 1], idx);
12848
- translated.push(nx, ny);
12938
+ const \u76F8\u5BFE = started && isRelative(cmd);
12939
+ const \u57FA\u70B9 = cur;
12940
+ const \u6B21\u306E\u5F15\u6570 = args.slice();
12941
+ for (const [xi, yi] of slots) {
12942
+ const x = xi >= 0 ? \u76F8\u5BFE ? \u57FA\u70B9.x + args[xi] : args[xi] : \u57FA\u70B9.x;
12943
+ const y = yi >= 0 ? \u76F8\u5BFE ? \u57FA\u70B9.y + args[yi] : args[yi] : \u57FA\u70B9.y;
12944
+ const [nx, ny] = map(x, y, idx);
12945
+ if (!\u76F8\u5BFE) {
12946
+ if (xi >= 0) \u6B21\u306E\u5F15\u6570[xi] = nx;
12947
+ if (yi >= 0) \u6B21\u306E\u5F15\u6570[yi] = ny;
12948
+ }
12849
12949
  idx += 1;
12950
+ cur = { x, y };
12850
12951
  }
12851
- out += cmd + " " + translated.join(" ") + " ";
12952
+ out += cmd + " " + \u6B21\u306E\u5F15\u6570.join(" ") + " ";
12953
+ started = true;
12852
12954
  }
12853
12955
  return out.trim();
12854
12956
  }
12855
12957
  function cornerBoundaryIndices(d) {
12856
- const commandRegex = /([MLCQZmlcqz])([^MLCQZmlcqz]*)/g;
12857
12958
  let idx = 0;
12858
12959
  let firstCornerEnd = -1;
12859
12960
  let lastCornerStart = -1;
12860
- let m;
12861
- while ((m = commandRegex.exec(d)) !== null) {
12862
- const cmd = m[1];
12863
- const argsStr = (m[2] ?? "").trim();
12864
- if (argsStr === "" || cmd === "Z" || cmd === "z") continue;
12865
- const pairs = argsStr.split(/[\s,]+/).filter(Boolean).length >> 1;
12866
- if ((cmd === "Q" || cmd === "q") && pairs >= 2) {
12961
+ for (const { cmd } of parsePathCommands(d)) {
12962
+ const pairs = coordSlots(cmd).length;
12963
+ if (cmd.toUpperCase() === "Q" && pairs >= 2) {
12867
12964
  if (firstCornerEnd < 0) firstCornerEnd = idx + 1;
12868
12965
  lastCornerStart = idx;
12869
12966
  }
@@ -32011,6 +32108,10 @@ function CdlNodeView({
32011
32108
  const offsetX = node.renderOffsetX !== void 0 ? resolveNumericAttr(node.renderOffsetX, stateValues, 0) : 0;
32012
32109
  const offsetY = node.renderOffsetY !== void 0 ? resolveNumericAttr(node.renderOffsetY, stateValues, 0) : 0;
32013
32110
  const hasOffset = offsetX !== 0 || offsetY !== 0;
32111
+ const \u7BB1\u306Estyle = {
32112
+ transition: "opacity 120ms ease-out, transform 200ms ease-out",
32113
+ ...\u95A2\u4FC2\u306E\u53D7\u3051\u53E3 === void 0 ? {} : { cursor: "pointer" }
32114
+ };
32014
32115
  return /* @__PURE__ */ jsxRuntime.jsxs(
32015
32116
  "g",
32016
32117
  {
@@ -32032,7 +32133,6 @@ function CdlNodeView({
32032
32133
  role: "button",
32033
32134
  tabIndex: 0,
32034
32135
  "aria-pressed": \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u7126\u70B9 === node.id,
32035
- style: { cursor: "pointer" },
32036
32136
  onPointerEnter: () => \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u89E6\u308C\u305F(node.id),
32037
32137
  onPointerLeave: () => \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u96E2\u308C\u305F(),
32038
32138
  onFocus: () => \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u89E6\u308C\u305F(node.id),
@@ -32047,7 +32147,7 @@ function CdlNodeView({
32047
32147
  transform: hasOffset ? `translate(${offsetX}, ${offsetY})` : void 0,
32048
32148
  opacity: boundOpacity,
32049
32149
  "data-cdl-tone": node.tone,
32050
- style: { transition: "opacity 120ms ease-out, transform 200ms ease-out" },
32150
+ style: \u7BB1\u306Estyle,
32051
32151
  children: [
32052
32152
  /* @__PURE__ */ jsxRuntime.jsx(
32053
32153
  "rect",