Skip to content
Merged

[Tag] #423

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion aftman.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
# To add a new tool, add an entry to this table.
[tools]
rojo = "rojo-rbx/rojo@7.4.1"
selene = "Kampfkarren/selene@0.26.1"
selene = "Kampfkarren/selene@0.26.1"
Comment thread
dphfox marked this conversation as resolved.
luau-lsp = "JohnnyMorganz/luau-lsp@1.38.1"
61 changes: 61 additions & 0 deletions src/Instances/Tag.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
--!strict
--!nolint LocalUnused
--!nolint LocalShadow
local task = nil -- Disable usage of Roblox's task scheduler

--[[
A special key for property tables, which allows users to apply custom
CollectionService tags to instances
]]

local Package = script.Parent.Parent
local Types = require(Package.Types)
-- Memory
local checkLifetime = require(Package.Memory.checkLifetime)
-- Graph
local Observer = require(Package.Graph.Observer)
-- State
local castToState = require(Package.State.castToState)
local peek = require(Package.State.peek)

local keyCache: { [string]: Types.SpecialKey } = {}

-- TODO: should this accept tagName: UsedAs<string>?
local function Tag(tagName: string): Types.SpecialKey
local key = keyCache[tagName]
if key == nil then
key = {
type = "SpecialKey",
kind = "Tag",
stage = "self",
apply = function(self: Types.SpecialKey, scope: Types.Scope<unknown>, value: unknown, applyTo: Instance)
if castToState(value) then
local value = value :: Types.StateObject<unknown>
checkLifetime.bOutlivesA(
scope,
applyTo,
value.scope,
value.oldestTask,
checkLifetime.formatters.boundTag,
tagName
)
Observer(scope, value :: any):onBind(function()
if peek(value) == true then
applyTo:AddTag(tagName)
elseif applyTo:HasTag(tagName) then
applyTo:RemoveTag(tagName)
end
end)
else
if value == true then
applyTo:AddTag(tagName)
end
end
end,
}
keyCache[tagName] = key
end
return key
end

return Tag
10 changes: 10 additions & 0 deletions src/Memory/checkLifetime.luau
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ function checkLifetime.formatters.boundAttribute(
return `The {boundName} (bound to the {attribute} attribute)`, `the {selfName} instance`
end

function checkLifetime.formatters.boundTag(
instance: Instance,
bound: unknown,
tag: string
): (string, string)
local selfName = instance.Name
local boundName = nameOf(bound, "value")
return `The {boundName} (bound to the {tag} CollectionService tag)`, `the {selfName} instance`
end

function checkLifetime.formatters.propertyOutputsTo(
instance: Instance,
bound: unknown,
Expand Down
1 change: 1 addition & 0 deletions src/Types.luau
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ export type Fusion = {
Attribute: (attributeName: string) -> SpecialKey,
AttributeChange: (attributeName: string) -> SpecialKey,
AttributeOut: (attributeName: string) -> SpecialKey,
Tag: (tagName: string) -> SpecialKey,
}

export type ExternalProvider = {
Expand Down
7 changes: 4 additions & 3 deletions src/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ do
External.setExternalProvider(RobloxExternal)
end

local Fusion: Fusion = table.freeze {
local Fusion: Fusion = table.freeze({
-- General
version = {major = 0, minor = 4, isRelease = false},
version = { major = 0, minor = 4, isRelease = false },
Contextual = require(script.Utility.Contextual),
Safe = require(script.Utility.Safe),

Expand Down Expand Up @@ -73,10 +73,11 @@ local Fusion: Fusion = table.freeze {
OnChange = require(script.Instances.OnChange),
OnEvent = require(script.Instances.OnEvent),
Out = require(script.Instances.Out),
Tag = require(script.Instances.Tag),

-- Animation
Tween = require(script.Animation.Tween),
Spring = require(script.Animation.Spring),
}
})

return Fusion
51 changes: 51 additions & 0 deletions test/Spec/Instances/Tag.spec.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--!strict
--!nolint LocalUnused
local task = nil -- Disable usage of Roblox's task scheduler

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Fusion = ReplicatedStorage.Fusion

local New = require(Fusion.Instances.New)
local Tag = require(Fusion.Instances.Tag)
local Value = require(Fusion.State.Value)
local doCleanup = require(Fusion.Memory.doCleanup)

return function()
local it = getfenv().it

it("adds tags (constant)", function()
local expect = getfenv().expect

local scope = {}
local child = New(scope, "Folder") {
[Tag "Foo"] = true
}
expect(child:HasTag("Foo")).to.equal(true)
doCleanup(scope)
end)

it("adds tags (state)", function()
local expect = getfenv().expect

local scope = {}
local addTag = Value(scope, true)
local child = New(scope, "Folder") {
[Tag "Foo"] = addTag
}
expect(child:HasTag("Foo")).to.equal(true)
end)

it("adds/removes tag when state objects are updated", function()
local expect = getfenv().expect

local scope = {}
local tagExists = Value(scope, true)
local child = New(scope, "Folder") {
[Tag "Foo"] = tagExists
}
expect(child:HasTag("Foo")).to.equal(true)
tagExists:set(false)
expect(child:HasTag("Foo")).to.equal(false)
doCleanup(scope)
end)
end