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

bis-view

v0.1.4

Published

Vue 3 components for tables, cards, and charts with typed column definitions (BiS view).

Readme

bis-view

Vue 3 components to present the same dataset in multiple ways—table, card grid, line chart, bar chart, and pie chart—using one column definition (ColumnsDef) as the single source of truth for labels, types, formatting, and chart eligibility.

For tool- or agent-oriented API detail (types, props, filter contracts, edge cases), see AI-AGENT-REFERENCE.md in this package (node_modules/bis-view/AI-AGENT-REFERENCE.md after install).


Install

npm install bis-view

Peer dependencies

Install these in your app (versions should satisfy the ranges in this package’s peerDependencies):

  • vue
  • piniarequired when using useBisFilters, VdvFilterBar, or integrated DataView filters (Pinia backs the bis-view-filters store).
  • echarts
  • vue-echarts
  • vue-routeroptional (only needed if you use createVueRouterBisFiltersUrlSync for URL ⇄ filters sync).

Styles

Import the bundled CSS once (e.g. in main.ts or your app shell):

import "bis-view/style.css";

If your bundler struggles with the export subpath, use:

import "bis-view/dist/style.css";

The stylesheet maps design tokens to CSS variables (--vdv-*, with fallbacks to common --color-* / --space-* names). Override those variables in your app to theme tables and charts.


Core idea

  1. items: an array of row objects (TItem extends ColumnObject).
  2. columns: a typed array describing every visible column (including a special "actions" column if you need row buttons).
  3. Derived behavior: sorting, pagination, cell text, chart axes, and aggregates all read from that same columns metadata plus items—you do not configure charts with a second, parallel schema unless you opt in (e.g. filterDefs for bar/pie categories and, when using filters, one shared filterDefs list for VdvFilterBar / useBisFilters).

Filtering

bis-view exposes a Pinia store (bis-view-filters) plus composables and VdvFilterBar, so one DataViewFilterDef[] can drive the filter UI, server query params, and client-side row filtering.

The library never fetches. Your app:

  1. Calls useBisFilters(scope, filterDefs) to read serverFilterParams / filterItems.
  2. Loads data (your HTTP client).
  3. Passes serverFilterParams into the backend when applyMode === "server".
  4. Passes the result through filterItems(items) for applyMode === "client" defs.

Prerequisites

  1. app.use(createPinia()) before any useBisFilters / VdvFilterBar usage.
  2. (Optional — URL sync for shareable/bookmarkable filter state) app.provide after app.use(router):
import {
  bisFiltersUrlSyncKey,
  createVueRouterBisFiltersUrlSync,
} from "bis-view";

app.use(router);
app.provide(
  bisFiltersUrlSyncKey,
  createVueRouterBisFiltersUrlSync(router),
);

Without this inject, useBisFilters still updates Pinia only; pushQueryFromStore becomes a no-op.

Definitions (DataViewFilterDef)

| Field | Notes | |-------|------| | key, type | select | date | dateRange | text | number. | | labelKey | Filter labels go through translate(labelKey) in VdvFilterBar (supply translate from your i18n). | | applyMode | server → only serverFilterParams (backend must enforce). clientfilterItems / filterItemsByDefs in the browser; not added to serverFilterParams. | | urlKey | Optional; defaults to key. dateRange maps to query ${urlKey}From / ${urlKey}To. | | customFilter | Optional; for client defs, replaces default field matching: (item, filterValue) => boolean. | | select.options() | Runtime () => { value, label }[] (not serializable as JSON by itself). |

useBisFilters(pageScope, filterDefs, options?)

  • pageScope: stable string per page (e.g. "/orders"). Use the same value as VdvFilterBar’s filter-scope and DataView’s filter-scope-key.
  • filterDefs: static array or Ref / computed array.

Returns (important):

  • filterValues, setFilter, setFilters, clearFilters
  • serverFilterParamscomputed, map to your API (see pickServerFilters when building requests manually).
  • filterItems(items, getField?) — applies client defs to items using current store values; getField(row, key) optional for nested keys.

On first invocation, useBisFilters hydrates store from queryToFilters (+ mergeDateRangeDefaults for empty ranges). options.onSyncFromUrl runs when the URL seeded filters.

Calling useBisFilters twice with the same scope is OK (e.g. parent + VdvFilterBar) — they share one Pinia store. Keep filterDefs aligned.

Filter bar UI (VdvFilterBar)

Renders useBisFilters(filterScope, filterDefs) internally. Parent must still call useBisFilters so filteredItems and serverFilterParams reflect the same controls.

Pass translate for labelKey, emptyLabelKey, clearFilters, applyFilters, all, dateFrom, dateTo, etc. (fallback English strings exist when translate is omitted).

Dates use <input type="date" (ISO yyyy-mm-dd).

<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { VdvFilterBar, useBisFilters, type DataViewFilterDef } from "bis-view";

const { t } = useI18n();
const PAGE = "/example";
const filterDefs = computed<DataViewFilterDef[]>(() => [
  { key: "q", type: "text", labelKey: "search", applyMode: "client" },
]);

const { filterItems, serverFilterParams } = useBisFilters(PAGE, filterDefs);

const translate = (key: string) => t(key);
</script>

<template>
  <VdvFilterBar
    :filter-scope="PAGE"
    :filter-defs="filterDefs"
    :translate="translate"
  />
</template>

DataView: built-in filter row

<DataView
  :items="items"
  :columns="columns"
  :filter-defs="filterDefs"
  show-filter-bar
  filter-scope-key="/example"
  :translate="translate"
/>

Requires non-empty filterDefs. You still use useBisFilters("/example", filterDefs) (same scope string) in script for data loading and filterItems.

Bar / pie and filterDefs

filterDefs on DataView is also used by DataBarChart / DataPieChart: type: "select" entries participate in getBarChartXDescriptors (see Bar chart).

Headless exports

| Export | Role | |--------|------| | useBisViewFilterStore | Pinia API: getFilters, setFilter, filterItems, … | | filterItemsByDefs, pickServerFilters, matchesFilter, mergeDateRangeDefaults, isEmptyFilterValue | Pure logic / tests / custom pipelines | | filtersToQuery, queryToFilters | Router query ↔ FilterValues | | bisFiltersUrlSyncKey, createVueRouterBisFiltersUrlSync | Optional URL bridge | | calendarMonthRangeISO | Default current month range for empty dateRange during merge |


Business logic

This section describes how the library decides what to render and how, not just API names.

Column model

  • Each column has a key matching a property on the row (unless you use getValue or the special actions key).
  • type drives:
    • Default formatting when no custom formatter is set (tableFormatters).
    • Sort behavior (numbers, dates, currencies, text).
    • Chart eligibility (see below).
  • Column header text (table, cards, chart axis pickers) uses resolveColumnDisplayLabel:
    • If labelKey is set, translate(labelKey) is used with the optional translate prop (i18n lookup).
    • Otherwise label is shown as-is (use for literals or already translated text, e.g. Hebrew resolved in the parent).
  • actions columns render TableAction buttons; optional icon is resolved via iconResolver (e.g. map a string name to a Vue component).

label vs labelKey

| Field | Behavior | | ----- | -------- | | label | Display string when labelKey is absent. Not passed through translate. | | labelKey | Optional. When set (non-empty), the visible title is translate(labelKey). |

You can export resolveColumnDisplayLabel from the package if you need the same rule outside components.

Table & cards (DataTable, DataColumnar)

  1. Sorting

    • Clicking a sortable column header toggles asc/desc on that column key.
    • Sort uses getValue when present, otherwise row[key].
    • Comparison is type-aware (number / currency / date / text).
  2. Pagination

    • Operates on the sorted list.
    • Page size changes emit update:pageSize; the parent owns the effective page size if you store it.
    • When disabled or page size is zero, all sorted rows are shown.
  3. Cells

    • Optional named slots per column key override rendering for that column.
    • Otherwise TableCell formats from type, format, or formatter.
    • wrapper (text | button | chip | link) chooses the host element; link uses linkComponent (e.g. RouterLink) or a plain <a> with href(row).
  4. Footer totals

    • If any column defines total, a footer row runs those functions on the full sorted dataset (not only the current page).

Cards repeat the same row/column semantics in a denser layout; actions appear in a footer strip per card.

DataView (unified toggle)

  • DataViewMode: "table" | "cards" | "chart" | "scatterChart" | "barChart" | "pieChart".
  • Renders modes based on view (v-model via update:view).
  • Line, scatter (raw points, same date + numeric columns as line, no group-by), bar, and pie buttons when supported.
  • Optional chartColors: string array forwarded to line, scatter, bar, and pie ECharts options (see resolveChartColors).
  • Optional pieTopN, pieInnerRadius: forwarded to the pie view only (top slices + Other, donut hole).
  • If the requested view becomes invalid (e.g. columns change), the view falls back to table and update:view may emit table.
  • Use normalizeDataViewMode to mirror the same fallback in parent state.

Scatter (DataScatterChart)

Same column eligibility as the line chart (chartSupported: at least one date and one numeric/currency). Uses the same X/Y pickers; plots one point per row (no group by / bucketing). Implemented with useTimeSeriesChart (plotPoints only).

Line chart (DataChart)

Eligibility (“business rule”): at least one column with type === "date" and one with type === "number" or "currency" (see chartSupported / dataChartSupport).

Behavior:

  1. X axis uses a chosen date column; Y uses a numeric/currency column.
  2. Raw points are (time, value) pairs; invalid dates or numbers are skipped.
  3. Group by (granularity none / day / week / month / year) buckets points in local calendar time. Inside each bucket, Y values are combined with aggregateY—default sumAggregate—so overlapping rows in the same bucket add up (typical for amounts over time).
  4. Tooltip titles use formatChartBucketLabel so labels match the granularity.

Charts render with Apache ECharts via vue-echarts. If every Y value is zero after aggregation (line) or in raw points (scatter), or for bar/pie, charts show the same empty state as noData where applicable (no misleading charts).

Pie chart (DataPieChart)

Same eligibility, pickers, and aggregation as the bar chart (aggregateBarChart, getBarChartXDescriptors, filterDefs). Optional pieTopN (≥ 2): keep the largest values and merge the rest into pieChartOther. Optional pieInnerRadius (e.g. 40 or "45%") for a donut.

Bar chart (DataBarChart)

Eligibility: at least one categorical X source:

  1. type === "boolean" columns, or
  2. DataViewSelectFilterDef entries passed in filterDefs (type: "select"), paired by key—optionally joined to a real column via the same key (if the row only holds an id, getValue / dot-path lookups can still resolve a value).

Ordering of X descriptors: boolean columns first, then select-backed keys; duplicate keys dedupe (boolean wins if both exist—unlikely).

Y axis:

  • sum / mean need a numeric/currency column.
  • count counts rows per category and does not require a numeric column.

Buckets: values are aggregated per category label. Booleans map to yes / no / empty** via **translate**. Select categories use **options()** on the **DataViewSelectFilterDef` for display labels where possible.

See aggregateBarChart, getBarChartXDescriptors, barChartSupported.

Internationalization (translate)

  • Components accept optional translate: (key: string) => string (e.g. vue-i18n t).
  • Built-in UI strings (toolbar, pagination, yes/no, chart chrome, …) go through the same helper that applies default English when translate is missing, or when a key is unknown.
  • Column headers do not call translate(label) anymore. Use label for final text, or set labelKey to pass only that string through translate.

Icons (iconResolver)

  • TableAction.icon may be a Vue component or an opaque string (your app maps strings to icons if you supply iconResolver).

Integration props (summary)

| Prop | Role | |------|------| | translate | Localize all built-in keys (table, loading, chart strings, pagination, …). | | iconResolver | Resolve actions[k].icon when icons are strings. | | linkComponent | e.g. RouterLink; default link behavior otherwise. | | filterDefs | Bar and pie category sources from select filter definitions (DataViewFilterDef[]); also the schema for VdvFilterBar / useBisFilters when you use filtering. | | showFilterBar | When true, render VdvFilterBar above the view toggle (requires filter-scope-key and non-empty filterDefs). | | filterScopeKey | Scope string for embedded bar + useBisFilters (must match the scope you pass to useBisFilters in script). | | chartColors | Optional ECharts colors for line / bar / pie. | | pieTopN | Pie only: max slices; remainder merged into pieChartOther (requires ≥ 2 to take effect). | | pieInnerRadius | Pie only: donut inner radius (number → percent, or string e.g. "40%"). |


Quick usage

<script setup lang="ts">
import { DataView, type ColumnsDef } from "bis-view";

interface Row extends Record<string, unknown> {
  id: number;
  name: string;
  amount: number;
  createdAt: string;
}

const items: Row[] = [
  /* ... */
];

const columns = [
  { key: "name", label: "Name", type: "text" },
  { key: "amount", label: "Amount", type: "currency" },
  {
    key: "createdAt",
    label: "Created",
    type: "date",
    format: "datetime",
  },
] as const satisfies ColumnsDef<Row>;
</script>

<template>
  <DataView
    :items="items"
    :columns="columns"
    :translate="(k) => k"
    :show-view-toggle="true"
  />
</template>

Pass translate from vue-i18n, iconResolver, and link-component in real apps.


Exported utilities

Use these when you want the same rules outside the UI (e.g. feature flags or server docs):

| Export | Purpose | |--------|---------| | useBisFilters, UseBisFiltersOptions | Page scope + defs → filterValues, serverFilterParams, filterItems, URL sync when provided. | | VdvFilterBar | Filter form UI (Pinia + optional URL). | | useBisViewFilterStore | Low-level Pinia store for filter state. | | filterItemsByDefs, pickServerFilters, matchesFilter, mergeDateRangeDefaults, isEmptyFilterValue, defaultGetField, urlHasDateRange | Pure filter engine. | | filtersToQuery, queryToFilters | Serialize / parse router query. | | bisFiltersUrlSyncKey, BisFiltersUrlSync, createVueRouterBisFiltersUrlSync | Optional vue-router URL bridge. | | calendarMonthRangeISO | Default month range for dateRange merge. | | translateDataViewKey | Resolve a key with the same built-in English fallbacks as DataView. | | chartSupported, getDateColumnKeys, getNumericColumnKeys | Line-chart column detection. | | barChartSupported, getBarChartXDescriptors | Bar and pie category detection (+ filterDefs). | | normalizeDataViewMode | Same view fallbacks as DataView (unsupported chart / scatterChart / barChart / pieCharttable). | | aggregateBarChart | Same aggregation as DataBarChart / DataPieChart. | | applyPieTopN, hasRenderableSeriesValues | Pie bucketing and “all zeros” checks. | | resolveChartColors, DEFAULT_CHART_COLORS, chartLineAreaFill | Chart palette helpers. | | useCategoricalChart | Shared bar/pie axis state (category column, aggregate, series). | | useTimeSeriesChart | Shared line/scatter pipeline (date + numeric columns, raw plotPoints, aggregated chartPlotPoints). | | aggregateTimeSeriesPoints, formatChartBucketLabel, sumAggregate, … | Line-chart bucketing. | | formatCell, getCellValue, getRowId | Table/card formatting and identity. | | resolveColumnDisplayLabel, ColumnLikeForLabel | Same label / labelKey title rules as the UI. | | useTableSort, useTablePagination | Headless sort/pagination. |


Build from source

In this repo’s package folder:

npm run build
npm test

prepublishOnly runs npm run build before npm publish.


License

Released under the MIT License. See LICENSE in this package.