Changes to @umriss-ui/table
This document describes what changes for callers of the package: exports, props, behaviour.
The numbers follow the rule of @umriss-ui/core and @umriss-ui/charts: the
middle digit rises when something is added, the last one when something is
repaired, and whatever changes existing behaviour stands under a heading
"Changed" of its own, no matter which digit rose.
From 0.34.0 the packages share one number - five, and six from 0.40.0, when the dashboard joined: they are released together, and a package without a change of its own still gets a heading for it.
Names read forwards. Where an entry describes a name that has since been renamed, it is named here as it is called today; names that were removed stand as they stood.
Release candidates. 0.2.0-rc.0 and 0.2.0-rc.1 were the two versions on
the registry, under the tag next. With 0.2.0 that is over:
pnpm add @umriss-ui/table @umriss-ui/core is the whole install line. 0.1.0
was never published. Where an entry below names a @umriss-ui/core version, it
is one of the internal numbers from before core's first publication as 0.1.0
(see core's changelog).
0.40.0 – No change of its own (Oct. 2026)
Released with the other five packages under their one number. No change of its own.
0.39.0 – No change of its own (Oct. 2026)
Released with the other four packages under their one number. No change of its own.
0.38.0 – No change of its own (Oct. 2026)
Released with the other four packages under their one number. No change of its own.
0.37.0 – No change of its own (Oct. 2026)
Needs @umriss-ui/core 0.37; the peer range is ^0.37.0. No change of its
own.
0.36.0 – The tip for a cut value lies above the toasts in a dialog (Oct. 2026)
Needs @umriss-ui/core 0.36; the peer range is ^0.36.0.
Changed
- The tip that shows the whole of a cut value lies above the toasts inside a modal dialog, as core's tooltip does: there it stands in the top layer, where the browser has the Popover API. Outside a dialog nothing changes.
0.35.0 – React 19 is the floor, and a table that holds at two hundred thousand rows (Oct. 2026)
Needs @umriss-ui/core 0.35; the peer range is ^0.35.0.
Fixed
- A table with an aggregate no longer throws above roughly 125,000 rows. A minimum, a maximum or a range, and the decimals of a footer's sum or average, passed every row's value as an argument of one call, which the engine refuses with a
RangeError.
Changed
React 19 is required. The peers
reactandreact-domare>=19;0.34.0is the last version that serves React 18.A column's
value,sortValueandgroupValueand therowKeyare compared by their source text. A caller who writes them in the call hands the table a new function on every render; the table now takes a function with the same text for the same function, so nothing is filtered, sorted or keyed again. The limit: a function that reads an outer variable which has changed counts as unchanged, and the table keeps showing the old values. Give such a function a new text, or carry the variable in the row data. A bound or native function is compared by identity, and thepreFilteris not compared this way at all: it may read the caller's state as before.The package ships ES2022 syntax. An application that serves older browsers transpiles it itself.
Otherwise
- Scrolling, paging, folding and re-rendering no longer grow with the rows. The model is computed in stages, each held while what it reads is unchanged; an aggregate is computed once for a set of rows; a scroll inside the virtual window renders nothing. At 200,000 rows a scroll step takes 3.7 ms where it took 135 ms, a re-render of the caller with nothing changed 1.5 ms (308), a page turned 6.2 ms (312), a group folded 2.4 ms (902), a column hidden 2.8 ms (486). A sort reads each row's value once and takes 151 ms (488). The figures and how they were taken stand in the package's capability record.
0.34.0 – The alarm list leaves, and a view is set (Oct. 2026)
From this version all five packages carry one number and are released
together. Needs @umriss-ui/core 0.34; the peer range is ^0.34.0.
Fixed
A new row in a tree that is a grid has as many cells as the head. With
tree, grid mode,onRowAddand aRowDetail, the placeholder row for a new row counted the expander column the tree leaves out, and stood one cell too wide.A tree is never cut into pages. Where the body computed its rows anew - a column that registers after the hook has run - a tree with a
Paginationpart could be paged; the hook never paged a tree, and the two now agree.
Changed
AlarmList,alarmModeland every other export of the alarm model are removed. An application shows its alerts in aTable; the scenario "Work through the alerts" does.footeron a column andfilterin the hook's options are gone. They were the old names ofaggregateandpreFilter, deprecated since the rename. A column withfooter="sum"isaggregate="sum",useTable(rows, { filter })isuseTable(rows, { preFilter }); the old names are no longer read, so they do nothing, and the typeFooterForis no longer exported.initialViewis read once, andt.setView(view)goes to another view later. A differentinitialViewon a later render used to be applied when its content differed, and a view the table had reported itself, handed back within a second, was told apart by the clock; now it is ignored, as React ignores a changeddefaultValue. A caller who kept the reported view in state and handed it back (useStateplusonViewChange: setViewplusinitialView: view) writesconst t = useTable(rows, { initialView })and callst.setView(kept)where it restored; two tables kept in step useonViewChange: (v) => other.setView(v)in both directions.setViewis not reported throughonViewChange, which reports what the reader did, and a view with the content already shown changes nothing.
0.16.0 – Rows are copied, and the CSV follows the language (Oct. 2026)
Needs @umriss-ui/core 0.33; the peer range moves to ^0.33.0.
Added
The unit tests run against React 18.3.1 and 19 in CI, and the table is rendered to a string with no
windowin a test (tests-unit/ssr.test.tsx).A snooze's reason is shown in the tip and spoken.
<AlarmList>passes the reasonsnooze()stored (trimmed, none when blank) toavailabilitySnoozed: "Snoozed until 14:00 by M. Weber: valve being replaced". The badge stays one word. Needs@umriss-ui/corewith the third argument.Rows that change every second were tested, and what holds is stated in the README: sort, selection by key, an open Row draft, the Active cell, the scroll position and the virtual window across ticks (
tests-unit/rowsThatKeepArriving.test.tsx). No fault was found, so no ticket follows; grouping, tree rows and real scroll and focus in a browser are not proved.Ctrl or Cmd with C copies the selected rows, or - in grid mode with no selection - the Active cell. The rows come in the visible columns under a header line, tab-separated, with the export's values and the formats' decimal mark; a screen reader hears "3 rows copied", "Cell copied" or "Could not copy" through core's one voice (
announce) and not a region of the table's own. What was copied flashes once in an accent tint (held without the fade under reduced motion, the system's selection colours under forced colours). Marked text and a focused field keep the browser's own copy; in an open cell editor with nothing marked in it the key copies the value that stands in the field.selectionAsText()on the table hands the same text to a button of your own. Needs@umriss-ui/corewithannounce,copiedRows,copiedCellandcopyFailed.
Changed
The CSV follows the language in use (ADR-0061). With the English defaults the export now has commas between fields, a decimal point and
yes/no;asCsv()without a provider returns that text. A German application - wording and formats from@umriss-ui/core/wording/de- gets the file it got before, byte for byte. German wording with English formats gets German words with comma and point: take both halves (ADR-0024). The separator is a semicolon wherever the formats' decimal mark is a comma.Every entry begins with
"use client";. The built filedist/table.jsstarts with the directive, then the stylesheet import, so a bundler that splits server from client code takes each as client code. The directive is written to the built entries only; the sources are unchanged.Every export is a client component. A hook called in a Server Component now fails with React's message about a client reference, not one about
useState.
Fixed
- A sticking group line is no longer covered by the pinned cells of the rows it passes. With a pinned column and a scrolled body, the line's pinned part - its label, the fold, the checkbox - was painted over by the data rows' pinned cells, and only the part right of the pinned block showed. The line's pinned cells, and a span group's pinned cell, stand one level above them.
0.15.0 – A reason when snoozing (Oct. 2026)
Needs @umriss-ui/core 0.32; the peer range moves to ^0.32.0.
Added
<AlarmList snoozeReason="optional" | "required" />. "Snooze" opens a small panel instead of the menu: the durations (the first chosen), a field for the reason and a button that reports.onSnooze(ids, until, reason)gets the reason trimmed,undefinedwhen it is optional and left empty; with"required"the button waits for text. Escape and a click outside close the panel without reporting. Without the prop nothing changes.snooze(alarm, until, by, reason?)stores the reason on the alarm'ssnoozerecord (Snooze.reason); the list does not show it. Needs@umriss-ui/corewordingsnoozeDurationandsnoozeReason.
0.14.0 – Snoozing offered, and a search that takes its line (Oct. 2026)
Needs @umriss-ui/core 0.31; the peer range moves to ^0.31.0.
Added
<AlarmList onSnooze snoozeDurations onUnsnooze />. The list offers snoozing as a reported intent, beside "Acknowledge" and on the same ticked alarms:onSnooze(ids, until)after the operator chose one ofsnoozeDurations(milliseconds, no default list),onUnsnooze(ids)for the snoozed ones. The list changes nothing; the application callssnooze()orunsnooze()on its own data, or refuses. Each action appears only with its handler. Needs@umriss-ui/corewith the four new snooze wordings.<Search grow />. The search takes the rest of its line in the table toolbar; the parts after it - column menu, export - stand at the end of the line. Withoutgrowthe search stays its natural width.
0.13.6 – Core 0.30.0 (Oct. 2026)
Needs @umriss-ui/core 0.30; the peer range moves to ^0.30.0. Nothing else changes for a caller.
0.13.5 – Core 0.29.0 (Oct. 2026)
Needs @umriss-ui/core 0.29; the peer range moves to ^0.29.0. Nothing else changes for a caller.
0.13.4 – Core 0.28.0 (Oct. 2026)
Needs @umriss-ui/core 0.28; the peer range moves to ^0.28.0. Nothing else changes for a caller.
0.13.3 – Core 0.27.0 (Oct. 2026)
Needs @umriss-ui/core 0.27; the peer range moves to ^0.27.0. Nothing else changes for a caller.
0.13.2 – Core 0.26.0 (Oct. 2026)
Needs @umriss-ui/core 0.26; the peer range moves to ^0.26.0. Nothing else changes for a caller.
0.13.1 – A head that holds its width (Oct. 2026)
Changed
- A column with a
widthis that wide, whatever its label says. Chromium and Safari let a long label widen the column past its width; now the label ends in an ellipsis, and the tip that completes a cut value shows it whole - under the pointer after the tooltip's delay, at once when the keyboard reaches the sort button. A column without a width grows with its label, as before. "Fit to content" counts the whole label. - A width too narrow for the head's sort arrow, rank and filter is raised to them, and a warning in development names the column once.
Fixed
- The sort arrow is never cut. In Firefox a 75 px column with a long label cut its arrow at the edge, and a sorted column read as unsorted. The rank and the filter's funnel stay as well. The page Width and pinning shows it in "A label wider than its column".
0.13.0 – The table holds its view, and server mode (Oct. 2026)
Released together with @umriss-ui/charts and @umriss-ui/schedule, which
break in the same release (ADR-0047). A view handed in applies whenever its
content changes, every change of it is reported in every mode, and manual mode
is called what it is: server mode, reporting the Request - the five parts
that decide the rows - and nothing else. The old names are removed, not
deprecated.
Changed
Moving over. Every name that went, and what stands in its place:
| Was | Is |
|---|---|
useTable(rows, { manual: true, rowCount, onViewChange }) |
useTable(rows, { server: true, rowCount, onRequest }) |
ManualView (the whole view, the five parts always present) |
TableRequest - search, conditions, sort, page and page size, and nothing else |
onViewChange(view: ManualView) in manual mode |
onRequest(request: TableRequest); onViewChange is the general report of every mode now (see Added) |
t.manual, TableSnapshot.manual, TableRef.manual |
t.server, TableSnapshot.server, TableRef.server |
a key to start a table again from another view |
the view handed in as initialView - a key still restarts |
- Manual mode is server mode. The option, the snapshot's flag, the type
of its report and the report itself are renamed as above; what the mode
does is unchanged. The DEV warnings say so and their ids read
server-prefilter,server-virtual,server-grouping,server-groupable,server-groupable-columnandserver-filter-options. - The request is the five parts and nothing else.
ManualViewcarried the whole view, widths, order and pins included, though it was reported only when one of the five changed;TableRequestis exactly search, conditions, sort, page and page size, each present at its default too. It is still reported once when the table first stands and once per change. initialViewapplies whenever its content differs from the last one handed in, no longer on the first render only. The same view again changes nothing - a view written in the call is compared by its content, not its identity; what a new one leaves out goes back to its default, conditions, grouping, folds, pins and branches included; leaving it out keeps the table's own. A table that was handed a changinginitialViewand ignored it now goes to it. A known limit: a view handed in that equals one the table reported less than a second ago, and that has not come back yet, is taken for the table's own report coming back late and is not applied - so that tables kept in step do not jump back. An application that restores a just-reported view within that second sees no change.
Added
onViewChange(view)in every mode: every change of the view, once, always the whole view - the one to keep, or to hand another table as itsinitialView; not the view the table starts with. In server mode it hears whatonRequestdoes not: a width, an order, a pin, a fold, a hidden column.- The demo: the page 'Manual mode' is 'Server mode' at
/server-mode/; the old address lands there. The 'View' example keeps the reported view and hands it back, without akey.
0.12.0 – Every export explained, and actions that may scroll (Oct. 2026)
The package explains itself where it is used: every export carries a comment,
every default a @default tag, every type it names can be imported, and the
demo lists all of it in an API index (ADR-0044). RowActions may leave its
column in the flow. Needs @umriss-ui/core 0.25; the peer range moves to
^0.25.0.
Added
RowActionstakespin:pin={false}leaves the actions column in the flow instead of sticking at the end of the row, for a place the application finds too narrow for a pinned block - it measures the place and decides. A column pinned to the end still takes the actions with it, and an open Row draft's Save still sticks. The default,true, is the behaviour so far.- Every type a props table or a signature names can be imported:
Present,Absent,Pin,ToolbarSize,FormatFor,FilterFor,GroupFor,FooterFor,AggregateForandPresentationare new public types, for annotating one's own variables and wrappers with what a column or a toolbar part accepts. So are the types a page's table headers and type definitions name:ColumnBase,FieldColumn,ValuePaths,GroupByBase,VerdictBase,Displayable,IsDisplayable,DatePeriodandRowGroup; and those a hook's or a function's signature names:ModelColumn- whatalarmModelandALARM_COLUMNSread of a column, so far an unexportedColumnin the.d.ts-,TableInput,TreeInput,GroupLevel,AggregateColumn,AggregateKind,OwnAggregate,Computed,NumberField,AlarmBaseandAlarmAvailability. Type exports only; nothing else changes. - Every export carries a comment: what a component, a hook, a function or a
type is for, and for a hook or a function
@paramand@returnswhere its type alone does not say it. The editor shows it on hover, from the package's.d.ts. Nothing else changes. - A prop's default stands in a
@defaulttag where its comment used to say it in words: the editor shows it on hover, and the props table in its Default column. Nothing else changes. - The demo has an API index:
/table/api/, the last entry of the sidebar, lists every export of@umriss-ui/table, grouped as components, hooks, functions, constants and types, alphabetical - each value with its comment and its declaration, each type with a link to its table or its definition, and the pages that use it. A hook or a function a page names, in its text or its import line, leads to its entry.docs/llms-full.mdends with the same index, in place of the appendix "The rest of the API". The library is unchanged. docs/llms-full.mddefines every type a props table names: a type without a table of its own (TableRef,Pin, core'sLimit) stands under "Types on this page" of each page that names it, with its comment and its members or its declaration.- Every page says its keys: First table has a Keyboard section of its
own (Enter and Space on a sortable header, Escape on a cut value's tip), and
every other page whose examples take Tab links the keys that apply there -
First table's, ColumnMenu's, RowActions', and those of core's Input, Select,
Popover, Checkbox, Button and the other controls a part is built from.
First table, Column, Formats, Row appearance, RowActions, Toolbar and
AlarmList gain a section Accessibility: the table's name and row header,
aria-sort,aria-busy, the toolbar's count of matches, the alarm list's one live number, and the label a presentation's component needs. In the demo, the Markdown twins anddocs/llms-full.md; the table is unchanged. - Every prop of a props table is shown, or named as an exception: new
examples for stripes across a wide table (
striped), a selection held by the application (selectionfromuseTableSelection), an own filter that offers the values that occur (values) and the toolbar's parts outside a toolbar (size, the pager'sof). The five props left without an example are pass-throughs, a deprecated alias and a twin of a prop shown on a sibling type; the demo's list says so for each. The table is unchanged.
Fixed
docs/llms-full.mdnames the peer in its install line:npm install @umriss-ui/table @umriss-ui/core, read from the manifest. It had named the table alone, and an install after it lacked core.
0.11.2 – Core 0.24.0 (Oct. 2026)
Needs @umriss-ui/core 0.24; the peer range moves to ^0.24.0. Nothing else changes for a caller.
0.11.1 – Core 0.23.0 (Oct. 2026)
Needs @umriss-ui/core 0.23; the peer range moves to ^0.23.0. Nothing else changes for a caller.
0.11.0 – Rows that hold still (Oct. 2026)
A row's height never follows what it shows (ADR-0042). Needs @umriss-ui/core
0.22 for --u-delay-stale; the peer range moves to ^0.22.0.
Changed
- Every line of the table is one row pitch. The head, a row, a group header, a placeholder and the footer are 36 px at the regular density, 27 px at the compact one under a mouse and 35 px under a finger - in Chromium, Firefox and WebKit alike. A row with a checkbox, a fold or a badge is no longer taller than one without; a row was 36.5 px before, rounded three ways.
- A value stays on one line. Nothing in a cell wraps; text ends in an
ellipsis, and a tip shows a cut value whole under the pointer, or at once on
the Active cell. A component that does not fit - a row of badges, one of the
application's own - is cut at the column's edge, and one taller than a row
at the row's, which a warning in development names. The page Formats shows
it in "Fit what does not fit". A column without a width grows with its values up to
min(20rem, 60vw); a column with a width is exactly that wide. A cell's content now stands in a box inside the cell: a test that finds a cell by its text takesclosest("td, th"). - Controls in the table are small. The table is a
ControlSizeProvideratsm, so aButton,Selector editor in a cell fits the row. - A page holds its height. With a
Pagination, a page keeps the most rows it has shown, up topageSize: a short last page, a search or a filter that leaves a few rows, and an empty result end in a filler of the rows they lack - the empty result with its message at the top; a table that never had more rows stays as it is. A grouped page ispageSizelines, the group headers it repeats counted (one page may now end a line earlier than before); the first load shows a page of placeholders. loadingover rows keeps them. The rows stay, dim after 200 ms and take no pointer untilloadingis false; placeholders stand only where there are no rows. An application that wants placeholders on every request hands in no rows while it loads.
Removed
virtual.rowHeight. The window counts in the pitch the table measures from its head.virtual: { rowHeight: 37 }becomesvirtual: true;virtual: { rowHeight: 37, overscan: 8 }becomesvirtual: { overscan: 8 }.
0.10.0 – A toolbar that sizes its controls (Oct. 2026)
Needs @umriss-ui/core 0.21, whose fields hold still and take a place's size;
the peer range moves to ^0.21.0.
Changed
Toolbarsets the size of the controls a caller puts into it. It is aControlSizeProviderat itssize: aSelect,MultiSelectorButtonof one's own stands atsmbeside the search without a word, atmdinToolbar size="md", and keeps its ownsizeif it says one.- No part of the toolbar is given a width any more. A field from
@umriss-ui/coreis its natural width there by itself and never what it shows: the search is no longer 160 px, the page-size select ofPaginationis as wide as its longest size instead of 64 px, and a multiselect no longer grows with its chips. - Demo: the Toolbar controls and Filter examples lost their widths in
styleand theirsize="sm".
0.9.0 – Row filters, and one size for the toolbar (Oct. 2026)
Needs @umriss-ui/core 0.20, whose MultiSelect and Combobox take the
toolbar's sizes; the peer range moves to ^0.20.0.
Added
- Row filters:
rowFilter({ id, label, matches, describe? })- a condition the application defines over the whole row, by a field no column shows or by several at once ("overdue" = past its window and not delivered). Defined once, outside the component, and named inrowFiltersonuseTable.t.setFilter(filter, condition)sets it from any control of the application's own,t.conditionOf(filter)reads it back - both typed bymatches' second parameter;nulllifts it.- Its condition is a condition like a column's: "43 of 1,204" counts it,
"Reset" lifts it, a change goes back to page one,
t.view.conditionscarries it under its id (andinitialViewrestores it), manual mode reports it to the server. Row filters and column filters combine with AND. - With
describeit stands in the toolbar as a chip "label: text" whose cross lifts it; without, the application's control shows it. A table with row filters puts up a toolbar of its own where none is placed.
Toolbartakessize,"sm"(the default) or"md", and every part the table puts into it follows:Search,ColumnMenu,Export, "Reset", the bulk actions and "New row".Search,ColumnMenuandExporteach take their ownsizeas well, which wins.- Demo: the page Toolbar controls, and on the Filter page a field no column shows, rows from a request, and a question over several fields.
Changed
- In a table toolbar every part is as wide as its content, unless it says
its own width. A
SelectorComboboxfrom@umriss-ui/core- 100 % wide anywhere - took a line of its own there. The search keeps its width.
Fixed
- A pre-filter over rows written in the call no longer loops. Rows that
are a new array on every render (
data ?? [],data.map(...)) together withpreFilterended in "Too many re-renders". - The ratio and "Reset" no longer stand over the search in Safari. The
right part of the toolbar left the flow while the ratio was empty through a
:has(:empty)rule Safari did not ask again when the ratio got its text.
0.8.0 – Tree rows (Oct. 2026)
Needs @umriss-ui/core 0.19: it filters through the tree model's new match
and reads the tree rows' wording; the peer range moves to ^0.19.0.
Added
- Tree rows:
childRowsonuseTablenames the children of a row; the rows passed in are the roots. Every row with children gets a fold in the first column that opens the next level beneath it, indented - as uneven as the application's hierarchy is. The figures of every level are the application's; the table sums nothing for a branch.- Every level sorts on its own; a child never leaves its parent.
- A search or a condition shows a match with the rows above it - path
rows, muted and said to be one - without writing the open branches.
"43 of 1,204" counts matches and rows on every level;
filteredand "select all" hold the matches on every level. - The footer sums the roots only, since a parent holds its children, and says so ("Top level only").
- The export writes every row of the filtered tree, open or not, with a first column "Level", 1 for a root.
defaultBranches- keys, or how many levels stand open; the open branches stand in the view asbranches. The snapshot hasbranches,toggleBranch,unfoldAllBranchesandfoldAllBranches; theColumnMenuoffers "Unfold all" and "Fold all".- The fold has the group fold's keys: → opens, ← closes or goes to the
parent's fold, Alt with either acts on the siblings too. A tree is a
treegridin both modes; in grid mode the arrows keep walking the cells. - A branch reads by weight - medium across its row, a leaf regular - and a root after the first carries a group header's stronger line above it.
- A row's
RowDetailopens with the row's own fold - one chevron per row; a tree has no expander column. The detail hangs from the fold on a rail, without a box, and a table inside it drops its frame. A branch's fold opens its detail and then its children, as one state: its detail is open whenever the branch is - by default, by a view, by "Unfold all". - Not with grouping or pagination (each passed over with a warning in development), not in manual mode (a compile error).
Changed
- A
RowDetailthat comes to nothing -null,undefinedorfalsefor a row - gives that row no expander and no detail line. It had an expander that opened an empty line.
0.7.5 – Core 0.18.0 (Sep. 2026)
Needs @umriss-ui/core 0.18; the peer range moves to ^0.18.0. Nothing else changes for a caller.
0.7.4 – Core 0.17.0 (Sep. 2026)
Needs @umriss-ui/core 0.17; the peer range moves to ^0.17.0.
Changed
- A row's actions menu opens from an
IconButton: a 26px square in place of an oblong, with its name ("Actions for …") as a tooltip.
0.7.3 – Room on a phone (Sep. 2026)
Needs @umriss-ui/core 0.16; the peer range moves to ^0.16.0.
Changed
- Pinned blocks take at most half of the width a table shows. On a narrower table the end block, then the start block scrolls with the rest, and sticks again once there is room: on a phone a Row draft's actions column or both blocks of a wide table left nothing between them to scroll. The pin itself, in the column menu and the view, stays.
- An actions column that holds nothing at rest - a Row draft's buttons, no Delete, no row action - sticks only while a draft is open.
- Row actions stick at the end as a block of their own, as a grid's Row draft buttons already did: a table a little too wide for a phone cut the last button ("Forwa").
- A short value without a space stays on one line: an id, a date or a
string a column's
childrenreturns, such asFP-1004223,2026-03-16or06:00–08:00, no longer breaks at its hyphen or dash. - A table that scrolls sideways fades out at the edge where more columns lie (and no pinned block stands), so a value cut there reads as one that scrolls on.
- A span's value is cut at 35 % of the window instead of 45 %, below its 18rem: on a phone it took 60 % of the table.
Fixed
- The pagination bar wraps the page and its two steps as one; on a phone "Next" stood alone on a line beneath "Back".
- The empty body's message stays in view in a table wider than its scroll area, instead of being centred across the full width and cut.
AlarmList's alarm column keeps 10rem: on a phone it was the width of its longest word, and every name wrapped over five lines. Its duration holds one line: "3 hrs 18" no longer stands over "min".
0.7.2 – Room in a dense row (Sep. 2026)
Needs @umriss-ui/core 0.15: a compact table reads its new
--u-control-height-xs; the peer range moves to ^0.15.0.
Changed
- A compact table's controls are a step smaller: at
density="compact"a Row draft's fields and buttons, its row actions and a cell's editor are 18px tall instead of 26px, which filled a compact row edge to edge. Under a coarse pointer (touch) they keep 26px. - A cell editor keeps its cell's side padding instead of 4px, in both densities: a field no longer stands against the frame or against its neighbour's field.
- A row with actions stands as tall as a row of text. The actions cell and, in a compact table, the selection and expander cells trim their vertical padding.
- The toolbar's search is 160px wide in the toolbar, instead of taking a line of its own on a phone.
Fixed
- A narrow table's toolbar wraps its parts - by the table's width, not by the window's: beside a sidebar the search, the buttons and the grouping tag ran out of the frame and over one another. An empty ratio no longer leaves an empty line beneath them.
- Save and Discard, and a delete's question, stand 8px apart; at 4px the filled button and the quiet one beside it read as one piece.
- A compact head's sort button no longer reaches past the head.
0.7.1 – Found by search (Sep. 2026)
Fixed
- The package's description and keywords say what it is in the words a
search uses, and the demo it links to addresses every page by a path
(
…/table/<page>/) instead of a hash, with the page's text readable before the demo starts. Old#/<page>links still land. Nothing in the package's code changed.
0.7.0 – Rows edited on purpose (Sep. 2026)
Needs @umriss-ui/core 0.14: it reads the new row wording keys; the peer
range moves to ^0.14.0. All of it applies to a table in grid mode
(<Table grid>), as editing did (ADR-0036).
Added
editMode="row"opens every cell of a row that edits at once, as one Row draft, and reports nothing until the row is saved on purpose - by its Save button or Enter; Discard or Escape drops it.onRowSavereceives{ rowKey, changes, row }with only the columns that changed; a row saved unchanged reports nothing. A row with a draft is left only by saving or discarding it: a click into another row is refused with "Save or discard this row first". On Save every open cell runs itsvalidate, and the row stays open until all pass.editMode="cell"stays the default.onRowAddputs up a "New row" button - in the table toolbar, or beneath the table where none stands. It opens an empty Row draft above the rows, whatever the sort, filter or page;newRowgives the values it starts with. Saved, it is reported whole as{ values }by column id and appears when the rows passed in carry it. Both edit modes.onRowDeleteputs a Delete button on every row; pressed, it asks "Delete?" inside its row, and only the confirmation reports{ rowKey, row }. Both edit modes.- The types
RowSave,RowAddandRowDelete.
Changed
- A click on a cell that edits opens its editor. It only made the cell active; Enter, F2 or typing opened it. A select opens with its list shown, a day with its calendar. The arrow keys and Tab still walk without editing.
- A cell that edits says so under the pointer: a text cursor over a text or a number, a pointer over a select, a day or an editor of one's own, and a field's edge on hover. Nothing marks it at rest.
- A click outside the table ends an edit of one cell as a click on another cell does: reported where it validates, left open with its message where not; the focus stays where the click put it. A click in the panel the editor opened - a day's calendar - is inside. A Row draft stays open.
- The actions column stands pinned at the end in a grid with
editMode="row",onRowAddoronRowDelete, and holds the width of its widest buttons from the start, so a draft or a delete's question shifts no column. A row keeps its height while its draft is open.
0.6.2 – Averages without float noise (Sep. 2026)
Changed
- A sum and an average in a column without a
formatare written to the digits their values carry. They were written to full precision, which is right for a measurement and wrong for a computed value: three estimates of 18, 12 and 10 hours averaged to13.3333333333. A sum now keeps the decimals of its most precise value, an average one more - whole hours average to tenths,13.3. The count is fixed, so that the totals of a column stand aligned on the point (12.0beside13.3); a sum of0.5and0.5reads1.0where it read1. In the footer and in every group header alike. A column with aformatwrites its aggregates in that format as before;min,maxandrangeare values that went in and are written as they are.
0.6.1 – Core 0.13.0 (Sep. 2026)
Needs @umriss-ui/core 0.13; the peer range moves to ^0.13.0. Nothing else changes for a caller.
0.6.0 – Tooltips and a column menu that opens by its button (Sep. 2026)
Needs @umriss-ui/core 0.12: it reads the new tooltip wording keys; the peer range moves to ^0.12.0.
Added
- Tooltips on the icon keys. The column menu's move and pin keys and a column's filter key show what they do on hover and focus: "Move forward", "Pin to start", "Unpin", "Filter". The keys' accessible names are unchanged.
Changed
- The column menu opens at its button's start edge. The toolbar puts its parts at its start, and the panel grew away from them over the page. It still flips to the end edge where there is no room. A condition in the toolbar opens its filter the same way; the filter key in a header keeps the end edge, where it stands.
Fixed
- The column menu of a wide table no longer stretches across the window. The grouping choices stood in one line and set the panel's width; they now wrap within the column list.
0.5.0 – Alarms without a plant (Sep. 2026)
Needs @umriss-ui/core 0.11: it reads the renamed alarm wording keys; the peer range moves to ^0.11.0.
Changed
The alarm names no longer assume a plant (ADR-0035). One hard cut before 1.0, with no deprecated aliases: rename as below and the compiler finds the rest. The concepts are ISA-18.2's as before;
docs/standards.mdmaps each ISA term to the name here. The string values change with the names, and so dodata-lifecycleanddata-availabilityin the DOM.Before Now "standing-unacknowledged","standing-acknowledged""active-unacknowledged","active-acknowledged""cleared-unacknowledged","cleared-acknowledged""resolved-unacknowledged","resolved-acknowledged"Transition"cleared""resolved"Alarm.cleared(the moment)Alarm.resolvedisStandingisActiveAlarmProjection.standingUnacknowledgedAlarmProjection.activeUnacknowledgedAvailability"shelved""snoozed"Availability"suppressed-by-design""suppressed"Availability"out-of-service""disabled"shelve(alarm, until, by),unshelve(alarm)snooze(alarm, until, by),unsnooze(alarm)takeOutOfService(alarm),returnToService(alarm)disable(alarm),enable(alarm)the type Shelf, the fieldAlarm.shelfthe type Snooze, the fieldAlarm.snoozeisHiddenFromOperationisHiddenAlarmProjection.hiddenFromOperationAlarmProjection.hiddenAlarmsReturnBand.direction"obere","untere""upper","lower""in-service"andacknowledgekeep their names.<AlarmList>reads the renamed wording keys, so its words change with them: "Active" and "Resolved" for the lifecycle, "Snoozed", "Suppressed" and "Disabled" for the availability, "Hidden: 3" for the count.
0.4.0 – Grid, server, pinning, availability (Sep. 2026)
Needs @umriss-ui/core 0.10: it reads the new wording keys and --u-shadow-sticky; the peer range moves to ^0.10.0.
Added
- Manual mode, for data the browser does not hold:
useTable(page, { manual: true, rowCount, onViewChange }). The rows are the page a server answered androwCountits filtered set; the table searches, filters, sorts and pages nothing of its own.onViewChange(view)receives aManualView- search, conditions, sort, page and page size, always present - once when the table first stands and once per change of those five (a width or a hidden column fetches nothing).loadingthen lays as many placeholders as the page had rows over it, in its rows' height and its columns' widths, witharia-busy. A list filter offers whatfilterOptions(column)names (with a warning in development where it names nothing). Keys selected on another page stay selected, and a bulk action receives every selected row the table has seen; "select all" selects the page and says so ("Select all on this page"). The export writes the page, and its button says so ("Export page"). There is no grouping, no footer, no pre-filter and no virtualisation in manual mode - each would be the page's and not the server's; a grouping asked for is passed over with a warning in development.TableSnapshotgainsrowCountandmanual. Withoutmanualnothing changes. - Grid mode, on request (ADR-0034):
<Table grid>makes the tablerole="grid"(treegridwhen grouped) and one tab stop with an Active cell - the arrows walk the cells of the head, the rows, group headers, folded spans, details and the footer; Home and End go to the row's ends, Ctrl+Home and Ctrl+End to the table's, PageUp and PageDown by the rows in view. A cell's own controls leave the tab order and are reached with Enter or F2 and left with Escape. The Active cell holds on to its row and its column: it follows a sort, stands where a filtered row stood, and walks to rows a virtual window has not rendered. Withoutgridnothing changes. - Editing in place:
editon a column -"text","number","select"(overeditOptions, or the values that occur),"date", or an editor of one's own receivingCellEditorProps- gives it the core field for its value. Enter, F2 or typing starts an edit, Enter commits, Escape cancels, Tab commits and moves on to the next cell that edits; a picked day commits at once.validate(value, row)keeps a draft that does not pass open, its message beneath the field. The table applies nothing:onCellEdit({ rowKey, columnId, value, row })on<Table>reports, and the cell shows the value once the rows carry it. New typesCellEdit,CellEditorProps,EditFor,EditOptions. - The Windows contrast mode (
forced-colors: active, forced-colors 03). A selected row keeps its selection as lines in the system's selection colour and a focused virtual row its ring as lines in the text colour, both drawn inside the cells, where the sticky cells cannot paint over them; the frame, the panels and every focus ring keep an outline where the box-shadow stood, and a group's share bar a system colour. Nothing changes outside forced colours. docs/llms-full.md, the package's documentation as one Markdown file for a coding agent, in the npm package and pinned to its version: every demo page with its import line, its examples' source, its props tables and why it is built as it is, and the declaration of every other export. The same text stands online as https://romanhaendler.github.io/umriss-ui/table/llms-full.txt, with an index of the pages beside it (llms.txt).- Availability, the second field beside the lifecycle (ISA-18.2's special
states):
availability?: "in-service" | "snoozed" | "suppressed" | "disabled"onAlarm, never merged into the four lifecycle values. Without a statement an alarm is in service, so existing alarms read as before. A shelved alarm carriessnooze: { until, by }- and cannot be written without it. - Four pure transitions, one alarm in and one out, performed by the
application:
snooze(alarm, until, by),unsnooze,disable,enable. One that does not apply returns the same object. A shelf never overwrites out of service or suppressed by design, and taking out of service never overwrites suppressed by design - the plant's logic owns it. availabilityAt(alarm, asOf): a shelf that has reached its end is in service again at the as-of time - by the model's clock, not by a timer.AlarmRow.availabilitycarries that value.isHidden,AVAILABILITIES, the typesAvailabilityandSnooze, and on the projectionhiddenAlarms: how many of the filtered set are hidden. The model still removes nothing.- An
availabilitycolumn inalarmColumns/ALARM_COLUMNS(its label fromwording.columnAvailability), valued by rank like the priority. <AlarmList>shows what is hidden from operation: the row stays, drawn neutrally - no edge, the priority a word without its colour, the type muted - with its availability as a word before its lifecycle ("Shelved until 11:10 by M. Keller"). The bar counts them ("Hidden from operation: 3").hiddenOnlyandonHiddenOnlyChangeon<AlarmList>: with the handler the count becomes a switch for the view; the application filters with the table's ownfilterandisHidden. The switch stays while the view is on, even at zero.- Any column can be pinned:
pin="start"orpin="end"on aColumnor aVerdictColumnkeeps it in view while the table scrolls sideways, in a block before or after every other column. The selection and the expander stick with the start block (and a group span with them), the row actions with the end block; a group header's label and aggregates stick in their blocks, and virtual rows pin like any other. A shadow on a block's inner edge shows only while content lies under it. t.pinnedandt.setPin(column, "start" | "end" | null), andpinnedin the view: the user's choice, whole, once it deviates from what the columns declare ({}when every declared pin was undone). A view handed in asinitialViewrestores it.- The column menu pins: two keys per column, "Pin to start" and "Pin to end"; the key of the side a column is pinned to stands in the accent and reads "Unpin". The focus stays on the key that was pressed while the entry moves to its block.
Changed
stickyRowHeaderispin="start"on the row header. It looks as before; the difference is that the row header can now be unpinned (withsetPin) and share its block with other pinned columns. The sticky offsets are measured from the head row instead of assumed from the 34 px of a control cell, so a control cell that grows no longer slides under the row header.In the column menu a column moves only inside its block (start, the unpinned ones, end). Before, only the sticky row header was held in place.
DEFAULT_ORDERbegins with availability: in service above hidden, then priority, acknowledgement and time as before. Only alarms with an availability of their own move.activeUnacknowledgedcounts only alarms in service. The live figure calls somebody over; it must not call them to a shelved alarm.A done alarm's row (
keepDone) is muted as it always claimed to be. Its colour stood on the row, and the table's cells set their own; it now stands on the cells.Final polish of pinning, grid mode and the alarm list (table-column-pinning 03, table-grid-mode 05, alarm-standards 05):
- The shadow of a pinned block and of a stuck group header comes from core's
--u-shadow-sticky: dark as a shadow in the dark theme, no longer a glow. - A group header draws its top line inside its cells in every table, as a table with pinned columns already did - one drawing, one look.
- In grid mode the open editor lies over its cell at the cell's size, and
the message of a draft that does not validate hangs beneath it in a
popover (core
Popover); nothing in the table shifts while editing. - The scroll area carries a
scroll-paddingof the sticky head's height and the pinned blocks' widths, so a cell the keys walk to is scrolled clear of them; a pinned cell's own scroll target is shifted out of that padding, so focusing it does not scroll the table back. - Space in a grid with selection selects or deselects the Active cell's row, from any of its cells (as AG Grid and MUI do); without selection it does nothing - it no longer starts a text edit either.
- In a grid with an editable column every cell that does not edit carries
aria-readonly="true". AlarmListshows the availability as a small neutral badge before the lifecycle state; until when and by whom stand in its tooltip and are spoken with it. The state keeps to one line. A shelf that ends on another day than the as-of time shows its date beside the time. The "Hidden from operation" switch's label is in the live figure's size.
- The shadow of a pinned block and of a stuck group header comes from core's
Alarmis a type, no longer an interface - the union with the shelf needs it.Partial<Alarm>spread into anAlarmno longer compiles; name the fields you mean (Pick<Alarm, "resolved" | "acknowledgedAt">).
Fixed
- A table's rounded corner stays whole under a sticky head or pinned cells: the scroll area carries the frame's radius and clips to it.
- A group header whose first column carries an aggregate keeps its aggregates under their columns. Grouped by one level, its label stood in a cell over no column, which a browser counts as one, and every aggregate stood a column too far right. The label now takes the first column's place in the header; its sum stays in the footer.
0.3.3 – Depth work on the table (Sep. 2026)
Needs @umriss-ui/core 0.9: it reads --u-tracking-figures; the peer range moves to ^0.9.0.
Changed
- Every head reads the same way: label, arrow, funnel. A numeric head mirrored that order, so its arrow and funnel stood before the label - and its funnel beside the funnel of the column before. The group now keeps the order in every column and stands flush right in a numeric one, ending where the figures end.
- A selected row carries core's
--u-color-accent-subtle, a step deeper under the pointer. Before, only the checkbox said a row was selected. - The head follows
density="compact". Its padding shrank with the rows' and it stood half again as tall as a row below it. - The head is 1 to 3 px lower in every table: the sort key sits at the top of its cell instead of on the baseline of its label.
Fixed
- A numeric head's label stands on the line of its neighbours'; it stood 1.13 px higher.
- A focused virtual row shows its ring all round. The sticky selection and row-header cells painted over it; the cells now draw it themselves.
- A loading placeholder stands where its value will: right in a numeric column.
0.3.2 – Core 0.8.0: the canon of states (Sep. 2026)
Needs @umriss-ui/core 0.8: it reads core's pressed surfaces and motion tokens; the peer range moves to ^0.8.0.
Changed
Pressed and disabled follow core's canon of interaction states. The filter key, the fold and the row expander, the column menu's move keys and the grouping options sink while pressed. A disabled move key or grouping option dims to half opacity with the not-allowed cursor; the move key was dimmed twice (opacity and muted type), the grouping option only repainted.
The sort arrow turns as a path. Its half turn ran on the 120 ms of a hover; it now reads core's
--u-transition-path(140 ms), like the fold chevrons beside it. Hover and colour changes run on core's own curve since--u-transitioncarries it.The glyphs keep core's one stroke width (1.4 per 10 units): the sort arrow, the sortable indicator, the column reordering's arrows and the group fold are a shade lighter, the fold drawn in the nominal box. The row expander is core's
AngleGlyph. The sort arrow and indicator are nowaria-hiddenthemselves, not only through their container.
0.3.1 – The focus survives a fold (Sep. 2026)
Fixed
- The focus survives a fold. When a fold hides the element that holds the
focus - a row's checkbox, a cell, a row action, the fold of an inner group -
the focus goes to the fold of the nearest group that still stands for it: its
folded line or its header. Before, it fell to the page whenever it was not on
the fold that was pressed. This holds for
toggleFold,foldAll, the tag menu's "Fold all" and Alt-click or Alt+arrow on a sibling's fold; a focus outside the folded group stays where it is.
Changed
- Danger as type reads core's
--u-color-danger-text: the verdict glyph and excess ofVerdictColumnand the lost freshness ofAlarmList- lighter in the dark theme, 4.5:1 on every surface. Needs@umriss-ui/core0.7.
0.3.0 – Grouping (Sep. 2026)
Added
- Grouping.
defaultGroupingonuseTablegroups by up to three columns or group keys, the outermost first; without it, the user groups from the column menu's new "Grouping" section. The outermost level is a group header with the value, the count and every aggregate under its column; the innermost of several levels is a span - the grouping column first, its value once beside its rows (ADR-0029). Groups fold (one by one, all siblings with Alt, everything from the grouping's tag in the table toolbar), select as a whole across pages, page as lines with their headers repeated as "continued", virtualise with sticky headers, and make the table atreegridfor assistive technology. <GroupBy>, a group key: a value to group by that is no column - no cell, no export, no entry among the columns.groupValuebesidesortValueandexportValue,group="day" | "week" | "month" | "year"for points in time,groupableon a column and on the table.aggregateon a column:sum,avg,min,max,range,count,distinct, or a function of one's own whose result runs through the column's presentation - in the footer over the filtered set, in a group's header over its rows, always from the rows and never from other aggregates.aggregate="worst"onVerdictColumn.shareswitches off the share bar under the first sum of a group header.- The view carries
groupingandfolded; the snapshot carriesgrouping,setGrouping,folded,toggleFold,foldAllandunfoldAll.
Changed
- Every table can be grouped by its users where a
ColumnMenustands: the menu gains the "Grouping" section.groupable={false}on the table takes it away. footeris calledaggregatenow. The old name keeps working for this minor version and says so once in development; it goes with the next one.- A point-in-time aggregate follows its column's alignment, and a range within one year names the year once.
- Needs
@umriss-ui/core0.6 for the grouping's wording.
0.2.4 – Core 0.5.0 (Sep. 2026)
Changed
- The table takes
@umriss-ui/core0.5. Nothing in the table changed; the peer range moves to^0.5.0, so that it installs beside@umriss-ui/calculation, which needs core's new wording.
0.2.3 – Core 0.4.0 (Sep. 2026)
Changed
- The table takes
@umriss-ui/core0.4. Nothing in the table changed; the peer range moves to^0.4.0, whose focus is a crisp two-pixel accent edge (see core's changelog) - the table's rows and cells show it.
0.2.2 – Phones, touch and a review (Sep. 2026)
Fixed
- A table no longer widens the page. A cell's hidden "No value" for the screen reader was positioned against the page rather than the scroll area and pushed it wider — on a phone the page zoomed out to 758 px. The scroll area now holds it.
- Toolbar and pagination bar wrap on narrow screens instead of running out of the frame.
- The width grip can be hit with a finger: on a coarse pointer it is 16 px wide and faintly visible, since there is no hover to reveal it.
- "Fit to content" fits a widened column back down. A double click on the grip (or Alt+Home) measured the cells at the width they had, and a cell is never narrower than that — a column dragged wide only ever grew. It now measures without that width.
- A virtualised table counts its rows fully for a screen reader: the header
row carries
aria-rowindex="1", and a footer row counts towardsaria-rowcountand says where it stands. - A loading table says so:
aria-busywhileloadingis set. - The alarm list's table has a name, the list's heading ("Alarms").
- Export keeps its download address alive for a moment instead of revoking it straight after the click — some browsers (Safari among them) fetch it only afterwards and got no file.
- The doc comments of
SearchandExportnamed the German defaults; they name the English ones now.
0.2.1 – The README catches up (Sep. 2026)
Nothing in the code changed. The README that shipped with 0.2.0 still called
the table a release candidate under the tag next; that paragraph is gone. The
peer range on @umriss-ui/core is ^0.3.1, the core released beside it.
0.2.0 – The first release (Sep. 2026)
No longer a release candidate: the tag next is gone and
pnpm add @umriss-ui/table @umriss-ui/core is the whole install line.
Nothing in the package changed for it. The source of 0.2.0 is the source
of 0.2.0-rc.1, line for line. What moved is outside it: @umriss-ui/core
released 0.3.0, and the candidate on the registry peers on ^0.2.0 — so an
application taking both would have been told they do not fit. They do; the
range was simply older than the core beside it. Released together with
@umriss-ui/core 0.3.0, @umriss-ui/charts 0.3.0 and @umriss-ui/schedule
0.1.0, and peering on ^0.3.0.
0.2.0-rc.1 – Styles that load themselves, and touch nothing else (Sep. 2026)
Delivery report for .scratch/styles-without-side-effects/spec.md, the table's
share (ADR-0021). It takes @umriss-ui/core 0.2.0 as its peer: the table's
styles read the tokens that @umriss-ui/core now loads by itself.
Changed
- No stylesheet imports any more.
dist/table.jsimports its own stylesheet, and@umriss-ui/coreimports core's.@umriss-ui/table/styles.cssstays exported and is optional. - Cascade layers, own elements only. The table's rules lie in
umriss.baseandumriss.componentsand select only its own elements. The frame, toolbar, pagination and alarm list carry their text context; sort button, filter button and focusable header cell carry the focus ring; the scroll containers their scrollbars. - Light and dark follow the application's
color-scheme, through core's tokens.
0.2.0-rc.0 – One language, one scope
Delivery report for .scratch/english-and-umriss-ui/spec.md, the table's share.
- The package is called
@umriss-ui/table, and it takes@umriss-ui/coreas its peer — the package that was@umriss/uiand now lies atpackages/core. The npm scope moved from@umriss/*to@umriss-ui/*, the org actually secured for this library. Nothing was ever published under the old names — all three returned 404 from the registry — so there is no alias and no deprecation window, and the version number stays where it is. An import line changes from@umriss/tableto@umriss-ui/tableand from@umriss/uito@umriss-ui/core; nothing else about the call changes. - Every identifier and every document is English (ADR-0018, which supersedes
ADR-0015). The hook is
useTable, the modeltableModel, the companionuseCompanion, the alarm modelalarmModel;src/kern/issrc/model/— notcore/, which would name both the package and this layer, one grep with two answers. The long prose headers that carry the design reasoning were translated, not shortened. - The library ships two wordings, English by default (ADR-0019). The table
reads its texts out of the wording of
@umriss-ui/core, as it always did, so its labels are now English by default: the column menu, the search, the export, the row actions, the empty state, the conditions in the table toolbar and the ratio "43 of 1.204". The German that used to be the default is unchanged and ships as the subpath@umriss-ui/core/wording/de; oneUmrissProviderwith it switches both packages back at once. - The formats are still
de-DE, so the default renders English words around German digits — "43 of 1.204", a date as "17.03.2026". A locale is not a language, and changing it is its own decision with its own pass over the baselines; ADR-0019 records this rather than hiding it. An application that wants otherwise passesformatsto theUmrissProvider, and the table's tests prove the cells use it rather than formatters of their own. - The licence is MIT, and a
LICENSEfile ships in the package.
Changed — the values, not only the names
- The alarm lifecycle.
LifecycleStateis"active-unacknowledged","active-acknowledged","resolved-unacknowledged","resolved-acknowledged";Transitionis"raised","acknowledged","resolved";Priorityis"high","medium","low". In the DOM,data-zustandisdata-lifecycleanddata-prioritaetisdata-priority. - The limit model's wire format follows
@umriss-ui/coreand@umriss-ui/charts:data-urteilisdata-verdict, andVerdictColumnreadsassessment.verdictandassessment.excess. - Freshness is
"fresh" | "stale" | "lost", and thefreshnessprop ofAlarmListtakes{ stale, lost }instead of{ alt, abgerissen }.data-aktualitaetisdata-freshness. - The
virtualprop is{ rowHeight, overscan }, not{ zeilenHoehe, puffer }.
Fixed
- Virtualisation never measured a real row.
useVirtualin@umriss-ui/corelooked for[data-zeile], which the table does not write — its rows carrydata-row. The measured row height therefore stayed at therowHeightpassed as a starting point, and the filler rows that hold the scrollbar at the right length were sized against it. Two visual tests had been failing on this.
0.2.0-rc.0 – Column filters that do not shift the table
Delivery report for .scratch/table-filters/spec.md.
Added
columnFilter({ matches, Input, describe }): a filter an application writes itself — its check, its panel, the text of its condition. It is bound to a value type, not to a row kind; the compiler accepts it at a column only if that column's value fits. The table shows it, counts it and resets it like any other.matchesis never called for an absent value, and an absent value satisfies no condition of its own. The list filter is built with the same interface.filter="range": two bounds on a column, both inclusive, each may stay open. Over a number two number fields that take effect while typing — if "from" lies behind "to" they are invalid and the condition stays the last valid one — and over an instant the range picker. The bounds of an instant are calendar days in local time: "to 30.09." includes the whole of 30.09. An absent value never fits. The compiler allows"range"only on numbers and instants.t.filterandt.setFilter(column, condition | null): read and set conditions from outside, for instance out of a tile "3 blocked". Typing happens at the field of the row — a list of its values, and for numbers and instants a range as well; an id that is no field name takes any condition. A condition that does not fit the kind of the filter is passed over with a warning in development. A change resets to page one.conditionsin the view:initialViewsets them for the first render, without the unfiltered table flashing up;t.viewreads them back. A condition for an unknown column falls out.
Changed
filterWerteandsetFilterWertearet.filterandt.setFilter. The condition of a list filter is the chosen values themselves, an absent one asnull— no longer the keys"wert:…".- Conditions stand in the table toolbar, no longer in a row of their own
between the toolbar and the header. On the left, behind whatever is placed in
it, stands one tag per condition — a click opens the panel of its filter, the
cross lifts it — and on the right the ratio "43 of 1.204" in a status a screen
reader announces, and "Reset". A table with a search or a column filter has a
table toolbar even where none is placed: it then carries only the conditions,
the ratio and "Reset"; the number of the selection and its bulk actions belong
to a toolbar somebody put there. A
Toolbarstanding behind the table withofregisters only after the first frame (ToolbarProps). - The search no longer appears as a condition. It still counts, and "Reset" empties it.
filter="list"only on values with a text form. On an array or an object the compiler rejects it; at runtime no filter appeared there even before.- The application's filter is called
preFilterand is what its name says: it determines which rows the table has at all. It never appears as a condition, "Reset" leaves it standing, and it never belongs to the view. The ratio "43 of 1.204" counts only the rows it admits, the list filter offers only their values, and a table it leaves empty showsemptyinstead of "Nothing matches the search and filters".filterremains as the deprecated name for one minor version. - A change of the pre-filter no longer resets the page. The page stays
standing and is clamped to those that exist. Whoever wants to start at page one
after a change calls
t.setPage(1). A pre-filter determines the rows of the table; whoever reset on every change of the admitted rows reset on every data refresh.
Fixed
- The table moved down on the first keystroke (D1): the strip of active filters came into being with the first condition as a row of its own above the header and disappeared again when emptied. A browser check now holds that the header stays put while searching, filtering and resetting.
- The search stood there twice (D2): in its field and as a condition "Suche: …" beside it.
- A condition outlived its column (D6): when a filtered column left the JSX, its choice stayed in the state, went on counting as a restriction in the empty state, and took effect again when the column came back. The condition now goes with the column. A hidden column is still there and keeps its own.
- A filter written in the call made the table crash (D4):
filter: (z) => …yielded "Too many re-renders" on the first render, because a change of its identity reset the page while still rendering. The documentation demanded onlyuseCallback.preFiltermay stand in the call. - The list filter offered values the application's filter excludes (D5) — under a filter by plant, the values of the other plants.
- Figures and the empty state counted excluded rows (D3): the ratio named all rows, and where the application's filter alone left the table empty it offered "reset everything" — which left the table empty.
Removed
- The view as search parameters.
alsSuchparameter,ausSuchparametern,AusSuchparameternOptionen,t.suchparameterand the text form ofinitialeAnsichthave gone. The table writes no part of its state into the address: what the URLs of an application contain the application decides. What remains is the view as an object —initialViewon the first render,t.viewto read it back — which an application can keep wherever it likes. Whoever wants to go on using the address writes the few fields ofTableViewinto it themselves.
0.1.0 – A table, declared the way it reads (never published)
Delivery report for .scratch/umriss-table/spec.md, tickets 01–13 (ADR-0016,
ADR-0017). The package depends on @umriss-ui/core as a peer and imports only
its public entry; it has no runtime dependencies.
@umriss-ui/core has no table any more (ticket 14, @umriss-ui/core 0.9.0).
Model, export, view, selection and the alarm model stand only here; their
behaviour tests run against this package's demo
(.scratch/table-demo/spec.md).
Fixed before publication
- The sticky row header sticks where it belongs, even when every column has a
widthand the table is wider than their sum. Until then the table layout distributed the excess over the selection and expander cells as well; they grew wider than the 34 px the sticky offsets are computed from, and when scrolling sideways the row header slid over the expander. Found by the demo's browser suite — jsdom knows no layout. - The label of a condition in the filter strip ("Suche", "Status") stands in the secondary type rather than the muted one. On the surface of the tag the muted type held 4.5:1 in neither theme (3.09:1 light, 3.74:1 dark). Found by the demo's accessibility check, which saw a table with a condition at rest for the first time.
Added
useTable(rows, options)derives the row kind from the rows and gives out the parts that need it:Table,Column,VerdictColumn,RowDetail,RowActions,Action. With them the state — filtered and visible rows, search, sort levels, page, hidden columns, order, widths, expanded rows, list filters, selection —, the view as an object (view) and as text (suchparameter) andasCsv(). Options:rowKey,pageSize,defaultSort,filter,initialView(also as text out of the address bar),virtual,selection.- A column is an element with exactly one value — a field name or a function
with an
id— and optionally its presentation aschildren(value, row).childrenis not called for an absent value; withoutchildrenthe type of the value decides text, alignment, sorting and export.formatchooses a standard presentation ("prozent","anzahl",{ decimals },"datum","zeit","datumZeit"),footersums or averages over the filtered set,rowHeadernames the row,filter="list"offers the values that occur,sortValueandexportValuesort and write a value that has no order of its own. The compiler rejects a mistyped field name,footeron text, a date format on a number, and a missing renderer for an array. - The table renders its rows itself: hiding and reordering act on header,
body and footer at once. Props:
selectable,stickyHeader,stickyRowHeader,striped,density,maxHeight,empty,loading,rowProps,ariaLabel. column<P>(…)— a column bound to a property instead of to a row kind, to be spread intoColumn.- Unbound parts
Toolbar(with the bulk actions of the selection),Search,ColumnMenu(show, hide, arrange),Export(download oronExport),Pagination; placed inside the table or withof={t}. VerdictColumn— a numeric value against aLimitSet, with a character, a word and the excess; sorted by severity or withsortBy="value".AlarmListandalarmModel, taken over from@umriss-ui/coreand expressed in the same interface. The props are English here:view,selection,onAcknowledge,asOf,freshness.alsSuchparameter,ausSuchparametern,useTableSelectionand their types.
Worth knowing
- A table pages only when a
Paginationstands. Without one it shows the whole filtered set;pageSizeapplies as soon as there is a bar. - The table renders in the browser. Server-side rendering is not foreseen.
- A
Columnout of a differentuseTablecall can be placed into a table without the compiler objecting — JSX does not check children against their parents. In development the table warns.