@cardenelabs/dragon 0.35.1 → 0.36.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/dist/export.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { T as Theme, N as NotationLang, V as ViewMode } from './schema-BBWfniwH.cjs';
1
+ import { T as Theme, N as NotationLang, V as ViewMode } from './schema-9VLameDG.cjs';
2
2
 
3
3
  type ExportFormat = "mp4" | "gif" | "html" | "png";
4
4
  interface ExportOptions {
package/dist/export.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { T as Theme, N as NotationLang, V as ViewMode } from './schema-BBWfniwH.js';
1
+ import { T as Theme, N as NotationLang, V as ViewMode } from './schema-9VLameDG.js';
2
2
 
3
3
  type ExportFormat = "mp4" | "gif" | "html" | "png";
4
4
  interface ExportOptions {
package/dist/export.js CHANGED
@@ -284,8 +284,16 @@ var LOOK = {
284
284
  stroke: { hair: 1, edge: 1.7, edgeOn: 2.25, arrow: 1.5, flow: 1.3, halo: 7, on: 1.75, glyph: 2, lifeline: 1.2, envelope: 1.5, badgeRing: 1.5 },
285
285
  shadow: { near: 0.8, far: 4, nearY: 1, farY: 3 },
286
286
  mini: { w: 168, pad: 8, title: 14, inset: 12, radius: 10 },
287
- /** 寄って追う見せ方の窓の高さの、幅に対する割合と下限 */
288
- follow: { aspect: 0.75, minH: 320, ease: 0.6 },
287
+ /**
288
+ * 寄って追う見せ方の窓の高さの、幅に対する割合と下限。
289
+ * `fitMinH` は見える高さに収める時 (#3047) の窓の高さの下限で、これより低い枠では収めずに枠の中で送る。
290
+ * `narrow` は縦長の全体図を出す枠の幅の下限で、これより狭い枠 (携帯の幅) では全体図を出さずに窓へ幅を渡す (#3055)。
291
+ * `badgeRoom` は番号の丸が窓から外れる段で窓を寄せ直す時に、丸の周りに空ける幅 (#3046)。
292
+ * `headRoom` は時系列図の図の頭の箱の下端から、窓に線を見せ始める所までの幅 (#3118)。
293
+ * `head` は時系列図の窓の上端に貼る頭の列の頭の箱で、アイコンを外した名前と下の字の 2 段 (#3120)。
294
+ * `pad` は頭の列の上下の余白、`h` は箱の高さ、`nameY` と `subY` は箱の上端から名前と下の字の基準線まで
295
+ */
296
+ follow: { aspect: 0.75, minH: 320, fitMinH: 160, ease: 0.6, narrow: 440, badgeRoom: 8, headRoom: 10, head: { pad: 8, h: 40, nameY: 17, subY: 31 } },
289
297
  /** 色を混ぜる割合 (%) */
290
298
  mix: { icon: 14, regionIcon: 16, regionIconEdge: 34, cap: 12, capEdge: 42, badgePast: 14, badgePastEdge: 70, mini: 62, miniBand: 16, miniBandEdge: 36, view: 6 },
291
299
  /** 字の並びの基準線を、置きたい高さの真ん中からどれだけ下げるか */
@@ -3048,20 +3056,31 @@ function band(region) {
3048
3056
  const mark = region.icon && ICONS[region.icon] ? `<rect class="dg-bi" x="${r1(x)}" y="${r1(y)}" width="${icon}" height="${icon}" rx="${iconRadius}"/>${glyph(region.icon, x + icon / 2, y + icon / 2, iconScale)}` : "";
3049
3057
  return `<g style="${cs(region.color)}"><rect class="dg-band" x="${r1(region.x + 0.5)}" y="${r1(region.y + 0.5)}" width="${r1(region.w - 1)}" height="${r1(region.h - 1)}" rx="${radius}"/>${mark}${lines}${note}</g>`;
3050
3058
  }
3051
- function card(box, ctx, on, changes) {
3052
- const { m, sh } = ctx;
3053
- const { radius, icon, iconRadius, iconScale, iconY, nameY, subY } = LOOK.box;
3059
+ function subLines(box, m, changes, y) {
3054
3060
  const seq = [[0, box.sub ?? "", "dg-ss"], ...changes];
3055
- const subs = seq.map(([t, text2, cls], k) => {
3061
+ return seq.map(([t, text2, cls], k) => {
3056
3062
  const next = seq[k + 1]?.[0];
3057
3063
  const anim = k === 0 ? next === void 0 ? "" : m.until(next - MOTION.untilFade) : next === void 0 ? m.rise(t) : m.rise(t, next - MOTION.untilFade);
3058
3064
  if (text2 === "") return "";
3059
- return `<g${anim ? ` class="dg-a" style="${anim}"` : ""}><text class="${cls}" x="${r1(box.cx)}" y="${r1(box.cy + subY)}" text-anchor="middle">${esc(text2)}</text></g>`;
3065
+ return `<g${anim ? ` class="dg-a" style="${anim}"` : ""}><text class="${cls}" x="${r1(box.cx)}" y="${r1(y)}" text-anchor="middle">${esc(text2)}</text></g>`;
3060
3066
  }).join("");
3067
+ }
3068
+ function cardFrame(box, ctx, act, y, h) {
3069
+ const rect = (cls, extra = "") => `<rect class="${cls}" x="${r1(box.x)}" y="${r1(y)}" width="${r1(box.w)}" height="${h}" rx="${LOOK.box.radius}"${extra}/>`;
3070
+ return `${act ? `<g class="dg-a" style="${act}">${rect("dg-halo")}</g>` : ""}${rect("dg-st", ` ${ctx.sh}`)}${act ? `<g class="dg-a" style="${act}">${rect("dg-on")}</g>` : ""}`;
3071
+ }
3072
+ function card(box, ctx, on, changes) {
3073
+ const { icon, iconRadius, iconScale, iconY, nameY, subY } = LOOK.box;
3074
+ const subs = subLines(box, ctx.m, changes, box.cy + subY);
3061
3075
  const act = on.length > 0 ? activeKf(ctx, on) : void 0;
3062
- const rect = (cls, extra = "") => `<rect class="${cls}" x="${r1(box.x)}" y="${r1(box.y)}" width="${r1(box.w)}" height="${box.h}" rx="${radius}"${extra}/>`;
3063
3076
  const mark = box.icon && ICONS[box.icon] ? `<rect class="dg-ic" x="${r1(box.cx - icon / 2)}" y="${r1(box.cy + iconY - icon / 2)}" width="${icon}" height="${icon}" rx="${iconRadius}"/>${glyph(box.icon, box.cx, box.cy + iconY, iconScale)}` : "";
3064
- return `<g style="${cs(box.color)}">${act ? `<g class="dg-a" style="${act}">${rect("dg-halo")}</g>` : ""}${rect("dg-st", ` ${sh}`)}${act ? `<g class="dg-a" style="${act}">${rect("dg-on")}</g>` : ""}${mark}<text class="dg-sn" x="${r1(box.cx)}" y="${r1(box.cy + nameY)}" text-anchor="middle">${esc(box.name)}</text>${subs}</g>`;
3077
+ return `<g style="${cs(box.color)}">${cardFrame(box, ctx, act, box.y, box.h)}${mark}<text class="dg-sn" x="${r1(box.cx)}" y="${r1(box.cy + nameY)}" text-anchor="middle">${esc(box.name)}</text>${subs}</g>`;
3078
+ }
3079
+ function headCard(box, ctx, on, changes) {
3080
+ const { pad: pad2, h, nameY, subY } = LOOK.follow.head;
3081
+ const subs = subLines(box, ctx.m, changes, pad2 + subY);
3082
+ const act = on.length > 0 ? activeKf(ctx, on) : void 0;
3083
+ return `<g style="${cs(box.color)}">${cardFrame(box, ctx, act, pad2, h)}<text class="dg-sn" x="${r1(box.cx)}" y="${r1(pad2 + nameY)}" text-anchor="middle">${esc(box.name)}</text>${subs}</g>`;
3065
3084
  }
3066
3085
  function lifeline(x, y1, y2) {
3067
3086
  return `<line class="dg-ll" x1="${r1(x)}" y1="${r1(y1)}" x2="${r1(x)}" y2="${r1(y2)}"/>`;
@@ -3354,6 +3373,7 @@ var PART_CSS = [
3354
3373
  `.dg-tg text{${font(TYPE.tag)};fill:var(--on-c)}`,
3355
3374
  ".dg-vp-bg{fill:var(--bg)}",
3356
3375
  ".dg-vp-frame{fill:none;stroke:var(--hair)}",
3376
+ ".dg-hd-bg{fill:var(--bg)}",
3357
3377
  ".dg-mm-bg{fill:var(--surf);stroke:var(--hair)}",
3358
3378
  `.dg-mm-t{${font(TYPE.miniTitle)};fill:var(--mute)}`,
3359
3379
  ".dg-mm-world{fill:var(--sunk);stroke:var(--hair)}",
@@ -3467,13 +3487,34 @@ function drawSvg(diagram, layout, timeline, palette, theme, follow) {
3467
3487
  const content = Object.values(L).map((parts) => parts.join("")).join("");
3468
3488
  const { near, far, nearY, farY } = LOOK.shadow;
3469
3489
  const shadow = `<filter id="dg-sh-${id}" x="-20%" y="-30%" width="140%" height="180%"><feDropShadow dx="0" dy="${nearY}" stdDeviation="${near}" style="flood-color:var(--sh);flood-opacity:var(--shA)"/><feDropShadow dx="0" dy="${farY}" stdDeviation="${far}" style="flood-color:var(--sh);flood-opacity:var(--shB)"/></filter>`;
3470
- const view = follow ? followView(layout, timeline, diagram, m, id, content, follow) : { body: `<rect class="dg-bg" width="${layout.width}" height="${layout.height}" rx="${LOOK.region.radius}"/>${content}`, w: layout.width, h: layout.height };
3490
+ const heads = follow && layout.kind === "sequence" ? layout.boxes.map((box) => headCard(box, ctx, on.get(box.id) ?? [], subs.get(box.id) ?? [])).join("") : "";
3491
+ const view = follow ? followView(layout, timeline, diagram, m, id, content, follow, heads) : { body: `<rect class="dg-bg" width="${layout.width}" height="${layout.height}" rx="${LOOK.region.radius}"/>${content}`, w: layout.width, h: layout.height };
3471
3492
  const css = `.dg-${id}{${FONT_VARS};--T:${timeline.total}s;${paletteVars(palette, theme)}}${PART_CSS}${m.frames.join("")}`;
3472
3493
  return `<svg xmlns="http://www.w3.org/2000/svg" class="dg dg-${id}" viewBox="0 0 ${Math.ceil(view.w)} ${Math.ceil(view.h)}" width="${Math.ceil(view.w)}" height="${Math.ceil(view.h)}" role="img" aria-label="${esc(diagram.title)}" data-dragon="figure" data-view="${follow ? "follow" : "fixed"}"><style>${css}</style><defs>${shadow}</defs>${view.body}</svg>`;
3473
3494
  }
3495
+ function union(rects) {
3496
+ const x = Math.min(...rects.map((r) => r.x));
3497
+ const y = Math.min(...rects.map((r) => r.y));
3498
+ return { x, y, w: Math.max(...rects.map((r) => r.x + r.w)) - x, h: Math.max(...rects.map((r) => r.y + r.h)) - y };
3499
+ }
3474
3500
  function focusOf(layout, diagram, step) {
3501
+ const rects = focusParts(layout, diagram, step);
3502
+ return rects.length === 0 ? void 0 : union(rects);
3503
+ }
3504
+ function badgeFocus(badge2, parts, vw, vh) {
3505
+ const room = LOOK.badge.r + LOOK.follow.badgeRoom;
3506
+ const { x: bx, y: by } = badge2;
3507
+ const away = (p) => Math.hypot(Math.max(0, p.x - bx, bx - p.x - p.w), Math.max(0, p.y - by, by - p.y - p.h));
3508
+ let held = { x: bx - room, y: by - room, w: room * 2, h: room * 2 };
3509
+ for (const p of [...parts].sort((a, b) => away(a) - away(b))) {
3510
+ const next = union([held, p]);
3511
+ if (next.w <= vw && next.h <= vh) held = next;
3512
+ }
3513
+ return held;
3514
+ }
3515
+ function focusParts(layout, diagram, step) {
3475
3516
  const rects = [];
3476
- for (const e of layout.edges.filter((x2) => x2.step === step)) {
3517
+ for (const e of layout.edges.filter((x) => x.step === step)) {
3477
3518
  const xs = e.points.map((p) => p[0]);
3478
3519
  const ys = e.points.map((p) => p[1]);
3479
3520
  rects.push({ x: Math.min(...xs), y: Math.min(...ys), w: Math.max(...xs) - Math.min(...xs), h: Math.max(...ys) - Math.min(...ys) });
@@ -3486,24 +3527,44 @@ function focusOf(layout, diagram, step) {
3486
3527
  if (box && (layout.kind === "architecture" || event.type === "swap")) rects.push(box);
3487
3528
  }
3488
3529
  }
3489
- if (rects.length === 0) return void 0;
3490
- const x = Math.min(...rects.map((r) => r.x));
3491
- const y = Math.min(...rects.map((r) => r.y));
3492
- return { x, y, w: Math.max(...rects.map((r) => r.x + r.w)) - x, h: Math.max(...rects.map((r) => r.y + r.h)) - y };
3530
+ return rects;
3493
3531
  }
3494
- function followView(layout, timeline, diagram, m, id, content, follow) {
3532
+ function followView(layout, timeline, diagram, m, id, content, follow, heads) {
3495
3533
  const F = LOOK.follow;
3496
3534
  const M = LOOK.mini;
3497
- const vw = Math.min(layout.width, Math.max(1, follow.width));
3498
- const vh = Math.min(layout.height, Math.max(F.minH, vw * F.aspect));
3535
+ const frameWidth = Math.max(1, follow.width);
3536
+ const full = Math.min(layout.width, frameWidth);
3537
+ const title = FIGURE_TEXT[follow.lang].overview;
3538
+ const sized = (vh2) => {
3539
+ const mw2 = Math.min(M.w, full * 0.4, vh2 * 0.4 / layout.height * layout.width);
3540
+ const k2 = mw2 / layout.width;
3541
+ const mh2 = layout.height * k2;
3542
+ const bw2 = Math.max(mw2, Math.ceil(textWidth(title, TYPE.miniTitle)));
3543
+ return { vh: vh2, mw: mw2, k: k2, mh: mh2, bw: bw2, bgW: bw2 + M.pad * 2, bgH: mh2 + M.pad * 2 + M.title, beside: mh2 > mw2 };
3544
+ };
3545
+ const natural = sized(Math.min(layout.height, Math.max(F.minH, full * F.aspect)));
3546
+ const bare = natural.beside && frameWidth < F.narrow;
3547
+ const room = follow.height === void 0 ? Number.POSITIVE_INFINITY : natural.beside ? follow.height : follow.height - natural.bgH - M.inset;
3548
+ const { vh, mw, k, mh, bw, bgW, bgH, beside } = room < natural.vh ? sized(Math.max(F.fitMinH, room)) : { ...natural, vh: Number.isFinite(room) ? Math.min(layout.height, room) : natural.vh };
3549
+ const vw = beside && !bare ? Math.min(layout.width, frameWidth - bgW - M.inset) : full;
3499
3550
  const clampX = (x) => Math.min(layout.width - vw / 2, Math.max(vw / 2, x));
3500
- const clampY = (y) => Math.min(layout.height - vh / 2, Math.max(vh / 2, y));
3551
+ const top = heads === "" ? 0 : Math.max(...layout.boxes.map((b) => b.y + b.h)) + F.headRoom;
3552
+ const strip2 = heads === "" ? 0 : F.head.pad * 2 + F.head.h;
3553
+ const below = vh - strip2;
3554
+ const clampY = (y) => Math.min(layout.height - below / 2, Math.max(top + below / 2, y));
3555
+ const r = LOOK.badge.r;
3501
3556
  let prev = [layout.width / 2, layout.height / 2];
3502
- const centers = timeline.steps.map((_, s) => {
3557
+ const middles = timeline.steps.map((_, s) => {
3503
3558
  const f = focusOf(layout, diagram, s);
3504
3559
  prev = f ? [f.x + f.w / 2, f.y + f.h / 2] : prev;
3560
+ const at2 = [clampX(prev[0]), clampY(prev[1])];
3561
+ const b = layout.badges.find((x) => x.step === s);
3562
+ if (!b || Math.abs(b.x - at2[0]) <= vw / 2 - r && Math.abs(b.y - at2[1]) <= below / 2 - r) return at2;
3563
+ const near = badgeFocus(b, focusParts(layout, diagram, s), vw, below);
3564
+ prev = [near.x + near.w / 2, near.y + near.h / 2];
3505
3565
  return [clampX(prev[0]), clampY(prev[1])];
3506
3566
  });
3567
+ const centers = middles.map(([x, y]) => [x, y - strip2 / 2]);
3507
3568
  const c0 = centers[0];
3508
3569
  const cam = [
3509
3570
  [0, c0[0], c0[1]],
@@ -3518,22 +3579,26 @@ function followView(layout, timeline, diagram, m, id, content, follow) {
3518
3579
  const cl = centers[centers.length - 1];
3519
3580
  cam.push([timeline.last, cl[0], cl[1], 1, EASE.inOut], [timeline.total, c0[0], c0[1]]);
3520
3581
  const camKf = m.move(cam.map(([t, cx, cy, s, e]) => [t, vw / 2 - cx, vh / 2 - cy, s, e]));
3521
- const mw = Math.min(M.w, vw * 0.4, vh * 0.4 / layout.height * layout.width);
3522
- const k = mw / layout.width;
3523
- const mh = layout.height * k;
3524
- const title = FIGURE_TEXT[follow.lang].overview;
3525
- const bw = Math.max(mw, Math.ceil(textWidth(title, TYPE.miniTitle)));
3526
- const bx = vw - bw - M.inset - M.pad;
3582
+ const bgX = beside ? vw + M.inset : vw - bgW - M.inset;
3583
+ const bgY = beside ? 0 : vh + M.inset;
3584
+ const bx = bgX + M.pad;
3527
3585
  const mx = bx + (bw - mw) / 2;
3528
- const my = M.inset + M.pad + M.title;
3529
- const shapes = [
3530
- ...layout.regions.map((r) => `<rect class="dg-mm-band" style="${cs(r.color)}" x="${mx + r.x * k}" y="${my + r.y * k}" width="${r.w * k}" height="${r.h * k}" rx="3"/>`),
3531
- ...layout.boxes.map((b) => `<rect class="dg-mm-card" style="${cs(b.color)}" x="${mx + b.x * k}" y="${my + b.y * k}" width="${b.w * k}" height="${b.h * k}" rx="1.5"/>`)
3532
- ].join("");
3533
- const vp = m.move(cam.map(([t, cx, cy, , e]) => [t, mx + (cx - vw / 2) * k, my + (cy - vh / 2) * k, 1, e]));
3534
- const mini = `<rect class="dg-mm-bg" x="${bx - M.pad}" y="${my - M.pad - M.title}" width="${bw + M.pad * 2}" height="${mh + M.pad * 2 + M.title}" rx="${M.radius}"/><text class="dg-mm-t" x="${bx}" y="${my - M.pad + 2}">${title}</text><g clip-path="url(#dg-mm-${id})"><rect class="dg-mm-world" x="${mx}" y="${my}" width="${mw}" height="${mh}"/>${shapes}<rect class="dg-mm-vp dg-a" width="${vw * k}" height="${vh * k}" rx="2" style="${vp}"/></g>`;
3535
- const body = `<defs><clipPath id="dg-vp-${id}"><rect width="${vw}" height="${vh}" rx="${LOOK.region.radius}"/></clipPath><clipPath id="dg-mm-${id}"><rect x="${mx}" y="${my}" width="${mw}" height="${mh}" rx="4"/></clipPath></defs><g clip-path="url(#dg-vp-${id})"><rect class="dg-vp-bg" width="${vw}" height="${vh}"/><g class="dg-a" style="${camKf}">${content}</g></g><rect class="dg-vp-frame" x="0.5" y="0.5" width="${vw - 1}" height="${vh - 1}" rx="${LOOK.region.radius}"/>${mini}`;
3536
- return { body, w: vw, h: vh };
3586
+ const my = bgY + M.pad + M.title;
3587
+ const overview = () => {
3588
+ const shapes = [
3589
+ ...layout.regions.map((r3) => `<rect class="dg-mm-band" style="${cs(r3.color)}" x="${mx + r3.x * k}" y="${my + r3.y * k}" width="${r3.w * k}" height="${r3.h * k}" rx="3"/>`),
3590
+ ...layout.boxes.map((b) => `<rect class="dg-mm-card" style="${cs(b.color)}" x="${mx + b.x * k}" y="${my + b.y * k}" width="${b.w * k}" height="${b.h * k}" rx="1.5"/>`)
3591
+ ].join("");
3592
+ const vp = m.move(cam.map(([t, cx, cy, , e]) => [t, mx + (cx - vw / 2) * k, my + (cy - vh / 2) * k, 1, e]));
3593
+ return {
3594
+ clip: `<clipPath id="dg-mm-${id}"><rect x="${mx}" y="${my}" width="${mw}" height="${mh}" rx="4"/></clipPath>`,
3595
+ body: `<rect class="dg-mm-bg" x="${bgX}" y="${bgY}" width="${bgW}" height="${bgH}" rx="${M.radius}"/><text class="dg-mm-t" x="${bx}" y="${my - M.pad + 2}">${title}</text><g clip-path="url(#dg-mm-${id})"><rect class="dg-mm-world" x="${mx}" y="${my}" width="${mw}" height="${mh}"/>${shapes}<rect class="dg-mm-vp dg-a"${strip2 === 0 ? "" : ` y="${strip2 * k}"`} width="${vw * k}" height="${(vh - strip2) * k}" rx="2" style="${vp}"/></g>`
3596
+ };
3597
+ };
3598
+ const mini = bare ? { clip: "", body: "" } : overview();
3599
+ const head = strip2 === 0 ? "" : `<g class="dg-hd"><rect class="dg-hd-bg" width="${vw}" height="${strip2}"/><line class="dg-sep" x1="0" y1="${strip2}" x2="${vw}" y2="${strip2}"/><g class="dg-a" style="${m.move(cam.map(([t, cx, , s, e]) => [t, vw / 2 - cx, 0, s, e]))}">${heads}</g></g>`;
3600
+ const body = `<defs><clipPath id="dg-vp-${id}"><rect width="${vw}" height="${vh}" rx="${LOOK.region.radius}"/></clipPath>${mini.clip}</defs><g clip-path="url(#dg-vp-${id})"><rect class="dg-vp-bg" width="${vw}" height="${vh}"/><g class="dg-a" style="${camKf}">${content}</g>${head}</g><rect class="dg-vp-frame" x="0.5" y="0.5" width="${vw - 1}" height="${vh - 1}" rx="${LOOK.region.radius}"/>${mini.body}`;
3601
+ return { body, w: beside && !bare ? vw + M.inset + bgW : full, h: beside ? vh : bgY + bgH };
3537
3602
  }
3538
3603
  function renderDiagram(input, options = {}) {
3539
3604
  const lang = options.lang ?? "ja";
@@ -3557,8 +3622,10 @@ function renderDiagram(input, options = {}) {
3557
3622
  }
3558
3623
 
3559
3624
  // src/view.ts
3560
- function chooseView(rendered, frameWidth) {
3561
- const textPx = Math.max(0, frameWidth) / rendered.layout.width * TYPE.boxName.size;
3625
+ function chooseView(rendered, frameWidth, frameHeight) {
3626
+ const byWidth = Math.max(0, frameWidth) / rendered.layout.width;
3627
+ const scale = byWidth ;
3628
+ const textPx = scale * TYPE.boxName.size;
3562
3629
  if (rendered.view) return { mode: rendered.view, textPx };
3563
3630
  return { mode: textPx >= READABLE_PX ? "fixed" : "follow", textPx };
3564
3631
  }