@wingleeio/mugen-markdown 0.4.0 → 0.4.2
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/index.cjs +76 -36
- package/dist/index.mjs +76 -36
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2050,25 +2050,42 @@ const EMA_SEED_MS = 160;
|
|
|
2050
2050
|
const MIN_FADE_MS = 120;
|
|
2051
2051
|
const MAX_FADE_MS = 400;
|
|
2052
2052
|
const MAX_VEILS = 32;
|
|
2053
|
-
function commonPrefixLength(a, b) {
|
|
2054
|
-
const n = Math.min(a.length, b.length);
|
|
2055
|
-
let i = 0;
|
|
2056
|
-
while (i < n && a.charCodeAt(i) === b.charCodeAt(i)) i++;
|
|
2057
|
-
return i;
|
|
2058
|
-
}
|
|
2059
2053
|
function inChrome(node, container) {
|
|
2060
2054
|
for (let p = node.parentElement; p != null && p !== container; p = p.parentElement) if (p.tagName === "BUTTON") return true;
|
|
2061
2055
|
return false;
|
|
2062
2056
|
}
|
|
2057
|
+
/** Whether `el` (or an ancestor up to `container`) is interactive chrome. */
|
|
2058
|
+
function elementInChrome(el, container) {
|
|
2059
|
+
for (let p = el; p != null && p !== container; p = p.parentElement) if (p.tagName === "BUTTON") return true;
|
|
2060
|
+
return false;
|
|
2061
|
+
}
|
|
2063
2062
|
function contentTextFilter(container) {
|
|
2064
2063
|
return { acceptNode: (n) => inChrome(n, container) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT };
|
|
2065
2064
|
}
|
|
2066
|
-
/** `container
|
|
2067
|
-
function
|
|
2065
|
+
/** Chrome-free text length of `container` — a full walk; used to seed/reconcile. */
|
|
2066
|
+
function chromeFreeLength(container) {
|
|
2068
2067
|
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, contentTextFilter(container));
|
|
2069
|
-
let
|
|
2070
|
-
for (let
|
|
2071
|
-
return
|
|
2068
|
+
let n = 0;
|
|
2069
|
+
for (let t = walker.nextNode(); t != null; t = walker.nextNode()) n += t.data.length;
|
|
2070
|
+
return n;
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Chrome-free text length of one node's subtree — used to fold a single
|
|
2074
|
+
* added/removed node into the running length without touching the rest of the
|
|
2075
|
+
* DOM. Buttons inside the node are skipped; a button node itself counts zero.
|
|
2076
|
+
*/
|
|
2077
|
+
function subtreeTextLength(node) {
|
|
2078
|
+
if (node.nodeType === 3) return node.data.length;
|
|
2079
|
+
if (node.nodeType !== 1) return 0;
|
|
2080
|
+
const el = node;
|
|
2081
|
+
if (el.tagName === "BUTTON") return 0;
|
|
2082
|
+
let n = 0;
|
|
2083
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { acceptNode: (t) => {
|
|
2084
|
+
for (let p = t.parentElement; p != null && p !== el; p = p.parentElement) if (p.tagName === "BUTTON") return NodeFilter.FILTER_REJECT;
|
|
2085
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
2086
|
+
} });
|
|
2087
|
+
for (let t = walker.nextNode(); t != null; t = walker.nextNode()) n += t.data.length;
|
|
2088
|
+
return n;
|
|
2072
2089
|
}
|
|
2073
2090
|
let scratch;
|
|
2074
2091
|
function scratchCtx() {
|
|
@@ -2110,14 +2127,20 @@ function effectiveBackground(el, cache) {
|
|
|
2110
2127
|
}
|
|
2111
2128
|
/**
|
|
2112
2129
|
* Paints the dissolving veil over a single content element's newly-arrived text.
|
|
2113
|
-
* Driven by a MutationObserver
|
|
2114
|
-
*
|
|
2130
|
+
* Driven by a MutationObserver that folds each change into a running length, so
|
|
2131
|
+
* a streaming tick costs O(delta) — never an O(n) walk of the whole content,
|
|
2132
|
+
* which is what made long streams lag. A rAF loop runs only while veils are
|
|
2133
|
+
* alive, then stops until the next mutation, so an idle (settled) block uses no
|
|
2134
|
+
* frames. Only the small, recent veil region is ever measured for geometry.
|
|
2115
2135
|
*/
|
|
2116
2136
|
var FadePainter = class {
|
|
2117
2137
|
content = null;
|
|
2118
2138
|
canvas = null;
|
|
2119
2139
|
ctx = null;
|
|
2120
|
-
|
|
2140
|
+
/** Chrome-free text length, tracked incrementally from mutation records. */
|
|
2141
|
+
length = 0;
|
|
2142
|
+
/** Net length change observed since the last frame folded it in. */
|
|
2143
|
+
pendingDelta = 0;
|
|
2121
2144
|
veils = [];
|
|
2122
2145
|
ema = EMA_SEED_MS;
|
|
2123
2146
|
lastAppend = 0;
|
|
@@ -2131,14 +2154,29 @@ var FadePainter = class {
|
|
|
2131
2154
|
this.content = content;
|
|
2132
2155
|
this.canvas = canvas;
|
|
2133
2156
|
this.ctx = ctx;
|
|
2134
|
-
this.
|
|
2135
|
-
this.mo = new MutationObserver(() => this.
|
|
2157
|
+
this.length = chromeFreeLength(content);
|
|
2158
|
+
this.mo = new MutationObserver((records) => this.onMutations(records));
|
|
2136
2159
|
this.mo.observe(content, {
|
|
2137
2160
|
subtree: true,
|
|
2138
2161
|
childList: true,
|
|
2139
|
-
characterData: true
|
|
2162
|
+
characterData: true,
|
|
2163
|
+
characterDataOldValue: true
|
|
2140
2164
|
});
|
|
2141
2165
|
}
|
|
2166
|
+
onMutations(records) {
|
|
2167
|
+
const content = this.content;
|
|
2168
|
+
if (content == null) return;
|
|
2169
|
+
for (const rec of records) if (rec.type === "characterData") {
|
|
2170
|
+
const node = rec.target;
|
|
2171
|
+
if (inChrome(node, content)) continue;
|
|
2172
|
+
this.pendingDelta += (node.nodeValue?.length ?? 0) - (rec.oldValue?.length ?? 0);
|
|
2173
|
+
} else if (rec.type === "childList") {
|
|
2174
|
+
if (rec.target instanceof Element && elementInChrome(rec.target, content)) continue;
|
|
2175
|
+
for (let i = 0; i < rec.addedNodes.length; i++) this.pendingDelta += subtreeTextLength(rec.addedNodes[i]);
|
|
2176
|
+
for (let i = 0; i < rec.removedNodes.length; i++) this.pendingDelta -= subtreeTextLength(rec.removedNodes[i]);
|
|
2177
|
+
}
|
|
2178
|
+
this.wake();
|
|
2179
|
+
}
|
|
2142
2180
|
destroy() {
|
|
2143
2181
|
this.mo?.disconnect();
|
|
2144
2182
|
this.mo = null;
|
|
@@ -2163,25 +2201,24 @@ var FadePainter = class {
|
|
|
2163
2201
|
return;
|
|
2164
2202
|
}
|
|
2165
2203
|
const now = performance.now();
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
...v,
|
|
2171
|
-
start: Math.min(v.start, prefix),
|
|
2172
|
-
end: Math.min(v.end, prefix)
|
|
2173
|
-
})).filter((v) => v.end > v.start);
|
|
2174
|
-
if (text.length > prefix) {
|
|
2204
|
+
if (this.pendingDelta !== 0) {
|
|
2205
|
+
const newLength = Math.max(0, this.length + this.pendingDelta);
|
|
2206
|
+
this.pendingDelta = 0;
|
|
2207
|
+
if (newLength > this.length) {
|
|
2175
2208
|
if (this.lastAppend > 0) this.ema = this.ema * .7 + Math.min(now - this.lastAppend, 1e3) * .3;
|
|
2176
2209
|
this.lastAppend = now;
|
|
2177
2210
|
this.veils.push({
|
|
2178
|
-
start:
|
|
2179
|
-
end:
|
|
2211
|
+
start: this.length,
|
|
2212
|
+
end: newLength,
|
|
2180
2213
|
t0: now
|
|
2181
2214
|
});
|
|
2182
2215
|
if (this.veils.length > MAX_VEILS) this.veils.splice(0, this.veils.length - MAX_VEILS);
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2216
|
+
} else if (newLength < this.length) this.veils = this.veils.map((v) => ({
|
|
2217
|
+
...v,
|
|
2218
|
+
start: Math.min(v.start, newLength),
|
|
2219
|
+
end: Math.min(v.end, newLength)
|
|
2220
|
+
})).filter((v) => v.end > v.start);
|
|
2221
|
+
this.length = newLength;
|
|
2185
2222
|
}
|
|
2186
2223
|
const duration = Math.min(MAX_FADE_MS, Math.max(MIN_FADE_MS, this.ema * 3));
|
|
2187
2224
|
const boost = 1 + .3 * Math.max(0, this.veils.length - 2);
|
|
@@ -2201,11 +2238,13 @@ var FadePainter = class {
|
|
|
2201
2238
|
const groups = /* @__PURE__ */ new Map();
|
|
2202
2239
|
const minStart = this.veils.reduce((m, v) => Math.min(m, v.start), Infinity);
|
|
2203
2240
|
const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, contentTextFilter(content));
|
|
2204
|
-
|
|
2205
|
-
let node = walker.
|
|
2206
|
-
|
|
2241
|
+
walker.currentNode = content;
|
|
2242
|
+
let node = walker.lastChild();
|
|
2243
|
+
let end = this.length;
|
|
2244
|
+
while (node != null && end > minStart) {
|
|
2207
2245
|
const len = node.data.length;
|
|
2208
|
-
|
|
2246
|
+
const base = end - len;
|
|
2247
|
+
if (len > 0) for (let vi = 0; vi < this.veils.length; vi++) {
|
|
2209
2248
|
const v = this.veils[vi];
|
|
2210
2249
|
const s = Math.max(v.start - base, 0);
|
|
2211
2250
|
const e = Math.min(v.end - base, len);
|
|
@@ -2229,8 +2268,8 @@ var FadePainter = class {
|
|
|
2229
2268
|
range.setEnd(node, e);
|
|
2230
2269
|
for (const r of range.getClientRects()) group.path.rect(r.left - origin.left - 1, r.top - origin.top - 1, r.width + 2, r.height + 2);
|
|
2231
2270
|
}
|
|
2232
|
-
|
|
2233
|
-
node = walker.
|
|
2271
|
+
end = base;
|
|
2272
|
+
node = walker.previousNode();
|
|
2234
2273
|
}
|
|
2235
2274
|
for (const group of groups.values()) {
|
|
2236
2275
|
ctx.globalAlpha = group.alpha;
|
|
@@ -2241,6 +2280,7 @@ var FadePainter = class {
|
|
|
2241
2280
|
}
|
|
2242
2281
|
if (this.veils.length > 0) this.raf = requestAnimationFrame(this.frame);
|
|
2243
2282
|
else {
|
|
2283
|
+
this.length = chromeFreeLength(content);
|
|
2244
2284
|
this.running = false;
|
|
2245
2285
|
this.raf = 0;
|
|
2246
2286
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -2049,25 +2049,42 @@ const EMA_SEED_MS = 160;
|
|
|
2049
2049
|
const MIN_FADE_MS = 120;
|
|
2050
2050
|
const MAX_FADE_MS = 400;
|
|
2051
2051
|
const MAX_VEILS = 32;
|
|
2052
|
-
function commonPrefixLength(a, b) {
|
|
2053
|
-
const n = Math.min(a.length, b.length);
|
|
2054
|
-
let i = 0;
|
|
2055
|
-
while (i < n && a.charCodeAt(i) === b.charCodeAt(i)) i++;
|
|
2056
|
-
return i;
|
|
2057
|
-
}
|
|
2058
2052
|
function inChrome(node, container) {
|
|
2059
2053
|
for (let p = node.parentElement; p != null && p !== container; p = p.parentElement) if (p.tagName === "BUTTON") return true;
|
|
2060
2054
|
return false;
|
|
2061
2055
|
}
|
|
2056
|
+
/** Whether `el` (or an ancestor up to `container`) is interactive chrome. */
|
|
2057
|
+
function elementInChrome(el, container) {
|
|
2058
|
+
for (let p = el; p != null && p !== container; p = p.parentElement) if (p.tagName === "BUTTON") return true;
|
|
2059
|
+
return false;
|
|
2060
|
+
}
|
|
2062
2061
|
function contentTextFilter(container) {
|
|
2063
2062
|
return { acceptNode: (n) => inChrome(n, container) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT };
|
|
2064
2063
|
}
|
|
2065
|
-
/** `container
|
|
2066
|
-
function
|
|
2064
|
+
/** Chrome-free text length of `container` — a full walk; used to seed/reconcile. */
|
|
2065
|
+
function chromeFreeLength(container) {
|
|
2067
2066
|
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, contentTextFilter(container));
|
|
2068
|
-
let
|
|
2069
|
-
for (let
|
|
2070
|
-
return
|
|
2067
|
+
let n = 0;
|
|
2068
|
+
for (let t = walker.nextNode(); t != null; t = walker.nextNode()) n += t.data.length;
|
|
2069
|
+
return n;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Chrome-free text length of one node's subtree — used to fold a single
|
|
2073
|
+
* added/removed node into the running length without touching the rest of the
|
|
2074
|
+
* DOM. Buttons inside the node are skipped; a button node itself counts zero.
|
|
2075
|
+
*/
|
|
2076
|
+
function subtreeTextLength(node) {
|
|
2077
|
+
if (node.nodeType === 3) return node.data.length;
|
|
2078
|
+
if (node.nodeType !== 1) return 0;
|
|
2079
|
+
const el = node;
|
|
2080
|
+
if (el.tagName === "BUTTON") return 0;
|
|
2081
|
+
let n = 0;
|
|
2082
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { acceptNode: (t) => {
|
|
2083
|
+
for (let p = t.parentElement; p != null && p !== el; p = p.parentElement) if (p.tagName === "BUTTON") return NodeFilter.FILTER_REJECT;
|
|
2084
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
2085
|
+
} });
|
|
2086
|
+
for (let t = walker.nextNode(); t != null; t = walker.nextNode()) n += t.data.length;
|
|
2087
|
+
return n;
|
|
2071
2088
|
}
|
|
2072
2089
|
let scratch;
|
|
2073
2090
|
function scratchCtx() {
|
|
@@ -2109,14 +2126,20 @@ function effectiveBackground(el, cache) {
|
|
|
2109
2126
|
}
|
|
2110
2127
|
/**
|
|
2111
2128
|
* Paints the dissolving veil over a single content element's newly-arrived text.
|
|
2112
|
-
* Driven by a MutationObserver
|
|
2113
|
-
*
|
|
2129
|
+
* Driven by a MutationObserver that folds each change into a running length, so
|
|
2130
|
+
* a streaming tick costs O(delta) — never an O(n) walk of the whole content,
|
|
2131
|
+
* which is what made long streams lag. A rAF loop runs only while veils are
|
|
2132
|
+
* alive, then stops until the next mutation, so an idle (settled) block uses no
|
|
2133
|
+
* frames. Only the small, recent veil region is ever measured for geometry.
|
|
2114
2134
|
*/
|
|
2115
2135
|
var FadePainter = class {
|
|
2116
2136
|
content = null;
|
|
2117
2137
|
canvas = null;
|
|
2118
2138
|
ctx = null;
|
|
2119
|
-
|
|
2139
|
+
/** Chrome-free text length, tracked incrementally from mutation records. */
|
|
2140
|
+
length = 0;
|
|
2141
|
+
/** Net length change observed since the last frame folded it in. */
|
|
2142
|
+
pendingDelta = 0;
|
|
2120
2143
|
veils = [];
|
|
2121
2144
|
ema = EMA_SEED_MS;
|
|
2122
2145
|
lastAppend = 0;
|
|
@@ -2130,14 +2153,29 @@ var FadePainter = class {
|
|
|
2130
2153
|
this.content = content;
|
|
2131
2154
|
this.canvas = canvas;
|
|
2132
2155
|
this.ctx = ctx;
|
|
2133
|
-
this.
|
|
2134
|
-
this.mo = new MutationObserver(() => this.
|
|
2156
|
+
this.length = chromeFreeLength(content);
|
|
2157
|
+
this.mo = new MutationObserver((records) => this.onMutations(records));
|
|
2135
2158
|
this.mo.observe(content, {
|
|
2136
2159
|
subtree: true,
|
|
2137
2160
|
childList: true,
|
|
2138
|
-
characterData: true
|
|
2161
|
+
characterData: true,
|
|
2162
|
+
characterDataOldValue: true
|
|
2139
2163
|
});
|
|
2140
2164
|
}
|
|
2165
|
+
onMutations(records) {
|
|
2166
|
+
const content = this.content;
|
|
2167
|
+
if (content == null) return;
|
|
2168
|
+
for (const rec of records) if (rec.type === "characterData") {
|
|
2169
|
+
const node = rec.target;
|
|
2170
|
+
if (inChrome(node, content)) continue;
|
|
2171
|
+
this.pendingDelta += (node.nodeValue?.length ?? 0) - (rec.oldValue?.length ?? 0);
|
|
2172
|
+
} else if (rec.type === "childList") {
|
|
2173
|
+
if (rec.target instanceof Element && elementInChrome(rec.target, content)) continue;
|
|
2174
|
+
for (let i = 0; i < rec.addedNodes.length; i++) this.pendingDelta += subtreeTextLength(rec.addedNodes[i]);
|
|
2175
|
+
for (let i = 0; i < rec.removedNodes.length; i++) this.pendingDelta -= subtreeTextLength(rec.removedNodes[i]);
|
|
2176
|
+
}
|
|
2177
|
+
this.wake();
|
|
2178
|
+
}
|
|
2141
2179
|
destroy() {
|
|
2142
2180
|
this.mo?.disconnect();
|
|
2143
2181
|
this.mo = null;
|
|
@@ -2162,25 +2200,24 @@ var FadePainter = class {
|
|
|
2162
2200
|
return;
|
|
2163
2201
|
}
|
|
2164
2202
|
const now = performance.now();
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
...v,
|
|
2170
|
-
start: Math.min(v.start, prefix),
|
|
2171
|
-
end: Math.min(v.end, prefix)
|
|
2172
|
-
})).filter((v) => v.end > v.start);
|
|
2173
|
-
if (text.length > prefix) {
|
|
2203
|
+
if (this.pendingDelta !== 0) {
|
|
2204
|
+
const newLength = Math.max(0, this.length + this.pendingDelta);
|
|
2205
|
+
this.pendingDelta = 0;
|
|
2206
|
+
if (newLength > this.length) {
|
|
2174
2207
|
if (this.lastAppend > 0) this.ema = this.ema * .7 + Math.min(now - this.lastAppend, 1e3) * .3;
|
|
2175
2208
|
this.lastAppend = now;
|
|
2176
2209
|
this.veils.push({
|
|
2177
|
-
start:
|
|
2178
|
-
end:
|
|
2210
|
+
start: this.length,
|
|
2211
|
+
end: newLength,
|
|
2179
2212
|
t0: now
|
|
2180
2213
|
});
|
|
2181
2214
|
if (this.veils.length > MAX_VEILS) this.veils.splice(0, this.veils.length - MAX_VEILS);
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2215
|
+
} else if (newLength < this.length) this.veils = this.veils.map((v) => ({
|
|
2216
|
+
...v,
|
|
2217
|
+
start: Math.min(v.start, newLength),
|
|
2218
|
+
end: Math.min(v.end, newLength)
|
|
2219
|
+
})).filter((v) => v.end > v.start);
|
|
2220
|
+
this.length = newLength;
|
|
2184
2221
|
}
|
|
2185
2222
|
const duration = Math.min(MAX_FADE_MS, Math.max(MIN_FADE_MS, this.ema * 3));
|
|
2186
2223
|
const boost = 1 + .3 * Math.max(0, this.veils.length - 2);
|
|
@@ -2200,11 +2237,13 @@ var FadePainter = class {
|
|
|
2200
2237
|
const groups = /* @__PURE__ */ new Map();
|
|
2201
2238
|
const minStart = this.veils.reduce((m, v) => Math.min(m, v.start), Infinity);
|
|
2202
2239
|
const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, contentTextFilter(content));
|
|
2203
|
-
|
|
2204
|
-
let node = walker.
|
|
2205
|
-
|
|
2240
|
+
walker.currentNode = content;
|
|
2241
|
+
let node = walker.lastChild();
|
|
2242
|
+
let end = this.length;
|
|
2243
|
+
while (node != null && end > minStart) {
|
|
2206
2244
|
const len = node.data.length;
|
|
2207
|
-
|
|
2245
|
+
const base = end - len;
|
|
2246
|
+
if (len > 0) for (let vi = 0; vi < this.veils.length; vi++) {
|
|
2208
2247
|
const v = this.veils[vi];
|
|
2209
2248
|
const s = Math.max(v.start - base, 0);
|
|
2210
2249
|
const e = Math.min(v.end - base, len);
|
|
@@ -2228,8 +2267,8 @@ var FadePainter = class {
|
|
|
2228
2267
|
range.setEnd(node, e);
|
|
2229
2268
|
for (const r of range.getClientRects()) group.path.rect(r.left - origin.left - 1, r.top - origin.top - 1, r.width + 2, r.height + 2);
|
|
2230
2269
|
}
|
|
2231
|
-
|
|
2232
|
-
node = walker.
|
|
2270
|
+
end = base;
|
|
2271
|
+
node = walker.previousNode();
|
|
2233
2272
|
}
|
|
2234
2273
|
for (const group of groups.values()) {
|
|
2235
2274
|
ctx.globalAlpha = group.alpha;
|
|
@@ -2240,6 +2279,7 @@ var FadePainter = class {
|
|
|
2240
2279
|
}
|
|
2241
2280
|
if (this.veils.length > 0) this.raf = requestAnimationFrame(this.frame);
|
|
2242
2281
|
else {
|
|
2282
|
+
this.length = chromeFreeLength(content);
|
|
2243
2283
|
this.running = false;
|
|
2244
2284
|
this.raf = 0;
|
|
2245
2285
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wingleeio/mugen-markdown",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Measurable markdown for mugen — incremark-parsed, rendered with mugen primitives so the virtualizer's tree walker computes exact row heights.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Wing Lee <contact@winglee.io>",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"tsdown": "^0.22.2",
|
|
66
66
|
"typescript": "^6.0.3",
|
|
67
67
|
"vitest": "^4.1.8",
|
|
68
|
-
"@wingleeio/mugen": "0.3.
|
|
68
|
+
"@wingleeio/mugen": "0.3.5"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "tsdown",
|