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

@geoleaf-plugins/table

v1.2.2

Published

GeoLeaf table plugin

Readme

@geoleaf-plugins/table

GeoLeaf plugin that adds a data table panel to an interactive map. The user opens a bottom-sheet panel, picks a layer, browses its features as a sortable, searchable table, selects rows (the selection is highlighted on the map and can be zoomed to), and exports the chosen rows — or the whole layer — to GeoJSON, CSV, KML, GPX or Excel (XLSX). All in the browser.

  • MIT License — public registry (npmjs.org)
  • Requires @geoleaf/core loaded before this plugin
  • ESM only — no CommonJS/UMD
  • Zero runtime npm dependencies — Excel export uses an inlined, lazily-loaded OOXML writer (no SheetJS)

Extracted from @geoleaf/core. The data table used to be part of the core and configured via the root tableConfig key and the ui.showTable flag. It is now this plugin, configured under modules.table.*. See Migration from core — this is a breaking change.


Installation

npm install @geoleaf-plugins/table

Load in your HTML after @geoleaf/core and before GeoLeaf.boot():

<script
    type="module"
    src="node_modules/@geoleaf-plugins/table/dist/geoleaf-table.plugin.js"
></script>

The plugin mounts the GeoLeaf.Table namespace at load and, when enabled, injects its own UI: a "Table" tab in the desktop panel and a table icon in the mobile toolbar (both gated by modules.table.showButton).


Quick start

Enable the table in the active profile under modules.table (file config/plugins/table.json, referenced by Files.modules.table):

{
    "enabled": true,
    "showButton": true,
    "defaultVisible": false
}

Per layer, declare which columns the table shows under the layer's config.table (this binding stays on the layer):

{
    "table": {
        "enabled": true,
        "title": "Communes",
        "columns": [
            { "field": "properties.name", "label": "Name", "sortable": true },
            { "field": "properties.population", "label": "Population", "type": "number" }
        ]
    }
}

You can also drive the table programmatically:

<script type="module">
    GeoLeaf.Table.setLayer("communes");
    GeoLeaf.Table.show();
    GeoLeaf.Table.setSelection(["feat-1", "feat-2"]);
    GeoLeaf.Table.zoomToSelection();
    GeoLeaf.Table.exportSelection("csv", { csvSeparator: ";" });

    document.addEventListener("geoleaf:table:selectionChanged", (e) => {
        console.log("selection:", e.detail);
    });
</script>

Configuration (modules.table.*)

| Key | Type | Default | Description | | -------------------- | ------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | enabled | boolean | true | Mounts the module on geoleaf:map:ready when true. | | showButton | boolean | true | Shows the desktop tab + mobile icon (read by the core registry). | | defaultVisible | boolean | false | Opens the panel at boot. | | pageSize | number | — | Deprecated, inert. No default, no read site. | | maxRowsPerLayer | number | 30000 | Caps the rows kept per layer. The cut warns the user, once per layer and per session. 0 means zero. | | enableExportButton | boolean | true | Shows the export buttons. | | exportFormats | string[] | ["geojson", "csv", "kml", "gpx", "excel"] | Export buttons offered, in this order (panel.ts). | | csvSeparator | "," \| ";" | "," | Column separator of the CSV export. ";" suits spreadsheet locales that read , as the decimal mark. | | csvIncludeGeometry | boolean | false | Adds a __geometry column to the CSV export. | | virtualScrolling | boolean | — | Deprecated, inert. Virtualisation is decided by a row-count threshold, never by this key. | | defaultHeight | string | "40%" | Initial bottom-sheet height. | | minHeight | string | "20%" | Minimum height (resize). | | maxHeight | string | "60%" | Maximum height (resize). | | resizable | boolean | true | Allows resizing via the drag handle. |

Per-layer (layer.config.table, stays on the layer): enabled, title, columns (field, label, width, sortable, type), defaultSort (field, direction), searchFields (the properties the search interrogates; absent or empty means every column — both ["properties.name"] and ["name"] are accepted).


Public API (GeoLeaf.Table)

| Method | Description | | ------------------------------------ | ------------------------------------------------------------- | | show() | Shows the table panel. | | hide() | Hides the panel and clears the highlight overlay. | | toggle() / open() | Toggles the panel (open is the toolbar action entry point). | | setLayer(layerId) | Switches the active layer (null/"" clears). | | refresh() | Reloads the current layer's features. | | sortByField(field) | Cycles the sort for a field (none → asc → desc → none). | | setSelection(ids, add?) | Selects rows by id (replace, or append when add). | | getSelectedIds() | Returns the selected feature ids. | | clearSelection() | Clears the selection. | | zoomToSelection() | Fits the map to the selected features. | | highlightSelection(active) | Toggles the map highlight overlay. | | exportSelection(format?, options?) | Exports the selected rows (default geojson). | | exportLayer(format?, options?) | Exports all rows of the active layer. |

format: "geojson" \| "csv" \| "kml" \| "gpx" \| "excel". options: { csvSeparator?: "," \| ";", csvIncludeGeometry?: boolean }.


Events

Dispatched on document, and replicated on the map when it exposes fire — subscribe to one of the two, not both. The payloads are typed by GeoLeafEventMap in @geoleaf/core:

| Event | Detail | | ---------------------------------- | ---------------------------------------- | | geoleaf:table:opened | {} | | geoleaf:table:closed | {} | | geoleaf:table:layerChanged | { layerId } | | geoleaf:table:sortChanged | { field, direction } | | geoleaf:table:selectionChanged | { layerId, selectedIds } | | geoleaf:table:zoomToSelection | { layerId, selectedIds } | | geoleaf:table:highlightSelection | { layerId, selectedIds, active } | | geoleaf:table:exportSelection | { layerId, format, selectedIds, rows } | | geoleaf:table:exportLayer | { layerId, format, count } | | geoleaf:table:rowActivated | { layerId, featureId, properties } |

Row activation

geoleaf:table:rowActivated says that the user activated a row: a double-click on it, or Enter while the row has the focus (every row is a Tab stop). The checkbox cell and the sort header never activate. The table opens nothing itself — what an activation means is the subscriber's decision.

document.addEventListener("geoleaf:table:rowActivated", (e) => {
    const { layerId, featureId, properties } = e.detail;
    console.log("activated:", layerId, featureId, properties);
});
  • featureId is the row's identifier, the one selectionChanged lists in selectedIds. It is always a string, and a synthetic one (__gl_row_<n>) when the feature carries no identifier of its own — read the record's key off properties in that case.
  • properties are the feature's complete properties, not only the displayed columns, as a shallow copy. The payload holds no DOM node.
  • A double-click leaves the selection as it was. Its first click toggles the selection like any click — no click is delayed — and the activation restores the selection of before that click: a selectionChanged subscriber sees two emissions around a double-click, the second carrying the initial selection. Enter emits none.
  • A row re-created by the virtual scroll loses the focus: past the virtual threshold, the focused row does not survive a scroll.

Migration from core

Before the extraction (@geoleaf/core ≤ v3), the table was part of the core: GeoLeaf.Table was provided by the core and configured via the root tableConfig key plus the ui.showTable flag. Now the core no longer ships the table — this plugin does.

There is no compatibility shim. To migrate:

  1. Move the global tableConfig block out of the profile root into config/plugins/table.json.
  2. Declare it in the profile manifest under Files.modules.table.
  3. Replace ui.showTable with modules.table.showButton.
  4. Load this plugin's script on the page, after @geoleaf/core.

| Before (core) | After (plugin) | | -------------------------------------------- | ----------------------------------------------------- | | tableConfig at the profile root | modules.table (config/plugins/table.json) | | ui.showTable: true | modules.table.showButton: true | | GeoLeaf.Config.get("tableConfig") → object | GeoLeaf.Config.get("tableConfig") → undefined | | GeoLeaf.Table present with the core | requires this plugin's script after geoleaf.esm.js |

The per-layer binding is kept. The layer.config.table.* block (columns, sort, title) stays on each GeoJSON layer — layer-config.schema.json is unchanged. Only the global config (tableConfig + ui.showTable) moves to modules.table.


Limitations

  • The panel mounts on the map returned by GeoLeaf.Core.getMap() — one map at a time (no per-mapId scoping yet).
  • pageSize and virtualScrolling are deprecated and inert. They are kept in the published TableConfig type, marked, rather than removed: deleting a member of a published type breaks the compilation of an integrator who wrote it, whereas deprecating it tells them. Neither has a replacement — there is no pagination at all, and virtualisation is decided by a row-count threshold — so neither is a deprecation in the sense of VERSIONING_POLICY.md.
  • The Excel writer is write-only (no read, no formulas) — sufficient for tabular export.

Bundle budget

| Part | Size (gzip) | | ------------- | ------------------------- | | Plugin bundle | budget 50 KB (warn 38 KB) |

No third-party runtime dependency is bundled (the OOXML writer is inlined and lazily imported). @geoleaf/core is accessed via globalThis.GeoLeaf; maplibre-gl is a peer dependency provided by the host page.


MIT License

Copyright © 2026 Mattieu Pottier. See LICENSE for details.