-
-
Notifications
You must be signed in to change notification settings - Fork 190
feature: expose page-local root SVG element as global in rule scripts #579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: expose page-local root SVG element as global in rule scripts #579
Conversation
|
Hi @OskarLinde Thanks for the PR! I understand the underlaying though, but I actually don't agree that much. With that said: It's not always required to agree upon a thing, if it's just a question about exposing another context - and that's the case here! Therefore I'll happily merge it 😄. |
|
Let me iterate: Also, could I ask you to provide some additional context on why "The rule doesn't target a specific item directly" are a good argument? I'm asking, just to get more context about your usage of ha-floorplan. As we're not collecting any user matrix data, it's helpful to get some specific examples now and then 😄! |
|
Thanks for merging! I will elaborate. Maybe my usage is a bit unusual :-). Why unique IDs aren't possible: <line class="zone stroke" data-mesh-id="zone.dining" stroke="#FF0000" stroke-linecap="round" stroke-opacity="0.5" stroke-width="0.5px" x1="245.0622" x2="245.0622" y1="480.2159" y2="440.4425"/>
<path class="zone fill" d="M 174.354,334.380 L 174.354,374.153 L 245.062,480.216 Z" data-mesh-id="zone.dining" fill="#0f0f0f" fill-rule="evenodd" stroke="none"/>
<line class="zone stroke" data-mesh-id="zone.dining" stroke="#FF0000" stroke-linecap="round" stroke-opacity="0.5" stroke-width="0.5px" x1="174.3539" x2="174.3539" y1="334.38" y2="374.1534"/>
<line class="zone stroke" data-mesh-id="zone.dining" stroke="#FF0000" stroke-linecap="round" stroke-opacity="0.5" stroke-width="0.5px" x1="174.3539" x2="245.0622" y1="374.1534" y2="480.2159"/>
<path class="walls fill" d="M 354.232,249.228 L 373.368,234.873 L 331.269,260.710 Z" data-mesh-id="walls.0.3" fill="#0f0f0f" fill-rule="evenodd" stroke="none"/>
<line class="walls stroke" data-mesh-id="walls.0.3" stroke="#CCCCCC" stroke-linecap="round" stroke-width="0.5px" x1="331.2691" x2="354.2323" y1="260.7099" y2="249.2283"/>
<path class="walls fill" d="M 373.368,234.873 L 327.442,257.836 L 331.269,260.710 Z" data-mesh-id="walls.0.3" fill="#0f0f0f" fill-rule="evenodd" stroke="none"/>
<line class="walls stroke" data-mesh-id="walls.0.3" stroke="#CCCCCC" stroke-linecap="round" stroke-width="0.5px" x1="373.3683" x2="327.4419" y1="234.8728" y2="257.836"/>
<path class="zone fill" d="M 369.425,320.811 L 327.442,257.836 L 327.442,297.609 Z" data-mesh-id="zone.pantry" fill="#0f0f0f" fill-rule="evenodd" stroke="none"/>
<line class="zone stroke" data-mesh-id="zone.pantry" stroke="#FF0000" stroke-linecap="round" stroke-opacity="0.5" stroke-width="0.5px" x1="369.425" x2="327.4419" y1="320.8106" y2="257.836"/>
<line class="zone stroke" data-mesh-id="zone.pantry" stroke="#FF0000" stroke-linecap="round" stroke-opacity="0.5" stroke-width="0.5px" x1="327.4419" x2="327.4419" y1="257.836" y2="297.6094"/>
<path class="walls fill" d="M 315.960,268.364 L 323.615,259.750 L 292.997,279.846 Z" data-mesh-id="walls.0.3" fill="#0f0f0f" fill-rule="evenodd" stroke="none"/>
<line class="walls stroke" data-mesh-id="walls.0.3" stroke="#CCCCCC" stroke-linecap="round" stroke-width="0.5px" x1="292.9971" x2="315.9603" y1="279.8459" y2="268.3643"/>
<path class="walls fill" d="M 323.615,259.750 L 302.565,270.274 L 292.997,279.846 Z" data-mesh-id="walls.0.3" fill="#0f0f0f" fill-rule="evenodd" stroke="none"/>
<line class="walls stroke" data-mesh-id="walls.0.3" stroke="#CCCCCC" stroke-linecap="round" stroke-width="0.5px" x1="323.6147" x2="302.5651" y1="259.7496" y2="270.2744"/>
<path class="walls fill" d="M 373.368,234.873 L 373.368,274.646 L 415.351,337.621 Z" data-mesh-id="walls.0.3" fill="#191919" fill-rule="evenodd" stroke="none"/>
<line class="walls stroke" data-mesh-id="walls.0.3" stroke="#CCCCCC" stroke-linecap="round" stroke-width="0.5px" x1="373.3683" x2="373.3683" y1="234.8728" y2="274.6462"/>
<line class="walls stroke" data-mesh-id="walls.0.3" stroke="#CCCCCC" stroke-linecap="round" stroke-width="0.5px" x1="373.3683" x2="415.3514" y1="274.6462" y2="337.6208"/>
<path class="zone fill" d="M 285.343,278.886 L 347.212,371.690 L 351.040,369.777 Z" data-mesh-id="zone.downstairs_hallway" fill="#191919" fill-rule="evenodd" stroke="none"/>
<line class="zone stroke" data-mesh-id="zone.downstairs_hallway" stroke="#FF0000" stroke-linecap="round" stroke-opacity="0.5" stroke-width="0.5px" x1="285.3427" x2="347.2125" y1="278.8856" y2="371.6903"/>
<line class="zone stroke" data-mesh-id="zone.downstairs_hallway" stroke="#FF0000" stroke-linecap="round" stroke-opacity="0.5" stroke-width="0.5px" x1="347.2125" x2="351.0397" y1="371.6903" y2="369.7766"/>
<path class="zone fill" d="M 347.212,371.690 L 285.343,278.886 L 347.212,411.464 Z" data-mesh-id="zone.downstairs_hallway" fill="#191919" fill-rule="evenodd" stroke="none"/>
<line class="zone stroke" data-mesh-id="zone.downstairs_hallway" stroke="#FF0000" stroke-linecap="round" stroke-opacity="0.5" stroke-width="0.5px" x1="347.2125" x2="285.3427" y1="371.6903" y2="278.8856"/>One advantage of this is that in the 3D editor I can simply create an object and name it for example "binary_sensor.garage_door.closed", and then make another version of the same object – shift/rotate it to swing open – and name it "binary_sensor.garage_door.open". Here's the rule that takes advantage of the svg variable. Without that variable, this would have required a ton more code: - entities:
- binary_sensor.garage_door
- binary_sensor.hallway_garage_door
state_action:
action: call-service
service: floorplan.execute
service_data:
code: |
>
const openEls = Array.from(svg.querySelectorAll('[data-mesh-id="' + entity.entity_id + '.open"]'));
const closedEls = Array.from(svg.querySelectorAll('[data-mesh-id="' + entity.entity_id + '.closed"]'));
openEls.forEach(el => {
el.classList.toggle('open', entity.state === 'on');
el.classList.toggle('unavailable', entity.state === 'unavailable');
});
closedEls.forEach(el => {
el.classList.toggle('closed', entity.state === 'off');
el.classList.toggle('unavailable', entity.state === 'unavailable');
});with the corresponding css: /* Unavailable: closed mesh = red + blinking */
[data-mesh-id$=".closed"].unavailable {
fill: red;
visibility: visible;
opacity: 1;
animation: blink 1s steps(2, start) infinite;
}
/* Unavailable: open mesh = hidden */
[data-mesh-id$=".open"].unavailable {
visibility: hidden;
opacity: 0;
animation: none;
}
/* Normal closed state (non-blinking) */
[data-mesh-id$=".closed"].closed {
fill: green;
visibility: visible;
opacity: 1;
animation: none;
}
/* Normal open state */
[data-mesh-id$=".open"].open {
fill: red;
visibility: visible;
opacity: 1;
animation: none;
}
/* Hidden fallback for open/closed not matching state */
[data-mesh-id$=".closed"]:not(.closed):not(.unavailable) {
visibility: hidden;
opacity: 0;
}
[data-mesh-id$=".open"]:not(.open):not(.unavailable) {
visibility: hidden;
opacity: 0;
}
/* Blinking animation */
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}Note, this is all work in progress. |
|
Would it be much to ask for a new minor release containing these changes? 🙏 I would like to access the |
|
Sure, @rafalcieslak, thank you for the bump. I didn't meant to hold back the changes. v1.1.3: https://github.com/ExperienceLovelace/ha-floorplan/releases/tag/v1.1.3 |
This PR exposes the page-local
<svg>element as a global variablesvginside rule scripts.Why?
In many cases, it's necessary to access and manipulate SVG elements programmatically — especially when:
Currently, this requires traversing the DOM manually to locate the correct shadow root and extract the right element, which is error-prone in multi-page setups and often results in dozens of lines of utility code. It's easy to accidentally target another view’s SVG, leading to subtle bugs.
This change injects the page-local
<svg>element as a scoped svg variable into the script execution context, making it directly and safely accessible from within rules. This greatly simplifies common patterns like:and removes the need for custom DOM discovery logic typically injected via startup_action. There may be other ways to achieve this that I’ve overlooked, but this approach has worked very well for me in practice.