-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
restructure quel to have a push/pull mechanism instead
- Loading branch information
1 parent
024a3a0
commit ede750d
Showing
42 changed files
with
1,621 additions
and
863 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,3 +104,5 @@ dist | |
.tern-port | ||
|
||
.DS_Store | ||
|
||
**/*.bak.* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
section:first-child { | ||
padding-top: 15vh; | ||
} | ||
|
||
@media (prefers-color-scheme: light) { | ||
:root { | ||
--primary-color: #22577A; | ||
} | ||
|
||
pre > code { | ||
box-shadow: 0 6px 24px rgba(0 0 0 / 5%); | ||
} | ||
} | ||
|
||
@media (prefers-color-scheme: dark) { | ||
:root { | ||
--primary-color: #80ED99; | ||
} | ||
|
||
pre > code { | ||
box-shadow: 0 3px 12px rgba(0 0 0 / 50%); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import { Source } from './source' | ||
import { Signal, SignalOptions } from './signal' | ||
import { deferred, Deferred } from './util/deferred' | ||
import { isStoppable } from './util/stoppable' | ||
import { AbortableOptions, checkAbort } from './util/abortable' | ||
|
||
|
||
export const SKIP = Symbol() | ||
export const STOP = Symbol() | ||
|
||
export interface ExprOptions extends AbortableOptions { | ||
initial?: boolean | ||
} | ||
|
||
export type ExprResultSync<T> = undefined | T | typeof SKIP | typeof STOP | ||
export type ExprResult<T> = ExprResultSync<T> | Promise<ExprResultSync<T>> | ||
export type ExprFn<T> = (track: Track, options: ExprOptions) => ExprResult<T> | ||
export type SourceTrack = <T>(src: Source<T>) => T | ||
export type ExprTrack = <T>(expr: ExprFn<T>) => T | undefined | ||
export type Track = ExprTrack & SourceTrack | ||
export type Trackable<T> = Source<T> | ExprFn<T> | ||
|
||
export type ComputedOptions<T> = SignalOptions<T | undefined> | ||
|
||
export function wrap<T>(expr: ExprFn<T>): Computed<T> | ||
export function wrap<T>(src: Source<T>): Source<T> | ||
export function wrap<T>(src: Trackable<T>): Source<T> | Computed<T> | ||
export function wrap<T>(src: Trackable<T>): Source<T> | Computed<T> { | ||
return typeof src === 'function' ? | ||
((src as any).__computed__ ??= new Computed(src)) | ||
: src | ||
} | ||
|
||
export class Computed<T> extends Signal<T | undefined> { | ||
private _changedDependencies?: Source<unknown>[] | ||
private _initialRun?: Deferred<void> | ||
private _initialError?: unknown | ||
private _activeRuns = 0 | ||
private _activeDependencies = new WeakMap<Source<unknown>, boolean>() | ||
|
||
constructor(private expr: ExprFn<T>, options?: ComputedOptions<T>) { | ||
super({ initial: undefined, ...options }) | ||
|
||
this.run({ initial: true }) | ||
} | ||
|
||
private track<R>(src: Source<R>, tracked: Source<unknown>[], signal?: AbortSignal): R { | ||
if (!signal?.aborted && !src.stopped && !tracked.includes(src) && !this._activeDependencies.get(src)) { | ||
tracked.push(src) | ||
this._activeDependencies.set(src, true) | ||
|
||
src.listen(() => { | ||
(this._changedDependencies ??= []).push(src) | ||
this._activeDependencies.set(src, false) | ||
this.notify(this) | ||
}, { once: true }) | ||
} | ||
|
||
return src.last | ||
} | ||
|
||
override async get(options?: AbortableOptions) { | ||
if (this._changedDependencies) { | ||
const promises: Promise<unknown>[] = [] | ||
for (const dep of this._changedDependencies) { | ||
const promise = dep.get() | ||
!dep.valid && promises.push(promise) | ||
} | ||
|
||
this._changedDependencies = undefined | ||
promises.length > 0 && await Promise.all(promises) | ||
|
||
const p = this.run(options) | ||
p instanceof Promise && await p | ||
} else if (!this.valid) { | ||
await (this._initialRun ??= deferred()).promise | ||
} else if (this._initialError) { | ||
throw this._initialError | ||
} | ||
|
||
return this._last | ||
} | ||
|
||
public async reevaluate(options?: AbortableOptions) { | ||
const p = this.run(options) | ||
p instanceof Promise && await p | ||
|
||
return this._last | ||
} | ||
|
||
override get valid() { | ||
return this._activeRuns === 0 && ( | ||
!this._changedDependencies || this._changedDependencies.length === 0 | ||
) | ||
} | ||
|
||
protected override invalidate() { this._activeRuns++ } | ||
protected override validate() { this._activeRuns-- } | ||
|
||
protected run(options?: ExprOptions): void | Promise<void> { | ||
const tracked: Source<unknown>[] = [] | ||
try { | ||
checkAbort(options?.signal) | ||
const result = this.expr( | ||
(<R>(t: Trackable<R>) => this.track(wrap(t), tracked, options?.signal)) as Track, | ||
options ?? {} | ||
) | ||
tracked.length === 0 && this.stop() | ||
|
||
if (result instanceof Promise) { | ||
this.invalidate() | ||
|
||
return result.finally(() => { | ||
this.validate() | ||
}).then((value) => { | ||
checkAbort(options?.signal) | ||
this.emit(value) | ||
this._initialRun?.resolve() | ||
}).catch(err => { | ||
if (options?.initial) { | ||
this._initialRun?.reject(err) | ||
} else { | ||
throw err | ||
} | ||
}) | ||
} else { | ||
checkAbort(options?.signal) | ||
this.emit(result) | ||
} | ||
} catch (err) { | ||
if (options?.initial) { | ||
this._initialError = err | ||
} else { | ||
throw err | ||
} | ||
} | ||
} | ||
|
||
protected override emit(val: T | typeof STOP | typeof SKIP | undefined) { | ||
if (val === STOP) { | ||
this.stop() | ||
} else if (val === SKIP) { | ||
return | ||
} else if (!this.equals(val, this.last)) { | ||
if (isStoppable(this.last)) { | ||
this.last.stop() | ||
this._activeDependencies.delete(this.last as any) | ||
} | ||
|
||
this._last = val | ||
} | ||
} | ||
} | ||
|
||
export function computed<T>(expr: ExprFn<T>, options?: ComputedOptions<T>): Computed<T> { | ||
return new Computed(expr, options) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { dispose, isDisposable } from './util/disposable' | ||
import { Signal, SignalOptions } from './signal' | ||
|
||
|
||
export type Producer<T> = ( | ||
produce: (val: T) => void, | ||
finalize: (diposable: Disposable) => void | ||
) => Disposable | void | Promise<void> | ||
|
||
|
||
export interface ProducedSignalOptions<T> extends SignalOptions<T> { | ||
producer: Producer<T> | ||
} | ||
|
||
|
||
export class ProducedSignal<T> extends Signal<T> { | ||
cleanup?: Disposable | ||
|
||
constructor(options: ProducedSignalOptions<T>) { | ||
super(options) | ||
|
||
const cl = options.producer && options.producer(val => this.emit(val), cleanup => this.cleanup = cleanup) | ||
cl && isDisposable(cl) && (this.cleanup = cl) | ||
} | ||
|
||
override clean() { | ||
super.clean() | ||
this.cleanup && dispose(this.cleanup) | ||
} | ||
} | ||
|
||
|
||
export function create<T>(options: ProducedSignalOptions<T>) { | ||
return new ProducedSignal(options) | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,20 @@ | ||
import { Source } from './source' | ||
import { create } from './create' | ||
import { disposable } from './util/disposable' | ||
import { addListener, removeListener, EventMap } from './util/dom-events' | ||
|
||
|
||
export class EventSource<EventName extends keyof EventMap> extends Source<EventMap[EventName]> { | ||
constructor( | ||
readonly node: EventTarget, | ||
readonly name: EventName, | ||
readonly options?: boolean | AddEventListenerOptions, | ||
) { | ||
super(emit => { | ||
export function event<EventName extends keyof EventMap>( | ||
node: EventTarget, | ||
name: EventName, | ||
options?: boolean | AddEventListenerOptions, | ||
) { | ||
return create<undefined | EventMap[EventName]>({ | ||
initial: undefined, | ||
producer: emit => { | ||
const handler = (evt: EventMap[EventName]) => emit(evt) | ||
addListener(node, name, handler, options) | ||
|
||
return () => removeListener(node, name, handler, options) | ||
}) | ||
} | ||
return disposable(() => removeListener(node, name, handler, options)) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,25 @@ | ||
import { EventMap } from './util/dom-events' | ||
import { EventSource } from './event' | ||
import { InputSource } from './input' | ||
import { event } from './event' | ||
import { input } from './input' | ||
|
||
|
||
export function from(input: HTMLInputElement): InputSource | ||
export function from(node: EventTarget): EventSource<'click'> | ||
export function from(i: HTMLInputElement | HTMLTextAreaElement): ReturnType<typeof input> | ||
export function from(node: EventTarget): ReturnType<typeof event<'click'>> | ||
export function from<EventName extends keyof EventMap>( | ||
node: EventTarget, | ||
name: EventName, | ||
options?: boolean | AddEventListenerOptions | ||
): EventSource<EventName> | ||
): ReturnType<typeof event<EventName>> | ||
export function from<EventName extends keyof EventMap>( | ||
node: EventTarget, | ||
name?: EventName, | ||
options?: boolean | AddEventListenerOptions, | ||
): InputSource | EventSource<EventName> { | ||
) { | ||
if (!name && (node as any).tagName && ( | ||
(node as any).tagName === 'INPUT' || (node as any).tagName === 'TEXTAREA' | ||
)) { | ||
return new InputSource(node as HTMLInputElement) | ||
return input(node as HTMLInputElement) | ||
} else { | ||
return new EventSource(node, name ?? 'click' as EventName, options) | ||
return event(node, name ?? 'click' as EventName, options) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,13 @@ | ||
export * from './source' | ||
export * from './noop' | ||
export * from './create' | ||
export * from './signal' | ||
export * from './timer' | ||
export * from './subject' | ||
export * from './state' | ||
export * from './observe' | ||
export * from './iterate' | ||
export * from './event' | ||
export * from './input' | ||
export * from './from' | ||
export * from './types' | ||
export * from './disposable' | ||
export * from './source' | ||
export * from './util/listenable' | ||
export * from './util/disposable' | ||
|
Oops, something went wrong.