@use "/shell" as *; @use "quark:list" as list; #release-notice { /* opens once: sheet re-runs restart a delay, so gate it on the fact it writes */ &:not([data-did-open]) { @delay 7000 { is-open: ""; data-did-open: ""; } } /* prevent clicks from bubbling to the sheet */ @on mouseup, click (stop-propagation); } provider-fetch[api-url*="package-metas/index.json"][is-success] { $package-indices: prop("provision").body; /* `excom.navGroup` moves a package out of its type's list, into that group */ $ungrouped: list.reject($package-indices.packages, "navGroup"); .package-links { [bind-elements] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "kit-element")); } [bind-element-bases] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "element-base")); } [bind-tools] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "tool")); } /* the standalone libraries: same rows, rendered flat (shell.css) */ [bind-libraries] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "library")); } [bind-library-group] ul:not(ul ul) { content: iterate(list.filter($package-indices.packages, "navGroup", "libraries")); } ul:not(ul ul) > li { $pkg: item.shortName; > spa-a { route-href: "/packages/#{item.shortName}"; } } /* one `content` rule per link: a second one would rewrite the first's text on every pass */ ul:not(ul ul, [bind-libraries] ul) > li > spa-a { content: item.shortName; } [bind-libraries] ul:not(ul ul) > li > spa-a { content: displayName(item.shortName); } /* packages whose docs span several pages: one collapsible group per section */ [bind-sections] { content: iterate(item.docSections); summary { content: item.title; } details > ul { content: iterate(item.docs); spa-a { route-href: "/packages/#{$pkg}/#{item.name}"; content: item.title; } } details:not(:has(spa-a[is-active])) { open: none; } } } /* page titles: the page, then the site. The docs home keeps the title in the document head; the other routes carry theirs in markup */ spa-route[is-active] { /* a site guide */ &[route-href$=":name"] { $title-guide: list.find($package-indices.docs, "name", $route.params.name); document-title: "#{$title-guide.title or $route.params.name} · Nucleus · docs"; } /* a package README */ &[route-href$=":packageName"] { document-title: "#{displayName($route.params.packageName)} · Nucleus · docs"; } /* one page of a package's docs */ &[route-href$=":docName"] { $title-package: list.find($package-indices.packages, "shortName", $route.params.packageName); document-title: "#{docTitle($title-package, $route.params.docName)} · #{displayName($route.params.packageName)} · Nucleus · docs"; } } /* the page's markdown file, for the footer link: the docs home and each guide the index lists, and a package's page whose index entry says `markdown` (build-docs-index sets it; the dev server's index has none, so no link there). siteDocHref and the path check say which URL is the page's own: the 404 and a package's doc pages have none */ > spa-manager[active-url] { $page-path: attr("active-url").split("#").at(0).split("?").at(0); $page-name: if($page-path == SITE_HOME: SITE_HOME_DOC; else: $page-path.split("/").at(-1)); $page-guide: list.find($package-indices.docs, "name", $page-name); $page-package: list.find($package-indices.packages, "shortName", $page-name); $page-markdown: if($page-guide and siteDocHref($page-guide.name) == $page-path: "/docs/#{$page-guide.name}.md"; $page-package.markdown and "#{SITE_BASE}/packages/#{$page-package.shortName}" == $page-path: "/#{$page-package.shortName}.md"); footer [bind-page-markdown] { href: $page-markdown; content: ternary($page-markdown, "Markdown version of this page"); } } } spa-route { $route: prop("provision"); /* paramless routes name their guide in markup (the docs home, `/`); /docs/:name gets it from params */ $route-doc-name: attr("data-doc-name"); } /* the desktop aside and the mobile sheet stamp the same nav template */ [data-site-nav] { details:has(spa-a[is-active]) { /* not kosher */ open: ""; } spa-a[is-active] { aria-current: ""; } spa-a:not([is-active]) { aria-current: none; } } /* mobile sheet: Escape / back gesture while open; a tapped link closes it */ #site-menu { &[is-open] dismiss-watcher { is-active: ""; } &:not([is-open]) dismiss-watcher { is-active: none; } @on click (target: "spa-a") { is-open: none; } } #site-menu-gesture { &:has(> #site-menu[is-open]) { progress-offset: 1; } &:not(:has(> #site-menu[is-open])) { progress-offset: 0; } @on gesture-handler-start { #site-menu { is-scrubbing: ""; } } @on gesture-handler-end { #site-menu { is-open: event.detail.snap == 1; is-scrubbing: none; } } } main { @on copy-source (handle: copySource); } #search-dialog[open] { > include-content { is-active: ""; /* for some reason this is necessary on first render */ @on include-content-did-render (handle: focusInput); } input[type="search"] { /* works on all subsequent opens */ autofocus: ""; } } #search-dialog:not([open]) input[type="search"] { autofocus: none; }

data-table

Sortable, filterable tables from plain custom tags — one behavior owner (<data-table> + <data-th>), everything else is CSS.

Features

  • Sort Click a <data-th> to visually sort by string, number, or date
  • Filter filter-value hides non-matching rows
  • Export The --export command downloads visible / all rows as CSV / JSON
  • DOM-stable Sort / filter via CSS only. Does not conflict with DOM owners, such as Quark.
  • Bindable counts .provision is { totalRows, visibleRows, sortColumnIndex, sortDirection, filterValue } — a "12 of 40 rows" readout is one Quark rule
  • Plain structural tags <data-thead> / <data-tbody> / <data-tr> / <data-td> are CSS-only — no registration cost

Installation

This package is available in the NucleusKit. Or it can be used by itself:

CDN Package Manager
<script src="https://unpkg.com/@excom/kit-utils@0.3.0/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@excom/neutron@0.2.0/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@excom/data-table@0.1.5/dist/index.umd.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@excom/data-table@0.1.5/dist/index.css">
npm install @excom/data-table
HTML Imports JS / CSS Imports
<!-- import path to `node_modules` will depend on your build setup -->
<script type="module" src="/node_modules/@excom/data-table"></script>
<link rel="stylesheet" href="/node_modules/@excom/data-table">
import "@excom/data-table";
@import "@excom/data-table/index.css";
Peer dependencies (0)

Packages a consumer must install alongside this one. Workspace deps are bundled.

Package Version
View Dist Files

All exports:

.
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./data-table
  • types: ./dist/data-table.d.ts
  • import: ./dist/data-table.js
  • default: ./dist/data-table.js
./data-table.js
  • types: ./dist/data-table.d.ts
  • import: ./dist/data-table.js
  • default: ./dist/data-table.js
./data-table.min
  • types: ./dist/data-table.d.ts
  • import: ./dist/data-table.min.js
  • default: ./dist/data-table.min.js
./data-table.min.js
  • types: ./dist/data-table.d.ts
  • import: ./dist/data-table.min.js
  • default: ./dist/data-table.min.js
./data-table.umd.min
  • types: ./dist/data-table.d.ts
  • default: ./dist/data-table.umd.min.js
./data-table.umd.min.js
  • types: ./dist/data-table.d.ts
  • default: ./dist/data-table.umd.min.js
./data-th
  • types: ./dist/data-th.d.ts
  • import: ./dist/data-th.js
  • default: ./dist/data-th.js
./data-th.js
  • types: ./dist/data-th.d.ts
  • import: ./dist/data-th.js
  • default: ./dist/data-th.js
./data-th.min
  • types: ./dist/data-th.d.ts
  • import: ./dist/data-th.min.js
  • default: ./dist/data-th.min.js
./data-th.min.js
  • types: ./dist/data-th.d.ts
  • import: ./dist/data-th.min.js
  • default: ./dist/data-th.min.js
./data-th.umd.min
  • types: ./dist/data-th.d.ts
  • default: ./dist/data-th.umd.min.js
./data-th.umd.min.js
  • types: ./dist/data-th.d.ts
  • default: ./dist/data-th.umd.min.js
./index
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./index.bundle.min.css
  • default: ./dist/index.bundle.min.css
./index.css
  • default: ./dist/index.css
./index.js
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./index.min
  • types: ./dist/index.d.ts
  • import: ./dist/index.min.js
  • default: ./dist/index.min.js
./index.min.css
  • default: ./dist/index.min.css
./index.min.js
  • types: ./dist/index.d.ts
  • import: ./dist/index.min.js
  • default: ./dist/index.min.js
./index.umd.min
  • types: ./dist/index.d.ts
  • default: ./dist/index.umd.min.js
./index.umd.min.js
  • types: ./dist/index.d.ts
  • default: ./dist/index.umd.min.js

Usage

Only <data-table> and <data-th> are registered custom elements. <data-thead>, <data-tbody>, <data-tr>, <data-td>, <data-tfoot>, and <data-tf> are plain tags — this package's CSS styles them as a table (or apply the equivalent .tag-data-* classes).

Sort and filter are visual only (CSS order / display). Row nodes never move or leave the DOM, so Quark bindings and iterate() tables keep working.

<data-table>
  <data-thead>
    <data-tr>
      <data-th column-type="string" sort-direction="asc">Name</data-th>
      <data-th column-type="number">Age</data-th>
    </data-tr>
  </data-thead>
  <data-tbody>
    <data-tr><data-td>Adam</data-td><data-td>36</data-td></data-tr>
    <data-tr><data-td>Beau</data-td><data-td>29</data-td></data-tr>
  </data-tbody>
</data-table>

<data-tbody> is required — sorting and filtering both operate on its <data-tr> children.

.provision reports the row counts and the active sort / filter, recomputed after connect, after a sort, and after every filter change. Read it from a rule matching the table:

data-table {
  $visible: prop("provision").visibleRows;
  $total: prop("provision").totalRows;
  [bind-count] { content: "#{$visible} of #{$total} rows"; }
}

API Reference

data-table

Attributes (2) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default option filter-casing
Match filter-value case-sensitively instead of the default case-insensitive comparison. filterCasing
boolean false
option filter-value
Hides data-tr rows (via --data-tr-display: none) whose text content doesn't include this value. Case-insensitive unless filter-casing is set. Unset / empty clears the filter. Rows stay in the DOM so Quark bindings survive. filterValue
string null
Provision (1) This provision property lives on the DOM node. Read / watch it with Quark’s prop("provision"), or listen for the neutron-provision event from app JS. Property Type provision
{ totalRows, visibleRows, sortColumnIndex, sortDirection, filterValue } — recomputed after connect, after the data-table-sort default action, and after every filter change. Not reflected as an attribute. provision
DataTableProvision
Events (3)
Dispatches — events that data-table fires and their default actions. Dispatch Default Action
A "default action" is subsequent logic executed by the element if e.preventDefault() is not synchronously called on the event.
Name data-table-sort
Cancelable. Dispatched when the active sort column / direction changes: on connect (if a data-th already has sort-direction) and after every data-th-sort. Call preventDefault() to take over sorting yourself.

Type DataTableSortEvent
Sorts event.detail.rows by columnIndex with sortFn and sets --data-tr-order on each row (visual CSS order — DOM order is unchanged).
Listeners — events that data-table listens for which will trigger subsequent actions. Listener Type Action data-th-sort DataThSortEvent
Bubbled up from a descendant data-th when its sort-direction changes. Sets that header as the active sort column (clearing sort-direction from the previously active one) and emits data-table-sort.
Commands — verbs data-table accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action --export Builds a file from the table's data-tr / data-td / data-th text content and downloads it. Options are data-* on the invoker: data-file-type (csv, the default, or json), data-file-name (default export_table_<locale-date>), and data-full to download every row in DOM order instead of only the visible rows in visual sort order. Filtering needs no command: write filter-value / filter-casing.
Recognized Elements (4) Child or descendant elements are recognized by data-table and relevant to its functionality. Element Relationship Required data-tbody
Required row container. Sorting and filtering both operate on its data-tr children.
descendant true
data-td
Cell within a data-tr, read as sortable / export cell content.
descendant true
data-th
Sortable column header. Clicking toggles its sort-direction and fires data-th-sort, which becomes the active column.
descendant true
data-tr
Row, direct child of data-tbody. Gets --data-tr-order on sort and --data-tr-display on filter. DOM order is unchanged.
descendant true
Styles (8)
Classes — optional classes that change the appearance of data-table. Class Description
Variables — public CSS variables for theming data-table. Opt-out:
If you wish to opt-out of these styles on a case-to-case basis, use property all: revert-layer. Valence.css ships this as .unstyled and .unstyled-all for the entire tree.
Variable Syntax Default --data-table-cols <integer> 1
Aliases — @custom-selector synonyms so custom elements can share semantic styles. Element aliases match the host tag / .tag-* class; state aliases match attributes (nested under the element alias). Alias Kind Matches :--data-table element data-table, .tag-data-table :--data-tbody element data-tbody, .tag-data-tbody :--data-td element data-td, .tag-data-td :--data-tf element data-tf, .tag-data-tf :--data-tfoot element data-tfoot, .tag-data-tfoot :--data-thead element data-thead, .tag-data-thead :--data-tr element data-tr, .tag-data-tr

data-th

Attributes (2) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default option column-type
Sort comparator to use for this column's cell values. data-table treats null as "string". columnType
string "string" | "number" | "date" null
hybrid sort-direction
Current sort direction. A click toggles between asc / desc; setting it (by any means) fires data-th-sort. Only one data-th per table should carry this at a time — the parent <data-table> clears the previously active header when a new one is set. sortDirection
string "asc" | "desc" null
Provision (0) This provision property lives on the DOM node. Read / watch it with Quark’s prop("provision"), or listen for the neutron-provision event from app JS. Property Type
Events (1)
Dispatches — events that data-th fires and their default actions. Dispatch Default Action
A "default action" is subsequent logic executed by the element if e.preventDefault() is not synchronously called on the event.
Name data-th-sort
Dispatched whenever sort-direction is set, whether by a click or programmatically. Bubbles to the parent <data-table>.

Type DataThSortEvent
Listeners — events that data-th listens for which will trigger subsequent actions. Listener Type Action
Commands — verbs data-th accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action
Recognized Elements (0) Child or descendant elements are recognized by data-th and relevant to its functionality. Element Relationship Required
Styles (4)
Classes — optional classes that change the appearance of data-th. Class Description
Variables — public CSS variables for theming data-th. Opt-out:
If you wish to opt-out of these styles on a case-to-case basis, use property all: revert-layer. Valence.css ships this as .unstyled and .unstyled-all for the entire tree.
Variable Syntax Default
Aliases — @custom-selector synonyms so custom elements can share semantic styles. Element aliases match the host tag / .tag-* class; state aliases match attributes (nested under the element alias). Alias Kind Matches :--data-th element data-th, .tag-data-th :--data-th--sort state :is(:--data-th) :--data-th--sort-asc state [sort-direction="asc"], [aria-sort="ascending"] :--data-th--sort-desc state [sort-direction="desc"], [aria-sort="descending"]
Release notes (1)

0.1.1

  • Ship only dist (and declared extras) in the npm tarball; drop build logs, tests and sources
View Source

Examples

Filter rows + export as CSV

Filtering is State: write filter-value / filter-casing on the table — here a Quark @on input block copies the search field into them. Matching is case-insensitive unless filter-casing is set.

Invoke --export on the table (<button command="--export" commandfor="…">) to download its visible rows (visual sort order). The button's data-file-type is csv (default) or json; data-file-name sets the download name; data-full downloads every row in DOM order, regardless of filtering / sorting.

The first export button is the happy path (the table as you see it, including active filter / sort). The form below it writes data-file-type / data-file-name / data-full onto its button.

Beta. The Nucleus Stack is in beta for a few weeks until features are stabilized and optimized.

Thanks — we'll email you when it ships.

Something went wrong. Please try again.