react-resource-view
Changelog
What each release of react-resource-view added, fixed and changed.
Read from the CHANGELOG.md that Changesets maintains in the repository, so a release appears here as soon as its version is merged. Dates and download links are on the GitHub releases, and the package itself is on npm.
1.0.0#
Major Changes
- 1.0: a declaration that reads once and makes sense without the documentation. Breaking changes, each one rewritten by
npx react-resource-view migrateor explained step by step inMIGRATION-1.0.md;migrations/1.0.jsonis the same list as data, for a script or an AI to apply.- **One place per notion.**viewis gone: what every action shares —form,fullWidth,titleField,components,description— sits at the root of the resource; what belongs to one action sits inviews.list | read | create | update | delete, each typed with only the options that have an effect there. Root, action and layout are merged deeply, soviews.updatesays only what differs, andinputs: { notes: false }removes an inherited field. Actions are plain strings (views.update,rowActions: ["read"]);ActionListis still accepted. - **Permissions.**canRead,canList,canCreate,canUpdate,canDeleteandbehavior.canExportbecome onepermissionsobject —"all","readonly", a list of actions, or a boolean or a function per action. Everything is allowed by default, so the shortest declaration has all its buttons. A function is told the record (({ item }) => item.status !== "done") and may answer asynchronously; answers are cached per record while a list is on screen. A refused action also protects its route:/books/update/1draws the scope'sforbiddenFallbackinstead of the form.canListis now read for the list (it was ignored).usePermission(action, item)anduseAllowedActions(actions, item)are added;permissionResourcetakes{ item, context }and may return a promise. - **behavioris gone**, each option moved to where it has an effect:openIn→views.<action>.open("page","dialog","drawer");closeAfterUpdate,refreshDataAfterUpdate,redirectToAfterUpdate→ oneafterSave: "close" | "stay" | "reload" | { redirect: (item) => string };rowActions→views.list.rowActions;eventSourced→realtimeat the root;openRecordIn→views.read.open;selectable,bulkActions→views.list.selectable,views.list.bulkActions, for every layout of the list (bulkActionsalso takes"delete"). - **Layouts.**viewVariantsbecomesviews.list.layouts, and the seven factories onelayoutnamespace:layout.table(),layout.cards({ columns }),layout.board({ groupBy, columns }),layout.calendar({ dateField }),layout.timeline({ startField, endField, groupBy }),layout.split({ readInList }),layout.items()andlayout.custom()for one's own (read its options withuseLayout()). Field options are checked against the resource's item type — a typo is a TypeScript error — and share one suffix:titleField,dateField,colorField,statusField. A layout's URL id is its type, so a new label breaks no shared link;fieldspicks a layout's columns. - **Data.** Withoutpath,storage: "local"must be declared: a forgotten path no longer turns into localStorage silently.preGetCollection,preGetItem,preCreate,preUpdatebecomehooks.beforeList,beforeRead,beforeCreate,beforeUpdate, joined bybeforeDelete,afterSaveandafterDelete; hooks may be asynchronous and always receive the view context — creations and updates used to lose it on the way to the repository. Hand-written repository methods move torepository: { … }.limitbecomesquota: { get, fallback }(useLimit→useQuota). - **Titles and components.**nameis declared once; titles are derived and overridden withviews.<action>.title, a button's text withbuttonLabel.viewComponentand the otherxxxComponentkeys, anddecoratorComponent, become onecomponentsobject (view,wrapper,top,bottom,empty— exnoResult—,pagination,navigation,title,actions). - **Tabs.**subViewResourcebecomesviews.read | update.tabs: { orientation, aside, items }, a tab being{ id, label, resource, filter: ({ item }) => … }instead of anonInitViewResourcereturning the whole context. - **Scopes and defaults.** A resource no longer names itsscope: the scope'sresourcesties them, andmenu: "auto"(or a list mixing resources, links and groups) builds the navigation.setResourceConfig({ defaultResource })becomesconfigureResourceView({ defaults }), merged deeply into every resource, including those declared before it is called. - **Types.**ResourceDefinitionis what one writes,ViewResourcewhat the library builds;ViewResourceInterface,ResourceOptionInterface,ViewInterfaceand the factory option interfaces are replaced. - **Optimistic writes.** Every write going through a resource's repository — a form saved, a row deleted, a cell edited in place, a card moved, the writes of a bulk action made through its newmutationargument — is drawn before the API answers: the row changes at once (a created one appears, marked as being saved), the API's answer replaces it as soon as it comes, and a refusal puts it back with a toast — a form keeps the API's violations on its fields. Several writes to one record show the last one; a list read again while a write is in flight (a realtime push) keeps the row as written. On by default — a change of perceived behaviour —, turned off withoptimistic: falseat the root or per action (views.delete: { optimistic: false }).useResourceMutation(resource)gives an application's own actions the same optimism and rollback (create,update,remove,mutate,isPending), andRowInterfacegainspending. Built on the library's own state rather than React 19'suseOptimistic: no new requirement on the React version. - **One routed opening.**views.<action>.openis resolved once, and anything but"page"is written into the URL under the view it was opened from (…/tasks/list/tasks/update/12, filter and layout kept): a row's button — now a real link —, a board's card and a link typed by hand open the same frame, Back closes it, a reload opens it again over the same list, and a save closes it by going back to that URL. A list reads itself again once per save, instead of once per button of its rows.layout.board({ openRecord, expandable })is gone: a card opens the record'sreadview whereviews.read.opensays — on its page by default, where it used to open nothing. - **Frames of one's own.**open.custom({ type, placement, component }), on the model oflayout.custom(): the component receives the title, the view,closeand the link to the full page.placement: "inline"draws the view under the row that opened it in a table, a plain list or a card grid — alayout.custom()says it can withsupports: ["inline"]and draws<InlineChildView row={row} />— and falls back to a dialog elsewhere, with a warning in development.configureResourceView({ open: { accordion } })registers a frame under a name, typed through theOpenFramesinterface. - **"Open in full page".** Areadorupdateview opened in a frame has a link to the same view on its page beside the close button, a real<a href>that Ctrl/Cmd+click opens in a new tab.expandable: falseon the view takes it away;createanddeletedo not have it unless they sayexpandable: true. - **Checked in development.**createViewResourceandlayout.*throw on a key they do not read — with the closest key, or the exact 1.0 rewrite of a 0.x one — on a resource with no data source, a board withoutgroupBy, two layouts sharing a URL id, and warn about a title identical to the derived one. Nothing is checked in production. - **Command line.**npx react-resource-view migrate [paths]rewrites 0.x declarations (--dry-run,--check);create-view-variantwrites alayout.custom()file;initstarts on the 1.0 syntax.
Minor Changes
- feat(views): a list sorts by its column headers, selects its records in every layout and says when it could not load, not only on the board and not with an empty list-
views.list.sort: { fields, initial }: the table's headers of the fields named sort ascending, then descending, then not at all (aria-sort); the sort goes to the API through the dialect, into the URL, and survives "Clear the search".useSort()gives the same to a layout of one's own. - **Breaking (1.0):**selectableandbulkActionsleavelayout.board()forviews.list, and reach every layout: checkboxes on the table's rows (and "Select all" in its header), the cards and the items.bulkActionsaccepts"delete", which deletes the selection after a confirmation, as far aspermissions.deleteallows.selectabledefaults totrueon a board and wherever bulk actions are declared.npx react-resource-view migraterewrites both (RV1-BEHAV-006, RV1-LAYOUT-014). - A view whose records could not be loaded draws what the API answered and "Try again" (components.errorreplaces it), rather than "An error occurred" in a list, or an empty form that could be saved over the record.loadErroron the view context holds what the request threw. - Moving to another view shows that the click was heard, instead of nothing until the next view appears at once: a thin bar runs along the top of the screen, in the theme's primary colour, until the new view has its data, and the view fades in, sliding up a few pixels. No new dependency.- The bar waits 150 ms, so an instant navigation flashes nothing, then stays 300 ms at least; it runs quickly, then slower, and fills up when the view is there. The loading view is marked
aria-busy, the bar hasrole="progressbar", and neither takes the focus. - Underprefers-reduced-motion: reduce, the view fades without moving. -ResourceViewProvidertakesnavigationLoader— a node, a component receiving{ isNavigating }, orfalsefor nothing — andviewTransition(trueby default). - Any router works: a navigation counts from a click on a link of the library, or a call to itsnavigate, until the new view has loaded. The navigation port takes an optionaluseIsNavigating, which the TanStack adapter reads fromuseRouterState. -useIsNavigating(),useDelayedVisibility()andNavigationProgressBarare exported, for an indicator drawn elsewhere.
0.15.0#
Minor Changes
- Open a card of a board, select several and move them together, instead of only dragging one card at a time onto a column.-
behavior.openRecordIn("drawer","popup"or"window") makes the whole card clickable — hover and focus states, Enter from the keyboard — and opens the record'sreadview: in a panel on the right or a dialog, both written into the URL as a child view of the list (…/tasks/list/tasks/read/12, whichparseLinknow reads), so the link is shareable and Back closes it; or on the record's own page, throughgenerateLink. The panel has a way to edit the record, traps the focus and gives it back to the card. - A button, a link, a menu, a checkbox or a drag handle drawn in a card keeps its click, without anystopPropagation: the card only opens on a click that lands on none of them (data-card-controlmarks anything else), and not when text is being selected. - Cards can be selected — checkbox, Ctrl/Cmd+click, Shift+click for a range, a long press on a phone, Escape to clear — and a bar offers "Move to…",behavior.bulkActionsand "Deselect all". Dragging a selected card moves the whole selection under an "N cards" preview. Each record is updated through the repository and drawn in its new column at once; one that fails goes back, and a message says how many could not be moved.behavior.selectable: falseturns selection off. - Each column scrolls on its own, and an empty column shows where to drop. - Add a Django REST Framework dialect:
djangoDialect()reads DRF's{ count, next, previous, results }page (or a bare array when the view has no paginator), pages withpage/page_size— orlimit/offsetwithpagination: "limit-offset"— sorts withordering=-field, sends filters as query parameters for django-filter, addresses a record at/articles/{id}/with DRF's trailing slash, and pins a 400's{ field: ["message"] }on its input. Options:primaryKey,pagination,pageSizeParam,defaultItemsPerPage,orderParamandtrailingSlash.npx react-resource-view init -b djangostarts a back office on it, dumping the schema drf-spectacular serves at/api/schema/?format=json.
Patch Changes
initwrites thereact-data-form,react-mini-i18n,resource-registryand@tanstack/react-routerranges from the package's ownpeerDependencies, so a generated starter installs without--legacy-peer-depsinstead of failing withERESOLVEon a hard-codedreact-data-form@^0.2.1against a^0.1.0peer.- A record saved from a button opening over a list (
openIn: "popup"or"drawer") reads the list again with the filter it has now, instead of the one the button mounted with — a list filtered on « A », then on « B », no longer shows the rows of « A » under « B » after a save. The buttons of every row also share a single request instead of each reading the list again.
0.14.0#
Minor Changes
- Add a FastAPI dialect:
fastapiDialect()lists, reads, creates, updates and deletes against a standard FastAPI CRUD router, instead of showing empty lists under the JSON-LD default.A page isskip/limit, as in the FastAPI tutorial, orpage/sizewithpagination: "page-size"for fastapi-pagination, whose{ items, total }drives the page count; a bare array hides the pagination. A sort isorder_by=-created_at,title, an array filter repeats its parameter, and a 422 lands under the field itslocnames —["body", "address", "city"]onaddress.city. Options:primaryKey,pagination,defaultItemsPerPage,orderParamandtrailingSlash. - Add
npx react-resource-view init: it starts a back office on your API — API Platform, Spring Boot, another REST API, Supabase, Strapi or FastAPI — with the dialect wired, the URL in.env, andapi-schema/api-schema:checkscripts that dump the OpenAPI schema into TypeScript with api-dumper.Add a Spring Boot dialect:springDialect()pages withpage(from 0) andsize, sorts withsort=field,desc, reads aPage, aPagedModelor Spring Data REST's HAL, and pins Bean Validation errors on their field.
Patch Changes
- Capitalise the first letter of each view name in the view switcher, so a
boardorvue d'ensembletab readsBoardandVue d'ensemble, custom and translated names included — the rest of the label is left as written.
0.13.0#
Minor Changes
- Recognize a 401 or a 403 thrown by a scope's
authorizationfrom itsstatus, not only fromUnauthorizedErrorandForbiddenError. Any error carryingstatus: 401callsonUnauthorized, any error carryingstatus: 403renders theforbiddenFallback: an auth library no longer has to import the classes, and two copies ofreact-resource-viewin one bundle still agree. No other status is recognized, and errors recognized today behave as before.
Patch Changes
- Save a card moved to another column of a board kept in the local repository, instead of answering "Une erreur est survenue" and putting the card back.The drop sent only the short identifier of the record (
12), which the local repository compared to the@idit keys records on (/tasks/12) and never found. It now finds the dropped row again and sends the whole identity the dialect reads it by —@idandidfor JSON-LD,documentIdfor Strapi, the primary key for Supabase — so every repository addresses the record as before. A card dropped back into its own column sends nothing.The error a failed update shows is now the translatable keyAn error occurred, also used when a list or a calendar fails to load, rather than a French sentence. Applications translatingUne erreur est survenueshould add the new key. - Keep a column of the columns view at its width when one of its cards holds a long unbreakable word (a path, a URL, an identifier). As a flex item, the column was never narrower than its widest content: once a parent sized the columns, it grew around the word and slid under its neighbours. The column is now
min-w-0and the content of a record cardbreak-words, so the word wraps inside the card instead. - Keep the horizontal sub-view bar inside the page and level with the side column when
subViewResourcehas one, instead of pushing the page sideways and starting 4rem below the column.The sub-view track was a bare1fr, which is never narrower than what it holds — and the bar is as wide as all its tabs, a table in a tab as wide as its columns. The track is nowminmax(0, 1fr)and the tabs containermin-w-0, so the bar scrolls inside the width left beside the column, as it already did without one.The bar was also meant to stay pinned under the top bar while a long sub-view scrolls, but the scroll area's root setsposition: relativeinline, which beat itsstickyclass: the bar never stuck, and itstopoffset moved it down instead. The bar is now pinned from a wrapper of its own. - Keep the horizontal sub-view tab bar within its column when
subViewResource.viewComponentdraws a left column beside it. The tabs' grid track grew to the width of the whole row of tabs, so the bar never scrolled: the last tabs were cut off and the page itself overflowed to the right. The bar now scrolls sideways as it does without a left column; the vertical menu and records without a left column render as before.
0.12.0#
Minor Changes
- Let a scope's
authorizationwait for a real session —authClient.getSession(), a/mecall — instead of having to answer synchronously, and keep the scope hidden until it has.authorizationmay now returnboolean | Promise<boolean>; a synchronous function works as before. Nothing of the scope renders while it is pending:authorizationFallbackshows instead, the page loader by default. AnUnauthorizedError, thrown or rejected, still callsonUnauthorized. AForbiddenError, thrown or rejected, or afalseresult, now rendersforbiddenFallback— an "Access denied" message by default — where aForbiddenErrorused to reach the error boundary and afalsewas ignored. Both fallbacks are accepted on the scope and on the provider configuration, the scope's winning.The check runs again when the scope changes, and on demand throughrecheckAuthorization()fromuseScopeContext(), so a sign-out is taken into account without a reload. An answer that arrives after the scope changed is ignored. - Pin a block at the bottom of
AdminLayout's sidebar — "Need help?", support, documentation — instead of leaving its footer empty with no way to fill it.AdminLayoutandcreateAdminLayoutnow takefooterMenu, menu entries drawn like the scope'smenuunder an optionalfooterMenuTitle, andsidebarFooter, any node rendered below them. An entry whosehrefis absolute leaves the application: a web page opens in a new tab, amailto:hands over to the mail client. On narrow screens both go behind a last entry of the bottom bar, named afterfooterMenuTitle("More" without one), which opens them in a drawer. Without either option the sidebar renders no footer, as before.
Patch Changes
- Leave out the foot of a record card — its separator and the padding under it — when the resource permits none of the row's actions, instead of ending the card on a stray line above an empty strip.The card layout now reads which actions a row offers (
behavior.rowActions, thencanUpdate,canDelete…) before drawing their bar, as the calendar and timeline preview already did. A card with at least one permitted action is unchanged.
0.11.0#
Minor Changes
- Name the record on screen in
AdminLayout, instead of an empty top bar above a bare "Edit a user".The top bar now carries a breadcrumb — the scope'slabel, the resource, the record — where it used to hold nothing but the sidebar toggle. On a record, the page header links back to the list, takes the record's name as its title (read fromtitle,nameorlabel, or from the view's newtitleKey), says the action and the id underneath, and offers the actions still worth having there: edit from the read page, delete from either — deleting goes back to the list.A view overrides the title withcomponents.titleand the actions withcomponents.actions;components.navigationstill replaces the whole header. - Give a board, a calendar or a wide table the whole page in
AdminLayout, instead of the column every view was squeezed into.A view now takesfullWidth. Set on a resource'sview, it covers every action of the resource; on one ofviews, that action; on a list variant built by a*ViewOptionFactory, that variant only. The most specific declaration wins, so a resource drawn full width keeps its forms narrow withviews: { update: { fullWidth: false } }, and a board can be the one wide layout of a list whose table stays in the column.AdminLayoutdrops its width constraint for such a view and keeps its side margins; nothing changes for a view that does not ask.isFullWidthViewresolves the option for the view on screen, for adecoratorComponentof one's own that wants to honour it too.
Patch Changes
- Light the sidebar entry of the page
AdminLayoutopens on, and unfold the group of the page on screen, instead of leaving the reader to find either.A scope opened bare —/playground,?view=admin— shows the page itsdefaultViewResourceContextParamsname, but its URL names none, so no entry was lit until the reader clicked on one.useIsActiveItemMenunow takes that default view, andAdminLayouthands it the scope's.A sub-entry on screen now unfolds its group, even one the reader had folded on an earlier visit, and marks it with the accent colour, without the background the page itself carries. The entry on screen also carriesaria-current="page". - Fold the admin sidebar away, and keep a menu entry lit on the page it points at.Two things
AdminLayoutgot wrong on the screen it is mostly read on. The top bar's button only ever opened the mobile drawer, so on a wide screen — the one place it is shown — pressing it did nothing; it now folds the sidebar column away and back.And a menu entry was matched against the address bar as a string, which held only while the URL was exactly the one the entry was built from: opening a record of a resource turned its entry off, and in query mode an application whose views are mounted somewhere other than the configuredbasePathnever lit any entry at all. The comparison is now made context by context — scope, resource, record — so an entry pointing at a list stays lit for everything inside that list, while two entries on the same resource (its list, its creation form) stay distinguishable.
0.10.0#
Minor Changes
- Give a scope a ready-made admin shell instead of writing one from scratch.Every project reached for the same
decoratorComponent: a collapsible sidebar built from the scope'smenu, a top bar, a page header with sub-navigation tabs, and a bottom bar on narrow screens instead of the sidebar.AdminLayoutis that shell, exported so a scope only needs to point at it:ts const adminScope: ScopeInterface = { name: "admin", decoratorComponent: AdminLayout, menu: [createItemMenuWithResource({ resource: articlesResource })], }createAdminLayout({ logo, topBarEnd })builds a configured variant when the host wants a logo or actions in the top bar —decoratorComponentonly ever receiveschildren, soAdminLayoutitself takes no props there.
0.9.0#
Minor Changes
- Read the sub-views of a record without three rows of buttons in the way.The tab bar wrapped onto as many lines as the tabs needed, and cut every label to twelve characters below
mdto limit the damage — a record with eight sub-views pushed its content off a phone screen before a word of it was read. The bar now stays on one line and scrolls sideways, labels whole.subViewResource.orientation: "vertical"is the other shape: the tabs go down a column beside the sub-view, which is what a record with a dozen of them wants — the whole menu is read at once, and it follows the reader down the page. Belowmdthe column would leave the sub-view a third of the screen, so it falls back to the scrolling bar there; the same declaration reads both ways.
0.8.0#
Minor Changes
- Open a small window on a calendar event or a timeline bar.Clicking one used to print every key of the record —
@context,@idand the dates already written on the event included — which is a data dump rather than a preview. What opens now names the event, dates it in one line, summarises it in at most five fields labelled the way the resource'sform.inputslabels them, and hands over to the row's actions for the rest.The summary is the variant'srowComponent, which both layouts now default toPreviewRowComponentinstead ofDumpRowComponent: a resource with something better to show still declares its own, exactly as in the card and list layouts.
Patch Changes
- Keep a list header on one line when its description is long.The header laid the name, the layout switcher and the create button side by side, each free to wrap on its own. A resource introduced by a full sentence took the whole width for it, and the create button was pushed alone onto the line below, floating under the tabs.The text now yields instead of pushing — it is the flexible part of the line — and the switcher, the export and the create button form a single bar, so the width that does run out takes the whole bar down, never one button of it.
0.7.0#
Minor Changes
- Open an action in a drawer.
behavior: { openIn: "drawer" }joins"popup"and"window": the view slides in as a panel rather than replacing the page or sitting in the middle of it. From the right on a desktop, up from the bottom below themdbreakpoint — where a thumb reaches — and swiped away towards the side it came from.The panel is as tall as the screen, which is what a long form wants: a record with a dozen fields is filled in without the list behind it going anywhere. Like a dialog, it closes itself on the resource'sonChangerather than navigating after a creation. - Give a list a header of its own.The create button, the export button and the layout switcher used to be stacked above the rows as three loose controls, and the resource's
icon— already used by the menu and by the sub-view tabs — appeared nowhere on the screen it belongs to. They are now one line: the icon, the view'snameand itsdescriptionon the left, the layout switcher beside them, the actions on the right.Nothing is declared for it: a resource that names no icon simply shows none, and a list nested inside another view — a sub-view tab — leaves the naming to whatever contains it. The filter bar is untouched. - Add a command that scaffolds a view variant.
npx react-resource-view create-view-variant Heatmap --dir src/viewswrites one file — the list, row and item components, the factory that declares them and the options interface to extend — where the project keeps its views. Run bare, it asks for the name and the directory;--icon,--jsx,--force,--dry-runand--yescover the rest.Nothing about declaring a variant changes: the generated factory goes intoviewVariantsbeside the built-in ones.
0.6.0#
Minor Changes
- Speak Strapi and Supabase, not only JSON-LD.The views assumed one API family everywhere: a collection was
memberandtotalItems, an item carried its own IRI in@id, a page waspageanditemsPerPage, a validation failure was a list ofviolations. Every one of those is now a decision an **API dialect** makes, and three ship with the package:ts import { configureApi, strapiDialect } from "react-resource-view"configureApi({ baseUrl: "https://cms.example.com", getAuthToken: () => getUserToken(), dialect: strapiDialect(), })-jsonLdDialect()— API Platform and Hydra. The default, and it still goes through the client ofjsonld-api-client, so an application already talking to API Platform needs no change at all. -strapiDialect()— Strapi v4 and v5.pagination[page],filters[field][$eq],sort[0],populate, writes wrapped indata, and the v4{ id, attributes }envelope flattened so both versions read alike. -supabaseDialect({ apiKey })— Supabase, over PostgREST.limit/offset,field=eq.value,order, the count read fromContent-Range, rows addressed by their primary key, and writes that ask for the stored row back.A resource may also declare adialectof its own, for an application reading two backends at once. Filters, pagination and sorting are written once, in the package's own vocabulary, and the dialect translates them.JSON-LD is now an option rather than an assumption: a relation is only dereferenced as an IRI where the dialect says relations are IRIs, no Mercure subscription is opened against a backend that runs no hub, and the CSV export button hides itself where the API serves no CSV. - A row now offers update and delete. Read is gone from the default, and which actions a row draws is a view's decision —
view.behavior.rowActions.**This changes what an existing list renders.** A row is already the record — the table edits it in place, the other layouts draw it in full — so a button whose only job is to show the same fields again was not earning the width it took. Put it back where the detail view carries more than the list does:ts tableViewOptionFactory({ behavior: { rowActions: [ActionList.read, ActionList.update, ActionList.delete], }, })Permissions still apply on top: an action listed there without the matchingcan*renders nothing.ListResourceViewButtonhad two implementations — one exported from the entry point, one declared insideResourceViewButton.tsxand used by the table. The duplicate is gone; both now resolve to the same component.The dialog's close button carried a hard-coded French label, which no dictionary could reach. It goes throughTranslike every other string.
Patch Changes
- Stop overwriting
document.titlewhenownsDocumentHeadisfalse.The port says the host application declares its own head, but the title was still written imperatively from an effect. Any page embedding a view — a documentation page, a dashboard panel — ended up carrying the name of the embedded view instead of its own. - Keep the chosen layout when a list navigates to itself.Selecting a row in the split view switched the reader to the table. Two things lost the variant on the way:-
generateLinknever serialised it, so the URL the split view navigated to said nothing about which layout it came from. It now carries?variant=<id>, andparseLinkreads it back. -useResolvedViewParamsrebuilds the context field by field, andviewVariantIdwas not among them — so a variant handed toResourceViewProvider, or read off the URL, was dropped before it reached the view. Both now come through.Every layout that renders a record in full — card, item list, columns, split — draws it in a frame of its own and offers the row's actions, instead of handing the barerowComponentto the page.The columns layout wrapped each row a second time before passing it on, so the row component received an object whose every field wasundefinedand the board drew blank cards. Columns also gained a count, an empty state, and a drop target that highlights only the column under the pointer.The timeline'scolorByStatuswas declared by the factory and read by nobody; bars now take their colour from it. Its two footer counts were adjacent bare text nodes — a single anonymous flex item — so they read as one glued sentence rather than sitting at either end. - Make a form opening in a dialog behave like one.
behavior: { openIn: "popup" }drew a dialog with no name, introduced by whatever sentence the _list_ had been given — every action inherits the resource'sview, description included — and a creation made from it navigated straight out of the page the dialog sat on, taking the list, its filters and its page with it.- A popup is now titled by the view it opens, which assistive technology announces and a reader can read; the list's description stays on the list. - After a creation in a popup nothing navigates: the dialog closes on the resource'sonChangeand the list refreshes underneath. On a screen of its own the move to the new record's edit view is unchanged — and now builds its link in the resource's own scope, instead of walking out of the area it was made in. - A view an action does not name is called after that action: a resource whose list is "Coffee beans" edits "Edit — Coffee beans" rather than a second "Coffee beans". A name the action declares is left alone. - The delete confirmation speaks English — "Supprimer ?", "Annuler" and the error toast were shipped in French and never went throughreact-mini-i18n— and no longer repeats the name of the view that frames it. - Cards of one row line their actions up on the bottom edge, instead of leaving each set of buttons wherever its record happened to end.
0.5.0#
Minor Changes
- Stop shipping French. The calendar and the timeline formatted their dates with a hardcoded
frdate-fns locale, started their weeks on Monday and sorted their rows with a French collation, and three labels were written in French. Dates now follow thedateLocaleport — English (US) by default — which also decides the first day of the week and the collation:ts import { fr } from "date-fns/locale" configurePorts({ dateLocale: fr })getDateLocale()andgetWeekStartsOn()read it back.**Breaking for applications relying on the former defaults:** "Aujourd'hui" became "Today", "Nettoyer la recherche" became "Clear the search", and dates are formatted in English untildateLocaleis set. Translate the new labels throughreact-mini-i18n, as with every other label of the package.
0.4.1#
Patch Changes
- Keep what the user picks while the list is fetching. A request landing used to write the whole view context back — the one captured when it left — so a layout chosen from the variant tabs, or a filter applied, was reverted a few milliseconds later. Requests now bring back rows only,
setViewResourceaccepts an updater like React's own setters, and the variant tabs are controlled, so the highlighted tab is always the layout on screen.The timeline's footer counts are translatable, and the header of its row column can be named withgroupsLabel.
0.4.0#
Minor Changes
0.3.1#
Patch Changes
0.3.0#
Minor Changes
- Initial release: CRUD views for JSON-LD / Hydra APIs — list, read, create, update and delete — in table, card, column, split, calendar or timeline layouts, wired to the API and to the URL.The package knows no router: it asks for four navigation primitives through
configurePorts, and ships an adapter for TanStack Router on the/tanstacksubpath. Applications on another router install nothing extra. - The view context can now live entirely in the query string.
configurePorts({ routing: { mode: "query", basePath: "/docs.html" } })writes links as/docs.html?view=admin/articles/update/42instead of/admin/articles/update/42. It exists for static hosting, where a deep path has no server to answer it and returns 404, and for embedding the views in a page whose path is not yours to control.parseLinkreads a URL carrying the routing parameter as query mode whatever the configuration says, so a link shared from a statically hosted page keeps working wherever it is opened.- AdduseIsActiveItemMenu, which works while rendering on a server.isActiveItemMenureadswindow.location. There is nowindowon a server, so it threw mid-render; React caught it, silently fell back to rendering in the browser, and left an empty document behind — a page that looks fine to a visitor and empty to anything that only reads the markup.The new hook asks the navigation port instead, which a router can answer on either side, and returns a predicate so a menu can test every entry.isActiveItemMenuno longer throws off the browser, but reports every entry as inactive there; prefer the hook wherever the markup is rendered on a server.
Patch Changes
isActiveItemMenunow works in query mode.It comparedwindow.location.pathnameagainst the entry's href. In query mode the context lives in the query string, so the pathname is the same for every page and no entry was ever marked active.- Fix
filteranddefaultDatalosing everything after a reserved character.They were serialised withencodeURI, which leaves&,=and#untouched. A value such as{ title: "Tom & Jerry" }produced?defaultData={"title":"Tom & Jerry"}, where the ampersand ended the parameter and the rest was read as a stray one.encodeURIComponentescapes them.Links written with the previous encoding still parse, so bookmarks and shared URLs keep working.