mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
refactor!: design overhaul (#1)
* refactor!: use atom functions * docs: reformat api reference * docs: showcase atom functions * docs: remove unused import * refactor: finalize updates * chore: misc edits
This commit is contained in:
parent
4d4979daa9
commit
33ecae2f50
39 changed files with 577 additions and 729 deletions
132
src/atom.luau
132
src/atom.luau
|
|
@ -1,126 +1,34 @@
|
|||
local reference = require(script.Parent.utils.reference)
|
||||
local store = require(script.Parent.store)
|
||||
local types = require(script.Parent.types)
|
||||
type Atom<T> = types.Atom<T>
|
||||
type AtomOptions<T> = types.AtomOptions<T>
|
||||
|
||||
type AtomInternal<T> = {
|
||||
setForced: (self: AtomInternal<T>, state: T | ((T) -> T)) -> (),
|
||||
listeners: { [(T) -> ()]: true },
|
||||
debugTraceback: string,
|
||||
debugLabel: string,
|
||||
owners: { [any]: true },
|
||||
}
|
||||
local function atom<T>(state: T, options: AtomOptions<T>?): Atom<T>
|
||||
local equals = options and options.equals
|
||||
|
||||
export type Atom<T> = (() -> T) & ((state: T | ((T) -> T)) -> ()) & {
|
||||
get: ((self: Atom<T>) -> T) & (<Result>(self: Atom<T>, selector: (T) -> Result) -> Result),
|
||||
set: (self: Atom<T>, state: T | ((T) -> T)) -> (),
|
||||
memo: (self: Atom<T>, areEqual: (previous: T, current: T) -> boolean) -> Atom<T>,
|
||||
writable: (self: Atom<T>, onSet: ((update: T, previous: T) -> T?) & (update: T, previous: T) -> ()) -> Atom<T>,
|
||||
readonly: (self: Atom<T>) -> ReadonlyAtom<T>,
|
||||
named: (self: Atom<T>, label: string) -> Atom<T>,
|
||||
unmount: (self: Atom<T>) -> (),
|
||||
} & AtomInternal<T>
|
||||
|
||||
export type ReadonlyAtom<T> = (() -> T) & {
|
||||
get: ((self: ReadonlyAtom<T>) -> T) & (<Result>(self: ReadonlyAtom<T>, selector: (T) -> Result) -> Result),
|
||||
memo: (self: ReadonlyAtom<T>, areEqual: (previous: T, current: T) -> boolean) -> ReadonlyAtom<T>,
|
||||
writable: ((self: Atom<T>) -> Atom<T>)
|
||||
& ((self: Atom<T>, onSet: ((update: T, previous: T) -> T?) & (update: T, previous: T) -> ()) -> Atom<T>),
|
||||
readonly: (self: ReadonlyAtom<T>) -> ReadonlyAtom<T>,
|
||||
named: (self: ReadonlyAtom<T>, label: string) -> ReadonlyAtom<T>,
|
||||
unmount: (self: ReadonlyAtom<T>) -> (),
|
||||
} & AtomInternal<T>
|
||||
|
||||
local atomCount = 0
|
||||
|
||||
local function atom<State>(state: State): Atom<State>
|
||||
atomCount += 1
|
||||
|
||||
local atom = {
|
||||
listeners = {},
|
||||
debugTraceback = debug.traceback(nil, 2),
|
||||
debugLabel = `atom{atomCount}`,
|
||||
owners = {},
|
||||
} :: Atom<State>
|
||||
|
||||
local equals: ((State, State) -> boolean)?
|
||||
local setter: ((State, State) -> State?)?
|
||||
|
||||
function atom:get<Result>(selector: ((State) -> Result)?): any
|
||||
return if selector then selector(state) else state
|
||||
end
|
||||
|
||||
function atom:set(update: State | ((State) -> State))
|
||||
local nextState = if type(update) == "function" then update(state) else update
|
||||
|
||||
if setter then
|
||||
local result = setter(nextState, state)
|
||||
|
||||
if result ~= nil then
|
||||
nextState = result
|
||||
else
|
||||
return
|
||||
local function atom(...)
|
||||
if select("#", ...) == 0 then
|
||||
if store.shouldCapture() then
|
||||
store.captured[atom] = true
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
|
||||
if not (if equals then equals(state, nextState) else state == nextState) then
|
||||
local nextState = if type(...) == "function" then (...)(state) else ...
|
||||
|
||||
if state ~= nextState and not (equals and equals(state, nextState)) then
|
||||
state = nextState
|
||||
|
||||
for listener in table.clone(self.listeners) do
|
||||
listener(state)
|
||||
for listener in pairs(table.clone(store.listeners[atom])) do
|
||||
listener()
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
|
||||
function atom:setForced(update: State | ((State) -> State))
|
||||
local nextState = if type(update) == "function" then update(state) else update
|
||||
|
||||
if not (if equals then equals(state, nextState) else state == nextState) then
|
||||
state = nextState
|
||||
|
||||
for listener in table.clone(self.listeners) do
|
||||
listener(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function atom:memo(areEqual: (State, State) -> boolean): Atom<State>
|
||||
equals = areEqual
|
||||
return self
|
||||
end
|
||||
|
||||
function atom:writable(onSet: ((State, State) -> State?)?): Atom<State>
|
||||
setter = onSet
|
||||
return self
|
||||
end
|
||||
|
||||
function atom:readonly(): ReadonlyAtom<State>
|
||||
function setter(_, _)
|
||||
if _G.__DEV__ then
|
||||
error(`Attempt to set read-only atom '{self.debugLabel}' created at: {self.debugTraceback}`, 3)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function atom:named(name: string): Atom<State>
|
||||
self.debugLabel = name
|
||||
return self
|
||||
end
|
||||
|
||||
function atom:unmount()
|
||||
table.clear(self.listeners)
|
||||
table.clear(self.owners)
|
||||
reference.release(self)
|
||||
end
|
||||
|
||||
setmetatable(atom :: AtomInternal<State>, {
|
||||
__type = "Atom",
|
||||
__call = function(self, ...)
|
||||
if select("#", ...) > 0 then
|
||||
return self:set((...))
|
||||
else
|
||||
return self:get()
|
||||
end
|
||||
end,
|
||||
})
|
||||
store.listeners[atom] = setmetatable({}, { __mode = "v" })
|
||||
|
||||
return atom
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue