npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

geo-select-core

v0.4.10

Published

Vanilla JS core for interactive world map region selection

Readme

geo-select-core

Vanilla JS core for interactive world-region selection. The core accepts a GeoJSON FeatureCollection and renders a simple SVG map using an equirectangular projection.

Product intent

geo-select is designed to replace cumbersome country and region <select> fields in production signup, onboarding, and billing-address flows. Its north star is not a decorative map: it is an accessible, locale-aware form control that adds spatial interaction while returning stable ISO-based values to the host application.

Development follows these principles:

  • Preserve standard form semantics. Map interaction must integrate cleanly with values, validation, disabled states, resets, and input/change events.
  • Keep the map, search results, and an accessible list synchronized. The map must never be the only way to select a small country or territory.
  • Show names appropriate for the user's locale while exposing stable ISO identifiers to application code.
  • Treat location inference as a suggestion only. Never silently commit a country on the user's behalf.
  • Stay framework- and payment-provider-independent. The package selects billing geography; it does not collect card data or replace payment compliance checks.
  • Keep boundary and subdivision datasets optional so form-heavy applications can load only the detail they need.

The current release provides the country and territory selection core, bindFormField() for native form value and validation integration, bindSearchList() for an accessible map-independent search path, optional ISO 3166-2 subdivision loading and rendering, and locale-aware labels/search. Mobile data loading and deeper subdivision navigation remain future priorities.

When dataUrl is used, initialization is observable and recoverable. Await whenReady(), inspect getStatus() ('loading', 'ready', or 'error'), and use retry() after a network failure. onError receives the original Error; the container exposes an alert message and data-geo-select-status="error" until a retry succeeds.

The SVG map is responsive by default and preserves its configured aspect ratio inside a narrow form layout. Small regions receive transparent touch hit targets sized by touchTargetSize (24px by default), while the visible country paths remain the accessible keyboard controls. Set touchTargetSize: 0 to opt out when the host supplies its own interaction layer.

Mouse-wheel zoom follows the pointer position, and an enlarged map can be panned by dragging. Host controls for keyboard and touch users can call zoomIn(), zoomOut(), and resetZoom(); getZoom() returns the current scale. Configure maxZoom (8 by default), zoomStep (0.25), or set zoom: false to disable map navigation. onZoom can keep a host-provided scale indicator synchronized.

const core = new GeoCore(container, {
  data: worldData,
  maxZoom: 6,
  onZoom: scale => {
    zoomValue.textContent = `${Number(scale.toFixed(2))}x`;
  }
});

zoomInButton.addEventListener('click', () => core.zoomIn());
zoomOutButton.addEventListener('click', () => core.zoomOut());
resetZoomButton.addEventListener('click', () => core.resetZoom());

Product availability can be constrained without changing the supplied GeoJSON. allowedCountries and allowedSubdivisions accept stable ISO-2, ISO-3, feature IDs, or ISO 3166-2 codes; the corresponding excluded* list always wins. The same policy is applied to the map, search results, direct selection, and loaded billing subdivisions.

const core = new GeoCore(container, {
  data: worldData,
  allowedCountries: ['US', 'CA', 'GB'],
  excludedCountries: ['GB'],
  allowedSubdivisions: ['US-CA', 'US-NY']
});

These lists are product availability controls, not sanctions, tax, payment, or compliance validation. The host application remains responsible for current policy decisions and provider checks.

Install

npm install geo-select-core

Quick start

<div id="map"></div>
<script type="module">
import GeoCore from 'geo-select-core';
import worldData from 'geo-select-core/world';

const container = document.getElementById('map');
const core = new GeoCore(container, { data: worldData });
const unsubscribe = core.on('select', r => console.log('selected', r));

core.select('KR');
console.log(core.getSelected());
console.log(core.search('Korea'));
core.setContinent('Asia');
console.log(core.getContinents());
core.clear();
unsubscribe();
</script>

For a remote dataset, keep the form usable while the map loads and provide a retry action in the host UI:

const core = new GeoCore(container, {
  dataUrl: '/geo/world.geo.json',
  onError: error => console.error('Country map unavailable', error)
});

await core.whenReady();
if (core.getStatus() === 'error') {
  retryButton.addEventListener('click', () => core.retry(), { once: true });
}

Pass a locale to choose a dataset's localized label fields such as name_ko or name_es. Search is case-insensitive and diacritic-tolerant, and aliases lets a host application add product-specific names without changing its GeoJSON. direction: 'auto' applies RTL direction for Arabic, Persian, Hebrew, and Urdu locales.

const core = new GeoCore(container, {
  data: worldData,
  locale: 'ko-KR',
  direction: 'auto',
  aliases: { US: ['USA', 'United States'] }
});

core.search('cote d ivoire');
core.select('USA');
console.log(core.getSelected()?.country?.iso2); // stable machine value: US

Locale and aliases affect labels and lookup only. Form bindings continue to submit ISO-2/ISO-3 or feature IDs, so billing providers receive stable machine values rather than translated names.

To connect the selected ISO value to a signup or billing form, bind a named input. The binding keeps the input value, input/change events, required validation, programmatic selection, and form reset synchronized:

<form id="signup-form">
  <input name="billingCountry" aria-label="Billing country" />
</form>
const countryInput = document.querySelector('[name="billingCountry"]');
const binding = core.bindFormField(countryInput, {
  valueKey: 'iso2',
  required: true
});

core.select('KR');
console.log(new FormData(document.querySelector('#signup-form')).get('billingCountry'));

binding.setDisabled(true);
binding.destroy();

Use a text input or a visually hidden text input when browser constraint validation is required; native type="hidden" controls are excluded from constraint validation. valueKey accepts iso2 (default), iso3, or id.

For a map-independent accessible path, connect a search input to a listbox. The list opens on focus, supports Arrow keys and Enter, and stays synchronized when a region is selected on the map or cleared through the core API:

<input id="country-search" type="search" aria-label="Country" />
<ul id="country-results"></ul>
const searchBinding = core.bindSearchList(
  document.querySelector('#country-search'),
  document.querySelector('#country-results'),
  { listLabel: 'Country results' }
);

core.setContinent('Asia');
core.select('KR');
searchBinding.destroy();

bindSearchList() renders safe text nodes from the current GeoJSON results and does not silently select a result while typing. Style .geo-select-search-option-active in the host application to match its active keyboard option state.

Optional ISO 3166-2 subdivisions

Country data is bundled, but administrative subdivisions are intentionally injected by the host application so each product can choose a licensed and current dataset. The recommended GeoJSON contract for first-level billing regions is:

{
  "type": "Feature",
  "properties": {
    "parentIso2": "KR",
    "iso3166_2": "KR-11",
    "name": "Seoul",
    "level": "admin1"
  }
}

Load only the selected country's data, then bind a second input/listbox for a billing state, province, or region:

await core.loadSubdivisions('KR', {
  dataUrl: '/licensed-data/kr-admin1.geojson'
});

const regionBinding = core.bindFormField(
  document.querySelector('[name="billingRegion"]'),
  { scope: 'subdivision', valueKey: 'id', required: true }
);
const regionSearch = core.bindSearchList(
  document.querySelector('#billing-region-search'),
  document.querySelector('#billing-region-results'),
  { scope: 'subdivision', listLabel: 'Billing region results' }
);

core.selectSubdivision('KR-11');
console.log(core.getSelectedSubdivision()?.subdivision);

loadSubdivisions() filters by parentIso2/parentIso3 and accepts code aliases such as ISO_3166_2 or a configured codeProperty. A code prefixed with the parent country, such as KR-11, is also recognized. Pass allowUnscoped: true only when the supplied file is already scoped to the selected country. The package does not bundle subdivision boundaries or claim a dataset's license, freshness, or billing eligibility; the host application remains responsible for those choices.

When subdivisions are loaded, the SVG temporarily replaces the country layer with a scoped, keyboard-accessible subdivision layer. Clicking or keyboard-selecting a subdivision updates getSelectedSubdivision(), subdivision form bindings, and subdivision search bindings. Selecting a different country removes that layer and restores the country map, so country and billing-region state remain distinct.

CommonJS projects can load the same default export:

const GeoCore = require('geo-select-core');

The package includes ESM, CommonJS, UMD, and TypeScript declaration outputs. Importing geo-select-core/world loads the optional Natural Earth 1:50m dataset with 242 country and territory boundaries. The main core entry stays small, and custom GeoJSON can still be supplied through data or dataUrl.

select() accepts an exact country or region code/name and returns the selected region, or null when no match exists. search() returns every matching region while synchronizing map highlights. clear() resets both selection and search highlights; reset() remains available as an alias.

setContinent() filters visible regions, search results, and selectable map paths using a continent, CONTINENT, CONTINENT_UN, or REGION_UN GeoJSON property. Pass null or an empty string to clear the filter. getContinents() returns the available values from the loaded data.

Rendered regions are keyboard accessible buttons: focus a visible region and press Enter or Space to select it. Hidden regions are removed from the tab order, and selected regions expose aria-pressed="true".

With the bundled world data, each returned Region contains a normalized country object with ISO-2/3 and numeric codes, official and Korean names, continent and subregion, capitals and coordinates, population, GDP, economy, income group, and Wikidata ID. centroid uses Natural Earth's cartographic label position when available and falls back to an area-weighted centroid for custom Polygon and MultiPolygon data.

Country boundaries and metadata are derived from Natural Earth public-domain data. See DATA_LICENSE.md for source details.

Notes

  • 초기 버전은 간단한 projection(평면)과 기본 렌더링만 제공합니다.
  • 고해상도 렌더링, topojson 지원, 경계 smoothing 등은 향후 개선 예정입니다.