Skip to content

v4.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 25 Sep 08:20
cc510fb

4.0.0 (2026-09-24)

⚠ BREAKING CHANGES

  • lov: ILovConfiguration<TItem, TValue> gained a GetByKeyProvider property. A custom class implementing that interface must add it (returning null keeps the previous behaviour). Configurations built through LovBuilder are unaffected.
  • lov: MudBlazorLovFieldComponent and FluentUILovFieldComponent gained a TKey type parameter (<TModel, TValue, TKey, TItem>), with TValue now the field's bound type. Code that renders these components directly by their old three-argument generic shape must add the key type; fields configured through .AsLov(...) / .AsMultiSelectLov(...) are unaffected.
  • lov: AsMultiSelectLov<TModel, TValue, TItem>'s configure parameter changed from Action<LovBuilder<TModel, IEnumerable<TValue>, TItem>> to Action<LovBuilder<TModel, TValue, TItem>>, so WithKey now takes the scalar item key. Callers with an explicitly-typed configure lambda must update the type; inferred lambdas are unaffected.
  • core: IFormConfiguration<TModel>.ShowValidationSummary now defaults to false (it was true, but had no effect). A form now renders a validation summary only after opting in with .ShowValidationSummary(). IFormConfiguration<TModel>.ShowRequiredIndicator, .RequiredIndicator, FormBuilder<TModel>.ShowRequiredIndicator(bool, string) and FormLayout.Horizontal are [Obsolete]. A consumer that builds with warnings as errors gets CS0618 until it moves to .WithNativeRequired() / FormLayout.Grid or suppresses the warning locally.
  • core: a nested field's FieldName is now its full dotted path (Shipping.Value), not its last member name (Value). This is visible through GetRequiredFields, OnFieldChanged, validation message keys (FieldIdentifier), DependsOn/group membership keys, and DOM ids built from the field name. Single-level fields are unchanged.
  • security: EncryptField now fails closed. An expression that does not resolve to a string property, or a path that cannot be resolved, throws ArgumentException at configuration time instead of being silently skipped. Nested members (x => x.Address.City) are now actually encrypted and stored under their full dotted path.
  • core: FormDiagnosticScope and DiagnosticLog moved from FormCraft.ForMudBlazor to FormCraft.Diagnostics (core). Code that references them by the old namespace must update its using directive. No forwarding shim is provided.
  • core: move the UI-agnostic adapter machinery into core (#279) (#289)
  • core: expose Validators as IReadOnlyList and require AddValidator (#245)
  • lov: WithDisplay(Expression<Func<TItem,string>>) is removed. Pass a lambda directly, or expr.Compile() if you hold an Expression.

Features

  • core: localize default validation messages (#354) (#361) (6a9a2dc)
  • demo: redesign the demo site around code beside the live form (#390) (60766d3)
  • demo: redesign the demo site around the code-to-form binding (#268) (679e0a2)
  • fluentui: add a FormCraft.ForFluentUI adapter for Fluent UI Blazor v5 (#260) (#261) (713e358)
  • fluentui: bring the Fluent UI adapter to parity with MudBlazor (#278) (#291) (6f6acaf)
  • mudblazor: add a typed builder method for the native required opt-in (#204) (#236) (4210be4)
  • mudblazor: add a typed WithMask builder for text field masks (#265) (#273) (61e6445)
  • mudblazor: announce required fields without the HTML5 required attribute (#263) (#353) (3341705)
  • mudblazor: announce required file-upload fields to assistive technology (#262) (#270) (ba02aad)
  • mudblazor: give numeric adornments a typed click handler (#215) (#244) (8a03598)
  • mudblazor: make ShrinkLabel configurable per field and per form (#177) (#178) (7a7dc30)
  • mudblazor: warn when a mask blanks a stored value (#266) (#274) (1ea5425)
  • mudblazor: warn when ShrinkLabel=false cannot be honoured (#181) (#183) (b22d05d)
  • mudblazor: warn when the password toggle displaces a configured adornment (#219) (#243) (eccbf2d)

Bug Fixes

  • adapters: custom template no longer renders nothing for a non-property field (#330) (#387) (0d378af)
  • adapters: make Add/Remove inert and diagnosable on an unreadable collection binding (#433) (#439) (a8adaf5)
  • adapters: stop a null-intermediate collection binding from crashing render (#419) (#427) (1bd2dc3)
  • adapters: warn once on an unwritable nested collection item edit (#450) (#451) (e774a46)
  • adapters: write back through the field's compiled expression for custom templates (#413) (20d80c3)
  • build: stop Clean deleting the build output it is running from (#310) (#368) (7e3f9e7)
  • core: build CollectionSetter from the full nested member chain (#420) (#424) (9bac0ce)
  • core: evaluate DisabledCondition on render, mirroring VisibleWhen (#476) (#479) (e51c9ef)
  • core: honour or retire the inert form-level settings (#457) (8d75085)
  • core: keep collection validation messages consistent with the rendered form (#342) (#379) (69b56c4)
  • core: qualify collection field identity by its full nested path (#428) (#434) (e87756f)
  • core: qualify scalar field identity by its full nested path (#437) (37cb742)
  • core: resolve DisabledCondition in the core stub field renderers (#483) (#489) (f9df972)
  • core: share one security pipeline across both adapters (#321) (#402) (e1488a1)
  • core: stamp identifiers a validation pass writes fresh (#445) (#446) (a7e74ec)
  • core: stamp valid collection cells and skip hidden fields on write (#447) (#452) (430c664)
  • core: stop an unreadable nested binding from crashing render or validation (#397) (#403) (45d7c0d)
  • core: stop an unreadable nested collection binding from crashing validation (#414) (f52faa0)
  • core: stop FieldValueGetterCache swallowing genuine accessor faults and cancellation (#425) (#438) (6f6aaee)
  • core: validate each collection item field once per pass (#329) (#331) (65944e5)
  • demo: allow pinch-zoom in the Fluent UI demo (#341) (#360) (af2900e)
  • demo: correct ARIA state, disposal and fade-in regressions (#285) (#295) (3dd997c)
  • demo: guard every timed re-render against component disposal (#315) (#323) (8557ce3)
  • demo: make every demo page readable and fill the gallery (#405) (e5260fc)
  • demo: stop nesting cards in the form panel and give wide forms room (#411) (36e18c3)
  • fluentui: bind numeric bounds explicitly so a dropped bound is dropped (#348) (#382) (d4952cd)
  • fluentui: clear TooManyFilesError on a multiple-file-upload config swap (#416) (#421) (bd46189)
  • fluentui: focus the collection controls' own buttons, not their wrapper spans (#383) (#399) (f073d88)
  • fluentui: keep keyboard focus after collection controls unmount (#337) (#377) (59ea837)
  • fluentui: key collection rows on a weak per-item token (#401) (#410) (c73d088)
  • fluentui: re-read a field's configuration when the rendered field changes (#335) (#336) (d3af1ff)
  • lov: bind AsMultiSelectLov's WithKey to the scalar per-item key (#467) (70fa17c)
  • lov: let WithDisplay and WithKey accept a plain lambda (#180) (#182) (fa88097)
  • lov: resolve multi-select LOV component TValue from the field's bound type (#480) (0780e08)
  • lov: WithDataSource(collection) honors search/sort/context filters (#472) (a8f0561)
  • mudblazor: announce required fields to assistive technology (#199) (#254) (e8e3586)
  • mudblazor: bind a configured mask on both render paths (#211) (#253) (8484dba)
  • mudblazor: bind DisabledWhen to MudBlazor file upload components (#487) (0343e59)
  • mudblazor: bind factory masks whose pattern is computed lazily (#317) (#366) (75ebb81)
  • mudblazor: clear a multiple-file upload to an empty list, notifying once (#319) (#376) (0828678)
  • mudblazor: closing one LOV chip removes only that item instead of clearing the whole selection (#475) (0780e08)
  • mudblazor: dedupe field diagnostics across component re-mounts (#304) (#309) (44d46ff)
  • mudblazor: drop the write-only _valueLookup cache from the autocomplete component (#404) (#415) (769ec49)
  • mudblazor: focus Browse after clearing a file upload (#281) (#292) (da7d1dc)
  • mudblazor: forward a configured adornment to date item fields (#217) (#248) (b4b649f)
  • mudblazor: hide the upload's native file input and keep drops working (#456) (6d0de99)
  • mudblazor: honour a configured Culture on numeric item fields (#218) (#247) (e31a7e6)
  • mudblazor: honour Format and ShowSpinButtons on numeric fields (#208) (#240) (cefa8a8)
  • mudblazor: invoke the adornment click handler passed to WithAdornment (#192) (#194) (92add18)
  • mudblazor: judge the ShrinkLabel diagnostic on the rendered adornment (#212) (#241) (36dc2aa)
  • mudblazor: keep a password field masked when it also configures multiple lines (#207) (#232) (aea3e40)
  • mudblazor: key collection rows on a per-item weak token (#334) (#400) (705a48b)
  • mudblazor: map number, date and time to their input types (#210) (#239) (6dcab2d)
  • mudblazor: mask password fields inside collection item forms (#189) (#196) (421eee3)
  • mudblazor: move focus deliberately after a self-unmounting control (#318) (#324) (33102ff)
  • mudblazor: qualify the ShrinkLabel diagnostic key so scopes cannot collide (#242) (2d591c3)
  • mudblazor: re-read a field's configuration when the rendered field changes (#298) (#308) (947a3a5)
  • mudblazor: render a handler-less adornment as a plain icon, not a button (#216) (#224) (789acae)
  • mudblazor: render adornments on collection item fields (#184) (#188) (89f0dbc)
  • mudblazor: render adornments on numeric fields (#191) (#195) (7b3743f)
  • mudblazor: render each uploaded file once (#338) (#362) (e79abe4)
  • mudblazor: render long, float, short and byte collection item fields (#209) (#246) (0c8be25)
  • mudblazor: render novalidate on the form instead of applying it by script (#206) (#235) (c9b5047)
  • mudblazor: report post-init and partially-discarded masked values (#283) (#293) (af5c605)
  • mudblazor: stop emitting the HTML5 Required attribute on collection item fields (#190) (#193) (3bb3bde)
  • sanitize FieldName for CSS-safe DOM ids on nested fields (#449) (#453) (a47834f)
  • security: encrypt nested EncryptField members through their full path (#423) (5b9e5b3)
  • security: key audit log entries by full nested path, not last member (#406) (#412) (2e4fcf8)
  • security: redact nested audit keys by full path or last segment (#417) (#418) (bfc24df)
  • security: resolve EncryptField paths through inherited interface properties (#429) (#432) (864a071)
  • upload: resolve constraints through one path in every upload component (#340) (#388) (c1606a0)
  • validation: keep prior messages intact when ValidateModelAsync throws mid-pass (#440) (#441) (26ae1ff)
  • validation: stop message-store races in DynamicFormValidator (#443) (#444) (6052839)

Performance

  • core: compile each field's value getter once (#269) (#286) (cdc9ec4)
  • core: compile each field's value getter once per validation (#312) (#320) (320d689)
  • core: remove per-render reflection from the field render path (#314) (#378) (1cae68e)

Refactor

  • build: give the results directory one definition (#339) (#359) (3556327)
  • core: call the collection validator through a typed surface (#344) (#381) (bd725ba)
  • core: expose Validators as IReadOnlyList and require AddValidator (#245) (376866b)
  • core: move the UI-agnostic adapter machinery into core (#279) (#289) (e31cc39)
  • core: share collection row identity tracking across adapters (#422) (#442) (7b5111c)
  • core: share diagnostics warning log and per-form latch across adapters (#398) (c0044cf)
  • core: share numeric type-default resolution between both adapters (#389) (#393) (aaa27e9)
  • mudblazor: express the two diagnostic idioms once each (#284) (#294) (17e0dea)
  • mudblazor: render collection item fields through IFieldRendererService (#203) (#250) (5c66e29)