@substrate-system/actor-typeahead 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,7 +56,7 @@ Wrap an `<input>` with `<actor-typeahead>`, and import the JS and CSS.
56
56
 
57
57
  ```html
58
58
  <actor-typeahead>
59
- <input>
59
+ <input />
60
60
  </actor-typeahead>
61
61
  ```
62
62
 
@@ -69,6 +69,11 @@ Typing in the input shows a Suggestion list of matching accounts.
69
69
  Choosing one, with the arrow keys and Enter or with the pointer, fills in
70
70
  the input with the account's handle.
71
71
 
72
+ Enter and Escape are left alone when the component has nothing to do
73
+ with them. Enter with no highlighted Suggestion submits a surrounding
74
+ form, and Escape with the Suggestion list closed reaches a surrounding
75
+ `<dialog>`.
76
+
72
77
  ## API
73
78
 
74
79
  This exposes ESM and common JS via
@@ -95,7 +100,7 @@ it more than once is safe.
95
100
 
96
101
  ```html
97
102
  <actor-typeahead host="https://public.api.bsky.app" rows="3">
98
- <input>
103
+ <input />
99
104
  </actor-typeahead>
100
105
  ```
101
106
 
@@ -105,7 +110,7 @@ Both are reflected as properties, so `el.rows = '3'` works too.
105
110
 
106
111
  Events are namespaced by the tag name.
107
112
 
108
- * `actor-typeahead:select` -- A result was chosen. The event `detail` is
113
+ * `actor-typeahead:select` -- A result was chosen. The event's `detail` is
109
114
  `{ handle, avatar? }`.
110
115
 
111
116
  ```ts
@@ -106,6 +106,23 @@
106
106
  },
107
107
  "default": "null"
108
108
  },
109
+ {
110
+ "kind": "field",
111
+ "name": "#timer",
112
+ "privacy": "private",
113
+ "type": {
114
+ "text": "ReturnType<typeof setTimeout>|undefined"
115
+ }
116
+ },
117
+ {
118
+ "kind": "field",
119
+ "name": "#controller",
120
+ "privacy": "private",
121
+ "type": {
122
+ "text": "AbortController|null"
123
+ },
124
+ "default": "null"
125
+ },
109
126
  {
110
127
  "kind": "field",
111
128
  "name": "#limit",
@@ -153,6 +170,19 @@
153
170
  }
154
171
  ]
155
172
  },
173
+ {
174
+ "kind": "method",
175
+ "name": "#request",
176
+ "privacy": "private",
177
+ "parameters": [
178
+ {
179
+ "name": "query",
180
+ "type": {
181
+ "text": "string"
182
+ }
183
+ }
184
+ ]
185
+ },
156
186
  {
157
187
  "kind": "method",
158
188
  "name": "#onfocusout",
@@ -184,6 +214,12 @@
184
214
  }
185
215
  ]
186
216
  },
217
+ {
218
+ "kind": "method",
219
+ "name": "#cancel",
220
+ "privacy": "private",
221
+ "description": "Stop any pending Typeahead request: the debounce timer, and the\nrequest in flight, whose response is then dropped."
222
+ },
187
223
  {
188
224
  "kind": "method",
189
225
  "name": "#clear",
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ module.exports = __toCommonJS(index_exports);
25
25
  var import_web_component = require("@substrate-system/web-component");
26
26
  const DEFAULT_HOST = "https://public.api.bsky.app";
27
27
  const DEFAULT_ROWS = 5;
28
+ const DEBOUNCE_MS = 150;
28
29
  const ANCHORED = typeof CSS !== "undefined" && CSS.supports("anchor-scope: --a");
29
30
  let instances = 0;
30
31
  class ActorTypeahead extends import_web_component.WebComponent.create("actor-typeahead") {
@@ -37,12 +38,21 @@ class ActorTypeahead extends import_web_component.WebComponent.create("actor-typ
37
38
  #index = -1;
38
39
  #pressed = false;
39
40
  #suggestionList = null;
41
+ #timer;
42
+ #controller = null;
40
43
  constructor() {
41
44
  super();
42
45
  this.addEventListener("input", this);
43
46
  this.addEventListener("focusout", this);
44
47
  this.addEventListener("keydown", this);
45
48
  }
49
+ /**
50
+ * Stop any pending Typeahead request. The Suggestion list is left as
51
+ * is; the next Query rebuilds it.
52
+ */
53
+ disconnectedCallback() {
54
+ this.#cancel();
55
+ }
46
56
  get #limit() {
47
57
  const rows = Number.parseInt(this.rows ?? "");
48
58
  return Number.isNaN(rows) ? DEFAULT_ROWS : rows;
@@ -90,30 +100,52 @@ class ActorTypeahead extends import_web_component.WebComponent.create("actor-typ
90
100
  this.render();
91
101
  break;
92
102
  case "Escape":
93
- evt.preventDefault();
103
+ if (this.#actors.length) evt.preventDefault();
94
104
  this.#clear();
95
105
  break;
96
106
  case "Enter":
107
+ if (!this.#actors[this.#index]) {
108
+ this.#cancel();
109
+ break;
110
+ }
97
111
  evt.preventDefault();
98
112
  this.#select(this.#index);
99
113
  break;
100
114
  }
101
115
  }
102
- async #oninput(evt) {
116
+ #oninput(evt) {
103
117
  if (!(evt.target instanceof HTMLInputElement)) return;
104
118
  const query = evt.target.value;
105
119
  if (!query) {
106
120
  this.#clear();
107
121
  return;
108
122
  }
123
+ this.#cancel();
124
+ this.#timer = setTimeout(() => this.#request(query), DEBOUNCE_MS);
125
+ }
126
+ async #request(query) {
127
+ this.#timer = void 0;
109
128
  const url = new URL(
110
129
  "xrpc/app.bsky.actor.searchActorsTypeahead",
111
130
  this.host ?? DEFAULT_HOST
112
131
  );
113
132
  url.searchParams.set("q", query);
114
133
  url.searchParams.set("limit", String(this.#limit));
115
- const res = await fetch(url);
116
- const json = await res.json();
134
+ const controller = new AbortController();
135
+ this.#controller = controller;
136
+ const { signal } = controller;
137
+ let json;
138
+ try {
139
+ const res = await fetch(url, { signal });
140
+ if (signal.aborted) return;
141
+ if (!res.ok) throw new Error(`status ${res.status}`);
142
+ json = await res.json();
143
+ } catch (err) {
144
+ if (isAbortError(err)) return;
145
+ this.#clear();
146
+ return;
147
+ }
148
+ if (signal.aborted) return;
117
149
  this.#actors = parseActors(json);
118
150
  this.#index = -1;
119
151
  this.render();
@@ -140,7 +172,18 @@ class ActorTypeahead extends import_web_component.WebComponent.create("actor-typ
140
172
  this.#clear();
141
173
  this.emit("select", { detail: actor });
142
174
  }
175
+ /**
176
+ * Stop any pending Typeahead request: the debounce timer, and the
177
+ * request in flight, whose response is then dropped.
178
+ */
179
+ #cancel() {
180
+ clearTimeout(this.#timer);
181
+ this.#timer = void 0;
182
+ this.#controller?.abort();
183
+ this.#controller = null;
184
+ }
143
185
  #clear() {
186
+ this.#cancel();
144
187
  this.#actors = [];
145
188
  this.#index = -1;
146
189
  this.render();
@@ -243,6 +286,10 @@ function parseActors(json) {
243
286
  });
244
287
  }
245
288
  __name(parseActors, "parseActors");
289
+ function isAbortError(err) {
290
+ return err instanceof DOMException && err.name === "AbortError";
291
+ }
292
+ __name(isAbortError, "isAbortError");
246
293
  function isRecord(value) {
247
294
  return typeof value === "object" && value !== null;
248
295
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n *\n * Copyright (c) 2026 Jake Lazaroff\n *\n * Repository: https://tangled.org/jakelazaroff.com/actor-typeahead\n */\n\nimport { WebComponent } from '@substrate-system/web-component'\n\n// for document.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'actor-typeahead':ActorTypeahead\n }\n}\n\nexport interface Actor {\n handle:string;\n avatar?:string;\n}\n\nconst DEFAULT_HOST = 'https://public.api.bsky.app'\nconst DEFAULT_ROWS = 5\n\n// The Suggestion list is a top-layer popover, positioned against the host\n// with CSS anchor positioning. `anchor-scope` shipped last of that set, so\n// it gates the group. Without it, the old absolute layout is used.\nconst ANCHORED = typeof CSS !== 'undefined' &&\n CSS.supports('anchor-scope: --a')\n\n// counts instances, so each one gets unique element ids\nlet instances = 0\n\n/**\n * @attribute {string} [host] - The host to which to make the typeahead\n * API call.\n * @attribute {number} [rows] - The maximum number of Suggestions to\n * display in the Suggestion list.\n *\n * @fires actor-typeahead:select - A Suggestion was chosen from the\n * Suggestion list. The event `detail` is `{ handle, avatar }`.\n *\n * @cssprop --color-background - Controls the color of the Suggestion list\n * background.\n * @cssprop --color-border - Controls the color of the Suggestion list\n * border.\n * @cssprop --color-shadow - Controls the color of the Suggestion list\n * shadow.\n * @cssprop --color-hover - Controls the background color of each\n * Suggestion on hover.\n * @cssprop --color-avatar-fallback - Controls the background color of an\n * avatar circle if the image fails to load.\n * @cssprop --radius - Controls the corner radius of the Suggestion list.\n * @cssprop --padding-suggestion-list - Controls the padding of the\n * Suggestion list.\n *\n * @summary A small web component that progressively enhances an <input>\n * element into an autocomplete for ATProto handles.\n *\n * @tag actor-typeahead\n */\nexport class ActorTypeahead extends WebComponent.create('actor-typeahead') {\n static reflectedStringAttributes = ['host', 'rows']\n\n declare host:string|null\n declare rows:string|null\n\n #id = `actor-typeahead-${++instances}`\n #actors:Actor[] = []\n #index = -1\n #pressed = false\n #suggestionList:HTMLUListElement|null = null\n\n constructor () {\n super()\n this.addEventListener('input', this)\n this.addEventListener('focusout', this)\n this.addEventListener('keydown', this)\n }\n\n get #limit ():number {\n const rows = Number.parseInt(this.rows ?? '')\n return Number.isNaN(rows) ? DEFAULT_ROWS : rows\n }\n\n handleEvent (evt:Event) {\n switch (evt.type) {\n case 'input':\n this.#oninput(evt)\n break\n\n case 'keydown':\n if (evt instanceof KeyboardEvent) this.#onkeydown(evt)\n break\n\n case 'focusout':\n this.#onfocusout()\n break\n\n case 'pointerdown':\n this.#pressed = true\n break\n\n case 'pointerup':\n this.#onpointerup(evt)\n break\n }\n }\n\n #onkeydown (evt:KeyboardEvent) {\n const last = this.#actors.length - 1\n\n switch (evt.key) {\n case 'ArrowDown':\n evt.preventDefault()\n this.#index = Math.min(this.#index + 1, last)\n this.render()\n break\n\n case 'PageDown':\n evt.preventDefault()\n this.#index = last\n this.render()\n break\n\n case 'ArrowUp':\n evt.preventDefault()\n this.#index = Math.max(this.#index - 1, 0)\n this.render()\n break\n\n case 'PageUp':\n evt.preventDefault()\n this.#index = 0\n this.render()\n break\n\n case 'Escape':\n evt.preventDefault()\n this.#clear()\n break\n\n case 'Enter':\n evt.preventDefault()\n this.#select(this.#index)\n break\n }\n }\n\n async #oninput (evt:Event) {\n if (!(evt.target instanceof HTMLInputElement)) return\n\n const query = evt.target.value\n if (!query) {\n this.#clear()\n return\n }\n\n const url = new URL(\n 'xrpc/app.bsky.actor.searchActorsTypeahead',\n this.host ?? DEFAULT_HOST\n )\n url.searchParams.set('q', query)\n url.searchParams.set('limit', String(this.#limit))\n\n const res = await fetch(url)\n const json:unknown = await res.json()\n this.#actors = parseActors(json)\n this.#index = -1\n this.render()\n }\n\n #onfocusout () {\n if (this.#pressed) return\n this.#clear()\n }\n\n #onpointerup (evt:Event) {\n this.#pressed = false\n\n const option = evt.target instanceof Element ?\n evt.target.closest<HTMLElement>('[role=\"option\"]') :\n null\n\n if (!option) {\n this.qs('input')?.focus()\n return\n }\n\n this.#select(Number(option.dataset.index))\n }\n\n #select (index:number) {\n const actor = this.#actors[index]\n const input = this.qs('input')\n if (!actor || !input) return\n\n input.value = actor.handle\n input.focus()\n this.#clear()\n this.emit<Actor>('select', { detail: actor })\n }\n\n #clear () {\n this.#actors = []\n this.#index = -1\n this.render()\n }\n\n /**\n * Render into light DOM, next to the authored `<input>`. Only the\n * Suggestion list is rebuilt, so the input element, and anything the\n * consumer attached to it, is never replaced. On the input, only the\n * ARIA combobox attributes are written, plus `autocomplete=\"off\"`\n * when the page did not set `autocomplete`.\n */\n render () {\n if (!this.#suggestionList) {\n this.#suggestionList = document.createElement('ul')\n this.#suggestionList.className = 'suggestion-list'\n this.#suggestionList.id = `${this.#id}-list`\n this.#suggestionList.setAttribute('role', 'listbox')\n if (ANCHORED) this.#suggestionList.popover = 'manual'\n this.#suggestionList.addEventListener('pointerdown', this)\n this.#suggestionList.addEventListener('pointerup', this)\n }\n\n const list = this.#suggestionList\n if (list.parentNode !== this) this.append(list)\n\n list.replaceChildren(...this.#actors.map((actor, i) => {\n const option = suggestion(actor, i, i === this.#index)\n option.id = this.#optionId(i)\n return option\n }))\n\n const open = this.#toggle(list, this.#actors.length > 0)\n\n const input = this.qs('input')\n if (input) {\n input.setAttribute('role', 'combobox')\n input.setAttribute('aria-autocomplete', 'list')\n if (!input.hasAttribute('autocomplete')) {\n input.setAttribute('autocomplete', 'off')\n }\n input.setAttribute('aria-controls', list.id)\n input.setAttribute('aria-expanded', String(open))\n\n const labels = [...(input.labels ?? [])]\n .map(label => label.id)\n .filter(Boolean)\n if (labels.length) {\n list.setAttribute('aria-labelledby', labels.join(' '))\n } else {\n list.removeAttribute('aria-labelledby')\n }\n\n if (this.#actors[this.#index]) {\n input.setAttribute(\n 'aria-activedescendant',\n this.#optionId(this.#index)\n )\n } else {\n input.removeAttribute('aria-activedescendant')\n }\n }\n }\n\n /**\n * Show or hide the Suggestion list popover, guarded on its current\n * state so repeated renders neither throw nor toggle. A disconnected\n * popover cannot be shown, e.g. when a response arrives after the host\n * was removed. Returns whether the Suggestion list is open.\n */\n #toggle (list:HTMLElement, open:boolean):boolean {\n if (!ANCHORED) return open\n if (!list.isConnected) return false\n\n const isOpen = list.matches(':popover-open')\n if (open && !isOpen) list.showPopover()\n if (!open && isOpen) list.hidePopover()\n return list.matches(':popover-open')\n }\n\n #optionId (index:number):string {\n return `${this.#id}-option-${index}`\n }\n}\n\nActorTypeahead.define()\n\n/**\n * Build one Suggestion. Values from the API go through `textContent` and\n * properties, never through `innerHTML`.\n */\nfunction suggestion (\n actor:Actor,\n index:number,\n active:boolean\n):HTMLLIElement {\n const li = document.createElement('li')\n li.className = 'suggestion'\n li.setAttribute('role', 'option')\n li.setAttribute('aria-selected', String(active))\n li.dataset.index = String(index)\n li.dataset.handle = actor.handle\n\n const avatar = document.createElement('div')\n avatar.className = 'avatar'\n\n const img = document.createElement('img')\n img.className = 'img'\n img.alt = ''\n if (actor.avatar) img.src = actor.avatar\n\n const handle = document.createElement('span')\n handle.className = 'handle'\n handle.textContent = actor.handle\n\n avatar.append(img)\n li.append(avatar, handle)\n\n return li\n}\n\nfunction parseActors (json:unknown):Actor[] {\n if (!isRecord(json) || !Array.isArray(json.actors)) return []\n\n return json.actors.flatMap((actor:unknown) => {\n if (!isRecord(actor) || typeof actor.handle !== 'string') return []\n\n return [typeof actor.avatar === 'string' ?\n { handle: actor.handle, avatar: actor.avatar } :\n { handle: actor.handle }]\n })\n}\n\nfunction isRecord (value:unknown):value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n *\n * Copyright (c) 2026 Jake Lazaroff\n *\n * Repository: https://tangled.org/jakelazaroff.com/actor-typeahead\n */\n\nimport { WebComponent } from '@substrate-system/web-component'\n\n// for document.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'actor-typeahead':ActorTypeahead\n }\n}\n\nexport interface Actor {\n handle:string;\n avatar?:string;\n}\n\nconst DEFAULT_HOST = 'https://public.api.bsky.app'\nconst DEFAULT_ROWS = 5\n\n// how long the Query must stay unchanged before a Typeahead request\nconst DEBOUNCE_MS = 150\n\n// The Suggestion list is a top-layer popover, positioned against the host\n// with CSS anchor positioning. `anchor-scope` shipped last of that set, so\n// it gates the group. Without it, the old absolute layout is used.\nconst ANCHORED = typeof CSS !== 'undefined' &&\n CSS.supports('anchor-scope: --a')\n\n// counts instances, so each one gets unique element ids\nlet instances = 0\n\n/**\n * @attribute {string} [host] - The host to which to make the typeahead\n * API call.\n * @attribute {number} [rows] - The maximum number of Suggestions to\n * display in the Suggestion list.\n *\n * @fires actor-typeahead:select - A Suggestion was chosen from the\n * Suggestion list. The event `detail` is `{ handle, avatar }`.\n *\n * @cssprop --color-background - Controls the color of the Suggestion list\n * background.\n * @cssprop --color-border - Controls the color of the Suggestion list\n * border.\n * @cssprop --color-shadow - Controls the color of the Suggestion list\n * shadow.\n * @cssprop --color-hover - Controls the background color of each\n * Suggestion on hover.\n * @cssprop --color-avatar-fallback - Controls the background color of an\n * avatar circle if the image fails to load.\n * @cssprop --radius - Controls the corner radius of the Suggestion list.\n * @cssprop --padding-suggestion-list - Controls the padding of the\n * Suggestion list.\n *\n * @summary A small web component that progressively enhances an <input>\n * element into an autocomplete for ATProto handles.\n *\n * @tag actor-typeahead\n */\nexport class ActorTypeahead extends WebComponent.create('actor-typeahead') {\n static reflectedStringAttributes = ['host', 'rows']\n\n declare host:string|null\n declare rows:string|null\n\n #id = `actor-typeahead-${++instances}`\n #actors:Actor[] = []\n #index = -1\n #pressed = false\n #suggestionList:HTMLUListElement|null = null\n #timer:ReturnType<typeof setTimeout>|undefined\n #controller:AbortController|null = null\n\n constructor () {\n super()\n this.addEventListener('input', this)\n this.addEventListener('focusout', this)\n this.addEventListener('keydown', this)\n }\n\n /**\n * Stop any pending Typeahead request. The Suggestion list is left as\n * is; the next Query rebuilds it.\n */\n disconnectedCallback () {\n this.#cancel()\n }\n\n get #limit ():number {\n const rows = Number.parseInt(this.rows ?? '')\n return Number.isNaN(rows) ? DEFAULT_ROWS : rows\n }\n\n handleEvent (evt:Event) {\n switch (evt.type) {\n case 'input':\n this.#oninput(evt)\n break\n\n case 'keydown':\n if (evt instanceof KeyboardEvent) this.#onkeydown(evt)\n break\n\n case 'focusout':\n this.#onfocusout()\n break\n\n case 'pointerdown':\n this.#pressed = true\n break\n\n case 'pointerup':\n this.#onpointerup(evt)\n break\n }\n }\n\n #onkeydown (evt:KeyboardEvent) {\n const last = this.#actors.length - 1\n\n switch (evt.key) {\n case 'ArrowDown':\n evt.preventDefault()\n this.#index = Math.min(this.#index + 1, last)\n this.render()\n break\n\n case 'PageDown':\n evt.preventDefault()\n this.#index = last\n this.render()\n break\n\n case 'ArrowUp':\n evt.preventDefault()\n this.#index = Math.max(this.#index - 1, 0)\n this.render()\n break\n\n case 'PageUp':\n evt.preventDefault()\n this.#index = 0\n this.render()\n break\n\n case 'Escape':\n // with the list closed, let a dialog close; still cancel\n // any pending Typeahead request\n if (this.#actors.length) evt.preventDefault()\n this.#clear()\n break\n\n case 'Enter':\n // with nothing Highlighted, let the form submit, and\n // drop any pending Typeahead request\n if (!this.#actors[this.#index]) {\n this.#cancel()\n break\n }\n evt.preventDefault()\n this.#select(this.#index)\n break\n }\n }\n\n #oninput (evt:Event) {\n if (!(evt.target instanceof HTMLInputElement)) return\n\n const query = evt.target.value\n if (!query) {\n this.#clear()\n return\n }\n\n // keep the current Suggestions on screen until the new response\n this.#cancel()\n this.#timer = setTimeout(() => this.#request(query), DEBOUNCE_MS)\n }\n\n async #request (query:string) {\n this.#timer = undefined\n const url = new URL(\n 'xrpc/app.bsky.actor.searchActorsTypeahead',\n this.host ?? DEFAULT_HOST\n )\n url.searchParams.set('q', query)\n url.searchParams.set('limit', String(this.#limit))\n\n const controller = new AbortController()\n this.#controller = controller\n const { signal } = controller\n\n let json:unknown\n try {\n const res = await fetch(url, { signal })\n if (signal.aborted) return\n if (!res.ok) throw new Error(`status ${res.status}`)\n json = await res.json()\n } catch (err) {\n // a newer Query, or a close, cancelled this request\n if (isAbortError(err)) return\n // any other failure closes the Suggestion list, quietly\n this.#clear()\n return\n }\n if (signal.aborted) return\n\n this.#actors = parseActors(json)\n this.#index = -1\n this.render()\n }\n\n #onfocusout () {\n if (this.#pressed) return\n this.#clear()\n }\n\n #onpointerup (evt:Event) {\n this.#pressed = false\n\n const option = evt.target instanceof Element ?\n evt.target.closest<HTMLElement>('[role=\"option\"]') :\n null\n\n if (!option) {\n this.qs('input')?.focus()\n return\n }\n\n this.#select(Number(option.dataset.index))\n }\n\n #select (index:number) {\n const actor = this.#actors[index]\n const input = this.qs('input')\n if (!actor || !input) return\n\n input.value = actor.handle\n input.focus()\n this.#clear()\n this.emit<Actor>('select', { detail: actor })\n }\n\n /**\n * Stop any pending Typeahead request: the debounce timer, and the\n * request in flight, whose response is then dropped.\n */\n #cancel () {\n clearTimeout(this.#timer)\n this.#timer = undefined\n this.#controller?.abort()\n this.#controller = null\n }\n\n #clear () {\n this.#cancel()\n this.#actors = []\n this.#index = -1\n this.render()\n }\n\n /**\n * Render into light DOM, next to the authored `<input>`. Only the\n * Suggestion list is rebuilt, so the input element, and anything the\n * consumer attached to it, is never replaced. On the input, only the\n * ARIA combobox attributes are written, plus `autocomplete=\"off\"`\n * when the page did not set `autocomplete`.\n */\n render () {\n if (!this.#suggestionList) {\n this.#suggestionList = document.createElement('ul')\n this.#suggestionList.className = 'suggestion-list'\n this.#suggestionList.id = `${this.#id}-list`\n this.#suggestionList.setAttribute('role', 'listbox')\n if (ANCHORED) this.#suggestionList.popover = 'manual'\n this.#suggestionList.addEventListener('pointerdown', this)\n this.#suggestionList.addEventListener('pointerup', this)\n }\n\n const list = this.#suggestionList\n if (list.parentNode !== this) this.append(list)\n\n list.replaceChildren(...this.#actors.map((actor, i) => {\n const option = suggestion(actor, i, i === this.#index)\n option.id = this.#optionId(i)\n return option\n }))\n\n const open = this.#toggle(list, this.#actors.length > 0)\n\n const input = this.qs('input')\n if (input) {\n input.setAttribute('role', 'combobox')\n input.setAttribute('aria-autocomplete', 'list')\n if (!input.hasAttribute('autocomplete')) {\n input.setAttribute('autocomplete', 'off')\n }\n input.setAttribute('aria-controls', list.id)\n input.setAttribute('aria-expanded', String(open))\n\n const labels = [...(input.labels ?? [])]\n .map(label => label.id)\n .filter(Boolean)\n if (labels.length) {\n list.setAttribute('aria-labelledby', labels.join(' '))\n } else {\n list.removeAttribute('aria-labelledby')\n }\n\n if (this.#actors[this.#index]) {\n input.setAttribute(\n 'aria-activedescendant',\n this.#optionId(this.#index)\n )\n } else {\n input.removeAttribute('aria-activedescendant')\n }\n }\n }\n\n /**\n * Show or hide the Suggestion list popover, guarded on its current\n * state so repeated renders neither throw nor toggle. A disconnected\n * popover cannot be shown, e.g. when a response arrives after the host\n * was removed. Returns whether the Suggestion list is open.\n */\n #toggle (list:HTMLElement, open:boolean):boolean {\n if (!ANCHORED) return open\n if (!list.isConnected) return false\n\n const isOpen = list.matches(':popover-open')\n if (open && !isOpen) list.showPopover()\n if (!open && isOpen) list.hidePopover()\n return list.matches(':popover-open')\n }\n\n #optionId (index:number):string {\n return `${this.#id}-option-${index}`\n }\n}\n\nActorTypeahead.define()\n\n/**\n * Build one Suggestion. Values from the API go through `textContent` and\n * properties, never through `innerHTML`.\n */\nfunction suggestion (\n actor:Actor,\n index:number,\n active:boolean\n):HTMLLIElement {\n const li = document.createElement('li')\n li.className = 'suggestion'\n li.setAttribute('role', 'option')\n li.setAttribute('aria-selected', String(active))\n li.dataset.index = String(index)\n li.dataset.handle = actor.handle\n\n const avatar = document.createElement('div')\n avatar.className = 'avatar'\n\n const img = document.createElement('img')\n img.className = 'img'\n img.alt = ''\n if (actor.avatar) img.src = actor.avatar\n\n const handle = document.createElement('span')\n handle.className = 'handle'\n handle.textContent = actor.handle\n\n avatar.append(img)\n li.append(avatar, handle)\n\n return li\n}\n\nfunction parseActors (json:unknown):Actor[] {\n if (!isRecord(json) || !Array.isArray(json.actors)) return []\n\n return json.actors.flatMap((actor:unknown) => {\n if (!isRecord(actor) || typeof actor.handle !== 'string') return []\n\n return [typeof actor.avatar === 'string' ?\n { handle: actor.handle, avatar: actor.avatar } :\n { handle: actor.handle }]\n })\n}\n\nfunction isAbortError (err:unknown):boolean {\n return err instanceof DOMException && err.name === 'AbortError'\n}\n\nfunction isRecord (value:unknown):value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/index.d.ts CHANGED
@@ -50,6 +50,11 @@ export declare class ActorTypeahead extends ActorTypeahead_base {
50
50
  host: string | null;
51
51
  rows: string | null;
52
52
  constructor();
53
+ /**
54
+ * Stop any pending Typeahead request. The Suggestion list is left as
55
+ * is; the next Query rebuilds it.
56
+ */
57
+ disconnectedCallback(): void;
53
58
  handleEvent(evt: Event): void;
54
59
  /**
55
60
  * Render into light DOM, next to the authored `<input>`. Only the
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAG9D,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,iBAAiB,EAAC,cAAc,CAAA;KACnC;CACJ;AAED,MAAM,WAAW,KAAK;IAClB,MAAM,EAAC,MAAM,CAAC;IACd,MAAM,CAAC,EAAC,MAAM,CAAC;CAClB;;;;;;;;;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,cAAe,SAAQ,mBAAsC;;IACtE,MAAM,CAAC,yBAAyB,WAAmB;IAE3C,IAAI,EAAC,MAAM,GAAC,IAAI,CAAA;IAChB,IAAI,EAAC,MAAM,GAAC,IAAI,CAAA;;IAoBxB,WAAW,CAAE,GAAG,EAAC,KAAK;IA4HtB;;;;;;OAMG;IACH,MAAM;CAuET"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAG9D,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,iBAAiB,EAAC,cAAc,CAAA;KACnC;CACJ;AAED,MAAM,WAAW,KAAK;IAClB,MAAM,EAAC,MAAM,CAAC;IACd,MAAM,CAAC,EAAC,MAAM,CAAC;CAClB;;;;;;;;;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,cAAe,SAAQ,mBAAsC;;IACtE,MAAM,CAAC,yBAAyB,WAAmB;IAE3C,IAAI,EAAC,MAAM,GAAC,IAAI,CAAA;IAChB,IAAI,EAAC,MAAM,GAAC,IAAI,CAAA;;IAiBxB;;;OAGG;IACH,oBAAoB;IASpB,WAAW,CAAE,GAAG,EAAC,KAAK;IAwKtB;;;;;;OAMG;IACH,MAAM;CAuET"}
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
3
3
  import { WebComponent } from "@substrate-system/web-component";
4
4
  const DEFAULT_HOST = "https://public.api.bsky.app";
5
5
  const DEFAULT_ROWS = 5;
6
+ const DEBOUNCE_MS = 150;
6
7
  const ANCHORED = typeof CSS !== "undefined" && CSS.supports("anchor-scope: --a");
7
8
  let instances = 0;
8
9
  class ActorTypeahead extends WebComponent.create("actor-typeahead") {
@@ -15,12 +16,21 @@ class ActorTypeahead extends WebComponent.create("actor-typeahead") {
15
16
  #index = -1;
16
17
  #pressed = false;
17
18
  #suggestionList = null;
19
+ #timer;
20
+ #controller = null;
18
21
  constructor() {
19
22
  super();
20
23
  this.addEventListener("input", this);
21
24
  this.addEventListener("focusout", this);
22
25
  this.addEventListener("keydown", this);
23
26
  }
27
+ /**
28
+ * Stop any pending Typeahead request. The Suggestion list is left as
29
+ * is; the next Query rebuilds it.
30
+ */
31
+ disconnectedCallback() {
32
+ this.#cancel();
33
+ }
24
34
  get #limit() {
25
35
  const rows = Number.parseInt(this.rows ?? "");
26
36
  return Number.isNaN(rows) ? DEFAULT_ROWS : rows;
@@ -68,30 +78,52 @@ class ActorTypeahead extends WebComponent.create("actor-typeahead") {
68
78
  this.render();
69
79
  break;
70
80
  case "Escape":
71
- evt.preventDefault();
81
+ if (this.#actors.length) evt.preventDefault();
72
82
  this.#clear();
73
83
  break;
74
84
  case "Enter":
85
+ if (!this.#actors[this.#index]) {
86
+ this.#cancel();
87
+ break;
88
+ }
75
89
  evt.preventDefault();
76
90
  this.#select(this.#index);
77
91
  break;
78
92
  }
79
93
  }
80
- async #oninput(evt) {
94
+ #oninput(evt) {
81
95
  if (!(evt.target instanceof HTMLInputElement)) return;
82
96
  const query = evt.target.value;
83
97
  if (!query) {
84
98
  this.#clear();
85
99
  return;
86
100
  }
101
+ this.#cancel();
102
+ this.#timer = setTimeout(() => this.#request(query), DEBOUNCE_MS);
103
+ }
104
+ async #request(query) {
105
+ this.#timer = void 0;
87
106
  const url = new URL(
88
107
  "xrpc/app.bsky.actor.searchActorsTypeahead",
89
108
  this.host ?? DEFAULT_HOST
90
109
  );
91
110
  url.searchParams.set("q", query);
92
111
  url.searchParams.set("limit", String(this.#limit));
93
- const res = await fetch(url);
94
- const json = await res.json();
112
+ const controller = new AbortController();
113
+ this.#controller = controller;
114
+ const { signal } = controller;
115
+ let json;
116
+ try {
117
+ const res = await fetch(url, { signal });
118
+ if (signal.aborted) return;
119
+ if (!res.ok) throw new Error(`status ${res.status}`);
120
+ json = await res.json();
121
+ } catch (err) {
122
+ if (isAbortError(err)) return;
123
+ this.#clear();
124
+ return;
125
+ }
126
+ if (signal.aborted) return;
95
127
  this.#actors = parseActors(json);
96
128
  this.#index = -1;
97
129
  this.render();
@@ -118,7 +150,18 @@ class ActorTypeahead extends WebComponent.create("actor-typeahead") {
118
150
  this.#clear();
119
151
  this.emit("select", { detail: actor });
120
152
  }
153
+ /**
154
+ * Stop any pending Typeahead request: the debounce timer, and the
155
+ * request in flight, whose response is then dropped.
156
+ */
157
+ #cancel() {
158
+ clearTimeout(this.#timer);
159
+ this.#timer = void 0;
160
+ this.#controller?.abort();
161
+ this.#controller = null;
162
+ }
121
163
  #clear() {
164
+ this.#cancel();
122
165
  this.#actors = [];
123
166
  this.#index = -1;
124
167
  this.render();
@@ -221,6 +264,10 @@ function parseActors(json) {
221
264
  });
222
265
  }
223
266
  __name(parseActors, "parseActors");
267
+ function isAbortError(err) {
268
+ return err instanceof DOMException && err.name === "AbortError";
269
+ }
270
+ __name(isAbortError, "isAbortError");
224
271
  function isRecord(value) {
225
272
  return typeof value === "object" && value !== null;
226
273
  }
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n *\n * Copyright (c) 2026 Jake Lazaroff\n *\n * Repository: https://tangled.org/jakelazaroff.com/actor-typeahead\n */\n\nimport { WebComponent } from '@substrate-system/web-component'\n\n// for document.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'actor-typeahead':ActorTypeahead\n }\n}\n\nexport interface Actor {\n handle:string;\n avatar?:string;\n}\n\nconst DEFAULT_HOST = 'https://public.api.bsky.app'\nconst DEFAULT_ROWS = 5\n\n// The Suggestion list is a top-layer popover, positioned against the host\n// with CSS anchor positioning. `anchor-scope` shipped last of that set, so\n// it gates the group. Without it, the old absolute layout is used.\nconst ANCHORED = typeof CSS !== 'undefined' &&\n CSS.supports('anchor-scope: --a')\n\n// counts instances, so each one gets unique element ids\nlet instances = 0\n\n/**\n * @attribute {string} [host] - The host to which to make the typeahead\n * API call.\n * @attribute {number} [rows] - The maximum number of Suggestions to\n * display in the Suggestion list.\n *\n * @fires actor-typeahead:select - A Suggestion was chosen from the\n * Suggestion list. The event `detail` is `{ handle, avatar }`.\n *\n * @cssprop --color-background - Controls the color of the Suggestion list\n * background.\n * @cssprop --color-border - Controls the color of the Suggestion list\n * border.\n * @cssprop --color-shadow - Controls the color of the Suggestion list\n * shadow.\n * @cssprop --color-hover - Controls the background color of each\n * Suggestion on hover.\n * @cssprop --color-avatar-fallback - Controls the background color of an\n * avatar circle if the image fails to load.\n * @cssprop --radius - Controls the corner radius of the Suggestion list.\n * @cssprop --padding-suggestion-list - Controls the padding of the\n * Suggestion list.\n *\n * @summary A small web component that progressively enhances an <input>\n * element into an autocomplete for ATProto handles.\n *\n * @tag actor-typeahead\n */\nexport class ActorTypeahead extends WebComponent.create('actor-typeahead') {\n static reflectedStringAttributes = ['host', 'rows']\n\n declare host:string|null\n declare rows:string|null\n\n #id = `actor-typeahead-${++instances}`\n #actors:Actor[] = []\n #index = -1\n #pressed = false\n #suggestionList:HTMLUListElement|null = null\n\n constructor () {\n super()\n this.addEventListener('input', this)\n this.addEventListener('focusout', this)\n this.addEventListener('keydown', this)\n }\n\n get #limit ():number {\n const rows = Number.parseInt(this.rows ?? '')\n return Number.isNaN(rows) ? DEFAULT_ROWS : rows\n }\n\n handleEvent (evt:Event) {\n switch (evt.type) {\n case 'input':\n this.#oninput(evt)\n break\n\n case 'keydown':\n if (evt instanceof KeyboardEvent) this.#onkeydown(evt)\n break\n\n case 'focusout':\n this.#onfocusout()\n break\n\n case 'pointerdown':\n this.#pressed = true\n break\n\n case 'pointerup':\n this.#onpointerup(evt)\n break\n }\n }\n\n #onkeydown (evt:KeyboardEvent) {\n const last = this.#actors.length - 1\n\n switch (evt.key) {\n case 'ArrowDown':\n evt.preventDefault()\n this.#index = Math.min(this.#index + 1, last)\n this.render()\n break\n\n case 'PageDown':\n evt.preventDefault()\n this.#index = last\n this.render()\n break\n\n case 'ArrowUp':\n evt.preventDefault()\n this.#index = Math.max(this.#index - 1, 0)\n this.render()\n break\n\n case 'PageUp':\n evt.preventDefault()\n this.#index = 0\n this.render()\n break\n\n case 'Escape':\n evt.preventDefault()\n this.#clear()\n break\n\n case 'Enter':\n evt.preventDefault()\n this.#select(this.#index)\n break\n }\n }\n\n async #oninput (evt:Event) {\n if (!(evt.target instanceof HTMLInputElement)) return\n\n const query = evt.target.value\n if (!query) {\n this.#clear()\n return\n }\n\n const url = new URL(\n 'xrpc/app.bsky.actor.searchActorsTypeahead',\n this.host ?? DEFAULT_HOST\n )\n url.searchParams.set('q', query)\n url.searchParams.set('limit', String(this.#limit))\n\n const res = await fetch(url)\n const json:unknown = await res.json()\n this.#actors = parseActors(json)\n this.#index = -1\n this.render()\n }\n\n #onfocusout () {\n if (this.#pressed) return\n this.#clear()\n }\n\n #onpointerup (evt:Event) {\n this.#pressed = false\n\n const option = evt.target instanceof Element ?\n evt.target.closest<HTMLElement>('[role=\"option\"]') :\n null\n\n if (!option) {\n this.qs('input')?.focus()\n return\n }\n\n this.#select(Number(option.dataset.index))\n }\n\n #select (index:number) {\n const actor = this.#actors[index]\n const input = this.qs('input')\n if (!actor || !input) return\n\n input.value = actor.handle\n input.focus()\n this.#clear()\n this.emit<Actor>('select', { detail: actor })\n }\n\n #clear () {\n this.#actors = []\n this.#index = -1\n this.render()\n }\n\n /**\n * Render into light DOM, next to the authored `<input>`. Only the\n * Suggestion list is rebuilt, so the input element, and anything the\n * consumer attached to it, is never replaced. On the input, only the\n * ARIA combobox attributes are written, plus `autocomplete=\"off\"`\n * when the page did not set `autocomplete`.\n */\n render () {\n if (!this.#suggestionList) {\n this.#suggestionList = document.createElement('ul')\n this.#suggestionList.className = 'suggestion-list'\n this.#suggestionList.id = `${this.#id}-list`\n this.#suggestionList.setAttribute('role', 'listbox')\n if (ANCHORED) this.#suggestionList.popover = 'manual'\n this.#suggestionList.addEventListener('pointerdown', this)\n this.#suggestionList.addEventListener('pointerup', this)\n }\n\n const list = this.#suggestionList\n if (list.parentNode !== this) this.append(list)\n\n list.replaceChildren(...this.#actors.map((actor, i) => {\n const option = suggestion(actor, i, i === this.#index)\n option.id = this.#optionId(i)\n return option\n }))\n\n const open = this.#toggle(list, this.#actors.length > 0)\n\n const input = this.qs('input')\n if (input) {\n input.setAttribute('role', 'combobox')\n input.setAttribute('aria-autocomplete', 'list')\n if (!input.hasAttribute('autocomplete')) {\n input.setAttribute('autocomplete', 'off')\n }\n input.setAttribute('aria-controls', list.id)\n input.setAttribute('aria-expanded', String(open))\n\n const labels = [...(input.labels ?? [])]\n .map(label => label.id)\n .filter(Boolean)\n if (labels.length) {\n list.setAttribute('aria-labelledby', labels.join(' '))\n } else {\n list.removeAttribute('aria-labelledby')\n }\n\n if (this.#actors[this.#index]) {\n input.setAttribute(\n 'aria-activedescendant',\n this.#optionId(this.#index)\n )\n } else {\n input.removeAttribute('aria-activedescendant')\n }\n }\n }\n\n /**\n * Show or hide the Suggestion list popover, guarded on its current\n * state so repeated renders neither throw nor toggle. A disconnected\n * popover cannot be shown, e.g. when a response arrives after the host\n * was removed. Returns whether the Suggestion list is open.\n */\n #toggle (list:HTMLElement, open:boolean):boolean {\n if (!ANCHORED) return open\n if (!list.isConnected) return false\n\n const isOpen = list.matches(':popover-open')\n if (open && !isOpen) list.showPopover()\n if (!open && isOpen) list.hidePopover()\n return list.matches(':popover-open')\n }\n\n #optionId (index:number):string {\n return `${this.#id}-option-${index}`\n }\n}\n\nActorTypeahead.define()\n\n/**\n * Build one Suggestion. Values from the API go through `textContent` and\n * properties, never through `innerHTML`.\n */\nfunction suggestion (\n actor:Actor,\n index:number,\n active:boolean\n):HTMLLIElement {\n const li = document.createElement('li')\n li.className = 'suggestion'\n li.setAttribute('role', 'option')\n li.setAttribute('aria-selected', String(active))\n li.dataset.index = String(index)\n li.dataset.handle = actor.handle\n\n const avatar = document.createElement('div')\n avatar.className = 'avatar'\n\n const img = document.createElement('img')\n img.className = 'img'\n img.alt = ''\n if (actor.avatar) img.src = actor.avatar\n\n const handle = document.createElement('span')\n handle.className = 'handle'\n handle.textContent = actor.handle\n\n avatar.append(img)\n li.append(avatar, handle)\n\n return li\n}\n\nfunction parseActors (json:unknown):Actor[] {\n if (!isRecord(json) || !Array.isArray(json.actors)) return []\n\n return json.actors.flatMap((actor:unknown) => {\n if (!isRecord(actor) || typeof actor.handle !== 'string') return []\n\n return [typeof actor.avatar === 'string' ?\n { handle: actor.handle, avatar: actor.avatar } :\n { handle: actor.handle }]\n })\n}\n\nfunction isRecord (value:unknown):value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n *\n * Copyright (c) 2026 Jake Lazaroff\n *\n * Repository: https://tangled.org/jakelazaroff.com/actor-typeahead\n */\n\nimport { WebComponent } from '@substrate-system/web-component'\n\n// for document.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'actor-typeahead':ActorTypeahead\n }\n}\n\nexport interface Actor {\n handle:string;\n avatar?:string;\n}\n\nconst DEFAULT_HOST = 'https://public.api.bsky.app'\nconst DEFAULT_ROWS = 5\n\n// how long the Query must stay unchanged before a Typeahead request\nconst DEBOUNCE_MS = 150\n\n// The Suggestion list is a top-layer popover, positioned against the host\n// with CSS anchor positioning. `anchor-scope` shipped last of that set, so\n// it gates the group. Without it, the old absolute layout is used.\nconst ANCHORED = typeof CSS !== 'undefined' &&\n CSS.supports('anchor-scope: --a')\n\n// counts instances, so each one gets unique element ids\nlet instances = 0\n\n/**\n * @attribute {string} [host] - The host to which to make the typeahead\n * API call.\n * @attribute {number} [rows] - The maximum number of Suggestions to\n * display in the Suggestion list.\n *\n * @fires actor-typeahead:select - A Suggestion was chosen from the\n * Suggestion list. The event `detail` is `{ handle, avatar }`.\n *\n * @cssprop --color-background - Controls the color of the Suggestion list\n * background.\n * @cssprop --color-border - Controls the color of the Suggestion list\n * border.\n * @cssprop --color-shadow - Controls the color of the Suggestion list\n * shadow.\n * @cssprop --color-hover - Controls the background color of each\n * Suggestion on hover.\n * @cssprop --color-avatar-fallback - Controls the background color of an\n * avatar circle if the image fails to load.\n * @cssprop --radius - Controls the corner radius of the Suggestion list.\n * @cssprop --padding-suggestion-list - Controls the padding of the\n * Suggestion list.\n *\n * @summary A small web component that progressively enhances an <input>\n * element into an autocomplete for ATProto handles.\n *\n * @tag actor-typeahead\n */\nexport class ActorTypeahead extends WebComponent.create('actor-typeahead') {\n static reflectedStringAttributes = ['host', 'rows']\n\n declare host:string|null\n declare rows:string|null\n\n #id = `actor-typeahead-${++instances}`\n #actors:Actor[] = []\n #index = -1\n #pressed = false\n #suggestionList:HTMLUListElement|null = null\n #timer:ReturnType<typeof setTimeout>|undefined\n #controller:AbortController|null = null\n\n constructor () {\n super()\n this.addEventListener('input', this)\n this.addEventListener('focusout', this)\n this.addEventListener('keydown', this)\n }\n\n /**\n * Stop any pending Typeahead request. The Suggestion list is left as\n * is; the next Query rebuilds it.\n */\n disconnectedCallback () {\n this.#cancel()\n }\n\n get #limit ():number {\n const rows = Number.parseInt(this.rows ?? '')\n return Number.isNaN(rows) ? DEFAULT_ROWS : rows\n }\n\n handleEvent (evt:Event) {\n switch (evt.type) {\n case 'input':\n this.#oninput(evt)\n break\n\n case 'keydown':\n if (evt instanceof KeyboardEvent) this.#onkeydown(evt)\n break\n\n case 'focusout':\n this.#onfocusout()\n break\n\n case 'pointerdown':\n this.#pressed = true\n break\n\n case 'pointerup':\n this.#onpointerup(evt)\n break\n }\n }\n\n #onkeydown (evt:KeyboardEvent) {\n const last = this.#actors.length - 1\n\n switch (evt.key) {\n case 'ArrowDown':\n evt.preventDefault()\n this.#index = Math.min(this.#index + 1, last)\n this.render()\n break\n\n case 'PageDown':\n evt.preventDefault()\n this.#index = last\n this.render()\n break\n\n case 'ArrowUp':\n evt.preventDefault()\n this.#index = Math.max(this.#index - 1, 0)\n this.render()\n break\n\n case 'PageUp':\n evt.preventDefault()\n this.#index = 0\n this.render()\n break\n\n case 'Escape':\n // with the list closed, let a dialog close; still cancel\n // any pending Typeahead request\n if (this.#actors.length) evt.preventDefault()\n this.#clear()\n break\n\n case 'Enter':\n // with nothing Highlighted, let the form submit, and\n // drop any pending Typeahead request\n if (!this.#actors[this.#index]) {\n this.#cancel()\n break\n }\n evt.preventDefault()\n this.#select(this.#index)\n break\n }\n }\n\n #oninput (evt:Event) {\n if (!(evt.target instanceof HTMLInputElement)) return\n\n const query = evt.target.value\n if (!query) {\n this.#clear()\n return\n }\n\n // keep the current Suggestions on screen until the new response\n this.#cancel()\n this.#timer = setTimeout(() => this.#request(query), DEBOUNCE_MS)\n }\n\n async #request (query:string) {\n this.#timer = undefined\n const url = new URL(\n 'xrpc/app.bsky.actor.searchActorsTypeahead',\n this.host ?? DEFAULT_HOST\n )\n url.searchParams.set('q', query)\n url.searchParams.set('limit', String(this.#limit))\n\n const controller = new AbortController()\n this.#controller = controller\n const { signal } = controller\n\n let json:unknown\n try {\n const res = await fetch(url, { signal })\n if (signal.aborted) return\n if (!res.ok) throw new Error(`status ${res.status}`)\n json = await res.json()\n } catch (err) {\n // a newer Query, or a close, cancelled this request\n if (isAbortError(err)) return\n // any other failure closes the Suggestion list, quietly\n this.#clear()\n return\n }\n if (signal.aborted) return\n\n this.#actors = parseActors(json)\n this.#index = -1\n this.render()\n }\n\n #onfocusout () {\n if (this.#pressed) return\n this.#clear()\n }\n\n #onpointerup (evt:Event) {\n this.#pressed = false\n\n const option = evt.target instanceof Element ?\n evt.target.closest<HTMLElement>('[role=\"option\"]') :\n null\n\n if (!option) {\n this.qs('input')?.focus()\n return\n }\n\n this.#select(Number(option.dataset.index))\n }\n\n #select (index:number) {\n const actor = this.#actors[index]\n const input = this.qs('input')\n if (!actor || !input) return\n\n input.value = actor.handle\n input.focus()\n this.#clear()\n this.emit<Actor>('select', { detail: actor })\n }\n\n /**\n * Stop any pending Typeahead request: the debounce timer, and the\n * request in flight, whose response is then dropped.\n */\n #cancel () {\n clearTimeout(this.#timer)\n this.#timer = undefined\n this.#controller?.abort()\n this.#controller = null\n }\n\n #clear () {\n this.#cancel()\n this.#actors = []\n this.#index = -1\n this.render()\n }\n\n /**\n * Render into light DOM, next to the authored `<input>`. Only the\n * Suggestion list is rebuilt, so the input element, and anything the\n * consumer attached to it, is never replaced. On the input, only the\n * ARIA combobox attributes are written, plus `autocomplete=\"off\"`\n * when the page did not set `autocomplete`.\n */\n render () {\n if (!this.#suggestionList) {\n this.#suggestionList = document.createElement('ul')\n this.#suggestionList.className = 'suggestion-list'\n this.#suggestionList.id = `${this.#id}-list`\n this.#suggestionList.setAttribute('role', 'listbox')\n if (ANCHORED) this.#suggestionList.popover = 'manual'\n this.#suggestionList.addEventListener('pointerdown', this)\n this.#suggestionList.addEventListener('pointerup', this)\n }\n\n const list = this.#suggestionList\n if (list.parentNode !== this) this.append(list)\n\n list.replaceChildren(...this.#actors.map((actor, i) => {\n const option = suggestion(actor, i, i === this.#index)\n option.id = this.#optionId(i)\n return option\n }))\n\n const open = this.#toggle(list, this.#actors.length > 0)\n\n const input = this.qs('input')\n if (input) {\n input.setAttribute('role', 'combobox')\n input.setAttribute('aria-autocomplete', 'list')\n if (!input.hasAttribute('autocomplete')) {\n input.setAttribute('autocomplete', 'off')\n }\n input.setAttribute('aria-controls', list.id)\n input.setAttribute('aria-expanded', String(open))\n\n const labels = [...(input.labels ?? [])]\n .map(label => label.id)\n .filter(Boolean)\n if (labels.length) {\n list.setAttribute('aria-labelledby', labels.join(' '))\n } else {\n list.removeAttribute('aria-labelledby')\n }\n\n if (this.#actors[this.#index]) {\n input.setAttribute(\n 'aria-activedescendant',\n this.#optionId(this.#index)\n )\n } else {\n input.removeAttribute('aria-activedescendant')\n }\n }\n }\n\n /**\n * Show or hide the Suggestion list popover, guarded on its current\n * state so repeated renders neither throw nor toggle. A disconnected\n * popover cannot be shown, e.g. when a response arrives after the host\n * was removed. Returns whether the Suggestion list is open.\n */\n #toggle (list:HTMLElement, open:boolean):boolean {\n if (!ANCHORED) return open\n if (!list.isConnected) return false\n\n const isOpen = list.matches(':popover-open')\n if (open && !isOpen) list.showPopover()\n if (!open && isOpen) list.hidePopover()\n return list.matches(':popover-open')\n }\n\n #optionId (index:number):string {\n return `${this.#id}-option-${index}`\n }\n}\n\nActorTypeahead.define()\n\n/**\n * Build one Suggestion. Values from the API go through `textContent` and\n * properties, never through `innerHTML`.\n */\nfunction suggestion (\n actor:Actor,\n index:number,\n active:boolean\n):HTMLLIElement {\n const li = document.createElement('li')\n li.className = 'suggestion'\n li.setAttribute('role', 'option')\n li.setAttribute('aria-selected', String(active))\n li.dataset.index = String(index)\n li.dataset.handle = actor.handle\n\n const avatar = document.createElement('div')\n avatar.className = 'avatar'\n\n const img = document.createElement('img')\n img.className = 'img'\n img.alt = ''\n if (actor.avatar) img.src = actor.avatar\n\n const handle = document.createElement('span')\n handle.className = 'handle'\n handle.textContent = actor.handle\n\n avatar.append(img)\n li.append(avatar, handle)\n\n return li\n}\n\nfunction parseActors (json:unknown):Actor[] {\n if (!isRecord(json) || !Array.isArray(json.actors)) return []\n\n return json.actors.flatMap((actor:unknown) => {\n if (!isRecord(actor) || typeof actor.handle !== 'string') return []\n\n return [typeof actor.avatar === 'string' ?\n { handle: actor.handle, avatar: actor.avatar } :\n { handle: actor.handle }]\n })\n}\n\nfunction isAbortError (err:unknown):boolean {\n return err instanceof DOMException && err.name === 'AbortError'\n}\n\nfunction isRecord (value:unknown):value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/index.min.js CHANGED
@@ -1,2 +1,2 @@
1
- var T=Object.defineProperty;var i=(t,e)=>T(t,"name",{value:e,configurable:!0});var L=Object.defineProperty,k=i((t,e)=>L(t,"name",{value:e,configurable:!0}),"__name");function p(t){return Object.keys(t).reduce((e,r)=>{let n=t[r];return n?typeof n=="boolean"?n?(e+` ${r}`).trim():e:Array.isArray(n)?e+` ${r}="${n.join(" ")}"`:(e+` ${r}="${n}"`).trim():e},"")}i(p,"toAttributes");k(p,"toAttributes");var x=Object.defineProperty,S=i((t,e)=>x(t,"name",{value:e,configurable:!0}),"__name");function P(t){return document.querySelector(t)}i(P,"qs");S(P,"qs");var _=Object.defineProperty,C=i((t,e)=>_(t,"name",{value:e,configurable:!0}),"__name");function O(t){return document.querySelectorAll(t)}i(O,"qsa");C(O,"qsa");var N=Object.defineProperty,d=i((t,e)=>N(t,"name",{value:e,configurable:!0}),"__name");function b(t){return document.createElement(t).constructor!==window.HTMLElement}i(b,"isRegistered");d(b,"isRegistered");function $(t,e){typeof window>"u"||"customElements"in window&&(b(t)||window.customElements.define(t,e))}i($,"define");d($,"define");function h(t,e){return t.matches||(t=t.parentElement),t.matches(e)?t:t.closest(e)}i(h,"match");d(h,"match");var q=Object.defineProperty,u=i((t,e)=>q(t,"name",{value:e,configurable:!0}),"__name"),l=class t extends window.HTMLElement{static{i(this,"WebComponent")}static{u(this,"WebComponent")}static TAG="";TAG="";static reflectedBooleanAttributes=[];static reflectedStringAttributes=[];static get observedAttributes(){return[...new Set([...this.reflectedBooleanAttributes,...this.reflectedStringAttributes])]}static match(e){return h(e,this.TAG)}static create(e){let r=class extends t{static{i(this,"CreatedClass")}static{u(this,"CreatedClass")}static TAG=e;TAG=e;render(){throw new Error("`render` should be implemented by children")}};return r.define=function(){return t.define.call(this)},r.event=function(n){return t.event.call(this,n)},r}static define(){w(this.TAG,this)}attributeChangedCallback(e,r,n){let a=this[A(e)];a&&a.call(this,r,n)}connectedCallback(){this.render()}qs(e){return this.querySelector(e)}qsa(e){return this.querySelectorAll(e)}static event(e){return g(this.TAG,e)}emit(e,r={}){if(e==="*")throw new Error('Do not emit the literal "*"');let{bubbles:n=!0,cancelable:a=!0,detail:o}=r,s=`${this.TAG}:${e}`,c=new CustomEvent(s,{bubbles:n,cancelable:a,detail:o});return this.dispatchEvent(c)}dispatch(e,r={}){let n=new CustomEvent(e,{bubbles:r.bubbles===void 0?!0:r.bubbles,cancelable:r.cancelable===void 0?!0:r.cancelable,detail:r.detail});return this.dispatchEvent(n)}on(e,r,n){let a=t.event.call(this,e);this.addEventListener(a,r,n)}off(e,r,n){let a=t.event.call(this,e);this.removeEventListener(a,r,n)}};function g(t,e){return`${t}:${e}`}i(g,"eventName");u(g,"eventName");var m=Object.create(null);function A(t){return m[t]||(m[t]=`handleChange_${t}`)}i(A,"handlerKey");u(A,"handlerKey");function v(t){return document.createElement(t).constructor!==window.HTMLElement}i(v,"isRegistered");u(v,"isRegistered");function w(t,e){if(typeof window>"u"||!("customElements"in window)||v(t))return;let r=e,n=r.reflectedBooleanAttributes??[],a=r.reflectedStringAttributes??[],o=e.prototype;for(let s of n)s in HTMLElement.prototype||Object.getOwnPropertyDescriptor(o,s)||Object.defineProperty(o,s,{get(){return this.hasAttribute(s)},set(c){this.toggleAttribute(s,!!c)},configurable:!0,enumerable:!0});for(let s of a){if(n.includes(s)){console.warn(`[web-component] "${s}" appears in both reflectedBooleanAttributes and reflectedStringAttributes on <${t}>. Boolean wins.`);continue}s in HTMLElement.prototype||Object.getOwnPropertyDescriptor(o,s)||Object.defineProperty(o,s,{get(){return this.getAttribute(s)},set(c){c==null?this.removeAttribute(s):this.setAttribute(s,String(c))},configurable:!0,enumerable:!0})}window.customElements.define(t,e)}i(w,"define");u(w,"define");var M="https://public.api.bsky.app",j=5,E=typeof CSS<"u"&&CSS.supports("anchor-scope: --a"),D=0,f=class extends l.create("actor-typeahead"){static{i(this,"ActorTypeahead")}static reflectedStringAttributes=["host","rows"];#s=`actor-typeahead-${++D}`;#n=[];#e=-1;#i=!1;#t=null;constructor(){super(),this.addEventListener("input",this),this.addEventListener("focusout",this),this.addEventListener("keydown",this)}get#c(){let e=Number.parseInt(this.rows??"");return Number.isNaN(e)?j:e}handleEvent(e){switch(e.type){case"input":this.#l(e);break;case"keydown":e instanceof KeyboardEvent&&this.#u(e);break;case"focusout":this.#d();break;case"pointerdown":this.#i=!0;break;case"pointerup":this.#h(e);break}}#u(e){let r=this.#n.length-1;switch(e.key){case"ArrowDown":e.preventDefault(),this.#e=Math.min(this.#e+1,r),this.render();break;case"PageDown":e.preventDefault(),this.#e=r,this.render();break;case"ArrowUp":e.preventDefault(),this.#e=Math.max(this.#e-1,0),this.render();break;case"PageUp":e.preventDefault(),this.#e=0,this.render();break;case"Escape":e.preventDefault(),this.#r();break;case"Enter":e.preventDefault(),this.#a(this.#e);break}}async#l(e){if(!(e.target instanceof HTMLInputElement))return;let r=e.target.value;if(!r){this.#r();return}let n=new URL("xrpc/app.bsky.actor.searchActorsTypeahead",this.host??M);n.searchParams.set("q",r),n.searchParams.set("limit",String(this.#c));let o=await(await fetch(n)).json();this.#n=R(o),this.#e=-1,this.render()}#d(){this.#i||this.#r()}#h(e){this.#i=!1;let r=e.target instanceof Element?e.target.closest('[role="option"]'):null;if(!r){this.qs("input")?.focus();return}this.#a(Number(r.dataset.index))}#a(e){let r=this.#n[e],n=this.qs("input");!r||!n||(n.value=r.handle,n.focus(),this.#r(),this.emit("select",{detail:r}))}#r(){this.#n=[],this.#e=-1,this.render()}render(){this.#t||(this.#t=document.createElement("ul"),this.#t.className="suggestion-list",this.#t.id=`${this.#s}-list`,this.#t.setAttribute("role","listbox"),E&&(this.#t.popover="manual"),this.#t.addEventListener("pointerdown",this),this.#t.addEventListener("pointerup",this));let e=this.#t;e.parentNode!==this&&this.append(e),e.replaceChildren(...this.#n.map((a,o)=>{let s=H(a,o,o===this.#e);return s.id=this.#o(o),s}));let r=this.#f(e,this.#n.length>0),n=this.qs("input");if(n){n.setAttribute("role","combobox"),n.setAttribute("aria-autocomplete","list"),n.hasAttribute("autocomplete")||n.setAttribute("autocomplete","off"),n.setAttribute("aria-controls",e.id),n.setAttribute("aria-expanded",String(r));let a=[...n.labels??[]].map(o=>o.id).filter(Boolean);a.length?e.setAttribute("aria-labelledby",a.join(" ")):e.removeAttribute("aria-labelledby"),this.#n[this.#e]?n.setAttribute("aria-activedescendant",this.#o(this.#e)):n.removeAttribute("aria-activedescendant")}}#f(e,r){if(!E)return r;if(!e.isConnected)return!1;let n=e.matches(":popover-open");return r&&!n&&e.showPopover(),!r&&n&&e.hidePopover(),e.matches(":popover-open")}#o(e){return`${this.#s}-option-${e}`}};f.define();function H(t,e,r){let n=document.createElement("li");n.className="suggestion",n.setAttribute("role","option"),n.setAttribute("aria-selected",String(r)),n.dataset.index=String(e),n.dataset.handle=t.handle;let a=document.createElement("div");a.className="avatar";let o=document.createElement("img");o.className="img",o.alt="",t.avatar&&(o.src=t.avatar);let s=document.createElement("span");return s.className="handle",s.textContent=t.handle,a.append(o),n.append(a,s),n}i(H,"suggestion");function R(t){return!y(t)||!Array.isArray(t.actors)?[]:t.actors.flatMap(e=>!y(e)||typeof e.handle!="string"?[]:[typeof e.avatar=="string"?{handle:e.handle,avatar:e.avatar}:{handle:e.handle}])}i(R,"parseActors");function y(t){return typeof t=="object"&&t!==null}i(y,"isRecord");export{f as ActorTypeahead};
1
+ var T=Object.defineProperty;var s=(e,t)=>T(e,"name",{value:t,configurable:!0});var k=Object.defineProperty,L=s((e,t)=>k(e,"name",{value:t,configurable:!0}),"__name");function p(e){return Object.keys(e).reduce((t,n)=>{let r=e[n];return r?typeof r=="boolean"?r?(t+` ${n}`).trim():t:Array.isArray(r)?t+` ${n}="${r.join(" ")}"`:(t+` ${n}="${r}"`).trim():t},"")}s(p,"toAttributes");L(p,"toAttributes");var x=Object.defineProperty,S=s((e,t)=>x(e,"name",{value:t,configurable:!0}),"__name");function C(e){return document.querySelector(e)}s(C,"qs");S(C,"qs");var P=Object.defineProperty,_=s((e,t)=>P(e,"name",{value:t,configurable:!0}),"__name");function O(e){return document.querySelectorAll(e)}s(O,"qsa");_(O,"qsa");var M=Object.defineProperty,d=s((e,t)=>M(e,"name",{value:t,configurable:!0}),"__name");function b(e){return document.createElement(e).constructor!==window.HTMLElement}s(b,"isRegistered");d(b,"isRegistered");function N(e,t){typeof window>"u"||"customElements"in window&&(b(e)||window.customElements.define(e,t))}s(N,"define");d(N,"define");function h(e,t){return e.matches||(e=e.parentElement),e.matches(t)?e:e.closest(t)}s(h,"match");d(h,"match");var $=Object.defineProperty,u=s((e,t)=>$(e,"name",{value:t,configurable:!0}),"__name"),l=class e extends window.HTMLElement{static{s(this,"WebComponent")}static{u(this,"WebComponent")}static TAG="";TAG="";static reflectedBooleanAttributes=[];static reflectedStringAttributes=[];static get observedAttributes(){return[...new Set([...this.reflectedBooleanAttributes,...this.reflectedStringAttributes])]}static match(t){return h(t,this.TAG)}static create(t){let n=class extends e{static{s(this,"CreatedClass")}static{u(this,"CreatedClass")}static TAG=t;TAG=t;render(){throw new Error("`render` should be implemented by children")}};return n.define=function(){return e.define.call(this)},n.event=function(r){return e.event.call(this,r)},n}static define(){v(this.TAG,this)}attributeChangedCallback(t,n,r){let o=this[A(t)];o&&o.call(this,n,r)}connectedCallback(){this.render()}qs(t){return this.querySelector(t)}qsa(t){return this.querySelectorAll(t)}static event(t){return g(this.TAG,t)}emit(t,n={}){if(t==="*")throw new Error('Do not emit the literal "*"');let{bubbles:r=!0,cancelable:o=!0,detail:a}=n,i=`${this.TAG}:${t}`,c=new CustomEvent(i,{bubbles:r,cancelable:o,detail:a});return this.dispatchEvent(c)}dispatch(t,n={}){let r=new CustomEvent(t,{bubbles:n.bubbles===void 0?!0:n.bubbles,cancelable:n.cancelable===void 0?!0:n.cancelable,detail:n.detail});return this.dispatchEvent(r)}on(t,n,r){let o=e.event.call(this,t);this.addEventListener(o,n,r)}off(t,n,r){let o=e.event.call(this,t);this.removeEventListener(o,n,r)}};function g(e,t){return`${e}:${t}`}s(g,"eventName");u(g,"eventName");var m=Object.create(null);function A(e){return m[e]||(m[e]=`handleChange_${e}`)}s(A,"handlerKey");u(A,"handlerKey");function w(e){return document.createElement(e).constructor!==window.HTMLElement}s(w,"isRegistered");u(w,"isRegistered");function v(e,t){if(typeof window>"u"||!("customElements"in window)||w(e))return;let n=t,r=n.reflectedBooleanAttributes??[],o=n.reflectedStringAttributes??[],a=t.prototype;for(let i of r)i in HTMLElement.prototype||Object.getOwnPropertyDescriptor(a,i)||Object.defineProperty(a,i,{get(){return this.hasAttribute(i)},set(c){this.toggleAttribute(i,!!c)},configurable:!0,enumerable:!0});for(let i of o){if(r.includes(i)){console.warn(`[web-component] "${i}" appears in both reflectedBooleanAttributes and reflectedStringAttributes on <${e}>. Boolean wins.`);continue}i in HTMLElement.prototype||Object.getOwnPropertyDescriptor(a,i)||Object.defineProperty(a,i,{get(){return this.getAttribute(i)},set(c){c==null?this.removeAttribute(i):this.setAttribute(i,String(c))},configurable:!0,enumerable:!0})}window.customElements.define(e,t)}s(v,"define");u(v,"define");var q="https://public.api.bsky.app",D=5,j=150,E=typeof CSS<"u"&&CSS.supports("anchor-scope: --a"),H=0,f=class extends l.create("actor-typeahead"){static{s(this,"ActorTypeahead")}static reflectedStringAttributes=["host","rows"];#c=`actor-typeahead-${++H}`;#e=[];#t=-1;#o=!1;#n=null;#i;#a=null;constructor(){super(),this.addEventListener("input",this),this.addEventListener("focusout",this),this.addEventListener("keydown",this)}disconnectedCallback(){this.#s()}get#d(){let t=Number.parseInt(this.rows??"");return Number.isNaN(t)?D:t}handleEvent(t){switch(t.type){case"input":this.#f(t);break;case"keydown":t instanceof KeyboardEvent&&this.#h(t);break;case"focusout":this.#b();break;case"pointerdown":this.#o=!0;break;case"pointerup":this.#m(t);break}}#h(t){let n=this.#e.length-1;switch(t.key){case"ArrowDown":t.preventDefault(),this.#t=Math.min(this.#t+1,n),this.render();break;case"PageDown":t.preventDefault(),this.#t=n,this.render();break;case"ArrowUp":t.preventDefault(),this.#t=Math.max(this.#t-1,0),this.render();break;case"PageUp":t.preventDefault(),this.#t=0,this.render();break;case"Escape":this.#e.length&&t.preventDefault(),this.#r();break;case"Enter":if(!this.#e[this.#t]){this.#s();break}t.preventDefault(),this.#u(this.#t);break}}#f(t){if(!(t.target instanceof HTMLInputElement))return;let n=t.target.value;if(!n){this.#r();return}this.#s(),this.#i=setTimeout(()=>this.#p(n),j)}async#p(t){this.#i=void 0;let n=new URL("xrpc/app.bsky.actor.searchActorsTypeahead",this.host??q);n.searchParams.set("q",t),n.searchParams.set("limit",String(this.#d));let r=new AbortController;this.#a=r;let{signal:o}=r,a;try{let i=await fetch(n,{signal:o});if(o.aborted)return;if(!i.ok)throw new Error(`status ${i.status}`);a=await i.json()}catch(i){if(G(i))return;this.#r();return}o.aborted||(this.#e=B(a),this.#t=-1,this.render())}#b(){this.#o||this.#r()}#m(t){this.#o=!1;let n=t.target instanceof Element?t.target.closest('[role="option"]'):null;if(!n){this.qs("input")?.focus();return}this.#u(Number(n.dataset.index))}#u(t){let n=this.#e[t],r=this.qs("input");!n||!r||(r.value=n.handle,r.focus(),this.#r(),this.emit("select",{detail:n}))}#s(){clearTimeout(this.#i),this.#i=void 0,this.#a?.abort(),this.#a=null}#r(){this.#s(),this.#e=[],this.#t=-1,this.render()}render(){this.#n||(this.#n=document.createElement("ul"),this.#n.className="suggestion-list",this.#n.id=`${this.#c}-list`,this.#n.setAttribute("role","listbox"),E&&(this.#n.popover="manual"),this.#n.addEventListener("pointerdown",this),this.#n.addEventListener("pointerup",this));let t=this.#n;t.parentNode!==this&&this.append(t),t.replaceChildren(...this.#e.map((o,a)=>{let i=R(o,a,a===this.#t);return i.id=this.#l(a),i}));let n=this.#g(t,this.#e.length>0),r=this.qs("input");if(r){r.setAttribute("role","combobox"),r.setAttribute("aria-autocomplete","list"),r.hasAttribute("autocomplete")||r.setAttribute("autocomplete","off"),r.setAttribute("aria-controls",t.id),r.setAttribute("aria-expanded",String(n));let o=[...r.labels??[]].map(a=>a.id).filter(Boolean);o.length?t.setAttribute("aria-labelledby",o.join(" ")):t.removeAttribute("aria-labelledby"),this.#e[this.#t]?r.setAttribute("aria-activedescendant",this.#l(this.#t)):r.removeAttribute("aria-activedescendant")}}#g(t,n){if(!E)return n;if(!t.isConnected)return!1;let r=t.matches(":popover-open");return n&&!r&&t.showPopover(),!n&&r&&t.hidePopover(),t.matches(":popover-open")}#l(t){return`${this.#c}-option-${t}`}};f.define();function R(e,t,n){let r=document.createElement("li");r.className="suggestion",r.setAttribute("role","option"),r.setAttribute("aria-selected",String(n)),r.dataset.index=String(t),r.dataset.handle=e.handle;let o=document.createElement("div");o.className="avatar";let a=document.createElement("img");a.className="img",a.alt="",e.avatar&&(a.src=e.avatar);let i=document.createElement("span");return i.className="handle",i.textContent=e.handle,o.append(a),r.append(o,i),r}s(R,"suggestion");function B(e){return!y(e)||!Array.isArray(e.actors)?[]:e.actors.flatMap(t=>!y(t)||typeof t.handle!="string"?[]:[typeof t.avatar=="string"?{handle:t.handle,avatar:t.avatar}:{handle:t.handle}])}s(B,"parseActors");function G(e){return e instanceof DOMException&&e.name==="AbortError"}s(G,"isAbortError");function y(e){return typeof e=="object"&&e!==null}s(y,"isRecord");export{f as ActorTypeahead};
2
2
  //# sourceMappingURL=index.min.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../node_modules/@substrate-system/web-component/src/attributes.ts", "../node_modules/@substrate-system/web-component/src/qs.ts", "../node_modules/@substrate-system/web-component/src/qsa.ts", "../node_modules/@substrate-system/web-component/src/util.ts", "../node_modules/@substrate-system/web-component/src/index.ts", "../src/index.ts"],
4
- "sourcesContent": ["export type Attrs = Record<string, undefined|null|string|number|boolean|(string|number)[]>\n\n/**\n * Transform an object into an HTML attributes string. The object should be\n * like `{ attributeName: value }`.\n *\n * @param attrs An object for the attributes.\n * @returns {string} A string suitable for use as HTML attributes.\n */\nexport function toAttributes (attrs:Attrs):string {\n return Object.keys(attrs).reduce((acc, k) => {\n const value = attrs[k]\n if (!value) return acc\n\n if (typeof value === 'boolean') {\n if (value) return (acc + ` ${k}`).trim()\n return acc\n }\n\n if (Array.isArray(value)) {\n return (acc + ` ${k}=\"${value.join(' ')}\"`)\n }\n\n return (acc + ` ${k}=\"${value}\"`).trim()\n }, '')\n}\n", "export function qs<K extends keyof HTMLElementTagNameMap> (\n selectors:K\n):HTMLElementTagNameMap[K]|null\n\nexport function qs<K extends keyof SVGElementTagNameMap> (\n selectors:K\n):SVGElementTagNameMap[K]|null\n\nexport function qs<E extends Element = Element> (\n selectors:string\n):E|null\n\nexport function qs (selectors:string):Element|null {\n return document.querySelector(selectors)\n}\n", "export function qsa<K extends keyof HTMLElementTagNameMap> (\n selectors:K\n):NodeListOf<HTMLElementTagNameMap[K]>\n\nexport function qsa<K extends keyof SVGElementTagNameMap> (\n selectors:K\n):NodeListOf<SVGElementTagNameMap[K]>\n\nexport function qsa<E extends Element = Element> (\n selectors:string\n):NodeListOf<E>\n\nexport function qsa (selectors:string):NodeListOf<Element> {\n return document.querySelectorAll(selectors)\n}\n", "export { toAttributes } from './attributes.js'\n\n/**\n * Check if the given tag name has been registered.\n *\n * @see {@link https://stackoverflow.com/a/28210364 stackoverflow}\n * @param {string} elName The custom element tag name.\n * @returns {boolean} True if the given name has been registered already.\n */\nexport function isRegistered (elName:string):boolean {\n return document.createElement(elName).constructor !== window.HTMLElement\n}\n\nexport function define (name:string, element:CustomElementConstructor) {\n if (typeof window === 'undefined') return\n if (!('customElements' in window)) return\n if (!isRegistered(name)) {\n window.customElements.define(name, element)\n }\n}\n\n/**\n * Get the closes parent element matching the given selector.\n * @param el Element to start from\n * @param s Selector for an element\n * @returns {HTMLElement|null} The closes parent element that matches.\n */\nexport function match (el:HTMLElement, s:string):HTMLElement|null {\n if (!el.matches) el = el.parentElement!\n return el.matches(s) ? el : el.closest(s)\n}\n\nexport * from './qs.js'\nexport * from './qsa.js'\n", "import { match as _match } from './util.js'\n\nexport abstract class WebComponent extends window.HTMLElement {\n static TAG:string = ''\n TAG:string = ''\n\n /**\n * Declare boolean attributes that should be reflected as properties.\n * The base class auto-generates getters/setters and includes these in\n * `observedAttributes`. Framework property assignment (e.g. Preact's\n * `el.disabled = true`) will then correctly set the attribute.\n */\n static reflectedBooleanAttributes:string[] = []\n\n /**\n * Declare string attributes that should be reflected as properties.\n * Getter returns `string|null` (null when attribute is absent).\n * Setting `null` or `undefined` removes the attribute.\n */\n static reflectedStringAttributes:string[] = []\n\n /**\n * Auto-derived from `reflectedBooleanAttributes` and\n * `reflectedStringAttributes`. Override with `super.observedAttributes`\n * to add non-reflected observed attributes:\n *\n * ```ts\n * static get observedAttributes () {\n * return [...super.observedAttributes, 'aria-label']\n * }\n * ```\n */\n static get observedAttributes ():string[] {\n return [...new Set([\n ...this.reflectedBooleanAttributes,\n ...this.reflectedStringAttributes,\n ])]\n }\n\n static match (el:HTMLElement):HTMLElement|null {\n return _match(el, this.TAG)\n }\n\n static create<T extends string> (elementName:T):typeof WebComponent & {\n new (...args:any[]):WebComponent & { TAG:T };\n TAG:T;\n define: typeof WebComponent.define;\n event: typeof WebComponent.event;\n } {\n const CreatedClass = class extends WebComponent {\n static TAG = elementName\n TAG = elementName\n render () {\n throw new Error('`render` should be implemented by children')\n }\n }\n\n // Copy static methods with proper binding\n CreatedClass.define = function () {\n return WebComponent.define.call(this)\n }\n CreatedClass.event = function (evType:string) {\n return WebComponent.event.call(this, evType)\n }\n\n return CreatedClass\n }\n\n static define<T extends {\n new (...args:any[]):WebComponent;\n TAG:string;\n }>(this:T) {\n define(this.TAG, this)\n }\n\n /**\n * Runs when the value of an attribute is changed.\n *\n * Depends on `static observedAttributes`.\n *\n * Should name methods like `handleChange_disabled`.\n *\n * @param {string} name The attribute name\n * @param {string} oldValue The old attribute value\n * @param {string} newValue The new attribute value\n */\n attributeChangedCallback (\n name:string,\n oldValue:string,\n newValue:string\n ):void {\n const handler = this[handlerKey(name)]\n if (handler) {\n handler.call(this, oldValue, newValue)\n }\n }\n\n connectedCallback () {\n this.render()\n }\n\n abstract render ():any\n\n qs<K extends keyof HTMLElementTagNameMap>(\n selector:K\n ):HTMLElementTagNameMap[K]|null;\n\n qs<E extends Element = Element>(selector:string):E|null;\n qs (selector:string):Element|null {\n return this.querySelector(selector)\n }\n\n qsa<K extends keyof HTMLElementTagNameMap>(\n selector:K\n ):HTMLElementTagNameMap[K]|null;\n\n qsa<E extends Element = Element>(selector:string):E|null;\n qsa (selector:string):NodeListOf<Element> {\n return this.querySelectorAll(selector)\n }\n\n /**\n * Take a non-namepsaced event name, return namespace event name.\n *\n * @param {string} evType The non-namespace event name\n * @returns {string} Namespaced event name, eg, `my-component:click`\n */\n static event (evType:string):string {\n return eventName(this.TAG, evType)\n }\n\n /**\n * Emit a namespaced event.\n *\n * @param type (non-namespaced) event type string\n * @param opts `bubbles`, `detail`, and `cancelable`. Default is\n * `{ bubbles: true, cancelable: true }`\n * @returns {boolean}\n */\n emit<T = any> (type:string, opts:Partial<{\n bubbles:boolean,\n cancelable:boolean,\n detail:CustomEvent<T>['detail']\n }> = {}):boolean {\n if (type === '*') throw new Error('Do not emit the literal \"*\"')\n\n const { bubbles = true, cancelable = true, detail } = opts\n const namespacedType = `${this.TAG}:${type}`\n\n const event = new CustomEvent(namespacedType, {\n bubbles,\n cancelable,\n detail\n })\n\n return this.dispatchEvent(event)\n }\n\n /**\n * Create and emit an event, no namespacing.\n */\n dispatch<T> (type:string, opts:Partial<{\n bubbles:boolean,\n cancelable:boolean,\n detail:CustomEvent<T>['detail']\n }> = {}):boolean {\n const event = new CustomEvent(type, {\n bubbles: (opts.bubbles === undefined) ? true : opts.bubbles,\n cancelable: (opts.cancelable === undefined) ? true : opts.cancelable,\n detail: opts.detail\n })\n\n return this.dispatchEvent(event)\n }\n\n /**\n * Listen for namespaced events.\n */\n on<T extends Event = Event> (\n evName:string,\n handler:(ev:T)=>any,\n options?:boolean|AddEventListenerOptions\n ):void;\n\n on (\n evName:string,\n handler:EventListenerObject,\n options?:boolean|AddEventListenerOptions\n ):void;\n\n on (\n evName:string,\n handler:((ev:Event)=>any)|EventListenerObject,\n options?:boolean|AddEventListenerOptions\n ):void {\n const fullEvName = WebComponent.event.call(this, evName)\n this.addEventListener(fullEvName, handler as EventListenerOrEventListenerObject, options)\n }\n\n /**\n * Remove a namespaced event listener.\n */\n off<T extends Event = Event> (\n evName:string,\n handler:(ev:T)=>any,\n options?:boolean|EventListenerOptions\n ):void;\n\n off (\n evName:string,\n handler:EventListenerObject,\n options?:boolean|EventListenerOptions\n ):void;\n\n off (\n evName:string,\n handler:((ev:Event)=>any)|EventListenerObject,\n options?:boolean|EventListenerOptions\n ):void {\n const fullEvName = WebComponent.event.call(this, evName)\n this.removeEventListener(fullEvName, handler as EventListenerOrEventListenerObject, options)\n }\n}\n\nfunction eventName (namespace:string, evType:string) {\n return `${namespace}:${evType}`\n}\n\n/**\n * Cache of attribute name -> handler method name (`handleChange_<attr>`).\n * Building the lookup string once per attribute avoids re-allocating it on\n * every `attributeChangedCallback`.\n */\nconst handlerKeys:Record<string, string> = Object.create(null)\nfunction handlerKey (name:string):string {\n return handlerKeys[name] || (handlerKeys[name] = `handleChange_${name}`)\n}\n\n/**\n * Check if the given tag name has been registered.\n *\n * @see {@link https://stackoverflow.com/a/28210364 stackoverflow}\n * @param {string} elName The custom element tag name.\n * @returns {boolean} True if the given name has been registered already.\n */\nexport function isRegistered (elName:string):boolean {\n return document.createElement(elName).constructor !== window.HTMLElement\n}\n\nexport function define (name:string, element:CustomElementConstructor) {\n if (typeof window === 'undefined') return\n if (!('customElements' in window)) return\n if (isRegistered(name)) return\n\n const ctor = element as unknown as typeof WebComponent\n const boolAttrs:string[] = ctor.reflectedBooleanAttributes ?? []\n const strAttrs:string[] = ctor.reflectedStringAttributes ?? []\n const proto = (element as any).prototype\n\n for (const attr of boolAttrs) {\n // Skip built-in IDL attributes on HTMLElement and ancestors\n // (covers Element.prototype, Node.prototype, etc.)\n if (attr in HTMLElement.prototype) continue\n // Skip if the subclass already defines an own-property accessor\n if (Object.getOwnPropertyDescriptor(proto, attr)) continue\n Object.defineProperty(proto, attr, {\n get (this:HTMLElement):boolean {\n return this.hasAttribute(attr)\n },\n set (this:HTMLElement, v:unknown) {\n this.toggleAttribute(attr, Boolean(v))\n },\n configurable: true,\n enumerable: true,\n })\n }\n\n for (const attr of strAttrs) {\n if (boolAttrs.includes(attr)) {\n console.warn(\n `[web-component] \"${attr}\" appears in both ` +\n 'reflectedBooleanAttributes and reflectedStringAttributes ' +\n `on <${name}>. Boolean wins.`\n )\n continue\n }\n if (attr in HTMLElement.prototype) continue\n if (Object.getOwnPropertyDescriptor(proto, attr)) continue\n Object.defineProperty(proto, attr, {\n get (this:HTMLElement):string|null {\n return this.getAttribute(attr)\n },\n set (this:HTMLElement, v:unknown) {\n // null and undefined both remove the attribute\n if (v == null) {\n this.removeAttribute(attr)\n } else {\n this.setAttribute(attr, String(v))\n }\n },\n configurable: true,\n enumerable: true,\n })\n }\n\n window.customElements.define(name, element)\n}\n", "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n *\n * Copyright (c) 2026 Jake Lazaroff\n *\n * Repository: https://tangled.org/jakelazaroff.com/actor-typeahead\n */\n\nimport { WebComponent } from '@substrate-system/web-component'\n\n// for document.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'actor-typeahead':ActorTypeahead\n }\n}\n\nexport interface Actor {\n handle:string;\n avatar?:string;\n}\n\nconst DEFAULT_HOST = 'https://public.api.bsky.app'\nconst DEFAULT_ROWS = 5\n\n// The Suggestion list is a top-layer popover, positioned against the host\n// with CSS anchor positioning. `anchor-scope` shipped last of that set, so\n// it gates the group. Without it, the old absolute layout is used.\nconst ANCHORED = typeof CSS !== 'undefined' &&\n CSS.supports('anchor-scope: --a')\n\n// counts instances, so each one gets unique element ids\nlet instances = 0\n\n/**\n * @attribute {string} [host] - The host to which to make the typeahead\n * API call.\n * @attribute {number} [rows] - The maximum number of Suggestions to\n * display in the Suggestion list.\n *\n * @fires actor-typeahead:select - A Suggestion was chosen from the\n * Suggestion list. The event `detail` is `{ handle, avatar }`.\n *\n * @cssprop --color-background - Controls the color of the Suggestion list\n * background.\n * @cssprop --color-border - Controls the color of the Suggestion list\n * border.\n * @cssprop --color-shadow - Controls the color of the Suggestion list\n * shadow.\n * @cssprop --color-hover - Controls the background color of each\n * Suggestion on hover.\n * @cssprop --color-avatar-fallback - Controls the background color of an\n * avatar circle if the image fails to load.\n * @cssprop --radius - Controls the corner radius of the Suggestion list.\n * @cssprop --padding-suggestion-list - Controls the padding of the\n * Suggestion list.\n *\n * @summary A small web component that progressively enhances an <input>\n * element into an autocomplete for ATProto handles.\n *\n * @tag actor-typeahead\n */\nexport class ActorTypeahead extends WebComponent.create('actor-typeahead') {\n static reflectedStringAttributes = ['host', 'rows']\n\n declare host:string|null\n declare rows:string|null\n\n #id = `actor-typeahead-${++instances}`\n #actors:Actor[] = []\n #index = -1\n #pressed = false\n #suggestionList:HTMLUListElement|null = null\n\n constructor () {\n super()\n this.addEventListener('input', this)\n this.addEventListener('focusout', this)\n this.addEventListener('keydown', this)\n }\n\n get #limit ():number {\n const rows = Number.parseInt(this.rows ?? '')\n return Number.isNaN(rows) ? DEFAULT_ROWS : rows\n }\n\n handleEvent (evt:Event) {\n switch (evt.type) {\n case 'input':\n this.#oninput(evt)\n break\n\n case 'keydown':\n if (evt instanceof KeyboardEvent) this.#onkeydown(evt)\n break\n\n case 'focusout':\n this.#onfocusout()\n break\n\n case 'pointerdown':\n this.#pressed = true\n break\n\n case 'pointerup':\n this.#onpointerup(evt)\n break\n }\n }\n\n #onkeydown (evt:KeyboardEvent) {\n const last = this.#actors.length - 1\n\n switch (evt.key) {\n case 'ArrowDown':\n evt.preventDefault()\n this.#index = Math.min(this.#index + 1, last)\n this.render()\n break\n\n case 'PageDown':\n evt.preventDefault()\n this.#index = last\n this.render()\n break\n\n case 'ArrowUp':\n evt.preventDefault()\n this.#index = Math.max(this.#index - 1, 0)\n this.render()\n break\n\n case 'PageUp':\n evt.preventDefault()\n this.#index = 0\n this.render()\n break\n\n case 'Escape':\n evt.preventDefault()\n this.#clear()\n break\n\n case 'Enter':\n evt.preventDefault()\n this.#select(this.#index)\n break\n }\n }\n\n async #oninput (evt:Event) {\n if (!(evt.target instanceof HTMLInputElement)) return\n\n const query = evt.target.value\n if (!query) {\n this.#clear()\n return\n }\n\n const url = new URL(\n 'xrpc/app.bsky.actor.searchActorsTypeahead',\n this.host ?? DEFAULT_HOST\n )\n url.searchParams.set('q', query)\n url.searchParams.set('limit', String(this.#limit))\n\n const res = await fetch(url)\n const json:unknown = await res.json()\n this.#actors = parseActors(json)\n this.#index = -1\n this.render()\n }\n\n #onfocusout () {\n if (this.#pressed) return\n this.#clear()\n }\n\n #onpointerup (evt:Event) {\n this.#pressed = false\n\n const option = evt.target instanceof Element ?\n evt.target.closest<HTMLElement>('[role=\"option\"]') :\n null\n\n if (!option) {\n this.qs('input')?.focus()\n return\n }\n\n this.#select(Number(option.dataset.index))\n }\n\n #select (index:number) {\n const actor = this.#actors[index]\n const input = this.qs('input')\n if (!actor || !input) return\n\n input.value = actor.handle\n input.focus()\n this.#clear()\n this.emit<Actor>('select', { detail: actor })\n }\n\n #clear () {\n this.#actors = []\n this.#index = -1\n this.render()\n }\n\n /**\n * Render into light DOM, next to the authored `<input>`. Only the\n * Suggestion list is rebuilt, so the input element, and anything the\n * consumer attached to it, is never replaced. On the input, only the\n * ARIA combobox attributes are written, plus `autocomplete=\"off\"`\n * when the page did not set `autocomplete`.\n */\n render () {\n if (!this.#suggestionList) {\n this.#suggestionList = document.createElement('ul')\n this.#suggestionList.className = 'suggestion-list'\n this.#suggestionList.id = `${this.#id}-list`\n this.#suggestionList.setAttribute('role', 'listbox')\n if (ANCHORED) this.#suggestionList.popover = 'manual'\n this.#suggestionList.addEventListener('pointerdown', this)\n this.#suggestionList.addEventListener('pointerup', this)\n }\n\n const list = this.#suggestionList\n if (list.parentNode !== this) this.append(list)\n\n list.replaceChildren(...this.#actors.map((actor, i) => {\n const option = suggestion(actor, i, i === this.#index)\n option.id = this.#optionId(i)\n return option\n }))\n\n const open = this.#toggle(list, this.#actors.length > 0)\n\n const input = this.qs('input')\n if (input) {\n input.setAttribute('role', 'combobox')\n input.setAttribute('aria-autocomplete', 'list')\n if (!input.hasAttribute('autocomplete')) {\n input.setAttribute('autocomplete', 'off')\n }\n input.setAttribute('aria-controls', list.id)\n input.setAttribute('aria-expanded', String(open))\n\n const labels = [...(input.labels ?? [])]\n .map(label => label.id)\n .filter(Boolean)\n if (labels.length) {\n list.setAttribute('aria-labelledby', labels.join(' '))\n } else {\n list.removeAttribute('aria-labelledby')\n }\n\n if (this.#actors[this.#index]) {\n input.setAttribute(\n 'aria-activedescendant',\n this.#optionId(this.#index)\n )\n } else {\n input.removeAttribute('aria-activedescendant')\n }\n }\n }\n\n /**\n * Show or hide the Suggestion list popover, guarded on its current\n * state so repeated renders neither throw nor toggle. A disconnected\n * popover cannot be shown, e.g. when a response arrives after the host\n * was removed. Returns whether the Suggestion list is open.\n */\n #toggle (list:HTMLElement, open:boolean):boolean {\n if (!ANCHORED) return open\n if (!list.isConnected) return false\n\n const isOpen = list.matches(':popover-open')\n if (open && !isOpen) list.showPopover()\n if (!open && isOpen) list.hidePopover()\n return list.matches(':popover-open')\n }\n\n #optionId (index:number):string {\n return `${this.#id}-option-${index}`\n }\n}\n\nActorTypeahead.define()\n\n/**\n * Build one Suggestion. Values from the API go through `textContent` and\n * properties, never through `innerHTML`.\n */\nfunction suggestion (\n actor:Actor,\n index:number,\n active:boolean\n):HTMLLIElement {\n const li = document.createElement('li')\n li.className = 'suggestion'\n li.setAttribute('role', 'option')\n li.setAttribute('aria-selected', String(active))\n li.dataset.index = String(index)\n li.dataset.handle = actor.handle\n\n const avatar = document.createElement('div')\n avatar.className = 'avatar'\n\n const img = document.createElement('img')\n img.className = 'img'\n img.alt = ''\n if (actor.avatar) img.src = actor.avatar\n\n const handle = document.createElement('span')\n handle.className = 'handle'\n handle.textContent = actor.handle\n\n avatar.append(img)\n li.append(avatar, handle)\n\n return li\n}\n\nfunction parseActors (json:unknown):Actor[] {\n if (!isRecord(json) || !Array.isArray(json.actors)) return []\n\n return json.actors.flatMap((actor:unknown) => {\n if (!isRecord(actor) || typeof actor.handle !== 'string') return []\n\n return [typeof actor.avatar === 'string' ?\n { handle: actor.handle, avatar: actor.avatar } :\n { handle: actor.handle }]\n })\n}\n\nfunction isRecord (value:unknown):value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n"],
5
- "mappings": "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",
6
- "names": ["toAttributes", "attrs", "acc", "k", "value", "__name", "qs", "selectors", "__name", "qsa", "selectors", "__name", "isRegistered", "elName", "__name", "define", "name", "element", "match", "el", "s", "WebComponent", "_WebComponent", "__name", "el", "match", "elementName", "CreatedClass", "evType", "define", "name", "oldValue", "newValue", "handler", "handlerKey", "selector", "eventName", "type", "opts", "bubbles", "cancelable", "detail", "namespacedType", "event", "evName", "options", "fullEvName", "namespace", "handlerKeys", "isRegistered", "elName", "element", "ctor", "boolAttrs", "strAttrs", "proto", "attr", "v", "DEFAULT_HOST", "DEFAULT_ROWS", "ANCHORED", "instances", "ActorTypeahead", "WebComponent", "__name", "#id", "#actors", "#index", "#pressed", "#suggestionList", "#limit", "rows", "evt", "#oninput", "#onkeydown", "#onfocusout", "#onpointerup", "last", "#clear", "#select", "query", "url", "json", "parseActors", "option", "index", "actor", "input", "list", "i", "suggestion", "#optionId", "open", "#toggle", "labels", "label", "isOpen", "active", "li", "avatar", "img", "handle", "isRecord", "value"]
4
+ "sourcesContent": ["export type Attrs = Record<string, undefined|null|string|number|boolean|(string|number)[]>\n\n/**\n * Transform an object into an HTML attributes string. The object should be\n * like `{ attributeName: value }`.\n *\n * @param attrs An object for the attributes.\n * @returns {string} A string suitable for use as HTML attributes.\n */\nexport function toAttributes (attrs:Attrs):string {\n return Object.keys(attrs).reduce((acc, k) => {\n const value = attrs[k]\n if (!value) return acc\n\n if (typeof value === 'boolean') {\n if (value) return (acc + ` ${k}`).trim()\n return acc\n }\n\n if (Array.isArray(value)) {\n return (acc + ` ${k}=\"${value.join(' ')}\"`)\n }\n\n return (acc + ` ${k}=\"${value}\"`).trim()\n }, '')\n}\n", "export function qs<K extends keyof HTMLElementTagNameMap> (\n selectors:K\n):HTMLElementTagNameMap[K]|null\n\nexport function qs<K extends keyof SVGElementTagNameMap> (\n selectors:K\n):SVGElementTagNameMap[K]|null\n\nexport function qs<E extends Element = Element> (\n selectors:string\n):E|null\n\nexport function qs (selectors:string):Element|null {\n return document.querySelector(selectors)\n}\n", "export function qsa<K extends keyof HTMLElementTagNameMap> (\n selectors:K\n):NodeListOf<HTMLElementTagNameMap[K]>\n\nexport function qsa<K extends keyof SVGElementTagNameMap> (\n selectors:K\n):NodeListOf<SVGElementTagNameMap[K]>\n\nexport function qsa<E extends Element = Element> (\n selectors:string\n):NodeListOf<E>\n\nexport function qsa (selectors:string):NodeListOf<Element> {\n return document.querySelectorAll(selectors)\n}\n", "export { toAttributes } from './attributes.js'\n\n/**\n * Check if the given tag name has been registered.\n *\n * @see {@link https://stackoverflow.com/a/28210364 stackoverflow}\n * @param {string} elName The custom element tag name.\n * @returns {boolean} True if the given name has been registered already.\n */\nexport function isRegistered (elName:string):boolean {\n return document.createElement(elName).constructor !== window.HTMLElement\n}\n\nexport function define (name:string, element:CustomElementConstructor) {\n if (typeof window === 'undefined') return\n if (!('customElements' in window)) return\n if (!isRegistered(name)) {\n window.customElements.define(name, element)\n }\n}\n\n/**\n * Get the closes parent element matching the given selector.\n * @param el Element to start from\n * @param s Selector for an element\n * @returns {HTMLElement|null} The closes parent element that matches.\n */\nexport function match (el:HTMLElement, s:string):HTMLElement|null {\n if (!el.matches) el = el.parentElement!\n return el.matches(s) ? el : el.closest(s)\n}\n\nexport * from './qs.js'\nexport * from './qsa.js'\n", "import { match as _match } from './util.js'\n\nexport abstract class WebComponent extends window.HTMLElement {\n static TAG:string = ''\n TAG:string = ''\n\n /**\n * Declare boolean attributes that should be reflected as properties.\n * The base class auto-generates getters/setters and includes these in\n * `observedAttributes`. Framework property assignment (e.g. Preact's\n * `el.disabled = true`) will then correctly set the attribute.\n */\n static reflectedBooleanAttributes:string[] = []\n\n /**\n * Declare string attributes that should be reflected as properties.\n * Getter returns `string|null` (null when attribute is absent).\n * Setting `null` or `undefined` removes the attribute.\n */\n static reflectedStringAttributes:string[] = []\n\n /**\n * Auto-derived from `reflectedBooleanAttributes` and\n * `reflectedStringAttributes`. Override with `super.observedAttributes`\n * to add non-reflected observed attributes:\n *\n * ```ts\n * static get observedAttributes () {\n * return [...super.observedAttributes, 'aria-label']\n * }\n * ```\n */\n static get observedAttributes ():string[] {\n return [...new Set([\n ...this.reflectedBooleanAttributes,\n ...this.reflectedStringAttributes,\n ])]\n }\n\n static match (el:HTMLElement):HTMLElement|null {\n return _match(el, this.TAG)\n }\n\n static create<T extends string> (elementName:T):typeof WebComponent & {\n new (...args:any[]):WebComponent & { TAG:T };\n TAG:T;\n define: typeof WebComponent.define;\n event: typeof WebComponent.event;\n } {\n const CreatedClass = class extends WebComponent {\n static TAG = elementName\n TAG = elementName\n render () {\n throw new Error('`render` should be implemented by children')\n }\n }\n\n // Copy static methods with proper binding\n CreatedClass.define = function () {\n return WebComponent.define.call(this)\n }\n CreatedClass.event = function (evType:string) {\n return WebComponent.event.call(this, evType)\n }\n\n return CreatedClass\n }\n\n static define<T extends {\n new (...args:any[]):WebComponent;\n TAG:string;\n }>(this:T) {\n define(this.TAG, this)\n }\n\n /**\n * Runs when the value of an attribute is changed.\n *\n * Depends on `static observedAttributes`.\n *\n * Should name methods like `handleChange_disabled`.\n *\n * @param {string} name The attribute name\n * @param {string} oldValue The old attribute value\n * @param {string} newValue The new attribute value\n */\n attributeChangedCallback (\n name:string,\n oldValue:string,\n newValue:string\n ):void {\n const handler = this[handlerKey(name)]\n if (handler) {\n handler.call(this, oldValue, newValue)\n }\n }\n\n connectedCallback () {\n this.render()\n }\n\n abstract render ():any\n\n qs<K extends keyof HTMLElementTagNameMap>(\n selector:K\n ):HTMLElementTagNameMap[K]|null;\n\n qs<E extends Element = Element>(selector:string):E|null;\n qs (selector:string):Element|null {\n return this.querySelector(selector)\n }\n\n qsa<K extends keyof HTMLElementTagNameMap>(\n selector:K\n ):HTMLElementTagNameMap[K]|null;\n\n qsa<E extends Element = Element>(selector:string):E|null;\n qsa (selector:string):NodeListOf<Element> {\n return this.querySelectorAll(selector)\n }\n\n /**\n * Take a non-namepsaced event name, return namespace event name.\n *\n * @param {string} evType The non-namespace event name\n * @returns {string} Namespaced event name, eg, `my-component:click`\n */\n static event (evType:string):string {\n return eventName(this.TAG, evType)\n }\n\n /**\n * Emit a namespaced event.\n *\n * @param type (non-namespaced) event type string\n * @param opts `bubbles`, `detail`, and `cancelable`. Default is\n * `{ bubbles: true, cancelable: true }`\n * @returns {boolean}\n */\n emit<T = any> (type:string, opts:Partial<{\n bubbles:boolean,\n cancelable:boolean,\n detail:CustomEvent<T>['detail']\n }> = {}):boolean {\n if (type === '*') throw new Error('Do not emit the literal \"*\"')\n\n const { bubbles = true, cancelable = true, detail } = opts\n const namespacedType = `${this.TAG}:${type}`\n\n const event = new CustomEvent(namespacedType, {\n bubbles,\n cancelable,\n detail\n })\n\n return this.dispatchEvent(event)\n }\n\n /**\n * Create and emit an event, no namespacing.\n */\n dispatch<T> (type:string, opts:Partial<{\n bubbles:boolean,\n cancelable:boolean,\n detail:CustomEvent<T>['detail']\n }> = {}):boolean {\n const event = new CustomEvent(type, {\n bubbles: (opts.bubbles === undefined) ? true : opts.bubbles,\n cancelable: (opts.cancelable === undefined) ? true : opts.cancelable,\n detail: opts.detail\n })\n\n return this.dispatchEvent(event)\n }\n\n /**\n * Listen for namespaced events.\n */\n on<T extends Event = Event> (\n evName:string,\n handler:(ev:T)=>any,\n options?:boolean|AddEventListenerOptions\n ):void;\n\n on (\n evName:string,\n handler:EventListenerObject,\n options?:boolean|AddEventListenerOptions\n ):void;\n\n on (\n evName:string,\n handler:((ev:Event)=>any)|EventListenerObject,\n options?:boolean|AddEventListenerOptions\n ):void {\n const fullEvName = WebComponent.event.call(this, evName)\n this.addEventListener(fullEvName, handler as EventListenerOrEventListenerObject, options)\n }\n\n /**\n * Remove a namespaced event listener.\n */\n off<T extends Event = Event> (\n evName:string,\n handler:(ev:T)=>any,\n options?:boolean|EventListenerOptions\n ):void;\n\n off (\n evName:string,\n handler:EventListenerObject,\n options?:boolean|EventListenerOptions\n ):void;\n\n off (\n evName:string,\n handler:((ev:Event)=>any)|EventListenerObject,\n options?:boolean|EventListenerOptions\n ):void {\n const fullEvName = WebComponent.event.call(this, evName)\n this.removeEventListener(fullEvName, handler as EventListenerOrEventListenerObject, options)\n }\n}\n\nfunction eventName (namespace:string, evType:string) {\n return `${namespace}:${evType}`\n}\n\n/**\n * Cache of attribute name -> handler method name (`handleChange_<attr>`).\n * Building the lookup string once per attribute avoids re-allocating it on\n * every `attributeChangedCallback`.\n */\nconst handlerKeys:Record<string, string> = Object.create(null)\nfunction handlerKey (name:string):string {\n return handlerKeys[name] || (handlerKeys[name] = `handleChange_${name}`)\n}\n\n/**\n * Check if the given tag name has been registered.\n *\n * @see {@link https://stackoverflow.com/a/28210364 stackoverflow}\n * @param {string} elName The custom element tag name.\n * @returns {boolean} True if the given name has been registered already.\n */\nexport function isRegistered (elName:string):boolean {\n return document.createElement(elName).constructor !== window.HTMLElement\n}\n\nexport function define (name:string, element:CustomElementConstructor) {\n if (typeof window === 'undefined') return\n if (!('customElements' in window)) return\n if (isRegistered(name)) return\n\n const ctor = element as unknown as typeof WebComponent\n const boolAttrs:string[] = ctor.reflectedBooleanAttributes ?? []\n const strAttrs:string[] = ctor.reflectedStringAttributes ?? []\n const proto = (element as any).prototype\n\n for (const attr of boolAttrs) {\n // Skip built-in IDL attributes on HTMLElement and ancestors\n // (covers Element.prototype, Node.prototype, etc.)\n if (attr in HTMLElement.prototype) continue\n // Skip if the subclass already defines an own-property accessor\n if (Object.getOwnPropertyDescriptor(proto, attr)) continue\n Object.defineProperty(proto, attr, {\n get (this:HTMLElement):boolean {\n return this.hasAttribute(attr)\n },\n set (this:HTMLElement, v:unknown) {\n this.toggleAttribute(attr, Boolean(v))\n },\n configurable: true,\n enumerable: true,\n })\n }\n\n for (const attr of strAttrs) {\n if (boolAttrs.includes(attr)) {\n console.warn(\n `[web-component] \"${attr}\" appears in both ` +\n 'reflectedBooleanAttributes and reflectedStringAttributes ' +\n `on <${name}>. Boolean wins.`\n )\n continue\n }\n if (attr in HTMLElement.prototype) continue\n if (Object.getOwnPropertyDescriptor(proto, attr)) continue\n Object.defineProperty(proto, attr, {\n get (this:HTMLElement):string|null {\n return this.getAttribute(attr)\n },\n set (this:HTMLElement, v:unknown) {\n // null and undefined both remove the attribute\n if (v == null) {\n this.removeAttribute(attr)\n } else {\n this.setAttribute(attr, String(v))\n }\n },\n configurable: true,\n enumerable: true,\n })\n }\n\n window.customElements.define(name, element)\n}\n", "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n *\n * Copyright (c) 2026 Jake Lazaroff\n *\n * Repository: https://tangled.org/jakelazaroff.com/actor-typeahead\n */\n\nimport { WebComponent } from '@substrate-system/web-component'\n\n// for document.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'actor-typeahead':ActorTypeahead\n }\n}\n\nexport interface Actor {\n handle:string;\n avatar?:string;\n}\n\nconst DEFAULT_HOST = 'https://public.api.bsky.app'\nconst DEFAULT_ROWS = 5\n\n// how long the Query must stay unchanged before a Typeahead request\nconst DEBOUNCE_MS = 150\n\n// The Suggestion list is a top-layer popover, positioned against the host\n// with CSS anchor positioning. `anchor-scope` shipped last of that set, so\n// it gates the group. Without it, the old absolute layout is used.\nconst ANCHORED = typeof CSS !== 'undefined' &&\n CSS.supports('anchor-scope: --a')\n\n// counts instances, so each one gets unique element ids\nlet instances = 0\n\n/**\n * @attribute {string} [host] - The host to which to make the typeahead\n * API call.\n * @attribute {number} [rows] - The maximum number of Suggestions to\n * display in the Suggestion list.\n *\n * @fires actor-typeahead:select - A Suggestion was chosen from the\n * Suggestion list. The event `detail` is `{ handle, avatar }`.\n *\n * @cssprop --color-background - Controls the color of the Suggestion list\n * background.\n * @cssprop --color-border - Controls the color of the Suggestion list\n * border.\n * @cssprop --color-shadow - Controls the color of the Suggestion list\n * shadow.\n * @cssprop --color-hover - Controls the background color of each\n * Suggestion on hover.\n * @cssprop --color-avatar-fallback - Controls the background color of an\n * avatar circle if the image fails to load.\n * @cssprop --radius - Controls the corner radius of the Suggestion list.\n * @cssprop --padding-suggestion-list - Controls the padding of the\n * Suggestion list.\n *\n * @summary A small web component that progressively enhances an <input>\n * element into an autocomplete for ATProto handles.\n *\n * @tag actor-typeahead\n */\nexport class ActorTypeahead extends WebComponent.create('actor-typeahead') {\n static reflectedStringAttributes = ['host', 'rows']\n\n declare host:string|null\n declare rows:string|null\n\n #id = `actor-typeahead-${++instances}`\n #actors:Actor[] = []\n #index = -1\n #pressed = false\n #suggestionList:HTMLUListElement|null = null\n #timer:ReturnType<typeof setTimeout>|undefined\n #controller:AbortController|null = null\n\n constructor () {\n super()\n this.addEventListener('input', this)\n this.addEventListener('focusout', this)\n this.addEventListener('keydown', this)\n }\n\n /**\n * Stop any pending Typeahead request. The Suggestion list is left as\n * is; the next Query rebuilds it.\n */\n disconnectedCallback () {\n this.#cancel()\n }\n\n get #limit ():number {\n const rows = Number.parseInt(this.rows ?? '')\n return Number.isNaN(rows) ? DEFAULT_ROWS : rows\n }\n\n handleEvent (evt:Event) {\n switch (evt.type) {\n case 'input':\n this.#oninput(evt)\n break\n\n case 'keydown':\n if (evt instanceof KeyboardEvent) this.#onkeydown(evt)\n break\n\n case 'focusout':\n this.#onfocusout()\n break\n\n case 'pointerdown':\n this.#pressed = true\n break\n\n case 'pointerup':\n this.#onpointerup(evt)\n break\n }\n }\n\n #onkeydown (evt:KeyboardEvent) {\n const last = this.#actors.length - 1\n\n switch (evt.key) {\n case 'ArrowDown':\n evt.preventDefault()\n this.#index = Math.min(this.#index + 1, last)\n this.render()\n break\n\n case 'PageDown':\n evt.preventDefault()\n this.#index = last\n this.render()\n break\n\n case 'ArrowUp':\n evt.preventDefault()\n this.#index = Math.max(this.#index - 1, 0)\n this.render()\n break\n\n case 'PageUp':\n evt.preventDefault()\n this.#index = 0\n this.render()\n break\n\n case 'Escape':\n // with the list closed, let a dialog close; still cancel\n // any pending Typeahead request\n if (this.#actors.length) evt.preventDefault()\n this.#clear()\n break\n\n case 'Enter':\n // with nothing Highlighted, let the form submit, and\n // drop any pending Typeahead request\n if (!this.#actors[this.#index]) {\n this.#cancel()\n break\n }\n evt.preventDefault()\n this.#select(this.#index)\n break\n }\n }\n\n #oninput (evt:Event) {\n if (!(evt.target instanceof HTMLInputElement)) return\n\n const query = evt.target.value\n if (!query) {\n this.#clear()\n return\n }\n\n // keep the current Suggestions on screen until the new response\n this.#cancel()\n this.#timer = setTimeout(() => this.#request(query), DEBOUNCE_MS)\n }\n\n async #request (query:string) {\n this.#timer = undefined\n const url = new URL(\n 'xrpc/app.bsky.actor.searchActorsTypeahead',\n this.host ?? DEFAULT_HOST\n )\n url.searchParams.set('q', query)\n url.searchParams.set('limit', String(this.#limit))\n\n const controller = new AbortController()\n this.#controller = controller\n const { signal } = controller\n\n let json:unknown\n try {\n const res = await fetch(url, { signal })\n if (signal.aborted) return\n if (!res.ok) throw new Error(`status ${res.status}`)\n json = await res.json()\n } catch (err) {\n // a newer Query, or a close, cancelled this request\n if (isAbortError(err)) return\n // any other failure closes the Suggestion list, quietly\n this.#clear()\n return\n }\n if (signal.aborted) return\n\n this.#actors = parseActors(json)\n this.#index = -1\n this.render()\n }\n\n #onfocusout () {\n if (this.#pressed) return\n this.#clear()\n }\n\n #onpointerup (evt:Event) {\n this.#pressed = false\n\n const option = evt.target instanceof Element ?\n evt.target.closest<HTMLElement>('[role=\"option\"]') :\n null\n\n if (!option) {\n this.qs('input')?.focus()\n return\n }\n\n this.#select(Number(option.dataset.index))\n }\n\n #select (index:number) {\n const actor = this.#actors[index]\n const input = this.qs('input')\n if (!actor || !input) return\n\n input.value = actor.handle\n input.focus()\n this.#clear()\n this.emit<Actor>('select', { detail: actor })\n }\n\n /**\n * Stop any pending Typeahead request: the debounce timer, and the\n * request in flight, whose response is then dropped.\n */\n #cancel () {\n clearTimeout(this.#timer)\n this.#timer = undefined\n this.#controller?.abort()\n this.#controller = null\n }\n\n #clear () {\n this.#cancel()\n this.#actors = []\n this.#index = -1\n this.render()\n }\n\n /**\n * Render into light DOM, next to the authored `<input>`. Only the\n * Suggestion list is rebuilt, so the input element, and anything the\n * consumer attached to it, is never replaced. On the input, only the\n * ARIA combobox attributes are written, plus `autocomplete=\"off\"`\n * when the page did not set `autocomplete`.\n */\n render () {\n if (!this.#suggestionList) {\n this.#suggestionList = document.createElement('ul')\n this.#suggestionList.className = 'suggestion-list'\n this.#suggestionList.id = `${this.#id}-list`\n this.#suggestionList.setAttribute('role', 'listbox')\n if (ANCHORED) this.#suggestionList.popover = 'manual'\n this.#suggestionList.addEventListener('pointerdown', this)\n this.#suggestionList.addEventListener('pointerup', this)\n }\n\n const list = this.#suggestionList\n if (list.parentNode !== this) this.append(list)\n\n list.replaceChildren(...this.#actors.map((actor, i) => {\n const option = suggestion(actor, i, i === this.#index)\n option.id = this.#optionId(i)\n return option\n }))\n\n const open = this.#toggle(list, this.#actors.length > 0)\n\n const input = this.qs('input')\n if (input) {\n input.setAttribute('role', 'combobox')\n input.setAttribute('aria-autocomplete', 'list')\n if (!input.hasAttribute('autocomplete')) {\n input.setAttribute('autocomplete', 'off')\n }\n input.setAttribute('aria-controls', list.id)\n input.setAttribute('aria-expanded', String(open))\n\n const labels = [...(input.labels ?? [])]\n .map(label => label.id)\n .filter(Boolean)\n if (labels.length) {\n list.setAttribute('aria-labelledby', labels.join(' '))\n } else {\n list.removeAttribute('aria-labelledby')\n }\n\n if (this.#actors[this.#index]) {\n input.setAttribute(\n 'aria-activedescendant',\n this.#optionId(this.#index)\n )\n } else {\n input.removeAttribute('aria-activedescendant')\n }\n }\n }\n\n /**\n * Show or hide the Suggestion list popover, guarded on its current\n * state so repeated renders neither throw nor toggle. A disconnected\n * popover cannot be shown, e.g. when a response arrives after the host\n * was removed. Returns whether the Suggestion list is open.\n */\n #toggle (list:HTMLElement, open:boolean):boolean {\n if (!ANCHORED) return open\n if (!list.isConnected) return false\n\n const isOpen = list.matches(':popover-open')\n if (open && !isOpen) list.showPopover()\n if (!open && isOpen) list.hidePopover()\n return list.matches(':popover-open')\n }\n\n #optionId (index:number):string {\n return `${this.#id}-option-${index}`\n }\n}\n\nActorTypeahead.define()\n\n/**\n * Build one Suggestion. Values from the API go through `textContent` and\n * properties, never through `innerHTML`.\n */\nfunction suggestion (\n actor:Actor,\n index:number,\n active:boolean\n):HTMLLIElement {\n const li = document.createElement('li')\n li.className = 'suggestion'\n li.setAttribute('role', 'option')\n li.setAttribute('aria-selected', String(active))\n li.dataset.index = String(index)\n li.dataset.handle = actor.handle\n\n const avatar = document.createElement('div')\n avatar.className = 'avatar'\n\n const img = document.createElement('img')\n img.className = 'img'\n img.alt = ''\n if (actor.avatar) img.src = actor.avatar\n\n const handle = document.createElement('span')\n handle.className = 'handle'\n handle.textContent = actor.handle\n\n avatar.append(img)\n li.append(avatar, handle)\n\n return li\n}\n\nfunction parseActors (json:unknown):Actor[] {\n if (!isRecord(json) || !Array.isArray(json.actors)) return []\n\n return json.actors.flatMap((actor:unknown) => {\n if (!isRecord(actor) || typeof actor.handle !== 'string') return []\n\n return [typeof actor.avatar === 'string' ?\n { handle: actor.handle, avatar: actor.avatar } :\n { handle: actor.handle }]\n })\n}\n\nfunction isAbortError (err:unknown):boolean {\n return err instanceof DOMException && err.name === 'AbortError'\n}\n\nfunction isRecord (value:unknown):value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["toAttributes", "attrs", "acc", "k", "value", "__name", "qs", "selectors", "__name", "qsa", "selectors", "__name", "isRegistered", "elName", "__name", "define", "name", "element", "match", "el", "s", "WebComponent", "_WebComponent", "__name", "el", "match", "elementName", "CreatedClass", "evType", "define", "name", "oldValue", "newValue", "handler", "handlerKey", "selector", "eventName", "type", "opts", "bubbles", "cancelable", "detail", "namespacedType", "event", "evName", "options", "fullEvName", "namespace", "handlerKeys", "isRegistered", "elName", "element", "ctor", "boolAttrs", "strAttrs", "proto", "attr", "v", "DEFAULT_HOST", "DEFAULT_ROWS", "DEBOUNCE_MS", "ANCHORED", "instances", "ActorTypeahead", "WebComponent", "__name", "#id", "#actors", "#index", "#pressed", "#suggestionList", "#timer", "#controller", "#cancel", "#limit", "rows", "evt", "#oninput", "#onkeydown", "#onfocusout", "#onpointerup", "last", "#clear", "#select", "query", "#request", "url", "controller", "signal", "json", "res", "err", "isAbortError", "parseActors", "option", "index", "actor", "input", "list", "i", "suggestion", "#optionId", "open", "#toggle", "labels", "label", "isOpen", "active", "li", "avatar", "img", "handle", "isRecord", "value"]
7
7
  }
package/dist/meta.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "inputs": {
3
3
  "src/index.ts": {
4
- "bytes": 10311,
4
+ "bytes": 12336,
5
5
  "imports": [],
6
6
  "format": "esm"
7
7
  }
@@ -11,7 +11,7 @@
11
11
  "imports": [],
12
12
  "exports": [],
13
13
  "inputs": {},
14
- "bytes": 15510
14
+ "bytes": 18384
15
15
  },
16
16
  "dist/index.js": {
17
17
  "imports": [
@@ -27,10 +27,10 @@
27
27
  "entryPoint": "src/index.ts",
28
28
  "inputs": {
29
29
  "src/index.ts": {
30
- "bytesInOutput": 7148
30
+ "bytesInOutput": 8440
31
31
  }
32
32
  },
33
- "bytes": 7340
33
+ "bytes": 8632
34
34
  }
35
35
  }
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@substrate-system/actor-typeahead",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "A small web component that progressively enhances an <input> element into an autocomplete for ATProto handles.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "scripts": {
29
29
  "lint": "eslint \"./**/*.{ts,js}\"",
30
- "test": "esbuild ./test/index.ts --bundle | tapout",
30
+ "test": "esbuild ./test/index.ts --bundle | tapout --timeout 20000",
31
31
  "build-css": "lightningcss --nesting src/index.css -o dist/index.css",
32
32
  "build-css:min": "lightningcss --nesting --minify src/index.css -o dist/index.min.css",
33
33
  "build-cjs": "esbuild src/*.ts --format=cjs --keep-names --tsconfig=tsconfig.build.json --outdir=./dist --out-extension:.js=.cjs --sourcemap",
@@ -62,7 +62,7 @@
62
62
  "lightningcss": "^1.30.2",
63
63
  "lightningcss-cli": "^1.33.0",
64
64
  "markdown-toc": "^1.2.0",
65
- "nightralph": "^0.0.56",
65
+ "nightralph": "^0.1.4",
66
66
  "stylelint": "^17.0.0",
67
67
  "stylelint-config-standard": "^40.0.0",
68
68
  "typescript": "^6.0.2",