Snapshot of the gap analysis from 2026-07-12. Items already delivered are not listed (data-table v1, autocomplete, date-range picker; pagination, number / masked fields, rich-text, breadcrumbs, avatar, command palette are being delivered alongside this file). Grouped by priority for CRM/ERP use.
- data-table v2 — ✅ done (2026-07-12), same component/contract, +2.2 KB gz:
virtualwindowed rows over the server-rendered tbody (spacer rows, DOM and form state untouched),resizable/reorderableheader drags with materialColumnResize/Reorder for server persistence,sticky-start="N"pinned columns,tr.row-groupfold/unfold of server-computed aggregates,.cell-editinline editing over plain form controls (materialCellEdit), responsive columns (th[data-hide-below], container-based) with a per-row "…" inline fold-out for the hidden fields — no card stacking. - material-tree — ✅ done (2026-07-12): expand/collapse, tri-state
cascade selection (form-posts one entry per checked value), lazy children
(
src?parent=JSON ormaterialTreeLoad), nested markup or flat mptt-style rows withlevel, aligned trailing cells, ARIA tree keyboard nav. Remaining ideas: drag-to-reorder, typeahead. - material-transfer / dual listbox — ✅ done (2026-07-12): two panels + move buttons, per-side search, form-posts chosen values (getlist), keyboard support, RTL.
- File dropzone — ✅ done (2026-07-12): material-dropzone — DnD + browse, native input posts with the form (FILES.getlist), image previews, accept/max-size/max-files + cancelable add hook, setProgress() per-file upload UI. material-file-field stays the compact ClearableFileInput wrapper.
- Docked date picker — ✅ done (2026-07-12): material-date-field picker="auto|docked|modal" — docked dropdown commits on date click, auto-modal on compact viewports. (Time pickers stay dialog-only — M3 has no docked time variant.)
- Number formatting depth — ✅ done (2026-07-12): currency="USD|EUR|…" mode (Intl symbol/digits placement), Django DECIMAL_SEPARATOR / THOUSAND_SEPARATOR win over Intl when jsi18n is loaded.
- JSON editor — ✅ done (2026-07-13): material-json-field — compact JSON
tree editor (no syntax-highlighter, plain-DOM
.cell-editleaves, ~5.2 KB gz). Full structural editing (add/remove/rename keys, reorder array items, change value type), form-associated (posts one JSON string, always valid), readonly viewer mode. 0 axe WCAG 2.2 AA violations.
- Bottom sheet — ✅ done (2026-07-12): material-bottom-sheet — modal (native dialog scrim/top-layer) + standard (page stays interactive); drag handle peek↔full↔dismiss, flick-to-dismiss, click/Enter toggles, 640dp max width; same data-dialog-target triggers.
- Side sheet — ✅ done (2026-07-12): material-side-sheet — modal trailing-edge dialog, standard in-flow panel (host animates inline-size), adaptive (standard ≥840px) with open state surviving the swap; RTL via logical properties. Field-service showcase uses it as the record detail.
- Search view — ✅ done (2026-07-12):
material-searchships the full MD3 Search pair — contained pill bar + search view (docked popover on desktop, full-screen overlay on compact), slotted/JS/remote suggestion sources, sections, clear ×, form-associated, Unpoly-visible href items. - Rich tooltip — MD3 variant with subhead + actions (plain exists).
- Skeleton loader — ✅ done (2026-07-12): material-skeleton — three
primitives (text lines / circular / rectangular, em-based sizes),
composites are plain markup; RTL-aware shimmer, reduced-motion pulse
fallback, Unpoly
up-placeholderrecipe. 0.8 KB gz. - Empty state — icon/illustration + headline + action pattern component.
- Timeline / activity feed — record history, the heart of a CRM; entries with avatar, timestamp, grouped by day.
- Kanban / drag-and-drop — CRM pipeline boards; ship a sortable-list primitive first (keyboard-accessible DnD), board on top.
- Stepper / wizard — ✅ done (2026-07-12): material-stepper +
material-step — M2 anatomy, M3 tokens; horizontal/vertical, linear or
free-order; client-side next/back (document-delegated
data-stepper-next/back) gated on constraint validation, or a pure
server-driven indicator for django-formtools (
active/completed/errorfrom the template, cancelable materialStepClick → wizard_goto_step). material-textfield/textarea now mirror inner-input validity onto ElementInternals (checkValidity/reportValidity), so required fields actually gate submits. - Charts — do NOT build a chart engine; ship an MD3 token bridge for ECharts (colors, typography, tooltip surface) + 2–3 ready KPI widgets (stat card with trend, sparkline) like the ERP showcase draws by hand.
- Tests — visual-regression via Playwright screenshots of the existing demo pages (cheap, matches the demo-pages-as-test-suite convention); unit tests only where real logic lives (date parsing, mask engine, fuzzy match).
- Docs site — generate from the per-component readmes (already auto-generated by Stencil) + live demo embeds; changelog, semver, npm publish pipeline.
- Django integration package — form widgets that render these components
from
forms.Fielddefinitions; this is the adminui product on top of the library and the main commercial differentiator. - A11y statement — ✅ audit done (2026-07-12): axe-core (WCAG 2.2 AA
tags) swept all 56 demos + 4 showcases via playwright-cli — 0 violations
on every page after fixing: nested-interactive in selectable lists
(material-checkbox
nestedmode renders a non-widget visual), select / autocomplete chip roles + ≥24px remove targets, slider/list/select accessible names, tooltip cross-shadow aria refs (+ inert), side-sheet closed-state inert, data-table group rows (injected toggle button carries aria-expanded) + focusable scroll region, calendar outside-day contrast, disabled-link roles, carousel slide semantics, demo density/dir toggle target size. Remaining for "commercial": wire the sweep into CI (scratchpad script → repo script + exit code) and write the per-component WCAG statement doc. - RTL — ✅ done (2026-07-12): library-wide sweep converted physical
properties to logical ones, added
:dir(rtl)transform/clip-path flips (slider, switch, tabs badges, split-button, icon-button badge), made anchored-popover alignment logical, and fixed carousel math + arrow keys. Demo pages have a persistent LTR/RTL toggle next to Theme/Density. - Licensing — currently AGPL-3.0; decide dual-licensing (AGPL + commercial) before promoting third-party commercial use.
- i18n — ✅ done (2026-07-12), docs/i18n.md: deliberately NO shipped
catalogs — three-tier resolution (Django jsi18n → Intl → English msgid),
label props always win, Django
JavaScriptCatalog+get_formatwiring and msgid-extraction recipe documented; non-Django shim example included. - Density/theming docs — the A−/A/A+ rem system and Material Theme Builder re-skin flow are implemented but undocumented for consumers.
Map-based editors for the OpenGIS geometry types Django's contrib.gis
exposes (PointField … GeometryCollectionField, plus RasterField). Pure
client-side geometry manipulation — Django form-widget integration is a
separate project; here we invent the UX and ship the JS.
Engine-agnostic via an adapter — the consumer brings the map (preferred
over bundling one). Core material-geometry-field carries ZERO map
dependency and can live in the MAIN bundle (a few KB): it owns the value,
form association, validation, undo/redo, the parts model, the Material
toolbar / parts-list UI, a11y, and a raw lat/lng fallback editor. All map
rendering + projection + vertex hit-testing sits behind a small
GeometryEditorAdapter interface (mount, project/unproject,
renderGeometry(geojson, editState), drawing-mode events, fitBounds,
destroy). The consumer attaches an adapter for whatever engine they already
run — el.adapter = new SomeAdapter(map) — so the map engine (Leaflet ~40 KB
gz, MapLibre ~200 KB gz), tiles, version, and API keys are entirely theirs;
nothing map-related enters our bundle. Without an adapter the field still
works headless (posts/validates the value, raw-coordinate editing); the
visual map appears once an adapter is attached.
Don't reimplement vertex editing. The interface relocates the hard 60 %
(draw / edit / hit-test) into the adapter — it doesn't remove it. Writing an
adapter per engine from scratch = writing the whole map integration. So lean
on Terra Draw (engine-agnostic across Leaflet / MapLibre / OpenLayers,
covers all these geometry types) for the reference adapter — one thin adapter
then serves multiple engines. Ship it as an optional subpath
(advanced-material-web/geo-terradraw) and document the interface so others
can bring a different engine.
One component, type prop — not eight. A single material-geometry-field
with type="point|linestring|polygon|multipoint|multilinestring|multipolygon |geometry|geometrycollection" selecting the active tool set, mirroring the
material-date-field/number-field "one element, many modes" pattern. Keeps the
geo bundle lean and the API small.
Value contract. Form-associated; posts geometry as text under name.
format="geojson|wkt|ewkt" (default GeoJSON — trivial JSON.parse in JS;
WKT/EWKT for Django parity, since its widgets default to WKT). srid prop
(default 4326). value in/out; readonly = viewer; required/disabled.
Tiles are consumer-supplied (tile-url + attribution slot) — bundle no tile
provider (API keys / attribution / cost aren't ours to embed).
Shared chrome. Toolbar (pan / draw / edit-vertices / delete / clear /
fit-to-data), undo/redo, empty state, keyboard-accessible vertex editing,
lat/lng type-in fallback for point. Emits valueChange (serialized) +
materialGeometryChange (parsed).
Per-type UX to design/build:
- PointField — click to drop one marker, drag to move; synced lat/lng inputs. (Geocode/search = Django-integration territory, skip.)
- LineStringField — click to add vertices in order; drag to move, click a vertex to delete, midpoint handles to insert; double-click/Enter finishes. Show length.
- PolygonField — like line but closes the ring; drag/insert/delete vertices; secondary "add hole" mode for interior rings; show area.
- MultiPointField — many independent markers + a parts list.
- MultiLineStringField — "new line" starts another part; parts list.
- MultiPolygonField — several polygons, each with its own holes; parts list.
- GeometryField (base) — a geometry-type picker, then the matching single tool; accepts any one geometry.
- GeometryCollectionField — "add geometry" menu (point/line/polygon/…); heterogeneous parts coexist as layers in a parts list.
- RasterField — DEFER. PostGIS-only, no interchange standard, GDALRaster is server-side; at most a read-only extent/thumbnail preview if given a URL. Not an editor for v1.
Open decisions. (a) Which reference adapter(s) to publish — Terra Draw
(one adapter, multi-engine) is the strong default; whether to also ship
engine-native adapters (Leaflet-Geoman, mapbox-gl-draw) for consumers who
already depend on those. Engine choice itself is now the CONSUMER's — we
bundle none. (b) GeometryEditorAdapter interface shape — freeze it before
building the first adapter, since third-party engines depend on its
stability. Value format is settled: GeoJSON (JS-native default) + WKT/EWKT
(Django parity) via format.