Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ jobs:
- run: dotnet test -c Release -f ${{ matrix.target-framework }} --no-build
working-directory: tests/HerePlatform.Blazor.Tests

js-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: node --version
- run: node --test tests/HerePlatform.Blazor.Tests.Js/*.test.mjs

restclient-tests:
runs-on: ubuntu-latest
strategy:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-blazor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
package: blazor
csproj-path: HerePlatform.Blazor/HerePlatform.Blazor.csproj
test-directory: tests/HerePlatform.Blazor.Tests
js-test-directory: tests/HerePlatform.Blazor.Tests.Js
title-prefix: HerePlatform.NET.Blazor
secrets:
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
8 changes: 8 additions & 0 deletions .github/workflows/release-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ on:
description: 'Display name for GitHub Release title'
required: true
type: string
js-test-directory:
description: 'Path to the node:test directory (*.test.mjs); empty to skip'
required: false
type: string
default: ''
secrets:
nuget-api-key:
required: true
Expand All @@ -42,6 +47,9 @@ jobs:
- run: dotnet build -c Release --no-restore
- run: dotnet test -c Release --no-build
working-directory: ${{ inputs.test-directory }}
- name: Run JS tests
if: inputs.js-test-directory != ''
run: node --test ${{ inputs.js-test-directory }}/*.test.mjs
- name: Extract version from tag
run: |
if [[ ! "$GITHUB_REF_NAME" =~ ^${{ inputs.package }}/v ]]; then
Expand Down
23 changes: 19 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### [Unreleased]

### [1.4.1] - 2026-09-24

#### Changed

- `AutosuggestInputContext.PreventDefaultKeyDown`: the `[Obsolete]` message now states why the property is no
longer needed and what to do instead — remove `@onkeydown:preventDefault` from custom templates and only splat
`@attributes="context.InputAttributes"`
- Changelog and component docs now mark 1.3.0 as a known regression and point 1.3.0 users to 1.3.1 or later

### [1.4.0] - 2026-09-20

#### Added
Expand All @@ -30,7 +39,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
per-element flag and cannot be limited to single keys. The browser default is now suppressed
key-selectively by a `keydown` listener on the input, and only for `Enter` (implicit form submit) and
`ArrowUp`/`ArrowDown` (caret jump), and only while the dropdown is open. Enter semantics are unchanged:
it still picks the active suggestion, or the first one when none is active
it still picks the active suggestion, or the first one when none is active. This fixes the known
regression in 1.3.0 — upgrade from 1.3.0 is strongly recommended. A narrower form of the same defect
existed in 1.0.0–1.2.0: after navigating the list with the arrow keys, the default template swallowed
every keystroke until the dropdown closed; this is fixed as well

#### Deprecated

Expand All @@ -44,8 +56,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

> **Known broken — do not use.** With the default input template — and with every custom `InputTemplate`
> that followed the 1.3.0 guidance to apply `@onkeydown:preventDefault` — an open suggestion dropdown
> suppressed every keystroke, leaving the input uneditable after the first suggestions appeared.
> Fixed in the following release.
> suppressed every keystroke (typing, Backspace/Delete, Tab), leaving the input uneditable after the first
> suggestions appeared. This affects every `HereAutosuggest` without a custom `InputTemplate`. Fixed in
> 1.3.1: upgrade, and remove any `@onkeydown:preventDefault` binding from custom input templates. The
> `PreventDefaultKeyDown` guidance below is withdrawn.

#### Added

Expand All @@ -65,7 +79,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `HereAutosuggest` with a custom `InputTemplate`: pressing Enter while the dropdown was open selected a
suggestion AND submitted a surrounding `<form>`/`EditForm`, because `InputAttributes` splatting could not
carry the default template's `@onkeydown:preventDefault` modifier. Consumers must now also bind
`@onkeydown:preventDefault="@context.PreventDefaultKeyDown"` on their custom input
`@onkeydown:preventDefault="@context.PreventDefaultKeyDown"` on their custom input (withdrawn in 1.3.1 —
this binding blocks every keystroke)

### [1.2.0] - 2026-06-11

Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ dotnet build

```bash
dotnet test tests/HerePlatform.Blazor.Tests/
node --test tests/HerePlatform.Blazor.Tests.Js/*.test.mjs
```

The JS tests use the built-in `node:test` runner and need Node.js 20 or later.

## Pull Request Guidelines

1. **Branch from `main`** — create a feature or fix branch (e.g. `feat/my-feature` or `fix/issue-42`).
Expand Down
7 changes: 4 additions & 3 deletions HerePlatform.Blazor/Maps/Search/AutosuggestInputContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ public class AutosuggestInputContext
/// suppresses EVERY keystroke — characters, Backspace, Tab — once the dropdown is open.
/// Splat <see cref="InputAttributes"/> instead; it carries the marker the component needs.
/// </summary>
[Obsolete("Key-selective preventDefault is handled by the component since 1.3.1; do not apply " +
"@onkeydown:preventDefault in custom templates. Always false; scheduled for removal in " +
"the next major version.")]
[Obsolete("No longer needed since 1.3.1: the component suppresses the browser default for Enter and " +
"ArrowUp/ArrowDown itself. Remove @onkeydown:preventDefault from custom templates (it blocks " +
"every keystroke) and only splat @attributes=\"context.InputAttributes\". Always false; " +
"scheduled for removal in the next major version.", error: false)]
public bool PreventDefaultKeyDown
{
get => false;
Expand Down
2 changes: 1 addition & 1 deletion docs/HerePlatform.Docs.Generator/ApiDocGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ private static Dictionary<string, string> LoadXmlDocs(string xmlPath)
foreach (var member in doc.Descendants("member"))
{
var name = member.Attribute("name")?.Value;
var summary = member.Element("summary")?.Value.Trim();
var summary = XmlDocText.Render(member.Element("summary"));
if (name is not null && !string.IsNullOrEmpty(summary))
{
// Normalize whitespace
Expand Down
4 changes: 2 additions & 2 deletions docs/HerePlatform.Docs.Generator/RestApiDocGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ private static (Dictionary<string, string> summaries, Dictionary<string, Diction
var name = member.Attribute("name")?.Value;
if (name is null) continue;

var summary = member.Element("summary")?.Value.Trim();
var summary = XmlDocText.Render(member.Element("summary"));
if (!string.IsNullOrEmpty(summary))
{
summary = string.Join(" ", summary.Split(default(char[]), StringSplitOptions.RemoveEmptyEntries));
Expand All @@ -509,7 +509,7 @@ private static (Dictionary<string, string> summaries, Dictionary<string, Diction
foreach (var paramEl in member.Elements("param"))
{
var paramName = paramEl.Attribute("name")?.Value;
var paramDesc = paramEl.Value.Trim();
var paramDesc = XmlDocText.Render(paramEl);
if (paramName is not null && !string.IsNullOrEmpty(paramDesc))
{
paramDesc = string.Join(" ", paramDesc.Split(default(char[]), StringSplitOptions.RemoveEmptyEntries));
Expand Down
76 changes: 76 additions & 0 deletions docs/HerePlatform.Docs.Generator/XmlDocText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace HerePlatform.Docs.Generator;

/// <summary>
/// Renders an XML documentation element (summary, param) as plain text. <see cref="XElement.Value"/>
/// drops self-closing tags such as <c>&lt;see cref="..."/&gt;</c>, which leaves gaps like
/// "The provides" in the generated docs.
/// </summary>
internal static class XmlDocText
{
public static string? Render(XElement? element)
{
if (element is null) return null;

var sb = new StringBuilder();
Append(element, sb);

// Normalize whitespace
return string.Join(" ", sb.ToString().Split(default(char[]), StringSplitOptions.RemoveEmptyEntries));
}

private static void Append(XElement element, StringBuilder sb)
{
foreach (var node in element.Nodes())
{
if (node is XText text)
{
sb.Append(text.Value);
}
else if (node is XElement child)
{
if (child.Name == "para") sb.Append(' ');

if (child.Nodes().Any())
Append(child, sb);
else
sb.Append(RenderEmpty(child));

if (child.Name == "para") sb.Append(' ');
}
}
}

// <see cref/langword/href/>, <paramref name/>, <typeparamref name/>
private static string RenderEmpty(XElement element)
{
if (element.Attribute("cref")?.Value is { } cref) return ShortCref(cref);
if (element.Attribute("langword")?.Value is { } langword) return langword;
if (element.Attribute("name")?.Value is { } name) return name;
if (element.Attribute("href")?.Value is { } href) return href;
return "";
}

// "T:Ns.HereAutosuggest" -> "HereAutosuggest", "P:Ns.HereAutosuggest.InputTemplate" ->
// "HereAutosuggest.InputTemplate", "M:Ns.Type.#ctor(System.String)" -> "Type"
private static string ShortCref(string cref)
{
var kind = cref.Length > 2 && cref[1] == ':' ? cref[0] : 'T';
var path = cref.Length > 2 && cref[1] == ':' ? cref[2..] : cref;

var paren = path.IndexOf('(');
if (paren >= 0) path = path[..paren];
path = Regex.Replace(path, @"`+\d+", "");

var isConstructor = path.EndsWith(".#ctor", StringComparison.Ordinal);
if (isConstructor) path = path[..^".#ctor".Length];

var parts = path.Split('.');
if (kind is 'T' or 'N' || isConstructor || parts.Length < 2)
return parts[^1];
return $"{parts[^2]}.{parts[^1]}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ static per-event/per-element flag, so it would swallow every keystroke, includin
```

`AutosuggestInputContext.PreventDefaultKeyDown` is deprecated since 1.3.1, always returns `false`, and will
be removed in the next major version.
be removed in the next major version. Version 1.3.0 is known broken: once suggestions appeared, the input
swallowed every keystroke. If you are on 1.3.0, upgrade to 1.3.1 or later and remove any
`@onkeydown:preventDefault` binding from your custom input template.

## Events

Expand Down
24 changes: 12 additions & 12 deletions docs/HerePlatform.Docs/wwwroot/data/api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@
"name": "CenterChanged",
"kind": "Event",
"type": "EventCallback\u003CLatLngLiteral\u003E",
"description": "Callback for two-way binding of . Fired when the user finishes panning (on mapviewchangeend)."
"description": "Callback for two-way binding of AdvancedHereMap.Center. Fired when the user finishes panning (on mapviewchangeend)."
},
{
"name": "HeadingChanged",
"kind": "Event",
"type": "EventCallback\u003Cdouble\u003E",
"description": "Callback for two-way binding of . Fired when the user finishes rotating (on mapviewchangeend)."
"description": "Callback for two-way binding of AdvancedHereMap.Heading. Fired when the user finishes rotating (on mapviewchangeend)."
},
{
"name": "OnAfterInit",
Expand Down Expand Up @@ -284,13 +284,13 @@
"name": "TiltChanged",
"kind": "Event",
"type": "EventCallback\u003Cdouble\u003E",
"description": "Callback for two-way binding of . Fired when the user finishes tilting (on mapviewchangeend)."
"description": "Callback for two-way binding of AdvancedHereMap.Tilt. Fired when the user finishes tilting (on mapviewchangeend)."
},
{
"name": "ZoomChanged",
"kind": "Event",
"type": "EventCallback\u003Cdouble\u003E",
"description": "Callback for two-way binding of . Fired when the user finishes zooming (on mapviewchangeend)."
"description": "Callback for two-way binding of AdvancedHereMap.Zoom. Fired when the user finishes zooming (on mapviewchangeend)."
}
]
},
Expand Down Expand Up @@ -326,7 +326,7 @@
"name": "PreventDefaultKeyDown",
"kind": "Property",
"type": "bool",
"description": "Always false. Deprecated since 1.3.1: the component suppresses the browser default itself, key-selectively for Enter and ArrowUp/ArrowDown while the dropdown is open. A custom must NOT apply @onkeydown:preventDefault: Blazor\u0027s modifier is a static per-event/per-element flag, so it suppresses EVERY keystroke \u2014 characters, Backspace, Tab \u2014 once the dropdown is open. Splat instead; it carries the marker the component needs."
"description": "Always false. Deprecated since 1.3.1: the component suppresses the browser default itself, key-selectively for Enter and ArrowUp/ArrowDown while the dropdown is open. A custom HereAutosuggest.InputTemplate must NOT apply @onkeydown:preventDefault: Blazor\u0027s modifier is a static per-event/per-element flag, so it suppresses EVERY keystroke \u2014 characters, Backspace, Tab \u2014 once the dropdown is open. Splat AutosuggestInputContext.InputAttributes instead; it carries the marker the component needs."
}
]
},
Expand Down Expand Up @@ -1547,7 +1547,7 @@
"kind": "Property",
"type": "AutosuggestDesign",
"default": "Default",
"description": "Predefined design variant for the component. Default: ."
"description": "Predefined design variant for the component. Default: AutosuggestDesign.Default."
},
{
"name": "Disabled",
Expand All @@ -1561,7 +1561,7 @@
"kind": "Property",
"type": "RenderFragment\u003CAutosuggestInputContext\u003E",
"default": "null",
"description": "Custom template for rendering the input area. The provides an InputAttributes dictionary that must be splatted onto the custom \u003Cinput\u003E element (@attributes=\u0022context.InputAttributes\u0022) \u2014 nothing else is required. Keyboard defaults are handled by the component: splatting also applies the data-here-autosuggest-input marker that lets the component suppress the browser default for Enter and ArrowUp/ArrowDown \u2014 and only those \u2014 while the dropdown is open. Do NOT apply @onkeydown:preventDefault in a custom template."
"description": "Custom template for rendering the input area. The AutosuggestInputContext provides an InputAttributes dictionary that must be splatted onto the custom \u003Cinput\u003E element (@attributes=\u0022context.InputAttributes\u0022) \u2014 nothing else is required. Keyboard defaults are handled by the component: splatting also applies the data-here-autosuggest-input marker that lets the component suppress the browser default for Enter and ArrowUp/ArrowDown \u2014 and only those \u2014 while the dropdown is open. Do NOT apply @onkeydown:preventDefault in a custom template."
},
{
"name": "MinChars",
Expand Down Expand Up @@ -1589,14 +1589,14 @@
"kind": "Property",
"type": "RenderFragment\u003CAutosuggestItem\u003E",
"default": "null",
"description": "Custom template for rendering each suggestion item. When set, replaces the default item content inside the \u003Cli\u003E shell (click handler and active class are preserved). Ignored when is set."
"description": "Custom template for rendering each suggestion item. When set, replaces the default item content inside the \u003Cli\u003E shell (click handler and active class are preserved). Ignored when HereAutosuggest.SuggestionListTemplate is set."
},
{
"name": "SuggestionListTemplate",
"kind": "Property",
"type": "RenderFragment\u003CAutosuggestListContext\u003E",
"default": "null",
"description": "Custom template for rendering the entire suggestion dropdown. When set, replaces the default \u003Cul\u003E list completely. Use to wire up item selection."
"description": "Custom template for rendering the entire suggestion dropdown. When set, replaces the default \u003Cul\u003E list completely. Use AutosuggestListContext.SelectItem to wire up item selection."
},
{
"name": "UserAttributes",
Expand Down Expand Up @@ -1933,7 +1933,7 @@
"name": "IsOpenChanged",
"kind": "Event",
"type": "EventCallback\u003Cbool\u003E",
"description": "Callback for two-way binding of . Fired when the InfoBubble is closed by clicking the X button."
"description": "Callback for two-way binding of InfoBubbleComponent.IsOpen. Fired when the InfoBubble is closed by clicking the X button."
}
]
},
Expand Down Expand Up @@ -2958,13 +2958,13 @@
"name": "LatChanged",
"kind": "Event",
"type": "EventCallback\u003Cdouble\u003E",
"description": "Callback for two-way binding of . Fired when the marker is dragged to a new position."
"description": "Callback for two-way binding of MarkerComponent.Lat. Fired when the marker is dragged to a new position."
},
{
"name": "LngChanged",
"kind": "Event",
"type": "EventCallback\u003Cdouble\u003E",
"description": "Callback for two-way binding of . Fired when the marker is dragged to a new position."
"description": "Callback for two-way binding of MarkerComponent.Lng. Fired when the marker is dragged to a new position."
},
{
"name": "OnClick",
Expand Down
Loading
Loading