Compare commits

..

169 Commits

Author SHA1 Message Date
Atanas Korchev
fc6fe54635 Fix 302 redirect during SSR that hurt SEO for clean URLs
During server-side prerendering the ThemeChanged handler would call
NavigateTo which resulted in a 302 redirect (e.g. /datagrid → /datagrid?theme=material3).
This conflicted with the canonical tag pointing back to the clean URL,
confusing Google and causing ranking drops. Unsubscribe from ThemeChanged
during SSR so the redirect no longer occurs. Interactive theme switching
is unaffected since WebAssembly uses a separate scoped instance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 21:33:34 +02:00
Vladimir Enchev
505ffa7fe4 Splitter ChangeStateOnResize added 2026-02-06 12:36:29 +02:00
Vladimir Enchev
4f82ac9599 Accessing sub properties of nullable types improved 2026-02-06 10:36:53 +02:00
Vladimir Enchev
5bbf7c1fde build fixed 2026-02-04 14:46:25 +02:00
Vladimir Enchev
e416cede62 DropDownBase possible null reference exception with browser autofil 2026-02-04 12:08:41 +02:00
Vladimir Enchev
270a7e0f80 DropDown, ListBox and DropDownDatGrid paste using context menu into search box not filtering
Fix #2437
2026-02-04 11:28:12 +02:00
Vladimir Enchev
1a12a75bde Version updated 2026-02-03 09:56:04 +02:00
Vladimir Enchev
d1917eac0c Fixed QRCode eyes with transparent background 2026-02-03 08:44:24 +02:00
Vladimir Enchev
09830f0ea2 DataGrid possible memory leak fixed 2026-02-03 07:46:32 +02:00
Vladimir Enchev
d34e0684fb DataGrid GroupRowRenderEventArgs Expandable property added 2026-02-02 12:09:40 +02:00
Vladimir Enchev
482eca3278 tests fixed 2026-01-30 12:23:10 +02:00
Vladimir Enchev
5c8ac16c83 Version updated 2026-01-30 12:09:54 +02:00
Vladimir Enchev
29382cf0f4 DataGrid QueryOnlyVisibleColumns property added 2026-01-30 12:09:34 +02:00
Vladimir Enchev
53204cc8d6 RadzenPager GoToPage() will not update page index 2026-01-30 09:53:52 +02:00
Vladimir Enchev
6cf550c517 Version updated 2026-01-28 17:54:45 +02:00
vadimstrekha
7bf107af4c Fix sytax error in Radzen.Blazor.js (#2436) 2026-01-28 17:53:21 +02:00
Vladimir Enchev
adf2785a5a Version updated 2026-01-28 15:31:27 +02:00
Vladimir Enchev
cae44df00a Bar Charts have a zero for the min and max on the y-axis
Fix #2434
2026-01-28 15:31:05 +02:00
Vladimir Enchev
8ba1c69573 RadzenQRCode and RadzenBarcode ToSvg() methods added 2026-01-28 10:31:13 +02:00
Vladimir Enchev
56031c2fd4 QRCode and Barcode save to SVG examples added 2026-01-28 10:00:24 +02:00
Vladimir Enchev
ad44802d30 RadzenBarcodeEncoder and RadzenQREncoder made public
Fix #2433
2026-01-27 18:27:40 +02:00
Vladimir Enchev
64ca088e61 ListBox duplicates the first typed letter in WASM when inside Popup
Fix #2429
2026-01-27 14:08:46 +02:00
Theronguard
f4777565a2 Changed the method signature to virtual, to allow overrding Enum translation with component activators (#2432) 2026-01-27 13:14:28 +02:00
Vladimir Enchev
eb1423e757 Version update 2026-01-27 08:34:44 +02:00
Vladimir Enchev
596b251511 DataGrid column custom filter indicator is active even when no filter 2026-01-27 08:34:29 +02:00
Vladimir Enchev
e186315935 DataGrid grouping arrows do not show expanded state
Fix #2431
2026-01-27 08:27:26 +02:00
yordanov
cba9a5120d Reorder examples 2026-01-26 16:14:07 +02:00
Vladimir Enchev
ee62a21ab6 version updated 2026-01-26 14:36:48 +02:00
yordanov
0a5e318f80 Update premium themes 2026-01-26 14:34:01 +02:00
Vladimir Enchev
8dd7d7f521 DataGrid simple string filter clear button not shown 2026-01-26 10:34:05 +02:00
Vladimir Enchev
69573b2d7d demo updated 2026-01-26 10:06:23 +02:00
Vladimir Enchev
5b933c6643 DataGrid reorder column stick to mouse 2026-01-23 12:11:44 +02:00
Atanas Korchev
126b2d1efa RadzenSpiderChart (#2417)
* spider chart added

* code improved

* EventConsole added

* Pastel is the default color scheme

* Fix color schemes in Spider chart

* Update SpiderChart styles

---------

Co-authored-by: Ehab Hussein <me@ehabhussein.com>
Co-authored-by: Vladimir Enchev <vladimir.enchev@gmail.com>
Co-authored-by: yordanov <vasil@yordanov.info>
2026-01-22 14:27:01 +02:00
Vladimir Enchev
a65c1a9482 version updated 2026-01-21 17:31:55 +02:00
Vladimir Enchev
4939e8498a OData filtering by no string columns fixed 2026-01-21 17:31:33 +02:00
Vladimir Enchev
e380467853 Version updated 2026-01-21 10:17:06 +02:00
Vladimir Enchev
d3adc9733b PivotDataGrid user interaction breaks multi-column/row sorting
Fix #2427
2026-01-20 13:37:13 +02:00
Vladimir Enchev
a03fc50ee8 DataFilter should set filter Type on property change 2026-01-20 09:31:07 +02:00
Mason Voxland
c9201bf947 Fix TextProperty documentation in RadzenScheduler (#2426)
Should be string, not DateTime
2026-01-20 09:17:23 +02:00
yordanov
8c8d288afd Fix #2188 rz-layout height should be 100dvh by default 2026-01-19 17:15:40 +02:00
Vladimir Enchev
1a679af008 Chart demo source code fixed 2026-01-19 16:00:26 +02:00
yordanov
bc1654a405 Fix #2218 - RadzenSidebar border should take into account the sidebar's position 2026-01-19 13:12:54 +02:00
Atanas Korchev
94ef62e00b Customize split button dropdown icon demo is showing wrong source code. 2026-01-19 11:33:24 +02:00
Vladimir Enchev
13afd0fddb Version updated 2026-01-19 10:43:13 +02:00
Vladimir Enchev
fa7120571d DataGrid, DataFilter and PivotDataGrid numeric filter cannot be cleared 2026-01-19 10:42:35 +02:00
Vladimir Enchev
4d4e882dcc Upload API example fixed 2026-01-19 09:39:50 +02:00
Vladimir Enchev
af8c7179d1 Pivot Grid Sorting: SortOrder="SortOrder.Descending" can break sort controls
Fix #2425
2026-01-19 09:31:31 +02:00
Vladimir Enchev
c743054184 RadzenAIChat sometimes first character not detected in chat input field
Fix #2424
2026-01-19 09:17:31 +02:00
Vladimir Enchev
935d850296 Version updated 2026-01-16 11:46:46 +02:00
Vladimir Enchev
ad6b6886f1 ToFilterString() and ToODataString() fixed for non string values 2026-01-16 09:33:21 +02:00
Maxim Becker
d5d08c89de Fix filtering in RadzenAutoComplete if Data is simple list of strings (#2422) 2026-01-16 09:22:46 +02:00
Vladimir Enchev
5a246f732e Version updated 2026-01-15 18:28:58 +02:00
Vladimir Enchev
669ad421d9 Chart 0 at beginning and end of x-axis 2026-01-15 18:28:39 +02:00
Vladimir Enchev
98e8393d14 DataGrid column UniqueID initialization fixed 2026-01-15 18:08:38 +02:00
tharreck
de8dfb7e93 Fix issue where selectedItem is not updated when value is set to null in dropdown. (#2420)
Co-authored-by: Steven Torrelle <steven.torrelle@uzgent.be>
2026-01-15 17:14:16 +02:00
Vladimir Enchev
61b32d6490 RadzenDropDown with AllowFiltering and single selection should close on ENTER 2026-01-15 09:21:08 +02:00
Vladimir Enchev
5057fb1cc8 PivotDataGrid row/col groups should respect sorting
Fix #2413
2026-01-14 16:07:14 +02:00
Vladimir Enchev
ed49dfc2fa DatePicke invalid cast with nullable DateTimeOffset 2026-01-14 13:52:29 +02:00
Vladimir Enchev
839018795c version updated 2026-01-13 14:28:29 +02:00
yordanov
c541f9d3e5 Style borders demos 2026-01-13 14:11:02 +02:00
yordanov
5e5957e4c4 Add video to borders' demos 2026-01-13 14:05:46 +02:00
yordanov
84a759267e Style Barcode demo 2026-01-13 13:48:58 +02:00
Vladimir Enchev
eb1e6fab0d Barcode added (#2416)
demo improved

various fixes

more fixes
2026-01-13 10:44:09 +02:00
Vladimir Enchev
6094953009 DataGrid drag to group or reorder restricted over grid only 2026-01-13 10:41:01 +02:00
Vladimir Enchev
b845c73ad6 DataGrid cannot group by column in some cases 2026-01-13 10:00:58 +02:00
Vladimir Enchev
2788606105 Version updated 2026-01-12 17:09:07 +02:00
Vladimir Enchev
aebf267320 Scheduler Planner/Year view: appointments missing when start month is January
Fix #2415
2026-01-12 17:08:37 +02:00
Vladimir Enchev
33896ea8db RadzenChat user is typing option added
Fix #2376
2026-01-12 10:51:11 +02:00
Vladimir Enchev
cb8699f315 RadzenTabs aria-selected accessibility issue 2026-01-12 09:50:09 +02:00
Vladimir Enchev
2a43db6560 RadzenChat always eats first key press after sending first message 2026-01-12 09:37:35 +02:00
Pavlo Iatsiuk
0ad1200870 #2406 - Allow to define Http method for Upload component (#2407)
* #2406 - Allow to define Http method for Upload component

* #2406 - Allow to define Http method for Upload component and stream raw file data

* #2406 - Allow to define Http method for Upload component
Example how to stream file

---------

Co-authored-by: Pavlo Iatsiuk <pavlo.iatsiuk@nems.eco>
2026-01-12 09:22:15 +02:00
yordanov
2debdbfd38 Update copyright year 2026-01-11 13:20:15 +02:00
tharreck
07c96bd4bd fix strange oninput behavior on textbox and textarea (server rendering) (#2410)
* use bind:get and bind:set to fix  strange behavior on input while using server rendering

* only update the Immediate textarea and textbox

---------

Co-authored-by: Steven Torrelle <steven.torrelle@uzgent.be>
2026-01-09 14:29:03 +02:00
Vladimir Enchev
704c6abe7c Version updated 2026-01-09 08:09:02 +02:00
Vladimir Enchev
e0e0e608e2 DataGrid ToFilterString() fixed
Fix #2411
2026-01-09 08:07:17 +02:00
Vladimir Enchev
40e15a0720 Symbols package fixed 2026-01-08 10:48:34 +02:00
Vladimir Enchev
d6235fd147 Version updated 2026-01-08 10:24:26 +02:00
Vladimir Enchev
c5a3a911e0 DataGrid filtering by IsNull/IsNotNull fixed 2026-01-08 10:08:54 +02:00
Vladimir Enchev
4e7a037bc5 RadzenPickList selection by ValueProperty fixed 2026-01-08 10:08:29 +02:00
Vladimir Enchev
f1c3f46ad7 RadzenPickList ValueProperty added 2026-01-08 09:22:07 +02:00
Vladimir Enchev
6c0e39da20 DataGrid filter operators should check if the property is nullable 2026-01-07 13:48:59 +02:00
Vladimir Enchev
2a9d638acf null item selection restored 2026-01-07 13:23:04 +02:00
Vladimir Enchev
426e1ba8e8 DropDownBase/ListBox/DataGridHeaderCell selection of null item fixed 2026-01-07 13:03:07 +02:00
Vladimir Enchev
1bb36ef207 demo fixed 2026-01-07 10:06:45 +02:00
Vladimir Enchev
67cde0fd59 RadzenDropDownDataGrid paging fixed 2026-01-07 09:50:50 +02:00
Vladimir Enchev
0696cd20d5 Various warnings resolved and TreatWarningsAsErrors enabled (#2409) 2026-01-07 09:29:58 +02:00
Vladimir Enchev
34fce5188f DataGrid should not call Count() more than once 2026-01-07 09:11:33 +02:00
Vladimir Enchev
273ba0381f Version updated 2026-01-06 10:45:51 +02:00
Vladimir Enchev
63a05d86e8 Nested DataGrid ContextMenu not working properly 2026-01-06 10:41:52 +02:00
Vladimir Enchev
283e115d0a Upload file cannot be reselected after removed 2026-01-06 10:08:40 +02:00
Atanas Korchev
b513ebba8e Allow HEAD requests to routable URLs. 2026-01-05 11:41:22 +02:00
Vladimir Enchev
e324cea7b9 DropDown will select item on ENTER if there is only one item 2025-12-27 16:47:01 +02:00
Vladimir Enchev
c3cda91a2d DataGrid columns visibility not saved in settings when changing PageSize 2025-12-23 10:35:49 +02:00
Vladimir Enchev
bb9f611629 ClearSearchAfterSelection does not reset filtering after reopening DropDown
Fix #2405
2025-12-23 09:57:24 +02:00
Atanas Korchev
6cc8e62b15 Update the bug report issue template to include playground snippet instructions. 2025-12-22 14:46:27 +02:00
Atanas Korchev
5a44b76adb Use unique image name to produce a new deploy every time. 2025-12-22 12:45:07 +02:00
Atanas Korchev
109d6bc617 Update deploy.yml 2025-12-22 12:22:06 +02:00
Vladimir Enchev
7249ff6107 Version updated 2025-12-22 11:38:43 +02:00
Vladimir Enchev
406830fa13 Numeric fails to accept pasted input when Min is specified
Fix #2401
2025-12-22 10:25:17 +02:00
Vladimir Enchev
a6a3278443 Single selection filtered dropdown popup does not close when selecting an item with enter key
Fix #2398
2025-12-22 09:43:17 +02:00
Atanas Korchev
9fd8531529 Using ___ or *** in markdown content causes exception in RadzenMarkdown. Fixes #2402. 2025-12-22 08:29:07 +02:00
Atanas Korchev
c687976796 Update deploy.yml 2025-12-20 06:49:59 +02:00
Atanas Korchev
ae511929f7 Keep only one container version (the latest) 2025-12-20 06:30:41 +02:00
Atanas Korchev
e78e756206 Workaround for https://github.com/dotnet/aspnetcore/issues/64693. 2025-12-19 20:46:55 +02:00
Atanas Korchev
2e9e0dac1a Update deploy.yml 2025-12-19 19:44:36 +02:00
Atanas Korchev
bff97712e2 Update deploy.yml 2025-12-19 19:29:28 +02:00
Atanas Korchev
78bc25cce0 Update deploy.yml 2025-12-19 19:17:34 +02:00
Atanas Korchev
a2e829417d Update deploy.yml 2025-12-19 18:55:44 +02:00
Atanas Korchev
9902413daf Logging. 2025-12-19 18:54:05 +02:00
Atanas Korchev
06a35f4a73 Create deploy.yml 2025-12-19 18:26:49 +02:00
Atanas Korchev
e2a46157b9 Playground (#2400)
* Playground page.

* Persist the changes.

* Add copy button.

* Style Playground

* Update Playground button in CodeViewer

* Add AntiForgery support.

* Update Save alert

* Extract common code.

* Loading tweaks.

---------

Co-authored-by: yordanov <vasil@yordanov.info>
2025-12-19 17:36:32 +02:00
Atanas Korchev
c4913a94c4 Remove duplicate anchors from the chart series demo. 2025-12-19 11:29:47 +02:00
Vladimir Enchev
d6e04c3ae8 DropDownDataGrid ContextMenuDataGrid added 2025-12-18 16:04:02 +02:00
Vladimir Enchev
5b17ef5217 Version updated 2025-12-17 16:19:05 +02:00
Anspitzen
8edeedc32c Rework paste handler and add same function to new drop handler (#2394)
* Rework paste handler and add same function to new drop handler

* Remove eventlistener on destroy

* Try restore formating of Radzen.Balzor.js

* Fix missing delegate flag on create editor from restoring formating

* Handle drop and paste with same c# EventCallback OnPaste/Paste
2025-12-17 11:34:04 +02:00
Atanas Korchev
543dbc9e50 Serve uploaded images as static assets. 2025-12-17 11:18:30 +02:00
tharreck
3710f4297b Add ExpandAll/CollapseAll button in hierarchy datagrid (#2391)
* Add ExpandAll/CollapseAll button in hierarchy datagrid

* update icons of ExpandAll/CollapseAll button

* Update naming and use default values for ExpandAllTitle and CollapseAllTitle

---------

Co-authored-by: Steven Torrelle <steven.torrelle@uzgent.be>
2025-12-17 08:44:30 +02:00
Paul Ruston
91d6ba4da8 Add another condition for overlapping appointments (#2392) 2025-12-16 13:56:18 +02:00
Atanas Korchev
fbfa3c1abe Update README.md 2025-12-16 13:50:54 +02:00
Atanas Korchev
d13d7bef0c RadzenMarkdown allows unsafe protocols in attributes - javascript: etc. 2025-12-16 13:45:54 +02:00
joriverm
510f5d7190 RadzenDropdown: correctly close popup on item selection, ignoring OpenOnFocus (#2389)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-12-15 16:08:29 +02:00
Vladimir Enchev
595b98d4b7 DropDownDataGrid row not focused on select 2025-12-15 11:01:29 +02:00
Atanas Korchev
c696107aeb Build does not finish. 2025-12-11 18:18:16 +02:00
Atanas Korchev
632722cd7b Llms.txt fixes. 2025-12-11 17:54:58 +02:00
Atanas Korchev
60c264ea49 Remove UseStaticAssets to restore compression. 2025-12-11 17:10:19 +02:00
Vladimir Enchev
10ce92264d Version updated 2025-12-11 10:35:03 +02:00
Vladimir Enchev
2e5c7aa6bc Mask and Numeric Immediate property added
Fix #2384, #2386
2025-12-11 10:15:24 +02:00
yordanov
1e7e2c5b51 Add video to RadzenIcon demos 2025-12-11 10:08:29 +02:00
Atanas Korchev
46790ce3fe Html editor dropdown aria attributes. Closes #2385. (#2387)
* Add ARIA attributes to HtmlEditorDropdown.

* Fix spacing and font size of rz-html-editor-dropdown

---------

Co-authored-by: yordanov <vasil@yordanov.info>
2025-12-10 11:31:33 +02:00
yordanov
7bd891f8f0 Add video to RadzenText demos 2025-12-10 09:50:27 +02:00
Vladimir Enchev
1ae16acbac Fixed possible DropDown NullReferenceException
Fix #2380
2025-12-10 08:20:33 +02:00
Vladimir Enchev
2281fb7f61 DataGrid expand/collapse of items using right/left keys added 2025-12-10 08:12:10 +02:00
Vladimir Enchev
89e8db6c6e Version updated 2025-12-09 10:24:30 +02:00
Vladimir Enchev
11300692e5 DropDownBase search by key enabled when bound without TextProperty 2025-12-09 10:21:38 +02:00
Barry
a31161c6d8 Fix: TreeItem Template click does not toggle Checkbox (#2374)
* add click handler for treeviewitem when Template used with checkboxes

* change approach to expose a toggle option

* remove unused method and add select to toggle checkbox selection

* rename to toggle checked

---------

Co-authored-by: Barry Wright <ace90210ace@msn.com>
2025-12-09 09:02:38 +02:00
xsainteer
edd85f8ec0 added UseDisplayName parameter to DataGridColumn component (#2378) 2025-12-08 08:48:18 +02:00
Vladimir Enchev
0d420604dd code fixed 2025-12-05 12:03:43 +02:00
Vladimir Enchev
777f5666e2 Docker improved to avoid running demos browse problems 2025-12-05 11:59:42 +02:00
Vladimir Enchev
e7bded641f Version updated 2025-12-05 11:16:11 +02:00
Vladimir Enchev
6f0dfbe038 Fixed cannot use ?? unparenthesized within || and && expressions 2025-12-05 11:15:57 +02:00
Vladimir Enchev
0e92eeb50e UseStaticFiles() added to enable llms.txt visibility 2025-12-05 11:06:56 +02:00
Vladimir Enchev
50b6cb879a Version updated 2025-12-05 10:14:58 +02:00
Vladimir Enchev
739ebde6a8 Llmtxt generation added (#2379)
* RadzenBlazorDemos.Tools added

* GenerateLlmsTxt Condition update to Release

* GenerateLlmsTxt updated again

* code fixed

* content generation cleaned

* razor/html tags removed from example descriptions and the code is now inside codeblock

* more fixes
2025-12-05 10:14:15 +02:00
Vladimir Enchev
24ef74f16d Upload remove file throws cannot read property 'find' exception 2025-12-05 10:10:13 +02:00
Vladimir Enchev
9e0a57ca5b RadzenDropDownDataGrid ColumnResized, ColumnReordering and ColumnReordered added 2025-12-04 11:00:07 +02:00
Vladimir Enchev
feaebb6f0f Version updated 2025-12-04 10:52:15 +02:00
yordanov
9a45759414 Add rz-default-scrollbars CSS class to base themes 2025-12-04 10:49:10 +02:00
yordanov
14c5a0447b Remove Black Friday promo banner 2025-12-04 10:37:47 +02:00
yordanov
7793e03d82 Update premium themes 2025-12-03 10:27:13 +02:00
Martin Hans
f422573fcc Add resizable option to side dialog. (#2331)
* Add resizable option to side dialog.

* Fix resize bar CSS class typo and make sideDialogResizeHandleJsModule nullable

* Add tests for resizable side dialog.

* Rename element references.

* Enable nullable refernce types for RadzenDialg.razor.cs

* Resolve requested change

Move code back to RadzenDialog.razor.

* Use sideDialog.classList to gather position rather than data-dir attribute.

* Rework initSideDialogResize function.

- Take position from options.
- Take width and height from sideDialogs clientWidth and clientHeight.
- Take minWidth and minHeight from options.
- Remove superflous pointer capturing.
- Remove superflous 'dragging' class.

* Replace title and aria label string constants by properties.

* Treat resizableMinWidth and resizableMinHeight as present.

* Reformat

* Use options.resizableMinWidth/Height only

* No need to set min-width neither min-height on side dialog.

* Rename ResizableMinWidth => MinWidth, ResizableMinHeight => MinHeight.

* Rename initSideDialogResize => createSideDialogResizer.

* Add dialog resize bar css variables

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yordanov <vasil@yordanov.info>
2025-12-03 10:22:33 +02:00
tharreck
516109ecd3 Correctly select RadzenTocItem based on scroll direction in RadzenToc (#2370)
* Select toc item based on scroll direction in TOC

* Select correct RadzenTocItem when reloading a page with anchor
cleanup code

* add debounce back into scrollhandler

---------

Co-authored-by: Torrelle Steven <steven.torrelle@uzgent.be>
2025-12-02 15:45:10 +02:00
Mason Voxland
31d32b16a5 Clicking SelectAllText in CheckBoxList will do change (#2377) 2025-12-02 11:13:16 +02:00
yordanov
435c1bd6e2 Update Black Friday promo 2025-12-01 10:56:10 +02:00
Vladimir Enchev
6d5ebf3f58 Version updated 2025-12-01 10:55:13 +02:00
Vladimir Enchev
b97036b447 Support large strings added to QRCode
Fix #2373
2025-12-01 10:51:55 +02:00
Vladimir Enchev
d7ef3cb896 Upload file not removed properly when on file delete from UI 2025-11-27 14:10:49 +02:00
Robyn Choi
2218eefa67 RadzenPivotDataGrid AddPivotAggregate exposed (#2371) 2025-11-27 08:14:10 +02:00
Vladimir Enchev
666d33d767 Version updated 2025-11-26 14:00:45 +02:00
Vladimir Enchev
3f4f83d354 PivotDataGrid filter icon popup exception after adding more fields 2025-11-26 08:35:43 +02:00
Atanas Korchev
3dea547559 Enable CSS minification for the embedded themes. 2025-11-25 18:33:46 +02:00
rklfss
0e9c6acb84 Allow Specifying the interactive SortOrder Sequence on Data Grids (#2366) 2025-11-25 15:46:23 +02:00
Vladimir Enchev
1b6881673e AI assistant messages are sent as ‘user’ instead of ‘assistant’ role. ChatMessage Role property added
Fix #2365
2025-11-25 10:17:15 +02:00
yordanov
f069b33b60 Add videos to UI fundamentals demos 2025-11-25 10:03:54 +02:00
Vladimir Enchev
c0a86e31da Version updated 2025-11-24 10:05:17 +02:00
Vladimir Enchev
7b95778efe PivotDataGrid ColumnsCollection, RowsCollection and AggregatesCollection added 2025-11-24 09:45:02 +02:00
Vladimir Enchev
ef8a102d0a DataGrid OData column Type Guid exception on set filter
Fix #2363
2025-11-24 09:18:55 +02:00
Vladimir Enchev
95448de3ed DataGrid column CloseFilter() does not work when FilterProperty has a value 2025-11-21 10:14:08 +02:00
Vladimir Enchev
0a574762c7 RadzenTocItem inherits RadzenComponentWithChildren 2025-11-18 14:10:07 +02:00
joriverm
59b1440990 RadzenTocItem: take in attributes to pass on to the list item element (#2360)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-11-18 13:56:36 +02:00
582 changed files with 14868 additions and 5311 deletions

View File

@@ -11,10 +11,10 @@ assignees: ''
IMPORTANT: Read this first!!!
1. If you own a Radzen Blazor subscription you can report your issue or ask us a question via email at info@radzen.com. Radzen staff will reply within 24 hours (Pro) or 16 hours (Team)
1. If you own a Radzen Blazor Pro or Team subscription you can also report your issue or ask us a question via email at info@radzen.com. Radzen staff will reply within 24 hours (Pro) or 16 hours (Team)
2. The Radzen staff guarantees a response to issues in this repo only to paid subscribers.
3. If you have a HOW TO question start a new forum thread in the Radzen Community forum: https://forum.radzen.com. Radzen staff will close issues that are HOWTO questions.
4. Please adhere to the issue template. Specify all the steps required to reproduce the issue or link a project which reproduces it easily (without requiring extra steps such as restoring a database).
4. Please adhere to the issue template. Specify all the steps required to reproduce the issue.
-->
**Describe the bug**
@@ -27,7 +27,12 @@ Steps to reproduce the behavior:
3. Scroll down to '....'
4. See error
Alternatively link your repo with a sample project that can be run.
Alternatively make a new [playground](https://blazor.radzen.com/playground) snippet and paste its URL.
1. Go to any live demo at https://blazor.radzen.com
2. Click the **Edit Source** tab.
3. Then click **Open in Playground**.
4. Reproduce the problem and save the snippet.
5. Copy the snippet URL and provide it in the issue description.
**Expected behavior**
A clear and concise description of what you expected to happen.

66
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Deploy to blazor.radzen.com
on:
workflow_dispatch:
concurrency:
group: blazor-radzen-prod
cancel-in-progress: true
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Compute image tag (timestamp)
id: meta
run: |
TAG=$(date -u +"%Y%m%d%H%M%S")
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}"
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.image }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Push to dokku
uses: dokku/github-action@master
with:
git_remote_url: ${{ secrets.DOKKU_REPO }}
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }}
deploy_docker_image: ${{ steps.meta.outputs.image }}
- name: Prune GHCR versions (keep 1)
uses: actions/delete-package-versions@v5
with:
package-name: radzen-blazor
package-type: container
min-versions-to-keep: 1
delete-only-pre-release-versions: false

85
Directory.Build.props Normal file
View File

@@ -0,0 +1,85 @@
<Project>
<!--
Common build properties for all projects in the Radzen.Blazor solution.
To use this file:
1. Rename to Directory.Build.props (remove .sample extension)
2. Adjust settings based on your needs
3. Review the analyzer settings in .editorconfig
This file will be automatically imported by all projects in subdirectories.
-->
<PropertyGroup Label="Language Configuration">
<!-- Use latest C# language features -->
<LangVersion>latest</LangVersion>
<!-- Do NOT enable implicit usings - explicit imports preferred for library code -->
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>CA2007</NoWarn>
</PropertyGroup>
<PropertyGroup Label="Code Analysis Configuration">
<!-- Enable .NET code analyzers -->
<AnalysisLevel>latest</AnalysisLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<!-- Run analyzers during build and in IDE -->
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
<!-- Don't enforce code style in build (yet) - just show warnings -->
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<!-- Don't treat warnings as errors (yet) - too many to fix immediately -->
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<!-- Report all analyzer diagnostics -->
<AnalysisMode>All</AnalysisMode>
</PropertyGroup>
<PropertyGroup Label="Build Quality">
<!-- Enable deterministic builds for reproducibility -->
<Deterministic>true</Deterministic>
<!-- Enable deterministic builds in CI/CD -->
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<!-- Embed source files for better debugging -->
<EmbedAllSources>true</EmbedAllSources>
<!--
IMPORTANT:
- NuGet symbol packages (.snupkg) require portable PDB files.
- If DebugType=embedded, there are no standalone PDBs, so the .snupkg ends up effectively empty.
Use portable PDBs when symbols are enabled; otherwise use embedded for local debugging convenience.
-->
<!--
NOTE: Directory.Build.props is imported before project files, so properties like IncludeSymbols
set in a .csproj may not be available yet for Conditions here.
IsPacking *is* set by `dotnet pack`, so use that to switch DebugType for symbol packages.
-->
<DebugType Condition="'$(IsPacking)' == 'true'">portable</DebugType>
<DebugType Condition="'$(IsPacking)' != 'true'">embedded</DebugType>
</PropertyGroup>
<PropertyGroup Label="Demos and Tests Project Configuration" Condition="$(MSBuildProjectName.Contains('Demos')) OR $(MSBuildProjectName.Contains('Tests'))">
<!-- Demo projects and Tests should not be packable -->
<IsPackable>false</IsPackable>
<!-- DISABLE ALL ANALYZERS FOR DEMO PROJECTS AND TESTS -->
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
<RunAnalyzersDuringLiveAnalysis>false</RunAnalyzersDuringLiveAnalysis>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
</PropertyGroup>
<PropertyGroup Label="Performance">
<!-- Optimize startup time -->
<TieredCompilation>true</TieredCompilation>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
</PropertyGroup>
</Project>

View File

@@ -1,23 +1,48 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0
# =============================
# BUILD STAGE
# =============================
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY Radzen.Blazor /app/Radzen.Blazor
COPY Radzen.DocFX /app/Radzen.DocFX
COPY RadzenBlazorDemos /app/RadzenBlazorDemos
COPY RadzenBlazorDemos.Host /app/RadzenBlazorDemos.Host
# Copy project files first for better caching
COPY Radzen.Blazor/*.csproj Radzen.Blazor/
COPY RadzenBlazorDemos/*.csproj RadzenBlazorDemos/
COPY RadzenBlazorDemos.Host/*.csproj RadzenBlazorDemos.Host/
WORKDIR /app
# Radzen.DocFX usually has no csproj → copy full folder
COPY Radzen.DocFX/ Radzen.DocFX/
# Restore dependencies
RUN dotnet restore RadzenBlazorDemos.Host/RadzenBlazorDemos.Host.csproj
# Copy full source after restore layer
COPY . .
# Install docfx (build stage only)
RUN dotnet tool install -g docfx
ENV PATH="$PATH:/root/.dotnet/tools"
RUN wget https://dot.net/v1/dotnet-install.sh \
&& bash dotnet-install.sh --channel 8.0 --runtime dotnet --install-dir /usr/share/dotnet
# Build shared project (keep net8.0 if required)
RUN dotnet build -c Release Radzen.Blazor/Radzen.Blazor.csproj -f net8.0
# Generate documentation
RUN docfx Radzen.DocFX/docfx.json
WORKDIR /app/RadzenBlazorDemos.Host
RUN dotnet publish -c Release -o out
# Publish the Blazor host app
WORKDIR /src/RadzenBlazorDemos.Host
RUN dotnet publish -c Release -o /app/out
ENV ASPNETCORE_URLS=http://*:5000
WORKDIR /app/RadzenBlazorDemos.Host/out
# =============================
# RUNTIME STAGE
# =============================
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
# Copy only published output
COPY --from=build /app/out ./
# Set runtime URL
ENV ASPNETCORE_URLS=http://+:5000
ENTRYPOINT ["dotnet", "RadzenBlazorDemos.Host.dll"]

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2018-2025 Radzen Ltd
Copyright (c) 2018-2026 Radzen Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -54,4 +54,4 @@ Check the [getting started](https://blazor.radzen.com/get-started) instructions
## Run demos locally
Use Radzen.Server.sln to open and run demos as Blazor server application or Radzen.WebAssembly.sln to open and run demos as Blazor WebAssembly application. Radzen.sln has reference to all projects including tests.
Use **Radzen.Server.sln** to open and run demos as Blazor server application or **Radzen.WebAssembly.sln** to open and run demos as Blazor WebAssembly application. The demos require the .NET 10 SDK and should preferably be opened in VS2026.

View File

@@ -1,6 +1,9 @@
using Bunit;
using System.Collections;
using Bunit;
using Xunit;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Radzen.Blazor.Tests
{
@@ -135,5 +138,30 @@ namespace Radzen.Blazor.Tests
Assert.Equal("additional-name", AutoCompleteType.MiddleName.GetAutoCompleteValue());
Assert.Equal("family-name", AutoCompleteType.LastName.GetAutoCompleteValue());
}
[Fact]
public void AutoComplete_Filters_StringList()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Apple", "Banana", "Cherry" };
var component = ctx.RenderComponent<AutoCompleteWithAccessibleView>(parameters =>
{
parameters
.Add(p => p.Data, data)
.Add(p => p.SearchText, "Ban")
.Add(p => p.OpenOnFocus, true);
});
Assert.Contains("Banana", component.Instance.CurrentView.OfType<string>());
Assert.DoesNotContain("Apple", component.Instance.CurrentView.OfType<string>());
Assert.DoesNotContain("Cherry", component.Instance.CurrentView.OfType<string>());
}
private sealed class AutoCompleteWithAccessibleView : RadzenAutoComplete
{
public IEnumerable CurrentView => View;
}
}
}

View File

@@ -1728,6 +1728,7 @@ namespace Radzen.Blazor.Tests
{
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
builder.AddAttribute(1, "Property", "Name");
builder.AddAttribute(2, "Type", typeof(string));
builder.CloseComponent();
});
parameterBuilder.Add<bool>(p => p.AllowFiltering, true);
@@ -1751,6 +1752,7 @@ namespace Radzen.Blazor.Tests
{
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
builder.AddAttribute(1, "Property", "Name");
builder.AddAttribute(1, "Type", typeof(string));
builder.CloseComponent();
});
parameterBuilder.Add<bool>(p => p.AllowFiltering, true);
@@ -2804,6 +2806,12 @@ namespace Radzen.Blazor.Tests
builder.AddAttribute(1, "Property", "Id");
builder.AddAttribute(2, "Title", "Id");
builder.CloseComponent();
builder.OpenComponent(3, typeof(RadzenDataGridColumn<dynamic>));
builder.AddAttribute(4, "Property", "Tags");
builder.AddAttribute(5, "Title", "Tags");
builder.AddAttribute(6, "Type", typeof(object[]));
builder.CloseComponent();
});
parameterBuilder.Add<bool>(p => p.AllowFiltering, true);
parameterBuilder.Add<FilterMode>(p => p.FilterMode, FilterMode.SimpleWithMenu);
@@ -2834,6 +2842,12 @@ namespace Radzen.Blazor.Tests
builder.AddAttribute(1, "Property", "Id");
builder.AddAttribute(2, "Title", "Id");
builder.CloseComponent();
builder.OpenComponent(3, typeof(RadzenDataGridColumn<dynamic>));
builder.AddAttribute(4, "Property", "Tags");
builder.AddAttribute(5, "Title", "Tags");
builder.AddAttribute(6, "Type", typeof(object[]));
builder.CloseComponent();
});
parameterBuilder.Add<bool>(p => p.AllowFiltering, true);
parameterBuilder.Add<FilterMode>(p => p.FilterMode, FilterMode.SimpleWithMenu);

View File

@@ -1,11 +1,15 @@
using System;
using Bunit;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class DialogServiceTests
public class DialogServiceTests : ComponentBase
{
public class OpenDialogTests
{
@@ -124,13 +128,81 @@ namespace Radzen.Blazor.Tests
var openTask = dialogService.OpenAsync("Dynamic Open", typeof(RadzenButton), []);
dialogService.Close();
await openTask;
// Assert
Assert.Equal("Dynamic Open", resultingTitle);
Assert.Equal(typeof(RadzenButton), resultingType);
}
}
public class OpenSideDialogTests
{
[Fact(DisplayName = "SideDialogOptions resizable option is retained after OpenSideDialog call")]
public void SideDialogOptions_Resizable_AreRetained_AfterOpenSideDialogCall()
{
// Arrange
var options = new SideDialogOptions { Resizable = true };
SideDialogOptions resultingOptions = null;
var dialogService = new DialogService(null, null);
dialogService.OnSideOpen += (_, _, sideOptions) => resultingOptions = sideOptions;
// Act
dialogService.OpenSide<DialogServiceTests>("Test", [], options);
// Assert
Assert.NotNull(resultingOptions);
Assert.Same(options, resultingOptions);
Assert.True(resultingOptions.Resizable);
}
[Fact(DisplayName = "Side dialog shows resize bar when Resizable is true")]
public void SideDialog_Resizable_ShowsResizeBar()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
// Render the dialog host
var cut = ctx.RenderComponent<RadzenDialog>();
// Open a side dialog with Resizable=true
var dialogService = ctx.Services.GetRequiredService<DialogService>();
cut.InvokeAsync(() => dialogService.OpenSide("Test", typeof(RadzenButton),
new Dictionary<string, object>(), new SideDialogOptions { Resizable = true }));
// Assert: the resize bar element is present
cut.WaitForAssertion(() =>
{
var markup = cut.Markup;
Assert.Contains("rz-dialog-resize-bar", markup);
// Optionally ensure the inner handle exists too
Assert.Contains("rz-resize", markup);
});
}
[Fact(DisplayName = "Side dialog hides resize bar when Resizable is false")]
public void SideDialog_NonResizable_HidesResizeBar()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
// Render the dialog host
var cut = ctx.RenderComponent<RadzenDialog>();
// Open a side dialog with Resizable=false
var dialogService = ctx.Services.GetRequiredService<DialogService>();
cut.InvokeAsync(() => dialogService.OpenSide("Test", typeof(RadzenButton),
new Dictionary<string, object>(), new SideDialogOptions()));
// Assert: the resize bar element is not present
cut.WaitForAssertion(() =>
{
var markup = cut.Markup;
Assert.DoesNotContain("rz-dialog-resize-bar", markup);
});
}
}
public class ConfirmTests
{
[Fact(DisplayName = "ConfirmOptions is null and default values are set correctly")]

View File

@@ -22,7 +22,7 @@ namespace Radzen.Blazor.Tests
public Guid Id { get; set; }
public TimeOnly StartTime { get; set; }
public DateOnly BirthDate { get; set; }
public List<int> Scores { get; set; }
public IEnumerable<int> Scores { get; set; }
public List<string> Tags { get; set; }
public List<TestEntity> Children { get; set; }
public Address Address { get; set; }

View File

@@ -141,4 +141,28 @@ foo <!---> foo -->
{
Assert.Equal(expected, ToXml(markdown));
}
[Theory]
[InlineData("***foo bar***", @"<document>
<paragraph>
<emph>
<strong>
<text>foo bar</text>
</strong>
</emph>
</paragraph>
</document>")]
[InlineData("___foo bar___", @"<document>
<paragraph>
<emph>
<strong>
<text>foo bar</text>
</strong>
</emph>
</paragraph>
</document>")]
public void Parse_BoldAndItalic_ShouldNotThrowException(string markdown, string expected)
{
Assert.Equal(expected, ToXml(markdown));
}
}

View File

@@ -2,6 +2,7 @@ using Bunit;
using Bunit.JSInterop;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace Radzen.Blazor.Tests
@@ -54,7 +55,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_Renders_Summary() {
public async Task RadzenPager_Renders_Summary() {
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
@@ -64,7 +65,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<int>(p => p.Count, 100);
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(2);
await component.InvokeAsync(() => component.Instance.GoToPage(2));
component.Render();
Assert.Contains(@$"rz-pager-summary", component.Markup);
@@ -111,7 +112,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
public async Task RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
@@ -123,7 +124,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(0);
await component.InvokeAsync(() => component.Instance.GoToPage(0));
component.Render();
var firstPageButton = component.Find("a.rz-pager-first");
@@ -134,7 +135,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
public async Task RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
@@ -146,7 +147,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(9);
await component.InvokeAsync(() => component.Instance.GoToPage(9));
component.Render();
var lastPageButton = component.Find("a.rz-pager-last");

View File

@@ -1,6 +1,7 @@
using Bunit;
using Xunit;
using System.Collections.Generic;
using System.Linq;
namespace Radzen.Blazor.Tests
{
@@ -156,6 +157,64 @@ namespace Radzen.Blazor.Tests
Assert.Contains("disabled", component.Markup);
}
[Fact]
public void PickList_GetSelectedSources_Respects_ValueProperty_Single()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Item>
{
new Item { Id = 1, Name = "A" },
new Item { Id = 2, Name = "B" }
};
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.Source, data);
parameters.Add(p => p.TextProperty, "Name");
parameters.Add(p => p.ValueProperty, "Id");
parameters.Add(p => p.Multiple, false);
});
// Simulate ListBox selection when ValueProperty is set: selectedSourceItems becomes the value (Id)
var field = typeof(RadzenPickList<Item>).GetField("selectedSourceItems", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
field.SetValue(component.Instance, 2);
var selected = component.Instance.GetSelectedSources();
Assert.Single(selected);
Assert.Equal(2, selected.First().Id);
}
[Fact]
public void PickList_GetSelectedSources_Respects_ValueProperty_Multiple()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Item>
{
new Item { Id = 1, Name = "A" },
new Item { Id = 2, Name = "B" },
new Item { Id = 3, Name = "C" }
};
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.Source, data);
parameters.Add(p => p.TextProperty, "Name");
parameters.Add(p => p.ValueProperty, "Id");
parameters.Add(p => p.Multiple, true);
});
// Simulate ListBox selection when ValueProperty is set: selectedSourceItems becomes IEnumerable of values (Ids)
var field = typeof(RadzenPickList<Item>).GetField("selectedSourceItems", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
field.SetValue(component.Instance, new[] { 1, 3 });
var selected = component.Instance.GetSelectedSources().Select(i => i.Id).OrderBy(i => i).ToArray();
Assert.Equal(new[] { 1, 3 }, selected);
}
}
}

View File

@@ -605,7 +605,7 @@ namespace Radzen.Blazor.Tests
Assert.True(grid.AllowFieldsPicking);
}
private static IRenderedComponent<RadzenPivotDataGrid<SalesData>> RenderPivotDataGrid(TestContext ctx, Action<ComponentParameterCollectionBuilder<RadzenPivotDataGrid<SalesData>>>? configure = null)
private static IRenderedComponent<RadzenPivotDataGrid<SalesData>> RenderPivotDataGrid(TestContext ctx, Action<ComponentParameterCollectionBuilder<RadzenPivotDataGrid<SalesData>>> configure = null)
{
return ctx.RenderComponent<RadzenPivotDataGrid<SalesData>>(parameters =>
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Xunit;
@@ -114,6 +114,11 @@ namespace Radzen.Blazor.Tests
public List<string> Values { get; set; }
}
public class Order
{
public DateTime? OrderDate { get; set; }
}
[Fact]
public void GetProperty_Should_Resolve_DescriptionProperty()
{
@@ -137,6 +142,14 @@ namespace Radzen.Blazor.Tests
Assert.NotNull(idProperty);
}
[Fact]
public void GetPropertyType_Resolves_NullableDateTime_Date()
{
var propertyType = PropertyAccess.GetPropertyType(typeof(Order), "OrderDate.Date");
Assert.Equal(typeof(DateTime), propertyType);
}
interface ISimpleInterface : ISimpleNestedInterface
{
string Description { get; set; }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
using Radzen;
using System.Linq.Expressions;
namespace Radzen.Blazor.Tests
{
@@ -24,6 +25,11 @@ namespace Radzen.Blazor.Tests
public int Priority { get; set; }
}
private class Order
{
public DateTime? OrderDate { get; set; }
}
private List<TestItem> GetTestData()
{
return new List<TestItem>
@@ -36,6 +42,24 @@ namespace Radzen.Blazor.Tests
};
}
[Fact]
public void GetNestedPropertyExpression_Handles_NullableDateTime_Date()
{
var parameter = Expression.Parameter(typeof(Order), "x");
var expression = QueryableExtension.GetNestedPropertyExpression(parameter, "OrderDate.Date");
var getter = Expression.Lambda<Func<Order, DateTime>>(expression, parameter).Compile();
var order = new Order { OrderDate = new DateTime(2024, 2, 3, 14, 30, 0) };
var result = getter(order);
Assert.Equal(order.OrderDate.Value.Date, result);
var nullOrder = new Order { OrderDate = null };
var nullResult = getter(nullOrder);
Assert.Equal(default(DateTime), nullResult);
}
// OrderBy tests
[Fact]
public void OrderBy_SortsAscending_ByDefault()

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>disable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

View File

@@ -0,0 +1,63 @@
using Bunit;
using Microsoft.JSInterop;
using Microsoft.Extensions.DependencyInjection;
using Radzen.Blazor;
using Radzen.Blazor.Rendering;
using Radzen;
using System;
using System.Collections.Generic;
using System.Globalization;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class SchedulerYearRangeTests
{
class Appointment
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public string Text { get; set; } = "";
}
[Fact]
public void YearView_StartMonthJanuary_IncludesLastDaysOfYear_WhenYearStartsOnFirstDayOfWeek()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
ctx.JSInterop.Setup<Rect>("Radzen.createScheduler", _ => true)
.SetResult(new Rect { Left = 0, Top = 0, Width = 200, Height = 200 });
// Make the first day of week Monday and use a year where Jan 1 is Monday (2024-01-01).
var culture = (CultureInfo)CultureInfo.InvariantCulture.Clone();
culture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
var appointments = new List<Appointment>
{
new() { Start = new DateTime(2024, 12, 31), End = new DateTime(2025, 1, 1), Text = "Year end" }
};
var cut = ctx.RenderComponent<RadzenScheduler<Appointment>>(p =>
{
p.Add(x => x.Culture, culture);
p.Add(x => x.Date, new DateTime(2024, 6, 1));
p.Add(x => x.Data, appointments);
p.Add(x => x.StartProperty, nameof(Appointment.Start));
p.Add(x => x.EndProperty, nameof(Appointment.End));
p.Add(x => x.TextProperty, nameof(Appointment.Text));
p.AddChildContent<RadzenYearView>(v => v.Add(x => x.StartMonth, Radzen.Month.January));
});
var view = Assert.IsType<RadzenYearView>(cut.Instance.SelectedView);
// View should start on 2023-12-25 (one extra week above since 2024-01-01 is Monday).
Assert.Equal(new DateTime(2023, 12, 25), view.StartDate.Date);
// View end must include 2024-12-31 (it should extend to end-of-week containing the real year end).
Assert.True(view.EndDate.Date >= new DateTime(2024, 12, 31), $"EndDate was {view.EndDate:yyyy-MM-dd}");
}
}
}

View File

@@ -0,0 +1,27 @@
using Bunit;
using System.Collections.Generic;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class TocTests
{
[Fact]
public void TocItem_Renders_With_Attributes()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTocItem>(parameters =>
{
parameters.Add(p => p.Attributes, new Dictionary<string, object>
{
{ "data-enhance-nav", "false" },
{ "aria-label", "Table of Contents Item" }
});
});
Assert.Contains("data-enhance-nav=\"false\"", component.Markup);
Assert.Contains("aria-label=\"Table of Contents Item\"", component.Markup);
}
}
}

View File

@@ -6,6 +6,48 @@ root = true
#### Core EditorConfig Options ####
dotnet_diagnostic.CA1002.severity = none
dotnet_diagnostic.CA1003.severity = none
dotnet_diagnostic.CA1024.severity = none
dotnet_diagnostic.CA1030.severity = none
dotnet_diagnostic.CA1031.severity = none
dotnet_diagnostic.CA1033.severity = none
dotnet_diagnostic.CA1044.severity = none
dotnet_diagnostic.CA1050.severity = none
dotnet_diagnostic.CA1051.severity = none
dotnet_diagnostic.CA1052.severity = none
dotnet_diagnostic.CA1054.severity = none
dotnet_diagnostic.CA1055.severity = none
dotnet_diagnostic.CA1056.severity = none
dotnet_diagnostic.CA1063.severity = none
dotnet_diagnostic.CA1068.severity = none
dotnet_diagnostic.CA1308.severity = none
dotnet_diagnostic.CA1708.severity = none
dotnet_diagnostic.CA1711.severity = none
dotnet_diagnostic.CA1716.severity = none
dotnet_diagnostic.CA1720.severity = none
dotnet_diagnostic.CA1721.severity = none
dotnet_diagnostic.CA1724.severity = none
dotnet_diagnostic.CA1725.severity = none
dotnet_diagnostic.CA1802.severity = none
dotnet_diagnostic.CA1814.severity = none
dotnet_diagnostic.CA1815.severity = none
dotnet_diagnostic.CA1816.severity = none
dotnet_diagnostic.CA1819.severity = none
dotnet_diagnostic.CA1822.severity = none
dotnet_diagnostic.CA1827.severity = none
dotnet_diagnostic.CA1834.severity = none
dotnet_diagnostic.CA1845.severity = none
dotnet_diagnostic.CA1849.severity = none
dotnet_diagnostic.CA1851.severity = none
dotnet_diagnostic.CA1859.severity = none
dotnet_diagnostic.CA1863.severity = none
dotnet_diagnostic.CA1869.severity = none
dotnet_diagnostic.CA2007.severity = none
dotnet_diagnostic.CA2012.severity = none
dotnet_diagnostic.CA2211.severity = none
dotnet_diagnostic.CA2227.severity = none
# Indentation and spacing
indent_size = 4
indent_style = space

View File

@@ -21,13 +21,16 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
private readonly Dictionary<string, ConversationSession> sessions = new();
private readonly object sessionsLock = new();
// Add this static field to cache the JsonSerializerOptions instance
private static readonly JsonSerializerOptions CachedJsonSerializerOptions = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
/// <summary>
/// Gets the configuration options for the chat streaming service.
/// </summary>
public AIChatServiceOptions Options => options.Value;
/// <inheritdoc />
public async IAsyncEnumerable<string> GetCompletionsAsync(string userInput, string sessionId = null, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default, string model = null, string systemPrompt = null, double? temperature = null, int? maxTokens = null, string endpoint = null, string proxy = null, string apiKey = null, string apiKeyHeader = null)
public async IAsyncEnumerable<string> GetCompletionsAsync(string userInput, string? sessionId = null, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default, string? model = null, string? systemPrompt = null, double? temperature = null, int? maxTokens = null, string? endpoint = null, string? proxy = null, string? apiKey = null, string? apiKeyHeader = null)
{
if (string.IsNullOrWhiteSpace(userInput))
{
@@ -57,13 +60,18 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
stream = true
};
var request = new HttpRequestMessage(HttpMethod.Post, url)
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(JsonSerializer.Serialize(payload, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }), Encoding.UTF8, "application/json")
Content = new StringContent(JsonSerializer.Serialize(payload, CachedJsonSerializerOptions), Encoding.UTF8, "application/json")
};
if (!string.IsNullOrEmpty(effectiveApiKey))
{
if (string.IsNullOrWhiteSpace(effectiveApiKeyHeader))
{
throw new InvalidOperationException("API key header must be specified when an API key is provided.");
}
if (string.Equals(effectiveApiKeyHeader, "Authorization", StringComparison.OrdinalIgnoreCase))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", effectiveApiKey);
@@ -75,22 +83,22 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
}
var httpClient = serviceProvider.GetRequiredService<HttpClient>();
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
throw new Exception($"Chat stream failed: {await response.Content.ReadAsStringAsync(cancellationToken)}");
throw new HttpRequestException($"Chat stream failed: {await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)}");
}
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(stream);
var assistantResponse = new StringBuilder();
string line;
string? line;
while ((line = await reader.ReadLineAsync()) is not null && !cancellationToken.IsCancellationRequested)
{
if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data:"))
if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data:", StringComparison.Ordinal))
{
continue;
}
@@ -118,7 +126,7 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
}
/// <inheritdoc />
public ConversationSession GetOrCreateSession(string sessionId = null)
public ConversationSession GetOrCreateSession(string? sessionId = null)
{
lock (sessionsLock)
{
@@ -182,9 +190,14 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
private static string ParseStreamingResponse(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
return string.Empty;
}
try
{
var doc = JsonDocument.Parse(json);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
if (!root.TryGetProperty("choices", out var choices) || choices.GetArrayLength() == 0)
@@ -206,7 +219,11 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
return string.Empty;
}
catch
catch (JsonException)
{
return string.Empty;
}
catch (FormatException)
{
return string.Empty;
}

View File

@@ -16,15 +16,8 @@ public static class AIChatServiceExtensions
/// <returns>The updated service collection.</returns>
public static IServiceCollection AddAIChatService(this IServiceCollection services, Action<AIChatServiceOptions> configureOptions)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configureOptions);
services.Configure(configureOptions);
services.AddScoped<IAIChatService, AIChatService>();

View File

@@ -13,7 +13,7 @@ public class AIChatServiceOptions
/// <summary>
/// Gets or sets the proxy URL for the AI service, if any. If set, this will override the Endpoint.
/// </summary>
public string Proxy { get; set; } = null;
public string? Proxy { get; set; }
/// <summary>
/// Gets or sets the API key for authentication with the AI service.
@@ -28,7 +28,7 @@ public class AIChatServiceOptions
/// <summary>
/// Gets or sets the model name to use for executing chat completions (e.g., 'gpt-3.5-turbo').
/// </summary>
public string Model { get; set; }
public string? Model { get; set; }
/// <summary>
/// Gets or sets the system prompt for the AI assistant.

View File

@@ -22,19 +22,19 @@ namespace Radzen.Blazor
/// Gets or sets the text of the appointment.
/// </summary>
/// <value>The text.</value>
public string Text { get; set; }
public string? Text { get; set; }
/// <summary>
/// Gets or sets the data associated with the appointment
/// </summary>
/// <value>The data.</value>
public object Data { get; set; }
public object? Data { get; set; }
/// <summary>
/// Determines whether the specified object is equal to this instance. Used to check if two appointments are equal.
/// </summary>
/// <param name="obj">The object to compare with this instance.</param>
/// <returns><c>true</c> if the specified is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is AppointmentData data &&
Start == data.Start &&

View File

@@ -13,7 +13,7 @@ namespace Radzen.Blazor
/// </summary>
/// <value>The stroke.</value>
[Parameter]
public string Stroke { get; set; }
public string? Stroke { get; set; }
/// <summary>
/// Gets or sets the pixel width of axis.
/// </summary>
@@ -26,21 +26,21 @@ namespace Radzen.Blazor
/// </summary>
/// <value>The child content.</value>
[Parameter]
public RenderFragment ChildContent { get; set; }
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Gets or sets the format string used to display the axis values.
/// </summary>
/// <value>The format string.</value>
[Parameter]
public string FormatString { get; set; }
public string? FormatString { get; set; }
/// <summary>
/// Gets or sets a formatter function that formats the axis values.
/// </summary>
/// <value>The formatter.</value>
[Parameter]
public Func<object, string> Formatter { get; set; }
public Func<object, string>? Formatter { get; set; }
/// <summary>
/// Gets or sets the type of the line used to display the axis.
@@ -80,20 +80,20 @@ namespace Radzen.Blazor
/// </summary>
/// <value>The minimum.</value>
[Parameter]
public object Min { get; set; }
public object? Min { get; set; }
/// <summary>
/// Specifies the maximum value of the axis.
/// </summary>
/// <value>The maximum.</value>
[Parameter]
public object Max { get; set; }
public object? Max { get; set; }
/// <summary>
/// Specifies the step of the axis.
/// </summary>
[Parameter]
public object Step { get; set; }
public object? Step { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="AxisBase"/> is visible.
@@ -139,7 +139,7 @@ namespace Radzen.Blazor
}
else
{
return scale.FormatTick(FormatString, value);
return scale.FormatTick(FormatString ?? string.Empty, value);
}
}

View File

@@ -19,7 +19,17 @@ namespace Radzen.Blazor
/// Cache for the value returned by <see cref="Category"/> when that value is only dependent on
/// <see cref="CategoryProperty"/>.
/// </summary>
Func<TItem, double> categoryPropertyCache;
Func<TItem, double>? categoryPropertyCache;
/// <summary>
/// Returns the parent <see cref="RadzenChart"/> instance or throws an <see cref="InvalidOperationException"/> if not present.
/// </summary>
/// <returns>The parent <see cref="RadzenChart"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown when the parent chart is not set.</exception>
protected RadzenChart RequireChart()
{
return Chart ?? throw new InvalidOperationException($"{GetType().Name} requires a parent RadzenChart.");
}
/// <summary>
/// Creates a getter function that returns a value from the specified category scale for the specified data item.
@@ -34,13 +44,13 @@ namespace Radzen.Blazor
if (IsNumeric(CategoryProperty))
{
categoryPropertyCache = PropertyAccess.Getter<TItem, double>(CategoryProperty);
categoryPropertyCache = PropertyAccess.Getter<TItem, double>(CategoryProperty!);
return categoryPropertyCache;
}
if (IsDate(CategoryProperty))
{
var category = PropertyAccess.Getter<TItem, DateTime>(CategoryProperty);
var category = PropertyAccess.Getter<TItem, DateTime>(CategoryProperty!);
categoryPropertyCache = (item) => category(item).Ticks;
return categoryPropertyCache;
}
@@ -49,7 +59,7 @@ namespace Radzen.Blazor
{
Func<TItem, object> category = String.IsNullOrEmpty(CategoryProperty) ? (item) => string.Empty : PropertyAccess.Getter<TItem, object>(CategoryProperty);
return (item) => ordinal.Data.IndexOf(category(item));
return (item) => ordinal.Data?.IndexOf(category(item)) ?? -1;
}
return (item) => Items.IndexOf(item);
@@ -60,6 +70,8 @@ namespace Radzen.Blazor
/// </summary>
protected Func<TItem, double> ComposeCategory(ScaleBase scale)
{
ArgumentNullException.ThrowIfNull(scale);
return scale.Compose(Category(scale));
}
@@ -68,6 +80,8 @@ namespace Radzen.Blazor
/// </summary>
protected Func<TItem, double> ComposeValue(ScaleBase scale)
{
ArgumentNullException.ThrowIfNull(scale);
return scale.Compose(Value);
}
@@ -77,7 +91,7 @@ namespace Radzen.Blazor
/// <param name="propertyName">Name of the property.</param>
/// <returns><c>true</c> if the specified property name is date; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentException">Property {propertyName} does not exist</exception>
protected bool IsDate(string propertyName)
protected bool IsDate(string? propertyName)
{
if (String.IsNullOrEmpty(propertyName))
{
@@ -106,7 +120,7 @@ namespace Radzen.Blazor
/// <param name="propertyName">Name of the property.</param>
/// <returns><c>true</c> if the specified property name is numeric; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentException">Property {propertyName} does not exist</exception>
protected bool IsNumeric(string propertyName)
protected bool IsNumeric(string? propertyName)
{
if (String.IsNullOrEmpty(propertyName))
{
@@ -125,21 +139,21 @@ namespace Radzen.Blazor
/// <inheritdoc />
[Parameter]
public string Title { get; set; }
public string Title { get; set; } = null!;
/// <summary>
/// Gets or sets the child content.
/// </summary>
/// <value>The child content.</value>
[Parameter]
public RenderFragment ChildContent { get; set; }
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Gets or sets the tooltip template.
/// </summary>
/// <value>The tooltip template.</value>
[Parameter]
public RenderFragment<TItem> TooltipTemplate { get; set; }
public RenderFragment<TItem>? TooltipTemplate { get; set; }
/// <summary>
/// Gets the list of overlays.
@@ -157,7 +171,7 @@ namespace Radzen.Blazor
/// The name of the property of <typeparamref name="TItem" /> that provides the X axis (a.k.a. category axis) values.
/// </summary>
[Parameter]
public string CategoryProperty { get; set; }
public string? CategoryProperty { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="CartesianSeries{TItem}"/> is visible.
@@ -194,7 +208,7 @@ namespace Radzen.Blazor
/// The name of the property of <typeparamref name="TItem" /> that provides the Y axis (a.k.a. value axis) values.
/// </summary>
[Parameter]
public string ValueProperty { get; set; }
public string? ValueProperty { get; set; }
/// <inheritdoc />
[Parameter]
@@ -223,7 +237,7 @@ namespace Radzen.Blazor
/// </summary>
/// <value>The data.</value>
[Parameter]
public IEnumerable<TItem> Data { get; set; }
public IEnumerable<TItem>? Data { get; set; }
/// <summary>
/// Stores <see cref="Data" /> as an IList of <typeparamref name="TItem"/>.
@@ -272,6 +286,8 @@ namespace Radzen.Blazor
/// <inheritdoc />
public virtual ScaleBase TransformCategoryScale(ScaleBase scale)
{
ArgumentNullException.ThrowIfNull(scale);
if (Items == null)
{
return scale;
@@ -298,7 +314,7 @@ namespace Radzen.Blazor
var data = GetCategories();
if (scale is OrdinalScale ordinal)
if (scale is OrdinalScale ordinal && ordinal.Data != null)
{
foreach (var item in ordinal.Data)
{
@@ -328,6 +344,8 @@ namespace Radzen.Blazor
/// <inheritdoc />
public virtual ScaleBase TransformValueScale(ScaleBase scale)
{
ArgumentNullException.ThrowIfNull(scale);
if (Items != null)
{
if (Items.Any())
@@ -398,29 +416,32 @@ namespace Radzen.Blazor
{
if (Data != null)
{
if (Data is IList<TItem>)
if (Data is IList<TItem> list)
{
Items = Data as IList<TItem>;
Items = list;
}
else
{
Items = Data.ToList();
}
if (IsDate(CategoryProperty) || IsNumeric(CategoryProperty))
if (!string.IsNullOrEmpty(CategoryProperty) && (IsDate(CategoryProperty) || IsNumeric(CategoryProperty)))
{
Items = Items.AsQueryable().OrderBy(CategoryProperty).ToList();
}
}
await Chart.Refresh(false);
if (Chart != null)
{
await Chart.Refresh(false);
}
}
}
/// <inheritdoc />
protected override void Initialize()
{
Chart.AddSeries(this);
Chart?.AddSeries(this);
}
/// <inheritdoc />
@@ -443,6 +464,9 @@ namespace Radzen.Blazor
/// <returns><c>true</c> if the polygon contains the point, <c>false</c> otherwise.</returns>
protected bool InsidePolygon(Point point, Point[] polygon)
{
ArgumentNullException.ThrowIfNull(point);
ArgumentNullException.ThrowIfNull(polygon);
var minX = polygon[0].X;
var maxX = polygon[0].X;
var minY = polygon[0].Y;
@@ -479,18 +503,22 @@ namespace Radzen.Blazor
/// <inheritdoc />
public virtual RenderFragment RenderTooltip(object data)
{
var chart = RequireChart();
var item = (TItem)data;
return builder =>
{
if (Chart.Tooltip.Shared)
if (chart.Tooltip.Shared)
{
var category = PropertyAccess.GetValue(item, CategoryProperty);
builder.OpenComponent<ChartSharedTooltip>(0);
builder.AddAttribute(1, nameof(ChartSharedTooltip.Class), TooltipClass(item));
builder.AddAttribute(2, nameof(ChartSharedTooltip.Title), TooltipTitle(item));
builder.AddAttribute(3, nameof(ChartSharedTooltip.ChildContent), RenderSharedTooltipItems(category));
builder.CloseComponent();
var category = !string.IsNullOrEmpty(CategoryProperty) ? PropertyAccess.GetValue(item, CategoryProperty) : null;
if (category != null)
{
builder.OpenComponent<ChartSharedTooltip>(0);
builder.AddAttribute(1, nameof(ChartSharedTooltip.Class), TooltipClass(item));
builder.AddAttribute(2, nameof(ChartSharedTooltip.Title), TooltipTitle(item));
builder.AddAttribute(3, nameof(ChartSharedTooltip.ChildContent), RenderSharedTooltipItems(category));
builder.CloseComponent();
}
}
else
{
@@ -508,9 +536,11 @@ namespace Radzen.Blazor
private RenderFragment RenderSharedTooltipItems(object category)
{
var chart = RequireChart();
return builder =>
{
var visibleSeries = Chart.Series.Where(s => s.Visible).ToList();
var visibleSeries = chart.Series.Where(s => s.Visible).ToList();
foreach (var series in visibleSeries)
{
@@ -524,7 +554,7 @@ namespace Radzen.Blazor
{
return builder =>
{
var item = Items.FirstOrDefault(i => object.Equals(PropertyAccess.GetValue(i, CategoryProperty), category));
var item = Items.FirstOrDefault(i => !string.IsNullOrEmpty(CategoryProperty) && object.Equals(PropertyAccess.GetValue(i, CategoryProperty), category));
if (item != null)
{
@@ -553,7 +583,7 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
protected virtual string TooltipStyle(TItem item)
{
return Chart.Tooltip.Style;
return Chart?.Tooltip?.Style ?? string.Empty;
}
/// <summary>
@@ -562,7 +592,13 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
protected virtual string TooltipClass(TItem item)
{
return $"rz-series-{Chart.Series.IndexOf(this)}-tooltip";
var chart = Chart;
if (chart == null)
{
return "rz-series-tooltip";
}
return $"rz-series-{chart.Series.IndexOf(this)}-tooltip";
}
/// <inheritdoc />
@@ -576,6 +612,8 @@ namespace Radzen.Blazor
/// </summary>
protected virtual RenderFragment RenderLegendItem(bool clickable)
{
var chart = RequireChart();
var index = chart.Series.IndexOf(this);
var style = new List<string>();
if (IsVisible == false)
@@ -586,7 +624,7 @@ namespace Radzen.Blazor
return builder =>
{
builder.OpenComponent<LegendItem>(0);
builder.AddAttribute(1, nameof(LegendItem.Index), Chart.Series.IndexOf(this));
builder.AddAttribute(1, nameof(LegendItem.Index), index);
builder.AddAttribute(2, nameof(LegendItem.Color), Color);
builder.AddAttribute(3, nameof(LegendItem.MarkerType), MarkerType);
builder.AddAttribute(4, nameof(LegendItem.Style), string.Join(";", style));
@@ -617,19 +655,35 @@ namespace Radzen.Blazor
/// <inheritdoc />
public double GetMedian()
{
return Data.Select(e => Value(e)).OrderBy(e => e).Skip(Data.Count() / 2).FirstOrDefault();
var values = Items.Select(Value).OrderBy(e => e).ToList();
if (values.Count == 0)
{
return 0;
}
return values[values.Count / 2];
}
/// <inheritdoc />
public double GetMean()
{
return Data.Select(e => Value(e)).DefaultIfEmpty(double.NaN).Average();
return Items.Any() ? Items.Select(Value).Average() : double.NaN;
}
/// <inheritdoc />
public double GetMode()
{
return Data.Any() ? Data.GroupBy(e => Value(e)).Select(g => new { Value = g.Key, Count = g.Count() }).OrderByDescending(e => e.Count).FirstOrDefault().Value : double.NaN;
if (!Items.Any())
{
return double.NaN;
}
return Items
.GroupBy(item => Value(item))
.Select(g => new { Value = g.Key, Count = g.Count() })
.OrderByDescending(e => e.Count)
.First()
.Value;
}
/// <summary>
@@ -639,33 +693,43 @@ namespace Radzen.Blazor
{
double a = double.NaN, b = double.NaN;
if (Data.Any())
var chart = Chart;
if (chart == null)
{
return (a, b);
}
if (Items.Any())
{
Func<TItem, double> X;
Func<TItem, double> Y;
if (Chart.ShouldInvertAxes())
if (chart.ShouldInvertAxes())
{
X = e => Chart.CategoryScale.Scale(Value(e));
Y = e => Chart.ValueScale.Scale(Category(Chart.ValueScale)(e));
var valueScale = chart.ValueScale;
var categoryAccessor = Category(chart.ValueScale);
X = e => chart.CategoryScale.Scale(Value(e));
Y = e => valueScale.Scale(categoryAccessor(e));
}
else
{
X = e => Chart.CategoryScale.Scale(Category(Chart.CategoryScale)(e));
Y = e => Chart.ValueScale.Scale(Value(e));
var categoryAccessor = Category(chart.CategoryScale);
X = e => chart.CategoryScale.Scale(categoryAccessor(e));
Y = e => chart.ValueScale.Scale(Value(e));
}
var avgX = Data.Select(e => X(e)).Average();
var avgY = Data.Select(e => Y(e)).Average();
var sumXY = Data.Sum(e => (X(e) - avgX) * (Y(e) - avgY));
if (Chart.ShouldInvertAxes())
var data = Items.ToList();
var avgX = data.Select(e => X(e)).Average();
var avgY = data.Select(e => Y(e)).Average();
var sumXY = data.Sum(e => (X(e) - avgX) * (Y(e) - avgY));
if (chart.ShouldInvertAxes())
{
var sumYSq = Data.Sum(e => (Y(e) - avgY) * (Y(e) - avgY));
var sumYSq = data.Sum(e => (Y(e) - avgY) * (Y(e) - avgY));
b = sumXY / sumYSq;
a = avgX - b * avgY;
}
else
{
var sumXSq = Data.Sum(e => (X(e) - avgX) * (X(e) - avgX));
var sumXSq = data.Sum(e => (X(e) - avgX) * (X(e) - avgX));
b = sumXY / sumXSq;
a = avgY - b * avgX;
}
@@ -678,7 +742,9 @@ namespace Radzen.Blazor
{
IsVisible = !IsVisible;
if (Chart.LegendClick.HasDelegate)
var chart = Chart;
if (chart?.LegendClick.HasDelegate == true)
{
var args = new LegendClickEventArgs
{
@@ -687,18 +753,28 @@ namespace Radzen.Blazor
IsVisible = IsVisible,
};
await Chart.LegendClick.InvokeAsync(args);
await chart.LegendClick.InvokeAsync(args);
IsVisible = args.IsVisible;
}
await Chart.Refresh();
if (chart != null)
{
await chart.Refresh();
}
}
/// <inheritdoc />
public string GetTitle()
{
return String.IsNullOrEmpty(Title) ? $"Series {Chart.Series.IndexOf(this) + 1}" : Title;
var chart = Chart;
if (string.IsNullOrEmpty(Title))
{
var index = chart?.Series.IndexOf(this) ?? 0;
return $"Series {index + 1}";
}
return Title;
}
/// <summary>
@@ -716,8 +792,9 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
protected virtual string TooltipTitle(TItem item)
{
var category = Category(Chart.CategoryScale);
return Chart.CategoryAxis.Format(Chart.CategoryScale, Chart.CategoryScale.Value(category(item)));
var chart = RequireChart();
var category = Category(chart.CategoryScale);
return chart.CategoryAxis.Format(chart.CategoryScale, chart.CategoryScale.Value(category(item)));
}
/// <summary>
@@ -727,7 +804,8 @@ namespace Radzen.Blazor
/// <returns>System.String.</returns>
protected virtual string TooltipValue(TItem item)
{
return Chart.ValueAxis.Format(Chart.ValueScale, Chart.ValueScale.Value(Value(item)));
var chart = RequireChart();
return chart.ValueAxis.Format(chart.ValueScale, chart.ValueScale.Value(Value(item)));
}
/// <summary>
@@ -736,8 +814,9 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
internal virtual double TooltipX(TItem item)
{
var category = Category(Chart.CategoryScale);
return Chart.CategoryScale.Scale(category(item), true);
var chart = RequireChart();
var category = Category(chart.CategoryScale);
return chart.CategoryScale.Scale(category(item), true);
}
/// <summary>
@@ -746,7 +825,8 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
internal virtual double TooltipY(TItem item)
{
return Chart.ValueScale.Scale(Value(item), true);
var chart = RequireChart();
return chart.ValueScale.Scale(Value(item), true);
}
/// <inheritdoc />
@@ -760,25 +840,26 @@ namespace Radzen.Blazor
return new { Item = item, Distance = distance };
}).Aggregate((a, b) => a.Distance < b.Distance ? a : b).Item;
return (retObject,
return (retObject!,
new Point() { X = TooltipX(retObject), Y = TooltipY(retObject)});
}
return (null, null);
return (default!, new Point());
}
/// <inheritdoc />
public virtual IEnumerable<ChartDataLabel> GetDataLabels(double offsetX, double offsetY)
{
var chart = RequireChart();
var list = new List<ChartDataLabel>();
foreach (var d in Data)
foreach (var d in Items)
{
list.Add(new ChartDataLabel
{
Position = new Point { X = TooltipX(d) + offsetX, Y = TooltipY(d) + offsetY },
TextAnchor = "middle",
Text = Chart.ValueAxis.Format(Chart.ValueScale, Value(d))
Text = chart.ValueAxis.Format(chart.ValueScale, Value(d))
});
}
@@ -793,12 +874,12 @@ namespace Radzen.Blazor
/// <param name="defaultValue">The default value.</param>
/// <param name="colorRange">The color range value.</param>
/// <param name="value">The value of the item.</param>
protected string PickColor(int index, IEnumerable<string> colors, string defaultValue = null, IList<SeriesColorRange> colorRange = null, double value = 0.0)
protected string? PickColor(int index, IEnumerable<string>? colors, string? defaultValue = null, IList<SeriesColorRange>? colorRange = null, double value = 0.0)
{
if (colorRange != null)
{
var result = colorRange.Where(r => r.Min <= value && r.Max >= value).FirstOrDefault<SeriesColorRange>();
return result != null ? result.Color : defaultValue;
return result?.Color ?? defaultValue;
}
else
{
@@ -819,18 +900,20 @@ namespace Radzen.Blazor
/// <inheritdoc />
public async Task InvokeClick(EventCallback<SeriesClickEventArgs> handler, object data)
{
var category = Category(Chart.CategoryScale);
var chart = RequireChart();
var category = Category(chart.CategoryScale);
var dataItem = (TItem)data;
await handler.InvokeAsync(new SeriesClickEventArgs
{
Data = data,
Title = GetTitle(),
Category = PropertyAccess.GetValue(data, CategoryProperty),
Value = PropertyAccess.GetValue(data, ValueProperty),
Category = !string.IsNullOrEmpty(CategoryProperty) ? PropertyAccess.GetValue(data, CategoryProperty) : null,
Value = !string.IsNullOrEmpty(ValueProperty) ? PropertyAccess.GetValue(data, ValueProperty) : null,
Point = new SeriesPoint
{
Category = category((TItem)data),
Value = Value((TItem)data)
Category = category(dataItem),
Value = Value(dataItem)
}
});
}

View File

@@ -36,4 +36,8 @@ public class ChatMessage
/// Gets or sets whether this message is currently streaming.
/// </summary>
public bool IsStreaming { get; set; }
/// <summary>
/// Gets or sets the role associated with the message (e.g., "user", "assistant").
/// </summary>
public string? Role { get; set; }
}

View File

@@ -12,25 +12,25 @@ public class CompositeFilterDescriptor
/// Gets or sets the name of the filtered property.
/// </summary>
/// <value>The property.</value>
public string Property { get; set; }
public string? Property { get; set; }
/// <summary>
/// Gets or sets the property type.
/// </summary>
/// <value>The property type.</value>
public Type Type { get; set; }
public Type? Type { get; set; }
/// <summary>
/// Gets or sets the name of the filtered property.
/// </summary>
/// <value>The property.</value>
public string FilterProperty { get; set; }
public string? FilterProperty { get; set; }
/// <summary>
/// Gets or sets the value to filter by.
/// </summary>
/// <value>The filter value.</value>
public object FilterValue { get; set; }
public object? FilterValue { get; set; }
/// <summary>
/// Gets or sets the operator which will compare the property value with <see cref="FilterValue" />.
@@ -48,6 +48,6 @@ public class CompositeFilterDescriptor
/// Gets or sets the filters.
/// </summary>
/// <value>The filters.</value>
public IEnumerable<CompositeFilterDescriptor> Filters { get; set; }
public IEnumerable<CompositeFilterDescriptor>? Filters { get; set; }
}

View File

@@ -1,225 +1,227 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Radzen
{
/// <summary>
/// Class ContextMenuService. Contains various methods with options to open and close context menus.
/// Should be added as scoped service in the application services and RadzenContextMenu should be added in application main layout.
/// Implements the <see cref="IDisposable" />
/// </summary>
/// <seealso cref="IDisposable" />
/// <example>
/// <code>
/// @inject ContextMenuService ContextMenuService
/// &lt;RadzenButton Text="Show context menu" ContextMenu=@(args => ShowContextMenuWithContent(args)) /&gt;
/// @code {
/// void ShowContextMenuWithContent(MouseEventArgs args) =&gt; ContextMenuService.Open(args, ds =&gt;
/// @&lt;RadzenMenu Click="OnMenuItemClick"&gt;
/// &lt;RadzenMenuItem Text="Item1" Value="1"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="Item2" Value="2"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="More items" Value="3"&gt;
/// &lt;RadzenMenuItem Text="More sub items" Value="4"&gt;
/// &lt;RadzenMenuItem Text="Item1" Value="5"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="Item2" Value="6"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenu&gt;);
///
/// void OnMenuItemClick(MenuItemEventArgs args)
/// {
/// Console.WriteLine($"Menu item with Value={args.Value} clicked");
/// }
/// }
/// </code>
/// </example>
public class ContextMenuService : IDisposable
{
/// <summary>
/// Gets or sets the navigation manager.
/// </summary>
/// <value>The navigation manager.</value>
NavigationManager navigationManager { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenuService"/> class.
/// </summary>
/// <param name="uriHelper">The URI helper.</param>
public ContextMenuService(NavigationManager uriHelper)
{
navigationManager = uriHelper;
if (navigationManager != null)
{
navigationManager.LocationChanged += UriHelper_OnLocationChanged;
}
}
/// <summary>
/// Handles the OnLocationChanged event of the UriHelper control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs"/> instance containing the event data.</param>
private void UriHelper_OnLocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System;
using System.Collections.Generic;
namespace Radzen
{
/// <summary>
/// Class ContextMenuService. Contains various methods with options to open and close context menus.
/// Should be added as scoped service in the application services and RadzenContextMenu should be added in application main layout.
/// Implements the <see cref="IDisposable" />
/// </summary>
/// <seealso cref="IDisposable" />
/// <example>
/// <code>
/// @inject ContextMenuService ContextMenuService
/// &lt;RadzenButton Text="Show context menu" ContextMenu=@(args => ShowContextMenuWithContent(args)) /&gt;
/// @code {
/// void ShowContextMenuWithContent(MouseEventArgs args) =&gt; ContextMenuService.Open(args, ds =&gt;
/// @&lt;RadzenMenu Click="OnMenuItemClick"&gt;
/// &lt;RadzenMenuItem Text="Item1" Value="1"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="Item2" Value="2"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="More items" Value="3"&gt;
/// &lt;RadzenMenuItem Text="More sub items" Value="4"&gt;
/// &lt;RadzenMenuItem Text="Item1" Value="5"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="Item2" Value="6"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenu&gt;);
///
/// void OnMenuItemClick(MenuItemEventArgs args)
/// {
/// Console.WriteLine($"Menu item with Value={args.Value} clicked");
/// }
/// }
/// </code>
/// </example>
public class ContextMenuService : IDisposable
{
/// <summary>
/// Gets or sets the navigation manager.
/// </summary>
/// <value>The navigation manager.</value>
NavigationManager? navigationManager { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenuService"/> class.
/// </summary>
/// <param name="uriHelper">The URI helper.</param>
public ContextMenuService(NavigationManager? uriHelper)
{
this.OnNavigate?.Invoke();
}
/// <summary>
/// Occurs when [on navigate].
/// </summary>
public event Action OnNavigate;
/// <summary>
/// Raises the Close event.
/// </summary>
public event Action OnClose;
/// <summary>
/// Occurs when [on open].
/// </summary>
public event Action<MouseEventArgs, ContextMenuOptions> OnOpen;
/// <summary>
/// Opens the specified arguments.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="items">The items.</param>
/// <param name="click">The click.</param>
public void Open(MouseEventArgs args, IEnumerable<ContextMenuItem> items, Action<MenuItemEventArgs> click = null)
{
var options = new ContextMenuOptions();
options.Items = items;
options.Click = click;
OpenTooltip(args, options);
}
/// <summary>
/// Opens the specified arguments.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="childContent">Content of the child.</param>
public void Open(MouseEventArgs args, RenderFragment<ContextMenuService> childContent)
{
var options = new ContextMenuOptions();
options.ChildContent = childContent;
OpenTooltip(args, options);
}
/// <summary>
/// Opens the tooltip.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="options">The options.</param>
private void OpenTooltip(MouseEventArgs args, ContextMenuOptions options)
{
OnOpen?.Invoke(args, options);
}
/// <summary>
/// Closes this instance.
/// </summary>
public void Close()
{
OnClose?.Invoke();
}
/// <summary>
/// Disposes this instance.
/// </summary>
public void Dispose()
{
navigationManager.LocationChanged -= UriHelper_OnLocationChanged;
}
}
/// <summary>
/// Class ContextMenuOptions.
/// </summary>
public class ContextMenuOptions
{
/// <summary>
/// Gets or sets the child content.
/// </summary>
/// <value>The child content.</value>
public RenderFragment<ContextMenuService> ChildContent { get; set; }
/// <summary>
/// Gets or sets the items.
/// </summary>
/// <value>The items.</value>
public IEnumerable<ContextMenuItem> Items { get; set; }
/// <summary>
/// Gets or sets the click.
/// </summary>
/// <value>The click.</value>
public Action<MenuItemEventArgs> Click { get; set; }
}
/// <summary>
/// Class ContextMenu.
/// </summary>
public class ContextMenu
{
/// <summary>
/// Gets or sets the options.
/// </summary>
/// <value>The options.</value>
public ContextMenuOptions Options { get; set; }
/// <summary>
/// Gets or sets the mouse event arguments.
/// </summary>
/// <value>The mouse event arguments.</value>
public MouseEventArgs MouseEventArgs { get; set; }
}
/// <summary>
/// Class ContextMenuItem.
/// </summary>
public class ContextMenuItem
{
/// <summary>
/// Gets or sets the text.
/// </summary>
/// <value>The text.</value>
public string Text { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public object Value { get; set; }
/// <summary>
/// Gets or sets the icon.
/// </summary>
/// <value>The icon.</value>
public string Icon { get; set; }
/// <summary>
/// Gets or sets the icon color.
/// </summary>
/// <value>The icon color.</value>
public string IconColor { get; set; }
/// <summary>
/// Gets or sets the image.
/// </summary>
/// <value>The image.</value>
public string Image { get; set; }
/// <summary>
/// Gets or sets the image style.
/// </summary>
/// <value>The image style.</value>
public string ImageStyle { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is disabled.
/// </summary>
/// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value>
public bool Disabled { get; set; }
}
}
navigationManager = uriHelper;
if (navigationManager != null)
{
navigationManager.LocationChanged += UriHelper_OnLocationChanged;
}
}
/// <summary>
/// Handles the OnLocationChanged event of the UriHelper control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs"/> instance containing the event data.</param>
private void UriHelper_OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
{
this.OnNavigate?.Invoke();
}
/// <summary>
/// Occurs when [on navigate].
/// </summary>
public event Action? OnNavigate;
/// <summary>
/// Raises the Close event.
/// </summary>
public event Action? OnClose;
/// <summary>
/// Occurs when [on open].
/// </summary>
public event Action<MouseEventArgs, ContextMenuOptions>? OnOpen;
/// <summary>
/// Opens the specified arguments.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="items">The items.</param>
/// <param name="click">The click.</param>
public void Open(MouseEventArgs args, IEnumerable<ContextMenuItem> items, Action<MenuItemEventArgs>? click = null)
{
var options = new ContextMenuOptions();
options.Items = items;
options.Click = click;
OpenTooltip(args, options);
}
/// <summary>
/// Opens the specified arguments.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="childContent">Content of the child.</param>
public void Open(MouseEventArgs args, RenderFragment<ContextMenuService> childContent)
{
var options = new ContextMenuOptions();
options.ChildContent = childContent;
OpenTooltip(args, options);
}
/// <summary>
/// Opens the tooltip.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="options">The options.</param>
private void OpenTooltip(MouseEventArgs args, ContextMenuOptions options)
{
OnOpen?.Invoke(args, options);
}
/// <summary>
/// Closes this instance.
/// </summary>
public void Close()
{
OnClose?.Invoke();
}
/// <summary>
/// Disposes this instance.
/// </summary>
public void Dispose()
{
if (navigationManager != null)
{
navigationManager.LocationChanged -= UriHelper_OnLocationChanged;
}
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Class ContextMenuOptions.
/// </summary>
public class ContextMenuOptions
{
/// <summary>
/// Gets or sets the child content.
/// </summary>
/// <value>The child content.</value>
public RenderFragment<ContextMenuService>? ChildContent { get; set; }
/// <summary>
/// Gets or sets the items.
/// </summary>
/// <value>The items.</value>
public IEnumerable<ContextMenuItem>? Items { get; set; }
/// <summary>
/// Gets or sets the click.
/// </summary>
/// <value>The click.</value>
public Action<MenuItemEventArgs>? Click { get; set; }
}
/// <summary>
/// Class ContextMenu.
/// </summary>
public class ContextMenu
{
/// <summary>
/// Gets or sets the options.
/// </summary>
/// <value>The options.</value>
public ContextMenuOptions? Options { get; set; }
/// <summary>
/// Gets or sets the mouse event arguments.
/// </summary>
/// <value>The mouse event arguments.</value>
public MouseEventArgs? MouseEventArgs { get; set; }
}
/// <summary>
/// Class ContextMenuItem.
/// </summary>
public class ContextMenuItem
{
/// <summary>
/// Gets or sets the text.
/// </summary>
/// <value>The text.</value>
public string? Text { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public object? Value { get; set; }
/// <summary>
/// Gets or sets the icon.
/// </summary>
/// <value>The icon.</value>
public string? Icon { get; set; }
/// <summary>
/// Gets or sets the icon color.
/// </summary>
/// <value>The icon color.</value>
public string? IconColor { get; set; }
/// <summary>
/// Gets or sets the image.
/// </summary>
/// <value>The image.</value>
public string? Image { get; set; }
/// <summary>
/// Gets or sets the image style.
/// </summary>
/// <value>The image style.</value>
public string? ImageStyle { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is disabled.
/// </summary>
/// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value>
public bool Disabled { get; set; }
}
}

View File

@@ -44,7 +44,8 @@ public class ConversationSession
Messages.Add(new ChatMessage
{
UserId = role,
IsUser = role != "system",
Role = role,
IsUser = role == "user",
Content = content,
Timestamp = DateTime.Now
});
@@ -82,7 +83,7 @@ public class ConversationSession
// Add conversation messages
foreach (var message in Messages)
{
messages.Add(new { role = message.IsUser ? "user" : "system", content = message.Content });
messages.Add(new { role = message.Role, content = message.Content });
}
return messages;

View File

@@ -18,25 +18,39 @@ public static class ConvertType
/// <param name="type">The type.</param>
/// <param name="culture">The culture.</param>
/// <returns>System.Object</returns>
public static object ChangeType(object value, Type type, CultureInfo culture = null)
public static object? ChangeType(object value, Type type, CultureInfo? culture = null)
{
ArgumentNullException.ThrowIfNull(type);
// CA1062: Validate 'value' is non-null before using it
if (value == null)
{
if (Nullable.GetUnderlyingType(type) != null)
{
return null;
}
throw new ArgumentNullException(nameof(value));
}
if (culture == null)
{
culture = CultureInfo.CurrentCulture;
}
if (value == null && Nullable.GetUnderlyingType(type) != null)
{
return value;
}
if ((Nullable.GetUnderlyingType(type) ?? type) == typeof(Guid) && value is string)
{
return Guid.Parse((string)value);
}
if (Nullable.GetUnderlyingType(type)?.IsEnum == true)
var underlyingEnumType = Nullable.GetUnderlyingType(type);
if (underlyingEnumType?.IsEnum == true)
{
return Enum.Parse(Nullable.GetUnderlyingType(type), value.ToString());
var valueString = value.ToString();
if (valueString == null)
{
throw new ArgumentNullException(nameof(value), "Enum value cannot be null.");
}
return Enum.Parse(underlyingEnumType, valueString);
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))

View File

@@ -44,12 +44,12 @@ namespace Radzen
/// <summary>
/// Gets or sets a value indicating whether to use secure cookies.
/// </summary>
public bool IsSecure { get; set; } = false;
public bool IsSecure { get; set; }
/// <summary>
/// Gets or sets the SameSite attribute for the cookie.
/// </summary>
public CookieSameSiteMode? SameSite { get; set; } = null;
public CookieSameSiteMode? SameSite { get; set; }
}
/// <summary>
@@ -64,13 +64,16 @@ namespace Radzen
/// <summary>
/// Initializes a new instance of the <see cref="CookieThemeService" /> class.
/// </summary>
public CookieThemeService(IJSRuntime jsRuntime, ThemeService themeService, IOptions<CookieThemeServiceOptions> options)
public CookieThemeService(IJSRuntime jsRuntime, ThemeService themeService, IOptions<CookieThemeServiceOptions>? options)
{
this.jsRuntime = jsRuntime;
this.themeService = themeService;
this.options = options.Value;
this.options = options?.Value ?? new CookieThemeServiceOptions();
themeService.ThemeChanged += OnThemeChanged;
if (themeService != null)
{
themeService.ThemeChanged += OnThemeChanged;
}
_ = InitializeAsync();
}

View File

@@ -47,14 +47,14 @@ namespace Radzen
/// </summary>
/// <value>The name.</value>
[Parameter]
public string Name { get; set; }
public string? Name { get; set; }
/// <summary>
/// Gets or sets the placeholder.
/// </summary>
/// <value>The placeholder.</value>
[Parameter]
public string Placeholder { get; set; }
public string? Placeholder { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="DataBoundFormComponent{T}"/> is disabled.
@@ -80,36 +80,36 @@ namespace Radzen
/// <summary>
/// The form
/// </summary>
IRadzenForm _form;
IRadzenForm? form;
/// <summary>
/// Gets or sets the form.
/// </summary>
/// <value>The form.</value>
[CascadingParameter]
public IRadzenForm Form
public IRadzenForm? Form
{
get
{
return _form;
return form;
}
set
{
_form = value;
_form?.AddComponent(this);
form = value;
form?.AddComponent(this);
}
}
/// <summary>
/// The value
/// </summary>
private T _value = default;
private T? _value;
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
[Parameter]
public T Value
public T? Value
{
get
{
@@ -167,18 +167,18 @@ namespace Radzen
/// </summary>
/// <value>The text property.</value>
[Parameter]
public string TextProperty { get; set; }
public string? TextProperty { get; set; }
/// <summary>
/// The data
/// </summary>
IEnumerable _data = null;
IEnumerable? _data;
/// <summary>
/// Gets or sets the data.
/// </summary>
/// <value>The data.</value>
[Parameter]
public virtual IEnumerable Data
public virtual IEnumerable? Data
{
get
{
@@ -208,7 +208,7 @@ namespace Radzen
/// Gets the query.
/// </summary>
/// <value>The query.</value>
protected virtual IQueryable Query
protected virtual IQueryable? Query
{
get
{
@@ -220,7 +220,7 @@ namespace Radzen
/// Gets or sets the search text
/// </summary>
[Parameter]
public string SearchText
public string? SearchText
{
get
{
@@ -245,29 +245,30 @@ namespace Radzen
/// <summary>
/// The search text
/// </summary>
internal string searchText;
internal string? searchText;
/// <summary>
/// The view
/// </summary>
protected IQueryable _view = null;
protected IQueryable? _view;
/// <summary>
/// Gets the view.
/// </summary>
/// <value>The view.</value>
protected virtual IEnumerable View
protected virtual IEnumerable? View
{
get
{
if (_view == null && Query != null)
var query = Query;
if (_view == null && query != null)
{
if (!string.IsNullOrEmpty(searchText))
if (!string.IsNullOrEmpty(searchText) && !string.IsNullOrEmpty(TextProperty))
{
_view = Query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity);
_view = query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity);
}
else
{
_view = (typeof(IQueryable).IsAssignableFrom(Data.GetType())) ? Query.Cast<object>().ToList().AsQueryable() : Query;
_view = Data is IQueryable ? query.Cast<object>().ToList().AsQueryable() : query;
}
}
@@ -275,14 +276,14 @@ namespace Radzen
}
}
internal IEnumerable GetView() => View;
internal IEnumerable? GetView() => View;
/// <summary>
/// Gets or sets the edit context.
/// </summary>
/// <value>The edit context.</value>
[CascadingParameter]
public EditContext EditContext { get; set; }
public EditContext? EditContext { get; set; }
/// <summary>
/// Gets the field identifier.
@@ -296,7 +297,7 @@ namespace Radzen
/// </summary>
/// <value>The value expression.</value>
[Parameter]
public Expression<Func<T>> ValueExpression { get; set; }
public Expression<Func<T>>? ValueExpression { get; set; }
/// <summary>
/// Set parameters as an asynchronous operation.
/// </summary>
@@ -338,7 +339,7 @@ namespace Radzen
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="ValidationStateChangedEventArgs"/> instance containing the event data.</param>
private void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
private void ValidationStateChanged(object? sender, ValidationStateChangedEventArgs e)
{
StateHasChanged();
}
@@ -356,13 +357,15 @@ namespace Radzen
}
Form?.RemoveComponent(this);
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets the value.
/// </summary>
/// <returns>System.Object.</returns>
public virtual object GetValue()
public virtual object? GetValue()
{
return Value;
}
@@ -386,13 +389,13 @@ namespace Radzen
/// <summary> Provides support for RadzenFormField integration. </summary>
[CascadingParameter]
public IFormFieldContext FormFieldContext { get; set; }
public IFormFieldContext? FormFieldContext { get; set; }
/// <summary> Gets the current placeholder. Returns empty string if this component is inside a RadzenFormField.</summary>
protected string CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder;
protected string? CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder;
/// <summary>
/// Handles the <see cref="E:ContextMenu" /> event.
/// Handles the ContextMenu event.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <returns>Task.</returns>

View File

@@ -6,16 +6,16 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.CellContextMenu" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridCellMouseEventArgs<T> : Microsoft.AspNetCore.Components.Web.MouseEventArgs
public class DataGridCellMouseEventArgs<T> : Microsoft.AspNetCore.Components.Web.MouseEventArgs where T : notnull
{
/// <summary>
/// Gets the data item which the clicked DataGrid row represents.
/// </summary>
public T Data { get; internal set; }
public T? Data { get; internal set; }
/// <summary>
/// Gets the RadzenDataGridColumn which this cells represents.
/// </summary>
public RadzenDataGridColumn<T> Column { get; internal set; }
public RadzenDataGridColumn<T>? Column { get; internal set; }
}

View File

@@ -6,11 +6,11 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.CellRender" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridCellRenderEventArgs<T> : RowRenderEventArgs<T>
public class DataGridCellRenderEventArgs<T> : RowRenderEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the RadzenDataGridColumn which this cells represents.
/// </summary>
public RadzenDataGridColumn<T> Column { get; internal set; }
public RadzenDataGridColumn<T>? Column { get; internal set; }
}

View File

@@ -11,7 +11,7 @@ internal class DataGridChildData<T>
/// <summary>
/// Gets or sets the parent child data.
/// </summary>
internal DataGridChildData<T> ParentChildData { get; set; }
internal DataGridChildData<T>? ParentChildData { get; set; }
/// <summary>
/// Gets or sets the level.
@@ -21,6 +21,6 @@ internal class DataGridChildData<T>
/// <summary>
/// Gets or sets the data.
/// </summary>
internal IEnumerable<T> Data { get; set; }
internal IEnumerable<T>? Data { get; set; }
}

View File

@@ -6,22 +6,22 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.Filter" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnFilterEventArgs<T>
public class DataGridColumnFilterEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the filtered RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T> Column { get; internal set; }
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the new filter value of the filtered column.
/// </summary>
public object FilterValue { get; internal set; }
public object? FilterValue { get; internal set; }
/// <summary>
/// Gets the new second filter value of the filtered column.
/// </summary>
public object SecondFilterValue { get; internal set; }
public object? SecondFilterValue { get; internal set; }
/// <summary>
/// Gets the new filter operator of the filtered column.

View File

@@ -6,16 +6,16 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.Group" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnGroupEventArgs<T>
public class DataGridColumnGroupEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the grouped RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T> Column { get; internal set; }
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the group descriptor.
/// </summary>
public GroupDescriptor GroupDescriptor { get; internal set; }
public GroupDescriptor? GroupDescriptor { get; internal set; }
}

View File

@@ -6,12 +6,12 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.ColumnReordered" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnReorderedEventArgs<T>
public class DataGridColumnReorderedEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the reordered RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T> Column { get; internal set; }
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the old index of the column.

View File

@@ -6,17 +6,17 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.ColumnReordering" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnReorderingEventArgs<T>
public class DataGridColumnReorderingEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the reordered RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T> Column { get; internal set; }
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the reordered to RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T> ToColumn { get; internal set; }
public RadzenDataGridColumn<T>? ToColumn { get; internal set; }
/// <summary>
/// Gets or sets a value which will cancel the event.

View File

@@ -6,12 +6,12 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.ColumnResized" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnResizedEventArgs<T>
public class DataGridColumnResizedEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the resized RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T> Column { get; internal set; }
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the new width of the resized column.

View File

@@ -8,12 +8,12 @@ public class DataGridColumnSettings
/// <summary>
/// Property.
/// </summary>
public string UniqueID { get; set; }
public string UniqueID { get; set; } = string.Empty;
/// <summary>
/// Property.
/// </summary>
public string Property { get; set; }
public string Property { get; set; } = string.Empty;
/// <summary>
/// Visible.
@@ -23,7 +23,7 @@ public class DataGridColumnSettings
/// <summary>
/// Width.
/// </summary>
public string Width { get; set; }
public string Width { get; set; } = string.Empty;
/// <summary>
/// OrderIndex.
@@ -43,7 +43,7 @@ public class DataGridColumnSettings
/// <summary>
/// FilterValue.
/// </summary>
public object FilterValue { get; set; }
public object? FilterValue { get; set; }
/// <summary>
/// FilterOperator.
@@ -53,7 +53,7 @@ public class DataGridColumnSettings
/// <summary>
/// SecondFilterValue.
/// </summary>
public object SecondFilterValue { get; set; }
public object? SecondFilterValue { get; set; }
/// <summary>
/// SecondFilterOperator.
@@ -68,7 +68,7 @@ public class DataGridColumnSettings
/// <summary>
/// CustomFilterExpression.
/// </summary>
public string CustomFilterExpression { get; set; }
public string CustomFilterExpression { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the mode that determines whether the filter applies to any or all items in a collection.

View File

@@ -6,12 +6,12 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.Sort" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnSortEventArgs<T>
public class DataGridColumnSortEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the sorted RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T> Column { get; internal set; }
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the new sort order of the sorted column.

View File

@@ -12,12 +12,12 @@ public class DataGridLoadChildDataEventArgs<T>
/// Gets or sets the data.
/// </summary>
/// <value>The data.</value>
public IEnumerable<T> Data { get; set; }
public IEnumerable<T>? Data { get; set; }
/// <summary>
/// Gets the item.
/// </summary>
/// <value>The item.</value>
public T Item { get; internal set; }
public T? Item { get; internal set; }
}

View File

@@ -8,13 +8,13 @@ namespace Radzen;
/// Supplies information about a <see cref="Radzen.Blazor.RadzenDataGrid{TItem}.LoadColumnFilterData" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridLoadColumnFilterDataEventArgs<T>
public class DataGridLoadColumnFilterDataEventArgs<T> where T : notnull
{
/// <summary>
/// Gets or sets the data.
/// </summary>
/// <value>The data.</value>
public IEnumerable Data { get; set; }
public IEnumerable? Data { get; set; }
/// <summary>
/// Gets or sets the total data count.
@@ -37,17 +37,17 @@ public class DataGridLoadColumnFilterDataEventArgs<T>
/// Gets the filter expression as a string.
/// </summary>
/// <value>The filter.</value>
public string Filter { get; internal set; }
public string? Filter { get; internal set; }
/// <summary>
/// Gets or sets filter property used to limit and distinct values, if not set, args.Data are used as values.
/// </summary>
/// <value>The filter property.</value>
public string Property { get; set; }
public string? Property { get; set; }
/// <summary>
/// Gets the RadzenDataGridColumn.
/// </summary>
public Radzen.Blazor.RadzenDataGridColumn<T> Column { get; internal set; }
public Radzen.Blazor.RadzenDataGridColumn<T>? Column { get; internal set; }
}

View File

@@ -8,6 +8,6 @@ public class DataGridLoadSettingsEventArgs
/// <summary>
/// Gets or sets the settings.
/// </summary>
public DataGridSettings Settings { get; set; }
public DataGridSettings? Settings { get; set; }
}

View File

@@ -7,11 +7,11 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.PickedColumnsChanged" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridPickedColumnsChangedEventArgs<T>
public class DataGridPickedColumnsChangedEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the picked columns.
/// </summary>
public IEnumerable<RadzenDataGridColumn<T>> Columns { get; internal set; }
public IEnumerable<RadzenDataGridColumn<T>>? Columns { get; internal set; }
}

View File

@@ -6,12 +6,12 @@ namespace Radzen;
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.Render" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridRenderEventArgs<T>
public class DataGridRenderEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the instance of the RadzenDataGrid component which has rendered.
/// </summary>
public RadzenDataGrid<T> Grid { get; internal set; }
public RadzenDataGrid<T>? Grid { get; internal set; }
/// <summary>
/// Gets a value indicating whether this is the first time the RadzenDataGrid has rendered.

View File

@@ -9,6 +9,6 @@ public class DataGridRowMouseEventArgs<T> : Microsoft.AspNetCore.Components.Web.
/// <summary>
/// Gets the data item which the clicked DataGrid row represents.
/// </summary>
public T Data { get; internal set; }
public T? Data { get; internal set; }
}

View File

@@ -10,12 +10,12 @@ public class DataGridSettings
/// <summary>
/// Columns.
/// </summary>
public IEnumerable<DataGridColumnSettings> Columns { get; set; }
public IEnumerable<DataGridColumnSettings> Columns { get; set; } = System.Array.Empty<DataGridColumnSettings>();
/// <summary>
/// Groups.
/// </summary>
public IEnumerable<GroupDescriptor> Groups { get; set; }
public IEnumerable<GroupDescriptor> Groups { get; set; } = System.Array.Empty<GroupDescriptor>();
/// <summary>
/// CurrentPage.

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.Linq;
using System.Collections.Generic;
@@ -50,14 +51,14 @@ namespace Radzen.Blazor
{
if (min != null)
{
var minDate = Convert.ToDateTime(min);
var minDate = Convert.ToDateTime(min, CultureInfo.InvariantCulture);
Input.Start = minDate.Ticks;
Round = false;
}
if (max != null)
{
var maxDate = Convert.ToDateTime(max);
var maxDate = Convert.ToDateTime(max, CultureInfo.InvariantCulture);
Input.End = maxDate.Ticks;
Round = false;
}

View File

@@ -6,9 +6,9 @@ namespace Radzen;
/// <summary>
/// Utility class for debouncing and throttling function calls.
/// </summary>
internal class Debouncer
internal class Debouncer : IDisposable
{
private System.Timers.Timer timer;
private System.Timers.Timer? timer;
private DateTime timerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);
/// <summary>
@@ -86,4 +86,15 @@ internal class Debouncer
timer.Start();
timerStarted = curTime;
}
/// <inheritdoc />
public void Dispose()
{
if(timer != null)
{
timer.Stop();
timer.Dispose();
timer = null;
}
}
}

View File

@@ -12,7 +12,7 @@ using System.Threading.Tasks;
namespace Radzen
{
/// <summary>
/// Class DialogService. Contains various methods with options to open and close dialogs.
/// Class DialogService. Contains various methods with options to open and close dialogs.
/// Should be added as scoped service in the application services and RadzenDialog should be added in application main layout.
/// </summary>
/// <example>
@@ -41,8 +41,8 @@ namespace Radzen
/// </example>
public class DialogService : IDisposable
{
private DotNetObjectReference<DialogService> reference;
internal DotNetObjectReference<DialogService> Reference
private DotNetObjectReference<DialogService>? reference;
internal DotNetObjectReference<DialogService>? Reference
{
get
{
@@ -54,20 +54,20 @@ namespace Radzen
return reference;
}
}
/// <summary>
/// Gets or sets the URI helper.
/// </summary>
/// <value>The URI helper.</value>
NavigationManager UriHelper { get; set; }
IJSRuntime JSRuntime { get; set; }
NavigationManager? UriHelper { get; set; }
IJSRuntime? JSRuntime { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DialogService"/> class.
/// </summary>
/// <param name="uriHelper">The URI helper.</param>
/// <param name="jsRuntime">IJSRuntime instance.</param>
public DialogService(NavigationManager uriHelper, IJSRuntime jsRuntime)
public DialogService(NavigationManager? uriHelper, IJSRuntime? jsRuntime)
{
UriHelper = uriHelper;
JSRuntime = jsRuntime;
@@ -78,9 +78,9 @@ namespace Radzen
}
}
private void UriHelper_OnLocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
private void UriHelper_OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
{
while (dialogs.Any())
while (dialogs.Count > 0)
{
Close();
}
@@ -94,27 +94,27 @@ namespace Radzen
/// <summary>
/// Raises the Close event.
/// </summary>
public event Action<dynamic> OnClose;
public event Action<dynamic>? OnClose;
/// <summary>
/// Occurs when [on refresh].
/// </summary>
public event Action OnRefresh;
public event Action? OnRefresh;
/// <summary>
/// Occurs when a new dialog is open.
/// </summary>
public event Action<string, Type, Dictionary<string, object>, DialogOptions> OnOpen;
public event Action<string?, Type, Dictionary<string, object>, DialogOptions>? OnOpen;
/// <summary>
/// Raises the Close event for the side dialog
/// </summary>
public event Action<dynamic> OnSideClose;
public event Action<dynamic>? OnSideClose;
/// <summary>
/// Raises the Open event for the side dialog
/// </summary>
public event Action<Type, Dictionary<string, object>, SideDialogOptions> OnSideOpen;
public event Action<Type, Dictionary<string, object>, SideDialogOptions>? OnSideOpen;
/// <summary>
/// Opens a dialog with the specified arguments.
@@ -123,7 +123,7 @@ namespace Radzen
/// <param name="title">The text displayed in the title bar of the dialog.</param>
/// <param name="parameters">The dialog parameters.</param>
/// <param name="options">The dialog options.</param>
public virtual void Open<T>(string title, Dictionary<string, object> parameters = null, DialogOptions options = null) where T : ComponentBase
public virtual void Open<T>(string title, Dictionary<string, object>? parameters = null, DialogOptions? options = null) where T : ComponentBase
{
OpenDialog<T>(title, parameters, options);
}
@@ -135,7 +135,7 @@ namespace Radzen
/// <param name="componentType">The type of the component to be displayed in the dialog. Must inherit from <see cref="ComponentBase"/>.</param>
/// <param name="parameters">The dialog parameters.</param>
/// <param name="options">The dialog options.</param>
public virtual void Open(string title, Type componentType, Dictionary<string, object> parameters = null, DialogOptions options = null)
public virtual void Open(string title, Type componentType, Dictionary<string, object>? parameters = null, DialogOptions? options = null)
{
if (!typeof(ComponentBase).IsAssignableFrom(componentType))
{
@@ -143,8 +143,12 @@ namespace Radzen
}
var method = GetType().GetMethod(nameof(OpenDialog), BindingFlags.Instance | BindingFlags.NonPublic);
if (method == null)
{
throw new InvalidOperationException("OpenDialog method not found.");
}
method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters, options });
method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters!, options! });
}
/// <summary>
@@ -159,7 +163,7 @@ namespace Radzen
/// The tasks
/// </summary>
protected List<TaskCompletionSource<dynamic>> tasks = new List<TaskCompletionSource<dynamic>>();
private TaskCompletionSource<dynamic> sideDialogResultTask;
private TaskCompletionSource<dynamic>? sideDialogResultTask;
/// <summary>
/// Opens a dialog with the specified arguments.
@@ -169,7 +173,7 @@ namespace Radzen
/// <param name="parameters">The dialog parameters. Passed as property values of <typeparamref name="T" />.</param>
/// <param name="options">The dialog options.</param>
/// <returns>The value passed as argument to <see cref="Close" />.</returns>
public virtual Task<dynamic> OpenAsync<T>(string title, Dictionary<string, object> parameters = null, DialogOptions options = null) where T : ComponentBase
public virtual Task<dynamic> OpenAsync<T>(string title, Dictionary<string, object>? parameters = null, DialogOptions? options = null) where T : ComponentBase
{
var task = new TaskCompletionSource<dynamic>();
tasks.Add(task);
@@ -188,7 +192,7 @@ namespace Radzen
/// <param name="options">The dialog options.</param>
/// <returns>A task that represents the result passed as an argument to <see cref="Close"/>.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="componentType"/> does not inherit from <see cref="ComponentBase"/>.</exception>
public virtual Task<dynamic> OpenAsync(string title, Type componentType, Dictionary<string, object> parameters = null, DialogOptions options = null)
public virtual Task<dynamic> OpenAsync(string title, Type componentType, Dictionary<string, object>? parameters = null, DialogOptions? options = null)
{
if (!typeof(ComponentBase).IsAssignableFrom(componentType))
{
@@ -199,8 +203,12 @@ namespace Radzen
tasks.Add(task);
var method = GetType().GetMethod(nameof(OpenDialog), BindingFlags.Instance | BindingFlags.NonPublic);
if (method == null)
{
throw new InvalidOperationException("OpenDialog method not found.");
}
method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters, options });
method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters!, options! });
return task.Task;
}
@@ -214,7 +222,7 @@ namespace Radzen
/// <param name="parameters">The dialog parameters. Passed as property values of <typeparamref name="T"/></param>
/// <param name="options">The side dialog options.</param>
/// <returns>A task that completes when the dialog is closed or a new one opened</returns>
public Task<dynamic> OpenSideAsync<T>(string title, Dictionary<string, object> parameters = null, SideDialogOptions options = null)
public Task<dynamic> OpenSideAsync<T>(string title, Dictionary<string, object>? parameters = null, SideDialogOptions? options = null)
where T : ComponentBase
{
CloseSide();
@@ -238,7 +246,7 @@ namespace Radzen
/// <param name="options">The side dialog options.</param>
/// <returns>A task that represents the result passed as an argument to <see cref="CloseSide"/>.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="componentType"/> does not inherit from <see cref="ComponentBase"/>.</exception>
public Task<dynamic> OpenSideAsync(string title, Type componentType, Dictionary<string, object> parameters = null, SideDialogOptions options = null)
public Task<dynamic> OpenSideAsync(string title, Type componentType, Dictionary<string, object>? parameters = null, SideDialogOptions? options = null)
{
if (!typeof(ComponentBase).IsAssignableFrom(componentType))
{
@@ -267,7 +275,7 @@ namespace Radzen
/// <param name="title">The text displayed in the title bar of the side dialog.</param>
/// <param name="parameters">The dialog parameters. Passed as property values of <typeparamref name="T"/></param>
/// <param name="options">The side dialog options.</param>
public void OpenSide<T>(string title, Dictionary<string, object> parameters = null, SideDialogOptions options = null)
public void OpenSide<T>(string title, Dictionary<string, object>? parameters = null, SideDialogOptions? options = null)
where T : ComponentBase
{
CloseSide();
@@ -289,7 +297,7 @@ namespace Radzen
/// <param name="parameters">The dialog parameters, passed as property values of the specified component.</param>
/// <param name="options">The side dialog options.</param>
/// <exception cref="ArgumentException">Thrown if <paramref name="componentType"/> does not inherit from <see cref="ComponentBase"/>.</exception>
public void OpenSide(string title, Type componentType, Dictionary<string, object> parameters = null, SideDialogOptions options = null)
public void OpenSide(string title, Type componentType, Dictionary<string, object>? parameters = null, SideDialogOptions? options = null)
{
if (!typeof(ComponentBase).IsAssignableFrom(componentType))
{
@@ -312,7 +320,7 @@ namespace Radzen
/// Closes the side dialog
/// </summary>
/// <param name="result">The result of the Dialog</param>
public virtual void CloseSide(dynamic result = null)
public virtual void CloseSide(dynamic? result = null)
{
if (sideDialogResultTask?.Task.IsCompleted == false)
{
@@ -322,7 +330,7 @@ namespace Radzen
OnSideClose?.Invoke(result);
}
private TaskCompletionSource sideDialogCloseTask;
private TaskCompletionSource? sideDialogCloseTask;
internal void OnSideCloseComplete()
{
@@ -334,7 +342,7 @@ namespace Radzen
/// Closes the side dialog and waits for the closing animation to finish.
/// </summary>
/// <param name="result">The result of the Dialog</param>
public async Task CloseSideAsync(dynamic result = null)
public async Task CloseSideAsync(dynamic? result = null)
{
sideDialogCloseTask = new TaskCompletionSource();
@@ -351,7 +359,7 @@ namespace Radzen
/// <param name="options">The dialog options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The value passed as argument to <see cref="Close" />.</returns>
public virtual Task<dynamic> OpenAsync(string title, RenderFragment<DialogService> childContent, DialogOptions options = null, CancellationToken? cancellationToken = null)
public virtual Task<dynamic> OpenAsync(string title, RenderFragment<DialogService> childContent, DialogOptions? options = null, CancellationToken? cancellationToken = null)
{
var task = new TaskCompletionSource<dynamic>();
@@ -377,14 +385,14 @@ namespace Radzen
/// <param name="options">The dialog options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The value passed as argument to <see cref="Close" />.</returns>
public virtual Task<dynamic> OpenAsync(RenderFragment<DialogService> titleContent, RenderFragment<DialogService> childContent, DialogOptions options = null, CancellationToken? cancellationToken = null)
public virtual Task<dynamic> OpenAsync(RenderFragment<DialogService> titleContent, RenderFragment<DialogService> childContent, DialogOptions? options = null, CancellationToken? cancellationToken = null)
{
var task = new TaskCompletionSource<dynamic>();
// register the cancellation token
if (cancellationToken.HasValue)
cancellationToken.Value.Register(() => task.TrySetCanceled());
tasks.Add(task);
options ??= new DialogOptions();
@@ -402,7 +410,7 @@ namespace Radzen
/// <param name="title">The text displayed in the title bar of the dialog.</param>
/// <param name="childContent">The content displayed in the dialog.</param>
/// <param name="options">The dialog options.</param>
public virtual void Open(string title, RenderFragment<DialogService> childContent, DialogOptions options = null)
public virtual void Open(string title, RenderFragment<DialogService> childContent, DialogOptions? options = null)
{
options = options ?? new DialogOptions();
@@ -416,12 +424,13 @@ namespace Radzen
/// </summary>
protected List<object> dialogs = new List<object>();
internal void OpenDialog<T>(string title, Dictionary<string, object> parameters, DialogOptions options)
internal void OpenDialog<T>(string? title, Dictionary<string, object>? parameters, DialogOptions? options)
{
dialogs.Add(new object());
// Validate and set default values for the dialog options
options ??= new();
parameters ??= new Dictionary<string, object>();
options.Width = !String.IsNullOrEmpty(options.Width) ? options.Width : "600px";
options.Left = !String.IsNullOrEmpty(options.Left) ? options.Left : "";
options.Top = !String.IsNullOrEmpty(options.Top) ? options.Top : "";
@@ -440,7 +449,7 @@ namespace Radzen
/// </summary>
/// <param name="result">The result.</param>
[JSInvokable("DialogService.Close")]
public virtual void Close(dynamic result = null)
public virtual void Close(dynamic? result = null)
{
var dialog = dialogs.LastOrDefault();
@@ -464,7 +473,7 @@ namespace Radzen
reference?.Dispose();
reference = null;
UriHelper.LocationChanged -= UriHelper_OnLocationChanged;
UriHelper?.LocationChanged -= UriHelper_OnLocationChanged;
}
/// <summary>
@@ -475,7 +484,7 @@ namespace Radzen
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns><c>true</c> if the user clicked the OK button, <c>false</c> otherwise.</returns>
public virtual async Task<bool?> Confirm(string message = "Confirm?", string title = "Confirm", ConfirmOptions options = null, CancellationToken? cancellationToken = null)
public virtual async Task<bool?> Confirm(string message = "Confirm?", string title = "Confirm", ConfirmOptions? options = null, CancellationToken? cancellationToken = null)
{
// Validate and set default values for the dialog options
options ??= new();
@@ -484,8 +493,8 @@ namespace Radzen
options.Width = !String.IsNullOrEmpty(options.Width) ? options.Width : ""; // Width is set to 600px by default by OpenAsync
options.Style = !String.IsNullOrEmpty(options.Style) ? options.Style : "";
options.CssClass = !String.IsNullOrEmpty(options.CssClass) ? $"rz-dialog-confirm {options.CssClass}" : "rz-dialog-confirm";
options.WrapperCssClass = !String.IsNullOrEmpty(options.WrapperCssClass) ? $"rz-dialog-wrapper {options.WrapperCssClass}" : "rz-dialog-wrapper";
options.WrapperCssClass = !String.IsNullOrEmpty(options.WrapperCssClass) ? $"rz-dialog-wrapper {options.WrapperCssClass}" : "rz-dialog-wrapper";
return await OpenAsync(title, ds =>
{
RenderFragment content = b =>
@@ -524,7 +533,7 @@ namespace Radzen
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns><c>true</c> if the user clicked the OK button, <c>false</c> otherwise.</returns>
public virtual async Task<bool?> Confirm(RenderFragment message, string title = "Confirm", ConfirmOptions options = null, CancellationToken? cancellationToken = null)
public virtual async Task<bool?> Confirm(RenderFragment message, string title = "Confirm", ConfirmOptions? options = null, CancellationToken? cancellationToken = null)
{
// Validate and set default values for the dialog options
options ??= new();
@@ -573,12 +582,12 @@ namespace Radzen
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns><c>true</c> if the user clicked the OK button, <c>false</c> otherwise.</returns>
public virtual async Task<bool?> Alert(string message = "", string title = "Message", AlertOptions options = null, CancellationToken? cancellationToken = null)
public virtual async Task<bool?> Alert(string message = "", string title = "Message", AlertOptions? options = null, CancellationToken? cancellationToken = null)
{
// Validate and set default values for the dialog options
options ??= new();
options.OkButtonText = !String.IsNullOrEmpty(options.OkButtonText) ? options.OkButtonText : "Ok";
options.Width = !String.IsNullOrEmpty(options.Width) ? options.Width : "";
options.Width = !String.IsNullOrEmpty(options.Width) ? options.Width : "";
options.Style = !String.IsNullOrEmpty(options.Style) ? options.Style : "";
options.CssClass = !String.IsNullOrEmpty(options.CssClass) ? $"rz-dialog-alert {options.CssClass}" : "rz-dialog-alert";
options.WrapperCssClass = !String.IsNullOrEmpty(options.WrapperCssClass) ? $"rz-dialog-wrapper {options.WrapperCssClass}" : "rz-dialog-wrapper";
@@ -616,7 +625,7 @@ namespace Radzen
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns><c>true</c> if the user clicked the OK button, <c>false</c> otherwise.</returns>
public virtual async Task<bool?> Alert(RenderFragment message, string title = "Message", AlertOptions options = null, CancellationToken? cancellationToken = null)
public virtual async Task<bool?> Alert(RenderFragment message, string title = "Message", AlertOptions? options = null, CancellationToken? cancellationToken = null)
{
// Validate and set default values for the dialog options
options ??= new();
@@ -660,7 +669,7 @@ namespace Radzen
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged" /> event.
@@ -707,12 +716,12 @@ namespace Radzen
}
}
private string width;
private string? width;
/// <summary>
/// Gets or sets the width of the dialog.
/// </summary>
/// <value>The width.</value>
public string Width
public string? Width
{
get => width;
set
@@ -725,12 +734,12 @@ namespace Radzen
}
}
private string height;
private string? height;
/// <summary>
/// Gets or sets the height of the dialog.
/// </summary>
/// <value>The height.</value>
public string Height
public string? Height
{
get => height;
set
@@ -743,12 +752,12 @@ namespace Radzen
}
}
private string style;
private string? style;
/// <summary>
/// Gets or sets the CSS style of the dialog
/// </summary>
/// <value>The style.</value>
public string Style
public string? Style
{
get => style;
set
@@ -761,7 +770,7 @@ namespace Radzen
}
}
private bool closeDialogOnOverlayClick = false;
private bool closeDialogOnOverlayClick;
/// <summary>
/// Gets or sets a value indicating whether the dialog should be closed by clicking the overlay.
/// </summary>
@@ -779,11 +788,11 @@ namespace Radzen
}
}
private string cssClass;
private string? cssClass;
/// <summary>
/// Gets or sets dialog box custom class
/// </summary>
public string CssClass
public string? CssClass
{
get => cssClass;
set
@@ -796,11 +805,11 @@ namespace Radzen
}
}
private string wrapperCssClass;
private string? wrapperCssClass;
/// <summary>
/// Gets or sets the CSS classes added to the dialog's wrapper element.
/// </summary>
public string WrapperCssClass
public string? WrapperCssClass
{
get => wrapperCssClass;
set
@@ -813,11 +822,11 @@ namespace Radzen
}
}
private string contentCssClass;
private string? contentCssClass;
/// <summary>
/// Gets or sets the CSS classes added to the dialog's content element.
/// </summary>
public string ContentCssClass
public string? ContentCssClass
{
get => contentCssClass;
set
@@ -830,7 +839,7 @@ namespace Radzen
}
}
private int closeTabIndex = 0;
private int closeTabIndex;
/// <summary>
/// Gets or sets a value the dialog escape tabindex. Set to <c>0</c> by default.
/// </summary>
@@ -847,13 +856,14 @@ namespace Radzen
}
}
private RenderFragment<DialogService> titleContent;
private RenderFragment<DialogService>? titleContent;
private bool resizable;
/// <summary>
/// Gets or sets the title content.
/// </summary>
/// <value>The title content.</value>
public RenderFragment<DialogService> TitleContent
public RenderFragment<DialogService>? TitleContent
{
get => titleContent;
set
@@ -865,6 +875,23 @@ namespace Radzen
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the dialog is resizable. Set to <c>false</c> by default.
/// </summary>
/// <value><c>true</c> if resizable; otherwise, <c>false</c>.</value>
public bool Resizable
{
get => resizable;
set
{
if (resizable != value)
{
resizable = value;
OnPropertyChanged(nameof(Resizable));
}
}
}
}
/// <summary>
@@ -872,12 +899,12 @@ namespace Radzen
/// </summary>
public class SideDialogOptions : DialogOptionsBase
{
private string title;
private string? title;
/// <summary>
/// The title displayed on the dialog.
/// </summary>
public string Title
public string? Title
{
get => title;
set
@@ -927,7 +954,7 @@ namespace Radzen
}
}
private bool autoFocusFirstElement = false;
private bool autoFocusFirstElement;
/// <summary>
/// Gets or sets a value indicating whether to focus the first focusable HTML element. Set to <c>true</c> by default.
@@ -944,6 +971,70 @@ namespace Radzen
}
}
}
private double minWidth = 300.0;
/// <summary>
/// Gets or sets the minimum width (in pixels) enforced while resizing the side dialog.
/// </summary>
public double MinWidth
{
get => minWidth;
set
{
if (Equals(value, minWidth)) return;
minWidth = value;
OnPropertyChanged(nameof(MinWidth));
}
}
private double minHeight = 200.0;
/// <summary>
/// Gets or sets the minimum height (in pixels) enforced while resizing the side dialog.
/// </summary>
public double MinHeight
{
get => minHeight;
set
{
if (Equals(value, minHeight)) return;
minHeight = value;
OnPropertyChanged(nameof(MinHeight));
}
}
private string resizeBarTitle = "Drag to resize";
/// <summary>
/// Gets or sets the title of the resize bar.
/// </summary>
public string ResizeBarTitle
{
get => resizeBarTitle;
set
{
if (value == resizeBarTitle) return;
resizeBarTitle = value;
OnPropertyChanged(nameof(ResizeBarTitle));
}
}
private string resizeBarAriaLabel = "Resize side dialog";
/// <summary>
/// Gets or sets the aria label of the resize bar.
/// </summary>
public string ResizeBarAriaLabel
{
get => resizeBarAriaLabel;
set
{
if (value == resizeBarAriaLabel) return;
resizeBarAriaLabel = value;
OnPropertyChanged(nameof(ResizeBarAriaLabel));
}
}
}
/// <summary>
@@ -979,45 +1070,26 @@ namespace Radzen
/// </summary>
/// <value>The icon.</value>
public string Icon { get; set; }
public string? Icon { get; set; }
/// <summary>
/// Gets or sets the icon color in Title.
/// </summary>
/// <value>The icon color.</value>
public string IconColor { get; set; }
public string? IconColor { get; set; }
/// <summary>
/// Gets or sets the CSS style of the Icon in Title.
/// </summary>
public string IconStyle { get; set; } = "margin-right: 0.75rem";
private bool resizable;
/// <summary>
/// Gets or sets a value indicating whether the dialog is resizable. Set to <c>false</c> by default.
/// </summary>
/// <value><c>true</c> if resizable; otherwise, <c>false</c>.</value>
public bool Resizable
{
get => resizable;
set
{
if (resizable != value)
{
resizable = value;
OnPropertyChanged(nameof(Resizable));
}
}
}
private Action<Size> resize;
private Action<Size>? resize;
/// <summary>
/// Gets or sets the change.
/// </summary>
/// <value>The change.</value>
public Action<Size> Resize
public Action<Size>? Resize
{
get => resize;
set
@@ -1049,13 +1121,13 @@ namespace Radzen
}
}
private Action<Point> drag;
private Action<Point>? drag;
/// <summary>
/// Gets or sets the change.
/// </summary>
/// <value>The change.</value>
public Action<Point> Drag
public Action<Point>? Drag
{
get => drag;
set
@@ -1068,13 +1140,13 @@ namespace Radzen
}
}
private string left;
private string? left;
/// <summary>
/// Gets or sets the X coordinate of the dialog. Maps to the <c>left</c> CSS attribute.
/// </summary>
/// <value>The left.</value>
public string Left
public string? Left
{
get => left;
set
@@ -1087,13 +1159,13 @@ namespace Radzen
}
}
private string top;
private string? top;
/// <summary>
/// Gets or sets the Y coordinate of the dialog. Maps to the <c>top</c> CSS attribute.
/// </summary>
/// <value>The top.</value>
public string Top
public string? Top
{
get => top;
set
@@ -1106,13 +1178,13 @@ namespace Radzen
}
}
private string bottom;
private string? bottom;
/// <summary>
/// Specifies the <c>bottom</c> CSS attribute.
/// </summary>
/// <value>The bottom.</value>
public string Bottom
public string? Bottom
{
get => bottom;
set
@@ -1125,13 +1197,13 @@ namespace Radzen
}
}
private RenderFragment<DialogService> childContent;
private RenderFragment<DialogService>? childContent;
/// <summary>
/// Gets or sets the child content.
/// </summary>
/// <value>The child content.</value>
public RenderFragment<DialogService> ChildContent
public RenderFragment<DialogService>? ChildContent
{
get => childContent;
set
@@ -1147,7 +1219,7 @@ namespace Radzen
private bool autoFocusFirstElement = true;
/// <summary>
/// Gets or sets a value indicating whether to focus the first focusable HTML element.
/// Gets or sets a value indicating whether to focus the first focusable HTML element.
/// </summary>
/// <value><c>true</c> if the first focusable element is focused; otherwise, <c>false</c>. Default is <c>true</c>.</value>
public bool AutoFocusFirstElement
@@ -1188,12 +1260,12 @@ namespace Radzen
/// </summary>
public class AlertOptions : DialogOptions
{
private string okButtonText;
private string? okButtonText;
/// <summary>
/// Gets or sets the text of the OK button.
/// </summary>
public string OkButtonText
public string? OkButtonText
{
get => okButtonText;
set
@@ -1212,12 +1284,12 @@ namespace Radzen
/// </summary>
public class ConfirmOptions : AlertOptions
{
private string cancelButtonText;
private string? cancelButtonText;
/// <summary>
/// Gets or sets the text of the Cancel button.
/// </summary>
public string CancelButtonText
public string? CancelButtonText
{
get => cancelButtonText;
set
@@ -1236,13 +1308,13 @@ namespace Radzen
/// </summary>
public class Dialog : INotifyPropertyChanged
{
private string title;
private string? title;
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
public string Title
public string? Title
{
get => title;
set
@@ -1255,13 +1327,13 @@ namespace Radzen
}
}
private Type type;
private Type? type;
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public Type Type
public Type? Type
{
get => type;
set
@@ -1274,13 +1346,13 @@ namespace Radzen
}
}
private Dictionary<string, object> parameters;
private Dictionary<string, object>? parameters;
/// <summary>
/// Gets or sets the parameters.
/// </summary>
/// <value>The parameters.</value>
public Dictionary<string, object> Parameters
public Dictionary<string, object>? Parameters
{
get => parameters;
set
@@ -1293,13 +1365,13 @@ namespace Radzen
}
}
private DialogOptions options;
private DialogOptions? options;
/// <summary>
/// Gets or sets the options.
/// </summary>
/// <value>The options.</value>
public DialogOptions Options
public DialogOptions? Options
{
get => options;
set
@@ -1315,7 +1387,7 @@ namespace Radzen
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
@@ -1326,4 +1398,4 @@ namespace Radzen
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

View File

@@ -0,0 +1,85 @@
<Project>
<!--
Common build properties for all projects in the Radzen.Blazor solution.
To use this file:
1. Rename to Directory.Build.props (remove .sample extension)
2. Adjust settings based on your needs
3. Review the analyzer settings in .editorconfig
This file will be automatically imported by all projects in subdirectories.
-->
<PropertyGroup Label="Language Configuration">
<!-- Use latest C# language features -->
<LangVersion>latest</LangVersion>
<!-- Do NOT enable implicit usings - explicit imports preferred for library code -->
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>CA2007</NoWarn>
</PropertyGroup>
<PropertyGroup Label="Code Analysis Configuration">
<!-- Enable .NET code analyzers -->
<AnalysisLevel>latest</AnalysisLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<!-- Run analyzers during build and in IDE -->
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
<!-- Don't enforce code style in build (yet) - just show warnings -->
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<!-- Don't treat warnings as errors (yet) - too many to fix immediately -->
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<!-- Report all analyzer diagnostics -->
<AnalysisMode>All</AnalysisMode>
</PropertyGroup>
<PropertyGroup Label="Build Quality">
<!-- Enable deterministic builds for reproducibility -->
<Deterministic>true</Deterministic>
<!-- Enable deterministic builds in CI/CD -->
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<!-- Embed source files for better debugging -->
<EmbedAllSources>true</EmbedAllSources>
<!--
IMPORTANT:
- NuGet symbol packages (.snupkg) require portable PDB files.
- If DebugType=embedded, there are no standalone PDBs, so the .snupkg ends up effectively empty.
Use portable PDBs when symbols are enabled; otherwise use embedded for local debugging convenience.
-->
<!--
NOTE: Directory.Build.props is imported before project files, so properties like IncludeSymbols
set in a .csproj may not be available yet for Conditions here.
IsPacking *is* set by `dotnet pack`, so use that to switch DebugType for symbol packages.
-->
<DebugType Condition="'$(IsPacking)' == 'true'">portable</DebugType>
<DebugType Condition="'$(IsPacking)' != 'true'">embedded</DebugType>
</PropertyGroup>
<PropertyGroup Label="Demos and Tests Project Configuration" Condition="$(MSBuildProjectName.Contains('Demos')) OR $(MSBuildProjectName.Contains('Tests'))">
<!-- Demo projects and Tests should not be packable -->
<IsPackable>false</IsPackable>
<!-- DISABLE ALL ANALYZERS FOR DEMO PROJECTS AND TESTS -->
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
<RunAnalyzersDuringLiveAnalysis>false</RunAnalyzersDuringLiveAnalysis>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
</PropertyGroup>
<PropertyGroup Label="Performance">
<!-- Optimize startup time -->
<TieredCompilation>true</TieredCompilation>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
</PropertyGroup>
</Project>

View File

@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.JSInterop;
using Radzen.Blazor;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -22,12 +21,12 @@ namespace Radzen
[Parameter]
public int VirtualizationOverscanCount { get; set; }
internal Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<object> virtualize;
internal Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<object>? virtualize;
/// <summary>
/// The Virtualize instance.
/// </summary>
public Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<object> Virtualize
public Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<object>? Virtualize
{
get
{
@@ -35,12 +34,12 @@ namespace Radzen
}
}
List<object> virtualItems;
List<object>? virtualItems;
private async ValueTask<Microsoft.AspNetCore.Components.Web.Virtualization.ItemsProviderResult<object>> LoadItems(Microsoft.AspNetCore.Components.Web.Virtualization.ItemsProviderRequest request)
{
var data = Data != null ? Data.Cast<object>() : Enumerable.Empty<object>();
var view = (LoadData.HasDelegate ? data : View).Cast<object>().AsQueryable();
var view = (LoadData.HasDelegate ? data : View)?.Cast<object>().AsQueryable() ?? Enumerable.Empty<object>().AsQueryable();
var totalItemsCount = LoadData.HasDelegate ? Count : view.Count();
var top = request.Count;
@@ -54,7 +53,7 @@ namespace Radzen
await LoadData.InvokeAsync(new Radzen.LoadDataArgs() { Skip = request.StartIndex, Top = top, Filter = searchText });
}
virtualItems = (LoadData.HasDelegate ? Data : view.Skip(request.StartIndex).Take(top)).Cast<object>().ToList();
virtualItems = (LoadData.HasDelegate ? (Data ?? Enumerable.Empty<object>()) : view.Skip(request.StartIndex).Take(top)).Cast<object>().ToList();
return new Microsoft.AspNetCore.Components.Web.Virtualization.ItemsProviderResult<object>(virtualItems, LoadData.HasDelegate ? Count : totalItemsCount);
}
@@ -105,13 +104,13 @@ namespace Radzen
builder.AddAttribute(1, "ItemsProvider", new Microsoft.AspNetCore.Components.Web.Virtualization.ItemsProviderDelegate<object>(LoadItems));
builder.AddAttribute(2, "ChildContent", (RenderFragment<object>)((context) =>
{
return (RenderFragment)((b) =>
return b =>
{
RenderItem(b, context);
});
};
}));
if (VirtualizationOverscanCount != default(int))
if (VirtualizationOverscanCount != default)
{
builder.AddAttribute(3, "OverscanCount", VirtualizationOverscanCount);
}
@@ -122,9 +121,13 @@ namespace Radzen
}
else
{
foreach (var item in LoadData.HasDelegate ? Data : View)
var items = LoadData.HasDelegate ? Data : View;
if (items != null)
{
RenderItem(builder, item);
foreach (var item in items)
{
RenderItem(builder, item);
}
}
}
});
@@ -160,21 +163,18 @@ namespace Radzen
//
}
System.Collections.Generic.HashSet<object> keys = new System.Collections.Generic.HashSet<object>();
HashSet<object> keys = new HashSet<object>();
internal object GetKey(object item)
internal object? GetKey(object item)
{
var value = GetItemOrValueFromProperty(item, ValueProperty);
var value = GetItemOrValueFromProperty(item, ValueProperty ?? string.Empty);
if (!keys.Contains(value))
if (value != null)
{
keys.Add(value);
return value;
}
else
{
return item;
}
return value;
}
/// <summary>
@@ -182,7 +182,7 @@ namespace Radzen
/// </summary>
/// <value>The header template.</value>
[Parameter]
public RenderFragment HeaderTemplate { get; set; }
public RenderFragment? HeaderTemplate { get; set; }
/// <summary>
/// Gets or sets a value indicating whether filtering is allowed. Set to <c>false</c> by default.
@@ -231,21 +231,21 @@ namespace Radzen
/// </summary>
/// <value>The template.</value>
[Parameter]
public RenderFragment<dynamic> Template { get; set; }
public RenderFragment<dynamic>? Template { get; set; }
/// <summary>
/// Gets or sets the value property.
/// </summary>
/// <value>The value property.</value>
[Parameter]
public string ValueProperty { get; set; }
public string? ValueProperty { get; set; }
/// <summary>
/// Gets or sets the disabled property.
/// </summary>
/// <value>The disabled property.</value>
[Parameter]
public string DisabledProperty { get; set; }
public string? DisabledProperty { get; set; }
/// <summary>
/// Gets or sets the remove chip button title.
@@ -282,7 +282,7 @@ namespace Radzen
/// <summary>
/// The selected item
/// </summary>
protected object selectedItem = null;
protected object? selectedItem;
Type GetItemType(IEnumerable items)
{
var firstType = items.Cast<object>().FirstOrDefault()?.GetType() ?? typeof(object);
@@ -301,7 +301,7 @@ namespace Radzen
/// </summary>
protected virtual async System.Threading.Tasks.Task SelectAll()
{
if (Disabled)
if (Disabled || View == null)
{
return;
}
@@ -316,11 +316,14 @@ namespace Radzen
selectedItems.Clear();
}
if (!string.IsNullOrEmpty(ValueProperty))
if (!string.IsNullOrEmpty(ValueProperty) && Data != null)
{
var elementType = PropertyAccess.GetElementType(Data.GetType());
System.Reflection.PropertyInfo pi = PropertyAccess.GetProperty(elementType, ValueProperty);
internalValue = selectedItems.Select(i => GetItemOrValueFromProperty(i, ValueProperty)).AsQueryable().Cast(pi.PropertyType);
System.Reflection.PropertyInfo? pi = PropertyAccess.GetProperty(elementType, ValueProperty);
if (pi != null)
{
internalValue = selectedItems.Select(i => GetItemOrValueFromProperty(i, ValueProperty)).AsQueryable().Cast(pi.PropertyType);
}
}
else
{
@@ -329,25 +332,34 @@ namespace Radzen
internalValue = selectedItems.AsQueryable().Cast(type);
}
await collectionAssignment.MakeAssignment((IEnumerable)internalValue, ValueChanged);
if (internalValue != null)
{
await collectionAssignment.MakeAssignment((IEnumerable)internalValue, ValueChanged);
}
if (FieldIdentifier.FieldName != null) { EditContext?.NotifyFieldChanged(FieldIdentifier); }
await Change.InvokeAsync(internalValue);
StateHasChanged();
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", GetId());
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", GetId());
}
}
internal bool IsAllSelected()
{
List<object> notDisabledItemsInList = View.Cast<object>().ToList()
List<object> notDisabledItemsInList = View != null ? View.Cast<object>().ToList()
.Where(i => disabledPropertyGetter == null || disabledPropertyGetter(i) as bool? != true)
.ToList();
.ToList() : new List<object>();
if (LoadData.HasDelegate && !string.IsNullOrEmpty(ValueProperty))
{
return View != null && notDisabledItemsInList.Count > 0 && notDisabledItemsInList
.All(i => IsItemSelectedByValue(GetItemOrValueFromProperty(i, ValueProperty)));
.All(i => {
var value = GetItemOrValueFromProperty(i, ValueProperty);
return value != null ? IsItemSelectedByValue(value) : false;
});
}
return View != null && notDisabledItemsInList.Count > 0 && selectedItems.Count == notDisabledItemsInList.Count;
@@ -365,14 +377,17 @@ namespace Radzen
/// <summary>
/// Clears all.
/// </summary>
protected async System.Threading.Tasks.Task ClearAll()
protected async Task ClearAll()
{
if (Disabled)
return;
searchText = null;
await SearchTextChanged.InvokeAsync(searchText);
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, "");
if (JSRuntime != null)
{
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, "");
}
internalValue = collectionAssignment.GetCleared();
selectedItem = null;
@@ -381,7 +396,7 @@ namespace Radzen
selectedIndex = -1;
await ValueChanged.InvokeAsync((T)internalValue);
await ValueChanged.InvokeAsync(internalValue != null ? (T)internalValue : default(T)!);
if (FieldIdentifier.FieldName != null) { EditContext?.NotifyFieldChanged(FieldIdentifier); }
await Change.InvokeAsync(internalValue);
@@ -393,14 +408,14 @@ namespace Radzen
/// <summary>
/// The data
/// </summary>
IEnumerable _data;
IEnumerable? _data;
/// <summary>
/// Gets or sets the data.
/// </summary>
/// <value>The data.</value>
[Parameter]
public override IEnumerable Data
public override IEnumerable? Data
{
get
{
@@ -466,22 +481,26 @@ namespace Radzen
}
}
Func<object, object> GetGetter(string propertyName, Type type)
Func<object, object?> GetGetter(string propertyName, Type type)
{
if (propertyName?.Contains("[") == true)
if (propertyName?.Contains('[', StringComparison.Ordinal) == true)
{
var getter = typeof(PropertyAccess).GetMethod("Getter", [typeof(string), typeof(Type)]);
if (getter == null)
{
return PropertyAccess.Getter<object, object?>(propertyName, type);
}
var getterMethod = getter.MakeGenericMethod([type, typeof(object)]);
return (i) => getterMethod.Invoke(i, [propertyName, type]);
return (i) => getterMethod.Invoke(i, [propertyName, type])!;
}
return PropertyAccess.Getter<object, object>(propertyName, type);
return PropertyAccess.Getter<object, object?>(propertyName ?? string.Empty, type);
}
internal Func<object, object> valuePropertyGetter;
internal Func<object, object> textPropertyGetter;
internal Func<object, object> disabledPropertyGetter;
internal Func<object, object?>? valuePropertyGetter;
internal Func<object, object?>? textPropertyGetter;
internal Func<object, object?>? disabledPropertyGetter;
/// <summary>
/// Gets the item or value from property.
@@ -489,7 +508,7 @@ namespace Radzen
/// <param name="item">The item.</param>
/// <param name="property">The property.</param>
/// <returns>System.Object.</returns>
public object GetItemOrValueFromProperty(object item, string property)
public virtual object? GetItemOrValueFromProperty(object? item, string property)
{
if (item != null)
{
@@ -610,16 +629,19 @@ namespace Radzen
if (Disabled)
return;
await JSRuntime.InvokeVoidAsync("Radzen.togglePopup", Element, PopupID, true);
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", isFilter ? UniqueID : SearchID);
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.togglePopup", Element, PopupID, true);
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", isFilter ? UniqueID : SearchID);
}
if (list != null)
if (list != null && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.selectListItem", search, list, selectedIndex);
}
}
internal bool preventKeydown = false;
internal bool preventKeydown;
/// <summary>
/// Handles the key press.
@@ -627,9 +649,13 @@ namespace Radzen
/// <param name="args">The <see cref="Microsoft.AspNetCore.Components.Web.KeyboardEventArgs"/> instance containing the event data.</param>
/// <param name="isFilter">if set to <c>true</c> [is filter].</param>
/// <param name="shouldSelectOnChange">Should select item on item change with keyboard.</param>
protected virtual async System.Threading.Tasks.Task HandleKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool isFilter = false, bool? shouldSelectOnChange = null)
protected virtual async Task HandleKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool isFilter = false, bool? shouldSelectOnChange = null)
{
if (Disabled || Data == null)
ArgumentNullException.ThrowIfNull(args);
var key = args.Code != null ? args.Code : args.Key;
if (Disabled || Data == null || args == null || key == null)
return;
List<object> items = Enumerable.Empty<object>().ToList();
@@ -649,28 +675,29 @@ namespace Radzen
}
else
{
items = View.Cast<object>().ToList();
items = View != null ? View.Cast<object>().ToList() : Enumerable.Empty<object>().ToList();
}
}
var key = args.Code != null ? args.Code : args.Key;
if (!args.AltKey && (key == "ArrowDown" || key == "ArrowLeft" || key == "ArrowUp" || key == "ArrowRight"))
{
preventKeydown = true;
try
{
selectedIndex = await JSRuntime.InvokeAsync<int>("Radzen.focusListItem", search, list, key == "ArrowDown" || key == "ArrowRight", selectedIndex);
var popupOpened = await JSRuntime.InvokeAsync<bool>("Radzen.popupOpened", PopupID);
if (!Multiple && !popupOpened && shouldSelectOnChange != false)
if (JSRuntime != null)
{
var itemToSelect = items.ElementAtOrDefault(selectedIndex);
if (itemToSelect != null)
selectedIndex = await JSRuntime.InvokeAsync<int>("Radzen.focusListItem", search, list, key == "ArrowDown" || key == "ArrowRight", selectedIndex);
var popupOpened = await JSRuntime.InvokeAsync<bool>("Radzen.popupOpened", PopupID);
if (!Multiple && !popupOpened && shouldSelectOnChange != false)
{
await OnSelectItem(itemToSelect, true);
var itemToSelect = items.ElementAtOrDefault(selectedIndex);
if (itemToSelect != null)
{
await OnSelectItem(itemToSelect, true);
}
}
}
}
@@ -683,32 +710,40 @@ namespace Radzen
{
preventKeydown = true;
if (selectedIndex >= 0 && selectedIndex <= items.Count() - 1)
if (selectedIndex == -1 && items.Count == 1)
{
var itemToSelect = items.ElementAtOrDefault(selectedIndex);
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, $"{searchText}".Trim());
if (itemToSelect != null)
{
await OnSelectItem(itemToSelect, true);
}
selectedIndex = 0;
}
var popupOpened = await JSRuntime.InvokeAsync<bool>("Radzen.popupOpened", PopupID);
if (JSRuntime != null)
{
if (selectedIndex >= 0 && selectedIndex <= items.Count - 1)
{
var itemToSelect = items.ElementAtOrDefault(selectedIndex);
if (!popupOpened)
{
if(key != "Space")
{
await OpenPopup(key, isFilter);
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, $"{searchText}".Trim());
if (itemToSelect != null)
{
await OnSelectItem(itemToSelect, true);
}
}
}
else
{
if (!Multiple && !isFilter)
var popupOpened = await JSRuntime.InvokeAsync<bool>("Radzen.popupOpened", PopupID);
if (!popupOpened)
{
await ClosePopup(key);
if (key != "Space")
{
await OpenPopup(key, isFilter);
}
}
else
{
if (!Multiple && (!isFilter || key != "Space"))
{
await ClosePopup(key);
}
}
}
}
@@ -753,11 +788,20 @@ namespace Radzen
else if (args.Key.Length == 1 && !args.CtrlKey && !args.AltKey && !args.ShiftKey)
{
// searching for element
if (Query == null)
{
return;
}
var query = Query;
var elementType = query.ElementType;
if (elementType == null)
{
return;
}
var filteredItems = (!string.IsNullOrEmpty(TextProperty) ?
Query.Where(TextProperty, args.Key, StringFilterOperator.StartsWith, FilterCaseSensitivity.CaseInsensitive) :
Query)
.Cast(Query.ElementType).Cast<dynamic>().ToList();
query.Where(TextProperty, args.Key, StringFilterOperator.StartsWith, FilterCaseSensitivity.CaseInsensitive) :
query)
.Cast(elementType).Cast<dynamic>().ToList();
if (previousKey != args.Key)
{
@@ -765,7 +809,7 @@ namespace Radzen
itemIndex = -1;
}
itemIndex = itemIndex + 1 >= filteredItems.Count() ? 0 : itemIndex + 1;
itemIndex = itemIndex + 1 >= filteredItems.Count ? 0 : itemIndex + 1;
var itemToSelect = filteredItems.ElementAtOrDefault(itemIndex);
if (itemToSelect is not null)
@@ -782,7 +826,10 @@ namespace Radzen
{
selectedIndex = result.Index;
}
await JSRuntime.InvokeVoidAsync("Radzen.selectListItem", list, list, result.Index);
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.selectListItem", list, list, result.Index);
}
}
}
@@ -792,14 +839,17 @@ namespace Radzen
internal virtual async Task ClosePopup(string key)
{
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
}
}
int itemIndex;
string previousKey;
string? previousKey;
/// <summary>
/// Handles the <see cref="E:FilterKeyPress" /> event.
/// Handles the FilterKeyPress event.
/// </summary>
/// <param name="args">The <see cref="Microsoft.AspNetCore.Components.Web.KeyboardEventArgs"/> instance containing the event data.</param>
protected virtual async System.Threading.Tasks.Task OnFilterKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args)
@@ -849,13 +899,15 @@ namespace Radzen
if (Multiple)
selectedIndex = -1;
await JSRuntime.InvokeAsync<string>("Radzen.repositionPopup", Element, PopupID);
if (JSRuntime != null)
{
await JSRuntime.InvokeAsync<string>("Radzen.repositionPopup", Element, PopupID);
}
await InvokeAsync(() => SearchTextChanged.InvokeAsync(SearchText));
}
/// <summary>
/// Handles the <see cref="E:KeyPress" /> event.
/// Handles the KeyPress event.
/// </summary>
/// <param name="args">The <see cref="Microsoft.AspNetCore.Components.Web.KeyboardEventArgs"/> instance containing the event data.</param>
/// <param name="shouldSelectOnChange">Should select item on item change with keyboard.</param>
@@ -869,13 +921,13 @@ namespace Radzen
/// </summary>
/// <param name="item">The item.</param>
/// <param name="isFromKey">if set to <c>true</c> [is from key].</param>
protected virtual async System.Threading.Tasks.Task OnSelectItem(object item, bool isFromKey = false)
protected virtual async System.Threading.Tasks.Task OnSelectItem(object? item, bool isFromKey = false)
{
await SelectItem(item);
}
/// <summary>
/// Handles the <see cref="E:Filter" /> event.
/// Handles the Filter event.
/// </summary>
/// <param name="args">The <see cref="ChangeEventArgs"/> instance containing the event data.</param>
protected virtual async System.Threading.Tasks.Task OnFilter(ChangeEventArgs args)
@@ -886,6 +938,25 @@ namespace Radzen
}
}
/// <summary>
/// Handles filter input changes (e.g. paste).
/// </summary>
/// <param name="args">The <see cref="ChangeEventArgs"/> instance containing the event data.</param>
protected virtual async Task OnFilterInput(ChangeEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
searchText = $"{args.Value}";
await SearchTextChanged.InvokeAsync(searchText);
if (ResetSelectedIndexOnFilter)
{
selectedIndex = -1;
}
Debounce(DebounceFilter, FilterDelay);
}
/// <summary>
/// Gets the load data arguments.
/// </summary>
@@ -943,7 +1014,7 @@ namespace Radzen
if (valueChanged)
{
internalValue = parameters.GetValueOrDefault<object>(nameof(Value));
if (PreserveCollectionOnSelection)
if (PreserveCollectionOnSelection && internalValue != null)
{
collectionAssignment = new ReferenceGenericCollectionAssignment((T)internalValue);
}
@@ -966,7 +1037,7 @@ namespace Radzen
}
var shouldClose = visibleChanged && !Visible;
if (shouldClose && !firstRender)
if (shouldClose && !firstRender && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.destroyPopup", PopupID);
}
@@ -988,18 +1059,21 @@ namespace Radzen
}
}
SelectItemFromValue(internalValue);
SelectItemFromValue(internalValue);
return base.OnParametersSetAsync();
}
/// <summary>
/// Handles the <see cref="E:Change" /> event.
/// Handles the Change event.
/// </summary>
/// <param name="args">The <see cref="ChangeEventArgs"/> instance containing the event data.</param>
protected void OnChange(ChangeEventArgs args)
{
internalValue = args.Value;
if (args != null)
{
internalValue = args.Value;
}
}
/// <summary>
@@ -1007,17 +1081,18 @@ namespace Radzen
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if the specified item is selected; otherwise, <c>false</c>.</returns>
internal bool IsSelected(object item)
internal bool IsSelected(object? item)
{
if (!string.IsNullOrEmpty(ValueProperty))
{
return IsItemSelectedByValue(GetItemOrValueFromProperty(item, ValueProperty));
var value = GetItemOrValueFromProperty(item, ValueProperty);
return value != null ? IsItemSelectedByValue(value) : false;
}
else
{
if (Multiple)
{
return selectedItems.Contains(item);
return selectedItems.Contains(item!);
}
else
{
@@ -1031,7 +1106,7 @@ namespace Radzen
/// </summary>
/// <value>The selected item.</value>
[Parameter]
public object SelectedItem
public object? SelectedItem
{
get
{
@@ -1069,13 +1144,13 @@ namespace Radzen
/// Gets the view.
/// </summary>
/// <value>The view.</value>
protected override IEnumerable View
protected override IEnumerable? View
{
get
{
if (_view == null && Query != null)
{
_view = Query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity);
_view = Query.Where(TextProperty ?? string.Empty, searchText, FilterOperator, FilterCaseSensitivity);
}
return _view;
@@ -1087,7 +1162,7 @@ namespace Radzen
/// </summary>
void SetSelectedIndexFromSelectedItem()
{
if (selectedItem != null)
if (selectedItem != null && View != null)
{
if (typeof(EnumerableQuery).IsAssignableFrom(View.GetType()))
{
@@ -1109,17 +1184,17 @@ namespace Radzen
/// </summary>
/// <param name="item">The item.</param>
/// <param name="raiseChange">if set to <c>true</c> [raise change].</param>
internal async System.Threading.Tasks.Task SelectItemInternal(object item, bool raiseChange = true)
internal async Task SelectItemInternal(object item, bool raiseChange = true)
{
await SelectItem(item, raiseChange);
}
internal object internalValue;
internal object? internalValue;
/// <summary>
/// Will add/remove selected items from a bound ICollection&lt;T&gt;, instead of replacing it.
/// </summary>
protected bool PreserveCollectionOnSelection = false;
protected bool PreserveCollectionOnSelection;
private DefaultCollectionAssignment collectionAssignment = new();
/// <summary>
@@ -1127,7 +1202,7 @@ namespace Radzen
/// </summary>
/// <param name="item">The item.</param>
/// <param name="raiseChange">if set to <c>true</c> [raise change].</param>
public async System.Threading.Tasks.Task SelectItem(object item, bool raiseChange = true)
public async Task SelectItem(object? item, bool raiseChange = true)
{
if (disabledPropertyGetter != null && item != null && disabledPropertyGetter(item) as bool? == true)
{
@@ -1140,7 +1215,7 @@ namespace Radzen
return;
selectedItem = item;
if (!string.IsNullOrEmpty(ValueProperty))
if (!string.IsNullOrEmpty(ValueProperty) && item != null)
{
internalValue = PropertyAccess.GetItemOrValueFromProperty(item, ValueProperty);
}
@@ -1155,16 +1230,23 @@ namespace Radzen
}
else
{
UpdateSelectedItems(item);
UpdateSelectedItems(item!);
if (!string.IsNullOrEmpty(ValueProperty))
if (!string.IsNullOrEmpty(ValueProperty) && Data != null)
{
var elementType = PropertyAccess.GetElementType(Data.GetType());
System.Reflection.PropertyInfo pi = PropertyAccess.GetProperty(elementType, ValueProperty);
internalValue = selectedItems.Select(i => GetItemOrValueFromProperty(i, ValueProperty)).AsQueryable().Cast(pi.PropertyType);
System.Reflection.PropertyInfo? pi = PropertyAccess.GetProperty(elementType, ValueProperty);
if(pi != null)
{
internalValue = selectedItems.Select(i => GetItemOrValueFromProperty(i, ValueProperty)).AsQueryable().Cast(pi.PropertyType);
}
}
else
{
if (Data == null)
{
return;
}
var query = Data.AsQueryable();
var elementType = query.ElementType;
@@ -1193,11 +1275,14 @@ namespace Radzen
{
if (Multiple)
{
await collectionAssignment.MakeAssignment((IEnumerable)internalValue, ValueChanged);
if (internalValue != null)
{
await collectionAssignment.MakeAssignment((IEnumerable)internalValue, ValueChanged);
}
}
else
{
await ValueChanged.InvokeAsync((T)internalValue);
await ValueChanged.InvokeAsync(internalValue != null ? (T)internalValue : default(T)!);
}
}
@@ -1209,7 +1294,7 @@ namespace Radzen
}
/// <inheritdoc />
public override object GetValue()
public override object? GetValue()
{
return internalValue;
}
@@ -1220,7 +1305,7 @@ namespace Radzen
{
var value = GetItemOrValueFromProperty(item, ValueProperty);
if (!IsItemSelectedByValue(value))
if (value != null && !IsItemSelectedByValue(value))
{
selectedItems.Add(item);
}
@@ -1242,7 +1327,7 @@ namespace Radzen
/// Selects the item from value.
/// </summary>
/// <param name="value">The value.</param>
protected virtual void SelectItemFromValue(object value)
protected virtual void SelectItemFromValue(object? value)
{
var view = LoadData.HasDelegate ? Data : View;
if (value != null && view != null)
@@ -1289,7 +1374,7 @@ namespace Radzen
if (typeof(EnumerableQuery).IsAssignableFrom(view.GetType()))
{
item = view.OfType<object>().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), v)).FirstOrDefault();
item = view.OfType<object>().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), v)).FirstOrDefault()!;
}
else
{
@@ -1302,7 +1387,7 @@ namespace Radzen
}
},
LogicalFilterOperator.And,
FilterCaseSensitivity.Default).FirstOrDefault();
FilterCaseSensitivity.Default).FirstOrDefault()!;
}
if (!object.Equals(item, null) && !selectedItems.AsQueryable().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), v)).Any())
@@ -1327,7 +1412,7 @@ namespace Radzen
/// <summary>
/// For lists of objects, an IEqualityComparer to control how selected items are determined
/// </summary>
[Parameter] public IEqualityComparer<object> ItemComparer { get; set; }
[Parameter] public IEqualityComparer<object>? ItemComparer { get; set; }
internal bool IsItemSelectedByValue(object v)
{
@@ -1350,6 +1435,8 @@ namespace Radzen
base.Dispose();
keys.Clear();
GC.SuppressFinalize(this);
}
private class DefaultCollectionAssignment
@@ -1360,42 +1447,55 @@ namespace Radzen
{
if (object.Equals(selectedItems, null))
{
await valueChanged.InvokeAsync(default(T));
await valueChanged.InvokeAsync(default(T)!);
}
else
{
var list = (IList)Activator.CreateInstance(typeof(T));
foreach (var i in (IEnumerable)selectedItems)
var list = (IList?)Activator.CreateInstance<T>();
if (list != null)
{
list.Add(i);
foreach (var i in (IEnumerable)selectedItems)
{
list.Add(i);
}
await valueChanged.InvokeAsync((T)(object)list);
}
else
{
await valueChanged.InvokeAsync(default(T)!);
}
await valueChanged.InvokeAsync((T)(object)list);
}
}
else if (typeof(T).IsGenericType && typeof(ICollection<>).MakeGenericType(typeof(T).GetGenericArguments()[0]).IsAssignableFrom(typeof(T)))
{
if (object.Equals(selectedItems, null))
{
await valueChanged.InvokeAsync(default(T));
await valueChanged.InvokeAsync(default(T)!);
}
else
{
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(typeof(T).GetGenericArguments()[0]));
var list = (IList?)Activator.CreateInstance(typeof(List<>).MakeGenericType(typeof(T).GetGenericArguments()[0]));
if (list != null)
{
foreach (var i in (IEnumerable)selectedItems)
{
list.Add(i);
}
await valueChanged.InvokeAsync((T)(object)list);
}
else
{
await valueChanged.InvokeAsync(default(T)!);
}
}
}
else
{
await valueChanged.InvokeAsync(object.Equals(selectedItems, null) ? default(T) : (T)selectedItems);
await valueChanged.InvokeAsync(object.Equals(selectedItems, null) ? default(T)! : (T)selectedItems);
}
}
public virtual T GetCleared()
public virtual T? GetCleared()
{
return default(T);
}
@@ -1405,9 +1505,9 @@ namespace Radzen
{
private readonly T originalCollection;
private readonly bool canHandle;
private readonly System.Reflection.MethodInfo clearMethod;
private readonly System.Reflection.MethodInfo addMethod;
private readonly System.Reflection.MethodInfo removeMethod;
private readonly System.Reflection.MethodInfo? clearMethod;
private readonly System.Reflection.MethodInfo? addMethod;
private readonly System.Reflection.MethodInfo? removeMethod;
public ReferenceGenericCollectionAssignment(T originalCollection)
{
@@ -1434,34 +1534,34 @@ namespace Radzen
public override async Task MakeAssignment(IEnumerable selectedItems, EventCallback<T> valueChanged)
{
if (!canHandle)
if (!canHandle || originalCollection == null)
{
// Fallback to default behavior when we can't handle the type
// Fallback to default behavior when we can't handle the type or originalCollection is null
await base.MakeAssignment(selectedItems, valueChanged);
return;
}
var currentItems = selectedItems.Cast<object>().ToHashSet();
var existingItems = ((IEnumerable)originalCollection).Cast<object>().ToHashSet();
var existingItems = (originalCollection as IEnumerable)?.Cast<object>().ToHashSet() ?? new HashSet<object>();
foreach (var i in currentItems)
{
if (!existingItems.Contains(i))
addMethod.Invoke(originalCollection, [i]);
addMethod!.Invoke(originalCollection, [i]);
}
foreach (var i in existingItems)
{
if (!currentItems.Contains(i))
removeMethod.Invoke(originalCollection, [i]);
removeMethod!.Invoke(originalCollection, [i]);
}
await valueChanged.InvokeAsync(originalCollection);
}
public override T GetCleared()
public override T? GetCleared()
{
if (canHandle)
if (canHandle && originalCollection != null)
{
clearMethod.Invoke(originalCollection, null);
clearMethod!.Invoke(originalCollection, null);
return originalCollection;
}
return base.GetCleared();

View File

@@ -10,7 +10,7 @@ public class DropDownBaseItemRenderEventArgs<TValue>
/// <summary>
/// Gets the data item.
/// </summary>
public object Item { get; internal set; }
public object? Item { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether this item is visible.

View File

@@ -10,6 +10,6 @@ public class DropDownItemRenderEventArgs<TValue> : DropDownBaseItemRenderEventAr
/// <summary>
/// Gets the DropDown.
/// </summary>
public RadzenDropDown<TValue> DropDown { get; internal set; }
public RadzenDropDown<TValue>? DropDown { get; internal set; }
}

View File

@@ -9,8 +9,13 @@ namespace System.Linq.Dynamic.Core
/// </summary>
public static class DynamicExtensions
{
static readonly Func<string, Type> typeLocator = type => AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes()).FirstOrDefault(t => t.FullName.Replace("+", ".") == type);
static readonly Func<string, Type?> typeLocator = type => AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.FirstOrDefault(t =>
{
var fullName = t.FullName;
return fullName != null && fullName.Replace("+", ".", StringComparison.Ordinal) == type;
});
/// <summary>
/// Filters using the specified filter descriptors.
@@ -18,28 +23,30 @@ namespace System.Linq.Dynamic.Core
public static IQueryable<T> Where<T>(
this IQueryable<T> source,
string predicate,
object[] parameters = null, object[] otherParameters = null)
object[]? parameters = null, object[]? otherParameters = null)
{
ArgumentNullException.ThrowIfNull(source);
try
{
if (parameters != null && !string.IsNullOrEmpty(predicate))
{
predicate = Regex.Replace(predicate, @"@(\d+)", match =>
{
int index = int.Parse(match.Groups[1].Value);
int index = int.Parse(match.Groups[1].Value, System.Globalization.CultureInfo.InvariantCulture);
if (index >= parameters.Length)
throw new InvalidOperationException($"No parameter provided for {match.Value}");
return ExpressionSerializer.FormatValue(parameters[index]);
return ExpressionSerializer.FormatValue(parameters[index]) ?? string.Empty;
});
}
predicate = (predicate == "true" ? "" : predicate)
.Replace("DateTime(", "DateTime.Parse(")
.Replace("DateTimeOffset(", "DateTimeOffset.Parse(")
.Replace("DateOnly(", "DateOnly.Parse(")
.Replace("Guid(", "Guid.Parse(")
.Replace(" = ", " == ");
predicate = (predicate == "true" ? "" : predicate ?? string.Empty)
.Replace("DateTime(", "DateTime.Parse(", StringComparison.Ordinal)
.Replace("DateTimeOffset(", "DateTimeOffset.Parse(", StringComparison.Ordinal)
.Replace("DateOnly(", "DateOnly.Parse(", StringComparison.Ordinal)
.Replace("Guid(", "Guid.Parse(", StringComparison.Ordinal)
.Replace(" = ", " == ", StringComparison.Ordinal);
return !string.IsNullOrEmpty(predicate) ?
source.Where(ExpressionParser.ParsePredicate<T>(predicate, typeLocator)) : source;
@@ -56,8 +63,10 @@ namespace System.Linq.Dynamic.Core
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
string selector,
object[] parameters = null)
object[]? parameters = null)
{
ArgumentNullException.ThrowIfNull(source);
try
{
return QueryableExtension.OrderBy(source, selector);
@@ -71,8 +80,10 @@ namespace System.Linq.Dynamic.Core
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable Select<T>(this IQueryable<T> source, string selector, object[] parameters = null)
public static IQueryable Select<T>(this IQueryable<T> source, string selector, object[]? parameters = null)
{
ArgumentNullException.ThrowIfNull(source);
if (source.ElementType == typeof(object))
{
var elementType = source.ElementType;
@@ -95,8 +106,10 @@ namespace System.Linq.Dynamic.Core
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable Select(this IQueryable source, string selector, object[] parameters = null)
public static IQueryable Select(this IQueryable source, string selector, object[]? parameters = null)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(selector);
return source.Select(selector, expression => ExpressionParser.ParseLambda(expression, source.ElementType));
}
@@ -109,18 +122,27 @@ namespace System.Linq.Dynamic.Core
return source;
}
if (!selector.Contains("=>"))
if (!selector.Contains("=>", StringComparison.Ordinal))
{
var properties = selector
.Replace("new (", "").Replace(")", "").Replace("new {", "").Replace("}", "").Trim()
.Replace("new (", "", StringComparison.Ordinal).Replace(")", "", StringComparison.Ordinal).Replace("new {", "", StringComparison.Ordinal).Replace("}", "", StringComparison.Ordinal).Trim()
.Split(",", StringSplitOptions.RemoveEmptyEntries);
selector = string.Join(", ", properties
.Select(s => (s.Contains(" as ") ? s.Split(" as ").LastOrDefault().Trim().Replace(".", "_") : s.Trim().Replace(".", "_")) +
" = " + $"it.{s.Split(" as ").FirstOrDefault().Replace(".", "?.").Trim()}"));
.Select(s =>
{
var parts = s.Split(" as ", StringSplitOptions.RemoveEmptyEntries);
var sourcePart = (parts.FirstOrDefault() ?? s).Trim();
var targetPart = (parts.Length > 1 ? parts.Last() : sourcePart).Trim();
var safeTarget = targetPart.Replace(".", "_", StringComparison.Ordinal);
var safeSource = sourcePart.Replace(".", "?.", StringComparison.Ordinal);
return $"{safeTarget} = it.{safeSource}";
}));
}
var lambda = lambdaCreator(selector.Contains("=>") ? selector : $"it => new {{ {selector} }}");
var lambda = lambdaCreator(selector.Contains("=>", StringComparison.Ordinal) ? selector : $"it => new {{ {selector} }}");
return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), nameof(Queryable.Select),
[source.ElementType, lambda.Body.Type], source.Expression, Expression.Quote(lambda)));

View File

@@ -39,7 +39,7 @@ static class DynamicTypeFactory
"set_" + propertyNames[i],
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
null,
[propertyTypes[i]]);
new[] { propertyTypes[i] });
var setterIl = setterMethod.GetILGenerator();
setterIl.Emit(OpCodes.Ldarg_0);
@@ -51,6 +51,10 @@ static class DynamicTypeFactory
}
var dynamicType = typeBuilder.CreateType();
if (dynamicType == null)
{
throw new InvalidOperationException("Failed to create dynamic type.");
}
return dynamicType;
}
}

View File

@@ -28,7 +28,7 @@ public class ExpressionParser
/// </summary>
public static Expression<Func<T, TResult>> ParseLambda<T, TResult>(string expression, Func<string, Type?>? typeResolver = null)
{
var lambda = ParseLambda(expression, typeof(T), typeResolver);
var lambda = ParseLambda<T>(expression, typeResolver);
return Expression.Lambda<Func<T, TResult>>(lambda.Body, lambda.Parameters[0]);
}
@@ -52,7 +52,7 @@ public class ExpressionParser
}
private readonly List<Token> tokens;
private int position = 0;
private int position;
private readonly Func<string, Type?>? typeResolver;
private readonly Stack<ParameterExpression> parameterStack = new();

View File

@@ -30,6 +30,7 @@ public class ExpressionSerializer : ExpressionVisitor
/// <inheritdoc/>
protected override Expression VisitLambda<T>(Expression<T> node)
{
ArgumentNullException.ThrowIfNull(node);
if (node.Parameters.Count > 1)
{
_sb.Append("(");
@@ -52,6 +53,7 @@ public class ExpressionSerializer : ExpressionVisitor
/// <inheritdoc/>
protected override Expression VisitParameter(ParameterExpression node)
{
ArgumentNullException.ThrowIfNull(node);
_sb.Append(node.Name);
return node;
}
@@ -59,10 +61,11 @@ public class ExpressionSerializer : ExpressionVisitor
/// <inheritdoc/>
protected override Expression VisitMember(MemberExpression node)
{
ArgumentNullException.ThrowIfNull(node);
if (node.Expression != null)
{
Visit(node.Expression);
_sb.Append($".{node.Member.Name}");
_sb.Append(CultureInfo.InvariantCulture, $".{node.Member.Name}");
}
else
{
@@ -74,14 +77,15 @@ public class ExpressionSerializer : ExpressionVisitor
/// <inheritdoc/>
protected override Expression VisitMethodCall(MethodCallExpression node)
{
ArgumentNullException.ThrowIfNull(node);
if (node.Method.IsStatic && node.Arguments.Count > 0 &&
(node.Method.DeclaringType == typeof(Enumerable) ||
(node.Method.DeclaringType == typeof(Enumerable) ||
node.Method.DeclaringType == typeof(Queryable)))
{
Visit(node.Arguments[0]);
_sb.Append($".{node.Method.Name}(");
_sb.Append(CultureInfo.InvariantCulture, $".{node.Method.Name}(");
for (int i = 1; i < node.Arguments.Count; i++)
for (int i = 1; i < node.Arguments.Count; i++)
{
if (i > 1) _sb.Append(", ");
@@ -99,7 +103,7 @@ public class ExpressionSerializer : ExpressionVisitor
}
else if (node.Method.IsStatic)
{
_sb.Append($"{node.Method.DeclaringType.Name}.{node.Method.Name}(");
_sb.Append(CultureInfo.InvariantCulture, $"{node.Method.DeclaringType?.Name}.{node.Method.Name}(");
for (int i = 0; i < node.Arguments.Count; i++)
{
@@ -114,11 +118,11 @@ public class ExpressionSerializer : ExpressionVisitor
if (node.Object != null)
{
Visit(node.Object);
_sb.Append($".{node.Method.Name}(");
_sb.Append(CultureInfo.InvariantCulture, $".{node.Method.Name}(");
}
else
{
_sb.Append($"{node.Method.Name}(");
_sb.Append(CultureInfo.InvariantCulture, $"{node.Method.Name}(");
}
for (int i = 0; i < node.Arguments.Count; i++)
@@ -136,17 +140,18 @@ public class ExpressionSerializer : ExpressionVisitor
/// <inheritdoc/>
protected override Expression VisitUnary(UnaryExpression node)
{
ArgumentNullException.ThrowIfNull(node);
if (node.NodeType == ExpressionType.Not)
{
_sb.Append("(!(");
Visit(node.Operand);
_sb.Append("))");
}
else if (node.NodeType == ExpressionType.Convert)
else if (node.NodeType == ExpressionType.Convert)
{
if (node.Operand is IndexExpression indexExpr)
{
_sb.Append($"({node.Type.DisplayName(true).Replace("+",".")})");
_sb.Append(CultureInfo.InvariantCulture, $"({node.Type.DisplayName(true).Replace("+", ".", StringComparison.Ordinal)})");
Visit(indexExpr.Object);
@@ -175,20 +180,18 @@ public class ExpressionSerializer : ExpressionVisitor
/// <inheritdoc/>
protected override Expression VisitConstant(ConstantExpression node)
{
ArgumentNullException.ThrowIfNull(node);
_sb.Append(FormatValue(node.Value));
return node;
}
internal static string FormatValue(object value)
internal static string? FormatValue(object? value)
{
if (value == null)
return "null";
return value switch
{
string s when s == string.Empty => @"""""",
string s when s.Length == 0 => @"""""",
null => "null",
string s => @$"""{s.Replace("\"", "\\\"")}""",
string s => @$"""{s.Replace("\"", "\\\"", StringComparison.Ordinal)}""",
char c => $"'{c}'",
bool b => b.ToString().ToLowerInvariant(),
DateTime dt => FormatDateTime(dt),
@@ -198,7 +201,7 @@ public class ExpressionSerializer : ExpressionVisitor
Guid guid => $"Guid.Parse(\"{guid.ToString("D", CultureInfo.InvariantCulture)}\")",
IEnumerable enumerable when value is not string => FormatEnumerable(enumerable),
_ => value.GetType().IsEnum
? $"({value.GetType().FullName.Replace("+", ".")})" + Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture).ToString()
? $"({value.GetType()?.FullName?.Replace("+", ".", StringComparison.Ordinal)})" + Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture).ToString()
: Convert.ToString(value, CultureInfo.InvariantCulture)
};
}
@@ -214,14 +217,15 @@ public class ExpressionSerializer : ExpressionVisitor
private static string FormatEnumerable(IEnumerable enumerable)
{
var arrayType = enumerable.AsQueryable().ElementType;
var items = enumerable.Cast<object>().Select(FormatValue);
return $"new {(Nullable.GetUnderlyingType(arrayType) != null ? arrayType.DisplayName(true).Replace("+", ".") : "")}[] {{ {string.Join(", ", items)} }}";
return $"new {(Nullable.GetUnderlyingType(arrayType) != null ? arrayType.DisplayName(true).Replace("+", ".", StringComparison.Ordinal) : "")}[] {{ {string.Join(", ", items)} }}";
}
/// <inheritdoc/>
protected override Expression VisitNewArray(NewArrayExpression node)
{
ArgumentNullException.ThrowIfNull(node);
bool needsParentheses = node.NodeType == ExpressionType.NewArrayInit &&
(node.Expressions.Count > 1 || node.Expressions[0].NodeType != ExpressionType.Constant);
@@ -245,9 +249,10 @@ public class ExpressionSerializer : ExpressionVisitor
/// <inheritdoc/>
protected override Expression VisitBinary(BinaryExpression node)
{
ArgumentNullException.ThrowIfNull(node);
_sb.Append("(");
Visit(node.Left);
_sb.Append($" {GetOperator(node.NodeType)} ");
_sb.Append(CultureInfo.InvariantCulture, $" {GetOperator(node.NodeType)} ");
Visit(node.Right);
_sb.Append(")");
return node;
@@ -256,6 +261,7 @@ public class ExpressionSerializer : ExpressionVisitor
/// <inheritdoc/>
protected override Expression VisitConditional(ConditionalExpression node)
{
ArgumentNullException.ThrowIfNull(node);
_sb.Append("(");
Visit(node.Test);
_sb.Append(" ? ");
@@ -290,7 +296,7 @@ public class ExpressionSerializer : ExpressionVisitor
ExpressionType.Coalesce => "??",
_ => throw new NotSupportedException($"Unsupported operator: {type}")
};
}
}
}
/// <summary>
@@ -333,6 +339,7 @@ public static class SharedTypeExtensions
/// <returns>A string representing the type name.</returns>
public static string DisplayName(this Type type, bool fullName = true, bool compilable = false)
{
ArgumentNullException.ThrowIfNull(type);
var stringBuilder = new StringBuilder();
ProcessType(stringBuilder, type, fullName, compilable);
return stringBuilder.ToString();
@@ -443,7 +450,7 @@ public static class SharedTypeExtensions
}
}
var genericPartIndex = type.Name.IndexOf('`');
var genericPartIndex = type.Name.IndexOf('`', StringComparison.Ordinal);
if (genericPartIndex <= 0)
{
builder.Append(type.Name);

View File

@@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
@@ -18,8 +19,9 @@ namespace Radzen.Blazor
/// <summary>
/// Gets enum description.
/// </summary>
public static string GetDisplayDescription(this Enum enumValue, Func<string, string> translationFunction = null)
public static string GetDisplayDescription(this Enum enumValue, Func<string, string>? translationFunction = null)
{
ArgumentNullException.ThrowIfNull(enumValue);
var enumValueAsString = enumValue.ToString();
var val = enumValue.GetType().GetMember(enumValueAsString).FirstOrDefault();
var enumVal = val?.GetCustomAttribute<DisplayAttribute>()?.GetDescription() ?? enumValueAsString;
@@ -33,10 +35,12 @@ namespace Radzen.Blazor
/// <summary>
/// Converts Enum to IEnumerable of Value/Text.
/// </summary>
public static IEnumerable<object> EnumAsKeyValuePair(Type enumType, Func<string, string> translationFunction = null)
public static IEnumerable<object> EnumAsKeyValuePair(Type enumType, Func<string, string>? translationFunction = null)
{
ArgumentNullException.ThrowIfNull(enumType);
Type underlyingType = Enum.GetUnderlyingType(enumType);
return Enum.GetValues(enumType).Cast<Enum>().Distinct().Select(val => new { Value = Convert.ChangeType(val, underlyingType), Text = val.GetDisplayDescription(translationFunction) });
return Enum.GetValues(enumType).Cast<Enum>().Distinct().Select(val => new { Value = Convert.ChangeType(val, underlyingType, CultureInfo.InvariantCulture), Text = val.GetDisplayDescription(translationFunction) });
}
/// <summary>
@@ -67,7 +71,7 @@ namespace Radzen.Blazor
var value = typeValue.ToString();
value = Regex.Replace(value, "([^A-Z])([A-Z])", "$1-$2");
return Regex.Replace(value, "([A-Z]+)([A-Z][^A-Z$])", "$1-$2")
.Trim().ToLower();
.Trim().ToLowerInvariant();
}
}
}

View File

@@ -17,7 +17,7 @@ public class FileInfo : IBrowserFile
//
}
private IBrowserFile source;
private IBrowserFile? source;
/// <summary>
/// Creates FileInfo with IBrowserFile as source.
@@ -28,7 +28,9 @@ public class FileInfo : IBrowserFile
this.source = source;
}
private string name;
private string? name;
private DateTimeOffset? lastModified;
private string? contentType;
/// <summary>
/// Gets the name of the selected file.
@@ -37,7 +39,7 @@ public class FileInfo : IBrowserFile
{
get
{
return name ?? source.Name;
return name ?? source?.Name ?? string.Empty;
}
set
{
@@ -65,17 +67,25 @@ public class FileInfo : IBrowserFile
/// <summary>
/// Gets the IBrowserFile source.
/// </summary>
public IBrowserFile Source => source;
public IBrowserFile? Source => source;
/// <summary>
/// Gets the LastModified.
/// </summary>
public DateTimeOffset LastModified => source.LastModified;
public DateTimeOffset LastModified
{
get => lastModified ?? source?.LastModified ?? default;
set => lastModified = value;
}
/// <summary>
/// Gets the ContentType.
/// </summary>
public string ContentType => source.ContentType;
public string ContentType
{
get => contentType ?? source?.ContentType ?? string.Empty;
set => contentType = value;
}
/// <summary>
/// Open read stream.
@@ -85,6 +95,11 @@ public class FileInfo : IBrowserFile
/// <returns>The stream.</returns>
public System.IO.Stream OpenReadStream(long maxAllowedSize = 512000, CancellationToken cancellationToken = default)
{
if (source == null)
{
throw new InvalidOperationException("No underlying browser file is associated with this FileInfo instance.");
}
return source.OpenReadStream(maxAllowedSize, cancellationToken);
}
}

View File

@@ -12,26 +12,26 @@ public class FilterDescriptor
/// Gets or sets the name of the filtered property.
/// </summary>
/// <value>The property.</value>
public string Property { get; set; }
public string? Property { get; set; }
/// <summary>
/// Gets or sets the property type.
/// </summary>
/// <value>The property type.</value>
[JsonIgnore]
public Type Type { get; set; }
public Type? Type { get; set; }
/// <summary>
/// Gets or sets the name of the filtered property.
/// </summary>
/// <value>The property.</value>
public string FilterProperty { get; set; }
public string? FilterProperty { get; set; }
/// <summary>
/// Gets or sets the value to filter by.
/// </summary>
/// <value>The filter value.</value>
public object FilterValue { get; set; }
public object? FilterValue { get; set; }
/// <summary>
/// Gets or sets the operator which will compare the property value with <see cref="FilterValue" />.
@@ -43,7 +43,7 @@ public class FilterDescriptor
/// Gets or sets a second value to filter by.
/// </summary>
/// <value>The second filter value.</value>
public object SecondFilterValue { get; set; }
public object? SecondFilterValue { get; set; }
/// <summary>
/// Gets or sets the operator which will compare the property value with <see cref="SecondFilterValue" />.

View File

@@ -39,8 +39,30 @@ namespace Radzen
/// AutoCompleteType.</value>
public virtual string AutoCompleteAttribute
{
get => Attributes != null && Attributes.ContainsKey("AutoComplete") && $"{Attributes["AutoComplete"]}".ToLower() == "false" ? DefaultAutoCompleteAttribute :
Attributes != null && Attributes.ContainsKey("AutoComplete") ? Attributes["AutoComplete"] as string ?? AutoCompleteType.GetAutoCompleteValue() : AutoCompleteType.GetAutoCompleteValue();
get
{
if (Attributes != null && Attributes.TryGetValue("AutoComplete", out var value))
{
var v = (object?)value;
var autoCompleteValue = v switch
{
bool boolValue => boolValue
? AutoCompleteType.GetAutoCompleteValue()
: DefaultAutoCompleteAttribute,
string stringValue when bool.TryParse(stringValue, out var boolValue) => boolValue
? AutoCompleteType.GetAutoCompleteValue()
: DefaultAutoCompleteAttribute,
AutoCompleteType typeValue => typeValue.GetAutoCompleteValue(),
_ => value != null ? value.ToString() ?? AutoCompleteType.GetAutoCompleteValue() : AutoCompleteType.GetAutoCompleteValue()
};
return string.Equals(autoCompleteValue, "false", StringComparison.OrdinalIgnoreCase)
? DefaultAutoCompleteAttribute
: autoCompleteValue;
}
return AutoCompleteType.GetAutoCompleteValue();
}
}
/// <summary>
@@ -48,7 +70,7 @@ namespace Radzen
/// </summary>
public virtual string DefaultAutoCompleteAttribute { get; set; } = "off";
object ariaAutoComplete;
object? ariaAutoComplete;
/// <inheritdoc />
public override async Task SetParametersAsync(ParameterView parameters)
@@ -56,10 +78,10 @@ namespace Radzen
parameters = parameters.TryGetValue("aria-autocomplete", out ariaAutoComplete) ?
ParameterView.FromDictionary(parameters
.ToDictionary().Where(i => i.Key != "aria-autocomplete").ToDictionary(i => i.Key, i => i.Value)
.ToDictionary(i => i.Key, i => i.Value))
.ToDictionary(i => i.Key, i => (object?)i.Value))
: parameters;
await base.SetParametersAsync(parameters);
await base.SetParametersAsync(parameters).ConfigureAwait(false);
}
/// <summary>
@@ -70,9 +92,11 @@ namespace Radzen
/// <summary>
/// Gets the aria-autocomplete attribute's string value.
/// </summary>
public virtual string AriaAutoCompleteAttribute
public virtual string? AriaAutoCompleteAttribute
{
get => AutoCompleteAttribute == DefaultAutoCompleteAttribute ? DefaultAriaAutoCompleteAttribute : ariaAutoComplete as string;
get => string.Equals(AutoCompleteAttribute, DefaultAutoCompleteAttribute, StringComparison.Ordinal)
? DefaultAriaAutoCompleteAttribute
: ariaAutoComplete as string;
}
}
@@ -96,7 +120,7 @@ namespace Radzen
/// </summary>
/// <value>The component name.</value>
[Parameter]
public string Name { get; set; }
public string? Name { get; set; }
/// <summary>
/// Gets or sets the tab order index for keyboard navigation.
@@ -112,7 +136,7 @@ namespace Radzen
/// </summary>
/// <value>The placeholder.</value>
[Parameter]
public string Placeholder { get; set; }
public string? Placeholder { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="FormComponent{T}"/> is disabled.
@@ -124,21 +148,21 @@ namespace Radzen
/// <summary>
/// The form
/// </summary>
IRadzenForm _form;
IRadzenForm? _form;
/// <summary>
/// Gets or sets the edit context.
/// </summary>
/// <value>The edit context.</value>
[CascadingParameter]
public EditContext EditContext { get; set; }
public EditContext? EditContext { get; set; }
/// <summary>
/// Gets or sets the form.
/// </summary>
/// <value>The form.</value>
[CascadingParameter]
public IRadzenForm Form
public IRadzenForm? Form
{
get
{
@@ -189,14 +213,14 @@ namespace Radzen
/// <summary>
/// The value
/// </summary>
protected T _value;
protected T? _value;
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
[Parameter]
public virtual T Value
public virtual T? Value
{
get
{
@@ -230,7 +254,7 @@ namespace Radzen
/// </summary>
/// <value>The value expression.</value>
[Parameter]
public Expression<Func<T>> ValueExpression { get; set; }
public Expression<Func<T>>? ValueExpression { get; set; }
/// <summary>
/// Sets the parameters asynchronous.
/// </summary>
@@ -262,7 +286,7 @@ namespace Radzen
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="ValidationStateChangedEventArgs"/> instance containing the event data.</param>
private void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
private void ValidationStateChanged(object? sender, ValidationStateChangedEventArgs e)
{
StateHasChanged();
}
@@ -280,19 +304,21 @@ namespace Radzen
}
Form?.RemoveComponent(this);
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets the value.
/// </summary>
/// <returns>System.Object.</returns>
public object GetValue()
public object? GetValue()
{
return Value;
}
/// <summary>
/// Handles the <see cref="E:ContextMenu" /> event.
/// Handles the ContextMenu event.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <returns>Task.</returns>
@@ -318,10 +344,10 @@ namespace Radzen
/// <summary> Provides support for RadzenFormField integration. </summary>
[CascadingParameter]
public IFormFieldContext FormFieldContext { get; set; }
public IFormFieldContext? FormFieldContext { get; set; }
/// <summary> Gets the current placeholder. Returns empty string if this component is inside a RadzenFormField.</summary>
protected string CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder;
protected string? CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder;
/// <inheritdoc/>
public virtual async ValueTask FocusAsync()

View File

@@ -10,6 +10,6 @@ public class FormInvalidSubmitEventArgs
/// <summary>
/// Gets the validation errors.
/// </summary>
public IEnumerable<string> Errors { get; set; }
public IEnumerable<string>? Errors { get; set; }
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
@@ -18,7 +19,7 @@ namespace Radzen.Blazor
/// </summary>
/// <value>The child content.</value>
[Parameter]
public RenderFragment ChildContent
public RenderFragment? ChildContent
{
get; set;
}
@@ -38,7 +39,7 @@ namespace Radzen.Blazor
/// <summary>
/// The width and height are set
/// </summary>
bool widthAndHeightAreSet = false;
bool widthAndHeightAreSet;
/// <summary>
/// The first render
/// </summary>
@@ -57,7 +58,7 @@ namespace Radzen.Blazor
{
visibleChanged = false;
if (Visible)
if (Visible && JSRuntime != null)
{
var rect = await JSRuntime.InvokeAsync<Rect>("Radzen.createGauge", Element, Reference);
@@ -117,23 +118,20 @@ namespace Radzen.Blazor
double width = 0;
double height = 0;
if (CurrentStyle.ContainsKey("height"))
if (CurrentStyle.TryGetValue("height", out var pixelHeight))
{
var pixelHeight = CurrentStyle["height"];
if (pixelHeight.EndsWith("px"))
if (pixelHeight.EndsWith("px", StringComparison.Ordinal))
{
height = Convert.ToDouble(pixelHeight.TrimEnd("px".ToCharArray()));
height = Convert.ToDouble(pixelHeight.TrimEnd("px".ToCharArray()), CultureInfo.InvariantCulture);
}
}
if (CurrentStyle.ContainsKey("width"))
if (CurrentStyle.TryGetValue("width", out var pixelWidth))
{
var pixelWidth = CurrentStyle["width"];
if (pixelWidth.EndsWith("px"))
if (pixelWidth.EndsWith("px", StringComparison.Ordinal))
{
width = Convert.ToDouble(pixelWidth.TrimEnd("px".ToCharArray()));
width = Convert.ToDouble(pixelWidth.TrimEnd("px".ToCharArray()), CultureInfo.InvariantCulture);
}
}
@@ -149,7 +147,7 @@ namespace Radzen.Blazor
/// <summary>
/// The visible changed
/// </summary>
private bool visibleChanged = false;
private bool visibleChanged;
/// <summary>
/// Set parameters as an asynchronous operation.
@@ -166,7 +164,7 @@ namespace Radzen.Blazor
if (visibleChanged && !firstRender)
{
if (Visible == false)
if (Visible == false && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.destroyGauge", Element);
}
@@ -185,10 +183,12 @@ namespace Radzen.Blazor
{
base.Dispose();
if (Visible)
if (Visible && JSRuntime != null)
{
JSRuntime.InvokeVoid("Radzen.destroyGauge", Element);
}
GC.SuppressFinalize(this);
}
/// <summary>

View File

@@ -8,6 +8,6 @@ public class GoogleMapClickEventArgs
/// <summary>
/// The position which represents the clicked map location.
/// </summary>
public GoogleMapPosition Position { get; set; }
public GoogleMapPosition? Position { get; set; }
}

View File

@@ -20,7 +20,7 @@ public class GoogleMapPosition : IEquatable<GoogleMapPosition>
public double Lng { get; set; }
/// <inheritdoc />
public bool Equals(GoogleMapPosition other)
public bool Equals(GoogleMapPosition? other)
{
if (other != null)
{
@@ -31,7 +31,7 @@ public class GoogleMapPosition : IEquatable<GoogleMapPosition>
}
/// <inheritdoc />
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return this.Equals(obj as GoogleMapPosition);
}

View File

@@ -9,13 +9,13 @@ public class Group
/// Gets or sets the data.
/// </summary>
/// <value>The data.</value>
public GroupResult Data { get; set; }
public GroupResult Data { get; set; } = new GroupResult();
/// <summary>
/// Gets or sets the group descriptor.
/// </summary>
/// <value>The group descriptor.</value>
public GroupDescriptor GroupDescriptor { get; set; }
public GroupDescriptor? GroupDescriptor { get; set; }
/// <summary>
/// Gets or sets the level.

View File

@@ -9,7 +9,7 @@ public class GroupDescriptor
/// Gets or sets the property to group by.
/// </summary>
/// <value>The property.</value>
public string Property { get; set; }
public string Property { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the sort order.
@@ -21,13 +21,13 @@ public class GroupDescriptor
/// Gets or sets the title displayed in the group.
/// </summary>
/// <value>The title.</value>
public string Title { get; set; }
public string Title { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the format string used to display the key in the group.
/// </summary>
/// <value>The format string.</value>
public string FormatString { get; set; }
public string FormatString { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to show the footer for the group.

View File

@@ -22,12 +22,12 @@ public class GroupResult
/// <summary>
/// The resulting elements in the group.
/// </summary>
public IEnumerable Items { get; internal set; }
public IEnumerable? Items { get; internal set; }
/// <summary>
/// The resulting subgroups in the group.
/// </summary>
public IEnumerable<GroupResult> Subgroups { get; internal set; }
public IEnumerable<GroupResult>? Subgroups { get; internal set; }
/// <summary>
/// Returns a <see cref="System.String" /> showing the key of the group and the number of items in the group.

View File

@@ -15,7 +15,13 @@ public class GroupRowRenderEventArgs
/// <summary>
/// Gets the data item which the current row represents.
/// </summary>
public Group Group { get; internal set; }
public Group? Group { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether this group row is expandable.
/// </summary>
/// <value><c>true</c> if expandable; otherwise, <c>false</c>.</value>
public bool Expandable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this group row is expanded.

View File

@@ -24,6 +24,6 @@ public class HtmlEditorExecuteEventArgs
/// <summary>
/// Gets the name of the command which RadzenHtmlEditor is executing.
/// </summary>
public string CommandName { get; set; }
public string? CommandName { get; set; }
}

View File

@@ -9,6 +9,6 @@ public class HtmlEditorPasteEventArgs
/// Gets or sets the HTML content that is pasted in RadzenHtmlEditor. Use the setter to filter unwanted markup from the pasted value.
/// </summary>
/// <value>The HTML.</value>
public string Html { get; set; }
public string? Html { get; set; }
}

View File

@@ -22,8 +22,9 @@ namespace Radzen
/// <exception cref="Exception"></exception>
/// <exception cref="Exception">Unable to parse the response.</exception>
/// <exception cref="Exception"></exception>
public static async Task<T> ReadAsync<T>(this HttpResponseMessage response)
public static async Task<T?> ReadAsync<T>(this HttpResponseMessage response)
{
ArgumentNullException.ThrowIfNull(response);
try
{
response.EnsureSuccessStatusCode();
@@ -42,7 +43,8 @@ namespace Radzen
var responseAsString = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(responseAsString))
{
if (response.Content.Headers.ContentType.MediaType == "application/json")
var mediaType = response.Content.Headers.ContentType?.MediaType;
if (string.Equals(mediaType, "application/json", StringComparison.OrdinalIgnoreCase))
{
JsonDocument json;
try
@@ -51,7 +53,7 @@ namespace Radzen
}
catch
{
throw new Exception("Unable to parse the response.");
throw new InvalidOperationException("Unable to parse the response.");
}
JsonElement error;
@@ -60,13 +62,14 @@ namespace Radzen
JsonElement message;
if (error.TryGetProperty("message", out message))
{
throw new Exception(message.GetString());
var messageText = message.GetString();
throw new InvalidOperationException(messageText ?? "An error occurred.");
}
}
}
else
{
XElement error = null;
XElement? error = null;
try
{
var xml = XDocument.Parse(responseAsString);
@@ -82,12 +85,12 @@ namespace Radzen
}
catch
{
throw new Exception("Unable to parse the response.");
throw new InvalidOperationException("Unable to parse the response.");
}
if (error != null)
{
throw new Exception(error.Value);
throw new InvalidOperationException(error.Value);
}
}
}

View File

@@ -23,14 +23,14 @@ public interface IAIChatService
/// <param name="apiKey">Optional API key to override the configured API key.</param>
/// <param name="apiKeyHeader">Optional API key header name to override the configured header.</param>
/// <returns>An async enumerable that yields streaming response chunks from the AI model.</returns>
IAsyncEnumerable<string> GetCompletionsAsync(string userInput, string sessionId = null, CancellationToken cancellationToken = default, string model = null, string systemPrompt = null, double? temperature = null, int? maxTokens = null, string endpoint = null, string proxy = null, string apiKey = null, string apiKeyHeader = null);
IAsyncEnumerable<string> GetCompletionsAsync(string userInput, string? sessionId = null, CancellationToken cancellationToken = default, string? model = null, string? systemPrompt = null, double? temperature = null, int? maxTokens = null, string? endpoint = null, string? proxy = null, string? apiKey = null, string? apiKeyHeader = null);
/// <summary>
/// Gets or creates a conversation session.
/// </summary>
/// <param name="sessionId">The session ID. If null, a new session will be created.</param>
/// <returns>The conversation session.</returns>
ConversationSession GetOrCreateSession(string sessionId = null);
ConversationSession GetOrCreateSession(string? sessionId = null);
/// <summary>
/// Clears the conversation history for a specific session.

View File

@@ -25,13 +25,13 @@ public interface IRadzenFormComponent
/// Gets the value of the component.
/// </summary>
/// <returns>the value of the component - for example the text of RadzenTextBox.</returns>
object GetValue();
object? GetValue();
/// <summary>
/// Gets or sets the name of the component.
/// </summary>
/// <value>The name.</value>
string Name { get; set; }
string? Name { get; set; }
/// <summary>
/// Gets the field identifier.
@@ -58,6 +58,6 @@ public interface IRadzenFormComponent
/// <summary>
/// Sets the FormFieldContext of the component
/// </summary>
IFormFieldContext FormFieldContext { get; }
IFormFieldContext? FormFieldContext { get; }
}

View File

@@ -0,0 +1,22 @@
using System.Threading.Tasks;
namespace Radzen.Blazor
{
/// <summary>
/// Non-generic contract for <see cref="RadzenSpiderChart"/> used by configuration components
/// like <see cref="RadzenSpiderLegend"/> without relying on reflection (important for trimming/AOT).
/// </summary>
public interface IRadzenSpiderChart
{
/// <summary>
/// Gets or sets the legend configuration for the chart.
/// </summary>
RadzenSpiderLegend Legend { get; set; }
/// <summary>
/// Requests the chart to refresh its rendering.
/// </summary>
Task Refresh();
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
namespace Radzen.Blazor
{
/// <summary>
/// Non-generic contract for spider series so <see cref="RadzenSpiderChart"/> can be non-generic.
/// </summary>
internal interface IRadzenSpiderSeries
{
int Index { get; set; }
string Title { get; }
bool IsVisible { get; set; }
bool MarkersVisible { get; }
double MarkerSize { get; }
double StrokeWidth { get; }
IEnumerable<string> GetCategories();
IEnumerable<double> GetValues();
double GetValue(string category);
object? GetData(string category);
string FormatValue(double value);
double MeasureLegend();
RenderFragment RenderLegendItem();
void ForceUpdate();
}
}

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2018-2025 Radzen Ltd
Copyright (c) 2018-2026 Radzen Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -10,13 +10,13 @@ public class LegendClickEventArgs
/// <summary>
/// Gets the data at the clicked location.
/// </summary>
public object Data { get; set; }
public object? Data { get; set; }
/// <summary>
/// Gets the title of the clicked series. Determined by <see cref="CartesianSeries{TItem}.Title" />.
/// </summary>
/// <value>The title.</value>
public string Title { get; set; }
public string? Title { get; set; }
/// <summary>
/// Gets the visibility of the clicked legend. Determined by <see cref="CartesianSeries{TItem}.IsVisible" />. Always visible for Pie Charts.

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.Linq;
using System.Collections.Generic;
@@ -20,10 +21,10 @@ namespace Radzen.Blazor
if (String.IsNullOrEmpty(format))
{
return value.ToString();
return value.ToString() ?? String.Empty;
}
return string.Format(format, value);
return string.Format(CultureInfo.InvariantCulture, format, value);
}
public override double Scale(double value, bool padding)
@@ -96,7 +97,7 @@ namespace Radzen.Blazor
{
if (Step is IConvertible)
{
step = Convert.ToDouble(Step);
step = Convert.ToDouble(Step, CultureInfo.InvariantCulture);
}
}
@@ -124,7 +125,7 @@ namespace Radzen.Blazor
if (step == 0)
{
throw new ArgumentOutOfRangeException("Step must be non-zero");
throw new ArgumentOutOfRangeException(nameof(distance), "Step must be non-zero");
}
return (start, end, Math.Abs(step));

View File

@@ -10,6 +10,6 @@ public class ListBoxItemRenderEventArgs<TValue> : DropDownBaseItemRenderEventArg
/// <summary>
/// Gets the ListBox.
/// </summary>
public RadzenListBox<TValue> ListBox { get; internal set; }
public RadzenListBox<TValue>? ListBox { get; internal set; }
}

View File

@@ -23,11 +23,11 @@ public class LoadDataArgs
/// <summary>
/// Gets the sort expression as a string.
/// </summary>
public string OrderBy { get; set; }
public string? OrderBy { get; set; }
private Func<string> getFilter;
private Func<string>? getFilter;
internal Func<string> GetFilter
internal Func<string>? GetFilter
{
get
{
@@ -40,19 +40,19 @@ public class LoadDataArgs
}
}
private string filter;
private string? filter;
/// <summary>
/// Gets the filter expression as a string.
/// </summary>
/// <value>The filter.</value>
public string Filter
public string? Filter
{
get
{
if (filter == null && GetFilter != null)
{
filter = GetFilter();
filter = GetFilter?.Invoke();
}
return filter;
}
@@ -65,12 +65,12 @@ public class LoadDataArgs
/// <summary>
/// Gets the filter expression as a collection of filter descriptors.
/// </summary>
public IEnumerable<FilterDescriptor> Filters { get; set; }
public IEnumerable<FilterDescriptor>? Filters { get; set; }
/// <summary>
/// Gets the sort expression as a collection of sort descriptors.
/// </summary>
/// <value>The sorts.</value>
public IEnumerable<SortDescriptor> Sorts { get; set; }
public IEnumerable<SortDescriptor>? Sorts { get; set; }
}

View File

@@ -8,12 +8,12 @@ public class LoginArgs
/// <summary>
/// Gets or sets the username.
/// </summary>
public string Username { get; set; }
public string? Username { get; set; }
/// <summary>
/// Gets or sets the password.
/// </summary>
public string Password { get; set; }
public string? Password { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the user wants to remember their credentials.

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.Linq;
namespace Radzen;
@@ -59,6 +60,8 @@ public class MD5
/// <returns>The MD5 hash as a string.</returns>
public static string Calculate(byte[] input)
{
ArgumentNullException.ThrowIfNull(input);
uint a0 = 0x67452301; // A
uint b0 = 0xefcdab89; // B
uint c0 = 0x98badcfe; // C
@@ -124,7 +127,7 @@ public class MD5
private static string GetByteString(uint x)
{
return String.Join("", BitConverter.GetBytes(x).Select(y => y.ToString("x2")));
return String.Join("", BitConverter.GetBytes(x).Select(y => y.ToString("x2", CultureInfo.InvariantCulture)));
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
@@ -194,7 +195,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren
/// <inheritdoc />
public override void VisitLink(Link link)
{
if (link.Destination.StartsWith("#"))
if (link.Destination?.StartsWith('#') == true)
{
builder.OpenComponent<RadzenAnchor>(0);
builder.AddAttribute(0, "href", link.Destination);
@@ -210,7 +211,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren
{
builder.OpenComponent<RadzenLink>(0);
if (!HtmlSanitizer.IsDangerousUrl(link.Destination))
if (!string.IsNullOrEmpty(link.Destination) && !HtmlSanitizer.IsDangerousUrl(link.Destination))
{
builder.AddAttribute(1, nameof(RadzenLink.Path), link.Destination);
}
@@ -230,7 +231,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren
{
builder.OpenComponent<RadzenImage>(0);
if (!HtmlSanitizer.IsDangerousUrl(image.Destination))
if (!string.IsNullOrEmpty(image.Destination) && !HtmlSanitizer.IsDangerousUrl(image.Destination))
{
builder.AddAttribute(1, nameof(RadzenImage.Path), image.Destination);
}
@@ -291,7 +292,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren
if (match.Success)
{
var markerId = Convert.ToInt32(match.Groups[1].Value);
var markerId = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
outlet(builder, markerId);
}
else if (options.AllowHtml)
@@ -349,11 +350,12 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren
/// <inheritdoc />
public override void VisitHtmlInline(HtmlInline htmlInline)
{
if (htmlInline.Value == null) return;
var match = OutletRegex.Match(htmlInline.Value);
if (match.Success)
{
var markerId = Convert.ToInt32(match.Groups[1].Value);
var markerId = Convert.ToInt32(match.Groups[1].Value, CultureInfo.InvariantCulture);
outlet(builder, markerId);
return;
}
@@ -406,7 +408,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren
}
}
if (html.EndsWith("/>") || IsVoidElement(tagName))
if (html.EndsWith("/>", StringComparison.Ordinal) || IsVoidElement(tagName))
{
builder.CloseElement();
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace Radzen.Blazor.Markdown;
@@ -29,6 +30,7 @@ public abstract class BlockContainer : Block
/// <returns>The added block.</returns>
public virtual T Add<T>(T block) where T : Block
{
ArgumentNullException.ThrowIfNull(block);
children.Add(block);
block.Parent = this;
@@ -43,6 +45,8 @@ public abstract class BlockContainer : Block
/// <param name="target">The block to replace with.</param>
public void Replace(Block source, Block target)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(target);
var index = children.IndexOf(source);
if (index >= 0)

View File

@@ -1,3 +1,5 @@
using System;
namespace Radzen.Blazor.Markdown;
@@ -9,6 +11,7 @@ public class BlockQuote : BlockContainer
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitBlockQuote(this);
}

View File

@@ -1,3 +1,5 @@
using System;
namespace Radzen.Blazor.Markdown;
/// <summary>
@@ -14,6 +16,7 @@ public class Code(string value) : Inline
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitCode(this);
}
}

View File

@@ -1,3 +1,5 @@
using System;
namespace Radzen.Blazor.Markdown;
/// <summary>
@@ -17,6 +19,7 @@ public class Document : BlockContainer
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitDocument(this);
}

View File

@@ -1,3 +1,5 @@
using System;
namespace Radzen.Blazor.Markdown;
/// <summary>
@@ -8,6 +10,7 @@ public class Emphasis : InlineContainer
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitEmphasis(this);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Text.RegularExpressions;
namespace Radzen.Blazor.Markdown;
@@ -10,17 +11,18 @@ public class FencedCodeBlock : Leaf
/// <summary>
/// The delimiter used to start and end the code block.
/// </summary>
public string Delimiter { get; private set; }
public string? Delimiter { get; private set; }
internal int Indent { get; private set; }
/// <summary>
/// The info string of the code block. This is the first line of the code block and is used to specify the language of the code block.
/// </summary>
public string Info { get; private set; }
public string? Info { get; private set; }
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitFencedCodeBlock(this);
}
@@ -29,7 +31,7 @@ public class FencedCodeBlock : Leaf
base.Close(parser);
// first line becomes info string
var newlinePos = Value.IndexOf('\n');
var newlinePos = Value.IndexOf('\n', StringComparison.Ordinal);
var firstLine = Value[..newlinePos];
Info = firstLine.Trim();
Value = Value[(newlinePos + 1)..];
@@ -43,7 +45,7 @@ public class FencedCodeBlock : Leaf
var match = ClosingFenceRegex.Match(line);
if (indent <= 3 && parser.PeekNonSpace() == Delimiter[0] && match.Success && match.Length >= Delimiter.Length)
if (indent <= 3 && Delimiter != null && parser.PeekNonSpace() == Delimiter[0] && match.Success && match.Length >= Delimiter.Length)
{
// closing fence - we're at end of line, so we can return
parser.LastLineLength = parser.Offset + indent + match.Length;

View File

@@ -1,4 +1,6 @@
using System;
namespace Radzen.Blazor.Markdown;
/// <summary>
@@ -14,6 +16,7 @@ public abstract class Heading : Leaf
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitHeading(this);
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Text.RegularExpressions;
namespace Radzen.Blazor.Markdown;
@@ -12,6 +13,7 @@ public class HtmlBlock : Leaf
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitHtmlBlock(this);
}

View File

@@ -1,3 +1,5 @@
using System;
namespace Radzen.Blazor.Markdown;
/// <summary>
@@ -8,12 +10,13 @@ public class HtmlInline : Inline
/// <summary>
/// Gets or sets the HTML element value.
/// </summary>
public string Value { get; set; }
public string? Value { get; set; }
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitHtmlInline(this);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
@@ -78,7 +79,7 @@ class HtmlSanitizer
var safeAttributes = Regex.Replace(attributes, @"(\w+)\s*=\s*(""[^""]*""|'[^']*'|[^\s>]+)", SanitizeAttribute);
return $"<{(match.Value.StartsWith("</") ? "/" : "")}{tag}{safeAttributes}>";
return $"<{(match.Value.StartsWith("</", StringComparison.Ordinal) ? "/" : "")}{tag}{safeAttributes}>";
}
private string SanitizeAttribute(Match match)
@@ -91,6 +92,11 @@ class HtmlSanitizer
return string.Empty;
}
if ((value.StartsWith('\'') && value.EndsWith('\'')) || (value.StartsWith('"') && value.EndsWith('"')))
{
value = value[1..^1];
}
if (name == "style")
{
var decoded = HtmlDecode(value).ToLowerInvariant();
@@ -106,11 +112,6 @@ class HtmlSanitizer
return string.Empty;
}
if ((value.StartsWith('\'') && value.EndsWith('\'')) || (value.StartsWith('"') && value.EndsWith('"')))
{
value = value[1..^1];
}
return $" {name}=\"{value}\"";
}
@@ -128,9 +129,9 @@ class HtmlSanitizer
var decoded = HtmlDecode(value).Trim().ToLowerInvariant();
return decoded.StartsWith("javascript:") ||
decoded.StartsWith("vbscript:") ||
decoded.StartsWith("data:text/html") ||
decoded.Contains("expression(");
return decoded.StartsWith("javascript:", StringComparison.Ordinal) ||
decoded.StartsWith("vbscript:", StringComparison.Ordinal) ||
decoded.StartsWith("data:text/html", StringComparison.Ordinal) ||
decoded.Contains("expression(", StringComparison.Ordinal);
}
}

View File

@@ -1,3 +1,5 @@
using System;
namespace Radzen.Blazor.Markdown;
/// <summary>
@@ -8,16 +10,17 @@ public class Image : InlineContainer
/// <summary>
/// Gets or sets the destination (URL) of the image.
/// </summary>
public string Destination { get; set; }
public string? Destination { get; set; }
/// <summary>
/// Gets or sets the alternative text of the image.
/// </summary>
public string Title { get; set; }
public string? Title { get; set; }
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitImage(this);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
@@ -11,6 +12,7 @@ public class IndentedCodeBlock : Leaf
/// <inheritdoc />
public override void Accept(INodeVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
visitor.VisitIndentedCodeBlock(this);
}

Some files were not shown because too many files have changed in this diff Show More