@cardenelabs/cdl 0.59.0 → 0.59.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.59.0",
3
+ "version": "0.59.1",
4
4
  "description": "CDL (Chainome Diagram Language). Mermaid-like declarative DSL that compiles to animated SVG diagrams. Built for blockchain / Solidity flows, generic enough for sequence / flow / state / ER / topology diagrams.",
5
5
  "license": "MIT",
6
6
  "author": "cardene777",
@@ -133,6 +133,36 @@ export function displayWidth(text: string): number {
133
133
  return w;
134
134
  }
135
135
 
136
+ /**
137
+ * 字を先頭から半角の桁数 (`displayWidth`) で `桁` に収まるだけ取り、取った字と残りに割る (#851)。
138
+ *
139
+ * 2 桁の字が境目をまたぐ時は、その字を残りの側に回す (取った側が `桁` を越えない)。
140
+ */
141
+ export function 桁で割る(text: string, 桁: number): [string, string] {
142
+ let w = 0;
143
+ let 取った = "";
144
+ for (const ch of text) {
145
+ const code = ch.codePointAt(0) ?? 0;
146
+ const 字の桁 = code >= 0x20 && code <= 0x7e ? 1 : 2;
147
+ if (w + 字の桁 > 桁) break;
148
+ w += 字の桁;
149
+ 取った += ch;
150
+ }
151
+ return [取った, text.slice(取った.length)];
152
+ }
153
+
154
+ /**
155
+ * 字が半角の桁数で `上限` を越える時、「上限 - 1」 桁に収まるだけの字に `…` を付けて返す (#851)。
156
+ *
157
+ * 読み出しは字数で省いていた。 上限の字数は英字の幅で決めてあり、和文は 1 字が英字のおよそ 2 倍の幅なので
158
+ * 上限の字数に届く前に枠からはみ出していた。 上限を半角の桁数と読み替えれば、ASCII だけの字は変更前の
159
+ * `length > 上限 ? slice(0, 上限 - 1) + "…"` と同じ所で省き、和文は半分の字数で省く。
160
+ */
161
+ export function 桁で省く(text: string, 上限: number): string {
162
+ if (displayWidth(text) <= 上限) return text;
163
+ return `${桁で割る(text, 上限 - 1)[0]}…`;
164
+ }
165
+
136
166
  /**
137
167
  * rows[] から最低限必要な node width を算出。
138
168
  * 左 padding + 左列 max 幅 + col gap + 右列 max 幅 + 右 padding。
@@ -16,6 +16,7 @@ import {
16
16
  import type { Signal, Computed } from "../reactive";
17
17
  import { effect } from "../reactive";
18
18
  import { textWidth } from "../kinds/text-width";
19
+ import { 桁で割る, 桁で省く } from "../kinds/row-align";
19
20
 
20
21
  /**
21
22
  * interactive primitive (input widget / formula / scroll trigger / event handler) を SVG diagram に
@@ -7510,7 +7511,7 @@ function KanbanBoardReadout({
7510
7511
  style={{ userSelect: "none" }}
7511
7512
  >
7512
7513
  <title>{task.title}</title>
7513
- {task.title.length > 18 ? `${task.title.slice(0, 17)}…` : task.title}
7514
+ {桁で省く(task.title, 18)}
7514
7515
  </text>
7515
7516
  </g>
7516
7517
  );
@@ -7667,7 +7668,7 @@ function TimelineVerticalReadout({
7667
7668
  style={{ userSelect: "none" }}
7668
7669
  >
7669
7670
  <title>{ev.title}</title>
7670
- {ev.title.length > 38 ? `${ev.title.slice(0, 37)}…` : ev.title}
7671
+ {桁で省く(ev.title, 38)}
7671
7672
  </text>
7672
7673
  {ev.description ? (
7673
7674
  <text
@@ -7678,7 +7679,7 @@ function TimelineVerticalReadout({
7678
7679
  style={{ userSelect: "none" }}
7679
7680
  >
7680
7681
  <title>{ev.description}</title>
7681
- {ev.description.length > 44 ? `${ev.description.slice(0, 43)}…` : ev.description}
7682
+ {桁で省く(ev.description, 44)}
7682
7683
  </text>
7683
7684
  ) : null}
7684
7685
  </g>
@@ -8036,7 +8037,7 @@ function StepProgressReadout({
8036
8037
  style={{ userSelect: "none" }}
8037
8038
  >
8038
8039
  <title>{s}</title>
8039
- {s.length > 10 ? `${s.slice(0, 9)}…` : s}
8040
+ {桁で省く(s, 10)}
8040
8041
  </text>
8041
8042
  </g>
8042
8043
  );
@@ -8102,7 +8103,7 @@ function UserPresenceReadout({
8102
8103
  fill="#0f172a"
8103
8104
  style={{ userSelect: "none" }}
8104
8105
  >
8105
- {u.name.length > 24 ? `${u.name.slice(0, 23)}…` : u.name}
8106
+ {桁で省く(u.name, 24)}
8106
8107
  </text>
8107
8108
  <text
8108
8109
  x={width - 4}
@@ -8272,7 +8273,7 @@ function OrgChartMiniReadout({
8272
8273
  style={{ userSelect: "none" }}
8273
8274
  >
8274
8275
  <title>{n.name}</title>
8275
- {n.name.length > 10 ? `${n.name.slice(0, 9)}…` : n.name}
8276
+ {桁で省く(n.name, 10)}
8276
8277
  </text>
8277
8278
  {lvl < 2 && byLevel[lvl + 1]!.length > 0 ? (
8278
8279
  <line
@@ -8887,7 +8888,7 @@ function FileDropzoneReadout({
8887
8888
  </text>
8888
8889
  <text x={54} y={34} fontSize={12} fontWeight={600} fill="#0f172a" style={{ userSelect: "none" }}>
8889
8890
  <title>{filename}</title>
8890
- {filename.length > 26 ? `${filename.slice(0, 25)}…` : filename}
8891
+ {桁で省く(filename, 26)}
8891
8892
  </text>
8892
8893
  <text x={54} y={50} fontSize={10} fill="#64748b" style={{ userSelect: "none" }}>
8893
8894
  <字 k="readout.dropzone.uploaded" />
@@ -8972,7 +8973,7 @@ function LogStreamReadout({
8972
8973
  <rect x={0} y={0} width={width} height={height} fill="#0f172a" rx={4} />
8973
8974
  {shown.map((r, i) => {
8974
8975
  const y = 4 + i * rowH;
8975
- const displayMsg = r.msg.length > 24 ? `${r.msg.slice(0, 23)}…` : r.msg;
8976
+ const displayMsg = 桁で省く(r.msg, 24);
8976
8977
  return (
8977
8978
  <g key={i} data-cdl-log-row={i}>
8978
8979
  <text x={6} y={y + 12} fontSize={9} fill="#94a3b8" fontFamily="monospace" style={{ userSelect: "none" }}>
@@ -9071,7 +9072,7 @@ function AlertBannerReadout({
9071
9072
  style={{ userSelect: "none" }}
9072
9073
  >
9073
9074
  <title>{message}</title>
9074
- {message.length > 32 ? `${message.slice(0, 31)}…` : message}
9075
+ {桁で省く(message, 32)}
9075
9076
  </text>
9076
9077
  </svg>
9077
9078
  </div>
@@ -9144,7 +9145,7 @@ function ServiceHealthReadout({
9144
9145
  style={{ userSelect: "none" }}
9145
9146
  >
9146
9147
  <title>{s.name}</title>
9147
- {s.name.length > 10 ? `${s.name.slice(0, 9)}…` : s.name}
9148
+ {桁で省く(s.name, 10)}
9148
9149
  </text>
9149
9150
  </g>
9150
9151
  );
@@ -9328,7 +9329,7 @@ function PricingTierReadout({
9328
9329
  </text>
9329
9330
  {features.slice(0, 3).map((f, i) => {
9330
9331
  const y = 96 + i * 18;
9331
- const displayF = f.length > 24 ? `${f.slice(0, 23)}…` : f;
9332
+ const displayF = 桁で省く(f, 24);
9332
9333
  return (
9333
9334
  <g key={i} data-cdl-tier-feature={i}>
9334
9335
  <text x={16} y={y} fontSize={11} fill={colorAccent} style={{ userSelect: "none" }}>
@@ -9487,6 +9488,9 @@ function ArticlePreviewReadout({
9487
9488
  const width = 300;
9488
9489
  const height = 116;
9489
9490
  const thumbW = 80;
9491
+ // 抜粋は 1 行 28 桁で 2 行に割る (#851)。 1 行目は収まるだけ置いて `…` を付けず、残りを 2 行目に続ける。
9492
+ // 以前は 1 行目を 27 字と `…` で切り、2 行目を 29 字目から描いていたので、28 字目がどちらの行にも出なかった
9493
+ const [抜粋の一行目, 抜粋の残り] = 桁で割る(excerpt, 28);
9490
9494
  return (
9491
9495
  <div className="cdl-ip-item cdl-ip-readout-article-preview" data-cdl-readout={id}>
9492
9496
  <span className="cdl-ip-label">{label}</span>
@@ -9518,14 +9522,14 @@ function ArticlePreviewReadout({
9518
9522
  style={{ userSelect: "none" }}
9519
9523
  >
9520
9524
  <title>{title}</title>
9521
- {title.length > 26 ? `${title.slice(0, 25)}…` : title}
9525
+ {桁で省く(title, 26)}
9522
9526
  </text>
9523
9527
  <text x={thumbW + 20} y={44} fontSize={10} fill="#334155" style={{ userSelect: "none" }}>
9524
9528
  <title>{excerpt}</title>
9525
- {excerpt.length > 28 ? `${excerpt.slice(0, 27)}…` : excerpt}
9529
+ {抜粋の一行目}
9526
9530
  </text>
9527
9531
  <text x={thumbW + 20} y={60} fontSize={10} fill="#334155" style={{ userSelect: "none" }}>
9528
- {excerpt.length > 56 ? `${excerpt.slice(28, 55)}…` : excerpt.slice(28, 56)}
9532
+ {桁で省く(抜粋の残り, 28)}
9529
9533
  </text>
9530
9534
  <text x={thumbW + 20} y={height - 14} fontSize={10} fontWeight={600} fill="#64748b" style={{ userSelect: "none" }}>
9531
9535
  <字 k="readout.article.by" v={{ author }} />
@@ -9588,7 +9592,7 @@ function TocNavReadout({
9588
9592
  {shown.map((it, i) => {
9589
9593
  const y = 4 + i * rowH;
9590
9594
  const indent = 12 + it.level * 16;
9591
- const displayT = it.title.length > 30 ? `${it.title.slice(0, 29)}…` : it.title;
9595
+ const displayT = 桁で省く(it.title, 30);
9592
9596
  return (
9593
9597
  <g key={i} data-cdl-toc-item={i}>
9594
9598
  {it.active ? (