mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
fix: wrap iterated values in reactive()
This commit is contained in:
parent
9d4af8e669
commit
73954f56e7
2 changed files with 47 additions and 11 deletions
|
|
@ -81,6 +81,27 @@ test("should track iteration", function()
|
||||||
assert(table.concat(calls) == "a=2;b=2;", table.concat(calls))
|
assert(table.concat(calls) == "a=2;b=2;", table.concat(calls))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("should track iteration for nested tables", function()
|
||||||
|
local src = reactive({} :: { [string]: { value: number } })
|
||||||
|
local calls = {}
|
||||||
|
|
||||||
|
effect(function()
|
||||||
|
for key, obj in src do
|
||||||
|
table.insert(calls, `{key}={obj.value};`)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
src.a = { value = 1 }
|
||||||
|
|
||||||
|
table.clear(calls)
|
||||||
|
src.b = { value = 1 }
|
||||||
|
assert(table.concat(calls) == "a=1;b=1;", table.concat(calls))
|
||||||
|
|
||||||
|
table.clear(calls)
|
||||||
|
src.a.value = 2
|
||||||
|
assert(table.concat(calls) == "a=2;b=1;", table.concat(calls))
|
||||||
|
end)
|
||||||
|
|
||||||
test("should track array length", function()
|
test("should track array length", function()
|
||||||
local src = reactive({ array = {} :: { number } })
|
local src = reactive({ array = {} :: { number } })
|
||||||
local calls = {}
|
local calls = {}
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ local function setNotPending(sub: ReactiveNode, flags: ReactiveFlags)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function computedOper<T>(this: ComputedNode<T>): () -> T
|
local function computedOper<T>(this: ComputedNode<T>): () -> T
|
||||||
local function computedGetter(): T
|
local function readComputed(): T
|
||||||
local flags = this.flags
|
local flags = this.flags
|
||||||
if
|
if
|
||||||
bit32.btest(flags, DIRTY)
|
bit32.btest(flags, DIRTY)
|
||||||
|
|
@ -253,14 +253,14 @@ local function computedOper<T>(this: ComputedNode<T>): () -> T
|
||||||
end
|
end
|
||||||
return this.value :: T
|
return this.value :: T
|
||||||
end
|
end
|
||||||
return computedGetter
|
return readComputed
|
||||||
end
|
end
|
||||||
|
|
||||||
local function signalOper<T>(
|
local function signalOper<T>(
|
||||||
this: SignalNode<T>,
|
this: SignalNode<T>,
|
||||||
equals: ((current: T, incoming: T) -> boolean)?
|
equals: ((current: T, incoming: T) -> boolean)?
|
||||||
): (() -> T, (((T) -> T) | T) -> T)
|
): (() -> T, (((T) -> T) | T) -> T)
|
||||||
local function signalSetter(update: ((T) -> T) | T): T
|
local function writeSignal(update: ((T) -> T) | T): T
|
||||||
local value: T
|
local value: T
|
||||||
if type(update) == "function" then
|
if type(update) == "function" then
|
||||||
local prevSub = setActiveSub(nil)
|
local prevSub = setActiveSub(nil)
|
||||||
|
|
@ -291,7 +291,7 @@ local function signalOper<T>(
|
||||||
return this.currentValue
|
return this.currentValue
|
||||||
end
|
end
|
||||||
|
|
||||||
local function signalGetter(): T
|
local function readSignal(): T
|
||||||
if bit32.btest(this.flags, DIRTY) then
|
if bit32.btest(this.flags, DIRTY) then
|
||||||
if updateSignal(this) then
|
if updateSignal(this) then
|
||||||
local subs = this.subs
|
local subs = this.subs
|
||||||
|
|
@ -312,7 +312,7 @@ local function signalOper<T>(
|
||||||
return this.currentValue
|
return this.currentValue
|
||||||
end
|
end
|
||||||
|
|
||||||
return signalGetter, signalSetter
|
return readSignal, writeSignal
|
||||||
end
|
end
|
||||||
|
|
||||||
local function effectOper(this: EffectScopeNode): () -> ()
|
local function effectOper(this: EffectScopeNode): () -> ()
|
||||||
|
|
@ -406,9 +406,9 @@ end
|
||||||
@see https://github.com/littensy/charm?tab=readme-ov-file#atominitialValue-equals
|
@see https://github.com/littensy/charm?tab=readme-ov-file#atominitialValue-equals
|
||||||
]]
|
]]
|
||||||
local function atom<T>(initialValue: T, equals: ((current: T, incoming: T) -> boolean)?): Atom<T>
|
local function atom<T>(initialValue: T, equals: ((current: T, incoming: T) -> boolean)?): Atom<T>
|
||||||
local getter, setter = signal(initialValue, equals)
|
local get, set = signal(initialValue, equals)
|
||||||
local function atomOper(...)
|
local function atomOper(...)
|
||||||
return if select("#", ...) == 0 then getter() else setter((...))
|
return if select("#", ...) == 0 then get() else set((...))
|
||||||
end
|
end
|
||||||
return atomOper
|
return atomOper
|
||||||
end
|
end
|
||||||
|
|
@ -625,7 +625,7 @@ end
|
||||||
]]
|
]]
|
||||||
local function toSignal<T>(fn: () -> T): () -> T
|
local function toSignal<T>(fn: () -> T): () -> T
|
||||||
local name = debug.info(fn, "n")
|
local name = debug.info(fn, "n")
|
||||||
return if name == "signalGetter" or name == "computedGetter" or name == "atomOper" then fn else computed(fn)
|
return if name == "readSignal" or name == "readComputed" or name == "atomOper" then fn else computed(fn)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
|
@ -922,11 +922,22 @@ local function reactive<T>(initialValue: T & {}, trackParent: (() -> ())?): (T,
|
||||||
return proxy :: any, setValue
|
return proxy :: any, setValue
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Wraps a value in `reactive()` if it is a table; otherwise, returns the
|
||||||
|
value as-is.
|
||||||
|
|
||||||
|
@param value A value to make reactive.
|
||||||
|
@param trackParent An optional tracking function to propagate deep updates.
|
||||||
|
@return A reactive proxy if the value is a table.
|
||||||
|
]]
|
||||||
|
local function toReactive<T>(value: T, trackParent: (() -> ())?): T
|
||||||
|
return if type(value) == "table" then reactive(value, trackParent) else value
|
||||||
|
end
|
||||||
|
|
||||||
function ReactiveProxy.__index(self: ReactiveProxy, key: any): any
|
function ReactiveProxy.__index(self: ReactiveProxy, key: any): any
|
||||||
local track = self.__track
|
local track = self.__track
|
||||||
local signals = self.__signals
|
local signals = self.__signals
|
||||||
local target = self.__target
|
local target = self.__target
|
||||||
local value = target[key]
|
|
||||||
|
|
||||||
if activeSub then
|
if activeSub then
|
||||||
if not signals[key] then
|
if not signals[key] then
|
||||||
|
|
@ -938,7 +949,7 @@ function ReactiveProxy.__index(self: ReactiveProxy, key: any): any
|
||||||
signals[key]()
|
signals[key]()
|
||||||
end
|
end
|
||||||
|
|
||||||
return if type(value) == "table" then reactive(value, track) else value
|
return toReactive(target[key], track)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ReactiveProxy.__newindex(self: ReactiveProxy, key: any, value: any)
|
function ReactiveProxy.__newindex(self: ReactiveProxy, key: any, value: any)
|
||||||
|
|
@ -951,7 +962,11 @@ end
|
||||||
|
|
||||||
function ReactiveProxy.__iter(self: ReactiveProxy)
|
function ReactiveProxy.__iter(self: ReactiveProxy)
|
||||||
self.__track()
|
self.__track()
|
||||||
return next, self.__target
|
local function nextReactive(target: any, prevKey: any)
|
||||||
|
local nextKey, nextValue = next(target, prevKey)
|
||||||
|
return nextKey, toReactive(nextValue, self.__track)
|
||||||
|
end
|
||||||
|
return nextReactive, self.__target
|
||||||
end
|
end
|
||||||
|
|
||||||
function ReactiveProxy.__len(self: ReactiveProxy)
|
function ReactiveProxy.__len(self: ReactiveProxy)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue