@helping-ai-workflow/md2doc 3.5.0 → 3.6.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/lib/editor/cli.js +2 -2
- package/lib/editor/client.js +82 -5
- package/lib/editor/server.js +225 -49
- package/lib/editor/wave-codec.js +160 -3
- package/lib/editor/wave-draw.js +1470 -0
- package/lib/editor/wave-geometry.js +469 -19
- package/lib/editor/wave-panels.js +1102 -0
- package/lib/editor/wave-store.js +36 -1
- package/lib/editor/wave-ui.js +1807 -1802
- package/lib/md2doc.js +520 -50
- package/package.json +2 -2
package/lib/md2doc.js
CHANGED
|
@@ -1639,8 +1639,21 @@ ${itemsHtml}
|
|
|
1639
1639
|
position: fixed; inset: 0; z-index: 998; background: rgba(0,0,0,.45);
|
|
1640
1640
|
display: flex; align-items: center; justify-content: center;
|
|
1641
1641
|
}
|
|
1642
|
+
/* v3.6.0 Task 13: width cap raised from 1200px to 1920px — MEASURED
|
|
1643
|
+
(createEditorServer, four real viewports): at 1200px the cap itself
|
|
1644
|
+
was the binding constraint at 1280/1600/1920 alike (94vw there is
|
|
1645
|
+
1203/1504/1805px, all above the old 1200px cap), so every screen from a
|
|
1646
|
+
1280 laptop up to a 1920 monitor got the IDENTICAL canvas width — the
|
|
1647
|
+
exact complaint that opened this version ("a bigger monitor buys the
|
|
1648
|
+
user nothing"). 1920px is chosen so 94vw is the actual governor at
|
|
1649
|
+
every one of those four measured sizes (94% of 1920 is 1804.8, still
|
|
1650
|
+
under the new cap) — this number is a ceiling for a screen wider than
|
|
1651
|
+
that, not a value that binds within the measured range. At 800x600,
|
|
1652
|
+
94vw (752px) was already narrower than either cap, so that viewport is
|
|
1653
|
+
unchanged by this. See task-13-report.md for the re-measured
|
|
1654
|
+
toolbar/canvas table. */
|
|
1642
1655
|
.ed-wave-panel {
|
|
1643
|
-
background: #fff; border-radius: 6px; width: min(
|
|
1656
|
+
background: #fff; border-radius: 6px; width: min(1920px, 94vw);
|
|
1644
1657
|
height: min(760px, 90vh); display: flex; flex-direction: column;
|
|
1645
1658
|
box-shadow: 0 6px 28px rgba(0,0,0,.35); overflow: hidden;
|
|
1646
1659
|
}
|
|
@@ -1664,15 +1677,49 @@ ${itemsHtml}
|
|
|
1664
1677
|
display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
|
|
1665
1678
|
padding: 8px 12px; border-bottom: 1px solid #e2e2e2; background: #fafafa;
|
|
1666
1679
|
}
|
|
1667
|
-
.
|
|
1680
|
+
/* v3.6.0 Task 12: the eight captioned sections themselves. .ed-wave-tools
|
|
1681
|
+
is the row that holds them (the SAME element as .ed-wave-toolbar
|
|
1682
|
+
above -- that class still owns the bar's padding/border/background,
|
|
1683
|
+
this one owns only the group row's own gap). Each .ed-wave-tool-group
|
|
1684
|
+
is a bordered chip so eight adjacent sections read as eight things, not
|
|
1685
|
+
one longer wall of buttons; the caption sits to its buttons' LEFT (see
|
|
1686
|
+
wave-panels.js's toolGroup() for why not above). */
|
|
1687
|
+
.ed-wave-tools { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
|
|
1688
|
+
.ed-wave-tool-group {
|
|
1689
|
+
display: flex; align-items: center; gap: 4px;
|
|
1690
|
+
padding: 2px 6px; border: 1px solid #ececec; border-radius: 6px;
|
|
1691
|
+
}
|
|
1692
|
+
.ed-wave-tool-caption {
|
|
1693
|
+
font-size: 10px; color: #8a8a8a; letter-spacing: .5px;
|
|
1694
|
+
white-space: nowrap; margin-right: 2px;
|
|
1695
|
+
}
|
|
1696
|
+
.ed-wave-brushes, .ed-wave-meta {
|
|
1668
1697
|
display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
|
|
1669
1698
|
}
|
|
1670
|
-
.
|
|
1671
|
-
|
|
1672
|
-
|
|
1699
|
+
/* v3.6.0 Task 10: widened from a 28x28 bare-character square to a small
|
|
1700
|
+
column (glyph on top, the character kept as a caption underneath) so
|
|
1701
|
+
the 22-brush palette says what each brush paints instead of relying on
|
|
1702
|
+
the letter alone -- nine of those 22 (P, N, u, d, h, l, 2, 6-9) have no
|
|
1703
|
+
obvious on-canvas picture from the character by itself. */
|
|
1704
|
+
/* '.ed-wave-brushes .ed-wave-brush', not the bare class: '.ed-wave-toolbar
|
|
1705
|
+
button' below outranks a single class on specificity (class+type beats
|
|
1706
|
+
class alone) and would otherwise win the padding fight and squeeze the
|
|
1707
|
+
24px-wide icon into a content box narrower than it is. */
|
|
1708
|
+
.ed-wave-brushes .ed-wave-brush {
|
|
1709
|
+
width: 34px; height: 40px; border: 1px solid #c4c4c4; border-radius: 4px;
|
|
1710
|
+
background: #fff; font-family: monospace; cursor: pointer;
|
|
1711
|
+
display: flex; flex-direction: column; align-items: center;
|
|
1712
|
+
justify-content: center; gap: 1px; padding: 2px 0; box-sizing: border-box;
|
|
1673
1713
|
}
|
|
1674
1714
|
.ed-wave-brush.is-on { background: #1a73e8; color: #fff; border-color: #1a73e8; }
|
|
1675
|
-
.ed-wave-
|
|
1715
|
+
.ed-wave-brush-char { font-size: 11px; line-height: 1; }
|
|
1716
|
+
/* The glyph itself. Its shapes are drawn by wave-draw.js's levelIcon,
|
|
1717
|
+
which reuses renderCanvas's own drawing functions (bandOf, brickPath,
|
|
1718
|
+
the bus/clock/gap helpers) -- never a second hand-drawn set, so a
|
|
1719
|
+
change to how a brush paints on the real canvas cannot leave the
|
|
1720
|
+
toolbar showing the old picture. */
|
|
1721
|
+
.ed-wave-level-icon { display: block; overflow: visible; }
|
|
1722
|
+
.ed-wave-toolbar button {
|
|
1676
1723
|
border: 1px solid #c4c4c4; border-radius: 4px; background: #fff;
|
|
1677
1724
|
padding: 3px 8px; font-size: 12px; cursor: pointer;
|
|
1678
1725
|
}
|
|
@@ -1681,20 +1728,18 @@ ${itemsHtml}
|
|
|
1681
1728
|
treatment as .ed-wave-brush.is-on above — a picked tool reads the same
|
|
1682
1729
|
way whichever toolbar it lives in. */
|
|
1683
1730
|
.ed-wave-edge-arm.is-on { background: #1a73e8; color: #fff; border-color: #1a73e8; }
|
|
1731
|
+
/* v3.6.0 Task 13: the fixed 110px input width that used to live here moved
|
|
1732
|
+
into .ed-wave-section-body input below — every .ed-wave-metafield
|
|
1733
|
+
now lives inside a right-hand panel (關聯線/文件/Lane), not the toolbar,
|
|
1734
|
+
and a panel field should fill the ~340px column rather than sit at a
|
|
1735
|
+
width chosen for the old horizontal toolbar row. */
|
|
1684
1736
|
.ed-wave-metafield { display: flex; align-items: center; gap: 4px; font-size: 12px; }
|
|
1685
|
-
.
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
copied from .ed-wave-brushes, .ed-wave-cycleops, .ed-wave-meta above (the
|
|
1692
|
-
shared flex-row rule its own children need), and flex:1 1 100% is copied
|
|
1693
|
-
from .ed-wave-unmodelled below (what makes THAT sibling claim its own row
|
|
1694
|
-
instead of sitting beside the control before it) — this row needs both at
|
|
1695
|
-
once: it lays out its own children AND has to stop sharing a line with
|
|
1696
|
-
whatever toolbar control precedes it. */
|
|
1697
|
-
.ed-wave-edgebar { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; flex: 1 1 100%; }
|
|
1737
|
+
/* v3.6.0 Task 13: the 關聯線 panel's own content, stacked vertically now
|
|
1738
|
+
that it lives in a narrow side column rather than a wide flex-wrap
|
|
1739
|
+
toolbar row (the old 'flex: 1 1 100%' here was what made it claim a
|
|
1740
|
+
full row of THAT row; a panel body has no siblings to share a row
|
|
1741
|
+
with). */
|
|
1742
|
+
.ed-wave-edgebar { display: flex; flex-direction: column; gap: 4px; }
|
|
1698
1743
|
/* The "this drawing is not showing you everything" notice. period / phase /
|
|
1699
1744
|
config.hscale all change what the ENGINE paints and none of them is
|
|
1700
1745
|
modelled by the hand drawing, so a document carrying one says so instead
|
|
@@ -1709,14 +1754,33 @@ ${itemsHtml}
|
|
|
1709
1754
|
Same shape as its neighbour so the two read as one family, one step
|
|
1710
1755
|
louder because the information gap is bigger. */
|
|
1711
1756
|
.ed-wave-nolanes { color: #7a1f00; background: #ffe9e0; border-color: #e8a987; }
|
|
1712
|
-
.
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1757
|
+
/* v3.6.0 Task 13 fix round 1 (Critical 2): position: relative added.
|
|
1758
|
+
.ed-wave-side[data-expanded] below is position: absolute, and neither
|
|
1759
|
+
.ed-wave-panel nor (until this line) this element had a position of
|
|
1760
|
+
their own — so its containing block was .ed-wave-overlay
|
|
1761
|
+
(position: fixed; inset: 0), the WHOLE viewport, not the dialog.
|
|
1762
|
+
.ed-wave-panel's own overflow: hidden did not clip it either: clipping
|
|
1763
|
+
follows the containing-block chain, not DOM nesting, so the rail
|
|
1764
|
+
rendered against viewport corners at a narrow window and covered
|
|
1765
|
+
.ed-wave-head's 關閉 button — the only discoverable way to close the
|
|
1766
|
+
dialog (a hit-test at 關閉's own rectangle returned the edge panel's
|
|
1767
|
+
delete button instead; Escape still worked but was not discoverable).
|
|
1768
|
+
Anchoring the rail to THIS element rather than to .ed-wave-panel itself
|
|
1769
|
+
is deliberate, not incidental: .ed-wave-body sits below .ed-wave-head
|
|
1770
|
+
and above .ed-wave-status, so top: 0 / bottom: 0 on the rail now lands
|
|
1771
|
+
at body's own top/bottom — never over the header (關閉) or the status
|
|
1772
|
+
line — without this rule having to know either one's height. */
|
|
1773
|
+
.ed-wave-body { display: flex; flex: 1 1 auto; min-height: 0; position: relative; }
|
|
1774
|
+
/* v3.6.0 Task 13: the old comment here hand-computed the canvas's leftover
|
|
1775
|
+
width from the (now gone) .ed-wave-preview column's own flex-basis;
|
|
1776
|
+
that arithmetic no longer applies — the third column is .ed-wave-side
|
|
1777
|
+
below, which collapses to a 28px rail under 1100px specifically so the
|
|
1778
|
+
canvas is not squeezed the way a fixed 160px minimum used to squeeze it
|
|
1779
|
+
at 800x600 (MEASURED before this task: canvas width there was only
|
|
1780
|
+
282px). Re-measured after this task at four real viewports; see
|
|
1781
|
+
task-13-report.md rather than a formula here — a number derived by hand
|
|
1782
|
+
drifts the moment any sibling column's own width changes, which is
|
|
1783
|
+
exactly what this task did. */
|
|
1720
1784
|
.ed-wave-lanes {
|
|
1721
1785
|
flex: 0 0 200px; border-right: 1px solid #e2e2e2; overflow: auto;
|
|
1722
1786
|
padding: 8px 0 8px 0;
|
|
@@ -1727,27 +1791,37 @@ ${itemsHtml}
|
|
|
1727
1791
|
the rail spanning its lanes' rows instead, which is the shape the engine
|
|
1728
1792
|
itself gives it (horizontal space in the name column, no vertical space). */
|
|
1729
1793
|
.ed-wave-lane-rows { position: relative; }
|
|
1794
|
+
/* v3.6.0 Task 14: a row is a drag handle, a name field and (on a {}
|
|
1795
|
+
spacer) a badge. It used to be six buttons plus the field, and the six
|
|
1796
|
+
did not fit: flexed into what was left of 200px the field measured
|
|
1797
|
+
45px on the most crowded row, and a browser given a 24px box and a
|
|
1798
|
+
three-character Chinese caption lays that caption out VERTICALLY — the
|
|
1799
|
+
editor shipped columns of stacked glyphs where labels were meant.
|
|
1800
|
+
Those six are toolbar buttons now (the 訊號 / 群組 sections), acting
|
|
1801
|
+
on the one selection. */
|
|
1730
1802
|
.ed-wave-lane-row {
|
|
1731
1803
|
position: absolute; left: 0; right: 0; display: flex; align-items: center;
|
|
1732
|
-
gap:
|
|
1804
|
+
gap: 6px; padding-right: 6px; box-sizing: border-box;
|
|
1733
1805
|
}
|
|
1734
1806
|
.ed-wave-lane-row.is-selected { background: #eaf2fe; }
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
.ed-wave-lane-
|
|
1750
|
-
|
|
1807
|
+
.ed-wave-lane-handle {
|
|
1808
|
+
flex: 0 0 auto; cursor: grab; color: #aaa; user-select: none; font-size: 13px;
|
|
1809
|
+
}
|
|
1810
|
+
.ed-wave-lane-handle:active { cursor: grabbing; }
|
|
1811
|
+
/* min-width, not just flex-grow: min-width is what a flex item will not
|
|
1812
|
+
shrink past, and shrinking past it is the vertical-text failure above.
|
|
1813
|
+
MEASURED after this change (real page, 800x600, the journey suite's own
|
|
1814
|
+
wave fixture): rail 201px, handle 9.5px, badge 36px, and the field comes
|
|
1815
|
+
out 178.5px ungrouped, 162.5px one group deep, 136.5px on the {} spacer
|
|
1816
|
+
row that also carries the badge — writing-mode horizontal-tb on all six.
|
|
1817
|
+
So 120 is slack at every depth this fixture reaches, NOT a computed
|
|
1818
|
+
exact fit. Deeper nesting spends 16px a level and would eventually hit
|
|
1819
|
+
the floor; there the field stops shrinking and .ed-wave-lanes scrolls,
|
|
1820
|
+
which is the trade this rule chooses on purpose. */
|
|
1821
|
+
.ed-wave-lane-name {
|
|
1822
|
+
flex: 1 1 auto; min-width: 120px; font-size: 12px; padding: 2px 4px;
|
|
1823
|
+
}
|
|
1824
|
+
.ed-wave-lane-badge { flex: 0 0 auto; font-size: 10px; color: #888; }
|
|
1751
1825
|
.ed-wave-group, .ed-wave-group-input {
|
|
1752
1826
|
position: absolute; width: 14px; padding: 0; font-size: 11px;
|
|
1753
1827
|
writing-mode: vertical-rl; text-orientation: mixed; overflow: hidden;
|
|
@@ -1775,6 +1849,14 @@ ${itemsHtml}
|
|
|
1775
1849
|
}
|
|
1776
1850
|
.ed-wave-grid { stroke: #ececec; stroke-width: 1; }
|
|
1777
1851
|
.ed-wave-line, .ed-wave-edge { stroke: #111; stroke-width: 2; fill: none; }
|
|
1852
|
+
/* v3.6.0 Task 10: the toolbar glyph's plain-level backbone (0/1/h/l/z/u/d
|
|
1853
|
+
only -- bus/clock/x/gap glyphs draw their own shape with their own
|
|
1854
|
+
fixed canvas colour instead, further down). Declared after the rule
|
|
1855
|
+
above on purpose: both are single-class selectors, so source order is
|
|
1856
|
+
what lets currentColor win over the fixed #111 for this one case,
|
|
1857
|
+
turning the icon white together with the rest of the button's text
|
|
1858
|
+
under .ed-wave-brush.is-on. */
|
|
1859
|
+
.ed-wave-level-icon-path { stroke: currentColor; }
|
|
1778
1860
|
.ed-wave-weak { stroke-dasharray: 4 3; }
|
|
1779
1861
|
/* v3.5.0 Task 10: the P/N explicit-edge arrowhead — see wave-ui.js's
|
|
1780
1862
|
drawLane isClock branch for the measurement behind its shape and
|
|
@@ -1885,6 +1967,38 @@ ${itemsHtml}
|
|
|
1885
1967
|
untouched no matter what class it shares. */
|
|
1886
1968
|
text.ed-wave-edge-label { pointer-events: none; }
|
|
1887
1969
|
.ed-wave-edge-label { font-size: 11px; fill: #333; }
|
|
1970
|
+
/* v3.6.0 Task 8: the canvas's own name column — a mirror of the rail's
|
|
1971
|
+
.ed-wave-lane-name / .ed-wave-group (see the comment above
|
|
1972
|
+
.ed-wave-lane-rows). Unlike .ed-wave-edge-label these two class names
|
|
1973
|
+
are not shared with any non-SVG control, so pointer-events: none goes
|
|
1974
|
+
straight on the class rather than needing the element-scoped selector
|
|
1975
|
+
that rule uses. MEASURED (task-8-report.md): the longest lane name in
|
|
1976
|
+
this repo's fixtures is 12 chars ("he said \"hi\""), which at 11px in
|
|
1977
|
+
both the app's default sans-serif and the worst case (monospace)
|
|
1978
|
+
measures well under the 110px a top-level name has to fit in
|
|
1979
|
+
(nameColWidth 120 - NAME_GAP 10), so SIZES.nameColWidth stays 120. */
|
|
1980
|
+
.ed-wave-lane-label, .ed-wave-group-label { pointer-events: none; }
|
|
1981
|
+
.ed-wave-lane-label { font-size: 11px; fill: #333; }
|
|
1982
|
+
.ed-wave-group-label {
|
|
1983
|
+
font-size: 11px; fill: #555; writing-mode: vertical-rl; text-orientation: mixed;
|
|
1984
|
+
}
|
|
1985
|
+
/* v3.6.0 Task 9: the cycle ruler and the head/foot bands — see
|
|
1986
|
+
wave-draw.js's drawRuler()/drawBand() comment for the geometry (tick on
|
|
1987
|
+
boundaries, tock at centres, both filtered by every) and why this is
|
|
1988
|
+
read-only with respect to the document. Purely decorative like the name
|
|
1989
|
+
column above it, so pointer-events: none on the same principle. The
|
|
1990
|
+
ruler's own fill matches the pinned engine's own muted class (fill
|
|
1991
|
+
#aaa, node_modules/wavedrom's waveSkin CSS) so a tick/tock painted here
|
|
1992
|
+
reads the same shade as the identical mark in the WaveDrom preview
|
|
1993
|
+
beside it; the head/foot titles use the darker ed-wave-lane-label shade
|
|
1994
|
+
instead, matching the engine's own plain black fill for head.text /
|
|
1995
|
+
foot.text closely enough at this scale without introducing a third
|
|
1996
|
+
grey. */
|
|
1997
|
+
.ed-wave-ruler-tick, .ed-wave-ruler-tock, .ed-wave-head-text, .ed-wave-foot-text {
|
|
1998
|
+
pointer-events: none;
|
|
1999
|
+
}
|
|
2000
|
+
.ed-wave-ruler-tick, .ed-wave-ruler-tock { font-size: 11px; fill: #aaa; }
|
|
2001
|
+
.ed-wave-head-text, .ed-wave-foot-text { font-size: 11px; fill: #333; }
|
|
1888
2002
|
.ed-wave-edge-handle { fill: #1a73e8; stroke: #ffffff; }
|
|
1889
2003
|
.ed-wave-edge-arrow { fill: #0041c4; }
|
|
1890
2004
|
.ed-wave-edge-tee { stroke: #0041c4; }
|
|
@@ -1897,11 +2011,113 @@ ${itemsHtml}
|
|
|
1897
2011
|
stroke: #1a73e8; stroke-width: 2; stroke-dasharray: 5 4; fill: none;
|
|
1898
2012
|
pointer-events: none;
|
|
1899
2013
|
}
|
|
1900
|
-
.
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2014
|
+
/* v3.6.0 Task 11: the transition dots wave-draw.js's drawTransitions()
|
|
2015
|
+
paints while edge-creation is armed -- the visual answer to "where can
|
|
2016
|
+
I aim". pointer-events: none for the same reason ed-wave-selection and
|
|
2017
|
+
the other UI-state marks above are: the actual hit-testing runs through
|
|
2018
|
+
geometry.boundaryAt against the pointer's raw coordinates
|
|
2019
|
+
(wave-ui.js's cellFromEvent/onCanvasHover), never through this circle
|
|
2020
|
+
as a DOM target, so it must never itself swallow the press it exists
|
|
2021
|
+
to help aim. The [data-hot] variant is the same dot, larger and
|
|
2022
|
+
filled solid, for the one nearest the pointer right now
|
|
2023
|
+
(state.hoverBoundary in drawTransitions). */
|
|
2024
|
+
.ed-wave-transition {
|
|
2025
|
+
fill: #ffffff; stroke: #1a73e8; stroke-width: 1.5; pointer-events: none;
|
|
2026
|
+
}
|
|
2027
|
+
.ed-wave-transition[data-hot] { fill: #1a73e8; stroke: #0041c4; }
|
|
2028
|
+
/* v3.6.0 Task 13: the old flex-basis/border/padding here were for
|
|
2029
|
+
.ed-wave-preview as a direct flex child of .ed-wave-body; it now
|
|
2030
|
+
lives inside .ed-wave-section-body (the 預覽 panel), which already
|
|
2031
|
+
supplies the column width and padding, so only its own scroll behaviour
|
|
2032
|
+
is left to set — a wide diagram's preview SVG must scroll within the
|
|
2033
|
+
panel rather than stretching the whole 340px column. */
|
|
2034
|
+
.ed-wave-preview { overflow: auto; max-height: 260px; }
|
|
1904
2035
|
.ed-wave-preview-label { font-size: 11px; color: #666; margin-bottom: 6px; }
|
|
2036
|
+
/* v3.6.0 Task 13: the right-hand column — five collapsible panels
|
|
2037
|
+
(關聯線/文件/Lane/預覽/原始碼) replacing the old direct
|
|
2038
|
+
.ed-wave-preview column. 'flex: 0 0 340px' (not '0 1'): unlike the
|
|
2039
|
+
old preview column, this one does not shrink when the canvas wants
|
|
2040
|
+
more room — width-budget wins go through .ed-wave-panel's own cap
|
|
2041
|
+
above, not by starving this column, because starving it re-introduces
|
|
2042
|
+
the exact 160px-minimum squeeze this task's own .ed-wave-lanes
|
|
2043
|
+
comment measured as the 800x600 defect. Below 1100px it collapses to a
|
|
2044
|
+
28px rail instead (media query below) for the identical reason. */
|
|
2045
|
+
/* v3.6.0 Task 13 fix round 4: box-sizing: border-box added. wave-ui.js's
|
|
2046
|
+
clearSelectedEdgeOfSide() sets an inline width (in border-box, i.e.
|
|
2047
|
+
rendered, pixels — the same unit getBoundingClientRect() reports
|
|
2048
|
+
everywhere else it measures this element) to keep the selected edge's
|
|
2049
|
+
own handle clear of this panel when expanded. Under the default
|
|
2050
|
+
content-box sizing, width excludes padding (16px) and the border
|
|
2051
|
+
(1px), so a JS-computed width landed 17px narrower on screen than
|
|
2052
|
+
intended — MEASURED, not assumed: an inline width of 310px rendered at
|
|
2053
|
+
327px. border-box makes width mean the same thing in CSS and in
|
|
2054
|
+
getBoundingClientRect(), so the two agree without wave-ui.js having to
|
|
2055
|
+
hardcode this rule's own padding/border numbers into its arithmetic. */
|
|
2056
|
+
.ed-wave-side {
|
|
2057
|
+
box-sizing: border-box; flex: 0 0 340px; border-left: 1px solid #e2e2e2;
|
|
2058
|
+
overflow: auto; padding: 8px; display: flex; flex-direction: column; gap: 8px;
|
|
2059
|
+
}
|
|
2060
|
+
.ed-wave-section { border: 1px solid #ececec; border-radius: 6px; }
|
|
2061
|
+
.ed-wave-section-title {
|
|
2062
|
+
display: block; width: 100%; text-align: left; border: 0; background: #fafafa;
|
|
2063
|
+
font: inherit; font-size: 11px; letter-spacing: .5px; color: #555;
|
|
2064
|
+
padding: 5px 8px; cursor: pointer; border-radius: 6px 6px 0 0;
|
|
2065
|
+
}
|
|
2066
|
+
.ed-wave-section-body { display: none; padding: 8px; }
|
|
2067
|
+
/* v3.6.0 Task 13 fix round 3: data-open is this SECTION's own toggle
|
|
2068
|
+
state (wave-panels.js's section()) and says NOTHING about whether the
|
|
2069
|
+
RAIL is currently showing it — those are two independent attributes on
|
|
2070
|
+
two different elements. A section can report data-open with a
|
|
2071
|
+
zero-size, unhittable body the whole time .ed-wave-side itself is
|
|
2072
|
+
collapsed to its <1100px 28px rail (below): 關聯線/文件/Lane all default
|
|
2073
|
+
data-open, and none of that changes just because the rail hides them.
|
|
2074
|
+
Left deliberately this way rather than making data-open track actual
|
|
2075
|
+
visibility — that would conflate "this section is open" with "the rail
|
|
2076
|
+
happens to be showing right now", and toggling ONE section would then
|
|
2077
|
+
have to silently relabel every OTHER already-open section's own state
|
|
2078
|
+
too. What actually closes the gap this could otherwise leave (a control
|
|
2079
|
+
that LOOKS reachable in the DOM but is not) is wave-ui.js's render()
|
|
2080
|
+
expanding the rail on the one transition that matters (selecting an
|
|
2081
|
+
edge) rather than this attribute pretending to mean something it does
|
|
2082
|
+
not. */
|
|
2083
|
+
.ed-wave-section[data-open] .ed-wave-section-body { display: block; }
|
|
2084
|
+
.ed-wave-section-body label { display: flex; align-items: center; gap: 6px; margin: 4px 0; }
|
|
2085
|
+
.ed-wave-section-body label span { flex: 0 0 96px; font-size: 12px; color: #555; }
|
|
2086
|
+
.ed-wave-section-body input {
|
|
2087
|
+
flex: 1 1 auto; min-width: 0; font-size: 12px; padding: 2px 4px;
|
|
2088
|
+
}
|
|
2089
|
+
.ed-wave-source {
|
|
2090
|
+
margin: 0; font-size: 11px; line-height: 1.45; white-space: pre;
|
|
2091
|
+
overflow: auto; max-height: 260px;
|
|
2092
|
+
}
|
|
2093
|
+
/* 視窗窄於這條線時右欄收成一條直立標籤,展開時覆蓋在畫布上 —— 否則
|
|
2094
|
+
800x600 的測試視窗下畫布會被 340px 固定寬的側欄吃掉大半,拖曳手勢會
|
|
2095
|
+
不可用(v3.6.0 Task 13 自己的 .ed-wave-lanes 註解量到的舊值:只剩
|
|
2096
|
+
282px)。收起時仍是普通文件流寬度(28px),展開時疊在畫布上方而不擠
|
|
2097
|
+
壓它。data-expanded 有三個來源:wave-panels.js 的 section() 點擊 title、
|
|
2098
|
+
同檔案的 collapseBtn 反過來移除它、以及 fix round 3 新增的
|
|
2099
|
+
wave-panels.js 的 expandSide()(由 wave-ui.js 的 render() 在
|
|
2100
|
+
selectedEdge 從 null 變成有值的那一拍呼叫——選取一條 edge 之後,
|
|
2101
|
+
整個回饋只長在這個側欄的關聯線面板裡,窄視窗下不展開就等於使用者
|
|
2102
|
+
什麼都看不到、也按不到)。 */
|
|
2103
|
+
/* v3.6.0 Task 13 fix round 1: the collapse-back button. Hidden everywhere
|
|
2104
|
+
except the one state it exists for — the <1100px rail, expanded — so it
|
|
2105
|
+
never shows, and never sits in the tab order, at a wide viewport or in
|
|
2106
|
+
the rail's own default collapsed state. */
|
|
2107
|
+
.ed-wave-side-collapse { display: none; }
|
|
2108
|
+
@media (max-width: 1100px) {
|
|
2109
|
+
.ed-wave-side { flex: 0 0 28px; padding: 4px 2px; }
|
|
2110
|
+
.ed-wave-side:not([data-expanded]) .ed-wave-section-body { display: none; }
|
|
2111
|
+
.ed-wave-side[data-expanded] {
|
|
2112
|
+
position: absolute; right: 0; top: 0; bottom: 0; width: 340px;
|
|
2113
|
+
background: #ffffff; z-index: 3; flex: 0 0 340px; padding: 8px;
|
|
2114
|
+
}
|
|
2115
|
+
.ed-wave-side[data-expanded] .ed-wave-side-collapse {
|
|
2116
|
+
display: block; width: 100%; text-align: right; border: 0;
|
|
2117
|
+
background: transparent; font-size: 12px; color: #555; cursor: pointer;
|
|
2118
|
+
padding: 2px 4px 6px;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
1905
2121
|
.ed-wave-status {
|
|
1906
2122
|
border-top: 1px solid #e2e2e2; padding: 6px 12px; font-size: 12px;
|
|
1907
2123
|
color: #444; min-height: 18px;
|
|
@@ -2532,6 +2748,32 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
2532
2748
|
height: auto;
|
|
2533
2749
|
margin: 0 auto;
|
|
2534
2750
|
}
|
|
2751
|
+
/* A diagram wide enough that fitting it makes its own labels unreadable is
|
|
2752
|
+
drawn at natural size in a scroll box instead. The verdict is never "is
|
|
2753
|
+
this a waveform" -- it is "how small does fitting make the smallest
|
|
2754
|
+
glyph", because the damage is proportional to the aspect ratio. MEASURED,
|
|
2755
|
+
918px column: an 8-node flowchart fits at 0.71 and still draws 11.4px
|
|
2756
|
+
labels, while a 60-cycle waveform fits at 0.37 and draws 5.4px ones.
|
|
2757
|
+
applyDiagramScale() in the reader runtime below measures that and is the
|
|
2758
|
+
only thing that adds this class. The margin override is there because the
|
|
2759
|
+
".content svg" rule centres with auto margins, which has nothing to
|
|
2760
|
+
centre once the svg is wider than the box.
|
|
2761
|
+
Source order is load-bearing: ".diagram-scroll > svg" and the
|
|
2762
|
+
".content svg" rule above are both (0,1,1), so this wins only by coming
|
|
2763
|
+
after it. The child combinator matches the JS, which looks for the host's
|
|
2764
|
+
own ":scope > svg" -- one rule, stated twice, deliberately identically.
|
|
2765
|
+
One thing this rule does that it does not say: setting ONE axis to
|
|
2766
|
+
something other than visible promotes the OTHER axis to auto as well, per
|
|
2767
|
+
the CSS overflow spec, so every host here is a clipping block formatting
|
|
2768
|
+
context in BOTH directions. MEASURED on the three-engine fixture: all
|
|
2769
|
+
three scrolling hosts report a computed overflow-y of "auto" although only
|
|
2770
|
+
overflow-x is written, and all three have scrollHeight === clientHeight,
|
|
2771
|
+
so nothing is being clipped today. Recorded because a diagram that one day
|
|
2772
|
+
overflows VERTICALLY inside its host would be silently cut rather than
|
|
2773
|
+
overhanging, and neither this rule nor the tests would say why.
|
|
2774
|
+
(No backticks in this comment: it lives inside a JS template literal.) */
|
|
2775
|
+
.diagram-scroll { overflow-x: auto; }
|
|
2776
|
+
.diagram-scroll > svg { max-width: none; margin: 0; }
|
|
2535
2777
|
/* Anything the reader can pop out. The click handler uses the same
|
|
2536
2778
|
selectors, so this cursor and that behaviour cannot drift apart. */
|
|
2537
2779
|
.content img,
|
|
@@ -2723,6 +2965,20 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
2723
2965
|
}
|
|
2724
2966
|
@media print {
|
|
2725
2967
|
body { font-size: 11pt; }
|
|
2968
|
+
/* No scrollbars in a PDF, so an unscaled diagram there is not a
|
|
2969
|
+
scrollable one -- it is a clipped one, and half a waveform is worse
|
|
2970
|
+
than a small one. Hand it back to shrink-to-fit. Same (0,1,1) tie as
|
|
2971
|
+
the base rule, won the same way: source order.
|
|
2972
|
+
The !important is not decoration and was MEASURED: mermaid writes its
|
|
2973
|
+
own style="max-width: <natural>px" onto the svg element, and an inline
|
|
2974
|
+
declaration beats any rule in here regardless of specificity, so the
|
|
2975
|
+
plain version of this line clamped nothing and the first PDF built from
|
|
2976
|
+
it came out with a 16-stage flowchart cut off after Stage 3.
|
|
2977
|
+
applyDiagramScale's inline width is clamped by the same declaration --
|
|
2978
|
+
a max-width always beats a width -- so the runtime needs no print
|
|
2979
|
+
branch of its own. */
|
|
2980
|
+
.diagram-scroll { overflow-x: visible; }
|
|
2981
|
+
.diagram-scroll > svg { max-width: 100% !important; height: auto; }
|
|
2726
2982
|
.page-layout {
|
|
2727
2983
|
display: block;
|
|
2728
2984
|
max-width: 100%;
|
|
@@ -4258,9 +4514,223 @@ ${usesDrawioMultiPage ? ` // v3.4.0 batch2 Task 6: wired through a NAMED, reusa
|
|
|
4258
4514
|
` : ''}
|
|
4259
4515
|
// ── Diagram / image lightbox ──────────────────────────────────────────────
|
|
4260
4516
|
// Specs are read at 100% but their block diagrams and waveforms are drawn far
|
|
4261
|
-
// wider than the column, so the inline copy is
|
|
4262
|
-
// pops it into a modal stage that zooms and scrolls.
|
|
4517
|
+
// wider than the column, so the inline copy is shrunk to fit. Clicking one
|
|
4518
|
+
// pops it into a modal stage that zooms and scrolls. Past a point shrinking
|
|
4519
|
+
// stops being an acceptable inline copy at all -- that floor, and what the
|
|
4520
|
+
// inline copy does instead below it, is the block right after this one, and
|
|
4521
|
+
// the two share LIGHTBOX_TARGETS so they can never disagree about what
|
|
4522
|
+
// counts as a diagram.
|
|
4263
4523
|
var LIGHTBOX_TARGETS = 'img, .mermaid, .graphviz, .drawio, [id^="WaveDrom_Display_"], .wavedrom-diagram';
|
|
4524
|
+
|
|
4525
|
+
// ── Readability floor ─────────────────────────────────────────────────────
|
|
4526
|
+
//
|
|
4527
|
+
// The lightbox above is the escape hatch for a diagram drawn wider than the
|
|
4528
|
+
// column. This is the other half: the inline copy has to be worth reading in
|
|
4529
|
+
// the first place. ".content svg { max-width: 100% }" shrinks to fit and
|
|
4530
|
+
// never looks at how small it is making the text, and the damage grows with
|
|
4531
|
+
// the aspect ratio, so past some width the reader is handed a picture of a
|
|
4532
|
+
// waveform rather than a waveform.
|
|
4533
|
+
//
|
|
4534
|
+
// MEASURED on this branch, 1280x900 viewport, .content clientWidth 918, the
|
|
4535
|
+
// waveform skin's own labels at 11pt = 14.667px: a 60-cycle waveform is
|
|
4536
|
+
// 2480px natural, fits at scale 0.370, and its labels land at 5.43px, where
|
|
4537
|
+
// "clk" and "data" are smudges. The identical expression leaves an 8-node
|
|
4538
|
+
// flowchart alone (1287px natural, fits at 0.714, labels at 11.41px), which
|
|
4539
|
+
// is the whole point -- the trigger is the measured glyph size, not which
|
|
4540
|
+
// engine drew the diagram.
|
|
4541
|
+
//
|
|
4542
|
+
// This block ships in EVERY document, including diagram-free ones, and
|
|
4543
|
+
// test/md2doc.test.js asserts that such a document carries no diagram-engine
|
|
4544
|
+
// runtime. So engines may be named here in prose only, never by the global,
|
|
4545
|
+
// skin or probe identifiers the engines themselves define -- that guard
|
|
4546
|
+
// greps for those, and it cannot tell a comment from code.
|
|
4547
|
+
//
|
|
4548
|
+
// The floor was measured, not estimated. Eight widths were rendered and
|
|
4549
|
+
// screenshotted one per row at deviceScaleFactor 1 and read: 9.35px was the
|
|
4550
|
+
// last comfortable row, 8.86px was where lowercase labels start breaking up.
|
|
4551
|
+
// The full table lives in test/reader-panels.test.js section 5, beside the
|
|
4552
|
+
// assertions that bracket this number from both sides.
|
|
4553
|
+
var DIAGRAM_FONT_FLOOR = 9;
|
|
4554
|
+
|
|
4555
|
+
// The width the diagram was drawn at, in the SAME units diagramMinFontPx
|
|
4556
|
+
// reports -- the svg's own user units. The pairing is the whole point: the
|
|
4557
|
+
// rendered size of a glyph is its computed font-size times (rendered width /
|
|
4558
|
+
// this), so the two must not be measured in different coordinate systems.
|
|
4559
|
+
//
|
|
4560
|
+
// That is why the viewBox comes first and the width attribute is only the
|
|
4561
|
+
// fallback for an svg that has none (where user units are CSS px anyway).
|
|
4562
|
+
// MEASURED, three engines, three different width attributes for the same
|
|
4563
|
+
// idea: the waveform engine writes width="2480" (equal to its viewBox, so
|
|
4564
|
+
// the order does not matter), mermaid writes width="100%" and keeps the real
|
|
4565
|
+
// number in the viewBox ("0 0 6838.25 70" for a 40-node flowchart), and the
|
|
4566
|
+
// graph engine writes width="4069pt" against a 4069-unit viewBox. Reading
|
|
4567
|
+
// the attribute first gives 100 for every mermaid diagram -- which reads as
|
|
4568
|
+
// "already fits", so the floor silently never applies to any of them -- and
|
|
4569
|
+
// mixes points into a ratio of pixels for the graph one.
|
|
4570
|
+
function diagramNaturalWidth(svg) {
|
|
4571
|
+
var viewBox = svg.getAttribute('viewBox');
|
|
4572
|
+
if (viewBox) {
|
|
4573
|
+
// The doubled backslashes are not a typo and node --check will not miss
|
|
4574
|
+
// them for you: this whole runtime is emitted from a JS template
|
|
4575
|
+
// literal, where "\\s" is an unrecognised escape that collapses to a
|
|
4576
|
+
// bare "s". This split really did ship as /[s,]+/ once, which parsed
|
|
4577
|
+
// "0 0 6838.25 70" as a single token, returned 0, and turned the
|
|
4578
|
+
// non-waveform half of this feature into a silent no-op. Precedent: the
|
|
4579
|
+
// normaliseText helper above spells the same thing /\\s+/g.
|
|
4580
|
+
var vw = parseFloat(viewBox.split(/[\\s,]+/)[2]);
|
|
4581
|
+
if (vw > 0) return vw;
|
|
4582
|
+
}
|
|
4583
|
+
var attr = svg.getAttribute('width');
|
|
4584
|
+
if (attr && /^\\s*[0-9.]+(px)?\\s*$/.test(attr)) {
|
|
4585
|
+
var px = parseFloat(attr);
|
|
4586
|
+
if (px > 0) return px;
|
|
4587
|
+
}
|
|
4588
|
+
return 0;
|
|
4589
|
+
}
|
|
4590
|
+
|
|
4591
|
+
// The smallest glyph the diagram draws, in those same user units -- an svg's
|
|
4592
|
+
// computed font-size does not move when its viewBox is scaled, so the size
|
|
4593
|
+
// on screen is this times the fit scale.
|
|
4594
|
+
//
|
|
4595
|
+
// Also MEASURED rather than assumed: mermaid's default label is not an svg
|
|
4596
|
+
// <text> at all, it is an html <span> inside a <foreignObject> (79 of them
|
|
4597
|
+
// in the flowchart above, and zero <text> nodes), so a text-only query
|
|
4598
|
+
// returns Infinity for a mermaid diagram and hands it the same free pass.
|
|
4599
|
+
// Only a node that really carries a text node is counted; the font-size of
|
|
4600
|
+
// an empty wrapper is a number nobody can see.
|
|
4601
|
+
function diagramMinFontPx(svg) {
|
|
4602
|
+
var min = Infinity;
|
|
4603
|
+
var nodes = svg.querySelectorAll('text, tspan, foreignObject *');
|
|
4604
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
4605
|
+
var node = nodes[i];
|
|
4606
|
+
var carriesText = false;
|
|
4607
|
+
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
4608
|
+
// .trim() rather than a /\\S/ test: one less escape to get wrong in a
|
|
4609
|
+
// template literal, and it says the same thing.
|
|
4610
|
+
if (child.nodeType === 3 && child.nodeValue.trim()) { carriesText = true; break; }
|
|
4611
|
+
}
|
|
4612
|
+
if (!carriesText) continue;
|
|
4613
|
+
var size = parseFloat(window.getComputedStyle(node).fontSize);
|
|
4614
|
+
if (isFinite(size) && size > 0 && size < min) min = size;
|
|
4615
|
+
}
|
|
4616
|
+
return min;
|
|
4617
|
+
}
|
|
4618
|
+
|
|
4619
|
+
function applyDiagramScale() {
|
|
4620
|
+
var nodes = document.querySelectorAll(LIGHTBOX_TARGETS);
|
|
4621
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
4622
|
+
var host = nodes[i];
|
|
4623
|
+
// ":scope > svg" is the same relationship ".diagram-scroll > svg" in the
|
|
4624
|
+
// stylesheet asks for -- one rule, written twice, deliberately
|
|
4625
|
+
// identically. An <img> target has no svg and no text to measure, and
|
|
4626
|
+
// neither do the empty duplicate display divs the waveform engine's
|
|
4627
|
+
// repeated render passes leave behind.
|
|
4628
|
+
var svg = host.querySelector ? host.querySelector(':scope > svg') : null;
|
|
4629
|
+
if (!svg) continue;
|
|
4630
|
+
var natural = diagramNaturalWidth(svg);
|
|
4631
|
+
if (!natural) continue;
|
|
4632
|
+
// Always measure the FITTED geometry, never geometry a previous pass
|
|
4633
|
+
// produced. Clearing the inline width is half of that and matters on its
|
|
4634
|
+
// own: a mermaid svg is width="100%", so lifting max-width would leave it
|
|
4635
|
+
// exactly as wide as the column it is supposed to be escaping.
|
|
4636
|
+
host.classList.remove('diagram-scroll');
|
|
4637
|
+
svg.style.width = '';
|
|
4638
|
+
var avail = host.clientWidth;
|
|
4639
|
+
if (!avail) continue;
|
|
4640
|
+
var scale = avail / natural;
|
|
4641
|
+
if (scale >= 1) continue;
|
|
4642
|
+
var minFont = diagramMinFontPx(svg);
|
|
4643
|
+
if (!isFinite(minFont)) continue;
|
|
4644
|
+
if (minFont * scale >= DIAGRAM_FONT_FLOOR) continue;
|
|
4645
|
+
host.classList.add('diagram-scroll');
|
|
4646
|
+
// max-width alone is enough for a diagram that states its width in px
|
|
4647
|
+
// and not enough for one that states it in percent, so both get the
|
|
4648
|
+
// number explicitly. @media print is unaffected: a max-width always
|
|
4649
|
+
// beats a width, which is what lets the print rule hand these back to
|
|
4650
|
+
// shrink-to-fit without this line having to know about it.
|
|
4651
|
+
svg.style.width = natural + 'px';
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
|
|
4655
|
+
// When to re-run. Two inputs move after this script executes: whether the
|
|
4656
|
+
// diagram exists yet, and how wide the column is.
|
|
4657
|
+
//
|
|
4658
|
+
// The waveform engine's own render pass is scheduled four times
|
|
4659
|
+
// (DOMContentLoaded, load, +250ms, +1000ms) and mermaid renders on its own
|
|
4660
|
+
// clock, so an svg can appear long after this line. The MutationObserver is
|
|
4661
|
+
// deliberately not a copy of those timings -- a copy goes stale the first
|
|
4662
|
+
// time either engine changes its schedule, and silently: a pass that runs
|
|
4663
|
+
// before the svg exists looks exactly like a pass that decided to do
|
|
4664
|
+
// nothing. It only wakes for an
|
|
4665
|
+
// insertion that actually brought an svg, so edit-mode typing does not pay
|
|
4666
|
+
// for it, and it cannot self-trigger because adding the class is an
|
|
4667
|
+
// attribute mutation, not a childList one.
|
|
4668
|
+
//
|
|
4669
|
+
// The column width moves on a window resize, on crossing the 1080px drawer
|
|
4670
|
+
// breakpoint and on a sidebar-splitter drag. A ResizeObserver on .content
|
|
4671
|
+
// sees all three; a window resize listener sees only the first, which is why
|
|
4672
|
+
// there is no hand-placed call at the end of the sidebar handlers. Toggling
|
|
4673
|
+
// the class changes the host's HEIGHT (a scrollbar appears) and that feeds
|
|
4674
|
+
// back into the same observer, so a pass only runs when the width moved.
|
|
4675
|
+
var diagramScaleFrame = 0;
|
|
4676
|
+
function scheduleDiagramScale() {
|
|
4677
|
+
if (diagramScaleFrame) return;
|
|
4678
|
+
diagramScaleFrame = window.requestAnimationFrame(function () {
|
|
4679
|
+
diagramScaleFrame = 0;
|
|
4680
|
+
applyDiagramScale();
|
|
4681
|
+
});
|
|
4682
|
+
}
|
|
4683
|
+
applyDiagramScale();
|
|
4684
|
+
window.addEventListener('DOMContentLoaded', scheduleDiagramScale);
|
|
4685
|
+
window.addEventListener('load', scheduleDiagramScale);
|
|
4686
|
+
if (contentEl && typeof MutationObserver === 'function') {
|
|
4687
|
+
new MutationObserver(function (records) {
|
|
4688
|
+
for (var r = 0; r < records.length; r++) {
|
|
4689
|
+
var added = records[r].addedNodes;
|
|
4690
|
+
for (var a = 0; a < added.length; a++) {
|
|
4691
|
+
var node = added[a];
|
|
4692
|
+
if (!node || node.nodeType !== 1) continue;
|
|
4693
|
+
if (node.tagName.toLowerCase() === 'svg' ||
|
|
4694
|
+
(node.querySelector && node.querySelector('svg'))) {
|
|
4695
|
+
scheduleDiagramScale();
|
|
4696
|
+
return;
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
}).observe(contentEl, { childList: true, subtree: true });
|
|
4701
|
+
}
|
|
4702
|
+
if (contentEl && typeof ResizeObserver === 'function') {
|
|
4703
|
+
// KNOWN RISK, and honestly not measurable in this environment.
|
|
4704
|
+
//
|
|
4705
|
+
// On a platform with CLASSIC (space-taking) scrollbars rather than overlay
|
|
4706
|
+
// ones, adding the class makes the page taller -- the svg goes to natural
|
|
4707
|
+
// size and keeps height: auto -- and a page that gains a vertical
|
|
4708
|
+
// scrollbar loses about 15px of column width to it. That re-runs the
|
|
4709
|
+
// verdict at a narrower width, which can flip a diagram sitting close to
|
|
4710
|
+
// the floor back the other way, which makes the page shorter again. The
|
|
4711
|
+
// guard below is "the width changed", and the width is exactly what moves
|
|
4712
|
+
// in that loop, so the guard does not damp it.
|
|
4713
|
+
//
|
|
4714
|
+
// Not reproducible here: this environment only ever produces overlay
|
|
4715
|
+
// scrollbars (clientHeight === offsetHeight on a scrolling host, and
|
|
4716
|
+
// launching with the overlay-scrollbar features disabled changes nothing),
|
|
4717
|
+
// so neither the oscillation nor a fix for it can be observed -- and an
|
|
4718
|
+
// unmeasurable fix is how a worse bug gets shipped. Left alone on purpose.
|
|
4719
|
+
// The worst case is a busy loop rather than a hang, because
|
|
4720
|
+
// scheduleDiagramScale coalesces onto one animation frame. If it is ever
|
|
4721
|
+
// reproduced, the damping to reach for is hysteresis on the decision (two
|
|
4722
|
+
// thresholds instead of one), not a looser equality test on the width.
|
|
4723
|
+
var lastDiagramColW = -1;
|
|
4724
|
+
new ResizeObserver(function () {
|
|
4725
|
+
var w = contentEl.clientWidth;
|
|
4726
|
+
if (w === lastDiagramColW) return;
|
|
4727
|
+
lastDiagramColW = w;
|
|
4728
|
+
scheduleDiagramScale();
|
|
4729
|
+
}).observe(contentEl);
|
|
4730
|
+
} else {
|
|
4731
|
+
window.addEventListener('resize', scheduleDiagramScale);
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4264
4734
|
var LIGHTBOX_MIN_ZOOM = 0.05;
|
|
4265
4735
|
var LIGHTBOX_MAX_ZOOM = 8;
|
|
4266
4736
|
var LIGHTBOX_STEP = 1.25;
|