# How GeoLibre's Attribute Table Integrates with Layer Selections: A Technical Deep Dive

> Discover how GeoLibre's attribute table seamlessly integrates with layer selections using a Zustand store for real-time bidirectional synchronization and multi-selection capabilities.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: deep-dive
- Published: 2026-07-31

---

**GeoLibre's attribute table integrates with layer selections through a centralized Zustand store that synchronizes selected feature IDs bidirectionally between the map and table components, enabling real-time multi-selection with keyboard modifiers and automatic UI scrolling.**

The **GeoLibre** desktop application implements a tightly coupled, store-driven architecture that keeps the attribute table synchronized with the current layer's selection state. When users select features on the map or click rows in the table, the system updates a shared global state managed by `@geolibre/core`, ensuring all panels reflect the same selection set instantly. This integration relies on pure selection logic, virtualized scrolling, and persistent anchor tracking to deliver a seamless geographic information system (GIS) experience.

## Store-Driven Selection Architecture

The attribute table reads its selection state directly from the **Zustand store** via the `useAppStore` hook. According to the source code in [`apps/geolibre-desktop/src/components/panels/AttributeTable.tsx`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx) (lines 65-73), the component subscribes to three critical store values:

- `selectedLayerId` – Identifies which layer's features to display
- `selectedFeatureId` – The primary anchor feature for range selections
- `selectedFeatureIds` – The complete set of currently selected feature IDs

When any of these values change, the table re-renders automatically to highlight the corresponding rows. This **single source of truth** pattern ensures that selections initiated from the map, layer panel, or attribute table itself remain perfectly synchronized across the entire application interface.

## Row-Click Handling with Keyboard Modifiers

User interactions in the attribute table trigger a sophisticated selection algorithm that handles multiple keyboard modifiers. The `handleRowClick` function (lines 26-40 of [`AttributeTable.tsx`](https://github.com/opengeos/GeoLibre/blob/main/AttributeTable.tsx)) constructs a `RowSelectionInput` object and delegates the actual computation to the pure helper `computeRowSelection` defined in [`apps/geolibre-desktop/src/lib/attribute-selection.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/attribute-selection.ts) (lines 30-58).

The selection logic supports three distinct interaction modes:

- **Plain click** – Replaces the current selection with a single feature ID
- **Ctrl/Cmd toggle** – Adds or removes the clicked feature from the existing selection set
- **Shift range** – Selects all features between the current anchor and the clicked row, with optional Ctrl merging to combine ranges

After computing the new selection set, the table calls `selectFeatures(ids, anchor)` to push the updated state back into the global store, completing the bidirectional data flow.

```tsx
// Inside AttributeTable.tsx – click handler
const handleRowClick = (featureId: string, event: ReactMouseEvent) => {
  const additive = event.ctrlKey || event.metaKey; // Ctrl/Cmd toggles
  const range = event.shiftKey;                    // Shift selects range
  const { ids, anchor } = computeRowSelection({
    featureId,
    sortedIds: sorted.map(row => row.featureId),
    selectedIds: selectedFeatureIds,
    anchorId: selectedFeatureId,
    additive,
    range,
  });
  // Update global selection state
  selectFeatures(ids, anchor);
};

```

```ts
// Pure selection logic – attribute-selection.ts
export function computeRowSelection(input: RowSelectionInput): RowSelectionResult {
  const { featureId, sortedIds, selectedIds, anchorId, additive, range } = input;

  if (range) {
    // Shift‑range selection; optionally merge with existing selection
    const anchorIndex = anchorId != null ? sortedIds.indexOf(anchorId) : -1;
    const clickedIndex = sortedIds.indexOf(featureId);
    if (anchorIndex === -1 || clickedIndex === -1) return { ids: [featureId], anchor: featureId };
    const [from, to] = anchorIndex <= clickedIndex ? [anchorIndex, clickedIndex] : [clickedIndex, anchorIndex];
    const rangeIds = sortedIds.slice(from, to + 1);
    const merged = additive ? [...selectedIds, ...rangeIds] : rangeIds;
    return { ids: [...new Set(merged)], anchor: anchorId };
  }

  if (additive) {
    // Ctrl‑toggle: add or remove the clicked row
    const next = selectedIds.includes(featureId)
      ? selectedIds.filter(id => id !== featureId)
      : [...selectedIds, featureId];
    const anchor = next.includes(featureId) ? featureId
      : anchorId != null && next.includes(anchorId) ? anchorId
      : (next.at(-1) ?? null);
    return { ids: next, anchor };
  }

  // Plain click – single selection
  return { ids: [featureId], anchor: featureId };
}

```

## Selection-Aware View Modes

The attribute table implements **dual view modes** controlled by local state `featureView`, which accepts either `"all"` or `"selected"` values. When set to `"selected"`, the table filters its display to show only the currently selected features, enabling focused editing of specific subsets.

To prevent interface lock-in, the component automatically switches from `"selected"` back to `"all"` when the user performs a plain click on any row (lines 44-48 of [`AttributeTable.tsx`](https://github.com/opengeos/GeoLibre/blob/main/AttributeTable.tsx)). This ensures that narrowing the view to a specific selection does not trap the user in a single-row display when they attempt to select a different feature.

## Automatic Scrolling Synchronization

To maintain visual continuity when selections originate from external sources like the map canvas, the table implements **programmatic scroll synchronization**. A `useEffect` hook monitors `selectedFeatureId` and invokes `rowVirtualizer.scrollToIndex` (lines 87-92 of [`AttributeTable.tsx`](https://github.com/opengeos/GeoLibre/blob/main/AttributeTable.tsx)) to bring the corresponding virtualized row into the viewport.

This mechanism guarantees that when users select a feature on the map, the attribute table automatically scrolls to highlight that specific record, eliminating manual searching through large datasets. The virtualized rendering ensures this scrolling remains performant even with thousands of features loaded.

## Selection Set Persistence

The integration completes through the `selectFeatures` store action defined in `@geolibre/core`. This action updates both `selectedFeatureId` (the anchor for future range selections) and `selectedFeatureIds` (the complete multi-selection array) in the global state.

Because all UI components—including the map renderer, layer panel, and attribute table—subscribe to these same store values, the selection persists consistently across application state changes. The **pure function** design of `computeRowSelection` enables deterministic, testable selection logic that remains independent of React rendering cycles.

## Summary

- **Centralized state management** via Zustand ensures the attribute table, map, and layer panel share identical selection data through `selectedFeatureId` and `selectedFeatureIds`.
- **Modifier-aware selection logic** in [`attribute-selection.ts`](https://github.com/opengeos/GeoLibre/blob/main/attribute-selection.ts) handles Ctrl toggles, Shift ranges, and combined operations through the pure `computeRowSelection` function.
- **Bidirectional synchronization** allows selections initiated in the table to update the map and vice versa, with automatic scrolling to keep selected rows visible.
- **Dual view modes** (`"all"` vs `"selected"`) provide flexible data inspection, with automatic mode switching to prevent interface navigation dead-ends.
- **Virtualized rendering** with `rowVirtualizer.scrollToIndex` maintains performance and usability when synchronizing selections across large feature sets.

## Frequently Asked Questions

### How does GeoLibre handle multi-selection in the attribute table?

GeoLibre implements multi-selection through keyboard modifiers detected in the `handleRowClick` event handler. Ctrl or Cmd clicks toggle individual features in the selection set, while Shift clicks create ranges from the last anchor feature. The `computeRowSelection` pure function in [`attribute-selection.ts`](https://github.com/opengeos/GeoLibre/blob/main/attribute-selection.ts) encapsulates this logic, returning a deduplicated array of selected IDs and updating the anchor point for subsequent range operations.

### What happens when I select a feature on the map while the attribute table is open?

When a map selection occurs, the `selectedFeatureId` in the global Zustand store updates immediately. The attribute table detects this change through its `useAppStore` subscription and triggers a `useEffect` hook that calls `rowVirtualizer.scrollToIndex` to bring the corresponding row into view. This ensures the table automatically highlights and displays the map-selected feature without requiring manual scrolling.

### Can the attribute table display only selected features?

Yes. The component maintains a local `featureView` state that toggles between `"all"` and `"selected"` modes. When set to `"selected"`, the table filters its virtualized list to render only the feature IDs present in `selectedFeatureIds`. If the user clicks a row without keyboard modifiers while in this mode, the component automatically switches back to `"all"` view to prevent navigation limitations.

### Where is the selection logic stored in the GeoLibre codebase?

The selection logic is separated into two locations for maintainability. The UI interaction handler resides in [`apps/geolibre-desktop/src/components/panels/AttributeTable.tsx`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx), while the pure computational logic lives in [`apps/geolibre-desktop/src/lib/attribute-selection.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/attribute-selection.ts). The global store definitions and `selectFeatures` action are located in [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) and [`packages/core/src/actions.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/actions.ts) respectively, following GeoLibre's monorepo architecture.