@@ -164,6 +164,7 @@ build instructions.
164164 - [`frame:HookScript(scriptType, handler)`](#framehookscriptscripttype-handler)
165165 - [`frame:IsEventRegistered(event)`](#frameiseventregisteredevent)
166166 - [`frame:GetEffectiveAlpha()`](#framegeteffectivealpha)
167+ - [`frame:SetAttribute` / `SetAttributeNoHandler` / `GetAttribute` (+ unit-frame mouseover)](#framesetattributename-value--framesetattributenohandlername-value--framegetattribute)
167168
168169- [FriendList](#friendlist)
169170 - [`C_FriendList.SendWhoQueryByName(name)`](#c_friendlistsendwhoquerybynamename)
@@ -3779,6 +3780,97 @@ UIParent:GetEffectiveAlpha() -- 1
37793780Minimap:GetEffectiveAlpha() -- ~0.498 (0.5 truncates to 127/255)
37803781```
37813782
3783+ ### `frame:SetAttribute(name, value)` / `frame:SetAttributeNoHandler(name, value)` / `frame:GetAttribute(...)`
3784+
3785+ Backports the frame **attribute** system — a per-frame, case-insensitive
3786+ key→value store — to 1.12 as native methods on every frame. Attributes were
3787+ added in 2.0 with secure frames and don't exist in vanilla at all; `value`
3788+ can be any Lua type and round-trips exactly.
3789+
3790+ ```lua
3791+ f:SetAttribute("unit", "party1")
3792+ f:GetAttribute("unit") -- "party1"
3793+ f:SetAttribute("count", 3)
3794+ f:GetAttribute("count") -- 3
3795+ f:GetAttribute("missing") -- nil
3796+ ```
3797+
3798+ `GetAttribute` also has the modifier form `GetAttribute(prefix, name, suffix)`,
3799+ which tries, in order, and returns the first match (the same precedence retail
3800+ uses for `type1`/`*type1`-style resolution):
3801+
3802+ 1. `prefix..name..suffix`
3803+ 2. `"*"..name..suffix`
3804+ 3. `prefix..name.."*"`
3805+ 4. `"*"..name.."*"`
3806+ 5. `name`
3807+
3808+ `SetAttributeNoHandler` is an alias of `SetAttribute` here — see the
3809+ `OnAttributeChanged` note below.
3810+
3811+ **Unit-frame mouseover — the headline use.** Setting a **string `unit`
3812+ attribute** makes the frame a mouseover source: while the cursor is over it,
3813+ the `mouseover` unit token resolves to that frame's unit. This is the piece of
3814+ SecureUnitButton behavior modern unit-frame addons rely on — and since 1.12 has
3815+ no combat lockdown or taint, no secure machinery is needed to provide it.
3816+
3817+ ```lua
3818+ local f = CreateFrame("Button", "MyUnitFrame", UIParent)
3819+ f:SetWidth(120); f:SetHeight(40)
3820+ f:SetPoint("CENTER")
3821+ f:SetAttribute("unit", "party1")
3822+ -- Hover it → the `mouseover` token resolves to party1:
3823+ -- UnitName("mouseover"), UnitHealth("mouseover"), GameTooltip:SetUnit("mouseover"),
3824+ -- mouseover-cast, and UPDATE_MOUSEOVER_UNIT all work.
3825+ ```
3826+
3827+ How it works, and why it's robust: rather than installing `OnEnter`/`OnLeave`
3828+ on the frame (which an addon's own `SetScript("OnEnter", …)` would overwrite —
3829+ pfUI sets `unit` *before* its scripts, for instance), this mirrors retail and
3830+ SuperWoW's `SetMouseoverUnit`. The engine's mouse-focus frame is watched once
3831+ per frame; when a hovered frame carries a `unit` attribute, the engine's **real
3832+ mouseover setter** is invoked for that unit — **1:1 with hovering the unit's 3D
3833+ model**: the model highlights, the mouseover tooltip builds, and
3834+ `UPDATE_MOUSEOVER_UNIT` fires, in addition to the GUID slot being set. So
3835+ everything that reads mouseover — the resolver's `mouseover` branch,
3836+ `GameTooltip:SetUnit("mouseover")`, `UnitX("mouseover")`, mouseover-cast — sees
3837+ it natively. Because nothing touches the frame's scripts, an addon setting its
3838+ own handlers can't break it; and it's stomp-proof (the engine's 3D-hover setter
3839+ is event-driven, so while the cursor is over UI nothing overwrites the slot). It
3840+ follows live token changes (a `unit="target"` frame tracks your current target
3841+ while hovered). Setting a string `unit` also `EnableMouse`s the frame so a bare
3842+ frame becomes hoverable at all (real unit frames already are). The `unit` value
3843+ may be any token the resolver understands — `"party1"`, `"target"`, `"focus"`,
3844+ `"nameplateN"`, or a raw GUID literal. Set `unit` to a non-string (e.g. `nil`)
3845+ to stop the binding.
3846+
3847+ **Click actions (`type1` / `type2`).** A `type` attribute makes clicking the
3848+ frame act on its `unit`:
3849+
3850+ ```lua
3851+ f:SetAttribute("type1", "target") -- left-click targets the unit
3852+ f:SetAttribute("type2", "focus") -- right-click sets ClassicAPI focus to it
3853+ ```
3854+
3855+ Left-click reads `type1`, right-click reads `type2`, and both fall back to a
3856+ plain `type` attribute. Supported verbs: `"target"`, `"assist"` (target the
3857+ unit's target), `"focus"`. Setting a `type*` attribute installs a **chained
3858+ `OnClick` on that frame only** (nothing global) — it reads the attributes at
3859+ click time and runs *after* any handler the frame already had. Because it chains
3860+ whatever `OnClick` is present when the `type` attribute is set, set `type*`
3861+ **after** the frame's own scripts (real addons configure attributes after
3862+ building the widget). The frame must be a **Button** registered for the click:
3863+ left is the Button default; right needs `RegisterForClicks("RightButtonUp")`,
3864+ which real unit frames already call. Other verbs (`togglemenu`, `spell`,
3865+ `macro`) aren't backported yet.
3866+
3867+ **`OnAttributeChanged` is not fired.** Retail fires this script from
3868+ `SetAttribute` (and `SetAttributeNoHandler` suppresses it). Making it a real
3869+ `SetScript`-able handler needs a co-hook on the base-frame script-name resolver
3870+ (the analog of the tooltip-side `OnTooltipSet*` work); it isn't required for the
3871+ mouseover use case, so `SetAttribute` currently doesn't fire it and the two
3872+ setters behave identically. Fast-follow.
3873+
37823874## FriendList
37833875
37843876### `C_FriendList.SendWhoQueryByName(name)`
0 commit comments