@cardenelabs/dragon 0.35.0 → 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.cjs CHANGED
@@ -288,8 +288,16 @@ var LOOK = {
288
288
  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 },
289
289
  shadow: { near: 0.8, far: 4, nearY: 1, farY: 3 },
290
290
  mini: { w: 168, pad: 8, title: 14, inset: 12, radius: 10 },
291
- /** 寄って追う見せ方の窓の高さの、幅に対する割合と下限 */
292
- follow: { aspect: 0.75, minH: 320, ease: 0.6 },
291
+ /**
292
+ * 寄って追う見せ方の窓の高さの、幅に対する割合と下限。
293
+ * `fitMinH` は見える高さに収める時 (#3047) の窓の高さの下限で、これより低い枠では収めずに枠の中で送る。
294
+ * `narrow` は縦長の全体図を出す枠の幅の下限で、これより狭い枠 (携帯の幅) では全体図を出さずに窓へ幅を渡す (#3055)。
295
+ * `badgeRoom` は番号の丸が窓から外れる段で窓を寄せ直す時に、丸の周りに空ける幅 (#3046)。
296
+ * `headRoom` は時系列図の図の頭の箱の下端から、窓に線を見せ始める所までの幅 (#3118)。
297
+ * `head` は時系列図の窓の上端に貼る頭の列の頭の箱で、アイコンを外した名前と下の字の 2 段 (#3120)。
298
+ * `pad` は頭の列の上下の余白、`h` は箱の高さ、`nameY` と `subY` は箱の上端から名前と下の字の基準線まで
299
+ */
300
+ 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 } },
293
301
  /** 色を混ぜる割合 (%) */
294
302
  mix: { icon: 14, regionIcon: 16, regionIconEdge: 34, cap: 12, capEdge: 42, badgePast: 14, badgePastEdge: 70, mini: 62, miniBand: 16, miniBandEdge: 36, view: 6 },
295
303
  /** 字の並びの基準線を、置きたい高さの真ん中からどれだけ下げるか */
@@ -3052,20 +3060,31 @@ function band(region) {
3052
3060
  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)}` : "";
3053
3061
  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>`;
3054
3062
  }
3055
- function card(box, ctx, on, changes) {
3056
- const { m, sh } = ctx;
3057
- const { radius, icon, iconRadius, iconScale, iconY, nameY, subY } = LOOK.box;
3063
+ function subLines(box, m, changes, y) {
3058
3064
  const seq = [[0, box.sub ?? "", "dg-ss"], ...changes];
3059
- const subs = seq.map(([t, text2, cls], k) => {
3065
+ return seq.map(([t, text2, cls], k) => {
3060
3066
  const next = seq[k + 1]?.[0];
3061
3067
  const anim = k === 0 ? next === void 0 ? "" : m.until(next - MOTION.untilFade) : next === void 0 ? m.rise(t) : m.rise(t, next - MOTION.untilFade);
3062
3068
  if (text2 === "") return "";
3063
- 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>`;
3069
+ 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>`;
3064
3070
  }).join("");
3071
+ }
3072
+ function cardFrame(box, ctx, act, y, h) {
3073
+ 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}/>`;
3074
+ 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>` : ""}`;
3075
+ }
3076
+ function card(box, ctx, on, changes) {
3077
+ const { icon, iconRadius, iconScale, iconY, nameY, subY } = LOOK.box;
3078
+ const subs = subLines(box, ctx.m, changes, box.cy + subY);
3065
3079
  const act = on.length > 0 ? activeKf(ctx, on) : void 0;
3066
- 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}/>`;
3067
3080
  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)}` : "";
3068
- 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>`;
3081
+ 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>`;
3082
+ }
3083
+ function headCard(box, ctx, on, changes) {
3084
+ const { pad: pad2, h, nameY, subY } = LOOK.follow.head;
3085
+ const subs = subLines(box, ctx.m, changes, pad2 + subY);
3086
+ const act = on.length > 0 ? activeKf(ctx, on) : void 0;
3087
+ 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>`;
3069
3088
  }
3070
3089
  function lifeline(x, y1, y2) {
3071
3090
  return `<line class="dg-ll" x1="${r1(x)}" y1="${r1(y1)}" x2="${r1(x)}" y2="${r1(y2)}"/>`;
@@ -3358,6 +3377,7 @@ var PART_CSS = [
3358
3377
  `.dg-tg text{${font(TYPE.tag)};fill:var(--on-c)}`,
3359
3378
  ".dg-vp-bg{fill:var(--bg)}",
3360
3379
  ".dg-vp-frame{fill:none;stroke:var(--hair)}",
3380
+ ".dg-hd-bg{fill:var(--bg)}",
3361
3381
  ".dg-mm-bg{fill:var(--surf);stroke:var(--hair)}",
3362
3382
  `.dg-mm-t{${font(TYPE.miniTitle)};fill:var(--mute)}`,
3363
3383
  ".dg-mm-world{fill:var(--sunk);stroke:var(--hair)}",
@@ -3471,13 +3491,34 @@ function drawSvg(diagram, layout, timeline, palette, theme, follow) {
3471
3491
  const content = Object.values(L).map((parts) => parts.join("")).join("");
3472
3492
  const { near, far, nearY, farY } = LOOK.shadow;
3473
3493
  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>`;
3474
- 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 };
3494
+ const heads = follow && layout.kind === "sequence" ? layout.boxes.map((box) => headCard(box, ctx, on.get(box.id) ?? [], subs.get(box.id) ?? [])).join("") : "";
3495
+ 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 };
3475
3496
  const css = `.dg-${id}{${FONT_VARS};--T:${timeline.total}s;${paletteVars(palette, theme)}}${PART_CSS}${m.frames.join("")}`;
3476
3497
  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>`;
3477
3498
  }
3499
+ function union(rects) {
3500
+ const x = Math.min(...rects.map((r) => r.x));
3501
+ const y = Math.min(...rects.map((r) => r.y));
3502
+ 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 };
3503
+ }
3478
3504
  function focusOf(layout, diagram, step) {
3505
+ const rects = focusParts(layout, diagram, step);
3506
+ return rects.length === 0 ? void 0 : union(rects);
3507
+ }
3508
+ function badgeFocus(badge2, parts, vw, vh) {
3509
+ const room = LOOK.badge.r + LOOK.follow.badgeRoom;
3510
+ const { x: bx, y: by } = badge2;
3511
+ 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));
3512
+ let held = { x: bx - room, y: by - room, w: room * 2, h: room * 2 };
3513
+ for (const p of [...parts].sort((a, b) => away(a) - away(b))) {
3514
+ const next = union([held, p]);
3515
+ if (next.w <= vw && next.h <= vh) held = next;
3516
+ }
3517
+ return held;
3518
+ }
3519
+ function focusParts(layout, diagram, step) {
3479
3520
  const rects = [];
3480
- for (const e of layout.edges.filter((x2) => x2.step === step)) {
3521
+ for (const e of layout.edges.filter((x) => x.step === step)) {
3481
3522
  const xs = e.points.map((p) => p[0]);
3482
3523
  const ys = e.points.map((p) => p[1]);
3483
3524
  rects.push({ x: Math.min(...xs), y: Math.min(...ys), w: Math.max(...xs) - Math.min(...xs), h: Math.max(...ys) - Math.min(...ys) });
@@ -3490,24 +3531,44 @@ function focusOf(layout, diagram, step) {
3490
3531
  if (box && (layout.kind === "architecture" || event.type === "swap")) rects.push(box);
3491
3532
  }
3492
3533
  }
3493
- if (rects.length === 0) return void 0;
3494
- const x = Math.min(...rects.map((r) => r.x));
3495
- const y = Math.min(...rects.map((r) => r.y));
3496
- 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 };
3534
+ return rects;
3497
3535
  }
3498
- function followView(layout, timeline, diagram, m, id, content, follow) {
3536
+ function followView(layout, timeline, diagram, m, id, content, follow, heads) {
3499
3537
  const F = LOOK.follow;
3500
3538
  const M = LOOK.mini;
3501
- const vw = Math.min(layout.width, Math.max(1, follow.width));
3502
- const vh = Math.min(layout.height, Math.max(F.minH, vw * F.aspect));
3539
+ const frameWidth = Math.max(1, follow.width);
3540
+ const full = Math.min(layout.width, frameWidth);
3541
+ const title = FIGURE_TEXT[follow.lang].overview;
3542
+ const sized = (vh2) => {
3543
+ const mw2 = Math.min(M.w, full * 0.4, vh2 * 0.4 / layout.height * layout.width);
3544
+ const k2 = mw2 / layout.width;
3545
+ const mh2 = layout.height * k2;
3546
+ const bw2 = Math.max(mw2, Math.ceil(textWidth(title, TYPE.miniTitle)));
3547
+ 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 };
3548
+ };
3549
+ const natural = sized(Math.min(layout.height, Math.max(F.minH, full * F.aspect)));
3550
+ const bare = natural.beside && frameWidth < F.narrow;
3551
+ const room = follow.height === void 0 ? Number.POSITIVE_INFINITY : natural.beside ? follow.height : follow.height - natural.bgH - M.inset;
3552
+ 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 };
3553
+ const vw = beside && !bare ? Math.min(layout.width, frameWidth - bgW - M.inset) : full;
3503
3554
  const clampX = (x) => Math.min(layout.width - vw / 2, Math.max(vw / 2, x));
3504
- const clampY = (y) => Math.min(layout.height - vh / 2, Math.max(vh / 2, y));
3555
+ const top = heads === "" ? 0 : Math.max(...layout.boxes.map((b) => b.y + b.h)) + F.headRoom;
3556
+ const strip2 = heads === "" ? 0 : F.head.pad * 2 + F.head.h;
3557
+ const below = vh - strip2;
3558
+ const clampY = (y) => Math.min(layout.height - below / 2, Math.max(top + below / 2, y));
3559
+ const r = LOOK.badge.r;
3505
3560
  let prev = [layout.width / 2, layout.height / 2];
3506
- const centers = timeline.steps.map((_, s) => {
3561
+ const middles = timeline.steps.map((_, s) => {
3507
3562
  const f = focusOf(layout, diagram, s);
3508
3563
  prev = f ? [f.x + f.w / 2, f.y + f.h / 2] : prev;
3564
+ const at2 = [clampX(prev[0]), clampY(prev[1])];
3565
+ const b = layout.badges.find((x) => x.step === s);
3566
+ if (!b || Math.abs(b.x - at2[0]) <= vw / 2 - r && Math.abs(b.y - at2[1]) <= below / 2 - r) return at2;
3567
+ const near = badgeFocus(b, focusParts(layout, diagram, s), vw, below);
3568
+ prev = [near.x + near.w / 2, near.y + near.h / 2];
3509
3569
  return [clampX(prev[0]), clampY(prev[1])];
3510
3570
  });
3571
+ const centers = middles.map(([x, y]) => [x, y - strip2 / 2]);
3511
3572
  const c0 = centers[0];
3512
3573
  const cam = [
3513
3574
  [0, c0[0], c0[1]],
@@ -3522,22 +3583,26 @@ function followView(layout, timeline, diagram, m, id, content, follow) {
3522
3583
  const cl = centers[centers.length - 1];
3523
3584
  cam.push([timeline.last, cl[0], cl[1], 1, EASE.inOut], [timeline.total, c0[0], c0[1]]);
3524
3585
  const camKf = m.move(cam.map(([t, cx, cy, s, e]) => [t, vw / 2 - cx, vh / 2 - cy, s, e]));
3525
- const mw = Math.min(M.w, vw * 0.4, vh * 0.4 / layout.height * layout.width);
3526
- const k = mw / layout.width;
3527
- const mh = layout.height * k;
3528
- const title = FIGURE_TEXT[follow.lang].overview;
3529
- const bw = Math.max(mw, Math.ceil(textWidth(title, TYPE.miniTitle)));
3530
- const bx = vw - bw - M.inset - M.pad;
3586
+ const bgX = beside ? vw + M.inset : vw - bgW - M.inset;
3587
+ const bgY = beside ? 0 : vh + M.inset;
3588
+ const bx = bgX + M.pad;
3531
3589
  const mx = bx + (bw - mw) / 2;
3532
- const my = M.inset + M.pad + M.title;
3533
- const shapes = [
3534
- ...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"/>`),
3535
- ...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"/>`)
3536
- ].join("");
3537
- const vp = m.move(cam.map(([t, cx, cy, , e]) => [t, mx + (cx - vw / 2) * k, my + (cy - vh / 2) * k, 1, e]));
3538
- 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>`;
3539
- 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}`;
3540
- return { body, w: vw, h: vh };
3590
+ const my = bgY + M.pad + M.title;
3591
+ const overview = () => {
3592
+ const shapes = [
3593
+ ...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"/>`),
3594
+ ...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"/>`)
3595
+ ].join("");
3596
+ const vp = m.move(cam.map(([t, cx, cy, , e]) => [t, mx + (cx - vw / 2) * k, my + (cy - vh / 2) * k, 1, e]));
3597
+ return {
3598
+ clip: `<clipPath id="dg-mm-${id}"><rect x="${mx}" y="${my}" width="${mw}" height="${mh}" rx="4"/></clipPath>`,
3599
+ 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>`
3600
+ };
3601
+ };
3602
+ const mini = bare ? { clip: "", body: "" } : overview();
3603
+ 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>`;
3604
+ 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}`;
3605
+ return { body, w: beside && !bare ? vw + M.inset + bgW : full, h: beside ? vh : bgY + bgH };
3541
3606
  }
3542
3607
  function renderDiagram(input, options = {}) {
3543
3608
  const lang = options.lang ?? "ja";
@@ -3561,8 +3626,10 @@ function renderDiagram(input, options = {}) {
3561
3626
  }
3562
3627
 
3563
3628
  // src/view.ts
3564
- function chooseView(rendered, frameWidth) {
3565
- const textPx = Math.max(0, frameWidth) / rendered.layout.width * TYPE.boxName.size;
3629
+ function chooseView(rendered, frameWidth, frameHeight) {
3630
+ const byWidth = Math.max(0, frameWidth) / rendered.layout.width;
3631
+ const scale = byWidth ;
3632
+ const textPx = scale * TYPE.boxName.size;
3566
3633
  if (rendered.view) return { mode: rendered.view, textPx };
3567
3634
  return { mode: textPx >= READABLE_PX ? "fixed" : "follow", textPx };
3568
3635
  }