a tiny atproto handle typeahead web component
atproto bluesky
43
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix type errors

+27 -16
+27 -16
actor-typeahead.js
··· 98 98 </li> 99 99 `; 100 100 101 + /** 102 + * @template {HTMLElement} T 103 + * @param {T} tmpl 104 + */ 105 + function clone(tmpl) { 106 + return /** @type {T} */ (tmpl.cloneNode(true)); 107 + } 108 + 101 109 export default class ActorTypeahead extends HTMLElement { 102 110 static tag = "actor-typeahead"; 103 111 ··· 128 136 constructor() { 129 137 super(); 130 138 131 - this.#shadow.append(template.cloneNode(true).content); 139 + this.#shadow.append(clone(template).content); 132 140 this.#render(); 133 141 this.addEventListener("input", this); 134 142 this.addEventListener("focusout", this); ··· 149 157 handleEvent(evt) { 150 158 switch (evt.type) { 151 159 case "input": 152 - this.#oninput(/** @type {InputEvent} */ (evt)); 160 + this.#oninput(/** @type {InputEvent & { target: HTMLInputElement }} */ (evt)); 153 161 break; 154 162 155 163 case "keydown": ··· 161 169 break; 162 170 163 171 case "pointerdown": 164 - this.#onpointerdown(/** @type {PointerEvent} */ (evt)); 172 + this.#onpointerdown(/** @type {PointerEvent & { target: HTMLElement }} */ (evt)); 165 173 break; 166 174 167 175 case "pointerup": 168 - this.#onpointerup(/** @type {PointerEvent} */ (evt)); 176 + this.#onpointerup(/** @type {PointerEvent & { target: HTMLElement }} */ (evt)); 169 177 break; 170 178 } 171 179 } ··· 206 214 207 215 case "Enter": 208 216 evt.preventDefault(); 209 - this.#shadow.querySelectorAll(".user")[this.#index]?.click(); 217 + this.#shadow.querySelectorAll("button")[this.#index]?.click(); 210 218 break; 211 219 } 212 220 } 213 221 214 - /** @param {InputEvent} evt */ 222 + /** @param {InputEvent & { target: HTMLInputElement }} evt */ 215 223 async #oninput(evt) { 216 224 const query = evt.target?.value; 217 225 if (!query) { ··· 245 253 const fragment = document.createDocumentFragment(); 246 254 let i = -1; 247 255 for (const actor of this.#actors) { 248 - const li = user.cloneNode(true).content; 256 + const li = clone(user).content; 249 257 250 - const button = li.querySelector(".user"); 251 - button.dataset.handle = actor.handle; 252 - if (++i === this.#index) button.dataset.active = "true"; 258 + const button = li.querySelector("button"); 259 + if (button) { 260 + button.dataset.handle = actor.handle; 261 + if (++i === this.#index) button.dataset.active = "true"; 262 + } 253 263 254 - const avatar = li.querySelector(".img"); 255 - if (actor.avatar) avatar.src = actor.avatar; 264 + const avatar = li.querySelector("img"); 265 + if (avatar && actor.avatar) avatar.src = actor.avatar; 256 266 257 - li.querySelector(".handle").textContent = actor.handle; 267 + const handle = li.querySelector(".handle"); 268 + if (handle) handle.textContent = actor.handle; 258 269 259 270 fragment.append(li); 260 271 } ··· 267 278 this.#pressed = true; 268 279 } 269 280 270 - /** @param {PointerEvent} evt */ 281 + /** @param {PointerEvent & { target: HTMLElement }} evt */ 271 282 #onpointerup(evt) { 272 283 this.#pressed = false; 273 284 274 285 this.querySelector("input")?.focus(); 275 286 276 - const button = evt.target?.closest(".user"); 287 + const button = evt.target?.closest("button"); 277 288 const input = this.querySelector("input"); 278 289 if (!input || !button) return; 279 290 280 - input.value = button.dataset.handle; 291 + input.value = button.dataset.handle || ""; 281 292 this.#actors = []; 282 293 this.#render(); 283 294 }