-- selene: allow(unscoped_variables, global_usage) _G.maivi = _G.maivi or {} --[[@as table]] maivi.data = {} -- util ======================================================================= -- https://github.com/neovim/neovim/blob/04f802a2f6387028e3389334e6e04abc28d92587/runtime/lua/vim/F.lua#L58-L66 --- like {...} except preserve the length explicitly function maivi.pack_len(...) return { n = select("#", ...), ... } end --- like unpack() but use the length set by F.pack_len if present function maivi.unpack_len(t) return unpack(t, 1, t.n) end function maivi.print(...) local t = maivi.pack_len(...) for i = 1, t.n do print(maivi.inspect(t[i])) end end ---@generic T ---@param t table ---@param f fun(k: string): T ---@param mt metatable function maivi.defaulttable(t, f, mt) local mt_ = { __index = function(self, k) local v = f(k) rawset(self, k, v) return rawget(self, k) end, } if mt then for k, v in pairs(mt) do mt_[k] = v end end return setmetatable(t, mt_) end -- elem ======================================================================= ---@alias Displayable string | number | boolean | fun(): Displayable ---@alias Props table ---@alias Child Displayable | VNode ---@alias Children Child[] ---@alias PropsOrChildren { [string]: Displayable, [integer]: Child }|Child ---@alias VNode { type: ElementType?, props: Props, children: Children } ---@alias ElementType string | Component ---@alias Element fun(args: PropsOrChildren): VNode ---@alias Component fun(props: Props, children: Children): VNode ---@class ElementConfig ---@field close? boolean ---@field empty? boolean ---@class ParsedArgs ---@field props Props ---@field children Children ---@field boolean_props string[] ---@param etype ElementType? ---@param props Props ---@param children Children ---@return VNode function maivi.h(etype, props, children) if type(etype) == "function" then return etype(props, children) end return { type = etype, props = props, children = children } end ---@param etype? ElementType ---@param config? ElementConfig ---@return Element function maivi.elem(etype, config) return function(args) local parsed = maivi.parse(args) return setmetatable(maivi.h(etype, parsed.props, parsed.children), { config = config, }) end end -- components ================================================================= --- minimized version of `maivi.elem` since components dont use `.config` --- @param f Component --- @return Element function maivi.component(f) return function(args) local parsed = maivi.parse(args) return maivi.h(f, parsed.props, parsed.children) end end ---@alias Slots table ---@param f fun(slots: Slots): Element ---@return fun(page: Component): VNode function maivi.layout(f) return function(page) return function(args) local parsed = maivi.parse(args) local slots = page(parsed.props, parsed.children) return f(slots)(parsed.props, parsed.children) end end end -- props & children ============================================================ ---@param args PropsOrChildren? ---@return ParsedArgs function maivi.parse(args) ---@type Props local props = {} ---@type Children local children = {} ---@type string[] local boolean_props = {} if type(args) == "table" and getmetatable(args) == nil then for k, v in pairs(args) do if type(k) == "number" then table.insert(children, v) elseif k == "_" and type(v) == "table" then for _, prop in ipairs(v) do table.insert(boolean_props, prop) end elseif k == "data" and type(v) == "table" then for kdata, prop in pairs(v) do props["data-" .. kdata] = maivi.display.attr(prop) end else props[k] = maivi.display.attr(v) end end elseif args ~= nil then table.insert(children, args) end return { props = props, children = children, boolean_props = boolean_props, } end -- modules ==================================================================== maivi.mod = setmetatable({ graph = {}, }, { ---@param t table ---@param modname string __call = function(t, modname) local info = debug.getinfo(2, "Sln") local source = info.source t.graph[source] = t.graph[source] or {} table.insert(t.graph[source], modname) return function(...) if this.dev then package.loaded[modname] = nil end local ok, result = pcall(require, modname) if not ok then error( ([[%s:%s error while loading module '%s':\t\n%s]]):format( info.short_src, info.currentline, modname, result ) ) end if type(result) ~= "function" then error( ([[%s:%s return value from module '%s' is not a function]]):format( info.short_src, info.currentline, modname ) ) end return result(...) end end, }) -- hooks ======================================================================= ---@alias ctx table ---@alias props table ---@alias hook fun(ctx: ctx) maivi._hooks = {} ---@param event string ---@param props props ---@param fn hook function maivi.hook(event, props, fn) maivi._hooks[event] = maivi._hooks[event] or {} table.insert(maivi._hooks[event], { props = props, fn = fn }) end ---@param ref props ---@param props props ---@return boolean local function matchhook(ref, props) for k, v in pairs(props) do if k == "pattern" and ref.pattern then return string.match(ref.pattern, v) ~= nil elseif ref[k] ~= v then return false end end return true end ---@param ctx ctx ---@param event string ---@param props props function maivi.trigger(ctx, event, props) local hooks = maivi._hooks[event] if not hooks then return end for _, hook in ipairs(hooks) do -- fallen can be nil if ctx.fallen ~= true and matchhook(props, hook.props) then hook.fn(ctx) end end end return maivi