Skip to content
Brok UI

Loading…

No results

Phone Input

Open source

Country selector and national number field that produces an E.164-formatted value, with a filterable listbox of 30 common countries.

Version
v1.2.3
Stability
stable
License
MIT
Related
Input
Masked Input
Address Form

Preview

previews.components.phone-input.default.blade.php Blade
<x-ui.phone-input name="phone" default-country="GB" class="w-80" />

Installation

terminal
php artisan ui:add phone-input

Note

This component ships an Alpine behavior module at resources/js/ui/phone-input.js. Import it once from your bundle so it registers on alpine:init:

resources/js/ui/index.js JS
import './phone-input.js';

Registry contract

php artisan ui:add phone-input writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/phone-input.blade.php
  • js resources/js/ui/phone-input.js
Registry dependencies
None — installs on its own.
Packages
composer: jml/brok:^0.2
npm: alpinejs

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

phone-input.md
# Brok UI: Phone Input (`phone-input`)

Country selector and national number field that produces an E.164-formatted value, with a filterable listbox of 30 common countries.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add phone-input
```

## Usage

```blade
<x-ui.phone-input name="phone" default-country="GB" class="w-80" />
```

## Props

- `name` (mixed|null, default `null`) — Form field name; inherited from a wrapping field via @aware or set directly. Names the hidden input that carries the E.164 value.
- `value` (string, default ``) — Initial number; re-parsed on boot into a country and national part.
- `defaultCountry` (string, default `US`) — ISO country code preselected when no value is given.
- `placeholder` (string, default `Phone number`) — Placeholder text shown in the national number field.

## Use when

- Use when formatting, validation, or constrained entry meaningfully reduces errors, such as OTP, phone, currency, password, or numeric entry.
- Collecting a phone number that must submit as a valid E.164 value, with a country dial code the user can change.
- Needing inline SVG country flags with no emoji font dependency.

## Avoid when

- Do not add specialized behavior if a plain field would be simpler and more forgiving.
- The country is always fixed and known; use input type="tel" with a static prefix instead.
- A number that is not a contactable phone number, such as an internal extension format; use input or masked-input instead.

## Anti-patterns

- Adding formatting behavior that makes correction harder

## Rules

- Use the `<brok:phone-input>` tag (or `<x-ui.phone-input>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/phone-input
- Registry JSON (files, props, contract): https://brokui.dev/r/open/phone-input.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
name mixed | null null Form field name; inherited from a wrapping field via @aware or set directly. Names the hidden input that carries the E.164 value.
value string Initial number; re-parsed on boot into a country and national part.
defaultCountry string US ISO country code preselected when no value is given.
placeholder string Phone number Placeholder text shown in the national number field.

Slots

  • default — Unused; the control renders from props only.

Data slots

Stable hooks for CSS overrides and browser tests.

phone-input phone-input-listbox phone-input-listbox-wrapper phone-input-number phone-input-trigger

Behavior

  • The trigger opens the country list on Enter, Space, ArrowDown, ArrowUp, or any printable key, which seeds the filter.
  • Inside the popup, arrow keys, Home and End move the active option through aria-activedescendant while the filter input keeps DOM focus.
  • The hidden input, when name is set, always mirrors the E.164-formatted value so it submits correctly with any HTML form.
  • Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
  • Declares registry capability flags: a11y, interactive, responsive, rtl, darkMode, localized.

Guidance

Specialized input

Collect constrained information with error-reducing formatting.

Use when

  • Use when formatting, validation, or constrained entry meaningfully reduces errors, such as OTP, phone, currency, password, or numeric entry.
  • Collecting a phone number that must submit as a valid E.164 value, with a country dial code the user can change.
  • Needing inline SVG country flags with no emoji font dependency.

Avoid when

  • Do not add specialized behavior if a plain field would be simpler and more forgiving.
  • The country is always fixed and known; use input type="tel" with a static prefix instead.
  • A number that is not a contactable phone number, such as an internal extension format; use input or masked-input instead.

Use instead

  • Plain text or number field with validation

Anti-patterns

  • Adding formatting behavior that makes correction harder
Anatomy
root phone-input-trigger phone-input-listbox-wrapper phone-input-listbox phone-input-number
Theming hooks
phone-input phone-input-listbox phone-input-listbox-wrapper phone-input-number phone-input-trigger

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
Tab Enter Escape ArrowUp ArrowDown Home End
Focus
managed
  • The trigger carries aria-haspopup="listbox" and aria-expanded; Escape closes the popup and returns focus to the trigger.
  • Country flags are decorative and aria-hidden; the accessible information is the country name and dial code read from the option text.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Needs wire:key

Add a stable wire:key when Livewire can reorder this interactive component.

livewire-component.blade.php Blade
<div wire:key="phone-input-{{ $record->id }}">
    <x-ui.phone-input name="phone" default-country="GB" class="w-80" />
</div>

Validation

Validation support: laravel-error-bag. Keep the error message connected with aria-describedby.

livewire-form.blade.php Blade
<form wire:submit="save" class="space-y-2">
    <brok:phone-input
        wire:model="value"
        :aria-invalid="$errors->has('value') ? 'true' : 'false'"
        aria-describedby="value-error"
    />

    @error('value')
        <p id="value-error" role="alert">{{ $message }}</p>
    @enderror

    <brok:button type="submit" wire:loading.attr="disabled">
        <span wire:loading.remove>Save</span>
        <span wire:loading>Saving…</span>
    </brok:button>
</form>

Source

The exact, editable files ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/phone-input.blade.php Blade
{{-- A wrapping <x-ui.field name="…"> shares its name down via @aware so this
     composite self-wires old()/aria-invalid/aria-describedby off the shared
     $errors bag. Inert with no field ancestor and no own `name` —
     fully backward-compatible. --}}
@aware([
    'name' => null,
])

@props([
    'name'           => null,
    'value'          => '',
    'defaultCountry' => 'US',
    'placeholder'    => 'Phone number',
])

@php
    $awareName = $name ?? null;
    $fieldName = $awareName ?? $attributes->get('name');

    $bag = ($errors ?? null) instanceof \Illuminate\Support\ViewErrorBag ? $errors : null;
    $hasBagError = $bag && filled($fieldName) && $bag->has($fieldName);

    // old() repopulates the entered number after a failed redirect-back. The JS
    // re-parses it back into the country + national parts on boot.
    $resolvedValue = filled($fieldName) ? old($fieldName, $value) : $value;

    $describedBy = (filled($awareName) && $hasBagError) ? $fieldName.'-error' : null;
@endphp

{{--
    Phone Input — country selector + national number → E.164 value.
    Bundles a filterable listbox of ~30 common countries with inline SVG flags
    (no emoji font dependency). The hidden input
    (when `name` is set) always mirrors the E.164-formatted value so it
    submits correctly via any HTML form.

    Keyboard: the trigger opens on Enter/Space/ArrowDown/ArrowUp (and on any
    printable key, which seeds the filter — type-ahead). Inside the popup the
    filter input keeps DOM focus while Arrow Up/Down, Home and End move the
    active option through `aria-activedescendant`; Enter selects it and Escape
    closes and returns focus to the trigger.
--}}
<div
    x-data="uiPhoneInput({ defaultCountry: @js($defaultCountry), number: @js($resolvedValue) })"
    x-id="['phone-input-listbox', 'phone-input-option']"
    data-slot="phone-input"
    @if ($hasBagError) data-invalid="true" @endif
    @click.outside="close(false)"
    @keydown.escape="if (open) { $event.stopPropagation(); close() }"
    {{ $attributes->except(['name'])->merge(['class' => 'relative flex rounded-md border border-input bg-background focus-within:ring-2 focus-within:ring-ring']) }}
>
    @if (filled($fieldName))
        <input type="hidden" name="{{ $fieldName }}" :value="e164()" />
    @endif

    {{-- Country selector trigger --}}
    <button
        type="button"
        data-slot="phone-input-trigger"
        x-ref="trigger"
        aria-haspopup="listbox"
        :aria-expanded="open"
        :aria-controls="$id('phone-input-listbox')"
        aria-label="{{ __('Select country') }}"
        @click="open ? close() : openList()"
        @keydown="onTriggerKey($event)"
        class="inline-flex shrink-0 items-center gap-2 rounded-s-md border-e border-input bg-background px-3 text-sm text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none"
    >
        <span x-html="country?.flag" aria-hidden="true" class="inline-block size-5 shrink-0 overflow-hidden rounded-full [&>svg]:block [&>svg]:size-full"></span>
        <span x-text="country?.dial" class="text-muted-foreground"></span>
        <svg
            aria-hidden="true"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="size-4 shrink-0 text-muted-foreground"
            :class="open ? 'rotate-180' : ''"
        >
            <path d="m6 9 6 6 6-6" />
        </svg>
    </button>

    {{-- Filterable country listbox --}}
    <div
        data-slot="phone-input-listbox-wrapper"
        x-show="open"
        x-cloak
        x-transition:enter="motion-safe:transition motion-safe:duration-100"
        x-transition:enter-start="opacity-0 scale-95"
        x-transition:enter-end="opacity-100 scale-100"
        class="absolute start-0 top-full z-dropdown mt-1 w-64 overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md"
    >
        {{-- Search filter --}}
        <div class="border-b border-border p-2">
            <input
                type="text"
                x-ref="filter"
                x-model="query"
                role="combobox"
                aria-autocomplete="list"
                :aria-expanded="open"
                :aria-controls="$id('phone-input-listbox')"
                :aria-activedescendant="activeIndex >= 0 ? $id('phone-input-option', activeIndex + 1) : null"
                @input="activeIndex = 0"
                @keydown="onKey($event)"
                placeholder="{{ __('Search country…') }}"
                autocomplete="off"
                aria-label="{{ __('Filter countries') }}"
                class="h-8 w-full rounded-sm border border-input bg-background px-2 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring"
            />
        </div>

        {{-- Options list --}}
        <ul
            role="listbox"
            data-slot="phone-input-listbox"
            x-ref="list"
            :id="$id('phone-input-listbox')"
            aria-label="{{ __('Countries') }}"
            class="max-h-56 overflow-y-auto p-1"
        >
            <template x-for="(c, i) in filtered()" :key="c.code">
                <li
                    role="option"
                    :id="$id('phone-input-option', i + 1)"
                    :data-index="i"
                    :data-active="i === activeIndex"
                    :aria-selected="country?.code === c.code"
                    @click="select(c)"
                    @mouseenter="activeIndex = i"
                    class="flex cursor-pointer items-center gap-2 rounded-sm px-2 py-2 text-sm text-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground motion-reduce:transition-none"
                    :class="(i === activeIndex || country?.code === c.code) ? 'bg-accent text-accent-foreground' : ''"
                >
                    <span x-html="c.flag" aria-hidden="true" class="inline-block size-5 shrink-0 overflow-hidden rounded-full [&>svg]:block [&>svg]:size-full"></span>
                    <span x-text="c.name" class="grow truncate"></span>
                    <span x-text="c.dial" class="shrink-0 text-muted-foreground"></span>
                </li>
            </template>

            <li role="presentation" x-show="filtered().length === 0" class="px-2 py-6 text-center text-sm text-muted-foreground">
                {{ __('No countries found.') }}
            </li>
        </ul>
    </div>

    {{-- National number input --}}
    <input
        type="tel"
        inputmode="tel"
        data-slot="phone-input-number"
        x-ref="numberInput"
        x-model="number"
        :placeholder="@js($placeholder)"
        @if ($hasBagError) aria-invalid="true" @endif
        @if (filled($describedBy)) aria-describedby="{{ $describedBy }}" @endif
        autocomplete="tel-national"
        class="h-10 min-w-0 grow rounded-e-md bg-background px-3 text-sm text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
    />
</div>
resources/js/ui/phone-input.js JS
/**
 * Phone Input behavior.
 *
 * Bundles a small dataset of ~30 common countries, each with an inline SVG
 * flag. The country selector opens a filterable listbox; selecting a country
 * updates the dial prefix. The
 * national-number input accepts free-form digits. `e164()` returns the
 * E.164-style value: dial code + stripped national digits.
 */
/**
 * Flag artwork: circle-flags by HatScripts (MIT, https://github.com/HatScripts/circle-flags),
 * simplified round renditions inlined so the control ships self-contained. Each
 * mask id is namespaced per country so several flags can share one document.
 */
const FLAGS = {
    us: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-us-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-us-a)"><path fill="#eee" d="M256 0h256v64l-32 32 32 32v64l-32 32 32 32v64l-32 32 32 32v64l-256 32L0 448v-64l32-32-32-32v-64z"/><path fill="#d80027" d="M224 64h288v64H224Zm0 128h288v64H256ZM0 320h512v64H0Zm0 128h512v64H0Z"/><path fill="#0052b4" d="M0 0h256v256H0Z"/><path fill="#eee" d="m187 243 57-41h-70l57 41-22-67zm-81 0 57-41H93l57 41-22-67zm-81 0 57-41H12l57 41-22-67zm162-81 57-41h-70l57 41-22-67zm-81 0 57-41H93l57 41-22-67zm-81 0 57-41H12l57 41-22-67Zm162-82 57-41h-70l57 41-22-67Zm-81 0 57-41H93l57 41-22-67zm-81 0 57-41H12l57 41-22-67Z"/></g></svg>',
    gb: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-gb-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-gb-a)"><path fill="#eee" d="m0 0 8 22-8 23v23l32 54-32 54v32l32 48-32 48v32l32 54-32 54v68l22-8 23 8h23l54-32 54 32h32l48-32 48 32h32l54-32 54 32h68l-8-22 8-23v-23l-32-54 32-54v-32l-32-48 32-48v-32l-32-54 32-54V0l-22 8-23-8h-23l-54 32-54-32h-32l-48 32-48-32h-32l-54 32L68 0H0z"/><path fill="#0052b4" d="M336 0v108L444 0Zm176 68L404 176h108zM0 176h108L0 68ZM68 0l108 108V0Zm108 512V404L68 512ZM0 444l108-108H0Zm512-108H404l108 108Zm-68 176L336 404v108z"/><path fill="#d80027" d="M0 0v45l131 131h45L0 0zm208 0v208H0v96h208v208h96V304h208v-96H304V0h-96zm259 0L336 131v45L512 0h-45zM176 336 0 512h45l131-131v-45zm160 0 176 176v-45L381 336h-45z"/></g></svg>',
    ca: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-ca-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-ca-a)"><path fill="#d80027" d="M0 0v512h144l112-64 112 64h144V0H368L256 64 144 0Z"/><path fill="#eee" d="M144 0h224v512H144Z"/><path fill="#d80027" d="m301 289 44-22-22-11v-22l-45 22 23-44h-23l-22-34-22 33h-23l23 45-45-22v22l-22 11 45 22-12 23h45v33h22v-33h45z"/></g></svg>',
    au: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-au-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-au-a)"><path fill="#0052b4" d="M0 0h512v512H0z"/><path fill="#eee" d="m154 300 14 30 32-8-14 30 25 20-32 7 1 33-26-21-26 21 1-33-33-7 26-20-14-30 32 8zm222-27h47l-38 27 15-44 14 44zm7-162 7 15 16-4-7 15 12 10-15 3v17l-13-11-13 11v-17l-15-3 12-10-7-15 16 4zm57 67 7 15 16-4-7 15 12 10-15 3v16l-13-10-13 11v-17l-15-3 12-10-7-15 16 4zm-122 22 7 15 16-4-7 15 12 10-15 3v16l-13-10-13 11v-17l-15-3 12-10-7-15 16 4zm65 156 7 15 16-4-7 15 12 10-15 3v17l-13-11-13 11v-17l-15-3 12-10-7-15 16 4zM0 0v32l32 32L0 96v160h32l32-32 32 32h32v-83l83 83h45l-8-16 8-15v-14l-83-83h83V96l-32-32 32-32V0H96L64 32 32 0Z"/><path fill="#d80027" d="M32 0v32H0v64h32v160h64V96h160V32H96V0Zm96 128 128 128v-31l-97-97z"/></g></svg>',
    de: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-de-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-de-a)"><path fill="#ffda44" d="m0 345 256.7-25.5L512 345v167H0z"/><path fill="#d80027" d="m0 167 255-23 257 23v178H0z"/><path fill="#333" d="M0 0h512v167H0z"/></g></svg>',
    fr: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-fr-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-fr-a)"><path fill="#eee" d="M167 0h178l25.9 252.3L345 512H167l-29.8-253.4z"/><path fill="#0052b4" d="M0 0h167v512H0z"/><path fill="#d80027" d="M345 0h167v512H345z"/></g></svg>',
    es: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-es-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-es-a)"><path fill="#ffda44" d="m0 128 256-32 256 32v256l-256 32L0 384Z"/><path fill="#eee" d="M196 168q-11 1-15 11l-5-1q-15 1-16 16c-1 15 7 16 16 16q11 0 15-11a16 16 0 0 0 17-4 16 16 0 0 0 17 4 16 16 0 1 0 10-20 16 16 0 0 0-27-5q-4-6-12-6m0 8q8 1 8 8 0 8-8 8-7 0-8-8 1-7 8-8m24 0q8 1 8 8 0 8-8 8-7 0-8-8 1-7 8-8m-44 10 4 1 4 8q-1 7-8 7-9 0-8-8 1-7 8-8m64 0q8 1 8 8 0 8-8 8-7 0-8-7l4-8zm-112 38v80h16v-80zm80 0v40c-26 0-48 14-48 32s22 32 48 32 48-14 48-32v-72zm64 0v80h16v-80z"/><path fill="#ff9811" d="M200 160h16v32h-16z"/><path fill="#d80027" d="M0 0v128h512V0zm208 184c-22 0-40 11-40 24l8 8h64l8-8c0-13-18-24-40-24m-72 8a8 8 0 0 0-8 8v8a8 8 0 1 0 16 0v-8a8 8 0 0 0-8-8m144 0a8 8 0 0 0-8 8v8a8 8 0 1 0 16 0v-8a8 8 0 0 0-8-8m-120 32v24h-38a4 4 0 0 0-4 4 4 4 0 0 0 4 4h38v40a24 24 0 0 0 24 24 24 24 0 0 0 24-24 24 24 0 0 0 24 24 24 24 0 0 0 24-24v-24h-48v-48zm72 8a10 10 0 0 0-10 10v12a10 10 0 1 0 20 0v-12a10 10 0 0 0-10-10m24 16v8h38a4 4 0 0 0 4-4 4 4 0 0 0-4-4zm-134 24a4 4 0 0 0-4 4 4 4 0 0 0 4 4h28a4 4 0 0 0 4-4 4 4 0 0 0-4-4zm144 0a4 4 0 0 0-4 4 4 4 0 0 0 4 4h28a4 4 0 0 0 4-4 4 4 0 0 0-4-4zM0 384v128h512V384z"/><path fill="#ffda44" d="M186 196a6 6 0 0 0-6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0-6-6m22 0a6 6 0 0 0-6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0-6-6m22 0a6 6 0 0 0-6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0-6-6"/><path fill="#ff9811" d="M128 208a8 8 0 1 0 0 16h16a8 8 0 1 0 0-16zm144 0a8 8 0 1 0 0 16h16a8 8 0 1 0 0-16zm-96 8v8h64v-8zm-8 16v8h8v16h-8v8h32v-8h-8v-16h8v-8zm-8 40v24q1 12 9 19v-43zm19 0v47h10v-47zm20 0v43q9-7 9-19v-24zm-71 32a8 8 0 1 0 0 16h16a8 8 0 1 0 0-16zm144 0a8 8 0 1 0 0 16h16a8 8 0 1 0 0-16z"/><path fill="#338af3" d="M208 256a16 16 0 0 0-16 16 16 16 0 0 0 16 16 16 16 0 0 0 16-16 16 16 0 0 0-16-16m-80 64a8 8 0 1 0 0 16h16a8 8 0 1 0 0-16zm144 0a8 8 0 1 0 0 16h16a8 8 0 1 0 0-16z"/></g></svg>',
    it: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-it-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-it-a)"><path fill="#eee" d="M167 0h178l25.9 252.3L345 512H167l-29.8-253.4z"/><path fill="#6da544" d="M0 0h167v512H0z"/><path fill="#d80027" d="M345 0h167v512H345z"/></g></svg>',
    nl: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-nl-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-nl-a)"><path fill="#eee" d="m0 167 253.8-19.3L512 167v178l-254.9 32.3L0 345z"/><path fill="#a2001d" d="M0 0h512v167H0z"/><path fill="#0052b4" d="M0 345h512v167H0z"/></g></svg>',
    se: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-se-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-se-a)"><path fill="#0052b4" d="M0 0h133.6l35.3 16.7L200.3 0H512v222.6l-22.6 31.7 22.6 35.1V512H200.3l-32-19.8-34.7 19.8H0V289.4l22.1-33.3L0 222.6z"/><path fill="#ffda44" d="M133.6 0v222.6H0v66.8h133.6V512h66.7V289.4H512v-66.8H200.3V0z"/></g></svg>',
    no: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-no-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-no-a)"><path fill="#d80027" d="M0 0h100.2l66.1 53.5L233.7 0H512v189.3L466.3 257l45.7 65.8V512H233.7l-68-50.7-65.5 50.7H0V322.8l51.4-68.5-51.4-65z"/><path fill="#eee" d="M100.2 0v189.3H0v33.4l24.6 33L0 289.5v33.4h100.2V512h33.4l30.6-26.3 36.1 26.3h33.4V322.8H512v-33.4l-24.6-33.7 24.6-33v-33.4H233.7V0h-33.4l-33.8 25.3L133.6 0z"/><path fill="#0052b4" d="M133.6 0v222.7H0v66.7h133.6V512h66.7V289.4H512v-66.7H200.3V0z"/></g></svg>',
    dk: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-dk-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-dk-a)"><path fill="#d80027" d="M0 0h133.6l32.7 20.3 34-20.3H512v222.6L491.4 256l20.6 33.4V512H200.3l-31.7-20.4-35 20.4H0V289.4l29.4-33L0 222.7z"/><path fill="#eee" d="M133.6 0v222.6H0v66.8h133.6V512h66.7V289.4H512v-66.8H200.3V0h-66.7z"/></g></svg>',
    ie: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-ie-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-ie-a)"><path fill="#eee" d="M167 0h178l25.9 252.3L345 512H167l-29.8-253.4z"/><path fill="#6da544" d="M0 0h167v512H0z"/><path fill="#ff9811" d="M345 0h167v512H345z"/></g></svg>',
    pt: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-pt-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-pt-a)"><path fill="#6da544" d="M0 512h167l37.9-260.3L167 0H0z"/><path fill="#d80027" d="M512 0H167v512h345z"/><circle cx="167" cy="256" r="89" fill="#ffda44"/><path fill="#d80027" d="M116.9 211.5V267a50 50 0 1 0 100.1 0v-55.6H117z"/><path fill="#eee" d="M167 283.8c-9.2 0-16.7-7.5-16.7-16.7V245h33.4v22c0 9.2-7.5 16.7-16.7 16.7z"/></g></svg>',
    ch: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-ch-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-ch-a)"><path fill="#d80027" d="M0 0h512v512H0z"/><path fill="#eee" d="M389.6 211.5h-89v-89h-89.1v89h-89v89h89v89h89v-89h89z"/></g></svg>',
    at: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-at-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-at-a)"><path fill="#d80027" d="M0 0h512v167l-23.2 89.7L512 345v167H0V345l29.4-89L0 167z"/><path fill="#eee" d="M0 167h512v178H0z"/></g></svg>',
    be: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-be-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-be-a)"><path fill="#333" d="M0 0h167l38.2 252.6L167 512H0z"/><path fill="#d80027" d="M345 0h167v512H345l-36.7-256z"/><path fill="#ffda44" d="M167 0h178v512H167z"/></g></svg>',
    pl: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-pl-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-pl-a)"><path fill="#d80027" d="m0 256 256.4-44.3L512 256v256H0z"/><path fill="#eee" d="M0 0h512v256H0z"/></g></svg>',
    br: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-br-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-br-a)"><path fill="#6da544" d="M0 0h512v512H0z"/><path fill="#ffda44" d="M256 100.2 467.5 256 256 411.8 44.5 256z"/><path fill="#eee" d="M174.2 221a87 87 0 0 0-7.2 36.3l162 49.8a88.5 88.5 0 0 0 14.4-34c-40.6-65.3-119.7-80.3-169.1-52z"/><path fill="#0052b4" d="M255.7 167a89 89 0 0 0-41.9 10.6 89 89 0 0 0-39.6 43.4 181.7 181.7 0 0 1 169.1 52.2 89 89 0 0 0-9-59.4 89 89 0 0 0-78.6-46.8zM212 250.5a149 149 0 0 0-45 6.8 89 89 0 0 0 10.5 40.9 89 89 0 0 0 120.6 36.2 89 89 0 0 0 30.7-27.3A151 151 0 0 0 212 250.5z"/></g></svg>',
    mx: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-mx-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-mx-a)"><path fill="#eee" d="M144 0h223l33 256-33 256H144l-32-256z"/><path fill="#d80027" d="M368 0h144v512H368z"/><path fill="#751a46" d="M256 174c22 11 12 33 11 34l-2-4c-4-15-13-33-31-18v11q10 1 11 11-11 12-4 26l4 8-13 23 29-7 18 18v-11l11 11 23-11-35-21-2-13c22-2 34 4 51 29 9-83-45-86-64-86Z"/><path fill="#6da544" d="M209 183q-5 4-4 12 1 11 10 15c3 2 8 0 10 3 3 3-2 5-4 6q-8 5-9 14 3 10 12 15c2 2 7 4 5 7q-4 2-9-2-12-6-19-19c-2-3-1-10-7-10-7 2-4 10-2 14q8 14 21 23 9 8 20 3 10-6 4-17c-3-6-11-8-14-14-2-3 2-4 4-6q8-4 9-11-2-13-14-15-6 1-7-6c-1-3 3-7 0-11q-2-3-6-1"/><path fill="#496e2d" d="M0 0v512h144V0zm164 235a5 5 0 0 0-5 5 97 97 0 0 0 194 0 5 5 0 0 0-5-5 5 5 0 0 0-5 5 87 87 0 1 1-174 0 5 5 0 0 0-5-5m35 25-4 1q-3 4 1 8 23 21 54 24v17h12v-17q31-3 54-24 4-4 1-8-4-3-8 0a78 78 0 0 1-106 0z"/><path fill="#338af3" d="M256 316q-21 0-40-13l6-9c20 13 48 13 68 0l7 9q-18 13-41 13"/><rect width="34" height="22" x="239" y="299" fill="#ff9811" rx="11" ry="11"/><path fill="#ffda44" d="m234 186-12 11v11l18-9q4-3 1-7zm-62 79a10 10 0 0 0-10 10 10 10 0 0 0 10 10 10 10 0 0 0 10-10 10 10 0 0 0-10-10m169 0a10 10 0 0 0-10 10 10 10 0 0 0 10 10 10 10 0 0 0 10-10 10 10 0 0 0-10-10m-69 4-16 8v5l15-1 4-9zm-83 23a10 10 0 0 0-10 10 10 10 0 0 0 10 10 10 10 0 0 0 10-10 10 10 0 0 0-10-10m135 0a10 10 0 0 0-10 10 10 10 0 0 0 10 10 10 10 0 0 0 10-10 10 10 0 0 0-10-10m-108 21a10 10 0 0 0-10 10 10 10 0 0 0 10 10 10 10 0 0 0 10-10 10 10 0 0 0-10-10m81 0a10 10 0 0 0-10 10 10 10 0 0 0 10 10 10 10 0 0 0 10-10 10 10 0 0 0-10-10"/></g></svg>',
    ar: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-ar-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-ar-a)"><path fill="#338af3" d="M0 0h512v144.7L488 256l24 111.3V512H0V367.3L26 256 0 144.7z"/><path fill="#eee" d="M0 144.7h512v222.6H0z"/><path fill="#ffda44" d="m332.4 256-31.2 14.7 16.7 30.3-34-6.5-4.2 34.3-23.7-25.2-23.6 25.2-4.3-34.3-34 6.5 16.6-30.3-31.2-14.7 31.3-14.7L194 211l34 6.5 4.3-34.3 23.6 25.2 23.6-25.2 4.4 34.3 34-6.5-16.7 30.3z"/></g></svg>',
    in: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-in-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-in-a)"><path fill="#eee" d="m0 160 256-32 256 32v192l-256 32L0 352z"/><path fill="#ff9811" d="M0 0h512v160H0Z"/><path fill="#6da544" d="M0 352h512v160H0Z"/><circle cx="256" cy="256" r="72" fill="#0052b4"/><circle cx="256" cy="256" r="48" fill="#eee"/><circle cx="256" cy="256" r="24" fill="#0052b4"/></g></svg>',
    cn: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-cn-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-cn-a)"><path fill="#d80027" d="M0 0h512v512H0z"/><path fill="#ffda44" d="m140.1 155.8 22.1 68h71.5l-57.8 42.1 22.1 68-57.9-42-57.9 42 22.2-68-57.9-42.1H118zm163.4 240.7-16.9-20.8-25 9.7 14.5-22.5-16.9-20.9 25.9 6.9 14.6-22.5 1.4 26.8 26 6.9-25.1 9.6zm33.6-61 8-25.6-21.9-15.5 26.8-.4 7.9-25.6 8.7 25.4 26.8-.3-21.5 16 8.6 25.4-21.9-15.5zm45.3-147.6L370.6 212l19.2 18.7-26.5-3.8-11.8 24-4.6-26.4-26.6-3.8 23.8-12.5-4.6-26.5 19.2 18.7zm-78.2-73-2 26.7 24.9 10.1-26.1 6.4-1.9 26.8-14.1-22.8-26.1 6.4 17.3-20.5-14.2-22.7 24.9 10.1z"/></g></svg>',
    jp: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-jp-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-jp-a)"><path fill="#eee" d="M0 0h512v512H0z"/><circle cx="256" cy="256" r="111.3" fill="#d80027"/></g></svg>',
    kr: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-kr-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-kr-a)"><path fill="#eee" d="M0 0h512v512H0Z"/><path fill="#333" d="m350 335 24-24 16 16-24 23zm-39 39 24-24 15 16-23 24zm87 8 23-24 16 16-24 24zm-40 39 24-23 16 15-24 24Zm16-63 24-23 15 15-23 24zm-39 40 23-24 16 16-24 23zm63-221-63-63 15-15 64 63zm-63-15-24-24 16-16 23 24zm39 39-24-24 16-15 24 23zm8-87-24-23 16-16 24 24Zm39 40-23-24 15-16 24 24ZM91 358l63 63-16 16-63-63zm63 16 23 24-15 15-24-23zm-40-39 24 23-16 16-23-24zm24-24 63 63-16 16-63-63zm16-220-63 63-16-16 63-63zm23 23-63 63-15-16 63-63zm24 24-63 63-16-16 63-63z"/><path fill="#d80027" d="M319 319 193 193a89 89 0 1 1 126 126z"/><path fill="#0052b4" d="M319 319a89 89 0 1 1-126-126z"/><circle cx="224.5" cy="224.5" r="44.5" fill="#d80027"/><circle cx="287.5" cy="287.5" r="44.5" fill="#0052b4"/></g></svg>',
    sg: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-sg-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-sg-a)"><path fill="#d80027" d="M0 0h512v256l-256 32L0 256Z"/><path fill="#eee" d="M200 56a78 78 0 1 0 17 154 78 78 0 0 1 0-152q-8-2-17-2m56 5-5 17h-18l14 11-5 17 14-11 15 11-6-17 15-11h-18zm-43 34-6 17h-18l15 10-6 17 15-10 14 10-5-17 14-10h-18zm86 0-5 17h-18l14 10-5 17 14-10 15 10-6-17 15-10h-18zm-70 50-5 17h-18l14 10-5 17 14-10 15 10-6-17 15-10h-18zm54 0-6 17h-18l15 10-6 17 15-10 14 10-5-17 14-10h-18ZM0 256v256h512V256Z"/></g></svg>',
    za: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-za-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-za-a)"><path fill="#eee" d="m0 0 192 256L0 512h47l465-189v-34l-32-33 32-33v-34L47 0Z"/><path fill="#333" d="M0 142v228l140-114z"/><path fill="#ffda44" d="M192 256 0 95v47l114 114L0 370v47z"/><path fill="#6da544" d="M512 223H223L0 0v94l161 162L0 418v94l223-223h289z"/><path fill="#d80027" d="M512 0H47l189 189h276z"/><path fill="#0052b4" d="M512 512H47l189-189h276z"/></g></svg>',
    ng: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-ng-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-ng-a)"><path fill="#6da544" d="M0 0v512h160l96-64 96 64h160V0H352l-96 64-96-64Z"/><path fill="#eee" d="M160 0h192v512H160Z"/></g></svg>',
    ae: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-ae-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-ae-a)"><path fill="#a2001d" d="M0 0h167l52.3 252L167 512H0z"/><path fill="#eee" d="m167 167 170.8-44.6L512 167v178l-173.2 36.9L167 345z"/><path fill="#6da544" d="M167 0h345v167H167z"/><path fill="#333" d="M167 345h345v167H167z"/></g></svg>',
    nz: '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 512 512"><mask id="brok-flag-nz-a"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#brok-flag-nz-a)"><path fill="#0052b4" d="M256 0h256v512H0V256Z"/><path fill="#eee" d="M0 0v32l32 32L0 96v160h32l32-32 32 32h32v-83l83 83h45l-8-16 8-15v-14l-83-83h83V96l-32-32 32-32V0H96L64 32 32 0Zm382 92-11 35h-37l30 21-12 35 30-22 30 22-12-35 30-21h-37l-11-35Zm61 72-11 35h-37l30 21-11 35 29-21 30 21-12-35 30-21h-37Zm-123 10-11 35h-37l30 22-11 35 29-22 30 22-11-35 29-22h-36zm59 130-11 35h-37l30 21-11 35 29-21 30 21-11-35 29-21h-36z"/><path fill="#d80027" d="M32 0v32H0v64h32v160h64V96h160V32H96V0Zm96 128 128 128v-31l-97-97zm251 201-5 18h-19l15 10-6 18 15-11 15 11-5-18 14-10h-18Zm-59-129-5 17h-19l15 11-6 17 15-11 15 11-6-17 15-11h-18l-6-17zm123-11-6 18h-18l15 11-6 17 15-11 15 11-6-17 15-11h-18l-6-18zm-61-72-6 17h-18l15 11-6 17 15-10 15 10-6-17 15-11h-18z"/></g></svg>',
};

document.addEventListener('alpine:init', () => {
    window.Alpine.data('uiPhoneInput', (config = {}) => ({
        open: false,
        query: '',
        activeIndex: 0,
        number: config.number ?? '',
        countries: [
            { code: 'US', dial: '+1',   name: 'United States',        flag: FLAGS.us },
            { code: 'GB', dial: '+44',  name: 'United Kingdom',        flag: FLAGS.gb },
            { code: 'CA', dial: '+1',   name: 'Canada',                flag: FLAGS.ca },
            { code: 'AU', dial: '+61',  name: 'Australia',             flag: FLAGS.au },
            { code: 'DE', dial: '+49',  name: 'Germany',               flag: FLAGS.de },
            { code: 'FR', dial: '+33',  name: 'France',                flag: FLAGS.fr },
            { code: 'ES', dial: '+34',  name: 'Spain',                 flag: FLAGS.es },
            { code: 'IT', dial: '+39',  name: 'Italy',                 flag: FLAGS.it },
            { code: 'NL', dial: '+31',  name: 'Netherlands',           flag: FLAGS.nl },
            { code: 'SE', dial: '+46',  name: 'Sweden',                flag: FLAGS.se },
            { code: 'NO', dial: '+47',  name: 'Norway',                flag: FLAGS.no },
            { code: 'DK', dial: '+45',  name: 'Denmark',               flag: FLAGS.dk },
            { code: 'IE', dial: '+353', name: 'Ireland',               flag: FLAGS.ie },
            { code: 'PT', dial: '+351', name: 'Portugal',              flag: FLAGS.pt },
            { code: 'CH', dial: '+41',  name: 'Switzerland',           flag: FLAGS.ch },
            { code: 'AT', dial: '+43',  name: 'Austria',               flag: FLAGS.at },
            { code: 'BE', dial: '+32',  name: 'Belgium',               flag: FLAGS.be },
            { code: 'PL', dial: '+48',  name: 'Poland',                flag: FLAGS.pl },
            { code: 'BR', dial: '+55',  name: 'Brazil',                flag: FLAGS.br },
            { code: 'MX', dial: '+52',  name: 'Mexico',                flag: FLAGS.mx },
            { code: 'AR', dial: '+54',  name: 'Argentina',             flag: FLAGS.ar },
            { code: 'IN', dial: '+91',  name: 'India',                 flag: FLAGS.in },
            { code: 'CN', dial: '+86',  name: 'China',                 flag: FLAGS.cn },
            { code: 'JP', dial: '+81',  name: 'Japan',                 flag: FLAGS.jp },
            { code: 'KR', dial: '+82',  name: 'South Korea',           flag: FLAGS.kr },
            { code: 'SG', dial: '+65',  name: 'Singapore',             flag: FLAGS.sg },
            { code: 'ZA', dial: '+27',  name: 'South Africa',          flag: FLAGS.za },
            { code: 'NG', dial: '+234', name: 'Nigeria',               flag: FLAGS.ng },
            { code: 'AE', dial: '+971', name: 'United Arab Emirates',  flag: FLAGS.ae },
            { code: 'NZ', dial: '+64',  name: 'New Zealand',           flag: FLAGS.nz },
        ],
        country: null,

        init() {
            this.country =
                this.countries.find((c) => c.code === (config.defaultCountry || 'US')) ||
                this.countries[0];
        },

        filtered() {
            const q = this.query.toLowerCase();
            if (!q) {
                return this.countries;
            }
            return this.countries.filter(
                (c) => c.name.toLowerCase().includes(q) || c.dial.includes(q)
            );
        },

        /**
         * Open the popup, park the active option on the current country and
         * move DOM focus to the filter input — the listbox itself stays
         * unfocused and is driven through `aria-activedescendant`.
         */
        openList(seed = '') {
            this.open = true;
            this.query = seed;
            const list = this.filtered();
            const current = list.findIndex((c) => c.code === this.country?.code);
            this.activeIndex = current >= 0 ? current : 0;
            this.$nextTick(() => {
                this.$refs.filter?.focus();
                this.scrollActiveIntoView();
            });
        },

        /** Close the popup; `restoreFocus` returns focus to the trigger. */
        close(restoreFocus = true) {
            if (!this.open) {
                return;
            }
            this.open = false;
            this.query = '';
            this.activeIndex = 0;
            if (restoreFocus) {
                this.$nextTick(() => this.$refs.trigger?.focus());
            }
        },

        /** Move the active option by `delta`, wrapping at both ends. */
        move(delta) {
            const count = this.filtered().length;
            if (count === 0) {
                this.activeIndex = -1;
                return;
            }
            this.activeIndex = (this.activeIndex + delta + count) % count;
            this.scrollActiveIntoView();
        },

        moveTo(index) {
            const count = this.filtered().length;
            if (count === 0) {
                this.activeIndex = -1;
                return;
            }
            this.activeIndex = Math.min(Math.max(index, 0), count - 1);
            this.scrollActiveIntoView();
        },

        scrollActiveIntoView() {
            const option = this.$refs.list?.querySelector(
                `[data-index="${this.activeIndex}"]`
            );
            option?.scrollIntoView({ block: 'nearest' });
        },

        chooseActive() {
            const choice = this.filtered()[this.activeIndex];
            if (choice) {
                this.select(choice);
            }
        },

        /** Keyboard model for the filter input while the listbox is open. */
        onKey(event) {
            switch (event.key) {
                case 'ArrowDown':
                    event.preventDefault();
                    this.move(1);
                    break;
                case 'ArrowUp':
                    event.preventDefault();
                    this.move(-1);
                    break;
                case 'Home':
                    event.preventDefault();
                    this.moveTo(0);
                    break;
                case 'End':
                    event.preventDefault();
                    this.moveTo(this.filtered().length - 1);
                    break;
                case 'Enter':
                    event.preventDefault();
                    this.chooseActive();
                    break;
                case 'Escape':
                    event.preventDefault();
                    this.close();
                    break;
                case 'Tab':
                    this.close(false);
                    break;
                default:
                    break;
            }
        },

        /**
         * Trigger keyboard model: Enter/Space/Arrow open the popup, and any
         * printable character opens it with that character as the filter
         * (type-ahead).
         */
        onTriggerKey(event) {
            if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
                event.preventDefault();
                this.openList();
                return;
            }

            if (event.key === 'Escape') {
                this.close(false);
                return;
            }

            const printable =
                event.key.length === 1 &&
                !event.ctrlKey &&
                !event.metaKey &&
                !event.altKey &&
                event.key !== ' ';

            if (printable) {
                event.preventDefault();
                this.openList(event.key);
            }
        },

        select(c) {
            this.country = c;
            this.open = false;
            this.query = '';
            this.activeIndex = 0;
            this.$nextTick(() => this.$refs.numberInput?.focus());
        },

        e164() {
            return (this.country?.dial || '') + this.number.replace(/[^0-9]/g, '');
        },
    }));
});

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.2.3
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0