value-enhancer
Advanced tools
Comparing version 2.4.1 to 2.4.2
interface ReadonlyVal<TValue = any> { | ||
/** value */ | ||
readonly value: TValue; | ||
/** Compare two values. */ | ||
/** Compare two values. Default `===`. */ | ||
compare(newValue: TValue, oldValue: TValue): boolean; | ||
@@ -30,8 +30,9 @@ /** | ||
/** set new value */ | ||
set(this: void, value: TValue): void; | ||
readonly set: (this: void, value: TValue) => void; | ||
} | ||
declare type ValCompare<TValue = any> = (newValue: TValue, oldValue: TValue) => boolean; | ||
declare type ValSubscriber<TValue = any> = (newValue: TValue) => void; | ||
declare type ValDisposer = () => void; | ||
declare type ValOnStart = () => void | ValDisposer | undefined; | ||
type ValCompare<TValue = any> = (newValue: TValue, oldValue: TValue) => boolean; | ||
type ValSubscriber<TValue = any> = (newValue: TValue) => void; | ||
type ValDisposer = () => void; | ||
/** @ignore */ | ||
type ValOnStart = () => void | ValDisposer | undefined; | ||
interface ValConfig<TValue = any> { | ||
@@ -48,6 +49,8 @@ /** | ||
} | ||
declare type TValInputsValueTuple<TValInputs extends readonly ReadonlyVal[]> = Readonly<{ | ||
/** @ignore */ | ||
type ValInputsValueTuple<TValInputs extends readonly ReadonlyVal[]> = Readonly<{ | ||
[K in keyof TValInputs]: ExtractValValue<TValInputs[K]>; | ||
}>; | ||
declare type ExtractValValue<TVal> = TVal extends ReadonlyVal<infer TValue> ? TValue : never; | ||
/** @ignore */ | ||
type ExtractValValue<TVal> = TVal extends ReadonlyVal<infer TValue> ? TValue : never; | ||
@@ -118,2 +121,4 @@ declare enum SubscriberMode { | ||
declare const identity: <TValue>(value: TValue) => TValue; | ||
/** @returns `true` if `val` is `ReadonlyVal` or `Val`. */ | ||
declare const isVal: <T>(val: T) => val is T extends ReadonlyVal<any> ? T : never; | ||
@@ -132,3 +137,4 @@ /** | ||
declare type DerivedValTransform<TValue = any, TDerivedValue = any> = (newValue: TValue) => TDerivedValue; | ||
/** @ignore */ | ||
type DerivedValTransform<TValue = any, TDerivedValue = any> = (newValue: TValue) => TDerivedValue; | ||
/** | ||
@@ -149,3 +155,4 @@ * Derive a new val with same value from the given val. | ||
declare type CombineValTransform<TDerivedValue = any, TValues extends readonly any[] = any[], TMeta = any> = (newValues: TValues, oldValues?: TValues, meta?: TMeta) => TDerivedValue; | ||
/** @ignore */ | ||
type CombineValTransform<TDerivedValue = any, TValues extends readonly any[] = any[], TMeta = any> = (newValues: TValues, oldValues?: TValues, meta?: TMeta) => TDerivedValue; | ||
/** | ||
@@ -156,3 +163,3 @@ * Combines an array of vals into a single val with the array of values. | ||
*/ | ||
declare function combine<TValInputs extends readonly ReadonlyVal[] = ReadonlyVal[]>(valInputs: readonly [...TValInputs]): ReadonlyVal<[...TValInputsValueTuple<TValInputs>]>; | ||
declare function combine<TValInputs extends readonly ReadonlyVal[] = ReadonlyVal[]>(valInputs: readonly [...TValInputs]): ReadonlyVal<[...ValInputsValueTuple<TValInputs>]>; | ||
/** | ||
@@ -165,3 +172,3 @@ * Combines an array of vals into a single val with transformed value. | ||
*/ | ||
declare function combine<TValInputs extends readonly ReadonlyVal[] = ReadonlyVal[], TValue = any>(valInputs: readonly [...TValInputs], transform: CombineValTransform<TValue, [...TValInputsValueTuple<TValInputs>]>, config?: ValConfig<TValue>): ReadonlyVal<TValue>; | ||
declare function combine<TValInputs extends readonly ReadonlyVal[] = ReadonlyVal[], TValue = any>(valInputs: readonly [...TValInputs], transform: CombineValTransform<TValue, [...ValInputsValueTuple<TValInputs>]>, config?: ValConfig<TValue>): ReadonlyVal<TValue>; | ||
@@ -185,7 +192,25 @@ /** | ||
*/ | ||
declare function unwrap<TValue = any>(val: ReadonlyVal<Val<TValue> | ReadonlyVal<TValue>>): ReadonlyVal<TValue>; | ||
declare function unwrap<TValue = any>(val: ReadonlyVal<ReadonlyVal<TValue>>): ReadonlyVal<TValue>; | ||
/** | ||
* Unwrap a val of val to a val of the inner val value. | ||
* @param val Input value. | ||
* @returns An readonly val with value of inner val. | ||
* | ||
* @example | ||
* ```js | ||
* import { unwrap, val } from "value-enhancer"; | ||
* | ||
* const inner$ = val(12); | ||
* const outer$ = val(inner$); | ||
* | ||
* const unwrapped$ = unwrap(outer$); | ||
* | ||
* inner$.value === unwrapped$.value; // true | ||
* ``` | ||
*/ | ||
declare function unwrap<TValue = any>(val: ReadonlyVal<Val<TValue>>): ReadonlyVal<TValue>; | ||
/** | ||
* Unwrap an inner val extracted from a source val to a val of the inner val value. | ||
* @param val Input value. | ||
* @param get extract inner val from source val. | ||
* @param get extract inner val or value from source val. | ||
* @returns An readonly val with value of inner val. | ||
@@ -205,3 +230,3 @@ * | ||
*/ | ||
declare function unwrap<TSrcValue = any, TValue = any>(val: ReadonlyVal<TSrcValue>, get: (value: TSrcValue) => ReadonlyVal<TValue>, config?: ValConfig<TValue>): ReadonlyVal<TValue>; | ||
declare function unwrap<TSrcValue = any, TValue = any>(val: ReadonlyVal<TSrcValue>, get: (value: TSrcValue) => ReadonlyVal<TValue> | TValue, config?: ValConfig<TValue>): ReadonlyVal<TValue>; | ||
@@ -221,3 +246,3 @@ /** | ||
*/ | ||
declare const subscribe: <TValue>(val: ReadonlyVal<TValue>, subscriber: ValSubscriber<TValue>, eager?: boolean | undefined) => ValDisposer; | ||
declare const subscribe: <TValue>(val: ReadonlyVal<TValue>, subscriber: ValSubscriber<TValue>, eager?: boolean) => ValDisposer; | ||
/** | ||
@@ -230,3 +255,3 @@ * Subscribe to value changes without immediate emission. | ||
*/ | ||
declare const reaction: <TValue>(val: ReadonlyVal<TValue>, subscriber: ValSubscriber<TValue>, eager?: boolean | undefined) => ValDisposer; | ||
declare const reaction: <TValue>(val: ReadonlyVal<TValue>, subscriber: ValSubscriber<TValue>, eager?: boolean) => ValDisposer; | ||
/** | ||
@@ -240,2 +265,2 @@ * Remove the given subscriber. | ||
export { ReadonlyVal, ReadonlyValImpl, Val, ValCompare, ValConfig, ValDisposer, ValSubscriber, combine, derive, identity, reaction, setValue, subscribe, unsubscribe, unwrap, val }; | ||
export { CombineValTransform, DerivedValTransform, ExtractValValue, ReadonlyVal, ReadonlyValImpl, Val, ValCompare, ValConfig, ValDisposer, ValInputsValueTuple, ValOnStart, ValSubscriber, combine, derive, identity, isVal, reaction, setValue, subscribe, unsubscribe, unwrap, val }; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
// src/scheduler.ts | ||
@@ -34,2 +32,5 @@ var nextTick = /* @__PURE__ */ Promise.resolve(); | ||
var INIT_VALUE = {}; | ||
var VAL_SYMBOL = "$\u2009val\u2009"; | ||
var isVal = (val2) => !!(val2 && val2[VAL_SYMBOL]); | ||
var compute = (val2, subscriber) => val2._c(subscriber); | ||
@@ -99,3 +100,4 @@ // src/subscribers.ts | ||
} | ||
if (mode === 1 /* Async */ || this[1 /* Async */] <= 0) { | ||
if (mode === 1 /* Async */ || /* mode === SubscriberMode.Computed */ | ||
this[1 /* Async */] <= 0) { | ||
this.s = false; | ||
@@ -137,2 +139,3 @@ } | ||
constructor(value, { compare, eager = false } = {}, start) { | ||
Object.defineProperty(this, VAL_SYMBOL, { value: 1 }); | ||
this._v = value; | ||
@@ -163,2 +166,6 @@ this.eager = eager; | ||
} | ||
/** | ||
* @internal | ||
* For computed vals | ||
*/ | ||
_c(subscriber) { | ||
@@ -174,5 +181,23 @@ return this._u.a(subscriber, 3 /* Computed */); | ||
} | ||
/** | ||
* @returns the string representation of `this.value`. | ||
* | ||
* @example | ||
* ```js | ||
* const v$ = val(val(val(1))); | ||
* console.log(`${v$}`); // "1" | ||
* ``` | ||
*/ | ||
toString() { | ||
return String(this.value); | ||
} | ||
/** | ||
* @returns the JSON representation of `this.value`. | ||
* | ||
* @example | ||
* ```js | ||
* const v$ = val(val(val({ a: 1 }))); | ||
* JSON.stringify(v$); // '{"a":1}' | ||
* ``` | ||
*/ | ||
toJSON(key) { | ||
@@ -195,2 +220,3 @@ const value = this.value; | ||
} | ||
/** Set new value */ | ||
set = this._s; | ||
@@ -212,3 +238,3 @@ }; | ||
} | ||
return val2._c(() => { | ||
return compute(val2, () => { | ||
if (this._d < 2) { | ||
@@ -254,3 +280,3 @@ this._d = 2; | ||
const disposers = valInputs.map( | ||
(val2) => val2._c(() => { | ||
(val2) => compute(val2, () => { | ||
if (this._d < 2) { | ||
@@ -290,13 +316,7 @@ this._d = 2; | ||
constructor(val2, get = identity, config) { | ||
let innerVal; | ||
const getValue2 = () => { | ||
if (!innerVal || this._u.b.size <= 0) { | ||
innerVal = get(val2.value); | ||
} | ||
return innerVal.value; | ||
}; | ||
let innerValue; | ||
super(INIT_VALUE, config, () => { | ||
innerVal = get(val2.value); | ||
innerValue = get(val2.value); | ||
if (this._v === INIT_VALUE) { | ||
this._v = innerVal.value; | ||
this._v = isVal(innerValue) ? innerValue.value : innerValue; | ||
} else { | ||
@@ -311,15 +331,21 @@ this._d = this._d || 1; | ||
}; | ||
let innerDisposer = innerVal._c(markDirty); | ||
const outerDisposer = val2._c(() => { | ||
innerDisposer(); | ||
innerVal = get(val2.value); | ||
innerDisposer = innerVal._c(markDirty); | ||
let innerDisposer = isVal(innerValue) && compute(innerValue, markDirty); | ||
const outerDisposer = compute(val2, () => { | ||
innerDisposer && innerDisposer(); | ||
innerValue = get(val2.value); | ||
innerDisposer = isVal(innerValue) && compute(innerValue, markDirty); | ||
markDirty(); | ||
}); | ||
return () => { | ||
innerDisposer(); | ||
innerDisposer && innerDisposer(); | ||
outerDisposer(); | ||
}; | ||
}); | ||
this._g = getValue2; | ||
this._g = () => { | ||
if (this._u.b.size <= 0 || !innerValue) { | ||
innerValue = get(val2.value); | ||
} | ||
return isVal(innerValue) ? innerValue.value : innerValue; | ||
}; | ||
this._m = (newValue, oldValue) => isVal(innerValue) ? innerValue.compare(newValue, oldValue) : super.compare(newValue, oldValue); | ||
} | ||
@@ -340,5 +366,6 @@ get value() { | ||
} | ||
compare(_newValue, _oldValue) { | ||
return false; | ||
compare(newValue, oldValue) { | ||
return this._m(newValue, oldValue); | ||
} | ||
_m; | ||
_g; | ||
@@ -361,2 +388,3 @@ _d = 0; | ||
exports.identity = identity; | ||
exports.isVal = isVal; | ||
exports.reaction = reaction; | ||
@@ -368,2 +396,3 @@ exports.setValue = setValue; | ||
exports.val = val; | ||
//# sourceMappingURL=out.js.map | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "value-enhancer", | ||
"version": "2.4.1", | ||
"version": "2.4.2", | ||
"private": false, | ||
@@ -26,22 +26,4 @@ "description": "A tiny library to enhance value with reactive wrapper.", | ||
], | ||
"devDependencies": { | ||
"@jest/globals": "^28.1.3", | ||
"@types/node": "^18.8.5", | ||
"@typescript-eslint/eslint-plugin": "^4.31.0", | ||
"@typescript-eslint/parser": "^4.31.1", | ||
"cross-env": "^7.0.3", | ||
"eslint": "^7.32.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"gzip-size": "^7.0.0", | ||
"jest": "^28.1.3", | ||
"prettier": "^2.4.0", | ||
"pretty-bytes": "^6.0.0", | ||
"standard-version": "^9.3.1", | ||
"ts-jest": "^28.0.8", | ||
"tsup": "^6.2.3", | ||
"typedoc": "^0.23.15", | ||
"typescript": "^4.4.3", | ||
"yoctocolors": "^1.0.0" | ||
}, | ||
"scripts": { | ||
"prepublishOnly": "pnpm run build", | ||
"lint": "eslint --ext .ts,.tsx . && prettier --check .", | ||
@@ -55,3 +37,22 @@ "test": "jest", | ||
"release": "standard-version" | ||
}, | ||
"devDependencies": { | ||
"@jest/globals": "^29.5.0", | ||
"@types/node": "^18.15.0", | ||
"@typescript-eslint/eslint-plugin": "^5.54.1", | ||
"@typescript-eslint/parser": "^5.54.1", | ||
"cross-env": "^7.0.3", | ||
"eslint": "^8.35.0", | ||
"eslint-config-prettier": "^8.7.0", | ||
"gzip-size": "^7.0.0", | ||
"jest": "^29.5.0", | ||
"prettier": "^2.8.4", | ||
"pretty-bytes": "^6.1.0", | ||
"standard-version": "^9.5.0", | ||
"ts-jest": "^29.0.5", | ||
"tsup": "^6.6.3", | ||
"typedoc": "^0.23.26", | ||
"typescript": "^4.9.5", | ||
"yoctocolors": "^1.0.0" | ||
} | ||
} | ||
} |
122
README.md
@@ -194,4 +194,6 @@ # [value-enhancer](https://github.com/crimx/value-enhancer) | ||
## Create Val | ||
```js | ||
import { val, combine, derive } from "value-enhancer"; | ||
import { val } from "value-enhancer"; | ||
@@ -205,14 +207,63 @@ const count$ = val(2); | ||
count$.subscribe(count => console.log(`subscribe: ${count}`)); // subscribe: 3 | ||
count$.value = 4; | ||
console.log(count$.value); // 4 | ||
``` | ||
count$.reaction(count => console.log(`reaction: ${count}`)); // (nothing printed, only subscribe to changes) | ||
## Subscribe to value changes | ||
```js | ||
import { val, combine, derive } from "value-enhancer"; | ||
const count$ = val(3); | ||
// Emit the current value synchronously, then emit the new value when it changes. | ||
const disposeSubscribe = count$.subscribe(count => { | ||
console.log(`subscribe: ${count}`); | ||
}); // printed "subscribe: 3" | ||
// Only emit the new value when it changes. | ||
const disposeReaction = count$.reaction(count => { | ||
console.log(`reaction: ${count}`); | ||
}); // (nothing printed) | ||
// `===` equality check by default | ||
count$.set(3); // nothing happened | ||
count$.value = 4; // prints subscribe: 4, reaction: 4 | ||
// subscription triggered asynchronously by default | ||
count$.set(4); // nothing happened | ||
const derive$ = derive(count$, count => count * 3); | ||
console.log(derived$.value); // 12 | ||
derived$.subscribe(derived => console.log(`derived: ${derived}`)); // derived: 12 | ||
await Promise.resolve(); // wait for the next tick | ||
// printed "subscribe: 4" | ||
// printed "reaction: 4" | ||
disposeSubscribe(); | ||
disposeReaction(); | ||
``` | ||
## Derive Val | ||
Derive a new Val from another Val. | ||
```js | ||
import { val, derive } from "value-enhancer"; | ||
const count$ = val(2); | ||
const derived$ = derive(count$, count => count * 3); | ||
console.log(derived$.value); // 6 | ||
``` | ||
## Combine Val | ||
Combine multiple Vals into a new Val. | ||
```js | ||
import { val, derive, combine } from "value-enhancer"; | ||
const count$ = val(2); | ||
const derived$ = derive(count$, count => count * 3); | ||
const combined$ = combine( | ||
@@ -222,10 +273,57 @@ [count$, derived$], | ||
); | ||
console.log(combined$.value); // 16 | ||
combined$.subscribe(combined => console.log(`combined: ${combined}`)); // combined: 16 | ||
count$.set(5); // subscribe: 5, reaction: 5, derived: 15, combined: 20 | ||
console.log(combined$.value); // 8 | ||
``` | ||
## Legacy | ||
## Unwrap Val | ||
[(v1)](https://github.com/crimx/value-enhancer/tree/v1) | ||
Unwrap the inner Val from a Val of Val. This is useful for subscribing to a dynamic Val that is inside another Val. | ||
```js | ||
import { val, unwrap } from "value-enhancer"; | ||
const itemList$ = val([val(1), val(2), val(3)]); | ||
const firstItem$ = unwrap(itemList$, itemList => itemList[0]); | ||
console.log(firstItem$.value); // 1 | ||
itemList$.set([val(4), val(5), val(6)]); | ||
console.log(firstItem$.value); // 4 | ||
``` | ||
## Custom Compare | ||
By default, `===` equality check is used to determine whether a value has changed. You can customize the equality check by passing a `compare` function. | ||
```js | ||
import { val } from "value-enhancer"; | ||
const isSameXYPosition = (p1, p2) => p1.x === p2.x && p1.y === p2.y; | ||
const isSameXYZPosition = (p1, p2) => | ||
p1.x === p2.x && p1.y === p2.y && p1.z === p2.z; | ||
const xyzPosition$ = val({ x: 0, y: 0, z: 0 }, { compare: isSameXYZPosition }); | ||
const xyPosition$ = derive(xyPosition, { compare: isSameXYPosition }); | ||
xyPosition$.set({ x: 0, y: 0, z: 0 }); // nothing happened | ||
``` | ||
## Synchronous subscription | ||
Subscription is triggered asynchronously on next tick by default. To trigger synchronously, set `eager` parameter to `true`. | ||
```js | ||
count$.subscribe(count => console.log(`subscribe: ${count}`), true); | ||
count$.reaction(count => console.log(`reaction: ${count}`), true); | ||
``` | ||
Or set `eager` to `true` when creating the Val. | ||
```js | ||
// subscription of count$ is trigger synchronously by default | ||
const count$ = val(3, { eager: true }); | ||
const derived$ = derive(count$, count => count * 3, { eager: true }); | ||
``` |
import { ReadonlyValImpl } from "./readonly-val"; | ||
import type { ReadonlyVal, TValInputsValueTuple, ValConfig } from "./typings"; | ||
import { invoke, getValues, INIT_VALUE, identity } from "./utils"; | ||
import type { ReadonlyVal, ValInputsValueTuple, ValConfig } from "./typings"; | ||
import { invoke, getValues, INIT_VALUE, identity, compute } from "./utils"; | ||
type CombineValTransform< | ||
/** @ignore */ | ||
export type CombineValTransform< | ||
TDerivedValue = any, | ||
@@ -22,3 +23,3 @@ TValues extends readonly any[] = any[], | ||
TValue, | ||
[...TValInputsValueTuple<TValInputs>] | ||
[...ValInputsValueTuple<TValInputs>] | ||
>, | ||
@@ -36,3 +37,3 @@ config?: ValConfig<TValue> | ||
const disposers = valInputs.map(val => | ||
(val as ReadonlyValImpl)._compute_(() => { | ||
compute(val, () => { | ||
if (this._dirtyLevel_ < 2) { | ||
@@ -78,3 +79,3 @@ this._dirtyLevel_ = 2; | ||
valInputs: readonly [...TValInputs] | ||
): ReadonlyVal<[...TValInputsValueTuple<TValInputs>]>; | ||
): ReadonlyVal<[...ValInputsValueTuple<TValInputs>]>; | ||
/** | ||
@@ -92,3 +93,3 @@ * Combines an array of vals into a single val with transformed value. | ||
valInputs: readonly [...TValInputs], | ||
transform: CombineValTransform<TValue, [...TValInputsValueTuple<TValInputs>]>, | ||
transform: CombineValTransform<TValue, [...ValInputsValueTuple<TValInputs>]>, | ||
config?: ValConfig<TValue> | ||
@@ -103,6 +104,6 @@ ): ReadonlyVal<TValue>; | ||
TValue, | ||
[...TValInputsValueTuple<TValInputs>] | ||
[...ValInputsValueTuple<TValInputs>] | ||
> = identity as CombineValTransform< | ||
TValue, | ||
[...TValInputsValueTuple<TValInputs>] | ||
[...ValInputsValueTuple<TValInputs>] | ||
>, | ||
@@ -109,0 +110,0 @@ config?: ValConfig<TValue> |
import { ReadonlyValImpl } from "./readonly-val"; | ||
import type { ReadonlyVal, ValConfig } from "./typings"; | ||
import { identity, INIT_VALUE } from "./utils"; | ||
import { compute, identity, INIT_VALUE } from "./utils"; | ||
type DerivedValTransform<TValue = any, TDerivedValue = any> = ( | ||
/** @ignore */ | ||
export type DerivedValTransform<TValue = any, TDerivedValue = any> = ( | ||
newValue: TValue | ||
@@ -26,3 +27,3 @@ ) => TDerivedValue; | ||
} | ||
return (val as ReadonlyValImpl)._compute_(() => { | ||
return compute(val, () => { | ||
if (this._dirtyLevel_ < 2) { | ||
@@ -29,0 +30,0 @@ this._dirtyLevel_ = 2; |
@@ -8,2 +8,5 @@ export type { | ||
ValConfig, | ||
ValInputsValueTuple, | ||
ExtractValValue, | ||
ValOnStart, | ||
} from "./typings"; | ||
@@ -13,9 +16,9 @@ | ||
export { identity } from "./utils"; | ||
export { identity, isVal } from "./utils"; | ||
export { val } from "./val"; | ||
export { derive } from "./derived-val"; | ||
export { combine } from "./combine"; | ||
export { derive, type DerivedValTransform } from "./derived-val"; | ||
export { combine, type CombineValTransform } from "./combine"; | ||
export { unwrap } from "./unwrap-val"; | ||
export { setValue, subscribe, reaction, unsubscribe } from "./value-enhancer"; |
@@ -9,3 +9,3 @@ import { SubscriberMode, Subscribers } from "./subscribers"; | ||
} from "./typings"; | ||
import { invoke } from "./utils"; | ||
import { invoke, VAL_SYMBOL } from "./utils"; | ||
@@ -30,2 +30,4 @@ export class ReadonlyValImpl<TValue = any> implements ReadonlyVal<TValue> { | ||
) { | ||
Object.defineProperty(this, VAL_SYMBOL, { value: 1 }); | ||
this._value_ = value; | ||
@@ -32,0 +34,0 @@ |
export interface ReadonlyVal<TValue = any> { | ||
/** value */ | ||
readonly value: TValue; | ||
/** Compare two values. */ | ||
/** Compare two values. Default `===`. */ | ||
compare(newValue: TValue, oldValue: TValue): boolean; | ||
@@ -31,3 +31,3 @@ /** | ||
/** set new value */ | ||
set(this: void, value: TValue): void; | ||
readonly set: (this: void, value: TValue) => void; | ||
} | ||
@@ -46,2 +46,3 @@ | ||
/** @ignore */ | ||
export type ValOnStart = () => void | ValDisposer | undefined; | ||
@@ -61,3 +62,4 @@ | ||
export type TValInputsValueTuple<TValInputs extends readonly ReadonlyVal[]> = | ||
/** @ignore */ | ||
export type ValInputsValueTuple<TValInputs extends readonly ReadonlyVal[]> = | ||
Readonly<{ | ||
@@ -67,4 +69,5 @@ [K in keyof TValInputs]: ExtractValValue<TValInputs[K]>; | ||
/** @ignore */ | ||
export type ExtractValValue<TVal> = TVal extends ReadonlyVal<infer TValue> | ||
? TValue | ||
: never; |
import { ReadonlyValImpl } from "./readonly-val"; | ||
import type { ReadonlyVal, Val, ValConfig } from "./typings"; | ||
import { identity, INIT_VALUE } from "./utils"; | ||
import { compute, identity, INIT_VALUE, isVal } from "./utils"; | ||
@@ -11,18 +11,12 @@ class UnwrapValImpl<TSrcValue = any, TValue = any> | ||
val: ReadonlyVal<TSrcValue>, | ||
get: (value: TSrcValue) => ReadonlyVal<TValue> = identity as any, | ||
get: (value: TSrcValue) => ReadonlyVal<TValue> | TValue = identity as any, | ||
config?: ValConfig<TValue> | ||
) { | ||
let innerVal: ReadonlyVal<TValue> | undefined; | ||
const getValue = () => { | ||
if (!innerVal || this._subs_.subscribers_.size <= 0) { | ||
innerVal = get(val.value); | ||
} | ||
return innerVal.value; | ||
}; | ||
let innerValue: ReadonlyVal<TValue> | TValue | undefined; | ||
super(INIT_VALUE, config, () => { | ||
innerVal = get(val.value); | ||
innerValue = get(val.value); | ||
if (this._value_ === INIT_VALUE) { | ||
this._value_ = innerVal.value; | ||
this._value_ = isVal(innerValue) ? innerValue.value : innerValue; | ||
} else { | ||
@@ -38,11 +32,13 @@ this._dirtyLevel_ = this._dirtyLevel_ || 1; | ||
}; | ||
let innerDisposer = (innerVal as ReadonlyValImpl)._compute_(markDirty); | ||
const outerDisposer = (val as ReadonlyValImpl)._compute_(() => { | ||
innerDisposer(); | ||
innerVal = get(val.value); | ||
innerDisposer = (innerVal as ReadonlyValImpl)._compute_(markDirty); | ||
let innerDisposer = isVal(innerValue) && compute(innerValue, markDirty); | ||
const outerDisposer = compute(val, () => { | ||
innerDisposer && innerDisposer(); | ||
innerValue = get(val.value); | ||
innerDisposer = isVal(innerValue) && compute(innerValue, markDirty); | ||
markDirty(); | ||
}); | ||
return () => { | ||
innerDisposer(); | ||
innerDisposer && innerDisposer(); | ||
outerDisposer(); | ||
@@ -52,3 +48,13 @@ }; | ||
this._getValue_ = getValue; | ||
this._getValue_ = (): TValue => { | ||
if (this._subs_.subscribers_.size <= 0 || !innerValue) { | ||
innerValue = get(val.value); | ||
} | ||
return isVal(innerValue) ? innerValue.value : innerValue; | ||
}; | ||
this._compare_ = (newValue, oldValue) => | ||
isVal(innerValue) | ||
? innerValue.compare(newValue, oldValue) | ||
: super.compare(newValue, oldValue); | ||
} | ||
@@ -71,7 +77,8 @@ | ||
public override compare(_newValue: TValue, _oldValue: TValue): boolean { | ||
public override compare(newValue: TValue, oldValue: TValue): boolean { | ||
// follow upstream val by default | ||
return false; | ||
return this._compare_(newValue, oldValue); | ||
} | ||
private _compare_: (newValue: TValue, oldValue: TValue) => boolean; | ||
private _getValue_: () => TValue; | ||
@@ -99,8 +106,28 @@ private _dirtyLevel_ = 0; | ||
export function unwrap<TValue = any>( | ||
val: ReadonlyVal<Val<TValue> | ReadonlyVal<TValue>> | ||
val: ReadonlyVal<ReadonlyVal<TValue>> | ||
): ReadonlyVal<TValue>; | ||
/** | ||
* Unwrap a val of val to a val of the inner val value. | ||
* @param val Input value. | ||
* @returns An readonly val with value of inner val. | ||
* | ||
* @example | ||
* ```js | ||
* import { unwrap, val } from "value-enhancer"; | ||
* | ||
* const inner$ = val(12); | ||
* const outer$ = val(inner$); | ||
* | ||
* const unwrapped$ = unwrap(outer$); | ||
* | ||
* inner$.value === unwrapped$.value; // true | ||
* ``` | ||
*/ | ||
export function unwrap<TValue = any>( | ||
val: ReadonlyVal<Val<TValue>> | ||
): ReadonlyVal<TValue>; | ||
/** | ||
* Unwrap an inner val extracted from a source val to a val of the inner val value. | ||
* @param val Input value. | ||
* @param get extract inner val from source val. | ||
* @param get extract inner val or value from source val. | ||
* @returns An readonly val with value of inner val. | ||
@@ -122,3 +149,3 @@ * | ||
val: ReadonlyVal<TSrcValue>, | ||
get: (value: TSrcValue) => ReadonlyVal<TValue>, | ||
get: (value: TSrcValue) => ReadonlyVal<TValue> | TValue, | ||
config?: ValConfig<TValue> | ||
@@ -128,3 +155,3 @@ ): ReadonlyVal<TValue>; | ||
val: ReadonlyVal<TSrcValue>, | ||
get?: (value: TSrcValue) => ReadonlyVal<TValue>, | ||
get?: (value: TSrcValue) => ReadonlyVal<TValue> | TValue, | ||
config?: ValConfig<TValue> | ||
@@ -131,0 +158,0 @@ ): ReadonlyVal<TValue> { |
@@ -1,2 +0,8 @@ | ||
import type { ReadonlyVal, TValInputsValueTuple } from "./typings"; | ||
import type { ReadonlyValImpl } from "./readonly-val"; | ||
import type { | ||
ReadonlyVal, | ||
ValInputsValueTuple, | ||
ValDisposer, | ||
ValSubscriber, | ||
} from "./typings"; | ||
@@ -10,4 +16,4 @@ /** Returns the value passed in. */ | ||
valInputs: TValInputs | ||
): [...TValInputsValueTuple<TValInputs>] => | ||
valInputs.map(getValue) as [...TValInputsValueTuple<TValInputs>]; | ||
): [...ValInputsValueTuple<TValInputs>] => | ||
valInputs.map(getValue) as [...ValInputsValueTuple<TValInputs>]; | ||
@@ -26,1 +32,12 @@ export const invoke = <TValue>( | ||
export const INIT_VALUE: any = {}; | ||
export const VAL_SYMBOL = "$\u2009val\u2009"; | ||
/** @returns `true` if `val` is `ReadonlyVal` or `Val`. */ | ||
export const isVal = <T>(val: T): val is T extends ReadonlyVal ? T : never => | ||
!!(val && (val as any)[VAL_SYMBOL]); | ||
export const compute = ( | ||
val: ReadonlyVal, | ||
subscriber: ValSubscriber<void> | ||
): ValDisposer => (val as ReadonlyValImpl)._compute_(subscriber); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
124309
327
19
1719