import { Property } from '../../components/Property' import { Method } from '@grapp/nextra-theme'
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.
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 CounterNext, 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 CounterWe 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 CounterWe'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 CounterTo 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 CounterWe 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 CounterTo 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 CounterWe 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 CounterFinally, 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 CounterUsage 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:
Determines the size of the component based on the direction of the component's parent.
<Property types={['number']} />
Determines whether a component should grow or shrink based on the size of its container. When the
flexis set to0, it will be sized according to its own content. On the other hand, if theflexvalue is set to a number greater than0, it will be flexible and will grow or shrink based on the available space in its container.
<Property types={['number']} />
Assigns a unique identifier to the component.
<Property defaultValue="tostring(math.random())" types={['string']} />
hidden
Defines whether or not the component should be displayed.
<Property defaultValue="false" types={['boolean']} />
Indicates whether the component should receive focus automatically upon mounting.
<Property defaultValue="false" types={['boolean']} />
Indicates whether the component can be focused or not.
<Property defaultValue="true" types={['boolean']} />
Determines the direction in which the content of the component will be displayed.
<Property defaultValue="row" types={["'row' | 'column'"]} />
Defines the label displayed on the border of the component.
<Property types={['string | NuiText | BorderLabel']}
Where BorderLabel is:
{
text = NuiText | string,
icon = string,
edge = top | bottom,
align = left | center | right
}Defines the style property of the border around the component.
<Property defaultValue="row" types={["'double' | 'none' | 'rounded' | 'shadow' | 'single' | 'solid'"]} />
Defines the amount of padding space that surrounds the content area of the component.
<Property types={['number[] | Padding']}
Where Padding is:
{
top = number,
right = number,
bottom = number,
left = number
}Allows you to modify the appearance and behavior of the floating window.
<Property types={['WindowOptions']}
Where WindowOptions is:
{
blend = number,
highlight = table | string
}Examples:
window = {
highlight = {
FloatBorder = "Normal",
NormalFloat = "String",
}
}
window = {
highlight = "FloatBorder:Normal,NormalFloat:String"
}Specifies a global key used to focus on the component.
<Property types={['string']} />
Defines a callback function that is executed when the component gains focus.
<Property defaultValue="fn.ignore" types={['fun(component: Component): nil']} />
Defines a callback function that is executed when the component loses focus.
<Property defaultValue="fn.ignore" types={['fun(component: Component): nil']} />
Defines a callback function that is executed when the component is mounted.
<Property defaultValue="fn.ignore" types={['fun(component: Component): nil']} />
Defines a callback function that is executed when the component is unmounted.
<Property defaultValue="fn.ignore" types={['fun(component: Component): nil']} />
Defines any events that the component listens for and responds to accordingly.
<Property types={['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.
Add additional keyboard shortcuts for the component.
<Property types={['fun(component: Component): Mapping[]']}
Where Mapping is:
{
mode = string[] | string, -- "n", "i", "v"
key = string,
handler = function
}Defines a callback function that is executed to validate the component's state. This is used along with the
Formcomponent.
<Property types={['fun(value T): boolean']} />
Redraws the component's visual representation.
Creates the component's visual representation
Lets the user focus on a specific component.
Allows the developer to define custom events for the component.
Allows the developer to define custom mappings for the component.
Sets the component's initial content value.
Allows the user to modify the buffer content of the component.
<Method name="modify_buffer_content" args={[ ['callback_fn', 'fun(): nil'] ]} />
is_hidden
Determines whether or not a specific component is hidden.
Determines whether or not a specific component is focused.
Determines whether or not a specific component is focusable.
Determines whether the component is rendered for the first time or not.
Returns the unique identifier for the current component instance.
Returns the current direction of the component.
Returns a table of child components for the current component instance.
Returns a flattened table of all child components for the current component instance.
Returns the single (first) child component instance for the current component instance.
Returns the current component's parent component instance, if any (
nilotherwise).
Returns the current component's renderer instance.
Returns a table of props for the current component instance.
Returns the current value of the component if a value is set (
nilotherwise).
Returns the focus index for the current component instance
Allows the developer to set border text values for a specific component's edge. text and align.
<Method name="set_border_text" args={[ ['edge', "'top' | 'bottom'"], ['text', 'string'], ['align', "'left' | 'center' | 'right'"], ]} />
The method is used to modify buffer options for the current component instance.
<Method name="set_buffer_option" args={[ ['key', 'string'], ['value', 'any'] ]} />
Sets the specific value for the current component instance.
<Method name="set_current_value" args={[ ['value', 'T'] ]} />
Returns a table of specific prop types for the current component. This is used to validate properties passed to the component.
The method is called during a component's renderer's initialization process.
<Method name="on_renderer_initialization" args={[ ['renderer', 'Renderer'], ['parent', 'Component | nil'], ['children', 'Component[] | nil'], ]} />
The method gets executed every time the value of the signal, which is passed as a property, changes.
The method is called when the component is first mounted into the component tree.
The method is called when the component is removed from the component tree.
The method is called when the layout of the component has been updated.
The method is called whenever the component's visibility state changes.
