mirror of
https://github.com/centau/vide.git
synced 2026-08-20 23:01:37 +00:00
Add tag() action for CollectionService tags
Adds a built-in `vide.tag()` action that wraps `Instance:AddTag()` / `Instance:RemoveTag()` so tags can be applied through `create()` props. It accepts a static string or array of strings, or a reactive source returning either. The reactive variant diffs the previous and new tag sets so only the delta is applied. All tags added by the action are removed when the surrounding scope is destroyed. Includes mock support for `AddTag`/`RemoveTag`/`HasTag`/`GetTags`, tests, API reference docs, and a closing note in the actions tutorial. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
19eaeb424f
commit
18c1d02d43
7 changed files with 276 additions and 0 deletions
|
|
@ -66,6 +66,7 @@ local Instance = {} :: any do
|
|||
children: { Data },
|
||||
changed: { [string]: RBXScriptSignal },
|
||||
properties: { [string]: unknown },
|
||||
tags: { [string]: true },
|
||||
destroying: RBXScriptSignal,
|
||||
class: string,
|
||||
type: "Instance"
|
||||
|
|
@ -158,6 +159,7 @@ local Instance = {} :: any do
|
|||
children = {},
|
||||
changed = {},
|
||||
properties = {},
|
||||
tags = {},
|
||||
class = class,
|
||||
destroying = Signal.new() :: any,
|
||||
type = "Instance" :: "Instance"
|
||||
|
|
@ -211,6 +213,29 @@ local Instance = {} :: any do
|
|||
return data.changed[property]
|
||||
end
|
||||
|
||||
function methods.AddTag(userdata: userdata, tag: string)
|
||||
if type(tag) ~= "string" then error("tag must be a string", 2) end
|
||||
get_data(userdata).tags[tag] = true
|
||||
end
|
||||
|
||||
function methods.RemoveTag(userdata: userdata, tag: string)
|
||||
if type(tag) ~= "string" then error("tag must be a string", 2) end
|
||||
get_data(userdata).tags[tag] = nil
|
||||
end
|
||||
|
||||
function methods.HasTag(userdata: userdata, tag: string): boolean
|
||||
if type(tag) ~= "string" then error("tag must be a string", 2) end
|
||||
return get_data(userdata).tags[tag] == true
|
||||
end
|
||||
|
||||
function methods.GetTags(userdata: userdata): { string }
|
||||
local out = {}
|
||||
for name in get_data(userdata).tags do
|
||||
table.insert(out, name)
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
function methods.Destroy(userdata: userdata)
|
||||
local data = get_data(userdata);
|
||||
Signal.fire(data.destroying)
|
||||
|
|
|
|||
117
test/tests.luau
117
test/tests.luau
|
|
@ -24,6 +24,7 @@ local vide = require "../../vide"
|
|||
local spring = vide.spring
|
||||
local action = vide.action
|
||||
local changed = vide.changed
|
||||
local tag = vide.tag
|
||||
local apply = vide.apply
|
||||
local step = vide.step
|
||||
local graph = require "../../vide/src/graph"
|
||||
|
|
@ -2340,6 +2341,122 @@ TEST("changed()", wrap_root(function()
|
|||
end
|
||||
end))
|
||||
|
||||
TEST("tag()", wrap_root(function()
|
||||
do CASE "apply single tag"
|
||||
local frame = create "Frame" {
|
||||
tag("MyTag")
|
||||
}
|
||||
|
||||
CHECK(frame:HasTag("MyTag"))
|
||||
CHECK(#frame:GetTags() == 1)
|
||||
end
|
||||
|
||||
do CASE "apply multiple tags"
|
||||
local frame = create "Frame" {
|
||||
tag({ "A", "B", "C" })
|
||||
}
|
||||
|
||||
CHECK(frame:HasTag("A"))
|
||||
CHECK(frame:HasTag("B"))
|
||||
CHECK(frame:HasTag("C"))
|
||||
CHECK(#frame:GetTags() == 3)
|
||||
end
|
||||
|
||||
do CASE "compose multiple tag() actions"
|
||||
local frame = create "Frame" {
|
||||
tag("A"),
|
||||
tag("B")
|
||||
}
|
||||
|
||||
CHECK(frame:HasTag("A"))
|
||||
CHECK(frame:HasTag("B"))
|
||||
end
|
||||
|
||||
do CASE "remove tags on scope destroy"
|
||||
local _, frame, destroy = root(function(destroy)
|
||||
return create "Frame" {
|
||||
tag({ "A", "B" })
|
||||
}, destroy
|
||||
end)
|
||||
|
||||
CHECK(frame:HasTag("A"))
|
||||
CHECK(frame:HasTag("B"))
|
||||
|
||||
destroy()
|
||||
|
||||
CHECK(not frame:HasTag("A"))
|
||||
CHECK(not frame:HasTag("B"))
|
||||
end
|
||||
|
||||
do CASE "reactive single tag"
|
||||
local current = source("A")
|
||||
|
||||
local frame = create "Frame" {
|
||||
tag(current)
|
||||
}
|
||||
|
||||
CHECK(frame:HasTag("A"))
|
||||
CHECK(#frame:GetTags() == 1)
|
||||
|
||||
current("B")
|
||||
|
||||
CHECK(not frame:HasTag("A"))
|
||||
CHECK(frame:HasTag("B"))
|
||||
CHECK(#frame:GetTags() == 1)
|
||||
end
|
||||
|
||||
do CASE "reactive multiple tags"
|
||||
local current = source({ "A", "B" })
|
||||
|
||||
local frame = create "Frame" {
|
||||
tag(current)
|
||||
}
|
||||
|
||||
CHECK(frame:HasTag("A"))
|
||||
CHECK(frame:HasTag("B"))
|
||||
CHECK(#frame:GetTags() == 2)
|
||||
|
||||
-- preserve "A", remove "B", add "C"
|
||||
current({ "A", "C" })
|
||||
|
||||
CHECK(frame:HasTag("A"))
|
||||
CHECK(not frame:HasTag("B"))
|
||||
CHECK(frame:HasTag("C"))
|
||||
CHECK(#frame:GetTags() == 2)
|
||||
end
|
||||
|
||||
do CASE "reactive tag removed on scope destroy"
|
||||
local _, frame, destroy = root(function(destroy)
|
||||
local current = source("A")
|
||||
return create "Frame" {
|
||||
tag(current)
|
||||
}, destroy
|
||||
end)
|
||||
|
||||
CHECK(frame:HasTag("A"))
|
||||
|
||||
destroy()
|
||||
|
||||
CHECK(not frame:HasTag("A"))
|
||||
CHECK(#frame:GetTags() == 0)
|
||||
end
|
||||
|
||||
do CASE "reactive tag derived from source"
|
||||
local name = source("hello")
|
||||
|
||||
local frame = create "Frame" {
|
||||
tag(function() return "prefix-" .. name() end)
|
||||
}
|
||||
|
||||
CHECK(frame:HasTag("prefix-hello"))
|
||||
|
||||
name("world")
|
||||
|
||||
CHECK(not frame:HasTag("prefix-hello"))
|
||||
CHECK(frame:HasTag("prefix-world"))
|
||||
end
|
||||
end))
|
||||
|
||||
TEST("batch()", wrap_root(function()
|
||||
do CASE "evaluation deferred"
|
||||
local a = source(0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue