mnfst 0.5.162 → 0.5.164
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/manifest.appwrite.auth.js +107 -199
- package/lib/manifest.appwrite.data.js +1 -3
- package/lib/manifest.charts.js +88 -97
- package/lib/manifest.chat.js +515 -0
- package/lib/manifest.code.js +110 -340
- package/lib/manifest.colorpicker.js +252 -551
- package/lib/manifest.combobox.js +73 -91
- package/lib/manifest.components.js +34 -95
- package/lib/manifest.css +18 -144
- package/lib/manifest.data.js +189 -562
- package/lib/manifest.datepicker.js +43 -128
- package/lib/manifest.dropdowns.js +18 -39
- package/lib/manifest.export.js +29 -147
- package/lib/manifest.form.css +18 -12
- package/lib/manifest.icons.js +9 -24
- package/lib/manifest.integrity.json +23 -23
- package/lib/manifest.js +62 -126
- package/lib/manifest.localization.js +67 -200
- package/lib/manifest.markdown.js +65 -174
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.payments.js +40 -139
- package/lib/manifest.router.js +24 -73
- package/lib/manifest.status.js +20 -56
- package/lib/manifest.svg.js +12 -30
- package/lib/manifest.tooltips.js +28 -65
- package/lib/manifest.utilities.js +40 -107
- package/lib/manifest.virtual.js +15 -53
- package/package.json +1 -1
- package/lib/manifest.appwrite.presence.js +0 -1650
package/lib/manifest.charts.js
CHANGED
|
@@ -1,33 +1,18 @@
|
|
|
1
|
-
/* Manifest Charts
|
|
1
|
+
/* Manifest Charts — in-house SVG chart renderer (themeable, prerender-safe, a11y).
|
|
2
2
|
/* By Andrew Matlock under MIT license
|
|
3
3
|
/* https://manifestx.dev
|
|
4
|
-
/*
|
|
5
|
-
/* An in-house SVG chart renderer. SVG (not canvas) so charts inherit theme
|
|
6
|
-
/* colors via CSS variables, are restylable with the same selector
|
|
7
|
-
/* conventions as every other element, survive static prerendering as real
|
|
8
|
-
/* DOM, and expose accessible <title>/<desc>. The only dependencies are the
|
|
9
|
-
/* d3-scale / d3-shape / d3-array micro-modules (ISC, ~22KB), lazy-loaded
|
|
10
|
-
/* from esm.run on first scroll-into-view — the same posture as the code
|
|
11
|
-
/* plugin loading highlight.js on demand.
|
|
4
|
+
/* d3-scale/shape/array micro-modules (ISC) lazy-loaded from esm.run on scroll-in.
|
|
12
5
|
*/
|
|
13
6
|
|
|
14
7
|
(function () {
|
|
15
8
|
'use strict';
|
|
16
9
|
|
|
17
|
-
/*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* loaded. `_ui` is a reserved, self-identifying key: any loaded data source
|
|
21
|
-
* may carry a top-level `_ui` object, namespaced per element (`_ui.charts`,
|
|
22
|
-
* `_ui.colorpicker`, …); no manifest flag — overrides piggyback on the normal
|
|
23
|
-
* local-data/localization model. resolve() deep-merges every loaded source's
|
|
24
|
-
* `_ui[component]` onto the plugin's English fallbacks. Kept byte-identical
|
|
25
|
-
* across the date picker / color picker copies.
|
|
26
|
-
* ------------------------------------------------------------------ */
|
|
10
|
+
/* Shared localized-UI resolver: deep-merges every loaded source's `_ui[component]`
|
|
11
|
+
* onto the plugin's English fallbacks. Guarded; byte-identical across the picker
|
|
12
|
+
* copies (first plugin to load defines it). */
|
|
27
13
|
if (!window.ManifestUI) {
|
|
28
14
|
window.ManifestUI = {
|
|
29
|
-
/*
|
|
30
|
-
* sources only — never force-loads others just to scan them for `_ui`. */
|
|
15
|
+
/* Loaded data sources (current locale); never force-loads others. */
|
|
31
16
|
_loadedSourceNames() {
|
|
32
17
|
try {
|
|
33
18
|
const store = window.ManifestDataStore && window.ManifestDataStore.rawDataStore;
|
|
@@ -35,8 +20,7 @@
|
|
|
35
20
|
} catch (_) { }
|
|
36
21
|
return [];
|
|
37
22
|
},
|
|
38
|
-
/*
|
|
39
|
-
* Reads inside the caller's Alpine effect (if any) so $x/$locale make it reactive. */
|
|
23
|
+
/* Reads inside the caller's Alpine effect (if any) so $x/$locale stay reactive. */
|
|
40
24
|
resolve(component, fallbacks) {
|
|
41
25
|
const merged = JSON.parse(JSON.stringify(fallbacks || {}));
|
|
42
26
|
try {
|
|
@@ -256,19 +240,15 @@
|
|
|
256
240
|
t.appendChild(document.createTextNode(str == null ? '' : String(str))); // untrusted-safe
|
|
257
241
|
return t;
|
|
258
242
|
}
|
|
259
|
-
// Entry animations run only on
|
|
260
|
-
// (a bound value
|
|
261
|
-
// — otherwise dragging a slider replays the reveal every frame and the
|
|
262
|
-
// chart flickers. drawChart sets this before dispatching.
|
|
243
|
+
// Entry animations run only on first draw; redraws paint final state directly
|
|
244
|
+
// (else a bound value change replays the reveal every frame). Set by drawChart.
|
|
263
245
|
let _suppressAnim = false;
|
|
264
246
|
function animate(el, keyframes, opts) {
|
|
265
247
|
if (_suppressAnim || prefersReducedMotion() || typeof el.animate !== 'function') return;
|
|
266
248
|
try { el.animate(keyframes, Object.assign({ duration: 600, easing: 'cubic-bezier(0.22,1,0.36,1)', fill: 'backwards' }, opts)); } catch (_) { }
|
|
267
249
|
}
|
|
268
|
-
// Cursor-following tooltip
|
|
269
|
-
//
|
|
270
|
-
// box) — so charts use their own tip, themed to match, following the
|
|
271
|
-
// pointer (better UX for dense charts). aria-label carries AT semantics.
|
|
250
|
+
// Cursor-following tooltip: x-tooltip's CSS anchor positioning can't target SVG
|
|
251
|
+
// children (no layout box), so charts use their own themed tip. aria-label for AT.
|
|
272
252
|
function applyTip(seg, tip, cfg) {
|
|
273
253
|
seg.setAttribute('aria-label', tip);
|
|
274
254
|
if (!cfg.tooltip) return;
|
|
@@ -337,12 +317,8 @@
|
|
|
337
317
|
if (self.el.id) _registry[self.el.id] = self.api;
|
|
338
318
|
},
|
|
339
319
|
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
// tabs), and does NOT reset a pending timer on re-entry — a
|
|
343
|
-
// high-frequency reactive trigger (e.g. a plugin bumping the data-store
|
|
344
|
-
// version every tick) would otherwise perpetually reschedule and starve
|
|
345
|
-
// the draw.
|
|
320
|
+
// One draw per tick. setTimeout (not rAF) so backgrounded tabs still draw;
|
|
321
|
+
// doesn't reset a pending timer, so a high-frequency trigger can't starve it.
|
|
346
322
|
schedule() { if (this._t) return; this._t = setTimeout(() => { this._t = null; this.draw(); }, 0); },
|
|
347
323
|
|
|
348
324
|
renderError(msg) { this.el.innerHTML = ''; const d = document.createElement('small'); d.textContent = msg; this.el.appendChild(d); },
|
|
@@ -407,10 +383,8 @@
|
|
|
407
383
|
return 4 + n * cfg.rowHeight + 22;
|
|
408
384
|
}
|
|
409
385
|
|
|
410
|
-
// Palette size
|
|
411
|
-
//
|
|
412
|
-
// segment colours cycle through however many exist. Re-probed per draw
|
|
413
|
-
// so per-scope overrides apply.
|
|
386
|
+
// Palette size = count of consecutive --color-chart-N tokens (themes may extend
|
|
387
|
+
// past 8); colours cycle through them. Re-probed per draw for per-scope overrides.
|
|
414
388
|
let _paletteN = 8;
|
|
415
389
|
function probePalette(el) {
|
|
416
390
|
try {
|
|
@@ -649,10 +623,17 @@
|
|
|
649
623
|
if (cfg.legend) drawLegend(state, labels);
|
|
650
624
|
}
|
|
651
625
|
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
|
|
655
|
-
|
|
626
|
+
// Widest rendered label in px (exact via getComputedTextLength, char-estimate
|
|
627
|
+
// fallback). Sizes axis gutters to their content so charts sit balanced.
|
|
628
|
+
function labelWidth(root, items, cls) {
|
|
629
|
+
if (!items || !items.length) return 0;
|
|
630
|
+
const probe = svg('text', cls ? { class: cls, x: -9999, y: -9999 } : { x: -9999, y: -9999 }, root);
|
|
631
|
+
let max = 0;
|
|
632
|
+
for (const s of items) { probe.textContent = String(s); let w = 0; try { w = probe.getComputedTextLength(); } catch (_) { } if (!w) w = String(s).length * 7; if (w > max) max = w; }
|
|
633
|
+
root.removeChild(probe);
|
|
634
|
+
return Math.ceil(max);
|
|
635
|
+
}
|
|
636
|
+
|
|
656
637
|
function drawGauge(state, root, width, height) {
|
|
657
638
|
const cfg = state.config, d3 = state.d3;
|
|
658
639
|
const value = num(cfg.series[0] && cfg.series[0].data[0], 0);
|
|
@@ -661,9 +642,12 @@
|
|
|
661
642
|
const scale = d3.scaleLinear().domain([min, max]).range([START, END]).clamp(true);
|
|
662
643
|
const unit = cfg.unit || '';
|
|
663
644
|
|
|
664
|
-
|
|
645
|
+
// Reserve a band below the dial for the min/max labels, then centre the
|
|
646
|
+
// whole dial (arc + labels) within the frame so nothing spills out.
|
|
647
|
+
const PAD = 8, labelBand = cfg.axis ? 20 : 0;
|
|
648
|
+
const r = Math.max(0, Math.min(width / 2 - PAD, height - PAD - labelBand));
|
|
665
649
|
const thickness = Math.max(8, r * 0.22);
|
|
666
|
-
const cx = width / 2, cy =
|
|
650
|
+
const cx = width / 2, cy = Math.max(PAD, (height - (r + labelBand)) / 2) + r;
|
|
667
651
|
const g = svg('g', { transform: `translate(${cx},${cy})` }, root);
|
|
668
652
|
const arc = d3.arc().innerRadius(r - thickness).outerRadius(r).cornerRadius(cssRadius(state.el));
|
|
669
653
|
|
|
@@ -679,15 +663,12 @@
|
|
|
679
663
|
svg('path', { class: 'gauge-track', d: arc({ startAngle: START, endAngle: END }) }, g);
|
|
680
664
|
}
|
|
681
665
|
|
|
682
|
-
// Value arc — final geometry
|
|
683
|
-
//
|
|
684
|
-
// the sweep is a CSS reveal, never the source of the end state.
|
|
666
|
+
// Value arc — final geometry set unconditionally, so a background tab that
|
|
667
|
+
// skips the fade reveal still renders correctly.
|
|
685
668
|
const color = (cfg.series[0] && cfg.series[0].color) || 'var(--color-chart-1)';
|
|
686
669
|
const valueAngle = scale(value);
|
|
687
670
|
const vArc = svg('path', { class: 'gauge-value', d: arc({ startAngle: START, endAngle: valueAngle }), style: `--color-chart-color:${color}` }, g);
|
|
688
671
|
applyTip(vArc, (cfg.title ? cfg.title + ': ' : '') + value + unit, cfg);
|
|
689
|
-
// Reveal via fade (WAAPI, origin-independent) — the final geometry above
|
|
690
|
-
// is unconditional, so the gauge is correct even if this never runs.
|
|
691
672
|
animate(vArc, [{ opacity: 0 }, { opacity: 1 }], { duration: 500 });
|
|
692
673
|
|
|
693
674
|
// Centered readout + range end labels.
|
|
@@ -699,10 +680,8 @@
|
|
|
699
680
|
}
|
|
700
681
|
}
|
|
701
682
|
|
|
702
|
-
// Heatmap —
|
|
703
|
-
//
|
|
704
|
-
// --color-chart-heat-* tokens (no JS colour-interpolation dep), driven
|
|
705
|
-
// by the per-cell `--heat` percentage.
|
|
683
|
+
// Heatmap — matrix of cells (row per series, column per datum). Cell colour is a
|
|
684
|
+
// CSS color-mix of the --color-chart-heat-* tokens via a per-cell `--heat` %.
|
|
706
685
|
function drawHeatmap(state, root, width, height) {
|
|
707
686
|
const cfg = state.config, d3 = state.d3;
|
|
708
687
|
const rows = cfg.series;
|
|
@@ -710,7 +689,11 @@
|
|
|
710
689
|
const rowName = (r, i) => r.name || String(i + 1);
|
|
711
690
|
const showLabels = cfg.axis;
|
|
712
691
|
|
|
713
|
-
|
|
692
|
+
// Gutters sized to content: a uniform pad all round, plus a left gutter
|
|
693
|
+
// measured to the row labels so the grid sits balanced, not lopsided.
|
|
694
|
+
const PAD = 8;
|
|
695
|
+
const yW = showLabels ? labelWidth(root, rows.map((r, i) => rowName(r, i)), '') : 0;
|
|
696
|
+
const m = { top: PAD, right: PAD, bottom: showLabels ? PAD + 16 : PAD, left: showLabels ? PAD + yW + 8 : PAD };
|
|
714
697
|
const iw = width - m.left - m.right;
|
|
715
698
|
const ih = height - m.top - m.bottom;
|
|
716
699
|
// Tiles abut (padding 0); the gutter comes from insetting each rect
|
|
@@ -728,9 +711,8 @@
|
|
|
728
711
|
|
|
729
712
|
const plot = svg('g', { transform: `translate(${m.left},${m.top})` }, root);
|
|
730
713
|
|
|
731
|
-
// Round only the grid's outer corners: square tiles clipped to
|
|
732
|
-
//
|
|
733
|
-
// last tile's inner edge, so the radius isn't clipping empty gutter).
|
|
714
|
+
// Round only the grid's outer corners: square tiles clipped to one rounded
|
|
715
|
+
// rect hugging the cell extent (so the radius doesn't clip empty gutter).
|
|
734
716
|
const clipId = 'mnfst-heat-' + (++_uid);
|
|
735
717
|
const cp = svg('clipPath', { id: clipId }, svg('defs', null, root));
|
|
736
718
|
svg('rect', { x: 0, y: 0, width: Math.max(0, iw - gap), height: Math.max(0, ih - gap), rx: cssRadius(state.el) }, cp);
|
|
@@ -771,20 +753,22 @@
|
|
|
771
753
|
state.el.appendChild(footer);
|
|
772
754
|
}
|
|
773
755
|
|
|
774
|
-
// Gantt — each series is a track
|
|
775
|
-
//
|
|
776
|
-
//
|
|
777
|
-
// date-ish values → time, other strings → category (ordinal stages). A
|
|
778
|
-
// segment with no `to` is a point marker. Single track = a status strip;
|
|
779
|
-
// many tracks = swimlanes / Gantt.
|
|
756
|
+
// Gantt — each series is a track of `{ from, to, status?, label?, color? }`
|
|
757
|
+
// segments on a shared X axis, adapting numbers→linear, dates→time, else
|
|
758
|
+
// category. A `to`-less segment is a point marker.
|
|
780
759
|
function drawGantt(state, root, width, height) {
|
|
781
760
|
const cfg = state.config, d3 = state.d3;
|
|
782
761
|
const tracks = cfg.series;
|
|
783
762
|
const trackName = (t, i) => t.name || String(i + 1);
|
|
784
763
|
const showLabels = cfg.axis;
|
|
785
764
|
const unit = cfg.unit || '';
|
|
786
|
-
|
|
787
|
-
|
|
765
|
+
// Uniform pad all round; left gutter measured to the track labels, and
|
|
766
|
+
// extra headroom when markers carry a label drawn above the lanes.
|
|
767
|
+
const PAD = 8;
|
|
768
|
+
const hasMarkerLabels = Array.isArray(cfg.markers) && cfg.markers.some(mk => mk && mk.label);
|
|
769
|
+
const yW = showLabels ? labelWidth(root, tracks.map((t, i) => trackName(t, i)), 'gantt-track') : 0;
|
|
770
|
+
const m = { top: PAD + (hasMarkerLabels ? 10 : 0), right: PAD, bottom: 22, left: showLabels ? PAD + yW + 8 : PAD };
|
|
771
|
+
let iw = width - m.left - m.right;
|
|
788
772
|
const ih = height - m.top - m.bottom;
|
|
789
773
|
|
|
790
774
|
// Axis kind from the first segment's `from`.
|
|
@@ -794,18 +778,17 @@
|
|
|
794
778
|
: typeof firstFrom === 'number' ? 'numeric'
|
|
795
779
|
: (!isNaN(Date.parse(firstFrom)) ? 'time' : 'category');
|
|
796
780
|
|
|
797
|
-
|
|
781
|
+
// Domain is width-independent; the scale (and its ticks) is built from
|
|
782
|
+
// the current `iw` so it can be rebuilt after reserving label gutters.
|
|
783
|
+
let categoryDomain, dmin, dmax, conv, fmtBase;
|
|
798
784
|
if (mode === 'category') {
|
|
799
785
|
const seen = [];
|
|
800
786
|
tracks.forEach(t => (t.data || []).forEach(s => [s.from, s.to].forEach(v => { if (v != null && seen.indexOf(String(v)) < 0) seen.push(String(v)); })));
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
pos = v => x(String(v));
|
|
804
|
-
ticks = domain;
|
|
805
|
-
fmt = v => String(v);
|
|
787
|
+
categoryDomain = (cfg.labels && cfg.labels.length) ? cfg.labels.map(String) : seen;
|
|
788
|
+
fmtBase = v => String(v);
|
|
806
789
|
} else {
|
|
807
|
-
|
|
808
|
-
|
|
790
|
+
conv = mode === 'time' ? toTime : (v => num(v, 0));
|
|
791
|
+
dmin = Infinity; dmax = -Infinity;
|
|
809
792
|
tracks.forEach(t => (t.data || []).forEach(s => {
|
|
810
793
|
const a = conv(s.from), b = conv(s.to != null ? s.to : s.from);
|
|
811
794
|
if (a < dmin) dmin = a; if (b > dmax) dmax = b;
|
|
@@ -813,25 +796,38 @@
|
|
|
813
796
|
if (cfg.min != null) dmin = conv(cfg.min);
|
|
814
797
|
if (cfg.max != null) dmax = conv(cfg.max);
|
|
815
798
|
if (!isFinite(dmin) || !isFinite(dmax) || dmin === dmax) { dmin = 0; dmax = 1; }
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
799
|
+
fmtBase = mode === 'time' ? ganttFmt(dmin, dmax) : (v => String(v) + unit);
|
|
800
|
+
}
|
|
801
|
+
const buildX = () => {
|
|
802
|
+
if (mode === 'category') { const sc = d3.scalePoint().domain(categoryDomain).range([0, iw]); return { pos: v => sc(String(v)), ticks: categoryDomain, fmt: fmtBase }; }
|
|
803
|
+
const sc = (mode === 'time' ? d3.scaleTime() : d3.scaleLinear()).domain([dmin, dmax]).range([0, iw]);
|
|
804
|
+
return { pos: v => sc(conv(v)), ticks: sc.ticks(Math.max(2, Math.floor(iw / 80))), fmt: fmtBase };
|
|
805
|
+
};
|
|
806
|
+
let X = buildX();
|
|
807
|
+
// Reserve half of each end label so the natural (middle-anchored) edge
|
|
808
|
+
// ticks sit fully inside the frame — no overflow, no crushing.
|
|
809
|
+
if (showLabels && X.ticks.length) {
|
|
810
|
+
const halfFirst = Math.ceil(labelWidth(root, [X.fmt(X.ticks[0])]) / 2);
|
|
811
|
+
const halfLast = Math.ceil(labelWidth(root, [X.fmt(X.ticks[X.ticks.length - 1])]) / 2);
|
|
812
|
+
let changed = false;
|
|
813
|
+
if (PAD + halfFirst > m.left) { m.left = PAD + halfFirst; changed = true; }
|
|
814
|
+
if (PAD + halfLast > m.right) { m.right = PAD + halfLast; changed = true; }
|
|
815
|
+
if (changed) { iw = width - m.left - m.right; X = buildX(); }
|
|
820
816
|
}
|
|
817
|
+
const pos = X.pos, ticks = X.ticks, fmt = X.fmt;
|
|
821
818
|
const tipVal = v => mode === 'time' ? fmt(toTime(v)) : String(v) + (mode === 'numeric' ? unit : '');
|
|
822
819
|
|
|
823
820
|
const lane = ih / Math.max(1, tracks.length);
|
|
824
821
|
const pad = Math.min(6, lane * 0.18);
|
|
825
822
|
const plot = svg('g', { transform: `translate(${m.left},${m.top})` }, root);
|
|
826
823
|
|
|
827
|
-
// Grid lines + axis ticks (shared, at the bottom).
|
|
824
|
+
// Grid lines + axis ticks (shared, at the bottom). Labels sit naturally
|
|
825
|
+
// centred on their tick — the reserved gutters keep the ends inside.
|
|
828
826
|
ticks.forEach(tk => {
|
|
829
827
|
const xx = pos(tk);
|
|
830
828
|
if (xx == null || isNaN(xx)) return;
|
|
831
829
|
if (cfg.grid) svg('line', { x1: xx, x2: xx, y1: 0, y2: ih }, plot);
|
|
832
|
-
|
|
833
|
-
const anchor = xx <= 1 ? 'start' : xx >= iw - 1 ? 'end' : 'middle';
|
|
834
|
-
text(plot, fmt(tk), { x: xx, y: ih + 14, 'text-anchor': anchor });
|
|
830
|
+
text(plot, fmt(tk), { x: xx, y: ih + 14, 'text-anchor': 'middle' });
|
|
835
831
|
});
|
|
836
832
|
|
|
837
833
|
// Status → colour, stable across tracks. A per-segment `color` wins;
|
|
@@ -931,9 +927,8 @@
|
|
|
931
927
|
const valueById = {};
|
|
932
928
|
entries.forEach(([key, val]) => resolveRegion(key, geo).ids.forEach(id => { valueById[id] = val; }));
|
|
933
929
|
|
|
934
|
-
// Display
|
|
935
|
-
//
|
|
936
|
-
// redraw when they arrive.
|
|
930
|
+
// Display name: `_ui.map.regions` override → localized pack ($locale) →
|
|
931
|
+
// English atlas name. Async packs trigger a redraw on arrival.
|
|
937
932
|
const num2a2 = (geo.meta && geo.meta.num2a2) || {};
|
|
938
933
|
const locale = chartLocale();
|
|
939
934
|
const ui = (window.ManifestUI && window.ManifestUI.resolve) ? window.ManifestUI.resolve('map', {}) : {};
|
|
@@ -950,9 +945,8 @@
|
|
|
950
945
|
return eng;
|
|
951
946
|
};
|
|
952
947
|
|
|
953
|
-
// Author-supplied place gazetteer (config `places` or `_ui.map.cities`)
|
|
954
|
-
//
|
|
955
|
-
// source it from a third-party library in their own project.
|
|
948
|
+
// Author-supplied place gazetteer (config `places` or `_ui.map.cities`) for
|
|
949
|
+
// resolving point names → coords. No city data is bundled.
|
|
956
950
|
const placesSrc = cfg.places || ui.cities || null;
|
|
957
951
|
let places = null;
|
|
958
952
|
if (placesSrc) { places = {}; Object.keys(placesSrc).forEach(k => { places[k.toLowerCase()] = placesSrc[k]; }); }
|
|
@@ -1030,9 +1024,8 @@
|
|
|
1030
1024
|
state.el.appendChild(footer);
|
|
1031
1025
|
}
|
|
1032
1026
|
|
|
1033
|
-
// Legend is a <footer> sibling below the SVG
|
|
1034
|
-
//
|
|
1035
|
-
// a <span> with an <i> swatch carrying the series colour.
|
|
1027
|
+
// Legend is a <footer> sibling below the SVG, not an overlay, so it never
|
|
1028
|
+
// collides with axis labels. Each item: a <span> with an <i> colour swatch.
|
|
1036
1029
|
function drawLegend(state, labelsOverride) {
|
|
1037
1030
|
const cfg = state.config;
|
|
1038
1031
|
const items = labelsOverride || cfg.series.map(s => s.name).filter(Boolean);
|
|
@@ -1055,10 +1048,8 @@
|
|
|
1055
1048
|
const state = createState(el, expression, modifiers);
|
|
1056
1049
|
el._chartState = state;
|
|
1057
1050
|
el.classList.add('chart');
|
|
1058
|
-
// Reserve
|
|
1059
|
-
//
|
|
1060
|
-
// reliably trigger the lazy-load IntersectionObserver, and reserving it
|
|
1061
|
-
// also prevents a layout jump when the SVG renders.
|
|
1051
|
+
// Reserve height up front (config, default 240): a 0-height box doesn't
|
|
1052
|
+
// reliably trigger the lazy-load observer, and reserving avoids a layout jump.
|
|
1062
1053
|
const hm = expression && /height\s*:\s*(\d+)/.exec(expression);
|
|
1063
1054
|
el.style.minHeight = (hm ? +hm[1] : 240) + 'px';
|
|
1064
1055
|
observer().observe(el);
|