From e908ddfdbeae1577b126145cd81df972f1170dee Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Mon, 15 Jun 2026 12:43:48 -0400 Subject: [PATCH] chore: code cleanup Comment and docstring sweep plus two small refactors: - Strip narrations, historical PR/version references, and obvious code restatements across providers and the domain layer. - Where comments documented invariants (e.g. ``_tagged_once`` in akuvox and schlage), tighten rather than delete. - ts/generate-view.ts: extract ``wrapAsFoldOrSection`` helper to dedupe the fold-vs-section wrapper used by ``maybeGenerateFoldEntityRowCard`` and ``maybeGenerateFoldEntityRowConditionCard``. - ts/slot-card.ts: add ``HassElement`` and ``CardHelpers`` interfaces and extend the existing ``Window`` global; drop 3 ``as any`` casts plus two inline cast forms, removing the matching ``eslint-disable`` ``no-explicit-any`` directives. No behavior changes. Bundle regenerated by pre-commit yarn-build. Co-Authored-By: Claude Opus 4.7 (1M context) Entire-Checkpoint: c61cef7455e6 --- .../lock_code_manager/__init__.py | 11 +--- .../lock_code_manager/domain/callbacks.py | 4 -- .../lock_code_manager/domain/credentials.py | 23 +++---- .../lock_code_manager/domain/models.py | 5 -- .../lock_code_manager/domain/services.py | 1 - .../domain/slot_coordinator.py | 14 ++-- custom_components/lock_code_manager/event.py | 2 - .../lock_code_manager/providers/_base.py | 12 ++-- .../providers/_zwave_js_uc.py | 5 +- .../lock_code_manager/providers/akuvox.py | 7 +- .../lock_code_manager/providers/matter.py | 57 ++++++++--------- .../lock_code_manager/providers/schlage.py | 7 +- .../lock_code_manager/providers/zha.py | 9 --- .../providers/zigbee2mqtt.py | 8 +-- .../lock_code_manager/providers/zwave_js.py | 13 ++-- .../lock_code_manager/websocket.py | 16 ----- .../www/generated/lock-code-manager.js | 2 +- ts/generate-view.ts | 64 +++++++------------ ts/lock-codes-card.ts | 1 - ts/lock-section-strategy.ts | 1 - ts/slot-card.ts | 37 ++++++----- ts/slugify.ts | 1 - 22 files changed, 113 insertions(+), 187 deletions(-) diff --git a/custom_components/lock_code_manager/__init__.py b/custom_components/lock_code_manager/__init__.py index 55568bb37..c6714e862 100644 --- a/custom_components/lock_code_manager/__init__.py +++ b/custom_components/lock_code_manager/__init__.py @@ -322,7 +322,6 @@ async def async_setup(hass: HomeAssistant, config: Config) -> bool: await _async_register_strategy_resource(hass) - # Set up websocket API await async_websocket_setup(hass) _LOGGER.debug("Finished setting up websocket API") @@ -776,12 +775,9 @@ async def async_remove_entry( Called by Home Assistant only on entry deletion -- not on unload, reload, disable, or HA restart. The repair issues created by this - integration are flagged ``is_persistent=True`` precisely so they - survive restarts and reloads; deleting them in ``async_unload_entry`` - (the previous behavior) wiped them on every restart, causing the - "click an issue and it says repaired" short-circuit. With cleanup - moved here, persistent issues persist until the user actually - removes the entry. + integration are flagged ``is_persistent=True`` so they survive + restarts and reloads; clearing them belongs here, not in + ``async_unload_entry``, so they outlive any non-deletion unload. """ entry_id = config_entry.entry_id config = get_entry_config(config_entry) @@ -1018,7 +1014,6 @@ async def async_update_listener( err, ) - # Remove old lock entities if locks_to_remove: _LOGGER.debug( "%s (%s): Removing locks %s", entry_id, entry_title, locks_to_remove diff --git a/custom_components/lock_code_manager/domain/callbacks.py b/custom_components/lock_code_manager/domain/callbacks.py index 945bf2b40..0e0263973 100644 --- a/custom_components/lock_code_manager/domain/callbacks.py +++ b/custom_components/lock_code_manager/domain/callbacks.py @@ -89,8 +89,6 @@ class EntityCallbackRegistry: lock_added: list[LockUpdateCallback] = field(default_factory=list) lock_removed: list[LockRemoveCallback] = field(default_factory=list) - # --- Registration methods (return unregister functions) --- - def register_standard_adder( self, callback: StandardEntityCallback ) -> UnregisterFunc: @@ -157,8 +155,6 @@ def register_lock_removed_handler( else None ) - # --- Invocation methods (called by __init__.py orchestrator) --- - @callback def invoke_standard_adders(self, slot_num: int, ent_reg: er.EntityRegistry) -> None: """Invoke all standard entity creation callbacks.""" diff --git a/custom_components/lock_code_manager/domain/credentials.py b/custom_components/lock_code_manager/domain/credentials.py index 1fa6b2e36..b9582727e 100644 --- a/custom_components/lock_code_manager/domain/credentials.py +++ b/custom_components/lock_code_manager/domain/credentials.py @@ -191,19 +191,16 @@ class WriteResult(StrEnum): """ Outcome of a credential write (``async_set_credential``). - Replaces the old ``bool`` return, distinguishing three cases the seam - needs: - - - ``NO_CHANGE`` -- the value was already set; nothing was written (the old - ``False``). The coordinator is not refreshed. - - ``CONFIRMED`` -- the lock acknowledged the write (the old ``True``). The - slot is marked verified; non-push providers refresh to read it back. - - ``OPTIMISTIC`` -- the write returned an ambiguous result we are treating - as completed but have NOT confirmed (e.g. a Z-Wave driver - ``ERROR_UNKNOWN`` from a masked read-back). The slot is marked unverified - and awaits confirmation via a push event or hard refresh; if none - arrives, it re-syncs rather than silently reporting success. See the - Phase 2 push-as-commit spec. + - ``NO_CHANGE`` -- the value was already set; nothing was written. The + coordinator is not refreshed. + - ``CONFIRMED`` -- the lock acknowledged the write. The slot is marked + verified; non-push providers refresh to read it back. + - ``OPTIMISTIC`` -- the write returned an ambiguous result we treat as + completed but have NOT confirmed (e.g. a Z-Wave driver + ``ERROR_UNKNOWN`` from a masked read-back). The slot is marked + unverified and awaits confirmation via a push event or hard refresh; + if none arrives, it re-syncs rather than silently reporting success. + See the Phase 2 push-as-commit spec. """ NO_CHANGE = "no_change" diff --git a/custom_components/lock_code_manager/domain/models.py b/custom_components/lock_code_manager/domain/models.py index 024949699..c45fe38d0 100644 --- a/custom_components/lock_code_manager/domain/models.py +++ b/custom_components/lock_code_manager/domain/models.py @@ -151,11 +151,6 @@ class LockCodeManagerConfigEntryRuntimeData: # unload -- so an in-flight tick cannot keep running against torn-down # state. sync_managers: set[SlotSyncManager] = field(default_factory=set) - # Per-slot entity coordinators. Created when a slot is added and torn - # down when it is removed. Owns the derived "active" state and the - # intent-dispatch surface used by text/switch/active entities so they - # do not have to mutate the config entry or call sibling-entity - # services directly. slot_coordinators: dict[int, SlotEntityCoordinator] = field(default_factory=dict) # True once the options update listener has been registered for this # entry. Guards against stacking when _setup_entry_after_start runs more diff --git a/custom_components/lock_code_manager/domain/services.py b/custom_components/lock_code_manager/domain/services.py index 6f8ab25d0..d69b4c512 100644 --- a/custom_components/lock_code_manager/domain/services.py +++ b/custom_components/lock_code_manager/domain/services.py @@ -45,7 +45,6 @@ async def async_set_slot_condition( if not hass.states.get(entity_id): raise ServiceValidationError(f"Entity {entity_id} not found") - # Check for excluded platforms ent_reg = er.async_get(hass) entity_entry = ent_reg.async_get(entity_id) if entity_entry and entity_entry.platform in EXCLUDED_CONDITION_PLATFORMS: diff --git a/custom_components/lock_code_manager/domain/slot_coordinator.py b/custom_components/lock_code_manager/domain/slot_coordinator.py index 62f11dade..ad59eb796 100644 --- a/custom_components/lock_code_manager/domain/slot_coordinator.py +++ b/custom_components/lock_code_manager/domain/slot_coordinator.py @@ -1,13 +1,13 @@ """ Per-slot entity coordinator. -A SlotEntityCoordinator instance owns the per-slot state surface that -text, switch, and active-binary-sensor entities used to compute on their -own. Entities become read-only views over the coordinator: they register -write callbacks for state changes and dispatch user intent (set a PIN, -toggle enabled) through the coordinator. The coordinator updates the -canonical config entry, manages slot-level repair issues, and asks the -per-lock SlotSyncManagers to re-evaluate on the next tick. +A SlotEntityCoordinator instance owns the per-slot state surface for the +text, switch, and active-binary-sensor entities. Entities are read-only +views over the coordinator: they register write callbacks for state +changes and dispatch user intent (set a PIN, toggle enabled) through the +coordinator. The coordinator updates the canonical config entry, manages +slot-level repair issues, and asks the per-lock SlotSyncManagers to +re-evaluate on the next tick. There is one SlotEntityCoordinator per (config_entry, slot_num); the per- lock SlotSyncManager remains one per (config_entry, slot_num, lock). diff --git a/custom_components/lock_code_manager/event.py b/custom_components/lock_code_manager/event.py index 4df8f4e61..bad84bdf3 100644 --- a/custom_components/lock_code_manager/event.py +++ b/custom_components/lock_code_manager/event.py @@ -131,14 +131,12 @@ def _handle_event(self, event: Event) -> None: def _handle_add_locks(self, locks: list[BaseLock]) -> None: """Handle lock entities being added.""" super()._handle_add_locks(locks) - # Write state to reflect new event_types and unsupported_locks self.async_write_ha_state() @callback def _handle_remove_lock(self, lock_entity_id: str) -> None: """Handle lock entity being removed.""" super()._handle_remove_lock(lock_entity_id) - # Write state to reflect new event_types and unsupported_locks self.async_write_ha_state() async def async_added_to_hass(self) -> None: diff --git a/custom_components/lock_code_manager/providers/_base.py b/custom_components/lock_code_manager/providers/_base.py index 92717b390..43abe112a 100644 --- a/custom_components/lock_code_manager/providers/_base.py +++ b/custom_components/lock_code_manager/providers/_base.py @@ -1038,12 +1038,12 @@ async def async_clear_usercode(self, code_slot: int) -> bool: # Owner resolution is two-pass to match the same identity rule the # set path uses (see Matter's _find_user_index_for_slot). The # canonical pass matches by the ``lcm::`` tag in user.name; - # the legacy fallback handles pre-PR-B installs where - # ``credential.slot`` was pinned to the LCM slot. Matching by - # ``credential.slot == code_slot`` alone is unsafe once providers - # let the lock auto-allocate the credential index -- a tagged - # user for slot A whose credential lands at index B would be - # mis-matched when clearing slot B. + # the fallback adopts installs from before user-tag matching, + # where ``credential.slot`` was pinned to the LCM slot. Matching + # by ``credential.slot == code_slot`` alone is unsafe once + # providers let the lock auto-allocate the credential index -- a + # tagged user for slot A whose credential lands at index B would + # be mis-matched when clearing slot B. users = await self.async_get_users() # Both lookups require the user to actually own a PIN credential # at the slot we're clearing. Under the persistent-user-anchor diff --git a/custom_components/lock_code_manager/providers/_zwave_js_uc.py b/custom_components/lock_code_manager/providers/_zwave_js_uc.py index 2f4561381..9a0235e84 100644 --- a/custom_components/lock_code_manager/providers/_zwave_js_uc.py +++ b/custom_components/lock_code_manager/providers/_zwave_js_uc.py @@ -372,9 +372,8 @@ async def _async_uc_set_usercode( lock_entity_id=self.lock.entity_id, reason=f"set value returned {result.status.name}", ) - # Transient non-OK (canonically ``FAIL``): match the 3.x - # behavior of the HA service we used to call -- log and - # let the optimistic push + next sync tick converge. + # Transient non-OK (canonically ``FAIL``) is non-fatal: the + # optimistic push covers UI and the next sync tick reconciles. _LOGGER.info( "Lock %s slot %s: set returned %s; " "trusting optimistic push and continuing", diff --git a/custom_components/lock_code_manager/providers/akuvox.py b/custom_components/lock_code_manager/providers/akuvox.py index 892a4e185..9a86e992a 100644 --- a/custom_components/lock_code_manager/providers/akuvox.py +++ b/custom_components/lock_code_manager/providers/akuvox.py @@ -74,10 +74,9 @@ class AkuvoxLock(BaseLock): PIN value. Cleared slots report ``SlotCredential.empty()``. """ - # Tracks whether the initial auto-tag pass already ran for this - # provider instance. Skips re-tagging on reconnects so a drifted - # device list does not produce double-tag / rename storms. Reset - # naturally when the provider instance is recreated on full reload. + # Guards the initial auto-tag pass: skips re-tagging on reconnects so a + # drifted device list cannot produce double-tag / rename storms. Reset + # on full reload when the provider instance is recreated. _tagged_once: bool = field(default=False, init=False) @property diff --git a/custom_components/lock_code_manager/providers/matter.py b/custom_components/lock_code_manager/providers/matter.py index 026cbb2be..514a0089d 100644 --- a/custom_components/lock_code_manager/providers/matter.py +++ b/custom_components/lock_code_manager/providers/matter.py @@ -399,13 +399,12 @@ async def async_set_user(self, user: User) -> SetUserResult: if existing_user_index is not None: # UPDATE: rename via set_lock_user. # - # set_lock_user here is a metadata-only name update. The - # historical Matter contract (PR #1077) tolerated name-set - # failures so a transient 500 or a name the lock rejects - # does not block the subsequent credential write; the user - # still exists at the known index, the only thing lost is - # the name update. If every candidate in the cascade fails - # with MatterError we log a warning and fall through. + # set_lock_user here is a metadata-only name update. + # Name-set failures must not block the subsequent credential + # write -- the user still exists at the known index and only + # the cosmetic name update is lost. The cascade tries each + # candidate name; if every one fails with MatterError we log + # a warning and fall through. candidates = self._user_name_candidates(slot, user.name) try: ( @@ -419,16 +418,15 @@ async def async_set_user(self, user: User) -> SetUserResult: candidate_names=candidates, ) except (LockDisconnected, LockOperationFailed, MatterError) as err: - # UPDATE's historical contract (PR #1077): tolerate any - # rename failure so the subsequent credential write still - # proceeds. The user record is still valid at - # ``existing_user_index`` -- the only thing lost is the - # cosmetic name update. The helper raises typed seam - # exceptions (LockDisconnected for transport failures, + # UPDATE tolerates any rename failure so the subsequent + # credential write still proceeds. The user record is + # still valid at ``existing_user_index`` -- only the + # cosmetic name update is lost. The helper raises typed + # seam exceptions (LockDisconnected for transport, # LockOperationFailed for validation rejections, # MatterError when every candidate hit a lock-side - # rejection); we swallow all three here on the UPDATE - # path and log a warning instead. + # rejection); all three are swallowed here on the UPDATE + # path and logged as a warning. LOGGER.warning( "Lock %s: failed to update user name on slot %s " "(user_index=%s); continuing without name update. " @@ -693,8 +691,8 @@ async def _send_set_credential( ``credential_index=None`` auto-allocates the next free credential slot (CREATE). Passing an existing index addresses the user's current PIN - credential for MODIFY. ``code_slot`` is the LCM slot only and is used - for error reporting; it is no longer pinned to the Matter index. + credential for MODIFY. ``code_slot`` is the LCM slot, used only for + error reporting; the Matter credential index is opaque to LCM. Raises SetCredentialFailedError on lock rejection, CodeRejectedError on validation failure, @@ -733,11 +731,10 @@ async def _find_pin_credential_index_for_user(self, user_id: int) -> int | None: """ Return the user's current Matter PIN credential index, or ``None``. - LCM no longer pins ``credential_index`` to the LCM slot; instead it - treats Matter's credential index as opaque and rediscovers it per - operation. This helper deliberately walks the **raw** lock-side - user data (not ``async_get_users``) so the returned value is the - Matter credential index Matter expects for + LCM treats Matter's credential index as opaque and rediscovers it + per operation. This helper deliberately walks the **raw** + lock-side user data (not ``async_get_users``) so the returned + value is the Matter credential index Matter expects for ``set_lock_credential`` / ``clear_lock_credential`` -- not the LCM-projected slot that ``async_get_users`` exposes upward. """ @@ -1004,8 +1001,8 @@ def _handle_lock_operation(self, node_event: Any) -> None: Only PIN credentials (credentialType=1) trigger the event -- other credential types (RFID, fingerprint, etc.) are ignored. - The event's ``credentials[].credentialIndex`` is the Matter credential - index, which is no longer pinned to the LCM slot under the user-tag + The event's ``credentials[].credentialIndex`` is the Matter + credential index, which LCM treats as opaque under the user-tag model. To find the LCM slot we resolve via the event's top-level ``userIndex`` -> user.name -> ``lcm::`` tag, falling back to walking the user list for a PIN credential at ``credentialIndex`` @@ -1131,12 +1128,12 @@ def _handle_lock_user_change(self, node_event: Any) -> None: the owning user's name and parsing its ``lcm::`` tag -- ``userIndex`` alone is sufficient. ``dataIndex`` (the Matter credential index) is captured best-effort for log context only; - it's no longer pinned to the LCM slot under the user-tag model - and dropping otherwise-resolvable events when it's missing or - malformed would silently lose state updates. The lookup is async - (a fresh ``_raw_lock_users`` round-trip) so the callback - schedules a task rather than blocking the event loop. Events - for users LCM doesn't own (untagged names) are ignored. + under the user-tag model it is opaque to LCM, and dropping + otherwise-resolvable events when it's missing or malformed would + silently lose state updates. The lookup is async (a fresh + ``_raw_lock_users`` round-trip) so the callback schedules a task + rather than blocking the event loop. Events for users LCM + doesn't own (untagged names) are ignored. """ data: dict[str, Any] = getattr(node_event, "data", None) or {} diff --git a/custom_components/lock_code_manager/providers/schlage.py b/custom_components/lock_code_manager/providers/schlage.py index a36385920..635aba1fb 100644 --- a/custom_components/lock_code_manager/providers/schlage.py +++ b/custom_components/lock_code_manager/providers/schlage.py @@ -60,10 +60,9 @@ class SchlageLock(BaseLock): and ``SlotCredential.empty()`` for cleared slots. """ - # Tracks whether the initial auto-tag pass already ran for this - # provider instance. Skips re-tagging on reconnects so a drifted - # device list does not produce double-tag / rename storms. Reset - # naturally when the provider instance is recreated on full reload. + # Guards the initial auto-tag pass: skips re-tagging on reconnects so a + # drifted device list cannot produce double-tag / rename storms. Reset + # on full reload when the provider instance is recreated. _tagged_once: bool = field(default=False, init=False) @property diff --git a/custom_components/lock_code_manager/providers/zha.py b/custom_components/lock_code_manager/providers/zha.py index 2ed558bdb..d5480a1f4 100644 --- a/custom_components/lock_code_manager/providers/zha.py +++ b/custom_components/lock_code_manager/providers/zha.py @@ -37,10 +37,6 @@ _LOGGER = logging.getLogger(__name__) -# --------------------------------------------------------------------------- -# ZCL DoorLock event mappings -# --------------------------------------------------------------------------- - OPERATION_TO_LOCKED: dict[int, bool] = { DoorLock.OperationEvent.Lock: True, DoorLock.OperationEvent.KeyLock: True, @@ -63,11 +59,6 @@ } -# --------------------------------------------------------------------------- -# Provider -# --------------------------------------------------------------------------- - - @dataclass(repr=False, eq=False) class ZHALock(BaseLock): """ diff --git a/custom_components/lock_code_manager/providers/zigbee2mqtt.py b/custom_components/lock_code_manager/providers/zigbee2mqtt.py index 0367b2f2f..3a6ae4751 100644 --- a/custom_components/lock_code_manager/providers/zigbee2mqtt.py +++ b/custom_components/lock_code_manager/providers/zigbee2mqtt.py @@ -225,7 +225,6 @@ def _process_z2m_device_payload(self, payload: dict[str, Any]) -> None: ) return - # Handle users data in state update users_data = payload.get("users") if users_data and isinstance(users_data, dict): updates: dict[int, SlotCredential] = {} @@ -274,7 +273,6 @@ def _process_z2m_device_payload(self, payload: dict[str, Any]) -> None: ) self.coordinator.push_update(updates) - # Handle response to get request with pin_code data pin_code_data = payload.get("pin_code") if pin_code_data and isinstance(pin_code_data, dict): raw_user = pin_code_data.get("user") @@ -581,9 +579,9 @@ async def async_get_users(self) -> list[User]: slot_states: dict[int, SlotCredential] = {} # Query one slot at a time so Zigbee2MQTT / firmware can answer each GET before - # the next. Parallel gathers plus per-slot timeouts used to raise and fail the - # entire refresh, leaving coordinator.data empty — sync then skips every slot - # (see SlotSyncManager._resolve_slot_state). + # the next. Parallel gather + per-slot timeouts can fail the entire refresh and + # leave coordinator.data empty -- sync then skips every slot (see + # SlotSyncManager._resolve_slot_state). # Transient publish/timeout/read failures use the unreadable credential so sync # does not treat the slot as confirmed-empty and storm reprogramming after MQTT # recovery. diff --git a/custom_components/lock_code_manager/providers/zwave_js.py b/custom_components/lock_code_manager/providers/zwave_js.py index b42b5d07c..1852a91ae 100644 --- a/custom_components/lock_code_manager/providers/zwave_js.py +++ b/custom_components/lock_code_manager/providers/zwave_js.py @@ -64,8 +64,6 @@ _LOGGER = logging.getLogger(__name__) -# String key used by lock_helpers for Personal Identification Number credentials -# in the supported_credential_types dict returned by async_get_credential_capabilities. _PIN_TYPE_STR = lock_helpers.CREDENTIAL_TYPE_MAP[UserCredentialType.PIN_CODE] # Z-Wave UserCredentialType -> domain CredentialType. The domain vocabulary @@ -318,7 +316,7 @@ async def async_set_user(self, user: User) -> SetUserResult: The base seam passes a tagged ``user.name`` (``lcm::``) whose slot is the LCM-side identity for this credential. The Z-Wave lock's own ``user_id`` is whatever Z-Wave happens to allocate; LCM - no longer pins it to the slot. Discovery on every call: + treats it as opaque and rediscovers it via the tag on every call: 1. Scan the lock's current user list for a user whose name carries the same ``lcm::`` tag. @@ -553,11 +551,10 @@ def setup_push_subscription(self) -> None: deleted`` node events. In UC-fallback mode those events never fire (the driver only emits them from its own unified API methods, which the fallback bypasses), so we subscribe to raw - ``value updated`` events for the User Code CC values instead -- - the same push source the legacy 3.x provider used. When the - mode is not yet known (capability probe hasn't run), subscribe - to both; the handlers are self-filtering and pushes are - idempotent. + ``value updated`` events for the User Code CC values instead. + When the mode is not yet known (capability probe hasn't run), + subscribe to both; the handlers are self-filtering and pushes + are idempotent. """ if self._push_unsubs: return diff --git a/custom_components/lock_code_manager/websocket.py b/custom_components/lock_code_manager/websocket.py index bf61e3f30..ad99cc440 100644 --- a/custom_components/lock_code_manager/websocket.py +++ b/custom_components/lock_code_manager/websocket.py @@ -116,11 +116,6 @@ CALENDAR_ATTR_END_TIME = "end_time" -# ============================================================================= -# State Helper Functions -# ============================================================================= - - def _slot_code_payload( code: str | SlotCredential | None, *, @@ -243,11 +238,6 @@ def _get_last_changed( return None -# ============================================================================= -# Config Entry Helpers -# ============================================================================= - - def _find_config_entry_by_title(hass: HomeAssistant, title: str) -> ConfigEntry | None: """Find a config entry by title (slugified comparison).""" return next( @@ -751,7 +741,6 @@ def _get_condition_entity_data( if not state: return None - # Extract domain from entity_id domain = split_entity_id(condition_entity_id)[0] is_active = state.state == STATE_ON @@ -921,7 +910,6 @@ def _serialize_slot_card_data( calendar_next_event: dict[str, Any] | None = None, ) -> dict[str, Any]: """Serialize slot data for the slot card.""" - # Get slot metadata using module-level helpers name = _get_text_state(hass, slot_entities.name_entity_id) or "" pin = _get_text_state(hass, slot_entities.pin_entity_id) enabled = _get_bool_state(hass, slot_entities.enabled_entity_id) @@ -931,10 +919,8 @@ def _serialize_slot_card_data( hass, slot_entities.event_entity_id ) - # Get condition entity from config using helper condition_entity_id = _get_slot_condition_entity_id(config_entry, slot_num) - # Build per-lock status entry_locks = config_entry.runtime_data.locks entry_lock_ids = get_entry_config(config_entry).locks @@ -950,7 +936,6 @@ def _serialize_slot_card_data( if (lock := entry_locks.get(lock_entity_id)) ] - # Build result result: dict[str, Any] = { ATTR_SLOT_NUM: slot_num, ATTR_CONFIG_ENTRY_ID: config_entry.entry_id, @@ -1031,7 +1016,6 @@ async def subscribe_code_slot( slot_num = msg[ATTR_SLOT] reveal = msg["reveal"] - # Validate slot exists in config if not get_entry_config(config_entry).has_slot(slot_num): connection.send_error( msg["id"], diff --git a/custom_components/lock_code_manager/www/generated/lock-code-manager.js b/custom_components/lock_code_manager/www/generated/lock-code-manager.js index e5f23fe50..fea2af818 100644 --- a/custom_components/lock_code_manager/www/generated/lock-code-manager.js +++ b/custom_components/lock_code_manager/www/generated/lock-code-manager.js @@ -1 +1 @@ -var n,e,t,i,o,r,a,s,l,c,d,h,p,u,v,g,_,f,m,y,b,w,x,k,C,E,S,A,$,L,P,M,z,N,O,R,H,T,D,I,U,j,V,F,B,W,q,Z,K,G,Y,X,J,Q,nn,en,tn,on,rn,an,sn,ln,cn,dn,hn,pn,un,vn,gn,_n,fn,mn,yn,bn,wn,xn,kn,Cn,En,Sn,An,$n,Ln,Pn,Mn,zn,Nn,On,Rn,Hn,Tn,Dn,In,Un,jn,Vn,Fn=["config_entry_title"];function Bn(n,e){return e||(e=n.slice(0)),Object.freeze(Object.defineProperties(n,{raw:{value:Object.freeze(e)}}))}function Wn(n,e){var t=Object.keys(n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(n);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable}))),t.push.apply(t,i)}return t}function qn(n){for(var e=1;en.length)&&(e=n.length);for(var t=0,i=Array(e);t1?e-1:0),i=1;ie+(n=>{if(!0===n._$cssResult$)return n.cssText;if("number"==typeof n)return n;throw Error("Value passed to 'css' function must be a 'css' function result: "+n+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(t)+n[i+1]),n[0]);return new te(o,n,ne)},oe=Qn?n=>n:n=>n instanceof CSSStyleSheet?(n=>{var e="";for(var t of n.cssRules)e+=t.cssText;return(n=>new te("string"==typeof n?n:n+"",void 0,ne))(e)})(n):n,re=Object.is,ae=Object.defineProperty,se=Object.getOwnPropertyDescriptor,le=Object.getOwnPropertyNames,ce=Object.getOwnPropertySymbols,de=Object.getPrototypeOf,he=globalThis,pe=he.trustedTypes,ue=pe?pe.emptyScript:"",ve=he.reactiveElementPolyfillSupport,ge=(n,e)=>n,_e={toAttribute(n,e){switch(e){case Boolean:n=n?ue:null;break;case Object:case Array:n=null==n?n:JSON.stringify(n)}return n},fromAttribute(n,e){var t=n;switch(e){case Boolean:t=null!==n;break;case Number:t=null===n?null:Number(n);break;case Object:case Array:try{t=JSON.parse(n)}catch(n){t=null}}return t}},fe=(n,e)=>!re(n,e),me={attribute:!0,type:String,converter:_e,reflect:!1,useDefault:!1,hasChanged:fe};null!==(n=Symbol.metadata)&&void 0!==n||(Symbol.metadata=Symbol("metadata")),null!==(e=he.litPropertyMetadata)&&void 0!==e||(he.litPropertyMetadata=new WeakMap);var ye=class extends HTMLElement{static addInitializer(n){var e;this._$Ei(),(null!==(e=this.l)&&void 0!==e?e:this.l=[]).push(n)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(n){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:me;if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(n)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(n,e),!e.noAccessor){var t=Symbol(),i=this.getPropertyDescriptor(n,t,e);void 0!==i&&ae(this.prototype,n,i)}}static getPropertyDescriptor(n,e,t){var i,o=null!==(i=se(this.prototype,n))&&void 0!==i?i:{get(){return this[e]},set(n){this[e]=n}},r=o.get,a=o.set;return{get:r,set(e){var i=null==r?void 0:r.call(this);null!=a&&a.call(this,e),this.requestUpdate(n,i,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(n){var e;return null!==(e=this.elementProperties.get(n))&&void 0!==e?e:me}static _$Ei(){if(!this.hasOwnProperty(ge("elementProperties"))){var n=de(this);n.finalize(),void 0!==n.l&&(this.l=[...n.l]),this.elementProperties=new Map(n.elementProperties)}}static finalize(){if(!this.hasOwnProperty(ge("finalized"))){if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(ge("properties"))){var n=this.properties,e=[...le(n),...ce(n)];for(var t of e)this.createProperty(t,n[t])}var i=this[Symbol.metadata];if(null!==i){var o=litPropertyMetadata.get(i);if(void 0!==o)for(var r of o){var a=Yn(r,2),s=a[0],l=a[1];this.elementProperties.set(s,l)}}for(var c of(this._$Eh=new Map,this.elementProperties)){var d=Yn(c,2),h=d[0],p=d[1],u=this._$Eu(h,p);void 0!==u&&this._$Eh.set(u,h)}this.elementStyles=this.finalizeStyles(this.styles)}}static finalizeStyles(n){var e=[];if(Array.isArray(n)){var t=new Set(n.flat(1/0).reverse());for(var i of t)e.unshift(oe(i))}else void 0!==n&&e.push(oe(n));return e}static _$Eu(n,e){var t=e.attribute;return!1===t?void 0:"string"==typeof t?t:"string"==typeof n?n.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){var n;this._$ES=new Promise((n=>this.enableUpdating=n)),this._$AL=new Map,this._$E_(),this.requestUpdate(),null===(n=this.constructor.l)||void 0===n||n.forEach((n=>n(this)))}addController(n){var e,t;(null!==(e=this._$EO)&&void 0!==e?e:this._$EO=new Set).add(n),void 0!==this.renderRoot&&this.isConnected&&(null===(t=n.hostConnected)||void 0===t||t.call(n))}removeController(n){var e;null===(e=this._$EO)||void 0===e||e.delete(n)}_$E_(){var n=new Map,e=this.constructor.elementProperties;for(var t of e.keys())this.hasOwnProperty(t)&&(n.set(t,this[t]),delete this[t]);n.size>0&&(this._$Ep=n)}createRenderRoot(){var n,e=null!==(n=this.shadowRoot)&&void 0!==n?n:this.attachShadow(this.constructor.shadowRootOptions);return((n,e)=>{if(Qn)n.adoptedStyleSheets=e.map((n=>n instanceof CSSStyleSheet?n:n.styleSheet));else for(var t of e){var i=document.createElement("style"),o=Jn.litNonce;void 0!==o&&i.setAttribute("nonce",o),i.textContent=t.cssText,n.appendChild(i)}})(e,this.constructor.elementStyles),e}connectedCallback(){var n,e;null!==(n=this.renderRoot)&&void 0!==n||(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(e=this._$EO)||void 0===e||e.forEach((n=>{var e;return null===(e=n.hostConnected)||void 0===e?void 0:e.call(n)}))}enableUpdating(n){}disconnectedCallback(){var n;null===(n=this._$EO)||void 0===n||n.forEach((n=>{var e;return null===(e=n.hostDisconnected)||void 0===e?void 0:e.call(n)}))}attributeChangedCallback(n,e,t){this._$AK(n,t)}_$ET(n,e){var t=this.constructor.elementProperties.get(n),i=this.constructor._$Eu(n,t);if(void 0!==i&&!0===t.reflect){var o,r=(void 0!==(null===(o=t.converter)||void 0===o?void 0:o.toAttribute)?t.converter:_e).toAttribute(e,t.type);this._$Em=n,null==r?this.removeAttribute(i):this.setAttribute(i,r),this._$Em=null}}_$AK(n,e){var t=this.constructor,i=t._$Eh.get(n);if(void 0!==i&&this._$Em!==i){var o,r,a,s=t.getPropertyOptions(i),l="function"==typeof s.converter?{fromAttribute:s.converter}:void 0!==(null===(o=s.converter)||void 0===o?void 0:o.fromAttribute)?s.converter:_e;this._$Em=i;var c=l.fromAttribute(e,s.type);this[i]=null!==(r=null!=c?c:null===(a=this._$Ej)||void 0===a?void 0:a.get(i))&&void 0!==r?r:c,this._$Em=null}}requestUpdate(n,e,t){if(void 0!==n){var i,o,r=this.constructor,a=this[n];if(null!=t||(t=r.getPropertyOptions(n)),!((null!==(i=t.hasChanged)&&void 0!==i?i:fe)(a,e)||t.useDefault&&t.reflect&&a===(null===(o=this._$Ej)||void 0===o?void 0:o.get(n))&&!this.hasAttribute(r._$Eu(n,t))))return;this.C(n,e,t)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(n,e,t,i){var o,r,a,s=t.useDefault,l=t.reflect,c=t.wrapped;s&&!(null!==(o=this._$Ej)&&void 0!==o?o:this._$Ej=new Map).has(n)&&(this._$Ej.set(n,null!==(r=null!=i?i:e)&&void 0!==r?r:this[n]),!0!==c||void 0!==i)||(this._$AL.has(n)||(this.hasUpdated||s||(e=void 0),this._$AL.set(n,e)),!0===l&&this._$Em!==n&&(null!==(a=this._$Eq)&&void 0!==a?a:this._$Eq=new Set).add(n))}_$EP(){var n=this;return Gn((function*(){n.isUpdatePending=!0;try{yield n._$ES}catch(e){Promise.reject(e)}var e=n.scheduleUpdate();return null!=e&&(yield e),!n.isUpdatePending}))()}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(this.isUpdatePending){if(!this.hasUpdated){var n;if(null!==(n=this.renderRoot)&&void 0!==n||(this.renderRoot=this.createRenderRoot()),this._$Ep){for(var e of this._$Ep){var t=Yn(e,2),i=t[0],o=t[1];this[i]=o}this._$Ep=void 0}var r=this.constructor.elementProperties;if(r.size>0)for(var a of r){var s=Yn(a,2),l=s[0],c=s[1],d=c.wrapped,h=this[l];!0!==d||this._$AL.has(l)||void 0===h||this.C(l,void 0,c,h)}}var p=!1,u=this._$AL;try{var v;(p=this.shouldUpdate(u))?(this.willUpdate(u),null!==(v=this._$EO)&&void 0!==v&&v.forEach((n=>{var e;return null===(e=n.hostUpdate)||void 0===e?void 0:e.call(n)})),this.update(u)):this._$EM()}catch(u){throw p=!1,this._$EM(),u}p&&this._$AE(u)}}willUpdate(n){}_$AE(n){var e;null!==(e=this._$EO)&&void 0!==e&&e.forEach((n=>{var e;return null===(e=n.hostUpdated)||void 0===e?void 0:e.call(n)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(n)),this.updated(n)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(n){return!0}update(n){this._$Eq&&(this._$Eq=this._$Eq.forEach((n=>this._$ET(n,this[n])))),this._$EM()}updated(n){}firstUpdated(n){}};ye.elementStyles=[],ye.shadowRootOptions={mode:"open"},ye[ge("elementProperties")]=new Map,ye[ge("finalized")]=new Map,null!=ve&&ve({ReactiveElement:ye}),(null!==(t=he.reactiveElementVersions)&&void 0!==t?t:he.reactiveElementVersions=[]).push("2.1.1");var be=globalThis,we=be.trustedTypes,xe=we?we.createPolicy("lit-html",{createHTML:n=>n}):void 0,ke="$lit$",Ce="lit$".concat(Math.random().toFixed(9).slice(2),"$"),Ee="?"+Ce,Se="<".concat(Ee,">"),Ae=document,$e=()=>Ae.createComment(""),Le=n=>null===n||"object"!=typeof n&&"function"!=typeof n,Pe=Array.isArray,Me="[ \t\n\f\r]",ze=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Ne=/-->/g,Oe=/>/g,Re=RegExp(">|".concat(Me,"(?:([^\\s\"'>=/]+)(").concat(Me,"*=").concat(Me,"*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)"),"g"),He=/'/g,Te=/"/g,De=/^(?:script|style|textarea|title)$/i,Ie=(n=>function(e){for(var t=arguments.length,i=new Array(t>1?t-1:0),o=1;o{for(var t,i=n.length-1,o=[],r=2===e?"":3===e?"":"",a=ze,s=0;s"===d[0]?(a=null!=t?t:ze,h=-1):void 0===d[1]?h=-2:(h=a.lastIndex-d[2].length,c=d[1],a=void 0===d[3]?Re:'"'===d[3]?Te:He):a===Te||a===He?a=Re:a===Ne||a===Oe?a=ze:(a=Re,t=void 0);var u=a===Re&&n[s+1].startsWith("/>")?" ":"";r+=a===ze?l+Se:h>=0?(o.push(c),l.slice(0,h)+ke+l.slice(h)+Ce+u):l+Ce+(-2===h?s:u)}return[Be(n,r+(n[i]||"")+(2===e?"":3===e?"":"")),o]};class qe{constructor(n,e){var t,i=n.strings,o=n._$litType$;this.parts=[];var r=0,a=0,s=i.length-1,l=this.parts,c=Yn(We(i,o),2),d=c[0],h=c[1];if(this.el=qe.createElement(d,e),Fe.currentNode=this.el.content,2===o||3===o){var p=this.el.content.firstChild;p.replaceWith(...p.childNodes)}for(;null!==(t=Fe.nextNode())&&l.length0){t.textContent=we?we.emptyScript:"";for(var y=0;y2&&void 0!==arguments[2]?arguments[2]:n,l=arguments.length>3?arguments[3]:void 0;if(e===Ue)return e;var c=void 0!==l?null===(t=s._$Co)||void 0===t?void 0:t[l]:s._$Cl,d=Le(e)?void 0:e._$litDirective$;return(null===(i=c)||void 0===i?void 0:i.constructor)!==d&&(null!==(o=c)&&void 0!==o&&null!==(r=o._$AO)&&void 0!==r&&r.call(o,!1),void 0===d?c=void 0:(c=new d(n))._$AT(n,s,l),void 0!==l?(null!==(a=s._$Co)&&void 0!==a?a:s._$Co=[])[l]=c:s._$Cl=c),void 0!==c&&(e=Ze(n,c._$AS(n,e.values),c,l)),e}class Ke{constructor(n,e){this._$AV=[],this._$AN=void 0,this._$AD=n,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(n){var e,t=this._$AD,i=t.el.content,o=t.parts,r=(null!==(e=null==n?void 0:n.creationScope)&&void 0!==e?e:Ae).importNode(i,!0);Fe.currentNode=r;for(var a=Fe.nextNode(),s=0,l=0,c=o[0];void 0!==c;){var d;if(s===c.index){var h=void 0;2===c.type?h=new Ge(a,a.nextSibling,this,n):1===c.type?h=new c.ctor(a,c.name,c.strings,this,n):6===c.type&&(h=new nt(a,this,n)),this._$AV.push(h),c=o[++l]}s!==(null===(d=c)||void 0===d?void 0:d.index)&&(a=Fe.nextNode(),s++)}return Fe.currentNode=Ae,r}p(n){var e=0;for(var t of this._$AV)void 0!==t&&(void 0!==t.strings?(t._$AI(n,t,e),e+=t.strings.length-2):t._$AI(n[e])),e++}}class Ge{get _$AU(){var n,e;return null!==(n=null===(e=this._$AM)||void 0===e?void 0:e._$AU)&&void 0!==n?n:this._$Cv}constructor(n,e,t,i){var o;this.type=2,this._$AH=je,this._$AN=void 0,this._$AA=n,this._$AB=e,this._$AM=t,this.options=i,this._$Cv=null===(o=null==i?void 0:i.isConnected)||void 0===o||o}get parentNode(){var n,e=this._$AA.parentNode,t=this._$AM;return void 0!==t&&11===(null===(n=e)||void 0===n?void 0:n.nodeType)&&(e=t.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(n){n=Ze(this,n,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this),Le(n)?n===je||null==n||""===n?(this._$AH!==je&&this._$AR(),this._$AH=je):n!==this._$AH&&n!==Ue&&this._(n):void 0!==n._$litType$?this.$(n):void 0!==n.nodeType?this.T(n):(n=>Pe(n)||"function"==typeof(null==n?void 0:n[Symbol.iterator]))(n)?this.k(n):this._(n)}O(n){return this._$AA.parentNode.insertBefore(n,this._$AB)}T(n){this._$AH!==n&&(this._$AR(),this._$AH=this.O(n))}_(n){this._$AH!==je&&Le(this._$AH)?this._$AA.nextSibling.data=n:this.T(Ae.createTextNode(n)),this._$AH=n}$(n){var e,t=n.values,i=n._$litType$,o="number"==typeof i?this._$AC(n):(void 0===i.el&&(i.el=qe.createElement(Be(i.h,i.h[0]),this.options)),i);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===o)this._$AH.p(t);else{var r=new Ke(o,this),a=r.u(this.options);r.p(t),this.T(a),this._$AH=r}}_$AC(n){var e=Ve.get(n.strings);return void 0===e&&Ve.set(n.strings,e=new qe(n)),e}k(n){Pe(this._$AH)||(this._$AH=[],this._$AR());var e,t=this._$AH,i=0;for(var o of n)i===t.length?t.push(e=new Ge(this.O($e()),this.O($e()),this,this.options)):e=t[i],e._$AI(o),i++;i0&&void 0!==arguments[0]?arguments[0]:this._$AA.nextSibling,e=arguments.length>1?arguments[1]:void 0;for(null===(t=this._$AP)||void 0===t||t.call(this,!1,!0,e);n!==this._$AB;){var t,i=n.nextSibling;n.remove(),n=i}}setConnected(n){var e;void 0===this._$AM&&(this._$Cv=n,null===(e=this._$AP)||void 0===e||e.call(this,n))}}class Ye{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(n,e,t,i,o){this.type=1,this._$AH=je,this._$AN=void 0,this.element=n,this.name=e,this._$AM=i,this.options=o,t.length>2||""!==t[0]||""!==t[1]?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=je}_$AI(n){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,t=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=this.strings,r=!1;if(void 0===o)n=Ze(this,n,e,0),(r=!Le(n)||n!==this._$AH&&n!==Ue)&&(this._$AH=n);else{var a,s,l=n;for(n=o[0],a=0;a1&&void 0!==arguments[1]?arguments[1]:this,0))&&void 0!==e?e:je)!==Ue){var t=this._$AH,i=n===je&&t!==je||n.capture!==t.capture||n.once!==t.once||n.passive!==t.passive,o=n!==je&&(t===je||i);i&&this.element.removeEventListener(this.name,this,t),o&&this.element.addEventListener(this.name,this,n),this._$AH=n}}handleEvent(n){var e,t;"function"==typeof this._$AH?this._$AH.call(null!==(e=null===(t=this.options)||void 0===t?void 0:t.host)&&void 0!==e?e:this.element,n):this._$AH.handleEvent(n)}}class nt{constructor(n,e,t){this.element=n,this.type=6,this._$AN=void 0,this._$AM=e,this.options=t}get _$AU(){return this._$AM._$AU}_$AI(n){Ze(this,n)}}var et=be.litHtmlPolyfillSupport;null!=et&&et(qe,Ge),(null!==(i=be.litHtmlVersions)&&void 0!==i?i:be.litHtmlVersions=[]).push("3.3.3");var tt=globalThis,it=class extends ye{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){var n,e,t=super.createRenderRoot();return null!==(e=(n=this.renderOptions).renderBefore)&&void 0!==e||(n.renderBefore=t.firstChild),t}update(n){var e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(n),this._$Do=((n,e,t)=>{var i,o=null!==(i=null==t?void 0:t.renderBefore)&&void 0!==i?i:e,r=o._$litPart$;if(void 0===r){var a,s=null!==(a=null==t?void 0:t.renderBefore)&&void 0!==a?a:null;o._$litPart$=r=new Ge(e.insertBefore($e(),s),s,void 0,null!=t?t:{})}return r._$AI(n),r})(e,this.renderRoot,this.renderOptions)}connectedCallback(){var n;super.connectedCallback(),null===(n=this._$Do)||void 0===n||n.setConnected(!0)}disconnectedCallback(){var n;super.disconnectedCallback(),null===(n=this._$Do)||void 0===n||n.setConnected(!1)}render(){return Ue}};it._$litElement$=!0,it.finalized=!0,null===(o=tt.litElementHydrateSupport)||void 0===o||o.call(tt,{LitElement:it});var ot=tt.litElementPolyfillSupport;null==ot||ot({LitElement:it}),(null!==(r=tt.litElementVersions)&&void 0!==r?r:tt.litElementVersions=[]).push("4.2.1");var rt="code",at="pin_used",st="active",lt="in_sync",ct=["calendar","condition_entity"],dt={type:"divider"},ht="masked_with_reveal",pt=!0,ut=!0,vt=!0,gt=!0,_t=!0;function ft(n){var e,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"-",i="àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìıİłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·",o="aaaaaaaaaacccddeeeeeeeegghiiiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz".concat(t),r=new RegExp(i.split("").join("|"),"g");return""===n?e="":(e=n.toString().toLowerCase().replace(r,(n=>o.charAt(i.indexOf(n)))).replace(/(\d),(?=\d)/g,"$1").replace(/[^a-z0-9]+/g,t).replace(new RegExp("(".concat(t,")\\1+"),"g"),"$1").replace(new RegExp("^".concat(t,"+")),"").replace(new RegExp("".concat(t,"+$")),""),""===e&&(e="unknown")),e}function mt(n){return{cards:[{content:n,type:"markdown"}],title:arguments.length>1&&void 0!==arguments[1]?arguments[1]:"Lock Code Manager"}}function yt(n,e,t,i){var o,r=arguments.length,a=r<3?e:null===i?i=Object.getOwnPropertyDescriptor(e,t):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(n,e,t,i);else for(var s=n.length-1;s>=0;s--)(o=n[s])&&(a=(r<3?o(a):r>3?o(e,t,a):o(e,t))||a);return r>3&&a&&Object.defineProperty(e,t,a),a}"function"==typeof SuppressedError&&SuppressedError;var bt="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z",wt="M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z",xt="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z",kt="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z",Ct="M11.83,9L15,12.16C15,12.11 15,12.05 15,12A3,3 0 0,0 12,9C11.94,9 11.89,9 11.83,9M7.53,9.8L9.08,11.35C9.03,11.56 9,11.77 9,12A3,3 0 0,0 12,15C12.22,15 12.44,14.97 12.65,14.92L14.2,16.47C13.53,16.8 12.79,17 12,17A5,5 0 0,1 7,12C7,11.21 7.2,10.47 7.53,9.8M2,4.27L4.28,6.55L4.73,7C3.08,8.3 1.78,10 1,12C2.73,16.39 7,19.5 12,19.5C13.55,19.5 15.03,19.2 16.38,18.66L16.81,19.08L19.73,22L21,20.73L3.27,3M12,7A5,5 0 0,1 17,12C17,12.64 16.87,13.26 16.64,13.82L19.57,16.75C21.07,15.5 22.27,13.86 23,12C21.27,7.61 17,4.5 12,4.5C10.6,4.5 9.26,4.75 8,5.2L10.17,7.35C10.74,7.13 11.35,7 12,7Z",Et={attribute:!0,type:String,converter:_e,reflect:!1,hasChanged:fe},St=function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Et,e=arguments.length>1?arguments[1]:void 0,t=arguments.length>2?arguments[2]:void 0,i=t.kind,o=t.metadata,r=globalThis.litPropertyMetadata.get(o);if(void 0===r&&globalThis.litPropertyMetadata.set(o,r=new Map),"setter"===i&&((n=Object.create(n)).wrapped=!0),r.set(t.name,n),"accessor"===i){var a=t.name;return{set(t){var i=e.get.call(this);e.set.call(this,t),this.requestUpdate(a,i,n)},init(e){return void 0!==e&&this.C(a,void 0,n,e),e}}}if("setter"===i){var s=t.name;return function(t){var i=this[s];e.call(this,t),this.requestUpdate(s,i,n)}}throw Error("Unsupported decorator location: "+i)};function At(n){return(e,t)=>"object"==typeof t?St(n,e,t):((n,e,t)=>{var i=e.hasOwnProperty(t);return e.constructor.createProperty(t,n),i?Object.getOwnPropertyDescriptor(e,t):void 0})(n,e,t)}function $t(n){return At(qn(qn({},n),{},{state:!0,attribute:!1}))}var Lt=n=>null!=n?n:je,Pt=ie(a||(a=Bn(["\n :host {\n /* Section backgrounds */\n --lcm-section-bg: rgba(var(--rgb-primary-text-color), 0.03);\n --lcm-section-bg-hover: rgba(var(--rgb-primary-text-color), 0.06);\n\n /* Active states */\n --lcm-active-bg: rgba(var(--rgb-primary-color), 0.1);\n --lcm-active-bg-gradient: linear-gradient(\n 135deg,\n rgba(var(--rgb-primary-color), 0.1),\n rgba(var(--rgb-primary-color), 0.04)\n );\n\n /* Status colors */\n --lcm-success-color: var(--success-color, #4caf50);\n --lcm-warning-color: var(--warning-color, #ff9800);\n --lcm-error-color: var(--error-color, #f44336);\n --lcm-disabled-color: var(--disabled-text-color, #9e9e9e);\n\n /* Badge styling */\n --lcm-badge-radius: 999px;\n --lcm-badge-font-size: 10px;\n --lcm-badge-font-weight: 600;\n --lcm-badge-letter-spacing: 0.02em;\n --lcm-badge-padding: 2px 6px;\n\n /* Section header typography */\n --lcm-section-header-size: 11px;\n --lcm-section-header-weight: 600;\n --lcm-section-header-spacing: 0.05em;\n\n /* Code/PIN typography */\n --lcm-code-font: 'Roboto Mono', monospace;\n --lcm-code-font-size: 16px;\n --lcm-code-font-weight: 600;\n --lcm-code-letter-spacing: 1px;\n\n /* Border colors */\n --lcm-border-color: rgba(var(--rgb-primary-text-color), 0.06);\n --lcm-border-color-strong: rgba(var(--rgb-primary-text-color), 0.12);\n }\n"]))),Mt=ie(s||(s=Bn(["\n /* Base badge — used by identity tags (Managed/Unmanaged/Empty).\n Compact uppercase for category labels. */\n .lcm-badge {\n border-radius: var(--lcm-badge-radius);\n font-size: var(--lcm-badge-font-size);\n font-weight: var(--lcm-badge-font-weight);\n letter-spacing: var(--lcm-badge-letter-spacing);\n padding: var(--lcm-badge-padding);\n text-transform: uppercase;\n }\n\n /* State badges (active/inactive/disabled) align visually with the slot\n card's .state-chip: pill shape, sentence-case, optional colored dot\n prefix, 16% color tint. Same colors as the slot card so a state reads\n the same regardless of which card you're on. */\n .lcm-badge.active,\n .lcm-badge.inactive,\n .lcm-badge.disabled {\n align-items: center;\n border-radius: 12px;\n display: inline-flex;\n font-size: 10px;\n font-weight: 600;\n gap: 5px;\n letter-spacing: normal;\n padding: 3px 8px;\n text-transform: none;\n }\n .lcm-badge .dot {\n border-radius: 50%;\n flex-shrink: 0;\n height: 5px;\n width: 5px;\n }\n\n .lcm-badge.active {\n background: rgba(var(--rgb-success-color, 67, 160, 71), 0.16);\n color: var(--success-color, #43a047);\n }\n .lcm-badge.active .dot {\n background: var(--success-color, #43a047);\n }\n\n .lcm-badge.inactive {\n background: rgba(var(--rgb-warning-color, 255, 167, 38), 0.16);\n color: var(--warning-color, #ffa726);\n }\n .lcm-badge.inactive .dot {\n background: var(--warning-color, #ffa726);\n }\n\n .lcm-badge.disabled {\n background: rgba(var(--rgb-disabled-color, 117, 117, 117), 0.2);\n color: var(--secondary-text-color);\n }\n .lcm-badge.disabled .dot {\n background: var(--disabled-color, #757575);\n }\n\n .lcm-badge.empty {\n background: rgba(var(--rgb-primary-text-color), 0.08);\n color: var(--secondary-text-color);\n }\n\n .lcm-badge.managed {\n background: rgba(var(--rgb-primary-color), 0.16);\n color: var(--primary-color);\n }\n\n .lcm-badge.external {\n background: rgba(var(--rgb-primary-text-color), 0.08);\n color: var(--secondary-text-color);\n }\n"]))),zt=ie(l||(l=Bn(["\n .lcm-sync-icon {\n --mdc-icon-size: 18px;\n }\n\n .lcm-sync-icon.synced {\n color: var(--lcm-success-color);\n }\n\n .lcm-sync-icon.pending {\n color: var(--lcm-warning-color);\n }\n\n .lcm-sync-icon.syncing {\n color: var(--lcm-warning-color);\n }\n\n .lcm-sync-icon.suspended {\n color: var(--lcm-error-color);\n }\n\n .lcm-sync-icon.unknown {\n color: var(--lcm-disabled-color);\n }\n"]))),Nt=ie(c||(c=Bn(["\n .lcm-code {\n color: var(--primary-text-color);\n font-family: var(--lcm-code-font);\n font-size: var(--lcm-code-font-size);\n font-weight: var(--lcm-code-font-weight);\n letter-spacing: var(--lcm-code-letter-spacing);\n }\n\n .lcm-code.masked {\n color: var(--secondary-text-color);\n }\n\n /* Slot disabled by user — PIN exists in config but is intentionally not on\n the lock. Heavily dimmed dots in a muted pill, no strikethrough. */\n .lcm-code.off {\n background: var(--lcm-section-bg, rgba(127, 127, 127, 0.05));\n border-radius: 6px;\n color: var(--disabled-text-color);\n padding: 2px 8px;\n }\n\n /* Slot enabled but lock doesn't have the code yet (out-of-sync, syncing, etc.).\n Dim dots with a clock-icon prefix. No strikethrough. */\n .lcm-code.pending {\n align-items: center;\n color: var(--secondary-text-color);\n display: inline-flex;\n gap: 4px;\n }\n\n .lcm-code.pending .lcm-code-pending-icon {\n --mdc-icon-size: 12px;\n color: var(--secondary-text-color);\n flex-shrink: 0;\n }\n\n .lcm-code.no-code {\n color: var(--disabled-text-color);\n font-family: inherit;\n font-size: 12px;\n font-style: italic;\n font-weight: 400;\n letter-spacing: normal;\n }\n"]))),Ot=ie(d||(d=Bn(["\n .lcm-section {\n background: var(--lcm-section-bg);\n border-radius: 12px;\n padding: 16px;\n }\n\n .lcm-section-header {\n color: var(--secondary-text-color);\n font-size: var(--lcm-section-header-size);\n font-weight: var(--lcm-section-header-weight);\n letter-spacing: var(--lcm-section-header-spacing);\n margin-bottom: 12px;\n text-transform: uppercase;\n }\n"]))),Rt=ie(h||(h=Bn(["\n .lcm-reveal-button {\n /* 32px hit target — bumped from 28px to be a comfortable middle\n between the WCAG 2.2 SC 2.5.8 AA minimum (24px) and the\n SC 2.5.5 AAA recommendation (44px). */\n --mdc-icon-button-size: 32px;\n --mdc-icon-size: 16px;\n color: var(--secondary-text-color);\n }\n"]))),Ht=ie(p||(p=Bn(['\n .collapsible-section {\n background: var(--lcm-section-bg);\n border-radius: 12px;\n overflow: hidden;\n }\n\n .collapsible-header {\n align-items: center;\n cursor: pointer;\n display: flex;\n justify-content: space-between;\n padding: 12px 16px;\n user-select: none;\n }\n\n .collapsible-header:hover {\n background: var(--lcm-section-bg-hover);\n }\n\n .collapsible-title {\n align-items: center;\n color: var(--secondary-text-color);\n display: flex;\n font-size: var(--lcm-section-header-size);\n font-weight: var(--lcm-section-header-weight);\n gap: 8px;\n letter-spacing: var(--lcm-section-header-spacing);\n text-transform: uppercase;\n }\n\n .collapsible-badge {\n align-items: center;\n background: var(--lcm-active-bg);\n border-radius: 10px;\n color: var(--primary-color);\n display: inline-flex;\n font-size: var(--lcm-badge-font-size);\n gap: 4px;\n padding: 2px 8px;\n }\n\n /* Icon prefix on a collapsible badge — sized down to 12px so it pairs\n with the 10px badge text without dominating it. Color inherits from\n the badge color so success/warning modifiers carry through. */\n .collapsible-badge-icon {\n --mdc-icon-size: 12px;\n color: inherit;\n flex-shrink: 0;\n }\n\n .collapsible-badge.primary {\n background: var(--primary-color);\n color: var(--text-primary-color, #fff);\n }\n\n .collapsible-badge.warning {\n background: var(--warning-color, #ffa600);\n color: var(--text-primary-color, #fff);\n }\n\n /* Success modifier — used for the "allowing" condition summary so that\n allowing reads as green and blocking reads as warning everywhere\n across the cards. 16% follows the canonical chip/badge opacity stop. */\n .collapsible-badge.success {\n background: rgba(var(--rgb-success-color, 67, 160, 71), 0.16);\n color: var(--success-color, #43a047);\n }\n\n .collapsible-badge.muted {\n background: var(--lcm-section-bg);\n color: var(--secondary-text-color);\n }\n\n .collapsible-chevron {\n --mdc-icon-size: 20px;\n color: var(--secondary-text-color);\n transition: transform 0.2s ease;\n }\n\n .collapsible-content {\n max-height: 0;\n opacity: 0;\n overflow: hidden;\n padding: 0 16px;\n transition:\n max-height 0.3s ease,\n opacity 0.2s ease,\n padding 0.3s ease;\n }\n\n .collapsible-content.expanded {\n /* 1000px ceiling — was 500px, which clipped when many helpers +\n a calendar entity row stacked. A grid-rows transition to auto\n is the proper fix but more invasive; bumping the ceiling is\n the lower-risk shim. */\n max-height: 1000px;\n opacity: 1;\n padding: 0 16px 16px;\n }\n']))),Tt=ie(u||(u=Bn(["\n .editable {\n border-radius: 4px;\n cursor: pointer;\n margin: -4px -8px;\n padding: 4px 8px;\n text-decoration: underline dashed;\n text-decoration-color: var(--secondary-text-color);\n text-underline-offset: 3px;\n transition: background-color 0.2s;\n }\n\n .editable:hover {\n background: var(--lcm-active-bg);\n }\n\n .edit-input {\n background: var(--card-background-color, #fff);\n border: 1px solid var(--primary-color);\n border-radius: 4px;\n color: var(--primary-text-color);\n font-family: inherit;\n font-size: inherit;\n outline: none;\n padding: 4px 8px;\n width: 100%;\n }\n\n .edit-input:focus {\n box-shadow: 0 0 0 1px var(--primary-color);\n }\n\n .edit-help {\n color: var(--secondary-text-color);\n font-size: var(--lcm-section-header-size);\n margin-top: 4px;\n }\n"]))),Dt=ie(v||(v=Bn(["\n .visually-hidden {\n border: 0;\n clip: rect(0 0 0 0);\n height: 1px;\n margin: -1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n width: 1px;\n }\n"]))),It=ie(g||(g=Bn(["\n @media (prefers-reduced-motion: reduce) {\n .collapsible-content,\n .collapsible-chevron,\n .slot-chip.clickable,\n .editable,\n .hero-name-value.editable,\n .hero-pin-value.editable,\n .lcm-code.editable,\n .event-row {\n transition: none !important;\n }\n }\n"])));ie(_||(_=Bn(["\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n"])),Pt,Mt,zt,Nt,Ot,Rt,Ht,Tt,Dt,It);var Ut=[Pt,Mt,Nt,Rt,Dt,It,ie(f||(f=Bn(['\n :host {\n display: block;\n }\n\n ha-card {\n padding: 0;\n }\n\n .card-header {\n align-items: center;\n border-bottom: 1px solid var(--lcm-border-color);\n display: flex;\n gap: 12px;\n padding: 16px;\n }\n\n .header-icon {\n align-items: center;\n background: var(--lcm-active-bg);\n border-radius: 50%;\n color: var(--primary-color);\n display: flex;\n height: 40px;\n justify-content: center;\n width: 40px;\n }\n\n .header-icon ha-icon {\n --mdc-icon-size: 24px;\n }\n\n .card-header-title {\n color: var(--primary-text-color);\n font-size: 18px;\n font-weight: 500;\n margin: 0;\n }\n\n .card-content {\n padding: 16px;\n }\n\n .slots-grid {\n display: grid;\n gap: 10px;\n grid-template-columns: repeat(2, 1fr);\n }\n\n @media (max-width: 400px) {\n .slots-grid {\n grid-template-columns: 1fr;\n }\n }\n\n .slot-chip {\n background: var(--lcm-section-bg);\n border-radius: 12px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n overflow: hidden;\n padding: 12px 12px 14px;\n position: relative;\n }\n\n /* Active Lock Code Manager Managed: no special tint (inherits the\n chip\'s --lcm-section-bg). The state badge inside the chip carries\n the "active" signal — color the exception, not the norm. */\n\n /* Active Unmanaged (not Lock Code Manager): subtle warm-gray accent\n so an occupied unmanaged slot reads as different from an Empty\n chip (which uses the same --lcm-section-bg at 3%). 5% sits between\n the empty 3% and the inactive/disabled 6% tints. */\n .slot-chip.active.unmanaged {\n background: rgba(var(--rgb-primary-text-color), 0.05);\n }\n\n /* Inactive Lock Code Manager Managed: warning tint (orange) to match\n the slot card\'s inactive treatment — communicates "blocked by\n conditions" without shouting. 6% follows the canonical background\n opacity stop. */\n .slot-chip.inactive.managed {\n background: rgba(var(--rgb-warning-color, 255, 167, 38), 0.06);\n opacity: 0.9;\n }\n\n /* Disabled Lock Code Manager Managed: muted neutral tint, clear\n disabled state. 6% follows the canonical background opacity stop. */\n .slot-chip.disabled.managed {\n background: rgba(var(--rgb-primary-text-color), 0.06);\n opacity: 0.65;\n }\n\n .slot-chip.empty {\n background: var(--lcm-section-bg);\n opacity: 0.7;\n }\n\n .slot-chip.full-width {\n grid-column: 1 / -1;\n justify-self: center;\n max-width: 360px;\n width: 100%;\n }\n\n .slot-chip.clickable {\n cursor: pointer;\n transition:\n transform 0.1s ease,\n box-shadow 0.2s ease;\n }\n\n .slot-chip.clickable:hover {\n box-shadow: 0 2px 8px rgba(var(--rgb-primary-color), 0.25);\n transform: translateY(-1px);\n }\n\n .slot-chip.clickable:active {\n transform: translateY(0);\n }\n\n .slot-chip.clickable:focus-visible {\n outline: 2px solid var(--primary-color);\n outline-offset: 2px;\n }\n\n .slot-top {\n /* Align both children to the top so the slot label sits in a stable\n position regardless of how tall the badge stack grows. With\n align-items: center, a 2-badge stack would push the label down\n to sit between the badges; flex-start keeps it aligned with the\n first (top) badge — matching the empty-chip layout where the\n label and the single Empty badge share one row. */\n align-items: flex-start;\n display: flex;\n gap: 12px;\n justify-content: space-between;\n }\n\n .slot-badges {\n align-items: flex-end;\n display: inline-flex;\n flex-direction: column;\n flex-shrink: 0;\n gap: 4px;\n }\n\n .slot-label {\n color: var(--secondary-text-color);\n flex: 1;\n font-size: var(--lcm-section-header-size);\n font-weight: 500;\n letter-spacing: 0.03em;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n text-transform: uppercase;\n white-space: nowrap;\n }\n\n /* Config entry title appended to "Slot N · {entry}" — matches the slot-card kicker style. */\n .slot-entry-title {\n color: var(--secondary-text-color);\n font-weight: 500;\n }\n\n /* Two stacked rows per chip: name on top, PIN on the bottom. The chip is\n narrow enough that competing for one row truncated long names too\n aggressively (e.g. "Raman" → "R."). Stacking gives the name the full\n chip width and lets the PIN row anchor the eye icon to the right.\n This intentionally diverges from the slot-card hero, which uses a\n prominent single-line name; in the lock card the name is a label, not\n the focal point. */\n .slot-content-row {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 6px;\n min-width: 0;\n }\n\n .slot-name {\n color: var(--primary-text-color);\n font-size: 14px;\n font-weight: 400;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .slot-name.unnamed {\n color: var(--secondary-text-color);\n font-weight: 400;\n }\n\n /* Wrapper around the slot name; lays out the optional pending icon\n alongside the name with a small gap. Inline-flex so the row only takes\n the space it needs and the chip\'s column layout still controls width. */\n .slot-name-row {\n align-items: center;\n display: inline-flex;\n gap: 4px;\n max-width: 100%;\n min-width: 0;\n }\n\n /* Slot disabled by user — name shown in a muted pill, no strikethrough.\n Mirrors the .lcm-code.off treatment for visual consistency. */\n .slot-chip.disabled .slot-name {\n background: var(--lcm-section-bg, rgba(127, 127, 127, 0.05));\n border-radius: 6px;\n color: var(--disabled-text-color);\n padding: 2px 8px;\n }\n\n /* Slot enabled but lock doesn\'t have the code yet — clock-icon prefix on\n the name. Mirrors the .lcm-code.pending treatment. The disabled rule\'s\n pill background takes precedence if a slot is somehow both. */\n .slot-chip.pending .slot-name-pending-icon {\n --mdc-icon-size: 12px;\n color: var(--secondary-text-color);\n flex-shrink: 0;\n }\n\n .slot-chip.pending .slot-name {\n color: var(--secondary-text-color);\n }\n\n .slot-code-row {\n align-items: center;\n display: flex;\n gap: 8px;\n justify-content: space-between;\n }\n\n .slot-code-actions {\n display: inline-flex;\n }\n\n /* Editable code for unmanaged slots */\n .slot-code-edit {\n display: flex;\n flex-direction: column;\n gap: 4px;\n width: 100%;\n }\n\n .slot-code-edit-row {\n align-items: center;\n display: flex;\n gap: 8px;\n }\n\n .slot-code-input {\n background: var(--card-background-color, #fff);\n border: 1px solid var(--primary-color);\n border-radius: 6px;\n color: var(--primary-text-color);\n flex: 1;\n font-family: var(--lcm-code-font);\n font-size: 14px;\n font-weight: 500;\n letter-spacing: var(--lcm-code-letter-spacing);\n min-width: 0;\n outline: none;\n padding: 6px 10px;\n }\n\n .slot-code-input:focus {\n box-shadow: 0 0 0 1px var(--primary-color);\n }\n\n .slot-code-input::placeholder {\n color: var(--secondary-text-color);\n font-weight: 400;\n letter-spacing: normal;\n }\n\n .slot-code-edit-buttons {\n display: flex;\n gap: 4px;\n }\n\n .slot-code-edit-buttons ha-icon-button {\n --mdc-icon-button-size: 32px;\n --mdc-icon-size: 18px;\n }\n\n .slot-edit-help {\n color: var(--secondary-text-color);\n font-size: 10px;\n }\n\n /* Editable code display (click to edit) */\n .lcm-code.editable {\n border-radius: 4px;\n cursor: pointer;\n margin: -2px -4px;\n padding: 2px 4px;\n transition: background-color 0.2s;\n }\n\n .lcm-code.editable:hover {\n background: var(--lcm-active-bg);\n }\n\n .empty-summary {\n align-items: center;\n background: var(--lcm-section-bg);\n border: 1px dashed var(--lcm-border-color-strong);\n border-radius: 10px;\n color: var(--secondary-text-color);\n display: flex;\n font-size: 12px;\n gap: 8px;\n grid-column: 1 / -1;\n padding: 8px 12px;\n }\n\n .empty-summary ha-icon {\n --mdc-icon-size: 16px;\n color: var(--secondary-text-color);\n }\n\n .empty-summary-label {\n color: var(--secondary-text-color);\n font-size: var(--lcm-section-header-size);\n font-weight: 600;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n }\n\n .empty-summary-range {\n color: var(--primary-text-color);\n font-size: 13px;\n font-weight: 500;\n }\n\n .message {\n color: var(--secondary-text-color);\n font-style: italic;\n }\n\n /* Summary table */\n .summary-table {\n border-collapse: collapse;\n font-size: 12px;\n margin-top: 16px;\n table-layout: fixed;\n width: 100%;\n }\n\n .summary-table th,\n .summary-table td {\n overflow: hidden;\n padding: 6px 4px;\n text-align: center;\n text-overflow: ellipsis;\n }\n\n .summary-table th {\n background: rgba(var(--rgb-primary-text-color), 0.04);\n color: var(--secondary-text-color);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n }\n\n .summary-table th:first-child {\n border-radius: 6px 0 0 0;\n text-align: left;\n }\n\n .summary-table th:last-child {\n border-radius: 0 6px 0 0;\n }\n\n .summary-table td,\n .summary-table tbody th {\n border-top: 1px solid var(--lcm-border-color);\n color: var(--primary-text-color);\n font-weight: 500;\n }\n\n .summary-table tbody th[scope=\'row\'] {\n color: var(--secondary-text-color);\n font-size: var(--lcm-section-header-size);\n font-weight: 600;\n letter-spacing: 0.03em;\n text-align: left;\n text-transform: uppercase;\n }\n\n .summary-table tbody tr:last-child th[scope=\'row\'] {\n border-radius: 0 0 0 6px;\n }\n\n .summary-table tbody tr:last-child td:last-child {\n border-radius: 0 0 6px 0;\n }\n\n .summary-table .total-row td,\n .summary-table .total-row th {\n background: rgba(var(--rgb-primary-text-color), 0.02);\n border-top: 2px solid var(--lcm-border-color-strong);\n font-weight: 600;\n }\n\n .summary-cell-zero {\n color: var(--disabled-text-color) !important;\n font-weight: 400 !important;\n }\n\n ha-card.suspended {\n border: 1px solid var(--lcm-error-color);\n opacity: 0.85;\n }\n\n .suspended-banner {\n align-items: center;\n background: rgba(244, 67, 54, 0.08);\n border-bottom: 1px solid var(--lcm-border-color);\n color: var(--lcm-error-color);\n display: flex;\n font-size: 12px;\n font-weight: 500;\n gap: 8px;\n padding: 8px 16px;\n }\n\n .suspended-banner ha-icon {\n --mdc-icon-size: 16px;\n }\n'])))];function jt(n){class e extends n{constructor(){super(...arguments),this._revealed=!1,this._isStub=!1,this._subscribing=!1}connectedCallback(){super.connectedCallback(),this._subscribe()}disconnectedCallback(){super.disconnectedCallback(),this._unsubscribe()}_formatSubscriptionError(n){return n instanceof Error?n.message:"object"==typeof n&&null!==n&&"message"in n?String(n.message):"Failed to subscribe: ".concat(JSON.stringify(n))}_shouldReveal(){var n,e,t=null!==(n=null===(e=this._config)||void 0===e?void 0:e.code_display)&&void 0!==n?n:this._getDefaultCodeDisplay();return"unmasked"===t||"masked_with_reveal"===t&&this._revealed}_subscribe(){var n=this;return Gn((function*(){var e;if(!n._isStub&&n._hass&&n._config&&!n._unsub&&!n._subscribing)if(null!==(e=n._hass.connection)&&void 0!==e&&e.subscribeMessage){n._subscribing=!0;try{var t=n._buildSubscribeMessage();n._unsub=yield n._hass.connection.subscribeMessage((e=>{n._handleSubscriptionData(e),n._error=void 0,n.requestUpdate()}),t)}catch(e){n._data=void 0,n._error=n._formatSubscriptionError(e),n.requestUpdate()}finally{n._subscribing=!1}}else n._error="Websocket connection unavailable"}))()}_toggleReveal(){this._revealed=!this._revealed,this._unsubscribe(),this._subscribe()}_unsubscribe(){this._unsub&&(this._unsub(),this._unsub=void 0)}}return yt([$t()],e.prototype,"_revealed",void 0),e}var Vt="empty",Ft="unreadable_code";function Bt(n){return null===n||""===n||n===Vt}function Wt(n,e){return n===Ft||(!Bt(n)||!!e)}var qt="masked_with_reveal",Zt=jt(it);class Kt extends Zt{constructor(){super(...arguments),this._editingSlot=null,this._editValue="",this._saving=!1,this._wasRevealedBeforeEdit=!1}set hass(n){this._hass=n,this._subscribe()}static getConfigElement(){return document.createElement("lcm-lock-codes-editor")}static getStubConfig(n){return Gn((function*(){var e={lock_entity_id:"lock.stub",type:"custom:lcm-lock-codes"};try{return yield Promise.race([Gn((function*(){var t=yield n.callWS({domain:"lock_code_manager",type:"config_entries/get"});if(t.length>0){var i=yield n.callWS({config_entry_id:t[0].entry_id,type:"lock_code_manager/get_config_entry_data"});if(i.locks.length>0)return{lock_entity_id:i.locks[0].entity_id,type:"custom:lcm-lock-codes"}}return e}))(),new Promise((n=>setTimeout((()=>n(e)),2e3)))])}catch(n){return e}}))()}setConfig(n){var e;if(!n.lock_entity_id)throw new Error("lock_entity_id is required");null!==(e=this._config)&&void 0!==e&&e.lock_entity_id&&this._config.lock_entity_id!==n.lock_entity_id&&(this._unsubscribe(),this._data=void 0),this._config=n,this._isStub="lock.stub"===n.lock_entity_id,this._isStub||this._subscribe()}_getDefaultCodeDisplay(){return qt}_buildSubscribeMessage(){if(!this._config)throw new Error("Config not set");return{lock_entity_id:this._config.lock_entity_id,reveal:this._shouldReveal(),type:"lock_code_manager/subscribe_lock_codes"}}_handleSubscriptionData(n){this._data=n}render(){var n,e,t,i,o,r,a,s,l,c,d,h;if(this._isStub)return Ie(m||(m=Bn(['\n
\n \n

Lock Code Manager Lock Codes

\n
\n
'])));var p=null===(n=this._hass)||void 0===n||null===(n=n.states[null!==(e=null===(t=this._config)||void 0===t?void 0:t.lock_entity_id)&&void 0!==e?e:""])||void 0===n||null===(n=n.attributes)||void 0===n?void 0:n.friendly_name,u=null!==(i=null!==(o=null!==(r=null===(a=this._data)||void 0===a?void 0:a.lock_name)&&void 0!==r?r:p)&&void 0!==o?o:null===(s=this._config)||void 0===s?void 0:s.lock_entity_id)&&void 0!==i?i:"",v=null!==(l=null!==(c=null===(d=this._config)||void 0===d?void 0:d.title)&&void 0!==c?c:u)&&void 0!==l?l:"Lock Codes",g="suspended"===(null===(h=this._data)||void 0===h?void 0:h.sync_status);return Ie(y||(y=Bn(['\n \n
\n \n

',"

\n
\n ",'\n
\n ',"\n ","\n
\n
\n "])),g?"suspended":"",v,g?Ie(b||(b=Bn(['
\n \n Sync suspended — lock is unreachable\n
']))):je,this._error?Ie(w||(w=Bn(['
',"
"])),this._error):this._renderSlots(),this._renderSummaryTable())}_startEditing(n,e){n.stopPropagation(),this._wasRevealedBeforeEdit=this._revealed,this._revealed||(this._revealed=!0,this._unsubscribe(),this._subscribe());var t=Wt(e.code)&&e.code!==Ft?String(e.code):"";this._editValue=t,this._editingSlot=e.slot,this.updateComplete.then((()=>{var n,e=null===(n=this.shadowRoot)||void 0===n?void 0:n.querySelector(".slot-code-input");null==e||e.focus(),null==e||e.select()}))}_handleEditInput(n){var e=n.target;this._editValue=e.value}_handleEditKeydown(n){"Enter"===n.key&&null!==this._editingSlot?this._saveCode(this._editingSlot):"Escape"===n.key&&this._cancelEdit()}_cancelEdit(){this._editingSlot=null,this._editValue="",this._revealed!==this._wasRevealedBeforeEdit&&(this._revealed=this._wasRevealedBeforeEdit,this._unsubscribe(),this._subscribe())}_saveCode(n){var e=this;return Gn((function*(){if(e._hass&&e._config&&!e._saving){e._saving=!0;var t=e._editValue.trim();try{var i="string"==typeof n?parseInt(n,10):n;t?yield e._hass.connection.sendMessagePromise({code_slot:i,lock_entity_id:e._config.lock_entity_id,type:"lock_code_manager/set_usercode",usercode:t}):yield e._hass.connection.sendMessagePromise({code_slot:i,lock_entity_id:e._config.lock_entity_id,type:"lock_code_manager/clear_usercode"}),e._editingSlot=null,e._editValue=""}catch(n){console.error("Failed to set usercode:",n)}finally{e._saving=!1}}}))()}_navigateToSlot(n){if(n){var e="/config/integrations/integration/lock_code_manager#config_entry=".concat(n);history.pushState(null,"",e),window.dispatchEvent(new CustomEvent("location-changed"))}}_renderSlots(){var n,e,t=null!==(n=null===(e=this._data)||void 0===e?void 0:e.slots)&&void 0!==n?n:[];if(0===t.length)return Ie(x||(x=Bn(['
No codes reported
'])));var i=this._groupSlots(t),o=this._identifyBorrowedSlots(i),r=this._renderGroupsWithBorrowing(i,o);return Ie(k||(k=Bn(['
',"
"])),r)}_identifyBorrowedSlots(n){for(var e=new Set,t=0;t0?n[t-1]:null,s=t0?e.add(s.slots[0].slot):!r&&"empty"===(null==a?void 0:a.type)&&a.slots.length>0&&e.add(a.slots[a.slots.length-1].slot)}}return e}_renderGroupsWithBorrowing(n,e){for(var t=[],i=0;i0?n[i-1]:null,a=i0?(t.push(this._renderSlotChip(s,!1)),t.push(this._renderEmptySlotChip(a.slots[0]))):!l&&"empty"===(null==r?void 0:r.type)&&r.slots.length>0?(t.push(this._renderEmptySlotChip(r.slots[r.slots.length-1])),t.push(this._renderSlotChip(s,!1))):t.push(this._renderSlotChip(s,!0))}else for(var c of o.slots)t.push(this._renderSlotChip(c,!1));else{var d=o.slots.filter((n=>!e.has(n.slot)));d.length>0&&t.push(this._renderEmptySummary(qn(qn({},o),{},{rangeLabel:this._formatSlotRange(d),slots:d})))}}return t}_renderEmptySlotChip(n){return Ie(C||(C=Bn(['\n
\n
\n Slot ','\n
\n Empty\n
\n
\n
\n '])),n.slot)}_groupSlots(n){var e=[],t=[],i=[],o=()=>{t.length>0&&(e.push({rangeLabel:this._formatSlotRange(t),slots:t,type:"empty"}),t=[])},r=()=>{i.length>0&&(e.push({slots:i,type:"active"}),i=[])};for(var a of n){var s=!(!a.configured_code&&!a.configured_code_length),l=!0===a.managed&&(void 0!==a.enabled||void 0!==a.active);this._hasCode(a)||!0===a.managed&&(s||l)?(o(),i.push(a)):(r(),t.push(a))}return r(),o(),e}_formatSlotRange(n){if(0===n.length)return"";if(1===n.length)return"".concat(n[0].slot);var e=n.map((n=>Number(n.slot))).filter((n=>!isNaN(n)));if(e.length!==n.length)return"".concat(n.map((n=>n.slot)).join(", "));for(var t=[],i=Yn(e,1)[0],o=i,r=i,a=1;a\n
\n \n Slot\n ',"",'\n \n
\n \n ',"\n ","\n \n ",'\n
\n
\n
\n ',"\n ","\n
\n \n "])),r,y,f,m,e?"full-width":"",Lt(v?"Click to manage this slot":void 0),Lt(v?"button":void 0),Lt(v?"0":void 0),v?"Manage slot ".concat(n.slot).concat(n.config_entry_title?" · ".concat(n.config_entry_title):""):je,v?()=>this._navigateToSlot(n.config_entry_id):je,v?e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),this._navigateToSlot(n.config_entry_id))}:je,n.slot,n.config_entry_title?Ie(S||(S=Bn([' ·\n ',""])),n.config_entry_title):je,s,"active"===s||"inactive"===s||"disabled"===s?Ie(A||(A=Bn(['']))):je,a,void 0===d?je:Ie($||($=Bn(['\n ',"\n "])),d?"managed":"external",d?"Managed":"Unmanaged"),u?Ie(L||(L=Bn(['\n ','\n \n ',"\n \n "])),_?Ie(P||(P=Bn(['