# Live Search

Real-time country filtering with p.json(), valibot schema, ctx.bind, ctx.effect, renderList, and computed stores.

## Example code

```html
<!DOCTYPE html>
<html>

<head>
  <title>Live Search</title>
  <meta name="description"
    content="Real-time country filtering with p.json(), valibot schema, ctx.bind, ctx.effect, renderList, and computed stores.">
  <script type="importmap">
    {
      "imports": {
        "nanotags": "https://esm.sh/nanotags@latest",
        "nanotags/render": "https://esm.sh/nanotags@latest/render",
        "nanostores": "https://esm.sh/nanostores@latest",
        "valibot": "https://esm.sh/valibot@latest"
      }
    }
  </script>
</head>

<body>
  <div id="description">Real-time country filtering with <a href='api#json-props'><code>p.json()</code></a>, valibot schema, <a href='api#bind'><code>ctx.bind</code></a>, <a href='api#effect'><code>ctx.effect</code></a>, <a href='api#renderlist'><code>renderList</code></a>, and computed stores.</div>

  <div data-type="html">
    <x-live-search>
      <script type="application/json" data-prop="countries">[
        { "name": "Argentina", "code": "AR" }, { "name": "Australia", "code": "AU" },
        { "name": "Brazil", "code": "BR" }, { "name": "Canada", "code": "CA" },
        { "name": "China", "code": "CN" }, { "name": "Colombia", "code": "CO" },
        { "name": "Czech Republic", "code": "CZ" }, { "name": "Denmark", "code": "DK" },
        { "name": "Egypt", "code": "EG" }, { "name": "Finland", "code": "FI" },
        { "name": "France", "code": "FR" }, { "name": "Germany", "code": "DE" },
        { "name": "Greece", "code": "GR" }, { "name": "India", "code": "IN" },
        { "name": "Indonesia", "code": "ID" }, { "name": "Ireland", "code": "IE" },
        { "name": "Italy", "code": "IT" }, { "name": "Japan", "code": "JP" },
        { "name": "Mexico", "code": "MX" }, { "name": "Netherlands", "code": "NL" },
        { "name": "New Zealand", "code": "NZ" }, { "name": "Norway", "code": "NO" },
        { "name": "Poland", "code": "PL" }, { "name": "Portugal", "code": "PT" },
        { "name": "South Korea", "code": "KR" }, { "name": "Spain", "code": "ES" },
        { "name": "Sweden", "code": "SE" }, { "name": "Switzerland", "code": "CH" },
        { "name": "United Kingdom", "code": "GB" }, { "name": "United States", "code": "US" }
      ]</script>
      <input data-ref="input" type="text" placeholder="Search countries..." />
      <p data-ref="stats"></p>
      <ul data-ref="list">
        <template data-ref="itemTpl">
          <li>
            <span data-code></span>
            <span data-name></span>
          </li>
        </template>
      </ul>
    </x-live-search>
  </div>

  <script type="module" data-type="javascript">
    import { define } from "nanotags";
    import { renderList } from "nanotags/render";
    import { atom, computed } from "nanostores";
    import { array, object, string } from "valibot";

    const CountrySchema = array(object({ name: string(), code: string() }));

    define("x-live-search")
      .withProps(({ json }) => ({
        countries: json(CountrySchema, []),
      }))
      .withRefs(({ one }) => ({
        input: one("input"),
        stats: one("p"),
        list: one("ul"),
        itemTpl: one("template"),
      }))
      .setup((ctx) => {
        const $query = atom("");
        const $filtered = computed([$query, ctx.props.$countries], (query, countries) => {
          if (!query) return countries;
          const q = query.toLowerCase();
          return countries.filter(
            (c) => c.name.toLowerCase().includes(q) || c.code.toLowerCase().includes(q)
          );
        });

        ctx.bind($query, ctx.refs.input);

        ctx.effect($filtered, (filtered) => {
          const total = ctx.props.$countries.get().length;
          ctx.refs.stats.textContent = `${filtered.length} of ${total} countries`;
          renderList(ctx.refs.list, ctx.refs.itemTpl, {
            data: filtered,
            key: (c) => c.code,
            update: (el, country) => {
              ctx.getElement(el, "[data-code]").textContent = country.code;
              ctx.getElement(el, "[data-name]").textContent = country.name;
            },
          });
        });
      });
  </script>

  <style data-type="css">
    x-live-search {
      display: block;
    }

    x-live-search input {
      width: 100%;
    }

    x-live-search p {
      color: var(--text-muted);
      font-size: 13px;
      margin: 8px 0;
    }

    x-live-search ul {
      list-style: none;
      padding: 0;
      margin: 0;
      max-height: 300px;
      overflow-y: auto;
    }

    x-live-search li {
      display: flex;
      gap: 12px;
      padding: 6px 8px;
      border-bottom: 1px solid var(--border);
    }

    x-live-search li:hover {
      background: var(--surface-hover);
    }

    x-live-search [data-code] {
      font-family: monospace;
      color: var(--text-muted);
      min-width: 2.5em;
    }
  </style>
</body>

</html>

```
