mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-07 16:24:51 +01:00
master
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ea1fabdfdd |
Map: float every marker, keep the selection in the URL, pan-first focus, and fix attribution and zone radius display (#54596)
* Auto-hide compact map attribution and restyle it for dark mode MapLibre decides compact mode from the map canvas width alone (640px), so shrinking the window flipped the attribution from compact back to inline when the sidebar became a drawer and the map got wider. The engine now adds the control itself and forces compact on a narrow viewport (870px, the app's own narrow layout), swapping it when the breakpoint changes. MapLibre only collapsed the opened compact attribution on a drag; it now also collapses by itself after five seconds, unless the user opened it from the info button. MapLibre's stylesheet hardcodes a white attribution with black text, which Home Assistant never overrode, so it stood out on a dark map. The dark mode rules now match the zoom control styling, and the inline text color is set explicitly instead of inherited from a page that may be in the other mode. * Add show_zone_radius option to the map card In panel layout the map hides zone radii except on the Zones tab, for the selected zone, and while editing. A user asked to always see them, so the new option draws the radius of every visible zone on all tabs. Other layouts already show radii and are unaffected. * Regroup map clusters up front for every fit Clicking a bubble already regrouped its members at the zoom the fit would land on, so they parted as the animation started. Any other way of zooming in, such as picking a person in the map overview, waited for the move to end before the bubble split. The fit itself now does the regrouping, for all markers, so every animated fit shows the bubbles as they will end up. * Return the map to its previous view from the overview's back button Leaving a selected person or zone cleared the selection but left the map zoomed in on it. The card now remembers where the map was when the overview drilled in: the exact camera, or the previous entity's fit when one selection followed another, and the back button eases back there. A camera that was still auto-fitting resumes auto-fit instead of freezing. Clicking the map to deselect stays put, since that is the user looking around. Engines gain getView, and setView can animate. * Float the selected map marker above a dot at its location Selecting a person or device marked it with a ring only, and inside a zone it stayed hidden in the zone's bubble. The selected marker is now excluded from clustering, drawn a quarter larger, and hangs above a small dot at the exact coordinate with a tail pointing down to it. The accuracy circle around the dot carries the entity color, so the ring is dropped; an entity that reports no accuracy gets a nominal 5 meter circle so the color still shows. A fix of 10 meters or better draws that circle as a soft disc without an outline, since a hard edge would claim more precision than is known; map circles gained an outline option for this. The engine's marker anchor puts the dot on the coordinate; both engines support it. Bubble avatars keep their size. * Open more info from a button in the map overview detail The selected person, device or zone's name was the only way to the more-info dialog, which made the title read as a link. The name is plain text now and a filled More info button sits below the activity, where the user expects an action. Focus still moves to the name when the detail view opens. * Always show the selected marker's accuracy circle The circle only appeared for a GPS fix looser than 100 meters, so most phones never showed one. The selected marker now floats above a dot at its coordinate, so even a small circle around that dot reads clearly; it is never hidden for the selected entity. Focusing still fits the circle into view only above 100 meters, since a tighter circle fits at the focus zoom. * Describe every toggle in the map card editor Only Show all and Show zone radius explained themselves; Scale ruler, Auto fit, Fit zones and Cluster markers now have helper text too. * Float every map marker above its location Only the selected marker hung above a dot with a tail; the rest sat centered on their coordinate, so a marker's frame always hid the spot it marked. Every entity marker is now a pin whose tail points just above its coordinate. Selecting one scales its frame up and shows the dot at the exact location. Cluster bubble avatars keep their plain frames. * Keep the map overview selection in browser history The card remembered a single camera to restore from the overview's back button, so after selecting one entity and then another the original view was lost, and back from the second left the map zoomed on the first while the overview showed the list. The selected entity now lives in the URL as an entity_id query parameter; selecting pushes a history entry, and so does deselecting, whether from the overview's back arrow or a click on the map, so the browser's back and forward step through every selection. The overview's arrow itself always returns to the list. Selecting focuses the entity; deselecting leaves the map where it is. Only selectable domains are accepted from the URL; anything else is dropped from it. The saved camera, the animated setView and the autoFit view flag had no other users. * Pan to a selected map entity instead of zooming in on it Focusing a person jumped to zoom 19 and a zone fitted its circle, which often zoomed much further in than the user had been. Selecting now keeps the current zoom and pans the entity into the free part of the view. It only zooms in when another marker sits within 80 screen pixels, and then just far enough to clear it, capped at zoom 17. Markers at the very same position cannot be separated by zooming and are left alone. A zone is fitted as its whole circle with a little room around it under that same zoom cap, so a zone that fits the view keeps the zoom and a bigger one zooms out just enough. A marker already on screen eases over; one off screen flies there in an arc. Fits gain a fly option for this, and every other fit now eases straight to its bounds on both engines, where MapLibre used to fly. Map-engine gains pixelDistance, the Web Mercator screen distance at a zoom level, with a test for its scale. * Float every cluster bubble above its location Only a bubble attached to a zone had a tail and hung above the zone's circle; a bubble of nearby markers sat centered on the cluster, hiding the spot it stood for. Every bubble now has the tail and floats above its location with the same lift as a single marker's pin, so the two read as one style. A zone's bubble keeps floating above the zone circle. |
||
|
|
80ae2543e5 |
Fix map history paths flickering on every update (#54573)
Update map paths and zone circles in place instead of redrawing them |
||
|
|
0f00286641 |
Fix map overview tabs, activity timeline, cluster counts, zone fit and the new-zone route (#54512)
* Show four avatars in a cluster bubble before counting A bubble capped at three avatars added a +1 chip for the fourth member, taking a fourth slot to hide one avatar. Four members now show as four avatars; from five on, three avatars and a count fill the same four slots. * Open the new zone dialog on every visit to /config/zone/new The route was only handled in firstUpdated. The config panel is kept alive between visits and the router reuses the zone page when it is already the current one, so "Create zone from current location" did nothing once the zone list had been opened earlier in the session. The route is now handled on every route change, like /edit/ already was. The dialog also opened before the more-info dialog it was launched from had finished leaving the history, and that departure closed the new dialog again. The page now awaits the replace navigation, which waits for closing dialogs, before opening it. * Hide the map overview while the dashboard is being edited In panel view edit mode the map card kept rendering its overview. On phones that is a bottom sheet fixed over the bottom of the screen, which covered the card's edit bar and the view's add button, so the map dashboard could not be edited in portrait. The overview is for using the map, not configuring it, so it is left out while editing. * Show only the overview tabs that have something to list People and Zones were always offered, with an empty message when the map card's entities held none of them; only Devices was hidden when empty. All three now appear only with entries, the first remaining tab takes over when the selected one empties, and the overview tells the card so its marker filter follows. The empty-tab messages are gone with the empty tabs. * List the person of the current user first, as "Me" The overview's People tab sorted everyone alike. The person linked to the user looking at the map now comes first and is named "Me" in the list and the detail header, through a localized string. Their initials still come from their real name. * Draw the overview activity as a timeline The activity list reused logbook rows: the person's name on every row, the state pushed to the right, dividers between rows. It is now a timeline of its own. Each row names where the person went, with the clock time and how long ago underneath, hung off a dot in the zone's color with a rail to the next row. A zone's rows name who came or left and where they are. Five rows show at first, with a "Show more" button for the rest. The overview no longer needs the broad hass object, which only the logbook row took. * Fit the map to its zones when they are all it shows Zones only joined the auto-fit with the fit_zones option. The panel map does not set it, so on the Zones tab, with no people or devices left, the fit found nothing and jumped straight to the home coordinates at the default zoom. Zones now stand in whenever nothing else is on the map, which goes through the animated fit like the other tabs. With fit_zones they still join a fit of people and devices. |
||
|
|
87105ea76a | Fix ha-map crash when themes are not loaded yet (#54489) | ||
|
|
e174e924e8 |
Let users pick and adjust the map style (#54477)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
82a6dffe29 | Map bug fixes - Show map markers per overview tab, hide markers behind the globe, and cluster bubbling fixes (#54467) | ||
|
|
68c5e080ac |
Update dependency maplibre-gl to v6 [SECURITY] (#54073)
Co-authored-by: Maarten Lakerveld <git@maartenlakerveld.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bramkragten <5662298+bramkragten@users.noreply.github.com> Co-authored-by: Bram Kragten <mail@bramkragten.nl> |
||
|
|
ed2d316f00 |
Map marker styling (#54308)
* Expose CSS parts on ha-entity-marker Styling tools can target ::part(marker) and ::part(picture) from the map's scope, so a style applies to markers as they are created instead of being re-injected into each new element. Documents the marker's custom properties and parts as its styling surface. * Reuse cluster bubble avatars across rebuilds Cluster bubbles are rebuilt whenever the camera settles or an entity changes state, and each rebuild created new ha-entity-marker elements. Pictures were re-decoded, the entity state subscription was torn down and set up again, and styles applied to a marker by styling tools were lost on every pan or zoom. Avatars are now cached per entity id and moved into the new bubble. An avatar still on screen is left alone: Leaflet's markercluster keeps the outgoing bubble visible during its zoom animation, and taking its avatar would empty it mid-fade. Avatars of entities no longer shown are dropped when entities are redrawn. * Rebuild Leaflet cluster icons on every add markercluster caches a cluster's icon object and re-appends the same bubble element when zooming back out, without asking for a new one. Once avatars are reused across bubbles, a cluster that splits (removed before its child is created) loses its avatars to the child bubble and comes back empty. Building the divIcon inside createIcon runs the builder on every add, so the shown bubble always takes the avatars from the cache. * Export the marker parts through ha-map Both ha-entity-marker instances live in ha-map's shadow root, so their parts were unreachable from outside. Each marker now exports them under the generic names and per entity, e.g. marker-person-anne, so a page can style one person's marker with ha-map::part(marker-person-anne). * Reuse entity markers across redraws Every state change of a listed entity redraws all markers, and until now only cluster avatars kept their element. Standalone markers now come from the same per-entity cache through one helper, which also carries the reuse guard: an element still on screen is left alone and a fresh one takes its place, since Leaflet keeps an outgoing bubble during its zoom animation. Duplicate entities in one draw get their own element. |
||
|
|
3b9e026aff |
Edit zones and locations on the map engine instead of leaflet-draw (#54023)
* Edit zones and locations on the map engine instead of leaflet-draw ha-locations-editor (zone config, the location selector, onboarding) used raw Leaflet layers and leaflet-draw, which pinned it to the Leaflet engine. Its interaction surface is three gestures - drag a marker, drag a zone center, drag a zone radius handle - so no editing library is needed. MapEngine gains an optional editing capability (draggable markers and an addEditableCircle primitive) plus panTo and containsLocation. Only the MapLibre engine implements editing: the circle geometry plus a draggable center marker and a draggable radius handle on the east edge, the radius derived from the great-circle distance between them, redrawn per frame while dragging. The handle has a touch-sized hit target and is a keyboard-operable slider with an accessible name. The Leaflet engine stays a viewing fallback: without WebGL2 an editor shows its locations as static markers and a notice that this browser cannot edit on the map. ha-map exposes editableLocations and fires editable-location-moved/resized/clicked and editing-available-changed. Locations are reconciled by id: position and radius changes move the existing handles, while a changed icon, name, color, or editability rebuilds the marker, so edits made in the form fields show up on the map on either engine. ha-locations-editor becomes a thin layer over that with its public contract unchanged, so its consumers need no changes; it reads localize from the internationalization context instead of taking hass. The zone panel no longer zooms the map when a selection comes from the map itself. Clicking or dragging a marker selects its list item, and mwc-list reports that programmatic selection with the same "property" request-selected event a click ends with. The two are told apart by timing: a selection from the map has already set the active entry when its event arrives, while a click's event arrives before the handler sets it. Skipping the programmatic event also keeps the item selected, which the shared request-selected helper would otherwise reset. The layers property and the engine selector are removed along with the leaflet-draw dependency and its patch: Leaflet is now purely the viewing fallback, selected by WebGL2 support alone. The Leaflet engine keeps a leafletMap field for the ha-map fit tests, which run on it in jsdom. * Keep the retained circle data current while resizing The engine keeps its source specifications to rebuild them after a style swap; the editable circle's redraw updates that copy along with the map. * Address code review on map editing Editable markers, the circle center on both engines, and the static fallback center activate on Enter and Space, as their button role promises. Locations without a name get a localized one, since onboarding and the location selector pass none. The resize handle's advertised maximum follows the radius instead of being exceeded by it, and the handle stays near the circle at high latitudes. Clicking the active zone row zoomed to it again upstream but was swallowed by the check that told a map-driven selection from a click; a one-shot flag on selections made in code replaces that inference. The engine rebuilds its sources from its own record on a style swap, so a radius changed while the style was unloaded is not lost. * Test the map editing path The MapLibre engine's editable circle against the fake map: dragging the center moves the circle and the handle and reports the drop, dragging the handle resizes and snaps it back, arrow keys resize in steps committed on release, the advertised maximum follows a larger radius, one update echoing pre-drag values is ignored, the center activates by click, Enter and Space but not after a drag, and removal clears markers, layers and listeners. ha-map's reconciliation of editable locations through a fake engine with editing support: circles and markers are drawn, moved in place, rebuilt when their appearance changes and removed when dropped, move, resize and activation events carry the location id, and an engine without editing support shows the locations statically. * Leave zone row selection to the user Clicking or dragging a zone on the map, and saving one in the dialog, selected its row in code. That selection fires the same request-selected event a click ends with, so the page had to tell the two apart, and the row could not be shown selected and stay clickable at the same time: the highlight never actually appeared. The list now only follows the user's own clicks, which removes that bookkeeping along with the zoom that a radius drag used to trigger. * Hand marker elements back clean, and record sources before the style loads MapLibre leaves its positioning class and transform on a marker element after removal. Hosts like ha-locations-editor cache their elements and hand them to whatever engine draws next: after a fatal WebGL failure that is the Leaflet fallback, which shows the locations statically. A removed element, and every element still on the map when the engine is destroyed, is reset so it arrives as a plain element. The editable circle's center gets the same treatment. Custom sources and layers are now recorded when added and only inserted into the map once the style has loaded. A circle moved while a swapped style was still loading therefore reaches the record, and a swap in that window carries it over; the deferred insert skips what the swap already placed. * Guard a keyboard resize against host updates like a drag Arrow-key resizing changed the radius without arming the pre-edit guard that pointer drags use, so a host re-render with the old radius while a key was held, or its echo right after the commit, snapped the circle back. A key resize now counts as a drag from the first key press until the commit. * Let the engine own the drag cursor, and tidy the fallback notice The move cursor came from stylesheets keyed on whether a location may be edited, so it showed on circles that cannot move and on the static Leaflet fallback. The MapLibre engine now sets it on the elements it actually made draggable and clears it when handing them back. The fallback notice said the browser cannot edit locations, while clicking the map to place one still works there; it now names what is missing, dragging markers and resizing zones. The editor's internationalization context is reactive, like its neighbours. * Show the fallback notice only when a location is editable The Leaflet fallback showed the notice for every editor, including ones whose locations are all read-only, where nothing is missing. * Drop the named icon's move cursor, and describe the fallback as a map state The Leaflet fallback draws named location icons statically, but the stylesheet still gave the ones marked editable a move cursor. The engine sets that cursor on the elements it actually drags, so the rule goes. The fallback notice blamed the browser, while MapLibre can also be unavailable after a failed start or a lost WebGL context; it now describes what the map offers. The resize handle's east point only guards the pole itself, so its distance agrees with distanceMeters wherever a zone can be. * Announce the fallback notice, and let test frames redraw The notice appears after the engine has been chosen, so helper text would not reach a screen reader; it is an alert now. The engine test's frame stub returned an id that landed in the engine's pending frame after the callback had cleared it, so every redraw after the first was skipped. * Show the fallback notice over the map, and keep handle clicks off the map The notice followed a map that fills the editor's fixed height, so it landed outside the box: clipped in onboarding, spilling into the form in the location selector. It now sits over the top of the map, clear of the zoom control. The resize handle was the one marker whose click reached MapLibre's map click, which a pin-on-click host takes as a request to move the location there; the handle stops it like the other markers do. * Share one circle geometry, and keep arrow keys on the handle Drawing, fitting, and handle placement each converted meters to degrees on their own, with different pole guards, so above 89.4 degrees the resize handle left the drawn circle. They now share one conversion that also agrees with distanceMeters; only the pole itself is guarded. Arrow keys on the resize handle also reached MapLibre's keyboard handler and panned the map while resizing; the handle keeps them. * Drop the zone page's active entry It only re-selected the row the user had just clicked, after the shared request-selected helper had reset it, which fired the selection event a second time and zoomed to the zone twice. The row highlight comes from the list itself and nothing else read the value, so the list no longer mirrors a selection at all. * Make marker activation opt-in, and relabel handles on language change Every editable marker was a focusable button whose activation fired an event, whether or not the host did anything with it: the zone page and the location selector do not. Activation is now opt-in per location; the onboarding search results opt in, and a marker that does not act on it is draggable without button semantics. The engine contract separates taking pointer input from being a keyboard-focusable button for that. Marker titles and handle labels are localized when drawn, so a language change, or localization arriving after the first draw, redraws them. * Own the accessibility attributes the engine sets, and use spherical circle geometry setMarkerAccessibility only filled in what was missing and nothing removed it, so a cached element rebuilt with a new title or a different role kept the old attributes. The helper now records what it set, replaces exactly that on a rebuild, and clears it when the element leaves the map, while a caller's own attributes stay untouched. The flat meters-to-degrees conversion broke down for large circles near the poles, wrapping longitude and producing latitudes beyond 90. Handle placement and the polygon now use the great-circle destination, and the fitted bounds a spherical box that spans all longitudes once the circle reaches a pole, so all three agree with distanceMeters everywhere. |
||
|
|
ac582cbfbb |
Run ha-map on a runtime-selected engine: MapLibre GL native or Leaflet (#54022)
* Run ha-map on a runtime-selected engine: MapLibre GL native or Leaflet ha-map no longer drives Leaflet directly. All primitive operations - camera, HTML element markers, meter-radius circles, history paths, clustering with a pluggable icon builder, scale ruler, dark mode - go through a MapEngine interface, and the engine is selected at runtime: - MapLibreMapEngine renders the vector base map natively where WebGL2 is available, without Leaflet in the loop: continuous fractional zoom, DOM markers synced to the basemap every frame, GeoJSON zone and accuracy circles below the labels, GeoJSON history trails with hover popups, and a pixel-distance cluster grid recomputed when the camera settles. Dark mode swaps the style while carrying the custom layers over, and rotation and pitch stay disabled for north-up dashboards. - LeafletMapEngine wraps the existing behavior unchanged (vector tiles through the maplibre adapter with the raster fallback, markercluster) for browsers without WebGL2 - the legacy floor includes iOS 12-14 and kiosk browsers without hardware acceleration - and for ha-locations-editor, which manages raw Leaflet layers with leaflet-draw and declares engine=leaflet. A permanently lost WebGL context rebuilds the map on the Leaflet engine instead of leaving a dead canvas. Zoom levels keep Leaflet semantics across engines. MapLibre's stylesheet is shipped to /static/map for the native engine's controls and popups. * Enhance keyboard accessibility for icon click Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix dark mode style application logic Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Address code review on the map engine A fatal engine event during setup was dropped because the in-flight setup owned the loading flag; the fallback request is now recorded and setup switches to Leaflet once init settles. Entity markers accept Enter and Space, as both engines make them focusable buttons. History points show their popup on tap as well as hover, since touch has no hover. Tests cover the engine choice and fallback paths, including a fatal event mid-setup, and the MapLibre engine itself against a fake map: style swaps, queued layer work, custom layer carry-over, failed style requests, token refusal recovery, context-loss grace, and clustering. * Address second code review round on the map engine A cluster whose members share a spot, or that sits at maximum zoom, could never be opened: fitting its bounds changed nothing and the next regroup recreated it. Activating such a cluster now opens it: all its members are shown in a bubble with a tail pointing at their spot, each reachable on its own, until the map moves again. An engine whose init failed, or was abandoned by a disconnect or a fatal event during setup, was never destroyed and could keep a WebGL context; setup now destroys any engine that did not become the active one. The Leaflet engine sizes the cluster element like MapLibre does, so cluster bubbles no longer shrink to their text. * Keep the engine's own record of its map sources and layers Carrying zone circles and history paths over a style swap relied on MapLibre serializing the outgoing style into transformStyle's previous argument. The engine now keeps the source and layer specifications it added and rebuilds them into the new style itself, so the carry-over no longer depends on what MapLibre hands over, and a missing previous style cannot drop them. * Address review: setup teardown, marker input, cluster focus An engine still setting up could not be torn down: ha-map only held it once init resolved, so removing the element mid-load kept its WebGL context alive and left the loading guard set, ignoring a reconnect. The engine being set up is now tracked and destroyed on disconnect, a stale setup notices it was superseded, and the MapLibre engine settles a pending init when destroyed. Non-interactive markers let pointer input through, as they do on Leaflet. Opening a cluster from the keyboard moves focus to its first member, and a member that has focus when the bubble closes hands it to the icon that replaces it. * Tear down an engine that fails while still setting up A fatal event during setup only flagged the fallback and waited for init to settle, which relied on MapLibre still firing style.load. The engine being set up is destroyed instead, which settles its init, so the setup hands over to Leaflet without depending on the failed engine. * Make history point popups readable on MapLibre MapLibre's stylesheet is linked into the map's shadow root and wins over the component styles on equal specificity, property by property: its white popup background applied while our white text color did too, leaving the timestamp invisible. The popup rules now carry !important, as the Leaflet tooltip rules already do for the same reason. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
088f72fcc4 |
Fix map loading fully zoomed out when fitted before layout (#53428)
Leaflet computes the zoom level that fits a set of bounds from the current size of the map container. When ha-map's fit runs before the browser has laid out the container (a race that hits when the Leaflet chunks are already cached), the container measures 0x0, the computed zoom collapses to the minimum, and the map shows the entire world. The later invalidateSize() from the resize observer fixes the size but keeps the zoom, so the map stays on the world view. Defer fitting while the container has no size and run the pending fit once the resize observer reports a usable size. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |