@creative-locator/headless
v2.0.2
Published
Framework-agnostic platform adapters for Creative Locator. Drop-in implementations of the @creative-locator/core ApiClient / I18n / Storage / ConfigSource interfaces for headless consumers (Astro, Next.js, Eleventy, vanilla HTML) that talk to any WordPres
Readme
@creative-locator/headless
Drop-in platform adapters for Creative Locator consumers running outside WordPress.
Provides concrete implementations of the four @creative-locator/core adapter interfaces so that any host environment — Astro, Next.js, Eleventy, vanilla HTML, Remix, SvelteKit, or anything else that can render Leaflet in a browser — can render a Creative Locator against a Creative Locator REST backend over HTTP.
What's included
| Adapter | Interface | Purpose |
|---|---|---|
| HttpApiAdapter | ApiClient | Generic fetch-based client against {baseUrl}/dealers, /search, /geocode, /taxonomies, /analytics/*. Optional custom headers and fetch override. |
| IdentityI18nAdapter | I18n | Single-locale default. Returns source strings unchanged; implements sprintf for positional placeholders. |
| BrowserStorageAdapter | Storage | window.localStorage wrapper with graceful degradation for private browsing and SSR. |
| InMemoryStorageAdapter | Storage | Map-backed Storage for SSR and tests. |
| StaticConfigAdapter | ConfigSource | Takes a LocatorConfig at construction time. |
Install
pnpm add @creative-locator/core @creative-locator/headless leaflet leaflet.markerclusterQuick start
import { createLocator, type PlatformAdapters, type LocatorConfig } from '@creative-locator/core';
import {
HttpApiAdapter,
IdentityI18nAdapter,
BrowserStorageAdapter,
StaticConfigAdapter,
} from '@creative-locator/headless';
import '@creative-locator/core/styles/style.scss';
const locatorConfig: LocatorConfig = {
// ... your locator configuration. See @creative-locator/core's LocatorConfig type.
};
const adapters: PlatformAdapters = {
api: new HttpApiAdapter({
baseUrl: 'https://example.com/wp-json/creative-locator/v1',
}),
i18n: new IdentityI18nAdapter(),
storage: new BrowserStorageAdapter(),
config: new StaticConfigAdapter(locatorConfig),
};
const container = document.querySelector<HTMLElement>('#my-locator')!;
createLocator(container, { adapters });Framework recipes
Astro
Most common pattern: render the locator as an island in an .astro page.
---
// src/pages/locator.astro
import '@creative-locator/core/styles/style.scss';
---
<div id="my-locator"></div>
<script>
import { createLocator } from '@creative-locator/core';
import {
HttpApiAdapter,
IdentityI18nAdapter,
BrowserStorageAdapter,
StaticConfigAdapter,
} from '@creative-locator/headless';
createLocator(document.querySelector('#my-locator')!, {
adapters: {
api: new HttpApiAdapter({
baseUrl: import.meta.env.PUBLIC_LOCATOR_API_URL,
}),
i18n: new IdentityI18nAdapter(),
storage: new BrowserStorageAdapter(),
config: new StaticConfigAdapter({
/* LocatorConfig — see @creative-locator/core types */
}),
},
});
</script>The <script> block runs in the browser, so all adapters work as expected. Set PUBLIC_LOCATOR_API_URL in .env to your WordPress site's REST base.
Next.js (App Router, client component)
The locator must mount in a Client Component because Leaflet touches window.
// app/components/Locator.tsx
'use client';
import { useEffect, useRef } from 'react';
import { createLocator } from '@creative-locator/core';
import {
HttpApiAdapter,
IdentityI18nAdapter,
BrowserStorageAdapter,
StaticConfigAdapter,
} from '@creative-locator/headless';
import '@creative-locator/core/styles/style.scss';
export function Locator() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!ref.current) return;
const handle = createLocator(ref.current, {
adapters: {
api: new HttpApiAdapter({ baseUrl: process.env.NEXT_PUBLIC_LOCATOR_API_URL! }),
i18n: new IdentityI18nAdapter(),
storage: new BrowserStorageAdapter(),
config: new StaticConfigAdapter({
/* LocatorConfig */
}),
},
});
return () => handle?.destroy?.();
}, []);
return <div ref={ref} />;
}Then in your page:
// app/locator/page.tsx
import dynamic from 'next/dynamic';
const Locator = dynamic(() => import('@/components/Locator').then((m) => m.Locator), {
ssr: false,
});
export default function LocatorPage() {
return <Locator />;
}SvelteKit (+page.svelte with onMount)
Same pattern as Next.js — defer to onMount so SSR doesn't try to touch window.
<!-- src/routes/locator/+page.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { createLocator } from '@creative-locator/core';
import {
HttpApiAdapter,
IdentityI18nAdapter,
BrowserStorageAdapter,
StaticConfigAdapter,
} from '@creative-locator/headless';
import '@creative-locator/core/styles/style.scss';
let container: HTMLDivElement;
onMount(() => {
const handle = createLocator(container, {
adapters: {
api: new HttpApiAdapter({
baseUrl: import.meta.env.PUBLIC_LOCATOR_API_URL,
}),
i18n: new IdentityI18nAdapter(),
storage: new BrowserStorageAdapter(),
config: new StaticConfigAdapter({
/* LocatorConfig */
}),
},
});
return () => handle?.destroy?.();
});
</script>
<div bind:this={container}></div>Vanilla HTML
Smallest possible setup — no framework, just an ESM <script type="module">:
<!doctype html>
<link rel="stylesheet" href="/node_modules/@creative-locator/core/dist/style.css" />
<div id="my-locator"></div>
<script type="module">
import { createLocator } from 'https://esm.sh/@creative-locator/core';
import {
HttpApiAdapter,
IdentityI18nAdapter,
BrowserStorageAdapter,
StaticConfigAdapter,
} from 'https://esm.sh/@creative-locator/headless';
createLocator(document.querySelector('#my-locator'), {
adapters: {
api: new HttpApiAdapter({ baseUrl: 'https://your-site.com/wp-json/creative-locator/v1' }),
i18n: new IdentityI18nAdapter(),
storage: new BrowserStorageAdapter(),
config: new StaticConfigAdapter({
/* LocatorConfig */
}),
},
});
</script>SSR / hydration notes
- All adapters are client-only by design (Leaflet requires
window/document). BrowserStorageAdaptergracefully no-ops whenwindow.localStorageis unavailable (private browsing, SSR contexts) — safe to instantiate at module top-level without anif (typeof window …)guard.- For SSR-rendered pages: defer
createLocator()to a client-only mount (Astro<script>, Next.jsuseEffectin a Client Component, SvelteKitonMount, etc.).
TypeScript
All adapters ship with full .d.ts definitions. The HttpApiAdapter constructor takes a typed HttpApiAdapterOptions:
interface HttpApiAdapterOptions {
baseUrl: string;
headers?: Record<string, string>;
fetch?: typeof fetch;
}Override fetch for testing or for environments where the global fetch needs middleware (auth tokens, request logging, retry).
License
GPL-2.0-or-later.
