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:
richard 2024-03-25 19:53:28 -07:00 • committed by GitHub
parent 4d4979daa9
commit 33ecae2f50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 577 additions and 729 deletions

View file

@ -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