@glissade/scene 0.34.0-pre.1 → 0.35.0-pre.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/describe.js +14 -1
- package/dist/nodes.d.ts +26 -0
- package/dist/nodes.js +38 -5
- package/dist/type.d.ts +37 -1
- package/dist/type.js +79 -2
- package/package.json +2 -2
package/dist/describe.js
CHANGED
|
@@ -22,7 +22,7 @@ import { easings, listValueTypes } from "@glissade/core";
|
|
|
22
22
|
* never pulled onto the base embed path — a scene that never calls `describe()`
|
|
23
23
|
* pays zero bytes for it.
|
|
24
24
|
*/
|
|
25
|
-
const RAW_VERSION = "0.
|
|
25
|
+
const RAW_VERSION = "0.35.0-pre.0";
|
|
26
26
|
const PACKAGE_VERSION = RAW_VERSION.includes("GLISSADE_".concat("VERSION")) ? "0.0.0-dev" : RAW_VERSION;
|
|
27
27
|
/** Arity of a value type's numeric repr: vec2/vec2-arc → 2, number → 1; others (color/paint/path/string/boolean) carry no scalar arity. */
|
|
28
28
|
function arityOf(type) {
|
|
@@ -76,6 +76,7 @@ const CONSTRUCTION_PROP_META = {
|
|
|
76
76
|
fontWeight: { type: "number" },
|
|
77
77
|
fontStyle: { type: "'normal'|'italic'" },
|
|
78
78
|
align: { type: "'left'|'center'|'right'" },
|
|
79
|
+
box: { type: "{ valign: 'center'|'top'|'bottom', h? }" },
|
|
79
80
|
lineHeight: { type: "number" },
|
|
80
81
|
letterSpacing: { type: "number" },
|
|
81
82
|
fontVariationSettings: { type: "string" }
|
|
@@ -397,6 +398,18 @@ const HELPERS = [
|
|
|
397
398
|
import: "@glissade/scene/type",
|
|
398
399
|
usage: "splitText(text: Text | TextProps, opts?: { by?: 'word'|'line'|'grapheme', id?: string, measurer?: TextMeasurer }): { node: Group, children: Text[], parts: SplitPart[], targets(prop): string[] }"
|
|
399
400
|
},
|
|
401
|
+
{
|
|
402
|
+
name: "fitText",
|
|
403
|
+
summary: "Shrink-to-fit: set a Text's fontSize to the largest that wraps within maxW to <= maxLines / <= maxH (a build-time binary search over the measurer, like Grid/splitText). Fails loud if it can't fit even at minPx (or pass onOverflow:'clamp'). Pass { measurer } for exact fit. Tree-shaken off the base scene index.",
|
|
404
|
+
import: "@glissade/scene/type",
|
|
405
|
+
usage: "fitText(text: Text, opts: { maxW: number, maxH?, maxLines?, minPx?, onOverflow?: 'throw'|'clamp', measurer? }): Text"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: "fitTextGroup",
|
|
409
|
+
summary: "Fit several Texts to ONE shared fontSize (the largest at which every one fits its box) so a row/list of labels renders uniformly — kills the ragged 'same list, three sizes' bug. Returns the shared size. On the @glissade/scene/type subpath.",
|
|
410
|
+
import: "@glissade/scene/type",
|
|
411
|
+
usage: "fitTextGroup(texts: Text[], opts: { maxW: number, minPx?, measurer? }): number"
|
|
412
|
+
},
|
|
400
413
|
{
|
|
401
414
|
name: "Grid",
|
|
402
415
|
summary: "Build-time CSS-grid-style track resolver: position plain children into a column grid (fr/px tracks + gaps), returning a Group. Pure fan-out (no Yoga, no new target) — the goldens hold by construction. Tree-shaken off the base scene index.",
|
package/dist/nodes.d.ts
CHANGED
|
@@ -745,6 +745,19 @@ interface TextProps extends NodeProps {
|
|
|
745
745
|
fontAxes?: PropInit<FontAxes>;
|
|
746
746
|
/** Horizontal alignment about the node position; default 'left'. */
|
|
747
747
|
align?: 'left' | 'center' | 'right';
|
|
748
|
+
/**
|
|
749
|
+
* VERTICAL anchoring in a box (0.35). Text is baseline-anchored by default;
|
|
750
|
+
* `box: { valign: 'center' }` instead centers the text's real INK (ascent +
|
|
751
|
+
* descent from the measurer, single- AND multi-line) on the node position —
|
|
752
|
+
* killing the `fontSize * 0.35` fudge every boxed-text component hand-rolls.
|
|
753
|
+
* `'top'`/`'bottom'` frame the ink at the top/bottom of an `h`-tall box
|
|
754
|
+
* centered on the position (pass `h`). OMITTED ⇒ baseline (byte-identical
|
|
755
|
+
* default). Construction-only; needs the scene measurer (like wrapping).
|
|
756
|
+
*/
|
|
757
|
+
box?: {
|
|
758
|
+
valign: 'center' | 'top' | 'bottom';
|
|
759
|
+
h?: number;
|
|
760
|
+
};
|
|
748
761
|
/** Wrap width in px; unset = no wrapping (explicit \n still breaks). */
|
|
749
762
|
width?: PropInit<number>;
|
|
750
763
|
/** Line height as a multiple of fontSize; default 1.25. */
|
|
@@ -793,6 +806,11 @@ declare class Text extends Node {
|
|
|
793
806
|
* When non-empty, overrides {@link fontVariationSettings} in the FontSpec. */
|
|
794
807
|
readonly fontAxes: BindableSignal<FontAxes>;
|
|
795
808
|
readonly align: 'left' | 'center' | 'right';
|
|
809
|
+
/** vertical box anchoring (0.35); undefined = baseline-anchored (default). */
|
|
810
|
+
readonly box?: {
|
|
811
|
+
valign: 'center' | 'top' | 'bottom';
|
|
812
|
+
h?: number;
|
|
813
|
+
};
|
|
796
814
|
readonly width: BindableSignal<number>;
|
|
797
815
|
readonly lineHeight: number;
|
|
798
816
|
/** Static letter-spacing (tracking) in px; undefined = none. */
|
|
@@ -831,6 +849,14 @@ declare class Text extends Node {
|
|
|
831
849
|
x: number;
|
|
832
850
|
y: number;
|
|
833
851
|
};
|
|
852
|
+
/**
|
|
853
|
+
* 0.35 box-valign: the y offset added to the fillText line grid so the text's
|
|
854
|
+
* real INK (ascent+descent, single- OR multi-line) anchors vertically per
|
|
855
|
+
* `box.valign` — the ink-metric answer to the `fontSize * 0.35` fudge. 0 when
|
|
856
|
+
* no `box` is set, so the default draw is byte-identical. Shared by draw() and
|
|
857
|
+
* lineBoxes() so highlights/reveals follow the shifted text.
|
|
858
|
+
*/
|
|
859
|
+
private valignOffset;
|
|
834
860
|
/**
|
|
835
861
|
* The wrapped box {w, h}, measured with the scene's active measurer — the
|
|
836
862
|
* same numbers Layout flows with, public so bindings never hand-calculate
|
package/dist/nodes.js
CHANGED
|
@@ -480,7 +480,8 @@ const NODE_CONSTRUCTION_PROP_NAMES = {
|
|
|
480
480
|
"align",
|
|
481
481
|
"lineHeight",
|
|
482
482
|
"fontVariationSettings",
|
|
483
|
-
"letterSpacing"
|
|
483
|
+
"letterSpacing",
|
|
484
|
+
"box"
|
|
484
485
|
],
|
|
485
486
|
Image: ["assetId"],
|
|
486
487
|
Video: [
|
|
@@ -1904,6 +1905,8 @@ var Text = class Text extends Node {
|
|
|
1904
1905
|
* When non-empty, overrides {@link fontVariationSettings} in the FontSpec. */
|
|
1905
1906
|
fontAxes;
|
|
1906
1907
|
align;
|
|
1908
|
+
/** vertical box anchoring (0.35); undefined = baseline-anchored (default). */
|
|
1909
|
+
box;
|
|
1907
1910
|
width;
|
|
1908
1911
|
lineHeight;
|
|
1909
1912
|
/** Static letter-spacing (tracking) in px; undefined = none. */
|
|
@@ -1926,6 +1929,7 @@ var Text = class Text extends Node {
|
|
|
1926
1929
|
this.fontVariationSettings = props.fontVariationSettings;
|
|
1927
1930
|
this.fontAxes = initProp(signal({}), props.fontAxes);
|
|
1928
1931
|
this.align = props.align ?? "left";
|
|
1932
|
+
if (props.box !== void 0) this.box = props.box;
|
|
1929
1933
|
this.width = initProp(signal(0), props.width);
|
|
1930
1934
|
this.lineHeight = props.lineHeight ?? 1.25;
|
|
1931
1935
|
this.letterSpacing = props.letterSpacing;
|
|
@@ -2001,6 +2005,33 @@ var Text = class Text extends Node {
|
|
|
2001
2005
|
};
|
|
2002
2006
|
}
|
|
2003
2007
|
/**
|
|
2008
|
+
* 0.35 box-valign: the y offset added to the fillText line grid so the text's
|
|
2009
|
+
* real INK (ascent+descent, single- OR multi-line) anchors vertically per
|
|
2010
|
+
* `box.valign` — the ink-metric answer to the `fontSize * 0.35` fudge. 0 when
|
|
2011
|
+
* no `box` is set, so the default draw is byte-identical. Shared by draw() and
|
|
2012
|
+
* lineBoxes() so highlights/reveals follow the shifted text.
|
|
2013
|
+
*/
|
|
2014
|
+
valignOffset(m) {
|
|
2015
|
+
if (this.box === void 0) return 0;
|
|
2016
|
+
const font = this.fontSpec();
|
|
2017
|
+
const lines = breakLines(this.text(), font, this.width() > 0 ? this.width() : void 0, m);
|
|
2018
|
+
const step = quantize(font.size * this.lineHeight);
|
|
2019
|
+
let inkTop = Infinity;
|
|
2020
|
+
let inkBottom = -Infinity;
|
|
2021
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2022
|
+
const line = lines[i];
|
|
2023
|
+
if (!line) continue;
|
|
2024
|
+
const met = m.measureText(line, font);
|
|
2025
|
+
inkTop = Math.min(inkTop, i * step - met.ascent);
|
|
2026
|
+
inkBottom = Math.max(inkBottom, i * step + met.descent);
|
|
2027
|
+
}
|
|
2028
|
+
if (!Number.isFinite(inkTop)) return 0;
|
|
2029
|
+
const h = this.box.h ?? inkBottom - inkTop;
|
|
2030
|
+
if (this.box.valign === "center") return -(inkTop + inkBottom) / 2;
|
|
2031
|
+
if (this.box.valign === "top") return -h / 2 - inkTop;
|
|
2032
|
+
return h / 2 - inkBottom;
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2004
2035
|
* The wrapped box {w, h}, measured with the scene's active measurer — the
|
|
2005
2036
|
* same numbers Layout flows with, public so bindings never hand-calculate
|
|
2006
2037
|
* text dimensions (e.g. underline width = () => title.measuredSize().w).
|
|
@@ -2024,6 +2055,7 @@ var Text = class Text extends Node {
|
|
|
2024
2055
|
const maxWidth = this.width();
|
|
2025
2056
|
const lines = breakLines(text, font, maxWidth > 0 ? maxWidth : void 0, m);
|
|
2026
2057
|
const step = quantize(font.size * this.lineHeight);
|
|
2058
|
+
const vy = this.valignOffset(m);
|
|
2027
2059
|
const boxes = [];
|
|
2028
2060
|
for (let i = 0; i < lines.length; i++) {
|
|
2029
2061
|
const line = lines[i];
|
|
@@ -2034,7 +2066,7 @@ var Text = class Text extends Node {
|
|
|
2034
2066
|
boxes.push({
|
|
2035
2067
|
text: line,
|
|
2036
2068
|
x,
|
|
2037
|
-
y: i * step - met.ascent,
|
|
2069
|
+
y: i * step - met.ascent + vy,
|
|
2038
2070
|
w,
|
|
2039
2071
|
h: met.ascent + met.descent
|
|
2040
2072
|
});
|
|
@@ -2214,6 +2246,7 @@ var Text = class Text extends Node {
|
|
|
2214
2246
|
const maxWidth = this.width();
|
|
2215
2247
|
const lines = breakLines(text, font, maxWidth > 0 ? maxWidth : void 0, ctx.measurer);
|
|
2216
2248
|
const step = quantize(font.size * this.lineHeight);
|
|
2249
|
+
const vy = this.valignOffset(ctx.measurer);
|
|
2217
2250
|
const revealRaw = this.effectiveReveal(ctx.measurer);
|
|
2218
2251
|
const masked = Number.isFinite(revealRaw);
|
|
2219
2252
|
let remaining = masked ? Math.max(0, Math.floor(revealRaw)) : 0;
|
|
@@ -2230,7 +2263,7 @@ var Text = class Text extends Node {
|
|
|
2230
2263
|
color: this.fill()
|
|
2231
2264
|
},
|
|
2232
2265
|
x: 0,
|
|
2233
|
-
y: i * step,
|
|
2266
|
+
y: i * step + vy,
|
|
2234
2267
|
...this.align !== "left" ? { align: this.align } : {}
|
|
2235
2268
|
});
|
|
2236
2269
|
continue;
|
|
@@ -2248,7 +2281,7 @@ var Text = class Text extends Node {
|
|
|
2248
2281
|
color: this.fill()
|
|
2249
2282
|
},
|
|
2250
2283
|
x: 0,
|
|
2251
|
-
y: i * step,
|
|
2284
|
+
y: i * step + vy,
|
|
2252
2285
|
...this.align !== "left" ? { align: this.align } : {}
|
|
2253
2286
|
});
|
|
2254
2287
|
else {
|
|
@@ -2263,7 +2296,7 @@ var Text = class Text extends Node {
|
|
|
2263
2296
|
color: this.fill()
|
|
2264
2297
|
},
|
|
2265
2298
|
x: lineX,
|
|
2266
|
-
y: i * step
|
|
2299
|
+
y: i * step + vy
|
|
2267
2300
|
});
|
|
2268
2301
|
}
|
|
2269
2302
|
}
|
package/dist/type.d.ts
CHANGED
|
@@ -73,5 +73,41 @@ declare class SplitTextError extends Error {
|
|
|
73
73
|
* part geometry — see the dev-warning footgun below.
|
|
74
74
|
*/
|
|
75
75
|
declare function splitText(source: Text | TextProps, opts?: SplitTextOpts): SplitTextResult;
|
|
76
|
+
interface FitTextOpts {
|
|
77
|
+
/** wrap/measure width (px). Required — text wraps to this and never exceeds it. */
|
|
78
|
+
maxW: number;
|
|
79
|
+
/** cap the wrapped height (px). Optional — combine with maxLines. */
|
|
80
|
+
maxH?: number;
|
|
81
|
+
/** cap the number of wrapped lines. Optional. */
|
|
82
|
+
maxLines?: number;
|
|
83
|
+
/** never shrink below this (px). Default 6. Below it, fitText throws (fail loud). */
|
|
84
|
+
minPx?: number;
|
|
85
|
+
/** if the text can't fit even at minPx: 'throw' (default) or 'clamp' to minPx. */
|
|
86
|
+
onOverflow?: 'throw' | 'clamp';
|
|
87
|
+
/** measurer for exact fit — pass one (or call setTextMeasurer first), else the
|
|
88
|
+
* estimating fallback is used with a one-time dev warning (the splitText footgun). */
|
|
89
|
+
measurer?: TextMeasurer;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The largest integer-px fontSize ≤ the text's current size at which it fits the
|
|
93
|
+
* box — via a binary search over `measureWrappedText` (pure, no runtime state).
|
|
94
|
+
* The build-time answer to "shrink this to fit its container" the hand-rolled
|
|
95
|
+
* shrink loops re-implemented per component.
|
|
96
|
+
*/
|
|
97
|
+
declare function fitTextSize(text: Text, opts: FitTextOpts): number;
|
|
98
|
+
/**
|
|
99
|
+
* Shrink `text` to fit its box: sets its `fontSize` to `fitTextSize(...)` and
|
|
100
|
+
* returns it (a plain `signal.set`, so a later explicit bind still wins — the
|
|
101
|
+
* Grid()/splitText() mutate-and-return convention). Also sets `width` to maxW so
|
|
102
|
+
* the node wraps to the same box it was fitted against.
|
|
103
|
+
*/
|
|
104
|
+
declare function fitText(text: Text, opts: FitTextOpts): Text;
|
|
105
|
+
/**
|
|
106
|
+
* Fit several texts to ONE shared size — the largest px at which EVERY text fits
|
|
107
|
+
* its box — so a row/list of labels renders uniformly (kills the "same list, three
|
|
108
|
+
* different sizes" ragged-headers bug). Each text may carry its own maxW; a single
|
|
109
|
+
* `maxW` applies to all. Returns the shared size.
|
|
110
|
+
*/
|
|
111
|
+
declare function fitTextGroup(texts: readonly Text[], opts: FitTextOpts): number;
|
|
76
112
|
//#endregion
|
|
77
|
-
export { type GraphemeBox, type LineBox, SplitBy, SplitPart, SplitTextError, SplitTextOpts, SplitTextResult, type WordBox, splitText };
|
|
113
|
+
export { FitTextOpts, type GraphemeBox, type LineBox, SplitBy, SplitPart, SplitTextError, SplitTextOpts, SplitTextResult, type WordBox, fitText, fitTextGroup, fitTextSize, splitText };
|
package/dist/type.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as quantize, P as fallbackMeasurer, V as warnIfEstimating, r as Group, s as Text } from "./nodes.js";
|
|
1
|
+
import { I as measureWrappedText, L as quantize, P as fallbackMeasurer, V as warnIfEstimating, r as Group, s as Text } from "./nodes.js";
|
|
2
2
|
//#region src/type.ts
|
|
3
3
|
/**
|
|
4
4
|
* `@glissade/scene/type` — `splitText()`: build-time split-text sub-targets
|
|
@@ -131,5 +131,82 @@ function splitText(source, opts = {}) {
|
|
|
131
131
|
targets
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
+
/** Build a measurement FontSpec for `text` at a candidate size (public fields only). */
|
|
135
|
+
function fontAt(text, size) {
|
|
136
|
+
return {
|
|
137
|
+
family: text.fontFamily,
|
|
138
|
+
size,
|
|
139
|
+
weight: text.fontWeight,
|
|
140
|
+
...text.fontStyle === "italic" ? { style: "italic" } : {},
|
|
141
|
+
...text.letterSpacing !== void 0 ? { letterSpacing: text.letterSpacing } : {}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** True if `text` wraps within maxW to ≤ maxLines and ≤ maxH at this fontSize. */
|
|
145
|
+
function fits(text, size, opts, m) {
|
|
146
|
+
const font = fontAt(text, size);
|
|
147
|
+
const met = measureWrappedText(text.text(), font, opts.maxW, text.lineHeight, m);
|
|
148
|
+
if (opts.maxLines !== void 0 && met.lines.length > opts.maxLines) return false;
|
|
149
|
+
if (opts.maxH !== void 0 && met.height > opts.maxH) return false;
|
|
150
|
+
for (const line of met.lines) if (m.measureText(line, font).width > opts.maxW + .5) return false;
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The largest integer-px fontSize ≤ the text's current size at which it fits the
|
|
155
|
+
* box — via a binary search over `measureWrappedText` (pure, no runtime state).
|
|
156
|
+
* The build-time answer to "shrink this to fit its container" the hand-rolled
|
|
157
|
+
* shrink loops re-implemented per component.
|
|
158
|
+
*/
|
|
159
|
+
function fitTextSize(text, opts) {
|
|
160
|
+
const m = opts.measurer ?? text.measurerSource?.() ?? fallbackMeasurer();
|
|
161
|
+
warnIfEstimating(m, "fitText");
|
|
162
|
+
const minPx = opts.minPx ?? 6;
|
|
163
|
+
const hi = Math.max(minPx, Math.floor(text.fontSize()));
|
|
164
|
+
if (fits(text, hi, opts, m)) return hi;
|
|
165
|
+
if (!fits(text, minPx, opts, m)) {
|
|
166
|
+
if (opts.onOverflow === "clamp") return minPx;
|
|
167
|
+
throw new Error(`fitText: '${text.text().slice(0, 40)}${text.text().length > 40 ? "…" : ""}' does not fit ${opts.maxW}px${opts.maxLines !== void 0 ? ` in ${opts.maxLines} line(s)` : ""} even at minPx=${minPx} — raise maxW/maxLines/maxH, lower minPx, or pass { onOverflow: 'clamp' }`);
|
|
168
|
+
}
|
|
169
|
+
let lo = minPx;
|
|
170
|
+
let best = minPx;
|
|
171
|
+
let hiN = hi;
|
|
172
|
+
while (lo <= hiN) {
|
|
173
|
+
const mid = lo + hiN >> 1;
|
|
174
|
+
if (fits(text, mid, opts, m)) {
|
|
175
|
+
best = mid;
|
|
176
|
+
lo = mid + 1;
|
|
177
|
+
} else hiN = mid - 1;
|
|
178
|
+
}
|
|
179
|
+
return best;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Shrink `text` to fit its box: sets its `fontSize` to `fitTextSize(...)` and
|
|
183
|
+
* returns it (a plain `signal.set`, so a later explicit bind still wins — the
|
|
184
|
+
* Grid()/splitText() mutate-and-return convention). Also sets `width` to maxW so
|
|
185
|
+
* the node wraps to the same box it was fitted against.
|
|
186
|
+
*/
|
|
187
|
+
function fitText(text, opts) {
|
|
188
|
+
const size = fitTextSize(text, opts);
|
|
189
|
+
text.fontSize.set(size);
|
|
190
|
+
text.width.set(opts.maxW);
|
|
191
|
+
return text;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Fit several texts to ONE shared size — the largest px at which EVERY text fits
|
|
195
|
+
* its box — so a row/list of labels renders uniformly (kills the "same list, three
|
|
196
|
+
* different sizes" ragged-headers bug). Each text may carry its own maxW; a single
|
|
197
|
+
* `maxW` applies to all. Returns the shared size.
|
|
198
|
+
*/
|
|
199
|
+
function fitTextGroup(texts, opts) {
|
|
200
|
+
if (texts.length === 0) throw new Error("fitTextGroup needs at least one text");
|
|
201
|
+
const shared = Math.min(...texts.map((t) => fitTextSize(t, {
|
|
202
|
+
...opts,
|
|
203
|
+
onOverflow: "clamp"
|
|
204
|
+
})));
|
|
205
|
+
for (const t of texts) {
|
|
206
|
+
t.fontSize.set(shared);
|
|
207
|
+
t.width.set(opts.maxW);
|
|
208
|
+
}
|
|
209
|
+
return shared;
|
|
210
|
+
}
|
|
134
211
|
//#endregion
|
|
135
|
-
export { SplitTextError, splitText };
|
|
212
|
+
export { SplitTextError, fitText, fitTextGroup, fitTextSize, splitText };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/scene",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0-pre.0",
|
|
4
4
|
"description": "glissade scene graph: nodes, transforms, DisplayList emission. Renderer-agnostic; zero DOM/Node dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
],
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"yoga-layout": "^3.2.1",
|
|
72
|
-
"@glissade/core": "0.
|
|
72
|
+
"@glissade/core": "0.35.0-pre.0"
|
|
73
73
|
},
|
|
74
74
|
"repository": {
|
|
75
75
|
"type": "git",
|