Component
The Component
class is a powerful tool that enables developers to create customized user interface elements with specific properties, mappings, and events. These elements can be nested inside other components to form a hierarchy, thus allowing for a more complex and modular UI design.
In addition to enabling the creation of custom components, Component
also provides various methods for attaching mappings, rendering the component and its children, focusing on the component, retrieving information about the component's state, and setting and getting properties. This allows for greater flexibility when designing a UI, as developers can easily modify the behavior and appearance of their components as needed.
Furthermore, the Component
supports modifying the buffer content, handling layout changes, and managing mounting and unmounting phases.
Create a new component
To create a custom component called Counter
, we first extend the Component
class and pass the name of our component as the second parameter.
local Component = require("nui-components.component")
local Counter = Component:extend("Counter")
return Counter
Next, we define an initialization function for our custom component Counter
called :init(props)
. This function takes a single argument props
that contains all the properties passed to the component during its creation. It initializes the superclass component by passing it the props and some default values.
local Component = require("nui-components.component")
local Counter = Component:extend("Counter")
function Counter:init(props)
Counter.super.init(
self,
vim.tbl_extend("force", {
value = 1,
on_change = function() end,
}, props)
)
end
return Counter
We define the property types for our custom component Counter
using :prop_types()
. In this case, we define two properties - value
and on_change
. This method validates the properties passed to the component.
local Component = require("nui-components.component")
local Counter = Component:extend("Counter")
function Counter:init(props)
Counter.super.init(
self,
vim.tbl_extend("force", {
value = 1,
on_change = function() end,
}, props)
)
end
function Counter:prop_types()
return {
value = "number",
on_change = "function",
}
end
return Counter
We'll now proceed to define :initial_value()
. As the name suggests, this method sets the initial value of the component.
local Component = require("nui-components.component")
local Counter = Component:extend("Counter")
function Counter:init(props)
Counter.super.init(
self,
vim.tbl_extend("force", {
value = 1,
on_change = function() end,
}, props)
)
end
function Counter:prop_types()
return {
value = "number",
on_change = "function",
}
end
function Counter:initial_value()
return self:get_props().value
end
return Counter
To get the current value of our custom component Counter
, we use the function :get_current_value()
. If the value
property is a signal value, it will return the value associated with that signal.
local Component = require("nui-components.component")
local Counter = Component:extend("Counter")
function Counter:init(props)
Counter.super.init(
self,
vim.tbl_extend("force", {
value = 1,
on_change = function() end,
}, props)
)
end
function Counter:prop_types()
return {
value = "number",
on_change = "function",
}
end
function Counter:initial_value()
return self:get_props().value
end
function Counter:get_current_value()
local props = self:get_props()
if props.instance:is_signal("value") then
return props.value
end
return Counter.super.get_current_value(self)
end
return Counter
We define some keyboard mappings using :mappings()
. In this case, we define two mappings: one increments the counter when the user presses the up
arrow, and another decrements it when the user presses the down
arrow.
local Component = require("nui-components.component")
local Counter = Component:extend("Counter")
function Counter:init(props)
Counter.super.init(
self,
vim.tbl_extend("force", {
value = 1,
on_change = function() end,
}, props)
)
end
function Counter:prop_types()
return {
value = "number",
on_change = "function",
}
end
function Counter:initial_value()
return self:get_props().value
end
function Counter:get_current_value()
local props = self:get_props()
if props.instance:is_signal("value") then
return props.value
end
return Counter.super.get_current_value(self)
end
function Counter:mappings()
local props = self:get_props()
return {
{
mode = { "n" },
key = "<Up>",
handler = function()
local value = self:get_current_value() + 1
self:set_current_value(value)
props.on_change(value)
end,
},
{
mode = { "n" },
key = "<Down>",
handler = function()
local value = self:get_current_value() - 1
self:set_current_value(value)
props.on_change(value)
end,
},
}
end
return Counter
To display the current value, we use the :get_lines()
method. This method returns a new NuiLine
containing a current value.
local Component = require("nui-components.component")
local Counter = Component:extend("Counter")
function Counter:init(props)
Counter.super.init(
self,
vim.tbl_extend("force", {
value = 1,
on_change = function() end,
}, props)
)
end
function Counter:prop_types()
return {
value = "number",
on_change = "function",
}
end
function Counter:initial_value()
return self:get_props().value
end
function Counter:get_current_value()
local props = self:get_props()
if props.instance:is_signal("value") then
return props.value
end
return Counter.super.get_current_value(self)
end
function Counter:mappings()
local props = self:get_props()
return {
{
mode = { "n" },
key = "<Up>",
handler = function()
local value = self:get_current_value() + 1
self:set_current_value(value)
props.on_change(value)
end,
},
{
mode = { "n" },
key = "<Down>",
handler = function()
local value = self:get_current_value() - 1
self:set_current_value(value)
props.on_change(value)
end,
},
}
end
function Counter:get_lines()
local line = Line()
line:append(tostring(self:get_current_value()))
return line
end
return Counter
We define a layout method using :on_layout()
. This method calculates the height and width required for it to display properly. In this case, we calculate the width based on the length of the line returned by the get_lines()
method, and set the height to 1
(a single line).
local Component = require("nui-components.component")
local Counter = Component:extend("Counter")
function Counter:init(props)
Counter.super.init(
self,
vim.tbl_extend("force", {
value = 1,
on_change = function() end,
}, props)
)
end
function Counter:prop_types()
return {
value = "number",
on_change = "function",
}
end
function Counter:initial_value()
return self:get_props().value
end
function Counter:get_current_value()
local props = self:get_props()
if props.instance:is_signal("value") then
return props.value
end
return Counter.super.get_current_value(self)
end
function Counter:mappings()
local props = self:get_props()
return {
{
mode = { "n" },
key = "<Up>",
handler = function()
local value = self:get_current_value() + 1
self:set_current_value(value)
props.on_change(value)
end,
},
{
mode = { "n" },
key = "<Down>",
handler = function()
local value = self:get_current_value() - 1
self:set_current_value(value)
props.on_change(value)
end,
},
}
end
function Counter:get_lines()
local line = Line()
line:append(tostring(self:get_current_value()))
return line
end
function Counter:on_layout()
return {
height = 1,
width = self:get_lines():width(),
}
end
return Counter
Finally, we have the :on_update()
method, called whenever the component needs to be updated, such as when a signal is emitted. This method modifies the buffer content of our custom component Counter
, rendering the Line
component returned by our get_lines()
method.
local Component = require("nui-components.component")
local Line = require("nui.line")
local Counter = Component:extend("Counter")
function Counter:init(props)
Counter.super.init(
self,
vim.tbl_extend("force", {
value = 1,
on_change = function() end,
}, props)
)
end
function Counter:prop_types()
return {
value = "number",
on_change = "function",
}
end
function Counter:initial_value()
return self:get_props().value
end
function Counter:get_current_value()
local props = self:get_props()
if props.instance:is_signal("value") then
return props.value
end
return Counter.super.get_current_value(self)
end
function Counter:mappings()
local props = self:get_props()
return {
{
mode = { "n" },
key = "<Up>",
handler = function()
local value = self:get_current_value() + 1
self:set_current_value(value)
props.on_change(value)
end,
},
{
mode = { "n" },
key = "<Down>",
handler = function()
local value = self:get_current_value() - 1
self:set_current_value(value)
props.on_change(value)
end,
},
}
end
function Counter:get_lines()
local line = Line()
line:append(tostring(self:get_current_value()))
return line
end
function Counter:on_layout()
return {
height = 1,
width = self:get_lines():width(),
}
end
function Counter:on_update()
self:modify_buffer_content(function()
local content = self:get_lines()
content:render(self.bufnr, -1, 1)
end)
end
return Counter
Usage example:
local n = require("nui-components")
local counter = require("components.counter")
local renderer = n.create_renderer({
width = 60,
height = 3,
})
local signal = n.create_signal({
value = 1,
})
local body = function()
return counter({
border_label = {
text = "Counter",
align = "center",
},
autofocus = true,
value = signal.value,
on_change = function(value)
signal.value = value
end,
})
end
renderer:render(body)
And the final result of what we have already implemented:
Properties
size
Determines the size of the component based on the direction of the component's parent.
number
flex
Determines whether a component should grow or shrink based on the size of its container. When the
flex
is set to0
, it will be sized according to its own content. On the other hand, if theflex
value is set to a number greater than0
, it will be flexible and will grow or shrink based on the available space in its container.
number
id
Assigns a unique identifier to the component.
tostring(math.random())
string
hidden
Defines whether or not the component should be displayed.
false
boolean
autofocus
Indicates whether the component should receive focus automatically upon mounting.
false
boolean
is_focusable
Indicates whether the component can be focused or not.
true
boolean
direction
Determines the direction in which the content of the component will be displayed.
row
'row' | 'column'
border_label
Defines the label displayed on the border of the component.
string | NuiText | BorderLabel
Where BorderLabel
is:
{
text = NuiText | string,
icon = string,
edge = top | bottom,
align = left | center | right
}
border_style
Defines the style property of the border around the component.
row
'double' | 'none' | 'rounded' | 'shadow' | 'single' | 'solid'
padding
Defines the amount of padding space that surrounds the content area of the component.
number[] | Padding
Where Padding
is:
{
top = number,
right = number,
bottom = number,
left = number
}
window
Allows you to modify the appearance and behavior of the floating window.
WindowOptions
Where WindowOptions
is:
{
blend = number,
highlight = table | string
}
Examples:
window = {
highlight = {
FloatBorder = "Normal",
NormalFloat = "String",
}
}
window = {
highlight = "FloatBorder:Normal,NormalFloat:String"
}
global_focus_key
Specifies a global key used to focus on the component.
string
on_focus
Defines a callback function that is executed when the component gains focus.
fn.ignore
fun(component: Component): nil
on_blur
Defines a callback function that is executed when the component loses focus.
fn.ignore
fun(component: Component): nil
on_mount
Defines a callback function that is executed when the component is mounted.
fn.ignore
fun(component: Component): nil
on_unmount
Defines a callback function that is executed when the component is unmounted.
fn.ignore
fun(component: Component): nil
events
Defines any events that the component listens for and responds to accordingly.
fun(component: Component): Event[]
Where Event
is:
{
event = string | string[],
handler = function,
options? = {
once? = boolean
nested? = boolean
}
}
For further information, please refer to the documentation of nui.nvim
(opens in a new tab).
mappings
Add additional keyboard shortcuts for the component.
fun(component: Component): Mapping[]
Where Mapping
is:
{
mode = string[] | string, -- "n", "i", "v"
key = string,
handler = function
}
validate
Defines a callback function that is executed to validate the component's state. This is used along with the
Form
component.
fun(value T): boolean
Methods
redraw
Redraws the component's visual representation.
:redraw()
render
Creates the component's visual representation
:render()
focus
Lets the user focus on a specific component.
:focus()
events
Allows the developer to define custom events for the component.
:events()
Event[]
mappings
Allows the developer to define custom mappings for the component.
:mappings()
Mapping[]
initial_value
Sets the component's initial content value.
:initial_value()
T
modify_buffer_content
Allows the user to modify the buffer content of the component.
:modify_buffer_content(callback_fn)
callback_fn | fun(): nil |
is_hidden
Determines whether or not a specific component is hidden.
:is_hidden()
boolean
is_focused
Determines whether or not a specific component is focused.
:is_focused()
boolean
is_focusable
Determines whether or not a specific component is focusable.
:is_focusable()
boolean
is_first_render
Determines whether the component is rendered for the first time or not.
:is_first_render()
boolean
get_id
Returns the unique identifier for the current component instance.
:get_id()
string
get_direction
Returns the current direction of the component.
:get_direction()
column | row
get_children
Returns a table of child components for the current component instance.
:get_children()
Component[] | nil
get_flatten_children
Returns a flattened table of all child components for the current component instance.
:get_flatten_children()
Component[] | nil
get_only_child
Returns the single (first) child component instance for the current component instance.
:get_only_child()
Component | nil
get_parent
Returns the current component's parent component instance, if any (
nil
otherwise).
:get_parent()
Component | nil
get_renderer
Returns the current component's renderer instance.
:get_renderer()
Renderer
get_props
Returns a table of props for the current component instance.
:get_props()
table
get_current_value
Returns the current value of the component if a value is set (
nil
otherwise).
:get_current_value()
T | nil
get_focus_index
Returns the focus index for the current component instance
:get_focus_index()
number | nil
set_border_text
Allows the developer to set border text values for a specific component's edge. text and align.
:set_border_text(edge, text, align)
edge | 'top' | 'bottom' |
text | string |
align | 'left' | 'center' | 'right' |
set_buffer_option
The method is used to modify buffer options for the current component instance.
:set_buffer_option(key, value)
key | string |
value | any |
set_current_value
Sets the specific value for the current component instance.
:set_current_value(value)
value | T |
prop_types
Returns a table of specific prop types for the current component. This is used to validate properties passed to the component.
:prop_types()
table
on_renderer_initialization
The method is called during a component's renderer's initialization process.
:on_renderer_initialization(renderer, parent, children)
renderer | Renderer |
parent | Component | nil |
children | Component[] | nil |
on_update
The method gets executed every time the value of the signal, which is passed as a property, changes.
:on_update()
on_mount
The method is called when the component is first mounted into the component tree.
:on_mount()
on_unmount
The method is called when the component is removed from the component tree.
:on_unmount()
on_layout
The method is called when the layout of the component has been updated.
:on_layout()
on_visibility_change
The method is called whenever the component's visibility state changes.
:on_visibility_change()