global: snapshot

This commit is contained in:
nym21
2025-04-03 14:31:39 +02:00
parent 4c2da31bb3
commit e8c34dd59b
51 changed files with 2561 additions and 5501 deletions

View File

@@ -284,7 +284,7 @@
@font-face {
font-family: "Geist mono";
src: url("./assets/fonts/geist_mono_var_1_4_01.woff2") format("woff2");
font-weight: 300 500;
font-weight: 100 900;
font-display: block;
font-style: normal;
}
@@ -371,14 +371,6 @@
margin-bottom: 0rem;
}
header {
small {
font-weight: var(--font-weight-base);
font-size: var(--font-size-base);
line-height: var(--line-height-base);
}
}
body > &[hidden] {
display: flex !important;
}
@@ -447,7 +439,8 @@
}
}
h1 {
h1,
h2 {
text-transform: uppercase;
font-size: var(--font-size-2xl);
line-height: var(--line-height-2xl);
@@ -478,6 +471,7 @@
}
input {
text-transform: inherit;
border: 0;
width: 100%;
text-align: left;
@@ -655,7 +649,7 @@
height: 100%;
display: flex;
flex-direction: column;
gap: 0.5rem;
gap: 2rem;
padding-bottom: var(--bottom-area);
}
@@ -927,7 +921,7 @@
align-items: center;
gap: 0.5rem;
&[data-size="sm"] {
&[data-size="xs"] {
font-size: var(--font-size-xs);
line-height: var(--line-height-xs);
}
@@ -947,6 +941,10 @@
> hr {
min-width: 2rem;
fieldset[data-size="xs"] > div > & {
min-width: 1rem;
}
}
label {
@@ -957,6 +955,10 @@
> div {
display: flex;
gap: 1.5rem;
fieldset[data-size="xs"] > div > & {
gap: 1rem;
}
}
}
}
@@ -968,7 +970,7 @@
z-index: 20;
flex: 1;
margin-top: 2rem;
margin-bottom: 1.5rem;
margin-bottom: 1rem;
> legend {
text-transform: lowercase;
@@ -980,7 +982,7 @@
margin-right: var(--negative-main-padding);
padding-left: var(--main-padding);
padding-right: var(--main-padding);
padding-bottom: 1.5rem;
padding-bottom: 1rem;
overflow-x: auto;
min-width: 0;
@@ -1492,20 +1494,31 @@
<div class="shadow-bottom"></div>
<div id="resize-bar"></div>
<nav id="nav" hidden></nav>
<nav id="nav" hidden>
<h4 style="margin-top: 0.25rem">
<a href="/"
><span style="font-weight: 500">kibo</span
><span style="color: var(--gray)">.</span
><span style="color: var(--orange)">money</span></a
>
</h4>
</nav>
<search id="search" hidden>
<header>
<div>
<h3 style="display: flex; flex-direction: column">
<h3
style="
display: flex;
flex-direction: column;
text-transform: uppercase;
"
>
<input placeholder="Search..." id="search-input" />
<small id="search-small">
Focus the title or press <strong>/</strong> to search
</small>
</h3>
</div>
</header>
<ul id="search-results"></ul>
<ul id="search-results" style="text-transform: lowercase"></ul>
</search>
<footer>

View File

@@ -9,7 +9,7 @@ import {
Time,
ISeriesApi,
BaselineData,
} from "./v5.0.5/types";
} from "./v5.0.5-treeshaked/types";
import { VecId } from "../../scripts/vecid-to-indexes";
interface BaseSeriesBlueprint {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
// @ts-check
/** @import {ISeriesApi, SeriesDefinition} from './v5.0.5/types' */
/** @import {IChartApi, ISeriesApi, SeriesDefinition} from './v5.0.5-treeshaked/types' */
export default import("./v5.0.5/script.js").then((lc) => {
export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
const oklchToRGBA = createOklchToRGBA();
/**
@@ -51,11 +51,11 @@ export default import("./v5.0.5/script.js").then((lc) => {
const chart = lc.createChart(element, options);
chart.priceScale("right").applyOptions({
scaleMargins: {
top: 0.075,
bottom: 0.05,
},
minimumWidth: 78,
// scaleMargins: {
// top: 0.15,
// bottom: 0.05,
// },
minimumWidth: 80,
});
signals.createEffect(
@@ -150,7 +150,10 @@ export default import("./v5.0.5/script.js").then((lc) => {
let timeResource = /** @type {VecResource| null} */ (null);
let timeScaleSetCallback = /** @type {VoidFunction | null} */ (null);
let timeScaleSetCallback =
/** @type {((unknownTimeScaleCallback: VoidFunction) => void) | null} */ (
null
);
/**
* @param {ISeriesApi<SeriesType>} series
@@ -193,17 +196,18 @@ export default import("./v5.0.5/script.js").then((lc) => {
}
data.length -= offset;
series.setData(data);
timeScaleSetCallback?.();
if (
!timeScaleSet &&
(vecIndex === /** @satisfies {Quarterindex} */ (5) ||
vecIndex === /** @satisfies {Yearindex} */ (6) ||
vecIndex === /** @satisfies {Decadeindex} */ (7))
) {
ichart
.timeScale()
.setVisibleLogicalRange({ from: -1, to: data.length });
}
timeScaleSetCallback?.(() => {
if (
!timeScaleSet &&
(vecIndex === /** @satisfies {Quarterindex} */ (5) ||
vecIndex === /** @satisfies {Yearindex} */ (6) ||
vecIndex === /** @satisfies {Decadeindex} */ (7))
) {
ichart
?.timeScale()
.setVisibleLogicalRange({ from: -1, to: data.length });
}
});
timeScaleSet = true;
},
),
@@ -225,7 +229,7 @@ export default import("./v5.0.5/script.js").then((lc) => {
/**
* @param {Object} args
* @param {Index} args.index
* @param {VoidFunction} [args.timeScaleSetCallback]
* @param {((unknownTimeScaleCallback: VoidFunction) => void)} [args.timeScaleSetCallback]
*/
create({ index: _index, timeScaleSetCallback: _timeScaleSetCallback }) {
vecIndex = _index;
@@ -253,10 +257,19 @@ export default import("./v5.0.5/script.js").then((lc) => {
* @param {Object} args
* @param {VecId} args.vecId
* @param {string} args.name
* @param {number} [args.paneNumber]
* @param {Unit} args.unit
* @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive]
*/
addCandlestickSeries({ vecId, name, paneNumber, defaultActive }) {
addCandlestickSeries({
vecId,
name,
unit,
paneIndex: _paneIndex,
defaultActive,
}) {
const paneIndex = _paneIndex ?? 0;
if (!ichart || !timeResource) throw Error("Chart not fully set");
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
@@ -275,7 +288,7 @@ export default import("./v5.0.5/script.js").then((lc) => {
borderVisible: false,
visible: defaultActive !== false,
},
paneNumber,
paneIndex,
);
legend.add({
@@ -286,6 +299,14 @@ export default import("./v5.0.5/script.js").then((lc) => {
url: valuesResource.url,
});
createPriceScaleSelectorIfNeeded({
ichart,
paneIndex,
signals,
unit,
utils,
});
createSetDataEffect(series, valuesResource);
return series;
@@ -294,13 +315,23 @@ export default import("./v5.0.5/script.js").then((lc) => {
* @param {Object} args
* @param {VecId} args.vecId
* @param {string} args.name
* @param {Unit} args.unit
* @param {Color} [args.color]
* @param {number} [args.paneNumber]
* @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive]
*/
addLineSeries({ vecId, name, color, paneNumber, defaultActive }) {
addLineSeries({
vecId,
name,
unit,
color,
paneIndex: _paneIndex,
defaultActive,
}) {
if (!ichart || !timeResource) throw Error("Chart not fully set");
const paneIndex = _paneIndex ?? 0;
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
valuesResource.fetch();
activeResources.push(valuesResource);
@@ -315,7 +346,7 @@ export default import("./v5.0.5/script.js").then((lc) => {
priceLineVisible: false,
color: color(),
},
paneNumber,
paneIndex,
);
legend.add({
@@ -328,6 +359,21 @@ export default import("./v5.0.5/script.js").then((lc) => {
createSetDataEffect(series, valuesResource);
createPaneHeightObserver({
ichart,
paneIndex,
signals,
utils,
});
createPriceScaleSelectorIfNeeded({
ichart,
paneIndex,
signals,
unit,
utils,
});
return series;
},
/**
@@ -600,3 +646,154 @@ function createOklchToRGBA() {
};
}
}
/**
* @param {Object} args
* @param {IChartApi} args.ichart
* @param {number} args.paneIndex
* @param {Signals} args.signals
* @param {Utilities} args.utils
*/
function createPaneHeightObserver({ ichart, paneIndex, signals, utils }) {
if (!paneIndex) return;
const owner = signals.getOwner();
if (!owner) throw Error("Expect owner");
const one = "1";
const callback = () =>
setTimeout(() => {
try {
const _element = ichart?.panes().at(paneIndex)?.getHTMLElement();
if (!_element) return callback();
const element = _element;
if (element.dataset.observed === one) return;
element.dataset.observed = one;
signals.runWithOwner(owner, () => {
const height = signals.createSignal(null, {
save: {
keyPrefix: "charts",
key: `height-${paneIndex}`,
...utils.serde.optNumber,
},
});
const savedHeight = height();
if (savedHeight !== null) {
ichart.panes().at(paneIndex)?.setHeight(savedHeight);
}
let firstRun = true;
new ResizeObserver(() => {
if (firstRun && savedHeight !== null) {
firstRun = false;
} else {
const h = ichart.panes().at(paneIndex)?.getHeight();
if (h === undefined) return;
height.set(h);
}
}).observe(element);
});
} catch {
callback();
}
}, 5);
callback();
}
/**
* @param {Object} args
* @param {IChartApi} args.ichart
* @param {Unit} args.unit
* @param {number} args.paneIndex
* @param {Signals} args.signals
* @param {Utilities} args.utils
*/
function createPriceScaleSelectorIfNeeded({
ichart,
unit,
paneIndex,
signals,
utils,
}) {
const owner = signals.getOwner();
if (!owner) throw Error("Expect owner");
setTimeout(
() => {
const parent = ichart
?.panes()
.at(paneIndex)
?.getHTMLElement()
.children?.item(1)?.firstChild;
if (!parent) throw Error("Parent should exist");
const tagName = "fieldset";
if (parent.lastChild?.nodeName.toLowerCase() === tagName) {
return;
}
const choices = /**@type {const} */ (["lin", "log"]);
/** @typedef {(typeof choices)[number]} Choices */
const serializedValue = signals.createSignal(
/** @satisfies {Choices} */ (paneIndex ? "lin" : "log"),
{
save: {
...utils.serde.string,
keyPrefix: "charts",
key: `price-scale-${paneIndex}`,
},
},
);
const field = utils.dom.createHorizontalChoiceField({
title: unit,
selected: serializedValue(),
choices: choices,
id: unit,
signals,
});
field.addEventListener("change", (event) => {
// @ts-ignore
const value = event.target.value;
serializedValue.set(value);
});
const element = window.document.createElement(tagName);
element.dataset.size = "xs";
element.append(field);
const mode = signals.createMemo(() => {
switch (serializedValue()) {
case "lin":
return 0;
case "log":
return 1;
}
});
const pane = ichart?.panes().at(paneIndex);
if (!pane) throw Error("Expect pane");
signals.runWithOwner(owner, () => {
signals.createEffect(mode, (mode) => {
try {
pane.priceScale("right").applyOptions({
mode,
});
} catch {}
});
});
pane.getHTMLElement().children?.item(1)?.firstChild?.appendChild(element);
},
paneIndex ? 10 : 0,
);
}

View File

@@ -1,18 +1,15 @@
// @ts-nocheck
// src/core/error.ts
var NotReadyError = class extends Error {
};
var NotReadyError = class extends Error {};
var NoOwnerError = class extends Error {
constructor() {
super(
""
);
super("");
}
};
var ContextNotFoundError = class extends Error {
constructor() {
super(
""
);
super("");
}
};
@@ -50,14 +47,12 @@ var Owner = class {
h = defaultContext;
f = null;
constructor(signal = false) {
if (currentOwner && !signal)
currentOwner.append(this);
if (currentOwner && !signal) currentOwner.append(this);
}
append(child) {
child.l = this;
child.j = this;
if (this.g)
this.g.j = child;
if (this.g) this.g.j = child;
child.g = this.g;
this.g = child;
if (child.h !== this.h) {
@@ -68,9 +63,10 @@ var Owner = class {
}
}
dispose(self = true) {
if (this.a === STATE_DISPOSED)
return;
let head = self ? this.j || this.l : this, current = this.g, next = null;
if (this.a === STATE_DISPOSED) return;
let head = self ? this.j || this.l : this,
current = this.g,
next = null;
while (current && current.l === this) {
current.dispose(true);
current.n();
@@ -78,16 +74,12 @@ var Owner = class {
current.g = null;
current = next;
}
if (self)
this.n();
if (current)
current.j = !self ? this : this.j;
if (head)
head.g = current;
if (self) this.n();
if (current) current.j = !self ? this : this.j;
if (head) head.g = current;
}
n() {
if (this.j)
this.j.g = null;
if (this.j) this.j.g = null;
this.l = null;
this.j = null;
this.h = defaultContext;
@@ -96,8 +88,7 @@ var Owner = class {
this.emptyDisposal();
}
emptyDisposal() {
if (!this.e)
return;
if (!this.e) return;
if (Array.isArray(this.e)) {
for (let i = 0; i < this.e.length; i++) {
const callable = this.e[i];
@@ -109,9 +100,9 @@ var Owner = class {
this.e = null;
}
handleError(error) {
if (!this.f)
throw error;
let i = 0, len = this.f.length;
if (!this.f) throw error;
let i = 0,
len = this.f.length;
for (i = 0; i < len; i++) {
try {
this.f[i](error);
@@ -120,8 +111,7 @@ var Owner = class {
error = e;
}
}
if (i === len)
throw error;
if (i === len) throw error;
}
};
function createContext(defaultValue, description) {
@@ -131,7 +121,9 @@ function getContext(context, owner = currentOwner) {
if (!owner) {
throw new NoOwnerError();
}
const value = hasContext(context, owner) ? owner.h[context.id] : context.defaultValue;
const value = hasContext(context, owner)
? owner.h[context.id]
: context.defaultValue;
if (isUndefined(value)) {
throw new ContextNotFoundError();
}
@@ -143,15 +135,14 @@ function setContext(context, value, owner = currentOwner) {
}
owner.h = {
...owner.h,
[context.id]: isUndefined(value) ? context.defaultValue : value
[context.id]: isUndefined(value) ? context.defaultValue : value,
};
}
function hasContext(context, owner = currentOwner) {
return !isUndefined(owner?.h[context.id]);
}
function onCleanup(disposable) {
if (!currentOwner)
return;
if (!currentOwner) return;
const node = currentOwner;
if (!node.e) {
node.e = disposable;
@@ -176,12 +167,10 @@ var Computations = [];
var RenderEffects = [];
var Effects = [];
function flushSync() {
if (!runningScheduled)
runScheduled();
if (!runningScheduled) runScheduled();
}
function flushQueue() {
if (scheduled)
return;
if (scheduled) return;
scheduled = true;
queueMicrotask(runScheduled);
}
@@ -193,8 +182,7 @@ function runTop(node) {
}
}
for (let i = ancestors.length - 1; i >= 0; i--) {
if (ancestors[i].a !== STATE_DISPOSED)
ancestors[i].m();
if (ancestors[i].a !== STATE_DISPOSED) ancestors[i].m();
}
}
function runScheduled() {
@@ -222,8 +210,7 @@ function runScheduled() {
}
function runPureQueue(queue) {
for (let i = 0; i < queue.length; i++) {
if (queue[i].a !== STATE_CLEAN)
runTop(queue[i]);
if (queue[i].a !== STATE_CLEAN) runTop(queue[i]);
}
}
function runEffectQueue(queue) {
@@ -275,16 +262,12 @@ var Computation2 = class extends Owner {
this.s = compute2;
this.a = compute2 ? STATE_DIRTY : STATE_CLEAN;
this.d = initialValue;
if (options?.equals !== void 0)
this.t = options.equals;
if (options?.unobserved)
this.x = options?.unobserved;
if (options?.equals !== void 0) this.t = options.equals;
if (options?.unobserved) this.x = options?.unobserved;
}
y() {
if (this.s)
this.m();
if (!this.b || this.b.length)
track(this);
if (this.s) this.m();
if (!this.b || this.b.length) track(this);
newFlags |= this.i & ~currentMask;
if (this.i & ERROR_BIT) {
throw this.d;
@@ -337,11 +320,14 @@ var Computation2 = class extends Owner {
}
/** Update the computation with a new value. */
write(value, flags = 0, raw = false) {
const newValue = !raw && typeof value === "function" ? value(this.d) : value;
const valueChanged = newValue !== UNCHANGED && (!!(flags & ERROR_BIT) || this.t === false || !this.t(this.d, newValue));
if (valueChanged)
this.d = newValue;
const changedFlagsMask = this.i ^ flags, changedFlags = changedFlagsMask & flags;
const newValue =
!raw && typeof value === "function" ? value(this.d) : value;
const valueChanged =
newValue !== UNCHANGED &&
(!!(flags & ERROR_BIT) || this.t === false || !this.t(this.d, newValue));
if (valueChanged) this.d = newValue;
const changedFlagsMask = this.i ^ flags,
changedFlags = changedFlagsMask & flags;
this.i = flags;
this.w = clock + 1;
if (this.c) {
@@ -359,8 +345,7 @@ var Computation2 = class extends Owner {
* Set the current node's state, and recursively mark all of this node's observers as STATE_CHECK
*/
k(state) {
if (this.a >= state)
return;
if (this.a >= state) return;
this.a = state;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
@@ -375,17 +360,16 @@ var Computation2 = class extends Owner {
* @param newFlags The source's new flags, masked to just the changed ones.
*/
z(mask, newFlags2) {
if (this.a >= STATE_DIRTY)
return;
if (this.a >= STATE_DIRTY) return;
if (mask & this.p) {
this.k(STATE_DIRTY);
return;
}
if (this.a >= STATE_CHECK)
return;
if (this.a >= STATE_CHECK) return;
const prevFlags = this.i & mask;
const deltaFlags = prevFlags ^ newFlags2;
if (newFlags2 === prevFlags) ; else if (deltaFlags & prevFlags & mask) {
if (newFlags2 === prevFlags);
else if (deltaFlags & prevFlags & mask) {
this.k(STATE_CHECK);
} else {
this.i ^= deltaFlags;
@@ -434,10 +418,8 @@ var Computation2 = class extends Owner {
* Remove ourselves from the owner graph and the computation graph
*/
n() {
if (this.a === STATE_DISPOSED)
return;
if (this.b)
removeSourceObservers(this, 0);
if (this.a === STATE_DISPOSED) return;
if (this.b) removeSourceObservers(this, 0);
super.n();
}
};
@@ -451,7 +433,7 @@ function loadingState(node) {
node.m();
return !!(node.i & LOADING_BIT);
},
options
options,
);
computation.p = ERROR_BIT | LOADING_BIT;
setOwner(prevOwner);
@@ -467,7 +449,7 @@ function errorState(node) {
node.m();
return !!(node.i & ERROR_BIT);
},
options
options,
);
computation.p = ERROR_BIT;
setOwner(prevOwner);
@@ -475,10 +457,13 @@ function errorState(node) {
}
function track(computation) {
if (currentObserver) {
if (!newSources && currentObserver.b && currentObserver.b[newSourcesIndex] === computation) {
if (
!newSources &&
currentObserver.b &&
currentObserver.b[newSourcesIndex] === computation
) {
newSourcesIndex++;
} else if (!newSources)
newSources = [computation];
} else if (!newSources) newSources = [computation];
else if (computation !== newSources[newSources.length - 1]) {
newSources.push(computation);
}
@@ -488,7 +473,9 @@ function track(computation) {
}
}
function update(node) {
const prevSources = newSources, prevSourcesIndex = newSourcesIndex, prevFlags = newFlags;
const prevSources = newSources,
prevSourcesIndex = newSourcesIndex,
prevFlags = newFlags;
newSources = null;
newSourcesIndex = 0;
newFlags = 0;
@@ -505,8 +492,7 @@ function update(node) {
}
} finally {
if (newSources) {
if (node.b)
removeSourceObservers(node, newSourcesIndex);
if (node.b) removeSourceObservers(node, newSourcesIndex);
if (node.b && newSourcesIndex > 0) {
node.b.length = newSourcesIndex + newSources.length;
for (let i = 0; i < newSources.length; i++) {
@@ -518,10 +504,8 @@ function update(node) {
let source;
for (let i = newSourcesIndex; i < node.b.length; i++) {
source = node.b[i];
if (!source.c)
source.c = [node];
else
source.c.push(node);
if (!source.c) source.c = [node];
else source.c.push(node);
}
} else if (node.b && newSourcesIndex < node.b.length) {
removeSourceObservers(node, newSourcesIndex);
@@ -542,8 +526,7 @@ function removeSourceObservers(node, index) {
swap = source.c.indexOf(node);
source.c[swap] = source.c[source.c.length - 1];
source.c.pop();
if (!source.c.length)
source.x?.();
if (!source.c.length) source.x?.();
}
}
}
@@ -551,8 +534,7 @@ function isEqual(a, b) {
return a === b;
}
function untrack(fn) {
if (currentObserver === null)
return fn();
if (currentObserver === null) return fn();
return compute(getOwner(), fn, null);
}
function hasUpdated(fn) {
@@ -585,7 +567,9 @@ function latest(fn) {
}
}
function compute(owner, compute2, observer) {
const prevOwner = setOwner(owner), prevObserver = currentObserver, prevMask = currentMask;
const prevOwner = setOwner(owner),
prevObserver = currentObserver,
prevMask = currentMask;
currentObserver = observer;
currentMask = observer?.p ?? DEFAULT_FLAGS;
try {
@@ -603,8 +587,7 @@ var EagerComputation = class extends Computation2 {
Computations.push(this);
}
k(state) {
if (this.a >= state)
return;
if (this.a >= state) return;
if (this.a === STATE_CLEAN) {
Computations.push(this);
flushQueue();
@@ -624,8 +607,7 @@ var BaseEffect = class extends Computation2 {
this.o = initialValue;
}
write(value) {
if (value === UNCHANGED)
return this.d;
if (value === UNCHANGED) return this.d;
this.d = value;
this.q = true;
return value;
@@ -646,8 +628,7 @@ var Effect = class extends BaseEffect {
flushQueue();
}
k(state) {
if (this.a >= state)
return;
if (this.a >= state) return;
if (this.a === STATE_CLEAN) {
Effects.push(this);
flushQueue();
@@ -662,8 +643,7 @@ var RenderEffect = class extends BaseEffect {
RenderEffects.push(this);
}
k(state) {
if (this.a >= state)
return;
if (this.a >= state) return;
if (this.a === STATE_CLEAN) {
RenderEffects.push(this);
flushQueue();
@@ -679,7 +659,7 @@ function createSignal(first, second, third) {
const node2 = new Computation2(
first(p ? untrack(p[0]) : second),
null,
third
third,
);
return [node2.read.bind(node2), node2.write.bind(node2)];
});
@@ -691,7 +671,7 @@ function createSignal(first, second, third) {
function createAsync(fn, initial, options) {
const lhs = new EagerComputation(
{
d: initial
d: initial,
},
(p) => {
const value = p?.d;
@@ -703,7 +683,7 @@ function createAsync(fn, initial, options) {
wait() {
return source;
},
d: source
d: source,
};
}
const signal = new Computation2(value, null, options);
@@ -715,16 +695,15 @@ function createAsync(fn, initial, options) {
},
(error) => {
signal.write(error, ERROR_BIT);
}
},
);
} else {
let abort = false;
onCleanup(() => abort = true);
onCleanup(() => (abort = true));
(async () => {
try {
for await (let value2 of source) {
if (abort)
return;
if (abort) return;
signal.write(value2, 0);
}
} catch (error) {
@@ -733,7 +712,7 @@ function createAsync(fn, initial, options) {
})();
}
return signal;
}
},
);
return () => lhs.wait().wait();
}
@@ -743,31 +722,24 @@ function createMemo(compute2, initialValue, options) {
return () => {
if (node) {
value = node.wait();
if (!node.b?.length)
node = void 0;
if (!node.b?.length) node = void 0;
}
return value;
};
}
function createEffect(compute2, effect, initialValue, options) {
void new Effect(
initialValue,
compute2,
effect,
void 0
);
void new Effect(initialValue, compute2, effect, void 0);
}
function createRenderEffect(compute2, effect, initialValue, options) {
void new RenderEffect(
initialValue,
compute2,
effect,
void 0
);
void new RenderEffect(initialValue, compute2, effect, void 0);
}
function createRoot(init) {
const owner = new Owner();
return compute(owner, !init.length ? init : () => init(() => owner.dispose()), null);
return compute(
owner,
!init.length ? init : () => init(() => owner.dispose()),
null,
);
}
function runWithOwner(owner, run) {
try {
@@ -787,4 +759,31 @@ function catchError(fn, handler) {
}
}
export { Computation2 as Computation, ContextNotFoundError, NoOwnerError, NotReadyError, Owner, catchError, createAsync, createContext, createEffect, createMemo, createRenderEffect, createRoot, createSignal, flushSync, getContext, getObserver, getOwner, hasContext, hasUpdated, isEqual, isPending, latest, onCleanup, runWithOwner, setContext, untrack };
export {
Computation2 as Computation,
ContextNotFoundError,
NoOwnerError,
NotReadyError,
Owner,
catchError,
createAsync,
createContext,
createEffect,
createMemo,
createRenderEffect,
createRoot,
createSignal,
flushSync,
getContext,
getObserver,
getOwner,
hasContext,
hasUpdated,
isEqual,
isPending,
latest,
onCleanup,
runWithOwner,
setContext,
untrack,
};

View File

@@ -0,0 +1,727 @@
// @ts-nocheck
// src/core/error.ts
var NotReadyError = class extends Error {};
var EffectError = class extends Error {
constructor(effect, cause) {
super("");
this.cause = cause;
}
};
// src/core/constants.ts
var STATE_CLEAN = 0;
var STATE_CHECK = 1;
var STATE_DIRTY = 2;
var STATE_DISPOSED = 3;
var EFFECT_PURE = 0;
var EFFECT_RENDER = 1;
var EFFECT_USER = 2;
// src/core/scheduler.ts
var clock = 0;
function getClock() {
return clock;
}
function incrementClock() {
clock++;
}
var scheduled = false;
function schedule() {
if (scheduled) return;
scheduled = true;
if (!globalQueue.w) queueMicrotask(flushSync);
}
var Queue = class {
w = false;
l = [[], [], []];
u = [];
created = clock;
enqueue(type, node) {
this.l[0].push(node);
if (type) this.l[type].push(node);
schedule();
}
run(type) {
if (this.l[type].length) {
if (type === EFFECT_PURE) {
runPureQueue(this.l[type]);
this.l[type] = [];
} else {
const effects = this.l[type];
this.l[type] = [];
runEffectQueue(effects);
}
}
let rerun = false;
for (let i = 0; i < this.u.length; i++) {
rerun = this.u[i].run(type) || rerun;
}
if (type === EFFECT_PURE && this.l[type].length) return true;
}
flush() {
if (this.w) return;
this.w = true;
try {
while (this.run(EFFECT_PURE)) {}
incrementClock();
scheduled = false;
this.run(EFFECT_RENDER);
this.run(EFFECT_USER);
} finally {
this.w = false;
}
}
addChild(child) {
this.u.push(child);
}
removeChild(child) {
const index = this.u.indexOf(child);
if (index >= 0) this.u.splice(index, 1);
}
};
var globalQueue = new Queue();
function flushSync() {
while (scheduled) {
globalQueue.flush();
}
}
function runTop(node) {
const ancestors = [];
for (let current = node; current !== null; current = current.m) {
if (current.a !== STATE_CLEAN) {
ancestors.push(current);
}
}
for (let i = ancestors.length - 1; i >= 0; i--) {
if (ancestors[i].a !== STATE_DISPOSED) ancestors[i].p();
}
}
function runPureQueue(queue) {
for (let i = 0; i < queue.length; i++) {
if (queue[i].a !== STATE_CLEAN) runTop(queue[i]);
}
}
function runEffectQueue(queue) {
for (let i = 0; i < queue.length; i++) queue[i].K();
}
// src/core/owner.ts
var currentOwner = null;
var defaultContext = {};
function getOwner() {
return currentOwner;
}
function setOwner(owner) {
const out = currentOwner;
currentOwner = owner;
return out;
}
var Owner = class {
// We flatten the owner tree into a linked list so that we don't need a pointer to .firstChild
// However, the children are actually added in reverse creation order
// See comment at the top of the file for an example of the _nextSibling traversal
m = null;
h = null;
n = null;
a = STATE_CLEAN;
g = null;
i = defaultContext;
j = null;
f = globalQueue;
constructor(signal = false) {
if (currentOwner && !signal) currentOwner.append(this);
}
append(child) {
child.m = this;
child.n = this;
if (this.h) this.h.n = child;
child.h = this.h;
this.h = child;
if (child.i !== this.i) {
child.i = { ...this.i, ...child.i };
}
if (this.j) {
child.j = !child.j ? this.j : [...child.j, ...this.j];
}
if (this.f) child.f = this.f;
}
dispose(self = true) {
if (this.a === STATE_DISPOSED) return;
let head = self ? this.n || this.m : this,
current = this.h,
next = null;
while (current && current.m === this) {
current.dispose(true);
current.q();
next = current.h;
current.h = null;
current = next;
}
if (self) this.q();
if (current) current.n = !self ? this : this.n;
if (head) head.h = current;
}
q() {
if (this.n) this.n.h = null;
this.m = null;
this.n = null;
this.i = defaultContext;
this.j = null;
this.a = STATE_DISPOSED;
this.emptyDisposal();
}
emptyDisposal() {
if (!this.g) return;
if (Array.isArray(this.g)) {
for (let i = 0; i < this.g.length; i++) {
const callable = this.g[i];
callable.call(callable);
}
} else {
this.g.call(this.g);
}
this.g = null;
}
handleError(error) {
if (!this.j) throw error;
let i = 0,
len = this.j.length;
for (i = 0; i < len; i++) {
try {
this.j[i](error, this);
break;
} catch (e) {
error = e;
}
}
if (i === len) throw error;
}
};
function onCleanup(fn) {
if (!currentOwner) return fn;
const node = currentOwner;
if (!node.g) {
node.g = fn;
} else if (Array.isArray(node.g)) {
node.g.push(fn);
} else {
node.g = [node.g, fn];
}
return fn;
}
// src/core/flags.ts
var ERROR_OFFSET = 0;
var ERROR_BIT = 1 << ERROR_OFFSET;
var LOADING_OFFSET = 1;
var LOADING_BIT = 1 << LOADING_OFFSET;
var UNINITIALIZED_OFFSET = 2;
var UNINITIALIZED_BIT = 1 << UNINITIALIZED_OFFSET;
var DEFAULT_FLAGS = ERROR_BIT;
// src/core/core.ts
var currentObserver = null;
var currentMask = DEFAULT_FLAGS;
var newSources = null;
var newSourcesIndex = 0;
var newFlags = 0;
var notStale = false;
var UNCHANGED = Symbol(0);
var Computation = class extends Owner {
b = null;
c = null;
e;
B;
r;
// Used in __DEV__ mode, hopefully removed in production
O;
// Using false is an optimization as an alternative to _equals: () => false
// which could enable more efficient DIRTY notification
C = isEqual;
L;
/** Whether the computation is an error or has ancestors that are unresolved */
d = 0;
/** Which flags raised by sources are handled, vs. being passed through. */
D = DEFAULT_FLAGS;
E = null;
s = -1;
x = false;
constructor(initialValue, compute2, options) {
super(compute2 === null);
this.r = compute2;
this.a = compute2 ? STATE_DIRTY : STATE_CLEAN;
this.d = compute2 && initialValue === void 0 ? UNINITIALIZED_BIT : 0;
this.e = initialValue;
if (options?.equals !== void 0) this.C = options.equals;
if (options?.unobserved) this.L = options?.unobserved;
}
M() {
if (this.r) {
if (this.d & ERROR_BIT && this.s <= getClock()) update(this);
else this.p();
}
if (!this.r || this.b?.length) track(this);
newFlags |= this.d & ~currentMask;
if (this.d & ERROR_BIT) {
throw this.B;
} else {
return this.e;
}
}
/**
* Return the current value of this computation
* Automatically re-executes the surrounding computation when the value changes
*/
read() {
return this.M();
}
/**
* Return the current value of this computation
* Automatically re-executes the surrounding computation when the value changes
*
* If the computation has any unresolved ancestors, this function waits for the value to resolve
* before continuing
*/
wait() {
if (this.r && this.d & ERROR_BIT && this.s <= getClock()) {
update(this);
}
if ((notStale || this.d & UNINITIALIZED_BIT) && this.loading()) {
throw new NotReadyError();
}
return this.M();
}
/**
* Return true if the computation is the value is dependent on an unresolved promise
* Triggers re-execution of the computation when the loading state changes
*
* This is useful especially when effects want to re-execute when a computation's
* loading state changes
*/
loading() {
if (this.E === null) {
this.E = loadingState(this);
}
return this.E.read();
}
/** Update the computation with a new value. */
write(value, flags = 0, raw = false) {
const newValue =
!raw && typeof value === "function" ? value(this.e) : value;
const valueChanged =
newValue !== UNCHANGED &&
(!!(this.d & UNINITIALIZED_BIT) ||
this.C === false ||
!this.C(this.e, newValue));
if (valueChanged) {
this.e = newValue;
this.B = void 0;
}
const changedFlagsMask = this.d ^ flags,
changedFlags = changedFlagsMask & flags;
this.d = flags;
this.s = getClock() + 1;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
if (valueChanged) {
this.c[i].k(STATE_DIRTY);
} else if (changedFlagsMask) {
this.c[i].N(changedFlagsMask, changedFlags);
}
}
}
return this.e;
}
/**
* Set the current node's state, and recursively mark all of this node's observers as STATE_CHECK
*/
k(state, skipQueue) {
if (this.a >= state && !this.x) return;
this.x = !!skipQueue;
this.a = state;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
this.c[i].k(STATE_CHECK, skipQueue);
}
}
}
/**
* Notify the computation that one of its sources has changed flags.
*
* @param mask A bitmask for which flag(s) were changed.
* @param newFlags The source's new flags, masked to just the changed ones.
*/
N(mask, newFlags2) {
if (this.a >= STATE_DIRTY) return;
if (mask & this.D) {
this.k(STATE_DIRTY);
return;
}
if (this.a >= STATE_CHECK) return;
const prevFlags = this.d & mask;
const deltaFlags = prevFlags ^ newFlags2;
if (newFlags2 === prevFlags);
else if (deltaFlags & prevFlags & mask) {
this.k(STATE_CHECK);
} else {
this.d ^= deltaFlags;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
this.c[i].N(mask, newFlags2);
}
}
}
}
F(error) {
this.B = error;
this.write(
UNCHANGED,
(this.d & ~LOADING_BIT) | ERROR_BIT | UNINITIALIZED_BIT,
);
}
/**
* This is the core part of the reactivity system, which makes sure that the values are updated
* before they are read. We've also adapted it to return the loading state of the computation,
* so that we can propagate that to the computation's observers.
*
* This function will ensure that the value and states we read from the computation are up to date
*/
p() {
if (this.a === STATE_DISPOSED) {
throw new Error("Tried to read a disposed computation");
}
if (this.a === STATE_CLEAN) {
return;
}
let observerFlags = 0;
if (this.a === STATE_CHECK) {
for (let i = 0; i < this.b.length; i++) {
this.b[i].p();
observerFlags |= this.b[i].d;
if (this.a === STATE_DIRTY) {
break;
}
}
}
if (this.a === STATE_DIRTY) {
update(this);
} else {
this.write(UNCHANGED, observerFlags);
this.a = STATE_CLEAN;
}
}
/**
* Remove ourselves from the owner graph and the computation graph
*/
q() {
if (this.a === STATE_DISPOSED) return;
if (this.b) removeSourceObservers(this, 0);
super.q();
}
};
function loadingState(node) {
const prevOwner = setOwner(node.m);
const options = void 0;
const computation = new Computation(
void 0,
() => {
track(node);
node.p();
return !!(node.d & LOADING_BIT);
},
options,
);
computation.D = ERROR_BIT | LOADING_BIT;
setOwner(prevOwner);
return computation;
}
function track(computation) {
if (currentObserver) {
if (
!newSources &&
currentObserver.b &&
currentObserver.b[newSourcesIndex] === computation
) {
newSourcesIndex++;
} else if (!newSources) newSources = [computation];
else if (computation !== newSources[newSources.length - 1]) {
newSources.push(computation);
}
}
}
function update(node) {
const prevSources = newSources,
prevSourcesIndex = newSourcesIndex,
prevFlags = newFlags;
newSources = null;
newSourcesIndex = 0;
newFlags = 0;
try {
node.dispose(false);
node.emptyDisposal();
const result = compute(node, node.r, node);
node.write(result, newFlags, true);
} catch (error) {
if (error instanceof NotReadyError) {
node.write(
UNCHANGED,
newFlags | LOADING_BIT | (node.d & UNINITIALIZED_BIT),
);
} else {
node.F(error);
}
} finally {
if (newSources) {
if (node.b) removeSourceObservers(node, newSourcesIndex);
if (node.b && newSourcesIndex > 0) {
node.b.length = newSourcesIndex + newSources.length;
for (let i = 0; i < newSources.length; i++) {
node.b[newSourcesIndex + i] = newSources[i];
}
} else {
node.b = newSources;
}
let source;
for (let i = newSourcesIndex; i < node.b.length; i++) {
source = node.b[i];
if (!source.c) source.c = [node];
else source.c.push(node);
}
} else if (node.b && newSourcesIndex < node.b.length) {
removeSourceObservers(node, newSourcesIndex);
node.b.length = newSourcesIndex;
}
newSources = prevSources;
newSourcesIndex = prevSourcesIndex;
newFlags = prevFlags;
node.s = getClock() + 1;
node.a = STATE_CLEAN;
}
}
function removeSourceObservers(node, index) {
let source;
let swap;
for (let i = index; i < node.b.length; i++) {
source = node.b[i];
if (source.c) {
swap = source.c.indexOf(node);
source.c[swap] = source.c[source.c.length - 1];
source.c.pop();
if (!source.c.length) source.L?.();
}
}
}
function isEqual(a, b) {
return a === b;
}
function untrack(fn) {
if (currentObserver === null) return fn();
return compute(getOwner(), fn, null);
}
function latest(fn, fallback) {
const argLength = arguments.length;
const prevFlags = newFlags;
const prevNotStale = notStale;
notStale = false;
try {
return fn();
} catch (err) {
if (argLength > 1 && err instanceof NotReadyError) return fallback;
throw err;
} finally {
newFlags = prevFlags;
notStale = prevNotStale;
}
}
function compute(owner, fn, observer) {
const prevOwner = setOwner(owner),
prevObserver = currentObserver,
prevMask = currentMask,
prevNotStale = notStale;
currentObserver = observer;
currentMask = observer?.D ?? DEFAULT_FLAGS;
notStale = true;
try {
return fn(observer ? observer.e : void 0);
} finally {
setOwner(prevOwner);
currentObserver = prevObserver;
currentMask = prevMask;
notStale = prevNotStale;
}
}
// src/core/effect.ts
var Effect = class extends Computation {
y;
z;
t;
G = false;
A;
o;
constructor(initialValue, compute2, effect, error, options) {
super(initialValue, compute2, options);
this.y = effect;
this.z = error;
this.A = initialValue;
this.o = options?.render ? EFFECT_RENDER : EFFECT_USER;
if (this.o === EFFECT_RENDER) {
this.r = (p) =>
getClock() > this.f.created && !(this.d & ERROR_BIT)
? latest(() => compute2(p))
: compute2(p);
}
this.p();
!options?.defer &&
(this.o === EFFECT_USER ? this.f.enqueue(this.o, this) : this.K());
}
write(value, flags = 0) {
if (this.a == STATE_DIRTY) {
const currentFlags = this.d;
this.d = flags;
if (
this.o === EFFECT_RENDER &&
(flags & LOADING_BIT) !== (currentFlags & LOADING_BIT)
) {
this.f.H?.(this);
}
}
if (value === UNCHANGED) return this.e;
this.e = value;
this.G = true;
return value;
}
k(state, skipQueue) {
if (this.a >= state || skipQueue) return;
if (this.a === STATE_CLEAN) this.f.enqueue(this.o, this);
this.a = state;
}
F(error) {
this.t?.();
if (this.d & LOADING_BIT) {
this.f.H?.(this);
}
this.d = ERROR_BIT;
if (this.o === EFFECT_USER) {
try {
return this.z
? (this.t = this.z(error))
: console.error(new EffectError(this.y, error));
} catch (e) {
error = e;
}
}
this.handleError(error);
}
q() {
if (this.a === STATE_DISPOSED) return;
this.y = void 0;
this.A = void 0;
this.z = void 0;
this.t?.();
this.t = void 0;
super.q();
}
K() {
if (this.G && this.a !== STATE_DISPOSED) {
this.t?.();
try {
this.t = this.y(this.e, this.A);
} catch (e) {
this.handleError(e);
} finally {
this.A = this.e;
this.G = false;
}
}
}
};
var EagerComputation = class extends Computation {
constructor(initialValue, compute2, options) {
super(initialValue, compute2, options);
!options?.defer && this.p();
}
k(state, skipQueue) {
if (this.a >= state && !this.x) return;
if (this.a === STATE_CLEAN && !skipQueue) this.f.enqueue(EFFECT_PURE, this);
super.k(state, skipQueue);
}
};
// src/signals.ts
function createSignal(first, second, third) {
if (typeof first === "function") {
const memo = createMemo((p) => {
const node2 = new Computation(
first(p ? untrack(p[0]) : second),
null,
third,
);
return [node2.read.bind(node2), node2.write.bind(node2)];
});
return [() => memo()[0](), (value) => memo()[1](value)];
}
const node = new Computation(first, null, second);
return [node.read.bind(node), node.write.bind(node)];
}
function createMemo(compute2, value, options) {
let node = new Computation(value, compute2, options);
let resolvedValue;
return () => {
if (node) {
resolvedValue = node.wait();
if (!node.b?.length && node.h?.m !== node) {
node.dispose();
node = void 0;
} else if (!node.m && !node.c?.length) {
node.dispose();
node.a = STATE_DIRTY;
}
}
return resolvedValue;
};
}
function createEffect(compute2, effect, error, value, options) {
void new Effect(value, compute2, effect, error, options);
}
function createRoot(init) {
const owner = new Owner();
return compute(
owner,
!init.length ? init : () => init(() => owner.dispose()),
null,
);
}
function runWithOwner(owner, run) {
return compute(owner, run, null);
}
function resolve(fn) {
return new Promise((res, rej) => {
createRoot((dispose) => {
new EagerComputation(void 0, () => {
try {
res(fn());
} catch (err) {
if (err instanceof NotReadyError) throw err;
rej(err);
}
dispose();
});
});
});
}
export {
Owner,
createEffect,
createMemo,
createRoot,
createSignal,
flushSync,
getOwner,
onCleanup,
resolve,
runWithOwner,
untrack,
};

View File

@@ -0,0 +1,14 @@
/**
* See https://dev.to/modderme123/super-charging-fine-grained-reactive-performance-47ph
* State clean corresponds to a node where all the sources are fully up to date
* State check corresponds to a node where some sources (including grandparents) may have changed
* State dirty corresponds to a node where the direct parents of a node has changed
*/
export declare const STATE_CLEAN = 0;
export declare const STATE_CHECK = 1;
export declare const STATE_DIRTY = 2;
export declare const STATE_DISPOSED = 3;
export declare const EFFECT_PURE = 0;
export declare const EFFECT_RENDER = 1;
export declare const EFFECT_USER = 2;
export declare const SUPPORTS_PROXY: boolean;

View File

@@ -0,0 +1,170 @@
/**
* Nodes for constructing a graph of reactive values and reactive computations.
*
* - The graph is acyclic.
* - The user inputs new values into the graph by calling .write() on one more computation nodes.
* - The user retrieves computed results from the graph by calling .read() on one or more computation nodes.
* - The library is responsible for running any necessary computations so that .read() is up to date
* with all prior .write() calls anywhere in the graph.
* - We call the input nodes 'roots' and the output nodes 'leaves' of the graph here.
* - Changes flow from roots to leaves. It would be effective but inefficient to immediately
* propagate all changes from a root through the graph to descendant leaves. Instead, we defer
* change most change propagation computation until a leaf is accessed. This allows us to
* coalesce computations and skip altogether recalculating unused sections of the graph.
* - Each computation node tracks its sources and its observers (observers are other
* elements that have this node as a source). Source and observer links are updated automatically
* as observer computations re-evaluate and call get() on their sources.
* - Each node stores a cache state (clean/check/dirty) to support the change propagation algorithm:
*
* In general, execution proceeds in three passes:
*
* 1. write() propagates changes down the graph to the leaves
* direct children are marked as dirty and their deeper descendants marked as check
* (no computations are evaluated)
* 2. read() requests that parent nodes updateIfNecessary(), which proceeds recursively up the tree
* to decide whether the node is clean (parents unchanged) or dirty (parents changed)
* 3. updateIfNecessary() evaluates the computation if the node is dirty (the computations are
* executed in root to leaf order)
*/
import { type Flags } from "./flags.js";
import { Owner } from "./owner.js";
import { type IQueue } from "./scheduler.js";
export interface SignalOptions<T> {
name?: string;
equals?: ((prev: T, next: T) => boolean) | false;
unobserved?: () => void;
}
interface SourceType {
_observers: ObserverType[] | null;
_unobserved?: () => void;
_updateIfNecessary: () => void;
_stateFlags: Flags;
_time: number;
}
interface ObserverType {
_sources: SourceType[] | null;
_notify: (state: number, skipQueue?: boolean) => void;
_handlerMask: Flags;
_notifyFlags: (mask: Flags, newFlags: Flags) => void;
_time: number;
}
/**
* Returns the current observer.
*/
export declare function getObserver(): Computation | null;
export declare const UNCHANGED: unique symbol;
export type UNCHANGED = typeof UNCHANGED;
export declare class Computation<T = any> extends Owner implements SourceType, ObserverType {
_sources: SourceType[] | null;
_observers: ObserverType[] | null;
_value: T | undefined;
_error: unknown;
_compute: null | ((p?: T) => T);
_name: string | undefined;
_equals: false | ((a: T, b: T) => boolean);
_unobserved: (() => void) | undefined;
/** Whether the computation is an error or has ancestors that are unresolved */
_stateFlags: number;
/** Which flags raised by sources are handled, vs. being passed through. */
_handlerMask: number;
_loading: Computation<boolean> | null;
_time: number;
_forceNotify: boolean;
constructor(initialValue: T | undefined, compute: null | ((p?: T) => T), options?: SignalOptions<T>);
_read(): T;
/**
* Return the current value of this computation
* Automatically re-executes the surrounding computation when the value changes
*/
read(): T;
/**
* Return the current value of this computation
* Automatically re-executes the surrounding computation when the value changes
*
* If the computation has any unresolved ancestors, this function waits for the value to resolve
* before continuing
*/
wait(): T;
/**
* Return true if the computation is the value is dependent on an unresolved promise
* Triggers re-execution of the computation when the loading state changes
*
* This is useful especially when effects want to re-execute when a computation's
* loading state changes
*/
loading(): boolean;
/** Update the computation with a new value. */
write(value: T | ((currentValue: T) => T) | UNCHANGED, flags?: number, raw?: boolean): T;
/**
* Set the current node's state, and recursively mark all of this node's observers as STATE_CHECK
*/
_notify(state: number, skipQueue?: boolean): void;
/**
* Notify the computation that one of its sources has changed flags.
*
* @param mask A bitmask for which flag(s) were changed.
* @param newFlags The source's new flags, masked to just the changed ones.
*/
_notifyFlags(mask: Flags, newFlags: Flags): void;
_setError(error: unknown): void;
/**
* This is the core part of the reactivity system, which makes sure that the values are updated
* before they are read. We've also adapted it to return the loading state of the computation,
* so that we can propagate that to the computation's observers.
*
* This function will ensure that the value and states we read from the computation are up to date
*/
_updateIfNecessary(): void;
/**
* Remove ourselves from the owner graph and the computation graph
*/
_disposeNode(): void;
}
/**
* Reruns a computation's _compute function, producing a new value and keeping track of dependencies.
*
* It handles the updating of sources and observers, disposal of previous executions,
* and error handling if the _compute function throws. It also sets the node as loading
* if it reads any parents that are currently loading.
*/
export declare function update<T>(node: Computation<T>): void;
export declare function isEqual<T>(a: T, b: T): boolean;
/**
* Returns the current value stored inside the given compute function without triggering any
* dependencies. Use `untrack` if you want to also disable owner tracking.
*/
export declare function untrack<T>(fn: () => T): T;
/**
* Returns true if the given functinon contains signals that have been updated since the last time
* the parent computation was run.
*/
export declare function hasUpdated(fn: () => any): boolean;
/**
* Returns true if the given function contains async signals are out of date.
*/
export declare function isPending(fn: () => any): boolean;
export declare function isPending(fn: () => any, loadingValue: boolean): boolean;
/**
* Attempts to resolve value of expression synchronously returning the last resolved value for any async computation.
*/
export declare function latest<T>(fn: () => T): T;
export declare function latest<T, U>(fn: () => T, fallback: U): T | U;
export declare function catchError(fn: () => void): unknown | undefined;
/**
* Runs the given function in the given observer.
*
* Warning: Usually there are simpler ways of modeling a problem that avoid using this function
*/
export declare function runWithObserver<T>(observer: Computation, run: () => T): T | undefined;
/**
* A convenient wrapper that calls `compute` with the `owner` and `observer` and is guaranteed
* to reset the global context after the computation is finished even if an error is thrown.
*/
export declare function compute<T>(owner: Owner | null, fn: (val: T) => T, observer: Computation<T>): T;
export declare function compute<T>(owner: Owner | null, fn: (val: undefined) => T, observer: null): T;
export declare function flatten(children: any, options?: {
skipNonRendered?: boolean;
doNotUnwrap?: boolean;
}): any;
export declare function createBoundary<T>(fn: () => T, queue: IQueue): T;
export {};

View File

@@ -0,0 +1,34 @@
import { EFFECT_RENDER, EFFECT_USER } from "./constants.js";
import { Computation, type SignalOptions } from "./core.js";
/**
* Effects are the leaf nodes of our reactive graph. When their sources change, they are
* automatically added to the queue of effects to re-execute, which will cause them to fetch their
* sources and recompute
*/
export declare class Effect<T = any> extends Computation<T> {
_effect: (val: T, prev: T | undefined) => void | (() => void);
_onerror: ((err: unknown) => void | (() => void)) | undefined;
_cleanup: (() => void) | undefined;
_modified: boolean;
_prevValue: T | undefined;
_type: typeof EFFECT_RENDER | typeof EFFECT_USER;
constructor(initialValue: T, compute: (val?: T) => T, effect: (val: T, prev: T | undefined) => void | (() => void), error?: (err: unknown) => void | (() => void), options?: SignalOptions<T> & {
render?: boolean;
defer?: boolean;
});
write(value: T, flags?: number): T;
_notify(state: number, skipQueue?: boolean): void;
_setError(error: unknown): void;
_disposeNode(): void;
_runEffect(): void;
}
export declare class EagerComputation<T = any> extends Computation<T> {
constructor(initialValue: T, compute: () => T, options?: SignalOptions<T> & {
defer?: boolean;
});
_notify(state: number, skipQueue?: boolean): void;
}
export declare class ProjectionComputation extends Computation {
constructor(compute: () => void);
_notify(state: number, skipQueue?: boolean): void;
}

View File

@@ -0,0 +1,15 @@
import type { Owner } from "./owner.js";
export declare class NotReadyError extends Error {
}
export declare class NoOwnerError extends Error {
constructor();
}
export declare class ContextNotFoundError extends Error {
constructor();
}
export declare class EffectError extends Error {
constructor(effect: Function, cause: unknown);
}
export interface ErrorHandler {
(error: unknown, node: Owner): void;
}

View File

@@ -0,0 +1,11 @@
export type Flags = number;
export declare const ERROR_OFFSET = 0;
export declare const ERROR_BIT: number;
export declare const ERROR: unique symbol;
export declare const LOADING_OFFSET = 1;
export declare const LOADING_BIT: number;
export declare const LOADING: unique symbol;
export declare const UNINITIALIZED_OFFSET = 2;
export declare const UNINITIALIZED_BIT: number;
export declare const UNINITIALIZED: unique symbol;
export declare const DEFAULT_FLAGS: number;

View File

@@ -0,0 +1,8 @@
export { ContextNotFoundError, NoOwnerError, NotReadyError, type ErrorHandler } from "./error.js";
export { Owner, createContext, getContext, setContext, hasContext, getOwner, onCleanup, type Context, type ContextRecord, type Disposable } from "./owner.js";
export { Computation, createBoundary, getObserver, isEqual, untrack, hasUpdated, isPending, latest, flatten, catchError, UNCHANGED, compute, runWithObserver, type SignalOptions } from "./core.js";
export { Effect, EagerComputation } from "./effect.js";
export { flushSync, getClock, incrementClock, type IQueue, Queue } from "./scheduler.js";
export { createSuspense } from "./suspense.js";
export { SUPPORTS_PROXY } from "./constants.js";
export * from "./flags.js";

View File

@@ -0,0 +1,95 @@
/**
* Owner tracking is used to enable nested tracking scopes with automatic cleanup.
* We also use owners to also keep track of which error handling context we are in.
*
* If you write the following
*
* const a = createOwner(() => {
* const b = createOwner(() => {});
*
* const c = createOwner(() => {
* const d = createOwner(() => {});
* });
*
* const e = createOwner(() => {});
* });
*
* The owner tree will look like this:
*
* a
* /|\
* b-c-e
* |
* d
*
* Following the _nextSibling pointers of each owner will first give you its children, and then its siblings (in reverse).
* a -> e -> c -> d -> b
*
* Note that the owner tree is largely orthogonal to the reactivity tree, and is much closer to the component tree.
*/
import { type ErrorHandler } from "./error.js";
import { type IQueue } from "./scheduler.js";
export type ContextRecord = Record<string | symbol, unknown>;
export interface Disposable {
(): void;
}
/**
* Returns the currently executing parent owner.
*/
export declare function getOwner(): Owner | null;
export declare function setOwner(owner: Owner | null): Owner | null;
export declare class Owner {
_parent: Owner | null;
_nextSibling: Owner | null;
_prevSibling: Owner | null;
_state: number;
_disposal: Disposable | Disposable[] | null;
_context: ContextRecord;
_handlers: ErrorHandler[] | null;
_queue: IQueue;
constructor(signal?: boolean);
append(child: Owner): void;
dispose(this: Owner, self?: boolean): void;
_disposeNode(): void;
emptyDisposal(): void;
handleError(error: unknown): void;
}
export interface Context<T> {
readonly id: symbol;
readonly defaultValue: T | undefined;
}
/**
* Context provides a form of dependency injection. It is used to save from needing to pass
* data as props through intermediate components. This function creates a new context object
* that can be used with `getContext` and `setContext`.
*
* A default value can be provided here which will be used when a specific value is not provided
* via a `setContext` call.
*/
export declare function createContext<T>(defaultValue?: T, description?: string): Context<T>;
/**
* Attempts to get a context value for the given key.
*
* @throws `NoOwnerError` if there's no owner at the time of call.
* @throws `ContextNotFoundError` if a context value has not been set yet.
*/
export declare function getContext<T>(context: Context<T>, owner?: Owner | null): T;
/**
* Attempts to set a context value on the parent scope with the given key.
*
* @throws `NoOwnerError` if there's no owner at the time of call.
*/
export declare function setContext<T>(context: Context<T>, value?: T, owner?: Owner | null): void;
/**
* Whether the given context is currently defined.
*/
export declare function hasContext(context: Context<any>, owner?: Owner | null): boolean;
/**
* Runs an effect once before the reactive scope is disposed
* @param fn an effect that should run only once on cleanup
*
* @returns the same {@link fn} function that was passed in
*
* @description https://docs.solidjs.com/reference/lifecycle/on-cleanup
*/
export declare function onCleanup(fn: Disposable): Disposable;

View File

@@ -0,0 +1,29 @@
import type { Computation } from "./core.js";
import type { Effect } from "./effect.js";
export declare function getClock(): number;
export declare function incrementClock(): void;
export interface IQueue {
enqueue<T extends Computation | Effect>(type: number, node: T): void;
run(type: number): boolean | void;
flush(): void;
addChild(child: IQueue): void;
removeChild(child: IQueue): void;
created: number;
}
export declare class Queue implements IQueue {
_running: boolean;
_queues: [Computation[], Effect[], Effect[]];
_children: IQueue[];
created: number;
enqueue<T extends Computation | Effect>(type: number, node: T): void;
run(type: number): true | undefined;
flush(): void;
addChild(child: IQueue): void;
removeChild(child: IQueue): void;
}
export declare const globalQueue: Queue;
/**
* By default, changes are batched on the microtask queue which is an async process. You can flush
* the queue synchronously to get the latest updates by calling `flushSync()`.
*/
export declare function flushSync(): void;

View File

@@ -0,0 +1,11 @@
import { Computation } from "./core.js";
import { type Effect } from "./effect.js";
import { Queue } from "./scheduler.js";
export declare class SuspenseQueue extends Queue {
_nodes: Set<Effect>;
_fallback: boolean;
_signal: Computation<boolean>;
run(type: number): true | undefined;
_update(node: Effect): void;
}
export declare function createSuspense(fn: () => any, fallback: () => any): () => any;

View File

@@ -0,0 +1 @@
export declare function isUndefined(value: any): value is undefined;

View File

@@ -0,0 +1,2 @@
export { Owner, flushSync, getOwner, onCleanup, untrack } from "./core/index.js";
export * from "./signals.js";

View File

@@ -0,0 +1,22 @@
import type { Accessor } from "./signals.js";
export type Maybe<T> = T | void | null | undefined | false;
/**
* Reactively transforms an array with a callback function - underlying helper for the `<For>` control flow
*
* similar to `Array.prototype.map`, but gets the value and index as accessors, transforms only values that changed and returns an accessor and reactively tracks changes to the list.
*
* @description https://docs.solidjs.com/reference/reactive-utilities/map-array
*/
export declare function mapArray<Item, MappedItem>(list: Accessor<Maybe<readonly Item[]>>, map: (value: Accessor<Item>, index: Accessor<number>) => MappedItem, options?: {
keyed?: boolean | ((item: Item) => any);
fallback?: Accessor<any>;
}): Accessor<MappedItem[]>;
/**
* Reactively repeats a callback function the count provided - underlying helper for the `<Repeat>` control flow
*
* @description https://docs.solidjs.com/reference/reactive-utilities/repeat
*/
export declare function repeat(count: Accessor<number>, map: (index: number) => any, options?: {
from?: Accessor<number | undefined>;
fallback?: Accessor<any>;
}): Accessor<any[]>;

View File

@@ -0,0 +1,105 @@
import type { SignalOptions } from "./core/index.js";
import { Owner } from "./core/index.js";
export type Accessor<T> = () => T;
export type Setter<in out T> = {
<U extends T>(...args: undefined extends T ? [] : [value: Exclude<U, Function> | ((prev: T) => U)]): undefined extends T ? undefined : U;
<U extends T>(value: (prev: T) => U): U;
<U extends T>(value: Exclude<U, Function>): U;
<U extends T>(value: Exclude<U, Function> | ((prev: T) => U)): U;
};
export type Signal<T> = [get: Accessor<T>, set: Setter<T>];
export type ComputeFunction<Prev, Next extends Prev = Prev> = (v: Prev) => Next;
export type EffectFunction<Prev, Next extends Prev = Prev> = (v: Next, p?: Prev) => (() => void) | void;
export interface EffectOptions {
name?: string;
defer?: boolean;
}
export interface MemoOptions<T> {
name?: string;
equals?: false | ((prev: T, next: T) => boolean);
}
export type NoInfer<T extends any> = [T][T extends any ? 0 : never];
/**
* Creates a simple reactive state with a getter and setter
* ```typescript
* const [state: Accessor<T>, setState: Setter<T>] = createSignal<T>(
* value: T,
* options?: { name?: string, equals?: false | ((prev: T, next: T) => boolean) }
* )
* ```
* @param value initial value of the state; if empty, the state's type will automatically extended with undefined; otherwise you need to extend the type manually if you want setting to undefined not be an error
* @param options optional object with a name for debugging purposes and equals, a comparator function for the previous and next value to allow fine-grained control over the reactivity
*
* @returns ```typescript
* [state: Accessor<T>, setState: Setter<T>]
* ```
* * the Accessor is a function that returns the current value and registers each call to the reactive root
* * the Setter is a function that allows directly setting or mutating the value:
* ```typescript
* const [count, setCount] = createSignal(0);
* setCount(count => count + 1);
* ```
*
* @description https://docs.solidjs.com/reference/basic-reactivity/create-signal
*/
export declare function createSignal<T>(): Signal<T | undefined>;
export declare function createSignal<T>(value: Exclude<T, Function>, options?: SignalOptions<T>): Signal<T>;
export declare function createSignal<T>(fn: ComputeFunction<T>, initialValue?: T, options?: SignalOptions<T>): Signal<T>;
/**
* Creates a readonly derived reactive memoized signal
* ```typescript
* export function createMemo<T>(
* compute: (v: T) => T,
* value?: T,
* options?: { name?: string, equals?: false | ((prev: T, next: T) => boolean) }
* ): () => T;
* ```
* @param compute a function that receives its previous or the initial value, if set, and returns a new value used to react on a computation
* @param value an optional initial value for the computation; if set, fn will never receive undefined as first argument
* @param options allows to set a name in dev mode for debugging purposes and use a custom comparison function in equals
*
* @description https://docs.solidjs.com/reference/basic-reactivity/create-memo
*/
export declare function createMemo<Next extends Prev, Prev = Next>(compute: ComputeFunction<undefined | NoInfer<Prev>, Next>): Accessor<Next>;
export declare function createMemo<Next extends Prev, Init = Next, Prev = Next>(compute: ComputeFunction<Init | Prev, Next>, value: Init, options?: MemoOptions<Next>): Accessor<Next>;
/**
* Creates a reactive effect that runs after the render phase
* ```typescript
* export function createEffect<T>(
* compute: (prev: T) => T,
* effect: (v: T, prev: T) => (() => void) | void,
* value?: T,
* options?: { name?: string }
* ): void;
* ```
* @param compute a function that receives its previous or the initial value, if set, and returns a new value used to react on a computation
* @param effect a function that receives the new value and is used to perform side effects, return a cleanup function to run on disposal
* @param error an optional function that receives an error if thrown during the computation
* @param value an optional initial value for the computation; if set, fn will never receive undefined as first argument
* @param options allows to set a name in dev mode for debugging purposes
*
* @description https://docs.solidjs.com/reference/basic-reactivity/create-effect
*/
export declare function createEffect<Next>(compute: ComputeFunction<undefined | NoInfer<Next>, Next>, effect: EffectFunction<NoInfer<Next>, Next>, error?: (err: unknown) => void): void;
export declare function createEffect<Next, Init = Next>(compute: ComputeFunction<Init | Next, Next>, effect: EffectFunction<Next, Next>, error: ((err: unknown) => void) | undefined, value: Init, options?: EffectOptions): void;
/**
* Creates a new non-tracked reactive context with manual disposal
*
* @param fn a function in which the reactive state is scoped
* @returns the output of `fn`.
*
* @description https://docs.solidjs.com/reference/reactive-utilities/create-root
*/
export declare function createRoot<T>(init: ((dispose: () => void) => T) | (() => T)): T;
/**
* Runs the given function in the given owner to move ownership of nested primitives and cleanups.
* This method untracks the current scope.
*
* Warning: Usually there are simpler ways of modeling a problem that avoid using this function
*/
export declare function runWithOwner<T>(owner: Owner | null, run: () => T): T;
/**
* Returns a promise of the resolved value of a reactive expression
* @param fn a reactive expression to resolve
*/
export declare function resolve<T>(fn: () => T): Promise<T>;

View File

@@ -0,0 +1,6 @@
export type { Store, StoreSetter, StoreNode, NotWrappable, SolidStore } from "./store.js";
export type { Merge, Omit } from "./utils.js";
export { unwrap, isWrappable, createStore, $RAW, $TRACK, $PROXY, $TARGET } from "./store.js";
export { createProjection } from "./projection.js";
export { reconcile } from "./reconcile.js";
export { merge, omit } from "./utils.js";

View File

@@ -0,0 +1,8 @@
import { type Store, type StoreSetter } from "./store.js";
/**
* Creates a mutable derived value
*
* @see {@link https://github.com/solidjs/x-reactivity#createprojection}
*/
export declare function createProjection<T extends Object>(fn: (draft: T) => void, initialValue?: T): Store<T>;
export declare function wrapProjection<T>(fn: (draft: T) => void, store: Store<T>, setStore: StoreSetter<T>): [Store<T>, StoreSetter<T>];

View File

@@ -0,0 +1 @@
export declare function reconcile<T extends U, U>(value: T, key: string | ((item: NonNullable<any>) => any)): (state: U) => T;

View File

@@ -0,0 +1,34 @@
import { Computation } from "../core/index.js";
export type Store<T> = Readonly<T>;
export type StoreSetter<T> = (fn: (state: T) => void) => void;
type DataNode = Computation<any>;
type DataNodes = Record<PropertyKey, DataNode>;
declare const $RAW: unique symbol, $TRACK: unique symbol, $TARGET: unique symbol, $PROXY: unique symbol;
export declare const STORE_VALUE = "v", STORE_NODE = "n", STORE_HAS = "h";
export { $PROXY, $TRACK, $RAW, $TARGET };
export type StoreNode = {
[STORE_VALUE]: Record<PropertyKey, any>;
[STORE_NODE]?: DataNodes;
[STORE_HAS]?: DataNodes;
};
export declare namespace SolidStore {
interface Unwrappable {
}
}
export type NotWrappable = string | number | bigint | symbol | boolean | Function | null | undefined | SolidStore.Unwrappable[keyof SolidStore.Unwrappable];
export declare function wrap<T extends Record<PropertyKey, any>>(value: T): T;
export declare function isWrappable<T>(obj: T | NotWrappable): obj is T;
/**
* Returns the underlying data in the store without a proxy.
* @param item store proxy object
* @example
* ```js
* const initial = {z...};
* const [state, setState] = createStore(initial);
* initial === state; // => false
* initial === unwrap(state); // => true
* ```
*/
export declare function unwrap<T>(item: T, deep?: boolean, set?: Set<unknown>): T;
export declare function createStore<T extends object = {}>(store: T | Store<T>): [get: Store<T>, set: StoreSetter<T>];
export declare function createStore<T extends object = {}>(fn: (store: T) => void, store: T | Store<T>): [get: Store<T>, set: StoreSetter<T>];

View File

@@ -0,0 +1,29 @@
type DistributeOverride<T, F> = T extends undefined ? F : T;
type Override<T, U> = T extends any ? U extends any ? {
[K in keyof T]: K extends keyof U ? DistributeOverride<U[K], T[K]> : T[K];
} & {
[K in keyof U]: K extends keyof T ? DistributeOverride<U[K], T[K]> : U[K];
} : T & U : T & U;
type OverrideSpread<T, U> = T extends any ? {
[K in keyof ({
[K in keyof T]: any;
} & {
[K in keyof U]?: any;
} & {
[K in U extends any ? keyof U : keyof U]?: any;
})]: K extends keyof T ? Exclude<U extends any ? U[K & keyof U] : never, undefined> | T[K] : U extends any ? U[K & keyof U] : never;
} : T & U;
type Simplify<T> = T extends any ? {
[K in keyof T]: T[K];
} : T;
type _Merge<T extends unknown[], Curr = {}> = T extends [
infer Next | (() => infer Next),
...infer Rest
] ? _Merge<Rest, Override<Curr, Next>> : T extends [...infer Rest, infer Next | (() => infer Next)] ? Override<_Merge<Rest, Curr>, Next> : T extends [] ? Curr : T extends (infer I | (() => infer I))[] ? OverrideSpread<Curr, I> : Curr;
export type Merge<T extends unknown[]> = Simplify<_Merge<T>>;
export declare function merge<T extends unknown[]>(...sources: T): Merge<T>;
export type Omit<T, K extends readonly (keyof T)[]> = {
[P in keyof T as Exclude<P, K[number]>]: T[P];
};
export declare function omit<T extends Record<any, any>, K extends readonly (keyof T)[]>(props: T, ...keys: K): Omit<T, K>;
export {};

View File

@@ -28,7 +28,7 @@ export function init({
elements.charts.append(utils.dom.createShadow("left"));
elements.charts.append(utils.dom.createShadow("right"));
const { headerElement, titleElement } = utils.dom.createHeader({});
const { headerElement, headingElement } = utils.dom.createHeader({});
elements.charts.append(headerElement);
const chart = lightweightCharts.createChartElement({
@@ -45,10 +45,8 @@ export function init({
let firstRun = true;
signals.createEffect(selected, (option) => {
titleElement.innerHTML = option.title;
headingElement.innerHTML = option.title;
signals.createEffect(index_, (index) => {
utils.url.writeParam("index", String(index));
chart.reset({ owner: signals.getOwner() });
const TIMERANGE_LS_KEY = `chart-timerange-${index}`;
@@ -72,7 +70,7 @@ export function init({
chart.create({
index,
timeScaleSetCallback: () => {
timeScaleSetCallback: (unknownTimeScaleCallback) => {
const from_ = from();
const to_ = to();
if (from_ !== null && to_ !== null) {
@@ -80,6 +78,8 @@ export function init({
from: from_,
to: to_,
});
} else {
unknownTimeScaleCallback();
}
},
});
@@ -87,6 +87,7 @@ export function init({
const candles = chart.addCandlestickSeries({
vecId: "ohlc",
name: "Price",
unit: "US Dollars",
});
signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
if (!latest) return;
@@ -98,17 +99,18 @@ export function init({
});
[
{ blueprints: option.top, paneNumber: 0 },
{ blueprints: option.bottom, paneNumber: 1 },
].forEach(({ blueprints, paneNumber }) => {
{ blueprints: option.top, paneIndex: 0 },
{ blueprints: option.bottom, paneIndex: 1 },
].forEach(({ blueprints, paneIndex }) => {
blueprints?.forEach((blueprint) => {
if (vecIdToIndexes[blueprint.key].includes(index)) {
chart.addLineSeries({
vecId: blueprint.key,
color: blueprint.color,
name: blueprint.title,
unit: option.unit,
defaultActive: blueprint.defaultActive,
paneNumber,
paneIndex,
});
}
});
@@ -136,7 +138,6 @@ export function init({
* @param {Utilities} args.utils
*/
function createIndexSelector({ elements, signals, utils }) {
const indexLSKey = "chart-index";
const indexChoices = /**@type {const} */ ([
"timestamp",
"date",
@@ -149,8 +150,14 @@ function createIndexSelector({ elements, signals, utils }) {
"decade",
]);
/** @typedef {(typeof indexChoices)[number]} SerializedIndex */
const serializedIndex = signals.createSignal(
/** @type {SerializedIndex} */ (localStorage.getItem(indexLSKey) || "date"),
const serializedIndex = /** @type {Signal<SerializedIndex>} */ (
signals.createSignal("date", {
save: {
keyPrefix: "charts",
key: "index",
...utils.serde.string,
},
})
);
const indexesField = utils.dom.createHorizontalChoiceField({
title: "Index",
@@ -162,7 +169,6 @@ function createIndexSelector({ elements, signals, utils }) {
indexesField.addEventListener("change", (event) => {
// @ts-ignore
const value = event.target.value;
localStorage.setItem(indexLSKey, value);
serializedIndex.set(value);
});

View File

@@ -1,10 +1,10 @@
// @ts-check
/**
* @import { Option, Weighted, Color, DatasetCandlestickData, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple } from "./types/self"
* @import { Option, Weighted, Color, DatasetCandlestickData, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple, Unit } from "./types/self"
* @import { Marker, CreatePaneParameters, HoveredLegend, ChartPane, SplitSeries, SingleSeries, CreateSplitSeriesParameters, LineSeriesBlueprint, CandlestickSeriesBlueprint, BaselineSeriesBlueprint, CreateBaseSeriesParameters, BaseSeries, RemoveSeriesBlueprintFluff, SplitSeriesBlueprint, AnySeries, PriceSeriesType } from "../packages/lightweight-charts/types";
* @import * as _ from "../packages/ufuzzy/v1.0.14/types"
* @import { createChart as CreateClassicChart, createChartEx as CreateCustomChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, SeriesMarker, SeriesType, BaselineStyleOptions, SeriesOptionsCommon } from "../packages/lightweight-charts/v5.0.5/types"
* @import { createChart as CreateClassicChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, SeriesType, BaselineStyleOptions, SeriesOptionsCommon } from "../packages/lightweight-charts/v5.0.5-treeshaked/types"
* @import { SignalOptions } from "../packages/solid-signals/2024-11-02/types/core/core"
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/2024-11-02/types/core/owner"
* @import { createSignal as CreateSignal, createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo, createRoot as CreateRoot, runWithOwner as RunWithOwner } from "../packages/solid-signals/2024-11-02/types/signals";
@@ -490,30 +490,21 @@ function createUtils() {
return { input, signal };
},
/**
* @param {Object} param0
* @param {string} [param0.title]
* @param {Object} args
* @param {1 | 2 | 3} [args.level]
* @param {string} [args.title]
*/
createHeader({ title }) {
createHeader({ title, level = 1 }) {
const headerElement = window.document.createElement("header");
const div = window.document.createElement("div");
headerElement.append(div);
const h1 = window.document.createElement("h1");
div.append(h1);
h1.style.display = "flex";
h1.style.flexDirection = "column";
const titleElement = window.document.createElement("span");
if (title) {
titleElement.append(title);
}
h1.append(titleElement);
titleElement.style.display = "block";
const headingElement = window.document.createElement(`h${level}`);
headingElement.innerHTML = title || "";
headerElement.append(headingElement);
headingElement.style.display = "block";
return {
headerElement,
titleElement,
headingElement,
};
},
/**
@@ -852,6 +843,20 @@ function createUtils() {
};
const serde = {
string: {
/**
* @param {string} v
*/
serialize(v) {
return v;
},
/**
* @param {string} v
*/
deserialize(v) {
return v;
},
},
number: {
/**
* @param {number} v
@@ -1376,7 +1381,6 @@ function getElements() {
searchInput: /** @type {HTMLInputElement} */ (
getElementById("search-input")
),
searchSmall: getElementById("search-small"),
searchResults: getElementById("search-results"),
selectors: getElementById("frame-selectors"),
style: getComputedStyle(window.document.documentElement),
@@ -2108,8 +2112,6 @@ function main() {
const haystack = options.list.map((option) => option.title);
const searchSmallOgInnerHTML = elements.searchSmall.innerHTML;
const RESULTS_PER_PAGE = 100;
packages.ufuzzy().then((ufuzzy) => {
@@ -2193,7 +2195,6 @@ function main() {
});
if (!needle) {
elements.searchSmall.innerHTML = searchSmallOgInnerHTML;
elements.searchResults.innerHTML = "";
return;
}
@@ -2253,9 +2254,6 @@ function main() {
);
}
elements.searchSmall.innerHTML = `Found <strong>${
result?.[0]?.length || 0
}</strong> result(s)`;
elements.searchResults.innerHTML = "";
const list = computeResultPage(result, 0);

View File

@@ -15,7 +15,7 @@ import {
SeriesType,
ISeriesApi,
BaselineData,
} from "../../packages/lightweight-charts/v5.0.5/types";
} from "../../packages/lightweight-charts/v5.0.5-treeshaked/types";
import { AnyPossibleCohortId, Groups } from "../options";
type Color = () => string;
@@ -49,7 +49,7 @@ interface PartialOption {
interface PartialChartOption extends PartialOption {
title?: string;
unit?: string;
unit?: Unit;
top?: SplitSeriesBlueprint[];
bottom?: SplitSeriesBlueprint[];
}
@@ -93,7 +93,7 @@ interface ChartOption
extends Omit<PartialChartOption, "title">,
ProcessedOptionAddons {
kind: "chart";
unit: string;
unit: Unit;
}
type Option = UrlOption | ChartOption | SimulationOption;

View File

@@ -29,6 +29,16 @@
.lightweight-chart {
margin-left: var(--negative-main-padding);
fieldset {
padding-left: var(--main-padding);
padding-top: 0.5rem;
z-index: 10;
position: absolute;
left: 0;
top: 0;
gap: 0;
}
}
}
}