Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d9b86b8
Initial adaptation for Solid 2.0.
davedbase Apr 11, 2026
c1c19cf
Simplfieid and idiomatic changes for Solid 2
davedbase Apr 11, 2026
3b3cece
Prettier cleanup
davedbase Apr 11, 2026
91331aa
Updated README and primitive list
davedbase Apr 11, 2026
4cae183
port to tanstack start v1
birkskyum Apr 11, 2026
d95dd8b
fix import to manifest
birkskyum Apr 11, 2026
6bd0d7f
footer
birkskyum Apr 11, 2026
6b41680
add vite preview
birkskyum Apr 11, 2026
bb7a5a1
improe handling of not found
birkskyum Apr 11, 2026
2d2eb8b
footer
birkskyum Apr 11, 2026
954bd15
improve not found
birkskyum Apr 11, 2026
e8e10b5
clear suspense
birkskyum Apr 11, 2026
155dcb4
fix not found
birkskyum Apr 11, 2026
9fde982
filter prerender
birkskyum Apr 11, 2026
aa3157d
relative link for playground
birkskyum Apr 11, 2026
e0d276e
replace solid tippy with floating-ui/dom
birkskyum Apr 13, 2026
1abee62
Stabilize tests
davedbase Apr 15, 2026
aadb2ce
Exploring better ergonomics
davedbase Apr 20, 2026
ff098a0
Use the latest beta
davedbase Apr 20, 2026
32e27c8
pr feedback
birkskyum Apr 27, 2026
823c970
chore: port site to tanstack start v1 (#839)
davedbase Apr 27, 2026
16ded87
Format
davedbase Apr 27, 2026
32a1c13
Merge branch 'main' into update/v2/geolocation
davedbase Apr 29, 2026
9ab865b
Patch return type
davedbase Apr 29, 2026
039397a
Merge branch 'next' into update/v2/geolocation
davedbase May 2, 2026
fd0191a
Bump to beta 10 and run formatter
davedbase May 2, 2026
5a1893e
Added three new primitives
davedbase May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .changeset/geolocation-solid2-async-reactivity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
"@solid-primitives/geolocation": minor
---

Adapt geolocation primitives for Solid 2.0 async reactivity.

## `createGeolocationWatcher` — breaking changes

**Pending state via `NotReadyError` (replaces `undefined`):** `location()` now throws `NotReadyError` until the first GPS fix arrives, integrating with `<Loading>` boundaries. Previously it returned `undefined` (which was cast away) using the Solid 1.x `<Suspense>` suspension mechanism.

```tsx
// Before — undefined was returned silently (Solid 1.x Suspense behavior)
const { location } = createGeolocationWatcher(true);
location() // undefined before first fix

// After — throws NotReadyError (Solid 2.0 <Loading> pattern)
const { location } = createGeolocationWatcher(true);
location() // throws NotReadyError before first fix

// Integrate with <Loading>:
<Loading fallback="Acquiring GPS fix...">
<Map lat={location().latitude} lng={location().longitude} />
</Loading>
```

**Reactive options now correctly restart the watcher:** Previously `options` was read inside the effect handler (untracked), so changing a reactive `options` accessor had no effect. The effect source now tracks both `enabled` and `options` — when `options` changes while the watcher is active, the watcher is closed and reopened with the new options.

## SSR stubs — breaking changes

`createGeolocation` and `createGeolocationWatcher` now throw `NotReadyError` on the server instead of a plain `Error`. This means SSR renders show the `<Loading>` fallback (pending state) rather than hitting the `<Errored>` boundary, which is the correct behavior for client-side-only APIs that will hydrate and resolve client-side.
3 changes: 3 additions & 0 deletions configs/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export default defineConfig(({ mode }) => {
}),
},
resolve: {
alias: {
"solid-js/web": "@solidjs/web",
},
conditions: testSSR
? ["@solid-primitives/source", "node"]
: ["@solid-primitives/source", "browser", "development"],
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"rehype-highlight": "^7.0.2",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.1",
"solid-js": "2.0.0-beta.10",
"@solidjs/web": "^2.0.0-experimental.16",
"solid-js": "^2.0.0-beta.6",
"typescript": "^5.8.3",
"vinxi": "^0.5.7",
"vite": "^6.3.5",
Expand Down
13 changes: 13 additions & 0 deletions packages/geolocation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# @solid-primitives/geolocation

## 2.0.0

### Major Changes

- Updated to Solid.js 2.0 API and full primitive redesign:
- Added `makeGeolocation(options?)` — non-reactive one-shot query returning `[query, cleanup]` tuple, no Solid owner required
- Added `makeGeolocationWatcher(options?)` — non-reactive continuous watcher returning `[store, cleanup]` tuple, no Solid owner required
- `createGeolocation` now returns `[Accessor<GeolocationCoordinates>, refetch]` — async memo that suspends with `<Suspense>`, re-queries when reactive `options` change or `refetch()` is called. Supports `isPending()` for background re-query indicators
- `createGeolocationWatcher` now returns `{ location: Accessor<GeolocationCoordinates>, error: Accessor<GeolocationPositionError | null> }` — `location` suspends until the first GPS fix then updates reactively; `error` is a signal for recoverable in-component error handling
- Replaced `createResource` with async `createMemo` + version signal
- Replaced `createComputed` with `createEffect`
- Updated peer dependency to `solid-js@^2.0.0`

## 1.5.4

### Patch Changes
Expand Down
199 changes: 163 additions & 36 deletions packages/geolocation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,68 +12,202 @@ Primitives to query and watch geolocation information from within the browser.

## Installation

```
```bash
npm install @solid-primitives/geolocation
# or
yarn add @solid-primitives/geolocation
pnpm add @solid-primitives/geolocation
```

## How to use it
## `makeGeolocation`

### createGeolocation
A non-reactive one-shot query. No Solid owner required — can be used outside components. Returns a `[query, cleanup]` tuple.

Used to fetch current geolocation data as a resource. This primitive uses `createResource` to return a location, so `loading`, `error`
```ts
const [query, cleanup] = makeGeolocation({ enableHighAccuracy: true });
const coords = await query();
cleanup();
```

### Definition

```ts
const [location, refetch] = createGeolocation();
makeGeolocation(
options?: PositionOptions
): [query: () => Promise<GeolocationCoordinates>, cleanup: VoidFunction]
```

or with options:
---

## `makeGeolocationWatcher`

A non-reactive continuous watcher. No Solid owner required. Returns a `[store, cleanup]` tuple.

```ts
const [location, refetch] = createGeolocation({
enableHighAccuracy: false,
maximumAge: 0,
timeout: 200,
});
const [store, cleanup] = makeGeolocationWatcher();
console.log(store.location); // GeolocationCoordinates | null
console.log(store.error); // GeolocationPositionError | null
cleanup();
```

#### Definition
### Definition

```ts
createGeolocation(
options: MaybeAccessor<PositionOptions> // these override basic defaults (see Types section)
makeGeolocationWatcher(
options?: PositionOptions
): [
location: Resource<GeolocationCoordinates | undefined>,
refetch: Accessor<void>
store: { location: GeolocationCoordinates | null; error: GeolocationPositionError | null },
cleanup: VoidFunction
]
```

### createGeolocationWatcher
---

## `createGeolocation`

A reactive one-shot query. Returns an async accessor that integrates with `<Loading>` boundaries — the component subtree suspends until the position resolves. Re-queries automatically when reactive `options` change, or manually via `refetch()`.

```ts
const [location, refetch] = createGeolocation();
```

```tsx
// Suspends until first fix:
<Loading fallback="Locating...">
<div>{location().latitude}, {location().longitude}</div>
</Loading>

// Show a subtle indicator while re-querying in the background:
<Show when={isPending(() => location())}>Updating position...</Show>
```

With reactive options:

```ts
const [opts, setOpts] = createSignal<PositionOptions>({ enableHighAccuracy: false });
const [location, refetch] = createGeolocation(opts);
// Automatically re-queries when opts() changes
```

### Definition

```ts
createGeolocation(
options?: MaybeAccessor<PositionOptions>
): [location: () => Promise<GeolocationCoordinates>, refetch: VoidFunction]
```

---

## `createGeolocationWatcher`

Creates a geolocation watcher and updates a signal with the latest coordinates. This primitive returns two reactive getters: `location` and `error`.
A reactive continuous watcher. `location` throws `NotReadyError` (integrating with `<Loading>`) until the first GPS fix, then updates reactively without re-suspending. `error` is a signal accessor for recoverable in-component error handling. The watcher starts and stops reactively based on `enabled`. Reactive `options` restarts the watcher when the enabled state is active.

```ts
const watcher = createGeolocationWatcher(true);
console.log(watcher.location);
console.log(watcher.error);
const [enabled, setEnabled] = createSignal(true);
const { location, error } = createGeolocationWatcher(enabled);
```

#### Definition
```tsx
// Show error inline (recoverable — no error boundary needed):
<Show when={error()}>
Permission denied — <button onClick={retry}>retry</button>
</Show>

// Suspends until first GPS fix, then updates live:
<Loading fallback="Acquiring GPS fix...">
<Map lat={location().latitude} lng={location().longitude} />
</Loading>
```

### Definition

```ts
createGeolocationWatcher(
enabled: MaybeAccessor<boolean>,
options: MaybeAccessor<PositionOptions>
options?: MaybeAccessor<PositionOptions>
): {
location: GeolocationCoordinates | null,
error: GeolocationPositionError | null
location: Accessor<GeolocationCoordinates>;
error: Accessor<GeolocationPositionError | null>;
}
```

#### Types
---

## `createDistance`

The values returned in the location property are as follows:
Reactively calculates the distance from the user's current GPS location to a target coordinate using the [Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula). Returns `null` until the first GPS fix arrives.

```ts
const distance = createDistance({ latitude: 48.8566, longitude: 2.3522 });
```

```tsx
<Show when={distance() !== null} fallback="Locating...">
{distance()!.toFixed(1)} km from the Eiffel Tower
</Show>
```

With a reactive target and metre units:

```ts
const [target, setTarget] = createSignal({ latitude: 48.8566, longitude: 2.3522 });
const distance = createDistance(target, { unit: "m" });
```

### Definition

```ts
createDistance(
target: MaybeAccessor<GeolocationCoord>,
options?: {
unit?: "km" | "m"; // default "km"
enabled?: MaybeAccessor<boolean>;
watcherOptions?: MaybeAccessor<PositionOptions>;
}
): Accessor<number | null>
```

---

## `createWithinRadius`

Reactively tracks whether the user's GPS location is within a given radius (in **metres**) of a centre coordinate. Returns `false` until the first GPS fix arrives.

```ts
const nearby = createWithinRadius({ latitude: 48.8566, longitude: 2.3522 }, 500);
```

```tsx
<Show when={nearby()}>You are near the Eiffel Tower!</Show>
```

With a reactive radius:

```ts
const [radius, setRadius] = createSignal(500);
const nearby = createWithinRadius({ latitude: 48.8566, longitude: 2.3522 }, radius);
```

### Definition

```ts
createWithinRadius(
center: MaybeAccessor<GeolocationCoord>,
radius: MaybeAccessor<number>, // in metres
options?: {
enabled?: MaybeAccessor<boolean>;
watcherOptions?: MaybeAccessor<PositionOptions>;
}
): Accessor<boolean>
```

---

## Types

```ts
type GeolocationCoord = { latitude: number; longitude: number };
```

```ts
interface GeolocationCoordinates {
Expand All @@ -87,7 +221,7 @@ interface GeolocationCoordinates {
}
```

The options property defaults to the following value unless overwritten:
Default position options (overridden by anything you pass):

```ts
const geolocationDefaults: PositionOptions = {
Expand All @@ -101,13 +235,6 @@ const geolocationDefaults: PositionOptions = {

You may view a working example here: https://stackblitz.com/edit/vitejs-vite-dvk4m4

## Primitive Ideas

We're always looking to enhance our primitives. Some ideas:

- `createDistance` (supply a lat/lng and reactively calculate the difference in km/m)
- `createWithinRadius` (a signal for tracking if a user is within a radius boundary)

## Changelog

See [CHANGELOG.md](./CHANGELOG.md)
Loading