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:
Andrea Fletcher 2026-05-26 22:10:47 -07:00
parent 19eaeb424f
commit 18c1d02d43
No known key found for this signature in database
7 changed files with 276 additions and 0 deletions

71
src/tag.luau Normal file
View file

@ -0,0 +1,71 @@
local action = require "./action"()
local cleanup = require "./cleanup"
local effect = require "./effect"
type Tags = string | { string }
local function build_set(value: Tags): { [string]: true }
local set: { [string]: true } = {}
if type(value) == "string" then
set[value] = true
else
for _, name in value do
if type(name) ~= "string" then
error("tag() expects a string or an array of strings", 0)
end
set[name] = true
end
end
return set
end
local function tag(value: Tags | () -> Tags)
return action(function(instance)
if type(value) == "function" then
local current: { [string]: true } = {}
effect(function()
local new_set = build_set((value :: () -> Tags)())
for name in current do
if not new_set[name] then
instance:RemoveTag(name)
end
end
for name in new_set do
if not current[name] then
instance:AddTag(name)
end
end
current = new_set
end)
cleanup(function()
for name in current do
instance:RemoveTag(name)
end
end)
else
local set = build_set(value)
for name in set do
instance:AddTag(name)
end
cleanup(function()
for name in set do
instance:RemoveTag(name)
end
end)
end
end)
end
type Action = typeof(action(nil :: any))
return tag :: ((tag: string) -> Action)
& ((tags: { string }) -> Action)
& ((source: () -> string) -> Action)
& ((source: () -> { string }) -> Action)