How Attribute Table Selection Syncs with Map Highlighting and Zoom-to-Feature in GeoLibre
GeoLibre uses a centralized Zustand store to synchronize attribute table selection with map highlighting and zoom-to-feature through reactive state subscriptions and bidirectional updates.
In the opengeos/GeoLibre codebase, selection state acts as the single source of truth that connects the attribute table UI to the map canvas. When users click rows in the table or features on the map, the same store fields update—triggering automatic visual synchronization and optional viewport fitting. This article explains the complete data flow with source code references from the actual implementation.
Core Architecture: The Selection Store
GeoLibre's selection system centers on packages/core/src/store.ts, which exports a Zustand store containing two critical state fields:
selectedFeatureId— the anchor feature (last clicked), ornullselectedFeatureIds— the complete multi-selection array
The store provides two actions for updating selection:
// packages/core/src/store.ts – selection actions
selectFeature: (id) => set({
selectedFeatureId: id,
selectedFeatureIds: id ? [id] : []
}),
selectFeatures: (ids, anchorId) =>
set({
selectedFeatureIds: ids,
selectedFeatureId:
anchorId != null && ids.includes(anchorId)
? anchorId
: (ids.at(-1) ?? null),
}),
These actions ensure consistent state whether the selection originates from a single click, Ctrl/Cmd additive selection, or Shift range selection.
From Table Click to Store Update
In apps/geolibre-desktop/src/components/panels/AttributeTable.tsx, row clicks are handled by computeRowSelection, a helper that resolves the new selection set based on modifier keys:
// apps/geolibre-desktop/src/components/panels/AttributeTable.tsx
const handleRowClick = (featureId: string, event: ReactMouseEvent) => {
const additive = event.ctrlKey || event.metaKey; // Ctrl/Cmd toggles
const range = event.shiftKey; // Shift creates range
const { ids, anchor } = computeRowSelection({
featureId,
sortedIds: sorted.map(r => r.featureId),
selectedIds: selectedFeatureIds,
anchorId: selectedFeatureId,
additive,
range,
});
// Revert from "Show Selected" to "Show All" on plain click
if (!additive && !range && featureView === "selected") {
setFeatureView("all");
}
// Update global selection store
selectFeatures(ids, anchor);
};
The selectFeatures call propagates immediately to all subscribers, including the map canvas.
From Store Change to Map Highlight and Zoom
The packages/map/src/MapCanvas.tsx component subscribes to selection state via useAppStore. A useEffect reacts to every change and coordinates highlighting and zoom behavior:
// packages/map/src/MapCanvas.tsx – syncing selection → map
useEffect(() => {
const layer = layers.find((item) => item.id === selectedLayerId);
const highlightIds = resolveHighlightIds({
selectedFeatureIds,
selectedFeatureId
});
const nextKey = selectedLayerId && highlightIds.length > 0
? `${selectedLayerId}:${highlightIds.join("\u0000")}`
: null;
const shouldFit = Boolean(
zoomToSelectedFeature &&
nextKey &&
nextKey !== previousSelectedFeatureKey.current
);
previousSelectedFeatureKey.current = nextKey;
// Highlight (and optionally fit) features
controller.current?.highlightFeature(layer, highlightIds, { fit: shouldFit });
// DuckDB layers: fetch bounds separately for zoom
if (layer && isDuckDBQueryLayer(layer) && shouldFit && selectedFeatureId) {
const bounds = duckDBBridge()?.getFeatureBounds?.(layer.id, selectedFeatureId);
if (bounds) controller.current?.fitBounds(bounds);
}
}, [
layers,
selectedLayerId,
selectedFeatureId,
selectedFeatureIds,
zoomToSelectedFeature,
]);
The effect performs three critical functions:
resolveHighlightIds— determines which features should be visually emphasized based on anchor and multi-selection state- Stable key generation — uses
selectedLayerIdplus null-delimited IDs to detect meaningful changes and prevent redundant operations - Conditional zoom — only fits bounds when
zoomToSelectedFeatureis enabled and the selection has actually changed
DuckDB Layer Special Handling
For DuckDB-based query layers, the standard highlight mechanism may not provide geometry bounds directly. The MapCanvas effect detects these layers with isDuckDBQueryLayer() and requests bounds through the duckDBBridge:
if (layer && isDuckDBQueryLayer(layer) && shouldFit && selectedFeatureId) {
const bounds = duckDBBridge()?.getFeatureBounds?.(layer.id, selectedFeatureId);
if (bounds) controller.current?.fitBounds(bounds);
}
This ensures zoom-to-feature works consistently across both standard vector layers and dynamically queried datasets.
Bidirectional Synchronization: Map to Table
The synchronization flows both ways. When users click features directly on the map, the map controller calls selectFeature or selectFeatures on the same store. The attribute table subscribes to these same fields and uses a virtualizer to scroll the selected row into view, maintaining UI coherence regardless of interaction origin.
Key Implementation Files
| File | Purpose |
|---|---|
packages/core/src/store.ts |
Zustand store with selectedFeatureId, selectedFeatureIds, and selection actions |
apps/geolibre-desktop/src/components/panels/AttributeTable.tsx |
Row click handling, selection computation, and store updates |
packages/map/src/MapCanvas.tsx |
Reactive effect that highlights features and triggers zoom-to-feature |
apps/geolibre-desktop/src/lib/selection-actions.ts |
Shared helpers for dialog-driven selections |
Summary
- Centralized state in
@geolibre/coreeliminates synchronization bugs between table and map selectFeatureshandles single, additive, and range selection patterns with proper anchor management- Reactive subscription in
MapCanvasautomatically updates highlighting when selection changes - Zoom-to-feature is controlled by the
zoomToSelectedFeatureboolean and guarded by stable key comparison - DuckDB layers receive special bounds-fetching logic to ensure consistent zoom behavior
- Bidirectional updates mean any selection change—table or map—propagates to all UI components
Frequently Asked Questions
How does GeoLibre prevent unnecessary zoom operations when selection hasn't changed?
The MapCanvas effect builds a stable key from selectedLayerId and the sorted highlightIds array, joined with a null delimiter (\u0000). This key is stored in a ref (previousSelectedFeatureKey) and compared against the new key. Zoom only occurs when zoomToSelectedFeature is enabled, the key is non-null, and the key differs from the previous value.
Can multiple features be highlighted simultaneously from the attribute table?
Yes. The selectFeatures action accepts an array of IDs and manages multi-selection through the selectedFeatureIds array. The handleRowClick function in AttributeTable.tsx supports Ctrl/Cmd for additive selection and Shift for range selection, passing the resulting ID set to the store.
What happens when zoom-to-feature is enabled for a DuckDB query layer?
For DuckDB layers, the map controller cannot derive bounds directly from highlighted features. Instead, the effect calls duckDBBridge()?.getFeatureBounds(layer.id, selectedFeatureId) to fetch geometry bounds asynchronously, then invokes controller.current?.fitBounds(bounds) if valid bounds are returned.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →