Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export * from "./src/signals/mod.ts";
export * from "./src/layout/mod.ts";
export * from "./src/canvas/mod.ts";
export * from "./src/utils/mod.ts";
export * from "./src/input_reader/mod.ts";
export * from "./src/input/mod.ts";
8 changes: 4 additions & 4 deletions src/canvas/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,19 @@ export class TextObject extends Renderable<"text"> {
});
}

draw(): void {
override draw(): void {
this.#updateEffect.resume();
this.rectangle.subscribe(this.#rectangleSubscription);
super.draw();
}

erase(): void {
override erase(): void {
this.#updateEffect.pause();
this.rectangle.unsubscribe(this.#rectangleSubscription);
super.erase();
}

updateMovement(): void {
override updateMovement(): void {
const { objectsUnder, previousRectangle } = this;
const rectangle = this.rectangle.peek();

Expand Down Expand Up @@ -204,7 +204,7 @@ export class TextObject extends Renderable<"text"> {
}
}

rerender(): void {
override rerender(): void {
const { canvas, valueChars, omitCells, rerenderCells } = this;

const { frameBuffer, rerenderQueue } = canvas;
Expand Down
2 changes: 1 addition & 1 deletion src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { Rectangle } from "./types.ts";
import { SortedArray } from "./utils/sorted_array.ts";
import type { Renderable } from "./canvas/renderable.ts";
import type { View } from "./view.ts";
import type { InputEventRecord } from "./input_reader/mod.ts";
import type { InputEventRecord } from "./input/mod.ts";
import { Computed, Signal, type SignalOfObject } from "./signals/mod.ts";
import { signalify } from "./signals/signalify.ts";

Expand Down
14 changes: 6 additions & 8 deletions src/components/frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,12 @@ export interface FrameOptions extends ComponentOptions {
* });
* ```
*/
export class Frame extends Component {
declare drawnObjects: {
top: TextObject;
bottom: TextObject;
left: BoxObject;
right: BoxObject;
};

export class Frame extends Component<{
top: TextObject;
bottom: TextObject;
left: BoxObject;
right: BoxObject;
}> {
charMap: Signal<FrameUnicodeCharactersType>;

constructor(options: FrameOptions) {
Expand Down
11 changes: 5 additions & 6 deletions src/components/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,11 @@ export interface InputOptions extends Omit<ComponentOptions, "rectangle"> {
* });
* ```
*/
export class Input extends Box {
declare drawnObjects: {
box: BoxObject;
text: TextObject;
cursor: TextObject;
};
export class Input extends Box<{
box: BoxObject;
text: TextObject;
cursor: TextObject;
}> {
declare theme: InputTheme;

text: Signal<string>;
Expand Down
4 changes: 1 addition & 3 deletions src/components/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,7 @@ export interface LabelOptions extends Omit<ComponentOptions, "rectangle"> {
* })
* ```
*/
export class Label extends Component {
declare drawnObjects: { texts: TextObject[] };

export class Label extends Component<{ texts: TextObject[] }> {
#valueLines: Signal<string[]>;

text: Signal<string>;
Expand Down
3 changes: 1 addition & 2 deletions src/components/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ export interface SliderOptions extends ComponentOptions {
* });
* ```
*/
export class Slider extends Box {
declare drawnObjects: { box: BoxObject; thumb: BoxObject };
export class Slider extends Box<{ box: BoxObject; thumb: BoxObject }> {
declare theme: SliderTheme;

min: Signal<number>;
Expand Down
25 changes: 12 additions & 13 deletions src/components/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,19 @@ export interface TableOptions extends Omit<ComponentOptions, "rectangle"> {
*
* ```
*/
export class Table extends Component {
export class Table extends Component<{
frame: [
top: TextObject,
bottom: TextObject,
spacer: TextObject,
left: BoxObject,
right: BoxObject,
];

header: TextObject;
data: TextObject[];
}> {
declare theme: TableTheme;
declare drawnObjects: {
frame: [
top: TextObject,
bottom: TextObject,
spacer: TextObject,
left: BoxObject,
right: BoxObject,
];

header: TextObject;
data: TextObject[];
};

data: Signal<string[][]>;
headers: Signal<TableHeader<true>[]>;
Expand Down
4 changes: 1 addition & 3 deletions src/components/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ export interface TextOptions extends Omit<ComponentOptions, "rectangle"> {
* })
* ```
*/
export class Text extends Component {
declare drawnObjects: { text: TextObject };

export class Text extends Component<{ text: TextObject }> {
text: Signal<string>;
overwriteRectangle: Signal<boolean>;
multiCodePointSupport: Signal<boolean>;
Expand Down
2 changes: 1 addition & 1 deletion src/components/textbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { DeepPartial } from "../types.ts";
import { cropToWidth, insertAt } from "../utils/strings.ts";
import { clamp } from "../utils/numbers.ts";
import { Computed, Effect, Signal, signalify } from "../signals/mod.ts";
import type { KeyPressEvent } from "../input_reader/types.ts";
import type { KeyPressEvent } from "../input/types.ts";

export interface CursorPosition {
x: number;
Expand Down
2 changes: 1 addition & 1 deletion src/event_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type EventListener<
* Type for creating new arguments
* - Required as a workaround for simple tuples and arrays types not working properly
*/
export type EmitterEvent<Args extends unknown[] = unknown[]> = {
export type EmitterEvent<Args extends readonly unknown[] = unknown[]> = {
args: Args;
};

Expand Down
1 change: 0 additions & 1 deletion src/input/mouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export function decodeMouseSGR(
const action = code.at(-1);
if (!code.startsWith("\x1b[<") || (action !== "m" && action !== "M")) {
return undefined;
SSS;
}

const release = action === "m";
Expand Down
2 changes: 1 addition & 1 deletion src/layout/mod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2023 Im-Beast. MIT license.
export * from "./errors.ts";
export * from "./grid_layout.ts";
export * from "./horizontal_layout.ts";
export * from "./layout.ts";
export * from "./vertical_layout.ts";
export * from "./grid_layout.ts";
25 changes: 9 additions & 16 deletions src/signals/computed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,7 @@ import { Signal } from "./signal.ts";
import type { Dependant, Dependency } from "./types.ts";

import { activeSignals, trackDependencies } from "./dependency_tracking.ts";

/** Thrown whenever someone tries to directly modify `Computed.value` */
export class ComputedReadOnlyError extends Error {
constructor() {
super(
"Computed is read-only, you can't (and shouldn't!) directly modify its value",
);
}
}
import { ComputedReadOnlyError } from "./errors.ts";

/** Function that's used to calculate `Computed`'s value */
export interface Computable<T> {
Expand Down Expand Up @@ -75,18 +67,19 @@ export class Computed<T> extends Signal<T> implements Dependant, Dependency {
if (
this.$value !== (this.$value = this.computable(cause)) ||
this.forceUpdateValue
) {
this.propagate(cause);
}
) this.propagate(cause);
}

override dispose(): void {
super.dispose();

const { dependencies } = this;
for (const dependency of dependencies) {
dependency.dependants!.delete(this);
dependencies.delete(dependency);
for (const dependency of this.dependencies) {
dependency.dependants?.delete(this);
this.dependencies.delete(dependency);
}
}
}

export function computed<T>(computable: Computable<T>): Computed<T> {
return new Computed(computable);
}
26 changes: 14 additions & 12 deletions src/signals/dependency_tracking.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@
// Copyright 2023 Im-Beast. MIT license.
import type { Dependant, Dependency } from "./types.ts";
import type { Dependency, Dependish } from "./types.ts";

export let activeSignals: Set<Dependency> | undefined;
let incoming = 0;

/**
* Asynchronously tracks used signals for provided function
*/
export async function trackDependencies(
dependencies: Set<Dependency>,
export async function track<T extends Dependish>(
dependencies: Set<T>,
thisArg: unknown,
// this is supposed to mean every function
// deno-lint-ignore ban-types
func: Function,
): Promise<void> {
while (incoming !== 0) {
await Promise.resolve();
}
while (incoming) await Promise.resolve();

++incoming;
activeSignals = dependencies;

try {
func.call(thisArg);
} catch (error) {
incoming = 0;
throw error;
} finally {
activeSignals = undefined;
--incoming;
}
activeSignals = undefined;
--incoming;
}

/**
* Replaces all dependencies with root dependencies to prevent multiple updates caused by the same change.
*/
export function optimizeDependencies(
into: Set<Dependency | (Dependency & Dependant)>,
from = into,
export function optimize<T extends Dependish>(
into: Set<T>,
from: Iterable<T> = into,
): void {
for (const dependency of from) {
if ("dependencies" in dependency) {
into.delete(dependency);
optimizeDependencies(into, dependency.dependencies);
optimize(into, dependency.dependencies);
} else {
into.add(dependency);
}
}
}

export { optimize as optimizeDependencies, track as trackDependencies };
30 changes: 18 additions & 12 deletions src/signals/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class Effect implements Dependant, Disposable {

update(cause: Dependency | Dependant): void {
if (this.paused) {
throw "Something called update() on effect while being paused";
throw new Error("Something called update() on effect while being paused");
}

this.$effectable(cause);
Expand All @@ -75,9 +75,7 @@ export class Effect implements Dependant, Disposable {
if (this.disposed) {
throw new ReferenceError("Effect already disposed");
} else {
for (const { dependants } of this.dependencies) {
dependants?.delete(this);
}
for (const dep of this.dependencies) dep.dependants?.delete(this);
this.dependencies.clear();
this.disposed = true;
}
Expand All @@ -88,25 +86,33 @@ export class Effect implements Dependant, Disposable {
* - Doesn't clear dependencies, can be resumed!
*/
pause(): void {
this.paused = true;

for (const dependency of this.dependencies) {
dependency.dependants?.delete(this);
if (this.disposed) {
throw new ReferenceError("Effect already disposed");
}
if (!this.paused) {
this.paused = true;
for (const dep of this.dependencies) dep.dependants?.delete(this);
}
}

/**
* - Adds itself to all dependencies dependants
*/
resume(): void {
this.paused = false;

for (const dependency of this.dependencies) {
dependency.depend(this);
if (this.disposed) {
throw new ReferenceError("Effect already disposed");
}
if (this.paused) {
this.paused = false;
for (const dep of this.dependencies) dep.depend(this);
}
}

[Symbol.dispose](): void {
this.dispose();
}
}

export function effect(effectable: Effectable): Effect {
return new Effect(effectable);
}
17 changes: 17 additions & 0 deletions src/signals/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** Thrown whenever someone tries to directly modify `Computed.value` */
export class ComputedReadOnlyError extends Error {
constructor() {
super(
"Computed values are read-only and cannot be directly modified. If you need to change the value, change the signals it depends on instead.",
);
this.name = "ComputedReadOnlyError";
}
}

/** Thrown whenever `deepObserve` is set and `typeof value !== "object"` */
export class SignalDeepObserveTypeofError extends Error {
constructor() {
super("You can only deeply observe value with typeof 'object'");
this.name = "SignalDeepObserveTypeofError";
}
}
10 changes: 6 additions & 4 deletions src/signals/flusher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import type { Dependant, Dependency, LazyDependant } from "./types.ts";

/**
* Flusher tracks
* Flusher tracks dependants and updates them when `flush()` gets called.
*
* It can be used to delay updates of `LazyComputed` dependants until some
* condition is met, for example until next animation frame or until some
* expensive calculations get done.
*
* @example
* ```ts
Expand Down Expand Up @@ -36,9 +40,7 @@ export class Flusher implements Dependency {

flush(): void {
const { dependants } = this;
for (const dependant of dependants) {
dependant.update(this);
}
for (const dependant of dependants) dependant.update(this);
dependants.clear();
}
}
Loading
Loading