@masterteam/data-grid
v0.0.1
Published
Independent Angular data grid. Full reference parity is under development.
Readme
@masterteam/data-grid
Independent Angular 21 data-grid package, currently an incomplete implementation checkpoint. Full parity with the locked AG Grid 36.2.0 Community and Enterprise reference remains required; this package is not ready for that acceptance claim.
import { MtDataGridComponent, type MtColumnDef } from '@masterteam/data-grid';
// Add MtDataGridComponent to a standalone component's imports.
readonly rows = [{ id: 'a', name: 'Record', amount: 0 }];
readonly columns: MtColumnDef<(typeof this.rows)[number]>[] = [
{ field: 'name', width: 240 }, { field: 'amount', width: 180 },
];
readonly identify = (row: (typeof this.rows)[number]) => row.id;<mt-data-grid [rowData]="rows" [columnDefs]="columns" [getRowId]="identify" [height]="400" ariaLabel="Records" />Implemented foundations: fixed row/column virtual geometry, stable IDs, value getters/formatters, local stable multi-sort (Shift-click), keyboard arrows/Home/End/Page Up/Down, RTL positions, loading/empty text and a small owned API. Callers retain ownership of their input arrays/records. Without getRowId, IDs are source indices; provide IDs for identity across replacements. Values render as text. Duplicate identities and invalid sizes/sort models are errors.
Set direction="rtl" for right-to-left layout; label and overlay messages are caller-configurable. CSS variables currently expose mt-grid foreground/background/header/border/row-border/radius/font/cell-padding/hover/focus customization. These are an owned initial subset, not all upstream themes/parts.
Columns resize by dragging the header edge or pressing Alt+Left/Right from a focused header (2px per press, RTL aware). Shift-drag transfers width to the next displayed column within both columns' bounds. resizable:false disables that column's handle/keys; it can still receive width transferred by its neighbor. Width state belongs to each grid, including hidden columns. initialWidth applies on introduction of an ID; a later explicit width replaces its state, while omission preserves it. Removing and re-adding the ID creates fresh state. The default minimum is36px; minWidth/maxWidth bound widths.
The gridReady event exposes api.getColumnWidths() and api.setColumnWidths([{ colId: 'name', width: 280 }], finished = true). API requests outside the column's bounds are ignored, including0/negative requests; they still emit the requested completion notification with current widths. A same-width finished=true call can close a prior unfinished update. Nonfinite sizes and unknown/duplicate IDs fail atomically. columnResized emits { columns: [{ colId, width }], finished, source: 'api' | 'uiColumnResized' }. Input-definition replacement events, pointer no-change/Shift-final event details, exact upstream event timing/payloads, held-key acceleration, flex/auto/group sizing and full column state remain open; these owned names/signatures do not imply upstream API compatibility.
Build/test/API checks use the package scripts. Research, gaps, API differences and evidence are documented in .github/instructions/data-grid/README.md. Canonical integration is the existing showcase /data-grid route. Existing mt-table remains in place.
Portal integration contracts
Categorical analysis: give a column chartCategory: { values: ({ value, data }) =>
[{ id, label, color }], multiValue? } and charts group records by that identity,
label the axis/legend with label, colour items with the authored colour (or a
stable palette slot), and offer Count of records, Non-empty and Distinct
measures beside numeric series. A Title + Status range therefore charts
<chartRecordsLabel> by Status with integer counts and no numeric column;
numeric series sum per identity category by default. chartRecordsLabel names the
records ("Tasks"). Multi-value categories count a record once per member and
disable share charts; missing values group under the localized Unassigned.
Nested viewports isolate their sticky headers so details cannot paint above a
parent grid header. For a visible closed native end rail, use
{toolPanels: ['columns', 'filters'], position: 'right'} in LTR (left in RTL),
omitting defaultToolPanel. hiddenByDefault hides the rail itself.
For application-wide Columns panel defaults, provide
{provide: MT_GRID_COLUMNS_TOOL_PANEL_DEFAULTS, useValue: {suppressRowGroups: true}}.
This hides the duplicate Row Groups zone while retaining the top grouping strip,
column menus and grouping API. Only built-in Columns panels inherit these defaults;
explicit toolPanelParams override them. Other applications keep the full panel
by default. /data-table demonstrates this provider without changing /data-grid/demo.
[autoSizeStrategy]="{ type: 'fitGridWidth' }" fits displayed columns on mount,
viewport resizing and visibility/group changes, respecting minWidth, maxWidth
and suppressSizeToFit. Manual column widths survive sorting/filtering.
Applying or resetting column state refits saved widths to the current viewport
in this mode, retaining their proportions and authored limits. A layout saved on
a wider screen therefore does not force a scrollbar when reopened in a detail.
api.sizeColumnsToFit() requests another fit explicitly. When the minimum widths
cannot fit, horizontal scrolling remains available; data is never hidden to force
a fit. This mode measures the viewport, not every cell. It fits within the precise
content-box width, rounded down, so fractional layouts cannot create a phantom scrollbar.
paginationPanelShow="whenNeeded" hides the native pager while loading and when
the entire result fits one page; its default is always. The shared pager centers
numbered controls with the host's paginator tokens. Empty aggregation status panels
reserve no footer space. groupDisplayType="groupRows" gives a full-width heading
with source-field name, entity content and record count.
Loading uses bounded skeleton rows aligned with displayed column widths. A host may
provide MT_GRID_EMPTY_OVERLAY with an Angular component having a params input
(MtGridEmptyOverlayParams: message, filtered, clearFilters) to reuse its existing artwork and clear-filter action.
An explicit [noRowsTemplate] takes precedence. The engine imports no product UI.
dataProcessing="external" emits sort/filter state while leaving the supplied
server-owned page in its original order. The consumer owns cancellation, totals
and complete-result processing; this is not a server row-model implementation.
headerRenderer and groupRowRenderer accept owned component renderer contracts.
captureGridViewState, parseGridViewState and applyGridViewState expose version 1
configuration for columns, sort/filter, grouping, values and pivot, excluding rows.
forEachNodeAfterFilterAndSort iterates the filtered/sorted displayed result before
pagination; getDisplayedRowAtIndex addresses the current page. Detail/group/footer
rows remain distinguishable on their nodes. @masterteam/data-table supplies entity
cell renderers and native column adapters for direct grid use. Earlier compatibility
and Studio orchestration is isolated in its /legacy entry point.
localeText supplies a per-grid dictionary. Keys are trimmed English source
messages (for example Columns, Copy, No Rows To Show); default English and
caller data/column names remain intact. localeTextResolver(key, defaultValue,
params) can override a message, returning undefined to use the dictionary/default.
{parameter} messages interpolate text safely. Labels, filter operators, panels,
menus, chart controls and accessibility strings share the scope, including charts
mounted in an external container. The engine has no Transloco dependency.
Chart ranges optionally accept rowIds: an array keeps an explicit record
selection stable through sort/filter/data replacement, [] charts no records,
and null follows every matching record independently of group expansion, excluding
group totals. In pivot mode it follows the finest result groups without duplicate
parent aggregates. Omission retains positional range behavior.
New ranges selected in an identity-based chart capture their record IDs too.
Filtered-out selections produce an empty chart and return when the filter clears.
Styles
Load the compiled utilities once in the host's global styles, after installing/building the package:
@import "@masterteam/data-grid/assets/data-grid.css";pnpm --filter @masterteam/data-grid build runs build:tailwind and then ng-packagr.
For source-linked development, run pnpm --filter @masterteam/data-grid build:tailwind
after template utility changes. The output is the tracked, generated
packages/masterteam/data-grid/assets/data-grid.css (copied to dist by ng-packagr); the
showcase imports it from source and build-fixture regenerates and bundles it automatically.
The grid adds .mt-grid-root to its host. Its mtg: utilities are scoped, unlayered and
contain no preflight; other packages' Tailwind bundles can coexist in either load order.
The existing Masterteam --mt-grid-* tokens still control identity and host overrides.
Geometry, complex states and host/theme tokens remain in component styles. Do not scan this
package into a host utility bundle or replace its asset with a full unprefixed Tailwind build.
External chart containers receive the same scope on their chart-dialog host automatically.
Main demo
/data-grid/demo is the complete example walkthrough on independently generated players:
Data Size selects 100 / 1,000 / 10,000 / 100,000 rows (20 data columns plus row numbers
and selection), default 1,000. Participant, Game of Choice, Performance and Monthly Breakdown
groups use header text/number/set popups and twelve read-only months (three shown while
collapsed). The main demo is view-first: no editable columns, editors or undo/redo. The grid fills the available viewport, with
Columns open and counts/range aggregates in its status bar. Group/pivot drags, totals,
range/fill-handle selection and copy, all 35 charts and CSV/Excel/PDF exports run on this data.
rowGroupPanelShow="always", pivotPanelShow="always" and [showQuickFilter]="true"
compose one 48px panel row. pivotPanelShow accepts never, always, onlyWhenPivoting
(default); it is independent of the Row Groups zone. The optional quick-filter field shares
quickFilterText, filter events and api.resetQuickFilter() state; no page-level field is needed.
animateRows fades new rows for 150ms and respects reduced motion, without animating geometry.
The demo chrome uses mt-page and mt-select-field. Default Classic Masterteam identity
flows through the PrimeNG --p-* token bridge; no identity is seeded. Host html.dark
updates grid surfaces and linked chart colours, including already open dialogs. Explicit chart
formatting overrides still win. The behaviour reference is observed only; its code, data and
assets are not used. Run demo-check.mjs against the existing source showcase on port 4444.
Group-header drags contribute every eligible child in display order. The pointer carries a
Classic raised ghost; role zones show live chips, hide prospective grouped headers and restore
on leave/cancel. Horizontal chips use muted hierarchy chevrons. Pivot result leaves read
sum(Jan) / avg(Bank Balance) under their key; Total labels only totals groups. Generated
columns fit the longest aggregate header (minimum 100px) and retain subsequent user resizing.
Narrow horizontal role zones scroll their own chip lists, keeping reorder targets inside the zone.
Header filters open 2px below their buttons, start-aligned and flipped/clamped to the viewport.
Active filter buttons stay primary-tinted. Set popups focus Search, show tri-state Select All
and 32px checkbox rows, and apply immediately unless the host explicitly configures Apply.
The demo has no floating-filter row; the optional floatingFilter API remains available.
The showcase Arabic toggle drives the demo direction end to end. Charts, supplied directional
icons and pager controls mirror; data/header texts and Latin currency remain English.
Premium chart and column tools
Range charts use a 720×480 dialog (minimum 480×360) with a 44px Range Chart title bar, Maximize/Close and a single Chart Menu: Edit Chart, Advanced Settings, Unlink from Grid / Link to Grid, Download Chart. Alt+E/F/L/D invokes those actions; Escape closes the focused dialog. The 320px editor expands the dialog inside the host and uses underline tabs, accordion sections, 36px fields and complete chart-type labels. Title and bottom legend reservations shrink every plot; legend names wrap across three rows then paginate. Donuts use a 62% hole with a centre total.
pivotPanelShow defaults to onlyWhenPivoting (always remains available). Optional
pivotPanelHint provides a short empty-state example. Columns-panel drags preserve the source at
50% opacity, insert between siblings and move complete groups. Group-header resize handles distribute
the delta proportionally across visible resizable leaves, respecting their min/max bounds;
double-click autosizes the group and Alt+Left/Right resizes by 10px. The Values aggregation picker
anchors and flips around its chip, supports arrows/Enter/Escape and restores chip focus.
Group content and controlled paging
A column's groupValueRenderer renders its group key content; the native expander, count, keyboard and drag behaviour remain grid-owned. Without it, the source column's valueFormatter formats the key using a representative leaf. Group identities stay independent of labels. cellRenderer: 'group' accepts cellRendererParams.innerRenderer to retain component/function content beside the expander.
Pagination uses shared numbered pages and first/previous/next/last controls. For a server-owned page, combine dataProcessing="external" with paginationTotalRows and optional zero-based paginationCurrentPage; the supplied rows are already paged and are not sliced again. paginationChanged carries the requested page and size. The consumer owns cancellation, loading, errors and authoritative counts. This does not implement grouped/pivot server rows.
Complete client query and workbook utilities
queryClientRows(rows, columns, { filters, sort, search, quickFilterColumns }, getRowId)
uses the same typed predicates, quick search and comparators as the grid. Supply a
verified complete client array; unknown/non-filterable predicate columns reject
instead of silently broadening a scope. It never fetches or pages product data.
createExcelWorkbook([{ name, headers, rows }], rtl?) returns an XLSX Blob with typed
number/boolean/date/literal-text cells, filters, column widths and legal unique sheet
names. The caller owns authorization, acquisition, scope, cancellation and download.
It is a browser utility, not a durable export job or Chromium PDF implementation.
See the /data-table showcase's matching Records/Summary export.
Expanded master/detail rows remain on their master's page and do not increase the pager's row count or displace the next master onto another page.
Unlinked chart popups
Unlink a native chart from its Chart Menu (or Alt+L) to freeze its data and float
the same chart across the page. Drag its title, resize its edges, or maximize it
to the viewport. Link to Grid restores its grid position and live updates.
Themes, fonts and RTL continue to follow the source grid. Charts close when their
owner grid is destroyed. Explicit chartContainer embeds keep their container.
See /data-grid/demo in the showcase for the same native chart behaviour.
Data-based chart names
Chart popups and their accessible names use the chart's data title: for example,
Budget by Status or Amount, Budget by Team. Automatic titles follow selected
category/series fields; switching chart type, transposing or unlinking keeps the
field context. An explicit creation/API title or Format → Titles text takes
precedence and is also shown in the popup header. Hiding the plot title leaves
the popup named; clearing it falls back to the data description.
The localeText / resolver message {series} by {category} localizes the
relationship, with displayed property headers interpolated literally.
chartContext optionally supplies a source path (for example, Projects · Digital
Services Platform · Financial). The grid captures it when creating a chart;
MtCreateRangeChartParams.chartContext overrides it and MtChartModel.chartContext
reports it. The native popup shows this path above the analysis title, without
repeating the title in its plot. Embedded plots keep their title, and PNG downloads
include the context and title once. Unlinked charts retain their captured context.
With checked leaf records, Chart Range uses those stable row identities, the first non-time category and the first numeric measure containing data. Other chartable columns remain available in Data. Without checked records, the cell range applies. This uses raw typed values and declared chart roles, not formatted entity text.
Crowded cartesian charts keep a bounded canvas and every source point. Native category scrollbars expose horizontal windows for columns/lines and vertical windows for bars, with wrapped, unrotated labels at readable spacing. Wheel and keyboard (arrows, Page Up/Down, Home/End) move the window; numeric scales retain the full data domain. Authored navigators retain control of their own axes. Resize preserves the window; a changed category set resets it. Pie/scatter and other charts without category axes retain their own layouts.
Auto height and page-hosted chart popups — 2026-10-25
autoHeight sizes the grid by its content instead of height: the header band,
the status bar / pager / group panel, the plain rows and every open detail row.
autoHeightMaxRows caps the plain rows (the viewport then scrolls them) while
open details always count in full, so a nested grid inside a detail row never
squeezes the grid inside its detail row; autoHeightMinRows (default 3)
keeps room for the loading and no-rows states. A MtGridChartScope may also
set chartHost, a positioned page element: linked chart popups are appended
to it and bounded by it rather than by the (possibly small) grid that created
them. The product Studio uses both for its one-page expand-in-place hierarchy.
Page-owned unlinked charts
Provide MtGridChartScope at a page/schema component to keep native unlinked
popups alive when module tabs, record details or virtual rows destroy their grids.
Call scope.clear() on schema/security changes; destroying the provider closes
all its charts. scope.themeSource may point at a surviving theme anchor.
Scoped unlinked popups close through their close button or scope cleanup, and ignore Escape/outside clicks. Each keeps its frozen data, drag/resize, editor, chart types and downloads. After its source is destroyed, relinking and selecting a new source range disappear. The chart releases source-grid subscriptions and uses an independent injector; retaining a popup does not retain a hidden grid. Unscoped grids keep their existing destruction/Escape behaviour; explicit chart containers remain consumer-owned embeds and are not retained.
Chart editor polish — 2026-10-24
The native shared chart surface now wraps automatic horizontal category labels into at most three measured lines, thinning dense ticks instead of rotating them. Full category identities remain in the data and point tooltips. Explicit API-authored rotation remains supported. English and Arabic labels share the same layout.
Every chart type has an owned rounded-stroke shape preview, matching grid-action
icon weight and colour. The chooser and context menu offer 33 distinct presets.
coneFunnel remains an API/saved-model compatibility type; customCombo remains
restorable and is entered by editing a combination's per-series types in Data.
Saved compatibility types still appear selected in their editor. All 35 public
types remain supported. Stacked/normalised/combination/range choices explain their
two-series requirement; bubbles require X, Y and size. Extra series are selected
in Data, with unavailable single-series variants clearly identified.
The visible Format tab is Advanced settings. Its public format id and
formatPanel configuration remain stable. The editor has softer field/select
focus, selected options, consistent 36px controls, grouped axis settings, and a
48px tab strip that accommodates the longer English/Arabic label. The engine
still has no component-library/PrimeNG dependency.
The entity integration now formats calculated numeric ticks and aggregates from the supplied value, retaining percent/currency units. It no longer repeats a sample record's display text (e.g. 0%) for every tick. Ordinary entity cells retain their authored display text, and explicit native valueFormatter overrides win.
Sort getter values are reused within one sort invocation and refreshed on the next. Scoped unlinked charts survive immediate source teardown, including menu-driven Unlink. Verification: performance/lifecycle notes.
