From 77b4522e3c05fbd7b229874346ec69c3ec499c19 Mon Sep 17 00:00:00 2001 From: bahmcloud Date: Thu, 15 Jan 2026 09:46:41 +0000 Subject: [PATCH] custom_components/bahmcloud_store/panel/panel.js aktualisiert --- .../bahmcloud_store/panel/panel.js | 190 ++++++++++-------- 1 file changed, 110 insertions(+), 80 deletions(-) diff --git a/custom_components/bahmcloud_store/panel/panel.js b/custom_components/bahmcloud_store/panel/panel.js index ef0a588..98b500e 100644 --- a/custom_components/bahmcloud_store/panel/panel.js +++ b/custom_components/bahmcloud_store/panel/panel.js @@ -5,9 +5,7 @@ class BahmcloudStorePanel extends HTMLElement { this._hass = null; - // Views: store | manage | about | detail this._view = "store"; - this._data = null; this._loading = true; this._error = null; @@ -15,11 +13,9 @@ class BahmcloudStorePanel extends HTMLElement { this._customAddUrl = ""; this._customAddName = ""; - // Store filtering this._search = ""; this._category = "all"; - // Detail view state this._detailRepoId = null; this._detailRepo = null; this._readmeLoading = false; @@ -81,7 +77,6 @@ class BahmcloudStorePanel extends HTMLElement { } _goBack() { - // If we're in detail, go back to store list first. if (this._view === "detail") { this._view = "store"; this._detailRepoId = null; @@ -244,10 +239,7 @@ class BahmcloudStorePanel extends HTMLElement { border-color:var(--bcs-accent); background: color-mix(in srgb, var(--bcs-accent) 16%, var(--card-background-color)); } - button:disabled{ - opacity: 0.55; - cursor: not-allowed; - } + button:disabled{ opacity: 0.55; cursor: not-allowed; } .card{ border:1px solid var(--divider-color); @@ -277,14 +269,8 @@ class BahmcloudStorePanel extends HTMLElement { .error{ color:#b00020; white-space:pre-wrap; margin-top:10px; } - .grid2{ - display:grid; - grid-template-columns: 1fr; - gap: 12px; - } - @media (min-width: 900px){ - .grid2{ grid-template-columns: 1.2fr 0.8fr; } - } + .grid2{ display:grid; grid-template-columns: 1fr; gap: 12px; } + @media (min-width: 900px){ .grid2{ grid-template-columns: 1.2fr 0.8fr; } } .filters{ display:flex; @@ -309,7 +295,6 @@ class BahmcloudStorePanel extends HTMLElement { a{ color:var(--bcs-accent); text-decoration:none; } a:hover{ text-decoration:underline; } - /* FABs (detail view) */ .fabs{ position: fixed; right: 18px; @@ -337,12 +322,8 @@ class BahmcloudStorePanel extends HTMLElement { border-color: var(--bcs-accent); background: color-mix(in srgb, var(--bcs-accent) 18%, var(--card-background-color)); } - .fab[disabled]{ - opacity: .55; - cursor: not-allowed; - } + .fab[disabled]{ opacity: .55; cursor: not-allowed; } - /* README fallback pre */ pre.readme{ white-space: pre-wrap; word-break: break-word; @@ -351,6 +332,9 @@ class BahmcloudStorePanel extends HTMLElement { font-size: 12.5px; line-height: 1.5; } + + details{ margin-top: 10px; } + summary{ cursor:pointer; color: var(--bcs-accent); font-weight: 800; }
@@ -392,8 +376,7 @@ class BahmcloudStorePanel extends HTMLElement { }); } - // ✅ CRITICAL FIX: prevent HA global shortcuts (Assist/Command Palette) from - // stealing keystrokes while typing in inputs inside this panel. + // Prevent HA global shortcuts while typing inside panel inputs const stopIfFormField = (e) => { const t = e.composedPath ? e.composedPath()[0] : e.target; if (!t) return; @@ -410,14 +393,47 @@ class BahmcloudStorePanel extends HTMLElement { } }; - // Use capture phase so we intercept before HA handlers. root.addEventListener("keydown", stopIfFormField, true); root.addEventListener("keyup", stopIfFormField, true); root.addEventListener("keypress", stopIfFormField, true); } + _captureFocusState() { + const root = this.shadowRoot; + const ae = root.activeElement; + if (!ae || !ae.id) return null; + + const supported = new Set(["searchInput", "categorySelect", "addUrl", "addName"]); + if (!supported.has(ae.id)) return null; + + const state = { + id: ae.id, + value: ae.value, + selectionStart: typeof ae.selectionStart === "number" ? ae.selectionStart : null, + selectionEnd: typeof ae.selectionEnd === "number" ? ae.selectionEnd : null, + }; + return state; + } + + _restoreFocusState(state) { + if (!state) return; + const root = this.shadowRoot; + const el = root.getElementById(state.id); + if (!el) return; + + try { + el.focus({ preventScroll: true }); + if (state.selectionStart !== null && state.selectionEnd !== null && typeof el.setSelectionRange === "function") { + el.setSelectionRange(state.selectionStart, state.selectionEnd); + } + } catch (_) {} + } + _update() { const root = this.shadowRoot; + + const focusState = this._captureFocusState(); + const content = root.getElementById("content"); const err = root.getElementById("error"); const subtitle = root.getElementById("subtitle"); @@ -437,34 +453,39 @@ class BahmcloudStorePanel extends HTMLElement { if (this._loading) { content.innerHTML = `
Loading…
`; + this._restoreFocusState(focusState); return; } if (!this._data) { content.innerHTML = `
No data.
`; + this._restoreFocusState(focusState); return; } if (this._view === "store") { content.innerHTML = this._renderStore(); this._wireStore(); + this._restoreFocusState(focusState); return; } if (this._view === "manage") { content.innerHTML = this._renderManage(); this._wireManage(); + this._restoreFocusState(focusState); return; } if (this._view === "about") { content.innerHTML = this._renderAbout(); + this._restoreFocusState(focusState); return; } - // detail content.innerHTML = this._renderDetail(); this._wireDetail(); + this._restoreFocusState(focusState); } _renderStore() { @@ -488,32 +509,34 @@ class BahmcloudStorePanel extends HTMLElement { ...categories.map((c) => ``), ].join(""); - const rows = filtered - .map((r) => { - const badge = r.source === "custom" - ? `Custom` - : `Index`; + const rows = filtered.map((r) => { + const badge = r.source === "custom" + ? `Custom` + : `Index`; - const desc = r.description || r.meta_description || r.provider_description || "No description available."; - const creator = r.owner ? `Creator: ${r.owner}` : "Creator: -"; - const cat = r.category ? `Category: ${r.category}` : null; - const metaSrc = r.meta_source ? `Meta: ${r.meta_source}` : null; - const lineBits = [creator, cat, metaSrc].filter(Boolean); + const desc = r.description || "No description available."; - return ` -
-
-
-
${this._esc(r.name)}
-
${this._esc(desc)}
-
${this._esc(lineBits.join(" · "))}
-
- ${badge} + const creator = r.owner ? `Creator: ${r.owner}` : "Creator: -"; + const cat = r.category ? `Category: ${r.category}` : null; + const metaSrc = r.meta_source ? `Meta: ${r.meta_source}` : null; + + const latest = r.latest_version ? `Latest: ${r.latest_version}` : "Latest: unknown"; + + const lineBits = [creator, latest, cat, metaSrc].filter(Boolean); + + return ` +
+
+
+
${this._esc(r.name)}
+
${this._esc(desc)}
+
${this._esc(lineBits.join(" · "))}
+ ${badge}
- `; - }) - .join(""); +
+ `; + }).join(""); return `
@@ -563,10 +586,13 @@ class BahmcloudStorePanel extends HTMLElement { ? `Custom` : `Index`; - const desc = r.description || r.meta_description || r.provider_description || "No description available."; + const desc = r.description || "No description available."; + + const latest = r.latest_version ? `Latest: ${r.latest_version}` : "Latest: unknown"; const infoBits = [ r.owner ? `Creator: ${r.owner}` : "Creator: -", + latest, r.provider ? `Provider: ${r.provider}` : null, r.category ? `Category: ${r.category}` : null, r.meta_author ? `Author: ${r.meta_author}` : null, @@ -580,8 +606,14 @@ class BahmcloudStorePanel extends HTMLElement { ? `
README
-
Rendered Markdown (fallback to raw text if needed).
+ +
+ Show raw Markdown +
+
${this._esc(this._readmeText)}
+
+
` : ` @@ -628,9 +660,6 @@ class BahmcloudStorePanel extends HTMLElement { } _wireDetail() { - // Render README into container: - // 1) Try ha-markdown (if available) - // 2) Fallback: raw markdown in
     const root = this.shadowRoot;
     const container = root.getElementById("readmeContainer");
     if (!container) return;
@@ -639,19 +668,22 @@ class BahmcloudStorePanel extends HTMLElement {
 
     if (!this._readmeText) return;
 
-    // Try HA markdown element (best effort)
-    try {
-      const el = document.createElement("ha-markdown");
-      // some HA builds need hass set before content
-      try { el.hass = this._hass; } catch (_) {}
-      el.content = this._readmeText;
-      container.appendChild(el);
-      return;
-    } catch (_) {
-      // fall through
+    // Only attempt ha-markdown if the custom element exists
+    const hasHaMarkdown = typeof customElements !== "undefined" && !!customElements.get("ha-markdown");
+
+    if (hasHaMarkdown) {
+      try {
+        const el = document.createElement("ha-markdown");
+        try { el.hass = this._hass; } catch (_) {}
+        el.content = this._readmeText;
+        container.appendChild(el);
+        return;
+      } catch (_) {
+        // fall through to raw fallback (details already exists)
+      }
     }
 
-    // Fallback: raw text
+    // If ha-markdown is missing, show a minimal rendered area:
     const pre = document.createElement("pre");
     pre.className = "readme";
     pre.textContent = this._readmeText;
@@ -697,23 +729,21 @@ class BahmcloudStorePanel extends HTMLElement {
     const repos = Array.isArray(this._data.repos) ? this._data.repos : [];
     const custom = repos.filter((r) => r.source === "custom");
 
-    const list = custom
-      .map((r) => {
-        return `
-          
-
-
-
${this._esc(r.name)}
-
${this._esc(r.url)}
-
-
- -
+ const list = custom.map((r) => { + return ` +
+
+
+
${this._esc(r.name)}
+
${this._esc(r.url)}
+
+
+
- `; - }) - .join(""); +
+ `; + }).join(""); return `