From 822881a2e9fe072cac1605d197412fa85f71dadf Mon Sep 17 00:00:00 2001
From: NoobNotFound <82730163+NoobNotFound@users.noreply.github.com>
Date: Sun, 30 Aug 2026 02:30:49 +0530
Subject: [PATCH 1/4] feat(agent): embed versioned skill contract
Resolve FluentKit guidance from the exact NuGet package instead of maintaining release-specific skill snapshots.
---
.github/workflows/ci.yml | 177 +-
.github/workflows/release.yml | 91 +-
FluentKit.slnx | 1 -
README.md | 16 +-
docs/integration/README.md | 14 +-
docs/integration/agent-skill.md | 29 +
docs/integration/component-selection.md | 3 +-
docs/integration/overlays.md | 4 +-
docs/integration/sample-routes.md | 9 +
docs/integration/server-circuit-disposal.md | 8 +-
.../references => docs/integration}/setup.md | 8 +-
docs/reference/api.json | 2 +-
docs/reference/api.md | 2 +-
fluentkit-api/SKILL.md | 70 +-
fluentkit-api/agents/openai.yaml | 7 +
fluentkit-api/metadata.json | 7 -
fluentkit-api/references/api.json | 5422 -----------------
fluentkit-api/references/overlays.md | 5 -
fluentkit-api/references/sample-routes.md | 4 -
.../references/theming-and-tokens.md | 5 -
fluentkit-api/references/troubleshooting.md | 6 -
fluentkit-api/scripts/resolve-fluentkit.ps1 | 43 +
fluentkit-api/scripts/resolve-fluentkit.sh | 59 +
.../FluentKit.ApiSmoke.csproj | 8 +-
scripts/README.md | 18 +-
scripts/install-fluentkit-api-skill.ps1 | 49 +
scripts/install-fluentkit-api-skill.sh | 51 +
scripts/test-fluentkit-contract.py | 195 +
scripts/update-api-reference.bat | 28 +-
scripts/update-api-reference.sh | 28 +-
scripts/validate-fluentkit-api-skill.py | 67 +
.../Agent/FluentKit.Blazor.Agent.props | 9 +
src/FluentKit/Agent/FluentKit.Blazor.props | 5 +
src/FluentKit/FluentKit.csproj | 43 +-
.../Program.cs | 366 +-
tools/README.md | 12 +-
36 files changed, 1256 insertions(+), 5615 deletions(-)
create mode 100644 docs/integration/agent-skill.md
create mode 100644 docs/integration/sample-routes.md
rename {fluentkit-api/references => docs/integration}/setup.md (65%)
create mode 100644 fluentkit-api/agents/openai.yaml
delete mode 100644 fluentkit-api/metadata.json
delete mode 100644 fluentkit-api/references/api.json
delete mode 100644 fluentkit-api/references/overlays.md
delete mode 100644 fluentkit-api/references/sample-routes.md
delete mode 100644 fluentkit-api/references/theming-and-tokens.md
delete mode 100644 fluentkit-api/references/troubleshooting.md
create mode 100644 fluentkit-api/scripts/resolve-fluentkit.ps1
create mode 100755 fluentkit-api/scripts/resolve-fluentkit.sh
create mode 100644 scripts/install-fluentkit-api-skill.ps1
create mode 100755 scripts/install-fluentkit-api-skill.sh
create mode 100644 scripts/test-fluentkit-contract.py
create mode 100644 scripts/validate-fluentkit-api-skill.py
create mode 100644 src/FluentKit/Agent/FluentKit.Blazor.Agent.props
create mode 100644 src/FluentKit/Agent/FluentKit.Blazor.props
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d89d790..e0e9050 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,8 +1,5 @@
name: CI
-# Runs on every commit: any branch push, and any PR targeting main.
-# workflow_dispatch lets you trigger a run manually from the GitHub Actions page —
-# Release publishing is tag-driven; CI validates every commit and pull request.
on:
push:
branches: ["**"]
@@ -12,6 +9,7 @@ on:
env:
PACKAGE_PROJECT: src/FluentKit/FluentKit.csproj
+ SMOKE_PROJECT: samples/FluentKit.ApiSmoke/FluentKit.ApiSmoke.csproj
permissions:
contents: read
@@ -29,17 +27,12 @@ jobs:
with:
dotnet-version: "10.0.x"
- # against this preview SDK first.
- name: Install MAUI Android workload
run: dotnet workload install maui-android
- name: Restore
run: dotnet restore FluentKit.slnx
- # Building the .slnx builds every project it references — src/FluentKit
- # (net10.0, the library), samples/FluentKit.Sample.Wasm (net10.0), and the
- # Android target of samples/Fluentkit.Sample.Maui (net11.0-android) — so this
- # single step covers "build everything" that's buildable on a Linux runner.
- name: Build (Release)
run: dotnet build FluentKit.slnx -c Release --no-restore -warnaserror:RS0016,RS0017
@@ -47,22 +40,160 @@ jobs:
run: dotnet test tests/FluentKit.Tests/FluentKit.Tests.csproj -c Release --no-build --no-restore
- name: Verify generated API reference
+ run: |
+ ./scripts/update-api-reference.sh Release --verify
+ git diff --exit-code -- docs/reference/api.json docs/reference/api.md
+
+ - name: Validate generator fixture
+ run: dotnet run --project tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj -c Release --no-build --no-restore -- --self-test
+
+ - name: Validate standalone skill
+ run: python3 scripts/validate-fluentkit-api-skill.py
+
+ package-contract:
+ name: Package contract (${{ matrix.os }})
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1
+ with:
+ dotnet-version: "10.0.x"
+
+ - name: Verify generated API reference (Unix)
+ if: runner.os != 'Windows'
+ run: |
+ ./scripts/update-api-reference.sh Release --verify
+ git diff --exit-code -- docs/reference/api.json docs/reference/api.md
+
+ - name: Verify generated API reference (Windows)
+ if: runner.os == 'Windows'
+ shell: cmd
+ run: |
+ scripts\update-api-reference.bat Release --verify
+ git diff --exit-code -- docs/reference/api.json docs/reference/api.md
+
+ - name: Validate standalone skill
+ shell: pwsh
+ run: python scripts/validate-fluentkit-api-skill.py
+
+ - name: Build generator and library (Windows)
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: dotnet build $env:PACKAGE_PROJECT -c Release
+
+ - name: Build generator and library (Unix)
+ if: runner.os != 'Windows'
+ shell: bash
+ run: dotnet build "$PACKAGE_PROJECT" -c Release
+
+ - name: Pack package (Windows)
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ New-Item -ItemType Directory -Path package-output -Force | Out-Null
+ dotnet pack $env:PACKAGE_PROJECT -c Release --no-restore --output package-output
+ $package = (Get-ChildItem package-output -Filter '*.nupkg' | Select-Object -First 1).FullName
+ "PACKAGE_PATH=$package" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+ $version = (& dotnet msbuild $env:PACKAGE_PROJECT -getProperty:PackageVersion -nologo | Select-Object -Last 1).Trim()
+ "PACKAGE_VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+
+ - name: Pack package (Unix)
+ if: runner.os != 'Windows'
+ shell: bash
+ run: |
+ mkdir -p package-output
+ dotnet pack "$PACKAGE_PROJECT" -c Release --no-restore --output package-output
+ package_path="$(find package-output -maxdepth 1 -name '*.nupkg' -print -quit)"
+ echo "PACKAGE_PATH=$package_path" >> "$GITHUB_ENV"
+ echo "PACKAGE_VERSION=$(dotnet msbuild "$PACKAGE_PROJECT" -getProperty:PackageVersion -nologo)" >> "$GITHUB_ENV"
+
+ - name: Verify package contract (Windows)
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: >-
+ dotnet run --project tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj
+ -c Release --no-build --no-restore -- --verify-package "$env:PACKAGE_PATH"
+ --assembly "src/FluentKit/bin/Release/net10.0/FluentKit.dll"
+ --xml "src/FluentKit/bin/Release/net10.0/FluentKit.xml"
+ --manifest "docs/integration/manifest.json"
+
+ - name: Verify package contract (Unix)
+ if: runner.os != 'Windows'
+ shell: bash
+ run: >-
+ dotnet run --project tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj
+ -c Release --no-build --no-restore -- --verify-package "$PACKAGE_PATH"
+ --assembly "src/FluentKit/bin/Release/net10.0/FluentKit.dll"
+ --xml "src/FluentKit/bin/Release/net10.0/FluentKit.xml"
+ --manifest "docs/integration/manifest.json"
+
+ - name: Restore and build local package smoke test
+ shell: pwsh
+ run: |
+ $feed = Join-Path $env:RUNNER_TEMP 'fluentkit-feed'
+ $packages = Join-Path $env:RUNNER_TEMP 'fluentkit-packages'
+ New-Item -ItemType Directory -Path $feed, $packages -Force | Out-Null
+ Copy-Item -LiteralPath $env:PACKAGE_PATH -Destination $feed
+ $env:NUGET_PACKAGES = $packages
+ "NUGET_PACKAGES=$packages" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+ "LOCAL_NUGET_FEED=$feed" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+ dotnet restore $env:SMOKE_PROJECT -p:FluentKitSmokeVersion=$env:PACKAGE_VERSION --source $feed --source https://api.nuget.org/v3/index.json
+ dotnet build $env:SMOKE_PROJECT -c Release --no-restore
+
+ - name: Resolve package contract (Windows)
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ & powershell -NoProfile -ExecutionPolicy Bypass -File fluentkit-api/scripts/resolve-fluentkit.ps1 -Project $env:SMOKE_PROJECT -Property FluentKitSmokeVersion=$env:PACKAGE_VERSION -Source $env:LOCAL_NUGET_FEED
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ $manifest = (& dotnet msbuild $env:SMOKE_PROJECT -nologo -getProperty:FluentKitAgentManifestPath | Select-Object -Last 1).Trim()
+ $contract = Get-Content -Raw -LiteralPath $manifest | ConvertFrom-Json
+ if ($contract.packageVersion -ne $env:PACKAGE_VERSION) { throw "Resolved package version $($contract.packageVersion) does not equal $env:PACKAGE_VERSION" }
+
+ - name: Resolve package contract (Unix)
+ if: runner.os != 'Windows'
+ shell: bash
+ run: |
+ bash fluentkit-api/scripts/resolve-fluentkit.sh "$SMOKE_PROJECT" --property "FluentKitSmokeVersion=$PACKAGE_VERSION" --source "$LOCAL_NUGET_FEED"
+ manifest="$(dotnet msbuild "$SMOKE_PROJECT" -nologo -p:FluentKitSmokeVersion="$PACKAGE_VERSION" -getProperty:FluentKitAgentManifestPath | tail -n 1 | tr -d '\r')"
+ test -f "$manifest"
+ test "$(jq -r .packageVersion "$manifest")" = "$PACKAGE_VERSION"
+
+ - name: Test contract failure scenarios (Windows)
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ python scripts/test-fluentkit-contract.py `
+ --root $env:GITHUB_WORKSPACE `
+ --package $env:PACKAGE_PATH `
+ --version $env:PACKAGE_VERSION `
+ --source $env:LOCAL_NUGET_FEED `
+ --smoke-project $env:SMOKE_PROJECT `
+ --resolver fluentkit-api/scripts/resolve-fluentkit.ps1 `
+ --generator tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj `
+ --assembly src/FluentKit/bin/Release/net10.0/FluentKit.dll `
+ --xml src/FluentKit/bin/Release/net10.0/FluentKit.xml `
+ --manifest docs/integration/manifest.json
+
+ - name: Test contract failure scenarios (Unix)
+ if: runner.os != 'Windows'
shell: bash
run: |
- dotnet run --project tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj \
- -c Release --no-build -- \
+ python3 scripts/test-fluentkit-contract.py \
+ --root "$GITHUB_WORKSPACE" \
+ --package "$PACKAGE_PATH" \
+ --version "$PACKAGE_VERSION" \
+ --source "$LOCAL_NUGET_FEED" \
+ --smoke-project "$SMOKE_PROJECT" \
+ --resolver fluentkit-api/scripts/resolve-fluentkit.sh \
+ --generator tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj \
--assembly src/FluentKit/bin/Release/net10.0/FluentKit.dll \
--xml src/FluentKit/bin/Release/net10.0/FluentKit.xml \
- --manifest docs/integration/manifest.json \
- --json docs/reference/api.json \
- --markdown docs/reference/api.md \
- --summary-baseline docs/reference/summary-baseline.json \
- --check-summaries --verify
- cmp --silent docs/reference/api.json fluentkit-api/references/api.json
- test "$(dotnet msbuild src/FluentKit/FluentKit.csproj -getProperty:Version -nologo)" = "$(jq -r .fluentkitVersion fluentkit-api/metadata.json)"
-
- - name: Verify generator fixture
- run: dotnet run --project tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj -c Release --no-build -- --self-test
-
- - name: Report
- run: echo "Build, application smoke sample, generated reference, API baseline, and skill metadata verified."
+ --manifest docs/integration/manifest.json
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ee891f5..9068efd 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
inputs:
tag:
- description: "Existing release tag to publish (for example, v0.2.3). Leave blank to create a tag."
+ description: "Existing release tag to publish (for example, v0.2.6). Leave blank to create a tag."
required: false
type: string
version:
@@ -22,6 +22,7 @@ permissions:
env:
PACKAGE_PROJECT: src/FluentKit/FluentKit.csproj
+ SMOKE_PROJECT: samples/FluentKit.ApiSmoke/FluentKit.ApiSmoke.csproj
jobs:
release:
@@ -31,8 +32,6 @@ jobs:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
with:
- # A manual run starts from the selected branch. The resolve step below checks out
- # an existing input tag, or leaves this commit checked out to create a new tag.
fetch-depth: 0
- name: Setup .NET
@@ -71,7 +70,7 @@ jobs:
exit 1
fi
if [[ ! "$REQUESTED_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then
- echo "Version must be a SemVer-like value such as 0.2.4 or 0.2.4-rc.1." >&2
+ echo "Version must be a SemVer-like value such as 0.2.6 or 0.2.6-rc.1." >&2
exit 1
fi
release_tag="v$REQUESTED_VERSION"
@@ -95,8 +94,6 @@ jobs:
package_version="$(dotnet msbuild "$PACKAGE_PROJECT" -getProperty:Version -nologo)"
tag_version="${RELEASE_TAG#v}"
test "$package_version" = "$tag_version"
- test "$(jq -r .fluentkitVersion fluentkit-api/metadata.json)" = "$package_version"
- test "$(jq -r .sourceTag fluentkit-api/metadata.json)" = "$RELEASE_TAG"
- name: Create requested release tag
if: env.CREATE_TAG == 'true'
@@ -115,7 +112,7 @@ jobs:
- name: Restore
run: dotnet restore FluentKit.slnx
- - name: Build and verify public API baseline
+ - name: Build solution
run: dotnet build FluentKit.slnx -c Release --no-restore -warnaserror:RS0016,RS0017
- name: Run tests
@@ -123,33 +120,75 @@ jobs:
- name: Verify generated API reference
run: |
+ ./scripts/update-api-reference.sh Release --verify
+ git diff --exit-code -- docs/reference/api.json docs/reference/api.md
+
+ - name: Validate standalone skill
+ run: python3 scripts/validate-fluentkit-api-skill.py
+
+ - name: Package NuGet
+ shell: bash
+ run: |
+ set -euo pipefail
+ mkdir -p nupkg
+ dotnet pack "$PACKAGE_PROJECT" --no-restore --configuration Release --output ./nupkg
+ package_path="$(find nupkg -maxdepth 1 -name '*.nupkg' -print -quit)"
+ echo "PACKAGE_PATH=$package_path" >> "$GITHUB_ENV"
+ echo "PACKAGE_VERSION=$(dotnet msbuild "$PACKAGE_PROJECT" -getProperty:PackageVersion -nologo)" >> "$GITHUB_ENV"
+
+ - name: Verify package contract
+ shell: bash
+ run: |
+ set -euo pipefail
dotnet run --project tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj \
- -c Release --no-build -- \
+ -c Release --no-build --no-restore -- --verify-package "$PACKAGE_PATH" \
+ --assembly "src/FluentKit/bin/Release/net10.0/FluentKit.dll" \
+ --xml "src/FluentKit/bin/Release/net10.0/FluentKit.xml" \
+ --manifest "docs/integration/manifest.json"
+
+ - name: Restore and build local package smoke test
+ shell: bash
+ run: |
+ set -euo pipefail
+ feed_dir="$RUNNER_TEMP/fluentkit-feed"
+ packages_dir="$RUNNER_TEMP/fluentkit-packages"
+ mkdir -p "$feed_dir" "$packages_dir"
+ cp "$PACKAGE_PATH" "$feed_dir/"
+ echo "NUGET_PACKAGES=$packages_dir" >> "$GITHUB_ENV"
+ echo "LOCAL_NUGET_FEED=$feed_dir" >> "$GITHUB_ENV"
+ NUGET_PACKAGES="$packages_dir" dotnet restore "$SMOKE_PROJECT" \
+ -p:FluentKitSmokeVersion="$PACKAGE_VERSION" \
+ --source "$feed_dir" --source https://api.nuget.org/v3/index.json
+ NUGET_PACKAGES="$packages_dir" dotnet build "$SMOKE_PROJECT" -c Release --no-restore
+ NUGET_PACKAGES="$packages_dir" bash fluentkit-api/scripts/resolve-fluentkit.sh "$SMOKE_PROJECT" --property "FluentKitSmokeVersion=$PACKAGE_VERSION" --source "$feed_dir"
+ manifest_path="$(NUGET_PACKAGES="$packages_dir" dotnet msbuild "$SMOKE_PROJECT" -nologo -p:FluentKitSmokeVersion="$PACKAGE_VERSION" -getProperty:FluentKitAgentManifestPath | tail -n 1 | tr -d '\r')"
+ test "$(jq -r .packageVersion "$manifest_path")" = "$PACKAGE_VERSION"
+
+ python3 scripts/test-fluentkit-contract.py \
+ --root "$GITHUB_WORKSPACE" \
+ --package "$PACKAGE_PATH" \
+ --version "$PACKAGE_VERSION" \
+ --source "$feed_dir" \
+ --smoke-project "$SMOKE_PROJECT" \
+ --resolver fluentkit-api/scripts/resolve-fluentkit.sh \
+ --generator tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj \
--assembly src/FluentKit/bin/Release/net10.0/FluentKit.dll \
--xml src/FluentKit/bin/Release/net10.0/FluentKit.xml \
- --manifest docs/integration/manifest.json \
- --json docs/reference/api.json \
- --markdown docs/reference/api.md \
- --summary-baseline docs/reference/summary-baseline.json \
- --check-summaries --verify
- cmp --silent docs/reference/api.json fluentkit-api/references/api.json
-
- - name: Package NuGet
- run: dotnet pack "$PACKAGE_PROJECT" --no-restore --configuration Release --output ./nupkg
+ --manifest docs/integration/manifest.json
- - name: Package portable API skill
+ - name: Create bootstrap skill release assets
shell: bash
run: |
set -euo pipefail
- skill_archive="fluentkit-api.zip"
- rm -f "$skill_archive"
- zip -qr "$skill_archive" fluentkit-api
- test -s "$skill_archive"
- unexpected_entries="$(unzip -Z1 "$skill_archive" | grep -vE '^fluentkit-api(/|$)' || true)"
+ rm -f fluentkit-api.zip fluentkit-api.zip.sha256
+ zip -qr fluentkit-api.zip fluentkit-api
+ test -s fluentkit-api.zip
+ unexpected_entries="$(unzip -Z1 fluentkit-api.zip | grep -vE '^fluentkit-api(/|$)' || true)"
if [[ -n "$unexpected_entries" ]]; then
echo "Skill archive contains files outside fluentkit-api/." >&2
exit 1
fi
+ sha256sum fluentkit-api.zip > fluentkit-api.zip.sha256
- name: NuGet login (Trusted Publishing)
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841
@@ -180,13 +219,13 @@ jobs:
--source github \
--skip-duplicate
- - name: Create GitHub Release and attach skill bundle
+ - name: Create GitHub Release and attach bootstrap assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
- gh release upload "$RELEASE_TAG" fluentkit-api.zip --clobber
+ gh release upload "$RELEASE_TAG" fluentkit-api.zip fluentkit-api.zip.sha256 --clobber
else
- gh release create "$RELEASE_TAG" fluentkit-api.zip \
+ gh release create "$RELEASE_TAG" fluentkit-api.zip fluentkit-api.zip.sha256 \
--title "FluentKit ${RELEASE_TAG}" --generate-notes
fi
diff --git a/FluentKit.slnx b/FluentKit.slnx
index fb53cd2..36bba58 100644
--- a/FluentKit.slnx
+++ b/FluentKit.slnx
@@ -2,7 +2,6 @@
-
diff --git a/README.md b/README.md
index 8f5a53a..a4f4a78 100644
--- a/README.md
+++ b/README.md
@@ -45,9 +45,10 @@ styling, or token values were sourced from.
For a separate application, use the [integration documentation](docs/integration/README.md). The
generated [API reference](docs/reference/api.md) and machine-readable
[`api.json`](docs/reference/api.json) are produced from the compiled FluentKit assembly and
-are kept in sync by CI. Coding agents can use the versioned
-[`fluentkit-api`](fluentkit-api/SKILL.md) skill bundle, or
-[download the latest portable skill ZIP](https://github.com/VibeNoobNotFound/FluentKit/releases/latest/download/fluentkit-api.zip).
+are kept in sync by CI. Coding agents can use the standalone
+[`fluentkit-api`](fluentkit-api/SKILL.md) bootstrap skill. It resolves the exact API and
+integration contract embedded in the package used by the consumer app; install it once from
+the [latest portable skill ZIP](https://github.com/VibeNoobNotFound/FluentKit/releases/latest/download/fluentkit-api.zip).
**1. Install**
@@ -58,7 +59,7 @@ dotnet add package FluentKit.Blazor
```
```xml
-
+
```
Alternatively, to track `main` directly, use it as a project or repository reference instead:
@@ -363,10 +364,11 @@ directly.)
The `Release FluentKit` workflow runs automatically for a `vMAJOR.MINOR.PATCH` tag. It can also
be started from the GitHub Actions page: enter an existing tag in **tag** to republish that
immutable version, or leave **tag** blank and enter a version to create and publish a new tag
-from the selected branch. The project version and `fluentkit-api/metadata.json` must already
-match that version. Each release publishes NuGet and a standalone
+from the selected branch. The project version must already match that version. Each release
+publishes NuGet and a standalone
[`fluentkit-api.zip`](https://github.com/VibeNoobNotFound/FluentKit/releases/latest/download/fluentkit-api.zip)
-containing only the portable skill folder.
+containing the permanent bootstrap skill. The package itself contains the version-specific
+agent contract, and the release also publishes a checksum for the ZIP.
## Contributing
diff --git a/docs/integration/README.md b/docs/integration/README.md
index 86bc17b..2c766a3 100644
--- a/docs/integration/README.md
+++ b/docs/integration/README.md
@@ -1,8 +1,8 @@
# Using FluentKit in an application
FluentKit is a .NET 10 Razor Class Library for Blazor WebAssembly, Blazor Server, and
-MAUI Blazor Hybrid. Install the `FluentKit.Blazor` package and keep the package version
-aligned with the matching FluentKit release documentation.
+MAUI Blazor Hybrid. Install the `FluentKit.Blazor` package at the version your application
+requires. The package carries the matching agent contract and API reference.
The generated [API reference](../reference/api.md) describes the actual public assembly
surface. The [live sample](https://vibenoobnotfound.github.io/FluentKit/) is the runnable
@@ -13,7 +13,7 @@ visual reference; its source is in `samples/FluentKit.Sample.Shared`.
1. Add the package:
```bash
- dotnet add package FluentKit.Blazor --version 0.2.4
+ dotnet add package FluentKit.Blazor
```
2. Link the public token and icon stylesheets. The static web asset base is always
@@ -63,8 +63,8 @@ as an overlay, picker, navigation model, or selection state. Prefer FluentKit to
component parameters over app-local CSS values. Every component currently demonstrated by
the sample app is listed in [component-selection.md](component-selection.md).
-## Versioning
+## Agent guidance
-The API skill and generated API reference are released with the same `v{version}` tag
-as the NuGet package. For an existing app, inspect its `PackageReference` or lock file and
-use the matching release bundle; do not use the latest skill against an older package.
+Install the standalone `fluentkit-api` bootstrap skill once. It resolves the exact
+package-local API and integration contract from the consumer project's restored
+`FluentKit.Blazor` package, so updating the package does not require updating the skill.
diff --git a/docs/integration/agent-skill.md b/docs/integration/agent-skill.md
new file mode 100644
index 0000000..702e94e
--- /dev/null
+++ b/docs/integration/agent-skill.md
@@ -0,0 +1,29 @@
+---
+name: fluentkit-api
+description: Use the exact FluentKit.Blazor API and integration guidance shipped by the package referenced by the current Blazor or MAUI Blazor application.
+---
+
+# FluentKit application integration
+
+This contract is loaded from the exact `FluentKit.Blazor` package restored by the consumer
+project. The installed bootstrap skill has already resolved this file; do not replace it with
+an online or newer reference.
+
+## Required workflow
+
+1. Identify the consumer project's hosting model and the package version in use.
+2. Read `api.json` before using component parameters, callbacks, slots, enums, or services.
+3. Read `references/setup.md` and `references/component-selection.md` before adding components.
+4. Keep `ThemeProvider` at the root, register the theme, accent, and overlay services, and
+ render one `FluentOverlayHost` for overlays.
+5. Use the component-selection, theming, overlay, icon, and troubleshooting references when
+ the task touches those areas. Use the sample route in `api.json` to find a runnable example.
+6. Preserve the fixed static asset base `_content/FluentKit`; it is not
+ `_content/FluentKit.Blazor`.
+7. Prefer FluentKit tokens and the exact component parameters over app-local hard-coded Fluent
+ values.
+8. Build the consumer after changes and verify static assets and interactive overlays when
+ relevant.
+
+The manifest at `manifest.json` identifies the package, API files, references, and checksums.
+The package version is authoritative for this contract.
diff --git a/docs/integration/component-selection.md b/docs/integration/component-selection.md
index 555637a..adadada 100644
--- a/docs/integration/component-selection.md
+++ b/docs/integration/component-selection.md
@@ -14,4 +14,5 @@ Use it to find the smallest control that owns the behavior your feature needs.
| Navigation | `FluentNavigationView`, `FluentMenuBar`, `FluentPivot` | `/Composite/NavigationView` |
| Effects | `FluentMicaPanel`, `FluentAcrylicBrush`, `FluentRevealBackground` | `/Effects/Mica` |
-For the exact parameter and event surface, use [api.md](../reference/api.md).
+For the exact parameter and event surface, use the package-local `api.md` generated from the
+same assembly as this guide.
diff --git a/docs/integration/overlays.md b/docs/integration/overlays.md
index e0600fd..e321321 100644
--- a/docs/integration/overlays.md
+++ b/docs/integration/overlays.md
@@ -6,5 +6,5 @@ Register `IOverlayService` and render one `FluentOverlayHost` inside the root
Keep an overlay host mounted for the lifetime of the application. Rendering a second host
or placing it inside a transient page can make an overlay disappear when navigation occurs.
-The [overlay sample pages](../../samples/FluentKit.Sample.Shared/Pages/Composite) show the
-supported trigger, placement, close, focus, and keyboard patterns.
+The component's `sample` field in the package-local `api.json` identifies the corresponding
+visual sample route.
diff --git a/docs/integration/sample-routes.md b/docs/integration/sample-routes.md
new file mode 100644
index 0000000..88fbc82
--- /dev/null
+++ b/docs/integration/sample-routes.md
@@ -0,0 +1,9 @@
+# Sample routes
+
+The `sample` field in `api.json` points to the corresponding route in the live FluentKit
+sample application:
+
+`https://vibenoobnotfound.github.io/FluentKit{sample}`
+
+Use the route as a visual reference, then confirm the exact parameters and events in the
+package-local `api.json` before writing application code.
diff --git a/docs/integration/server-circuit-disposal.md b/docs/integration/server-circuit-disposal.md
index c746af1..54577f5 100644
--- a/docs/integration/server-circuit-disposal.md
+++ b/docs/integration/server-circuit-disposal.md
@@ -1,6 +1,6 @@
# Server circuit-disposal verification
-This runbook verifies the server-side failure mode fixed in FluentKit 0.2.4: a component tree
+This runbook verifies the server-side circuit-disposal failure mode: a component tree
containing `ThemeProvider` and `FluentMicaPanel` is disposed after a normal HTTP form post redirects
the browser to another page.
@@ -14,10 +14,12 @@ From the FluentKit repository:
```bash
FEED_DIR="$(mktemp -d)"
dotnet pack src/FluentKit/FluentKit.csproj -c Release -o "$FEED_DIR"
+FLUENTKIT_VERSION="$(dotnet msbuild src/FluentKit/FluentKit.csproj -getProperty:Version -nologo)"
+echo "Use FluentKit.Blazor version $FLUENTKIT_VERSION in the consumer checkout."
```
-In the consumer checkout, temporarily set its central `FluentKit.Blazor` package version to `0.2.4`,
-then restore using the temporary feed and NuGet.org:
+In the consumer checkout, temporarily set its central `FluentKit.Blazor` package version to the
+version under test, then restore using the temporary feed and NuGet.org:
```bash
CONSUMER_DIR="${CONSUMER_DIR:-../consumer}"
diff --git a/fluentkit-api/references/setup.md b/docs/integration/setup.md
similarity index 65%
rename from fluentkit-api/references/setup.md
rename to docs/integration/setup.md
index e84391a..0119d6b 100644
--- a/fluentkit-api/references/setup.md
+++ b/docs/integration/setup.md
@@ -1,14 +1,14 @@
# FluentKit setup
-Install `FluentKit.Blazor` at the version declared by `metadata.json`. Link these files in
-the host page:
+Install the `FluentKit.Blazor` package at the version selected by the consumer application.
+Link these public assets in the host page:
```html
```
-Register:
+Register the core services:
```csharp
builder.Services.AddScoped();
@@ -16,7 +16,7 @@ builder.Services.AddScoped();
builder.Services.AddScoped();
```
-At the root:
+At the application root, render one long-lived overlay host inside the theme provider:
```razor
diff --git a/docs/reference/api.json b/docs/reference/api.json
index 87a2b53..b916829 100644
--- a/docs/reference/api.json
+++ b/docs/reference/api.json
@@ -1,6 +1,6 @@
{
"assembly": "FluentKit",
- "version": "0.2.5",
+ "version": "0.2.6",
"components": [
{
"name": "FluentAcrylicBrush",
diff --git a/docs/reference/api.md b/docs/reference/api.md
index 8b95cbf..b32fc2a 100644
--- a/docs/reference/api.md
+++ b/docs/reference/api.md
@@ -1,6 +1,6 @@
# FluentKit API reference
-> Generated from `FluentKit` version `0.2.5`. Do not edit this file by hand.
+> Generated from `FluentKit` version `0.2.6`. Do not edit this file by hand.
## Components
diff --git a/fluentkit-api/SKILL.md b/fluentkit-api/SKILL.md
index 817177e..4beeb6a 100644
--- a/fluentkit-api/SKILL.md
+++ b/fluentkit-api/SKILL.md
@@ -1,33 +1,45 @@
---
name: fluentkit-api
-description: Use FluentKit.Blazor correctly when building a separate Blazor WASM, Server, or MAUI Blazor Hybrid product. Versioned with the FluentKit package.
+description: Use the exact FluentKit.Blazor API and integration guidance shipped by the package referenced by the current Blazor or MAUI Blazor application.
---
-# FluentKit application
-
-This skill is for using FluentKit from another application. It is not the repository
-maintainer workflow. The bundle version in `metadata.json` must match the installed
-`FluentKit.Blazor` package exactly.
-
-## Required workflow
-
-1. Inspect the app's project files and package lock/assets to identify its
- `FluentKit.Blazor` version and hosting model.
-2. Read `references/api.json` for the exact component parameters, callbacks, slots, enums,
- and services in this bundle's version. Do not invent a parameter from a newer release.
-3. Follow `references/setup.md` before adding components. The fixed static asset base is
- `_content/FluentKit`, not `_content/FluentKit.Blazor`.
-4. Keep `ThemeProvider` at the root, register the theme/accent/overlay services, and render
- one `FluentOverlayHost` for overlays.
-5. Prefer token variables and existing component parameters over app-local hard-coded Fluent
- values. Use the sample route in the API JSON to find a runnable example.
-6. After changes, build the app and verify static assets and interactive overlays.
-
-## References
-
-- [Setup](references/setup.md)
-- [Theming and tokens](references/theming-and-tokens.md)
-- [Overlays](references/overlays.md)
-- [Troubleshooting](references/troubleshooting.md)
-- [Sample routes](references/sample-routes.md)
-- [Generated API JSON](references/api.json)
+# FluentKit package contract bootstrap
+
+This is a permanent bootstrap skill. It is installed once and resolves the version-specific
+agent contract from the `FluentKit.Blazor` package restored by the consumer project. It must
+never use a release ZIP, online "latest" API snapshot, or a different package version.
+
+## Resolve the exact package contract
+
+1. If the user names a project, use that project. Otherwise inspect the current directory for the nearest `.csproj`, `.sln`, or `.slnx` containing a `FluentKit.Blazor` reference. If more than one valid project remains, ask which project is in scope.
+2. Restore the selected project. If its directory contains `packages.lock.json`, use locked
+ restore so the resolved dependency graph cannot change silently.
+3. Run the resolver script shipped beside this `SKILL.md`, matching the host operating system.
+ Substitute the installed skill directory for ``:
+
+ ```bash
+ bash /scripts/resolve-fluentkit.sh /absolute/path/to/Consumer.csproj
+ ```
+
+ ```powershell
+ \scripts\resolve-fluentkit.ps1 -Project C:\path\to\Consumer.csproj
+ ```
+
+4. If the project requires MSBuild properties or a private/local NuGet source to evaluate its
+ package references, forward them with `--property Name=Value` / `-Property Name=Value` or
+ `--source PATH` / `-Source PATH`.
+
+5. The resolver queries `FluentKitAgentManifestPath` and `FluentKitAgentSkillPath` through
+ `dotnet msbuild`. Read the returned package-local `SKILL.md` completely before acting.
+6. If either property is absent, explain that the package predates the agent contract and ask
+ the user to upgrade to the first contract-bearing FluentKit release. Do not fall back to
+ reflection or a downloaded historical bundle.
+
+The package-local contract contains the exact `api.json`, `api.md`, and integration references.
+Follow those instructions for the remainder of the task.
+
+## Safety and scope
+
+- Never write into the user's skill directory during package restore or application builds.
+- Do not assume the package's latest published version is the version used by the app.
+- Keep the fixed static asset base `_content/FluentKit`, not `_content/FluentKit.Blazor`.
diff --git a/fluentkit-api/agents/openai.yaml b/fluentkit-api/agents/openai.yaml
new file mode 100644
index 0000000..41306cb
--- /dev/null
+++ b/fluentkit-api/agents/openai.yaml
@@ -0,0 +1,7 @@
+interface:
+ display_name: "FluentKit API"
+ short_description: "Use the exact installed FluentKit API"
+ default_prompt: "Use $fluentkit-api to build against the exact FluentKit.Blazor package in this application."
+
+policy:
+ allow_implicit_invocation: true
diff --git a/fluentkit-api/metadata.json b/fluentkit-api/metadata.json
deleted file mode 100644
index b96038e..0000000
--- a/fluentkit-api/metadata.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "fluentkit-api",
- "fluentkitPackage": "FluentKit.Blazor",
- "fluentkitVersion": "0.2.5",
- "apiReference": "references/api.json",
- "sourceTag": "v0.2.5"
-}
diff --git a/fluentkit-api/references/api.json b/fluentkit-api/references/api.json
deleted file mode 100644
index 87a2b53..0000000
--- a/fluentkit-api/references/api.json
+++ /dev/null
@@ -1,5422 +0,0 @@
-{
- "assembly": "FluentKit",
- "version": "0.2.5",
- "components": [
- {
- "name": "FluentAcrylicBrush",
- "fullName": "FluentKit.Effects.FluentAcrylicBrush",
- "category": "Effects",
- "sample": "/Effects/Acrylic",
- "genericParameters": [],
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "FallbackColor",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Overrides the theme-default fallback color shown when the browser has no backdrop-filter support, or under prefers-reduced-transparency. Accepts any valid CSS color.",
- "settable": true
- },
- {
- "name": "Kind",
- "type": "FluentKit.Effects.AcrylicKind",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "TintColor",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Overrides the theme-default tint color (WinUI\u0027s AcrylicBrush.TintColor). Accepts any valid CSS color \u2014 hex, rgb()/rgba(), a named color, whatever the application already has on hand \u2014 since the actual blending is done with color-mix() rather than requiring an r,g,b triplet.",
- "settable": true
- },
- {
- "name": "TintLuminosityOpacity",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Mirrors WinUI\u0027s AcrylicBrush.TintLuminosityOpacity, 0.0-1.0. Real Acrylic composites TWO tint passes: a luminosity-blended wash against the blurred backdrop (keeps the tint reading consistently regardless of how light/dark whatever\u0027s actually behind the panel is) UNDER the flat TintOpacity wash above. Left unset, the brush keeps its simpler single-layer tint (the original behavior) \u2014 set this to opt into the closer two-layer approximation.",
- "settable": true
- },
- {
- "name": "TintOpacity",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Overrides the theme-default flat tint wash opacity (WinUI\u0027s AcrylicBrush.TintOpacity), 0.0-1.0. This is the single color layer sitting on top of the live blur.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentAutoSuggestBox",
- "fullName": "FluentKit.Composite.FluentAutoSuggestBox\u00601",
- "category": "Composite",
- "sample": "/Composite/AutoSuggestBox",
- "genericParameters": [
- "TValue"
- ],
- "summary": "Mirrors WinUI\u0027s AutoSuggestBox / fluent-svelte\u0027s AutoSuggestBox.svelte \u2014 a free-typing TextBox with a live-filtered suggestion dropdown underneath, distinct from ComboBox\u0027s editable mode in that the typed text is NOT required to match any item: is always whatever the user typed, fires with that raw text on Enter with no suggestion highlighted, and picking a suggestion (click, or Enter while one IS highlighted) fires separately and just replaces Text with the suggestion\u0027s Name \u2014 there\u0027s no persistent \u0022selected value\u0022 the way ComboBox has Value. Composes FluentTextBox (same \u0022don\u0027t reimplement input chrome\u0022 rule NumberBox follows) and, like ComboBox/ContentDialog/NumberBox\u0027s Expanded popout, stays self-contained/absolutely positioned rather than going through IOverlayService \u2014 the dropdown needs to be exactly the trigger\u0027s width, same reasoning as ComboBox\u0027s own dropdown.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "EnableSearchIcon",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "When true, shows a search icon on the right of the box (via TextBox\u0027s Buttons slot). Once there\u0027s text in the box, that icon swaps to a \u0022clear\u0022 icon that empties Text on click \u2014 same idea as the search field on the demo TextBox above, just wired in as an opt-in here.",
- "settable": true
- },
- {
- "name": "Filter",
- "type": "Func\u003CString, AutoSuggestBoxItem\u003CTValue\u003E, Boolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Optional custom match predicate over (typed text, candidate item). Defaults to a case-insensitive substring match against \u2014 deliberately \u0022contains\u0022 rather than ComboBox\u0027s \u0022starts-with\u0022, since AutoSuggestBox is meant for search-as-you-type against arbitrary positions in the name, not prefix completion.",
- "settable": true
- },
- {
- "name": "Header",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ItemTemplate",
- "type": "RenderFragment\u003CAutoSuggestBoxItem\u003CTValue\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Optional custom row content for each suggestion. Falls back to a plain \u003Cspan\u003E@item.Name\u003C/span\u003E, same default ComboBox\u0027s ItemTemplate uses.",
- "settable": true
- },
- {
- "name": "Items",
- "type": "IReadOnlyList\u003CAutoSuggestBoxItem\u003CTValue\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MaxSuggestions",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Upper bound on how many matches are shown at once.",
- "settable": true
- },
- {
- "name": "Placeholder",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "QuerySubmitted",
- "type": "EventCallback\u003CString\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Fired on Enter when no suggestion is highlighted \u2014 the raw typed text is the query, same as WinUI\u0027s QuerySubmitted with a null ChosenSuggestion.",
- "settable": true
- },
- {
- "name": "SuggestionChosen",
- "type": "EventCallback\u003CAutoSuggestBoxItem\u003CTValue\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Fired when the user picks a suggestion \u2014 click, or Enter while one is highlighted via arrow keys. Text is set to the suggestion\u0027s Name as part of the same interaction.",
- "settable": true
- },
- {
- "name": "Text",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "The raw typed text. Two-way bindable \u2014 always reflects what\u0027s in the box, never snapped back to a suggestion\u0027s Name unless the user actually picks one.",
- "settable": true
- },
- {
- "name": "TextChanged",
- "type": "EventCallback\u003CString\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentButton",
- "fullName": "FluentKit.Primitives.FluentButton",
- "category": "Primitives",
- "sample": "/Primitives/Button",
- "genericParameters": [],
- "summary": "Inherits for optional XAML-style Padding/Width/Height/ MinWidth/MinHeight/MaxWidth/MaxHeight overrides \u2014 see that class for defaults/semantics.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Height",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Href",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "The URL to navigate to. Setting this renders the button as an \u003Ca\u003E element instead of a \u003Cbutton\u003E \u2014 required for links to actually work (a \u003Cbutton\u003E ignores href entirely). Typically paired with for WinUI HyperlinkButton parity, but Href works with any variant if you want an accent/standard-styled button that\u0027s really a link.",
- "settable": true
- },
- {
- "name": "MaxHeight",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MaxWidth",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MinHeight",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MinWidth",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "OnClick",
- "type": "EventCallback\u003CMicrosoft.AspNetCore.Components.Web.MouseEventArgs\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Padding",
- "type": "Nullable\u003CFluentKit.Common.Thickness\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Inner spacing between the border and content, all sides. Unset = the control\u0027s CSS default.",
- "settable": true
- },
- {
- "name": "Target",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Anchor \u0022target\u0022 attribute. Defaults to \u0022_blank\u0022 (opens in a new tab) whenever is set, matching typical hyperlink-button expectations. Set to \u0022_self\u0022 to navigate in-place.",
- "settable": true
- },
- {
- "name": "Variant",
- "type": "FluentKit.Primitives.ButtonVariant",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Width",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentCalendarDatePicker",
- "fullName": "FluentKit.Composite.FluentCalendarDatePicker",
- "category": "Composite",
- "sample": "/Composite/Calendar",
- "genericParameters": [],
- "summary": "Ported from fluent-svelte\u0027s CalendarDatePicker.svelte \u2014 a button that shows the picked date (or a placeholder) and pops FluentCalendarView open beneath it on click. Per this project\u0027s own stated convention (NumberBox\u0027s Expanded popout, ComboBox/AutoSuggestBox\u0027s dropdown), this is self-contained/absolutely-positioned rather than routed through IOverlayService \u2014 fluent-svelte\u0027s own version wires CalendarView into its generic Flyout wrapper, but every other \u0022needs exact trigger width/position, not a generic floating panel\u0022 composite in this codebase has deliberately skipped the overlay service, and a date picker popout is the same shape of problem. Closing behavior mirrors fluent-svelte: picking a date closes the popout (its CalendarView listens for on:change and sets open = false); Escape closes it; and losing focus entirely closes it. The latter relies on FluentCalendarViewItem\u0027s cells (and this component\u0027s own header/ pagination buttons) using @onmousedown:preventDefault so clicking inside the popout never actually blurs the trigger button in the first place \u2014 without that, every internal focus change would incorrectly bubble a focusout up to this root and close the popout mid-click, the same problem AutoSuggestBox\u0027s dropdown solved the same way.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Blackout",
- "type": "IReadOnlyList\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Locale",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Max",
- "type": "Nullable\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Min",
- "type": "Nullable\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Placeholder",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "SelectionMode",
- "type": "FluentKit.Composite.CalendarSelectionMode",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Single (default), Multiple, or Range. See . In Multiple/Range modes the picker\u0027s label shows a summary (count of dates / \u0022start \u2013 end\u0022) instead of a single formatted date, and the popout stays open after a pick so the user can keep selecting \u2014 it only closes on Escape, an outside click, or (Range only) once the second, completing date is chosen.",
- "settable": true
- },
- {
- "name": "Value",
- "type": "Nullable\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "ValueChanged",
- "type": "EventCallback\u003CNullable\u003CDateTime\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Values",
- "type": "IReadOnlyList\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Selected dates when is Multiple or Range. Two-way bindable. Ignored (and used instead) when SelectionMode is Single.",
- "settable": true
- },
- {
- "name": "ValuesChanged",
- "type": "EventCallback\u003CIReadOnlyList\u003CDateTime\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "WeekStart",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentCalendarView",
- "fullName": "FluentKit.Composite.FluentCalendarView",
- "category": "Composite",
- "sample": "/Composite/Calendar",
- "genericParameters": [],
- "summary": "Ported from fluent-svelte\u0027s CalendarView.svelte \u2014 lets a user browse and pick a date across three drill levels (days/months/years). Supports single-select (), multi-select (), and range-select (also , interpreted as a chronological [start, end] pair) via \u2014 svelte\u0027s source only ever had a boolean multiple prop; is kept as a back-compat alias for SelectionMode=\u0022CalendarSelectionMode.Multiple\u0022, and Range is a net-new addition on top of the ported behavior. Animations: fluent-svelte uses Svelte transition directives (fadeScale on view switch, fly on page turn). Blazor has no direct equivalent, so the same visual effect is reproduced with plain CSS keyframe animations in FluentCalendarView.razor.css, re-triggered every render by keying the table/tbody on (View, Page) via @key \u2014 changing a @key forces Blazor to tear down and recreate the element, which restarts the CSS animation named by the relevant direction class (fluent-calendar-view-table--anim-up/down, fluent-calendar-view-body--anim-up/down). Remaining deviation: arrow-key navigation now walks off the current page\u0027s edge, auto-pages, and refocuses the newly revealed cell (matching svelte) \u2014 see OnGridKeyDownAsync.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Blackout",
- "type": "IReadOnlyList\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Dates the user cannot select \u2014 rendered with a diagonal strike, matching fluent-svelte\u0027s blackout prop.",
- "settable": true
- },
- {
- "name": "Change",
- "type": "EventCallback\u003CNullable\u003CDateTime\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Mirrors fluent-svelte\u0027s on:change \u2014 fires whenever a day is picked/unpicked, same payload as ValueChanged. Kept separate so applications can distinguish \u0022value changed because the user just clicked a day\u0022 from any other source of a Value update.",
- "settable": true
- },
- {
- "name": "Floating",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "INTERNAL USE \u2014 applies the floating/flyout-shadow presentation used when embedded in CalendarDatePicker\u0027s popout, mirroring fluent-svelte\u0027s __floating prop.",
- "settable": true
- },
- {
- "name": "Headers",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Shows a small overline label (month abbreviation / year) on the first day of each month or first month of each year, matching fluent-svelte\u0027s headers prop.",
- "settable": true
- },
- {
- "name": "Locale",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Locale used for month/weekday names and header text. Defaults to the current culture if unset, matching fluent-svelte inferring from navigator.language.",
- "settable": true
- },
- {
- "name": "Max",
- "type": "Nullable\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Min",
- "type": "Nullable\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Multiple",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Enables multi-date selection, matching fluent-svelte\u0027s multiple prop. When true, clicking days toggles membership in instead of setting . Superseded by \u2014 kept for back-compat; setting this true without touching is equivalent to SelectionMode=\u0022CalendarSelectionMode.Multiple\u0022.",
- "settable": true
- },
- {
- "name": "SelectionMode",
- "type": "FluentKit.Composite.CalendarSelectionMode",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Single (default), Multiple, or Range. Net-new on top of fluent-svelte\u0027s original single/multiple-only API. See for what each does.",
- "settable": true
- },
- {
- "name": "Value",
- "type": "Nullable\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "The currently selected date, or null. Two-way bindable. Ignored when is true.",
- "settable": true
- },
- {
- "name": "ValueChanged",
- "type": "EventCallback\u003CNullable\u003CDateTime\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Values",
- "type": "IReadOnlyList\u003CDateTime\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "The currently selected dates when is Multiple or Range. Two-way bindable. In Range mode this holds at most two entries, always chronologically ordered: [start] while a range is being picked, or [start, end] once complete.",
- "settable": true
- },
- {
- "name": "ValuesChange",
- "type": "EventCallback\u003CIReadOnlyList\u003CDateTime\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Mirrors fluent-svelte\u0027s on:change payload for multi-select mode; also used for Range mode, firing after both the start and (later) the completing end click.",
- "settable": true
- },
- {
- "name": "ValuesChanged",
- "type": "EventCallback\u003CIReadOnlyList\u003CDateTime\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "View",
- "type": "FluentKit.Composite.CalendarViewMode",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "The current drill level. Two-way bindable \u2014 clicking the header or a month/year cell changes it, same as fluent-svelte letting the user manually change view via header clicks.",
- "settable": true
- },
- {
- "name": "ViewChanged",
- "type": "EventCallback\u003CFluentKit.Composite.CalendarViewMode\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "WeekStart",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "0 = Sunday .. 6 = Saturday, matching fluent-svelte\u0027s weekStart.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentCalendarViewItem",
- "fullName": "FluentKit.Composite.FluentCalendarViewItem",
- "category": "Composite",
- "sample": "/Composite/Calendar",
- "genericParameters": [],
- "summary": "Ported from fluent-svelte\u0027s CalendarViewItem.svelte \u2014 a single round day/month/year cell button. Stateless: FluentCalendarView owns all selection/paging state and just tells each cell what to look like. @onmousedown:preventDefault is set unconditionally (same trick AutoSuggestBox\u0027s dropdown rows use) so clicking a cell inside CalendarDatePicker\u0027s self-contained popout doesn\u0027t blur the picker\u0027s trigger button before OnClick fires \u2014 that blur would otherwise closed the popout via focusout before the date selection registers.",
- "parameters": [
- {
- "name": "Blackout",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Current",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Header",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Small overline label shown above the cell content \u2014 the month abbreviation on the 1st day of a month (Days view) or the year on January (Months view), gated by FluentCalendarView\u0027s Headers parameter.",
- "settable": true
- },
- {
- "name": "InRange",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Range-selection mode only: true when this cell lies strictly between the range\u0027s start and end (exclusive of both edges), rendered with a continuous band background instead of the round single/multiple selection pill.",
- "settable": true
- },
- {
- "name": "OnClick",
- "type": "Microsoft.AspNetCore.Components.EventCallback",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "OnKeyDown",
- "type": "EventCallback\u003CMicrosoft.AspNetCore.Components.Web.KeyboardEventArgs\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "OutOfRange",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "RangeEnd",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Range-selection mode only: true when this cell is the range\u0027s end date.",
- "settable": true
- },
- {
- "name": "RangeStart",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Range-selection mode only: true when this cell is the range\u0027s start date.",
- "settable": true
- },
- {
- "name": "Selected",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "TabIndex",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Variant",
- "type": "FluentKit.Composite.CalendarViewItemVariant",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentCard",
- "fullName": "FluentKit.Primitives.FluentCard",
- "category": "Primitives",
- "sample": "/Primitives/Card",
- "genericParameters": [],
- "summary": "Generic surface primitive \u2014 not a literal WinUI control (WinUI doesn\u0027t ship a \u0022Card\u0022), but the CardBackgroundFillColorDefault / CardStrokeColorDefault tokens exist precisely because so many real controls (Settings cards, NavigationView content, Expander header/content) are built on this exact surface. Everything else that needs a bordered/tinted panel should compose this rather than repeat the background/border/radius trio inline.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentCheckBox",
- "fullName": "FluentKit.Primitives.FluentCheckBox",
- "category": "Primitives",
- "sample": "/Primitives/CheckBox",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s CheckBox, including the three-state (indeterminate) mode used for \u0022select all\u0022 style parent checkboxes. Built as a native ARIA checkbox rather than \u003Cinput type=\u0022checkbox\u0022\u003E so the indeterminate visual state is fully CSS-driven (native inputs only expose indeterminate via JS interop on the DOM property, not an attribute).",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Checked",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "CheckedChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Indeterminate",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Third state, matching WinUI\u0027s IsThreeState CheckBox. When true, the glyph renders as a dash regardless of , and clicking cycles unchecked -\u003E checked -\u003E indeterminate.",
- "settable": true
- },
- {
- "name": "IndeterminateChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ThreeState",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentComboBox",
- "fullName": "FluentKit.Composite.FluentComboBox\u00601",
- "category": "Composite",
- "sample": "/Composite/ComboBox",
- "genericParameters": [
- "TValue"
- ],
- "summary": "Mirrors fluent-svelte\u0027s ComboBox.svelte (dropdown list with an optional editable/searchable text-box mode). Two render modes off : - false (default): a FluentButton-shaped trigger showing the selected item\u0027s Name, opens a positioned \u003Cul\u003E of ComboBoxItem-equivalent \u003Cli\u003E rows on click. - true: a FluentTextBox-shaped trigger the user can type into, filtering/matching items by Name prefix (case-insensitive), same as fluent-svelte\u0027s handleInput/searchValue flow. Unlike FluentMenuFlyout, this does NOT go through IOverlayService/FluentOverlayHost \u2014 the dropdown list needs to be exactly the trigger\u0027s width and grow from a specific list index (the menuOffset/menuGrowDirection choreography below), which is easiest to keep as a plain absolutely-positioned child of the component\u0027s own root (same as ComboBox.scss\u0027s own \u0060position: relative\u0060 \u002B \u0060.combo-box-dropdown { position: absolute }\u0060). A portal would only add indirection for zero benefit here since there\u0027s no anchor-to-elsewhere requirement.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Editable",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Whether the ComboBox can be searched/typed into (fluent-svelte\u0027s \u0060editable\u0060 prop).",
- "settable": true
- },
- {
- "name": "ItemTemplate",
- "type": "RenderFragment\u003CComboBoxItem\u003CTValue\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Optional custom row content for each dropdown item. Falls back to a plain \u003Cspan\u003E@item.Name\u003C/span\u003E when not supplied \u2014 same \u0022text unless you say otherwise\u0022 default as fluent-svelte\u0027s own ComboBoxItem (which likewise only exposes a default slot, no separate templating concept). Wraps in the same .fluent-combo-box-item \u003Cli\u003E (selection bar, hover/active states, focus ring) regardless of what you render inside \u2014 only the row\u0027s inner content is replaced, not its interaction chrome.",
- "settable": true
- },
- {
- "name": "Items",
- "type": "IReadOnlyList\u003CComboBoxItem\u003CTValue\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Placeholder",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "SearchValue",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Current text in the search box. Only meaningful when is true.",
- "settable": true
- },
- {
- "name": "SearchValueChanged",
- "type": "EventCallback\u003CString\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "SelectedTemplate",
- "type": "RenderFragment\u003CComboBoxItem\u003CTValue\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Optional custom content for the closed/collapsed trigger label in non-editable ( = false) mode, e.g. to show an icon \u002B name instead of plain text once something is selected. Only used when there IS a selection \u2014 the placeholder text always renders plain regardless. Has no effect in Editable mode, since that trigger is a real \u003Cinput\u003E and can only ever hold plain text.",
- "settable": true
- },
- {
- "name": "Value",
- "type": "TValue",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Currently selected item\u0027s value. Two-way bindable.",
- "settable": true
- },
- {
- "name": "ValueChanged",
- "type": "EventCallback\u003CTValue\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentContentDialog",
- "fullName": "FluentKit.Composite.FluentContentDialog",
- "category": "Composite",
- "sample": "/Composite/ContentDialog",
- "genericParameters": [],
- "summary": "Mirrors fluent-svelte\u0027s ContentDialog.svelte (WinUI\u0027s ContentDialog) \u2014 a modal dialog centered over a full-viewport \u0022smoke\u0022 scrim, with an optional Title, a scrollable body (ChildContent), and an optional Footer (typically 1-2 FluentButton instances laid out in an equal-width row, same as ContentDialog.scss\u0027s \u0060.content-dialog-footer { grid-auto-flow: column }\u0060). Deliberately NOT built on IOverlayService/FluentOverlayHost like FluentFlyout/FluentMenuFlyout/ FluentTooltip \u2014 those all position relative to a trigger ElementReference, whereas a modal dialog is always viewport-centered regardless of what invoked it, and needs its own full-screen scrim underneath (which the anchor-relative overlay model has no concept of). Self-contained, same reasoning as FluentComboBox\u0027s dropdown.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Closable",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Whether the dialog can be dismissed via the Escape key or by clicking the scrim. Set false to force the user through an explicit footer action (e.g. a destructive confirm dialog with no implicit \u0022cancel by clicking away\u0022).",
- "settable": true
- },
- {
- "name": "Darken",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Whether the scrim behind the dialog darkens the rest of the page (SmokeFillColorDefault). Svelte\u0027s \u0060darken\u0060 prop \u2014 false gives a fully transparent backdrop, still blocking interaction, just not visually dimmed.",
- "settable": true
- },
- {
- "name": "Footer",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Optional footer row, typically 1-2 FluentButton instances. Omit entirely for a dialog with no footer (matches svelte\u0027s \u0060$$slots.footer\u0060 conditional render).",
- "settable": true
- },
- {
- "name": "IsOpen",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Two-way bindable open state.",
- "settable": true
- },
- {
- "name": "IsOpenChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "OnBackdropClick",
- "type": "Microsoft.AspNetCore.Components.EventCallback",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Size",
- "type": "FluentKit.Composite.ContentDialogSize",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Title",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Title text shown as the dialog header (TextBlock Subtitle variant). Omit for a dialog with no header \u2014 just the body content.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentContextMenu",
- "fullName": "FluentKit.Composite.FluentContextMenu",
- "category": "Composite",
- "sample": "/Composite/ContextMenu",
- "genericParameters": [],
- "summary": "Right-click context menu attached to arbitrary wrapped content (ContextMenu.svelte). Reuses FluentMenuFlyout\u0027s whole shape (IOverlayService \u002B MenuFlyoutCloseContext \u002B FluentMenuFlyoutSurface) rather than re-solving positioning: instead of porting fluent-svelte\u0027s own bespoke mousePosition/menuPosition clamp math, this points OverlayService.Show at a 0\u00D70 fixed-position \u0022cursor anchor\u0022 div moved to the click coordinates \u2014 the existing overlay-interop.js computePosition() (bottom-of-anchor, flips to top / clamps horizontally when there\u0027s no room) then does the exact same collision-avoidance job for free, with no new JS.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The content that reveals the context menu on right-click.",
- "settable": true
- },
- {
- "name": "CloseOnSelect",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsOpen",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "IsOpenChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MenuItems",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The menu\u0027s items \u2014 FluentMenuFlyoutItem/FluentMenuFlyoutDivider elements.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentDivider",
- "fullName": "FluentKit.Primitives.FluentDivider",
- "category": "Primitives",
- "sample": "/Design/SpacingAndGeometry",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s plain content-area Divider (a hairline rule), not MenuFlyoutDivider \u2014 that one lives in the Composite/Overlay layer since it only makes sense inside a MenuFlyout\u0027s own padding rules. This is the general-purpose one for separating sections of ordinary page content.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Vertical",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentDropDownButton",
- "fullName": "FluentKit.Composite.FluentDropDownButton",
- "category": "Composite",
- "sample": "/Composite/DropDownButton",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s DropDownButton \u2014 a button that always opens a MenuFlyout rather than firing a click action itself (unlike SplitButton, which splits those two concerns into separate regions). Thin composition over with a button-styled, chevron-suffixed trigger in place of MenuFlyout\u0027s plain ChildContent span.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsOpen",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "IsOpenChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MenuItems",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The menu\u0027s items \u2014 FluentMenuFlyoutItem/FluentMenuFlyoutDivider elements.",
- "settable": true
- },
- {
- "name": "Placement",
- "type": "FluentKit.Overlay.OverlayPlacement",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Variant",
- "type": "FluentKit.Primitives.ButtonVariant",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentExpander",
- "fullName": "FluentKit.Primitives.FluentExpander",
- "category": "Primitives",
- "sample": "/Primitives/Expander",
- "genericParameters": [],
- "summary": "Expander \u2014 header \u002B collapsible content area, header click/Enter/Space toggles. Animation uses fluent-svelte\u0027s clip\u002Bslide technique (max-block-size snap \u002B translateY on the content) rather than the CSS-grid 0fr/1fr trick, which has inconsistent auto-minimum-size behavior across browsers (notably Safari) and can leave the row stuck above 0 with content still visible.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Header title content.",
- "settable": true
- },
- {
- "name": "Content",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Collapsible body content.",
- "settable": true
- },
- {
- "name": "Icon",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Optional leading icon in the header.",
- "settable": true
- },
- {
- "name": "IsExpanded",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "IsExpandedChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentFlyout",
- "fullName": "FluentKit.Composite.FluentFlyout",
- "category": "Composite",
- "sample": "/Composite/Flyout",
- "genericParameters": [],
- "summary": "Generic positioned content flyout (WinUI 3 Flyout). Click/tap the trigger to open, click outside or press Escape to close. Built entirely on IOverlayService/FluentOverlayHost (Phase 3 infra) \u2014 this is the second overlay composite after FluentTooltip and the base every other overlay composite (MenuFlyout, ComboBox, TeachingTip, DropDownButton...) is expected to follow the same shape of.",
- "parameters": [
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The element that opens the flyout when clicked.",
- "settable": true
- },
- {
- "name": "FlyoutContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The content shown inside the flyout surface once open.",
- "settable": true
- },
- {
- "name": "IsOpen",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Two-way bindable open state, so callers can drive the flyout externally (e.g. open it from code, or close it from a \u0022Save\u0022/\u0022Cancel\u0022 button inside FlyoutContent).",
- "settable": true
- },
- {
- "name": "IsOpenChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "LightDismiss",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Closes when clicking outside the flyout. Set false for flyouts that should only be dismissed programmatically or via an explicit action inside the content.",
- "settable": true
- },
- {
- "name": "Placement",
- "type": "FluentKit.Overlay.OverlayPlacement",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Preferred side of the trigger the flyout opens on; flips automatically if there\u0027s no room.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentIcon",
- "fullName": "FluentKit.Primitives.FluentIcon",
- "category": "Primitives",
- "sample": "/Design/IconBrowser",
- "genericParameters": [],
- "summary": "Renders a glyph from the MIT-licensed Fluent System Icons webfont (https://github.com/microsoft/fluentui-system-icons, \u00A9 Microsoft Corporation) \u2014 see wwwroot/Icons/FluentSystemIcons-Regular.css. Replaces the earlier approach of wrapping SVG Icon instances from Microsoft.FluentUI.AspNetCore.Components.Icons: that package pulls in the full Microsoft.FluentUI.AspNetCore.Components core RCL as a transitive dependency, which auto-bundles its own scoped CSS into the host app\u0027s .styles.css and visually clashes with our tokens (see git history for the multi-hour static-web-assets fight that caused). The font approach has zero managed dependency and can\u0027t leak component CSS since it\u0027s just a CSS class. Name is the glyph identity as published in the font\u0027s own CSS, minus the \u0022icon-\u0022 prefix \u2014 e.g. \u0022ic_fluent_home_24_regular\u0022, \u0022ic_fluent_settings_20_regular\u0022. Use for compile-time-checked constants covering common glyphs, or copy any class name straight out of wwwroot/Icons/FluentSystemIcons-Regular.css / the font\u0027s own icon gallery (fluentui-system-icons repo) for anything not listed there. The glyph\u0027s own baked-in size suffix (_16/_20/_24/...) only affects which pixel grid it was drawn on \u2014 below independently controls the actual rendered box, any combination works, but picking the closest baked-in size gives the crispest result. Decorative by default (aria-hidden) since icons are almost always paired with visible text (see NavigationViewItem); pass aria-hidden=\u0022false\u0022 plus your own aria-label via AdditionalAttributes for icon-only usage. Applications must link wwwroot/Icons/FluentSystemIcons-Regular.css once in their host page (e.g. index.html / App.razor), same as tokens.css: \u003Clink rel=\u0022stylesheet\u0022 href=\u0022_content/FluentKit/Icons/FluentSystemIcons-Regular.css\u0022 /\u003E",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Name",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Size",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Pixel size for both width and height of the icon box, and its font-size (the glyph scales with the font box). Independent of whatever size is baked into .",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentIconButton",
- "fullName": "FluentKit.Primitives.FluentIconButton",
- "category": "Primitives",
- "sample": "/Primitives/IconButton",
- "genericParameters": [],
- "summary": "Ported from fluent-svelte\u0027s IconButton.svelte \u2014 bare icon-only button, fixed square box (icon centered, no label padding). Renders as an anchor when is set (matching svelte\u0027s svelte:element this={href ? \u0022a\u0022 : \u0022button\u0022} element-swap), otherwise a button. Supports the same set as (Standard/Accent/Subtle/Hyperlink) so icon-only and labeled buttons stay visually consistent \u2014 defaults to Subtle to preserve the original transparent-by-default look.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Height",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Href",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "When set (and not Disabled), renders an \u003Ca\u003E instead of a \u003Cbutton\u003E.",
- "settable": true
- },
- {
- "name": "MaxHeight",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MaxWidth",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MinHeight",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MinWidth",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "OnClick",
- "type": "EventCallback\u003CMicrosoft.AspNetCore.Components.Web.MouseEventArgs\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Padding",
- "type": "Nullable\u003CFluentKit.Common.Thickness\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Inner spacing between the border and content, all sides. Unset = the control\u0027s CSS default.",
- "settable": true
- },
- {
- "name": "Variant",
- "type": "FluentKit.Primitives.ButtonVariant",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Width",
- "type": "Nullable\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentInfoBadge",
- "fullName": "FluentKit.Primitives.FluentInfoBadge",
- "category": "Primitives",
- "sample": "/Primitives/InfoBadge",
- "genericParameters": [],
- "summary": "Small non-intrusive notification/status dot or count pill. Empty (no ChildContent) renders as a plain 8px dot \u2014 used to flag \u0022something changed\u0022 without a number; with ChildContent (typically a short number/text) it grows into a pill, same behavior as WinUI\u0027s InfoBadge Value/IconSource split.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Severity",
- "type": "FluentKit.Primitives.InfoBadgeSeverity",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentInfoBar",
- "fullName": "FluentKit.Primitives.FluentInfoBar",
- "category": "Primitives",
- "sample": "/Primitives/InfoBar",
- "genericParameters": [],
- "summary": "App-wide status message banner \u2014 Title/Message text, an optional action, and (by default) a close button. Ported from fluent-svelte\u0027s InfoBar: reuses (same five-value severity ramp as FluentInfoBadge, which is also this component\u0027s default icon slot \u2014 same simplification fluent-svelte makes rather than pulling in WinUI\u0027s actual per-severity symbol glyphs) and the same --system-fill-color-*-background tokens the InfoBadge tokens comment already calls out as shared between the two components. Skips fluent-svelte\u0027s JS-measured wrap detection (offsetTop comparisons to add message-wrapped/action-wrapped classes when the title/message/action overflow onto their own line) \u2014 plain flexbox wrap gives acceptably close layout without a measurement pass, and this is a static banner, not something worth a resize-observer for.",
- "parameters": [
- {
- "name": "Action",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Optional call-to-action content (typically a FluentButton), right-aligned in the content row.",
- "settable": true
- },
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Additional message content appended after \u2014 for rich content beyond plain text.",
- "settable": true
- },
- {
- "name": "Closable",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Whether the built-in close button is rendered.",
- "settable": true
- },
- {
- "name": "Icon",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Overrides the default severity-colored icon.",
- "settable": true
- },
- {
- "name": "IsOpen",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "IsOpenChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Message",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Severity",
- "type": "FluentKit.Primitives.InfoBadgeSeverity",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Title",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentListView",
- "fullName": "FluentKit.Primitives.FluentListView\u00601",
- "category": "Primitives",
- "sample": "/Primitives/ListView",
- "genericParameters": [
- "TValue"
- ],
- "summary": "Mirrors WinUI\u0027s ListView as a vertical item container. Owns roving-focus arrow-key navigation (Up/Down move focus between items, Home/End jump to the ends) via IFocusableListItem, same as before. Two composition modes, chosen by whether is set: - Data-bound (Items set): rows are generated from , with selection driven by and the SelectedValue/SelectedValues parameters below \u2014 same \u0022Items \u002B optional ItemTemplate\u0022 shape as FluentComboBox/FluentAutoSuggestBox. - Freeform (Items left null): the original ChildContent-only mode, where the caller places FluentListViewItem children manually and owns Selected/OnSelect itself (e.g. when selection needs to be driven by external context, the way FluentNavigationView\u0027s pane does it). TValue only matters for the data-bound mode \u2014 freeform callers can use any TValue (e.g. \u0022object\u0022) since it\u0027s never referenced.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Class",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Extra class(es) appended to the root element. A plain parameter (not attribute splatting) so it composes instead of clobbering the base \u0022fluent-list-view\u0022 class.",
- "settable": true
- },
- {
- "name": "ItemTemplate",
- "type": "RenderFragment\u003CListViewItem\u003CTValue\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Optional custom row content for each item. Falls back to a plain @item.Name when not supplied, same default ComboBox/AutoSuggestBox use. Only consulted when is set.",
- "settable": true
- },
- {
- "name": "Items",
- "type": "IReadOnlyList\u003CListViewItem\u003CTValue\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Data source for the data-bound mode. When set, rows are generated from this list instead of \u2014 see the type\u0027s own doc comment for the two modes.",
- "settable": true
- },
- {
- "name": "Role",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "ARIA role for the container. \u0022list\u0022 for a plain list, \u0022listbox\u0022 if items are meant to read as a single-select widget to AT.",
- "settable": true
- },
- {
- "name": "SelectedValue",
- "type": "TValue",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Currently selected value in mode. Two-way bindable.",
- "settable": true
- },
- {
- "name": "SelectedValueChanged",
- "type": "EventCallback\u003CTValue\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "SelectedValues",
- "type": "IReadOnlyList\u003CTValue\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Currently selected values in mode. Two-way bindable.",
- "settable": true
- },
- {
- "name": "SelectedValuesChanged",
- "type": "EventCallback\u003CIReadOnlyList\u003CTValue\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "SelectionMode",
- "type": "FluentKit.Primitives.ListViewSelectionMode",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "How many rows can be selected at once. Only meaningful in data-bound mode \u2014 freeform mode\u0027s selection is entirely caller-driven via each FluentListViewItem\u0027s own Selected/OnSelect.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentListViewItem",
- "fullName": "FluentKit.Primitives.FluentListViewItem",
- "category": "Primitives",
- "sample": "/Primitives/ListView",
- "genericParameters": [],
- "summary": "Single row inside a FluentListView. Selection is intentionally NOT owned here (see FluentListView\u0027s doc comment) \u2014 Selected is a plain bool the caller drives, OnSelect fires on click/Enter/Space (native \u003Cbutton\u003E gives Space/Enter for free, same reasoning as FluentNavigationViewItem). Root element registers with the cascaded FluentListView purely for arrow-key roving focus (via IFocusableListItem); it works fine with no FluentListView ancestor too \u2014 List is optional.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "AutoHeight",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Switches the row from ListView\u0027s default fixed 34px height to auto-height with a min-height floor \u2014 set by FluentListView\u003CTValue\u003E whenever a custom ItemTemplate is in play, since template content (e.g. a two-line contact row) is rarely exactly one line tall.",
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "OnSelect",
- "type": "Microsoft.AspNetCore.Components.EventCallback",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Selected",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ShowCheckmark",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Shows a decorative checkbox glyph at the start of the row, reflecting . Set by FluentListView\u003CTValue\u003E when SelectionMode is Multiple \u2014 purely visual, the row\u0027s own button still owns the click (no nested interactive element), same reasoning FluentComboBoxItem\u0027s checkmark uses.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentMenuBar",
- "fullName": "FluentKit.Composite.FluentMenuBar",
- "category": "Composite",
- "sample": "/Composite/MenuBar",
- "genericParameters": [],
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentMenuBarItem",
- "fullName": "FluentKit.Composite.FluentMenuBarItem",
- "category": "Composite",
- "sample": "/Composite/MenuBar",
- "genericParameters": [],
- "summary": "A single top-level entry in a FluentMenuBar (MenuBarItem.svelte) \u2014 e.g. \u0022File\u0022/\u0022Edit\u0022/\u0022View\u0022 in an app menu bar. Opens its (typically FluentMenuFlyoutItem/FluentMenuFlyoutDivider children, reusing FluentMenuFlyoutSurface \u002B MenuFlyoutCloseContext exactly like FluentMenuFlyoutItem\u0027s own cascading submenu does) below itself on click, Enter, or Space. Once one bar item\u0027s menu is open, hovering a sibling switches straight to its menu instead of requiring another click (svelte\u0027s handleMouseEnter/$currentMenu check), and ArrowLeft/ArrowRight walk between items with wraparound (svelte\u0027s sideNavigation context).",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The item\u0027s label.",
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Flyout",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The dropdown\u0027s contents \u2014 typically FluentMenuFlyoutItem/FluentMenuFlyoutDivider elements. Omit for a plain non-interactive bar entry.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentMenuFlyout",
- "fullName": "FluentKit.Composite.FluentMenuFlyout",
- "category": "Composite",
- "sample": "/Composite/MenuFlyout",
- "genericParameters": [],
- "summary": "Command menu opened from a trigger element (MenuFlyoutWrapper.svelte). Built on the same IOverlayService/FluentOverlayHost infra as FluentFlyout \u2014 this is effectively FluentFlyout with a MenuFlyoutCloseContext threaded through so its FluentMenuFlyoutItem children (and their own cascading submenus) know how to collapse the whole tree on selection. Note: fluent-svelte\u0027s MenuFlyoutWrapper also exposes an \u0060alignment\u0060 (start/center/end) prop alongside placement. That\u0027s not something OverlayPlacement/overlay-interop.js currently model (they only pick which side of the anchor and clamp to the viewport) \u2014 left out here rather than half-implemented; add it to OverlayPlacement/computePosition if an application needs it.",
- "parameters": [
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The element that opens the menu when clicked.",
- "settable": true
- },
- {
- "name": "Closable",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Whether the menu can be dismissed by conventional interaction at all.",
- "settable": true
- },
- {
- "name": "CloseOnSelect",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Whether selecting a standard/radio/toggle item closes the menu. Only applies if is true.",
- "settable": true
- },
- {
- "name": "IsOpen",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "IsOpenChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MatchAnchorWidth",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "When true, the menu is resized to exactly match the trigger\u0027s width (e.g. DropDownButton) instead of sizing to its own content.",
- "settable": true
- },
- {
- "name": "MenuItems",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The menu\u0027s items \u2014 FluentMenuFlyoutItem/FluentMenuFlyoutDivider elements.",
- "settable": true
- },
- {
- "name": "Placement",
- "type": "FluentKit.Overlay.OverlayPlacement",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentMenuFlyoutDivider",
- "fullName": "FluentKit.Composite.FluentMenuFlyoutDivider",
- "category": "Composite",
- "sample": "/Composite/MenuFlyout",
- "genericParameters": [],
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentMenuFlyoutItem",
- "fullName": "FluentKit.Composite.FluentMenuFlyoutItem",
- "category": "Composite",
- "sample": "/Composite/MenuFlyout",
- "genericParameters": [],
- "summary": "A single row inside a FluentMenuFlyout/FluentContextMenu (MenuFlyoutItem.svelte). Three input \u0022shapes\u0022 share one component, same as the Svelte original: Standard (plain command), Radio/Toggle (adds a bullet/checkmark glyph and a bindable ), and Cascading (opens a nested submenu instead of closing the tree on click). Radio mutual-exclusion is left to the caller (bind each item\u0027s own and flip siblings off in the click handler) rather than an internal group-value context \u2014 a menu\u0027s radio items are typically few and this keeps the item itself a plain, independently-bindable control, matching how little state fluent-svelte\u0027s own \u0060group\u0060 binding actually needs here.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Cascading",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Marks this item as having a cascading submenu, rendering in a nested overlay opened to the right (flips left if there\u0027s no room) instead of closing the menu tree when clicked.",
- "settable": true
- },
- {
- "name": "Checked",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Bindable checked state for Radio/Toggle variants.",
- "settable": true
- },
- {
- "name": "CheckedChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The item\u0027s label.",
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Flyout",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Content for the cascading submenu. Only used when is true \u2014 typically more \u003CFluentMenuFlyoutItem\u003E/\u003CFluentMenuFlyoutDivider\u003E elements.",
- "settable": true
- },
- {
- "name": "Hint",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Secondary trailing text \u2014 e.g. a keyboard accelerator hint (\u0022Ctrl\u002BC\u0022).",
- "settable": true
- },
- {
- "name": "Icon",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Optional leading icon slot.",
- "settable": true
- },
- {
- "name": "Indented",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Indents the label to line up with sibling items that have icons, even if this one doesn\u0027t render an .",
- "settable": true
- },
- {
- "name": "OnClick",
- "type": "Microsoft.AspNetCore.Components.EventCallback",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Raised when a non-cascading item is activated (click, Enter, or Space).",
- "settable": true
- },
- {
- "name": "Selected",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Styles the item as the current selection (e.g. the active view in a menu of views), independent of Radio/Toggle Checked state.",
- "settable": true
- },
- {
- "name": "Variant",
- "type": "FluentKit.Composite.MenuFlyoutItemVariant",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentMenuFlyoutSurface",
- "fullName": "FluentKit.Composite.FluentMenuFlyoutSurface",
- "category": "Composite",
- "sample": "/Composite/MenuFlyout",
- "genericParameters": [],
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "OnKeyDown",
- "type": "EventCallback\u003CMicrosoft.AspNetCore.Components.Web.KeyboardEventArgs\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentMicaPanel",
- "fullName": "FluentKit.Effects.FluentMicaPanel",
- "category": "Effects",
- "sample": "/Effects/Mica",
- "genericParameters": [],
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "BackgroundImageUrl",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Stand-in for the desktop wallpaper WinUI would otherwise sample directly via the OS compositor. Any image URL works (relative to wwwroot, or absolute). When omitted, the panel falls back to WinUI\u0027s own documented solid-color fallback.",
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Variant",
- "type": "FluentKit.Effects.MicaVariant",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentNavigationView",
- "fullName": "FluentKit.Composite.FluentNavigationView",
- "category": "Composite",
- "sample": "/Composite/NavigationView",
- "genericParameters": [],
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "AlwaysShowHeader",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "BackRequested",
- "type": "Microsoft.AspNetCore.Components.EventCallback",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "HeaderContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsBackButtonEnabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsBackButtonVisible",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsPaneOpen",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "IsPaneOpenChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ItemInvoked",
- "type": "EventCallback\u003CObject\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MenuItems",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "PaneDisplayMode",
- "type": "FluentKit.Composite.NavigationViewPaneDisplayMode",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "PaneFooter",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "PaneHeader",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "PaneHeaderIcon",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "SelectedValue",
- "type": "Object",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "SelectedValueChanged",
- "type": "EventCallback\u003CObject\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "SettingsRequested",
- "type": "Microsoft.AspNetCore.Components.EventCallback",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ShowSettingsButton",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentNavigationViewItem",
- "fullName": "FluentKit.Composite.FluentNavigationViewItem",
- "category": "Composite",
- "sample": "/Composite/NavigationView",
- "genericParameters": [],
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Icon",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsExpanded",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "IsExpandedChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsSelectable",
- "type": "Nullable\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Text",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Value",
- "type": "Object",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentNavigationViewItemHeader",
- "fullName": "FluentKit.Composite.FluentNavigationViewItemHeader",
- "category": "Composite",
- "sample": "/Composite/NavigationView",
- "genericParameters": [],
- "parameters": [
- {
- "name": "Text",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentNavigationViewItemSeparator",
- "fullName": "FluentKit.Composite.FluentNavigationViewItemSeparator",
- "category": "Composite",
- "sample": "/Composite/NavigationView",
- "genericParameters": [],
- "parameters": []
- },
- {
- "name": "FluentNumberBox",
- "fullName": "FluentKit.Composite.FluentNumberBox",
- "category": "Composite",
- "sample": "/Composite/NumberBox",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s NumberBox / fluent-svelte\u0027s NumberBox.svelte \u2014 a validated numeric TextBox with spin up/down buttons wired through FluentTextBox\u0027s Buttons slot (the plumbing added for this). Composes FluentTextBox rather than reimplementing input chrome, same as fluent-svelte\u0027s own NumberBox does with its TextBox \u002B TextBoxButton \u0022buttons\u0022 slot. Text is only committed back to when it parses as a valid double \u2014 an in-progress edit like \u0022-\u0022 or \u00223.\u0022 is kept on screen as-is without round-tripping through Value, same \u0022don\u0027t fight the user mid-keystroke\u0022 rule ComboBox\u0027s editable mode already established.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Header",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Max",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Min",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Mode",
- "type": "FluentKit.Composite.NumberBoxSpinButtonMode",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Compact: two small \u002B/- buttons side by side, always inside the box. Expanded (default): no buttons at rest \u2014 a bigger stacked up/down control pops out from the box\u0027s edge on focus.",
- "settable": true
- },
- {
- "name": "Placeholder",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Step",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Value",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "ValueChanged",
- "type": "EventCallback\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentOverlayHost",
- "fullName": "FluentKit.Overlay.FluentOverlayHost",
- "category": "Infrastructure",
- "sample": "/Composite/Flyout",
- "genericParameters": [],
- "parameters": []
- },
- {
- "name": "FluentPasswordBox",
- "fullName": "FluentKit.Primitives.FluentPasswordBox",
- "category": "Primitives",
- "sample": "/Primitives/PasswordBox",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s PasswordBox, specifically its reveal-button variant: a right-aligned eye icon button that temporarily shows the plaintext value while held/toggled, rather than a bare \u003Cinput type=\u0022password\u0022\u003E with no way to check what you typed. Composes \u002B the same way NumberBox composes TextBox \u002B TextBoxButton \u2014 swapping the underlying input\u0027s Type between \u0022password\u0022 and \u0022text\u0022 rather than reimplementing input chrome from scratch.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Header",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Placeholder",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Value",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "ValueChanged",
- "type": "EventCallback\u003CString\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentPersonPicture",
- "fullName": "FluentKit.Primitives.FluentPersonPicture",
- "category": "Primitives",
- "sample": "/Primitives/PersonPicture",
- "genericParameters": [],
- "summary": "Ported from fluent-svelte\u0027s PersonPicture.svelte \u2014 round avatar. Shows \u003Cimg Src\u003E when set and loads successfully; falls back to initials derived from (or ChildContent, or a Badge slot) otherwise. Mirrors svelte\u0027s own error local-state-on-img-error fallback: if the image 404s/fails to decode, this flips to the initials div instead of showing a broken image icon, same as the source\u0027s on:error={() =\u003E (error = true)}.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Alt",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Alt text for the image, and the source for the initials fallback (first letter of each space-separated word, uppercased) when there\u0027s no image or no .",
- "settable": true
- },
- {
- "name": "Badge",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Optional badge slot rendered in the top-right corner (e.g. a presence dot or notification count), matching fluent-svelte\u0027s slot=\u0022badge\u0022.",
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Overrides the initials fallback with custom content (e.g. a glyph).",
- "settable": true
- },
- {
- "name": "Size",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Diameter in pixels of the round picture.",
- "settable": true
- },
- {
- "name": "Src",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentPivot",
- "fullName": "FluentKit.Composite.FluentPivot",
- "category": "Composite",
- "sample": "/Composite/Pivot",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s Pivot/TabView (a horizontal strip of headers, one visible content pane at a time, arrow-key roving navigation between headers). Named FluentPivot rather than FluentTabView since that\u0027s the more fundamental WinUI 3 control the two largely share behavior with \u2014 TabView additionally supports closeable/add-new tabs, which can be layered on top of this later without changing the core header-strip/content-pane shape. The selection indicator (the blue underline) is a single absolutely-positioned div, not a border on each tab \u2014 sliding it between tabs on selection change needs its actual pixel position, which CSS alone can\u0027t derive from variable-width tab content, so wwwroot/Composite/Pivot/pivot-interop.js measures the selected button\u0027s offset/width and this re-renders with that baked into an inline transform/width (CSS transition handles the actual slide animation once those numbers land).",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "SelectedIndex",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "SelectedIndexChanged",
- "type": "EventCallback\u003CInt32\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentPivotItem",
- "fullName": "FluentKit.Composite.FluentPivotItem",
- "category": "Composite",
- "sample": "/Composite/Pivot",
- "genericParameters": [],
- "summary": "A single tab within a FluentPivot. See FluentPivot.razor.cs for the overall shape.",
- "parameters": [
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The pane content shown while this tab is selected.",
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "TabTemplate",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Custom tab header content (e.g. an icon \u002B text), overriding .",
- "settable": true
- },
- {
- "name": "Title",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Plain-text tab label \u2014 the common case. Ignored if is set.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentProgressBar",
- "fullName": "FluentKit.Primitives.FluentProgressBar",
- "category": "Primitives",
- "sample": "/Primitives/ProgressBar",
- "genericParameters": [],
- "summary": "Linear ProgressBar (ProgressBar_themeresources.xaml). Determinate mode fills to (0-100); with it plays WinUI\u0027s two-segment sliding-bar loop instead (pure CSS keyframe animation \u2014 no JS needed, unlike ProgressRing\u0027s arc math).",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsIndeterminate",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Value",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Progress value 0-100. Ignored when is true.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentProgressRing",
- "fullName": "FluentKit.Primitives.FluentProgressRing",
- "category": "Primitives",
- "sample": "/Primitives/ProgressRing",
- "genericParameters": [],
- "summary": "Circular ProgressRing. Rewritten to match fluent-svelte\u0027s precise SVG circle-stroke-dashoffset math and CSS keyframe animations. Uses vector strokes rather than conic-gradient masks for maximum rendering sharpness and accurate animation easing curves.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsIndeterminate",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Size",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Diameter in pixels.",
- "settable": true
- },
- {
- "name": "Value",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Progress value 0-100. Ignored when is true.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentRadioButton",
- "fullName": "FluentKit.Primitives.FluentRadioButton",
- "category": "Primitives",
- "sample": "/Primitives/RadioButton",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s RadioButton. Works two ways: 1. Standalone: bind directly (single independent radio, rare in practice). 2. Grouped (the common case): place inside a \u003CFluentRadioGroup\u003E and set \u2014 selection state then comes from the cascaded group context, matching fluent-svelte\u0027s bind:group pattern without relying on Blazor\u0027s InputRadioGroup (which requires EditContext).",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Checked",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "CheckedChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ItemTemplate",
- "type": "RenderFragment\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Full custom rendering for this option, replacing the default circle-and-dot indicator and label entirely. Receives whether this option is currently selected, so the template can style itself accordingly \u2014 e.g. a segmented \u0022chip\u0022 row (a toolbar\u0027s Bold/Italic/Underline group, or a category picker) instead of a classic circle-and-label radio. The template still renders inside the same role=\u0022radio\u0022/tabindex/keydown element as the default indicator, so whatever it renders stays keyboard-operable and announced correctly to a screen reader \u2014 you\u0027re only replacing the visual, not the selection semantics (still exactly one selected, never zero, exactly like the default indicator).",
- "settable": true
- },
- {
- "name": "Value",
- "type": "Object",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentRadioGroup",
- "fullName": "FluentKit.Primitives.FluentRadioGroup",
- "category": "Primitives",
- "sample": "/Primitives/RadioButton",
- "genericParameters": [],
- "summary": "Groups a set of FluentRadioButton children so only one can be selected at a time \u2014 the container-level equivalent of WinUI\u0027s RadioButtons list control, minus the automatic layout (wrap child FluentRadioButtons in your own StackPanel-equivalent div for spacing/orientation).",
- "parameters": [
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Value",
- "type": "Object",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "ValueChanged",
- "type": "EventCallback\u003CObject\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentRevealBackground",
- "fullName": "FluentKit.Effects.FluentRevealBackground",
- "category": "Effects",
- "sample": "/Effects/Reveal",
- "genericParameters": [],
- "summary": "Approximates WinUI\u0027s Reveal highlight \u2014 a soft radial-gradient spotlight that tracks the pointer across a control\u0027s border/background, used e.g. on NavigationView items and ListView rows to give hover feedback a sense of \u0022light\u0022 rather than a flat color swap. Same effects layer as Mica/Acrylic, but unlike those two this one\u0027s actual visual work is a single CSS radial-gradient positioned via two custom properties (--reveal-x/--reveal-y) \u2014 the JS module (wwwroot/Effects/Reveal/reveal-interop.js) only measures pointer position, it does no rendering itself. additionally reveals along a 1px border ring (WinUI\u0027s \u0022RevealBorderBrush\u0022 behavior), for use around a control\u0027s edge rather than its fill.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Bordered",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Also renders the highlight along a thin border ring instead of just the fill.",
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentSettingsCard",
- "fullName": "FluentKit.Composite.FluentSettingsCard",
- "category": "Composite",
- "sample": "/Composite/SettingsCard",
- "genericParameters": [],
- "summary": "1:1 port of the Windows Community Toolkit\u0027s SettingsCard (CommunityToolkit.WinUI.Controls.SettingsCard) \u2014 the base building block for consistent settings UI: an optional leading icon, a header/description stack, an end-aligned content slot (a ToggleSwitch, ComboBox, Button, etc.), and an optional trailing action icon (a chevron by default) that appears once the whole card is made clickable via . A FluentSettingsCard can also be hosted as an item inside a FluentSettingsExpander. WinUI\u0027s version reacts to available width with three extra VisualStates (RightWrapped, RightWrappedNoIcon, Vertical) driven by a ControlSizeTrigger. Blazor has no direct equivalent of that trigger, so the same three breakpoints (476px / 286px) are reproduced with CSS container queries on the card\u0027s own inline size in FluentSettingsCard.razor.css instead of C#-side layout code.",
- "parameters": [
- {
- "name": "ActionIcon",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The icon shown at the trailing edge when and are both true. Defaults to a chevron, same as WCT.",
- "settable": true
- },
- {
- "name": "ActionIconToolTip",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Tooltip for .",
- "settable": true
- },
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The end-aligned content of the card (maps to WCT\u0027s Content property \u2014 a ToggleSwitch, Button, ComboBox, etc.). Named ChildContent so it can be passed as the component\u0027s ordinary child markup.",
- "settable": true
- },
- {
- "name": "ContentAlignment",
- "type": "FluentKit.Composite.SettingsCardContentAlignment",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "The alignment of ChildContent. Defaults to , which keeps content end-aligned on one line and drops it below the header/description once the card gets too narrow for both to fit side by side.",
- "settable": true
- },
- {
- "name": "Description",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Description text (shown under the header, secondary/caption styling). Ignored when is supplied.",
- "settable": true
- },
- {
- "name": "DescriptionContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Description content, for anything beyond plain text (e.g. a HyperlinkButton).",
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Disables the card \u2014 dims foreground/icons, same as WinUI\u0027s Disabled visual state.",
- "settable": true
- },
- {
- "name": "Header",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Header text. Ignored when is supplied.",
- "settable": true
- },
- {
- "name": "HeaderContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Header content, for anything beyond plain text.",
- "settable": true
- },
- {
- "name": "HeaderIcon",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The icon shown at the leading edge of the card.",
- "settable": true
- },
- {
- "name": "IsActionIconVisible",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Whether the ActionIcon is shown (only relevant while is true).",
- "settable": true
- },
- {
- "name": "IsClickEnabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Makes the whole card behave like a button \u2014 pointer/press visual states, keyboard activation (Enter/Space), and the trailing chevron/action icon become visible.",
- "settable": true
- },
- {
- "name": "OnClick",
- "type": "EventCallback\u003CMicrosoft.AspNetCore.Components.Web.MouseEventArgs\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Fires when the card is activated (click, or Enter/Space while focused) and is true.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentSettingsExpander",
- "fullName": "FluentKit.Composite.FluentSettingsExpander",
- "category": "Composite",
- "sample": "/Composite/SettingsExpander",
- "genericParameters": [],
- "summary": "1:1 port of the Windows Community Toolkit\u0027s SettingsExpander (CommunityToolkit.WinUI.Controls.SettingsExpander) \u2014 a collapsible header (itself a -shaped surface) that reveals a list of nested items underneath when expanded. WCT\u0027s version hosts its items via an ItemsRepeater bound to an Items/ItemsSource pair. Blazor has no ItemsRepeater equivalent worth reproducing here \u2014 the idiomatic approach is simply letting the caller pass the items as ordinary child markup (, typically a handful of \u003CFluentSettingsCard\u003E elements), the same way every other FluentKit composite accepts its children. and are preserved as-is since they\u0027re just extra render slots above/below the item list. The expand/collapse animation reuses FluentExpander\u0027s clip\u002Bslide technique (see that component\u0027s remarks) rather than the CSS-grid 0fr/1fr trick.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The end-aligned content of the header row (maps to WCT\u0027s Content \u2014 commonly a ToggleSwitch that controls the whole group). Named ChildContent so it can be passed as the component\u0027s ordinary child markup.",
- "settable": true
- },
- {
- "name": "Collapsed",
- "type": "Microsoft.AspNetCore.Components.EventCallback",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Fires when the SettingsExpander is closed.",
- "settable": true
- },
- {
- "name": "Description",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Description text shown under the header. Ignored when is supplied.",
- "settable": true
- },
- {
- "name": "DescriptionContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Description content, for anything beyond plain text.",
- "settable": true
- },
- {
- "name": "Expanded",
- "type": "Microsoft.AspNetCore.Components.EventCallback",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Fires when the SettingsExpander is opened.",
- "settable": true
- },
- {
- "name": "Header",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Header text. Ignored when is supplied.",
- "settable": true
- },
- {
- "name": "HeaderContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Header content, for anything beyond plain text.",
- "settable": true
- },
- {
- "name": "HeaderIcon",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The icon shown at the leading edge of the header.",
- "settable": true
- },
- {
- "name": "IsExpanded",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "Whether the items area is expanded.",
- "settable": true
- },
- {
- "name": "IsExpandedChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ItemsContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The nested settings cards shown once expanded (maps to WCT\u0027s Items/ItemsSource \u2014 typically a handful of \u003CFluentSettingsCard\u003E elements).",
- "settable": true
- },
- {
- "name": "ItemsFooter",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Optional content shown below , inside the expanded area.",
- "settable": true
- },
- {
- "name": "ItemsHeader",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Optional content shown above , inside the expanded area.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentSlider",
- "fullName": "FluentKit.Primitives.FluentSlider",
- "category": "Primitives",
- "sample": "/Primitives/Slider",
- "genericParameters": [],
- "summary": "Slider (Slider_themeresources.xaml / fluent-svelte\u0027s Slider.svelte). Drag tracking is delegated to wwwroot/Primitives/Slider/slider-interop.js \u2014 pointer/touch position comes back as a plain 0-100 rail percentage so this component never has to know pixel geometry itself, only how to turn a percentage into a stepped and back. Fixed-size 20px thumb (see .razor.css) \u2014 offset math for keeping the thumb from overflowing the rail edges is \u0060halfWidth * (1 - 2*pct/100)\u0060, ported from fluent-svelte\u0027s own clamp formula, with the 20px width hardcoded here rather than measured at runtime since it\u0027s a fixed CSS value.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Max",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Min",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Orientation",
- "type": "FluentKit.Primitives.FluentSliderOrientation",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Reverse",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Flips which end of the rail 0/Max render at, without changing Min/Max semantics.",
- "settable": true
- },
- {
- "name": "ShowTooltip",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Shows the live value in a small callout above/beside the thumb while dragging, hovering, or focused. Matches fluent-svelte\u0027s default behavior.",
- "settable": true
- },
- {
- "name": "Step",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "TickFrequency",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Gap between ticks, in Value units. 0 (default) draws no ticks.",
- "settable": true
- },
- {
- "name": "TickPlacement",
- "type": "FluentKit.Primitives.FluentSliderTickPlacement",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Value",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "ValueChanged",
- "type": "EventCallback\u003CDouble\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentSplitButton",
- "fullName": "FluentKit.Composite.FluentSplitButton",
- "category": "Composite",
- "sample": "/Composite/SplitButton",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s SplitButton \u2014 a primary action region (fires immediately, like a plain button) joined to a separate narrow chevron region that opens a MenuFlyout of related actions, rendered as one visually-continuous pill (shared border, no gap, only the chevron side has rounded outer corners on its own edge \u2014 see .razor.css). Unlike , which is nothing but a menu trigger, SplitButton\u0027s main region is a real independent action. FluentMenuFlyout\u0027s trigger wraps the WHOLE two-button pill (not just the chevron) so its anchor width is the full control\u0027s width \u2014 needed for MatchAnchorWidth to stretch the menu to the button\u0027s own width rather than the chevron\u0027s ~32px. The action button stops its click from bubbling to that wrapping trigger (@@onclick:stopPropagation) so clicking it fires only ; the chevron has no handler of its own and relies on the bubble to open the menu.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "IsOpen",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "IsOpenChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MenuItems",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "The chevron\u0027s menu items \u2014 FluentMenuFlyoutItem/FluentMenuFlyoutDivider elements.",
- "settable": true
- },
- {
- "name": "OnClick",
- "type": "EventCallback\u003CMicrosoft.AspNetCore.Components.Web.MouseEventArgs\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Placement",
- "type": "FluentKit.Overlay.OverlayPlacement",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Variant",
- "type": "FluentKit.Primitives.ButtonVariant",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentTeachingTip",
- "fullName": "FluentKit.Composite.FluentTeachingTip",
- "category": "Composite",
- "sample": "/Composite/TeachingTip",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s TeachingTip \u2014 a persistent, anchor-relative callout (title/subtitle, an optional row of action buttons, and an explicit close button) used for contextual \u0022here\u0027s what this does\u0022 UI, as opposed to Flyout\u0027s generic content or ToolTip\u0027s hover-only hint. Structurally close to Flyout: built directly on IOverlayService/FluentOverlayHost with the same programmatic--binding shape as , and \u2014 like every other flyout/menu/tooltip \u2014 relies entirely on OverlaySurface\u0027s own default chrome (background, backdrop-filter: blur(60px), border, shadow) for its blur; it does NOT pass bare. One real difference from Flyout: it adds its own pointer/beak toward the anchor in targeted mode (WinUI teaching tips always draw one there \u2014 see the beak\u0027s own matching background/border in FluentTeachingTip.razor.css), and it defaults to false \u2014 a teaching tip is meant to stay up until the person reads it and dismisses it via the explicit close button, not disappear the moment they click elsewhere. Two placement modes, chosen automatically by whether is supplied: Targeted ( set) \u2014 is the anchor/target element the tip points its beak at, positioned via (top/bottom/left/right of the anchor), same anchor-measurement path as Flyout/MenuFlyout. Unlike Flyout/MenuFlyout it is NOT wrapped in a click handler \u2014 teaching tips are shown/hidden programmatically (e.g. after a first-run check), never by clicking their own target.Detached ( omitted) \u2014 for a tip that is not pointing at any particular control (e.g. a general new-features announcement). No beak is drawn, and position is set by relative to the viewport (defaults to bottom-center) via instead of the anchor-based overload.",
- "parameters": [
- {
- "name": "ActionContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Optional row of action buttons (e.g. \u0022Got it\u0022 / \u0022Learn more\u0022) below the text.",
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Content wrapped in the anchor span. Historically this also doubled as the anchor itself, which breaks the moment the thing you want the tip to point at isn\u0027t the same element you clicked to trigger it (e.g. a toolbar button that should show a tip pointing at a card somewhere else on the page). Prefer for that case \u2014 leave ChildContent unset when using Target, or set both if you also want the tip to wrap some markup for its own sake.",
- "settable": true
- },
- {
- "name": "IsOpen",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "IsOpenChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "LightDismiss",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Closes on outside click. Defaults false \u2014 teaching tips are dismissed explicitly.",
- "settable": true
- },
- {
- "name": "Placement",
- "type": "FluentKit.Overlay.OverlayPlacement",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Placement relative to the anchor. Only meaningful in targeted mode (i.e. when is set) \u2014 ignored in detached mode, where is used instead.",
- "settable": true
- },
- {
- "name": "ScreenPlacement",
- "type": "FluentKit.Overlay.OverlayScreenPlacement",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Placement relative to the viewport for the detached variant (i.e. when is NOT set). Ignored in targeted mode. Defaults to , matching WinUI\u0027s own default spot for an untargeted teaching tip.",
- "settable": true
- },
- {
- "name": "Subtitle",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Target",
- "type": "Nullable\u003CMicrosoft.AspNetCore.Components.ElementReference\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Explicit element the tip\u0027s beak points at, independent of whatever triggered . Takes priority over \u0027s own wrapper span as the anchor when both are set. If the target isn\u0027t fully visible in the viewport when the tip opens, it\u0027s scrolled into view (centered, instant) first \u2014 there\u0027s no guarantee it\u0027s already on screen the way a Flyout\u0027s own trigger always is. If the target element is later removed from the DOM while the tip is still open, the tip auto-closes rather than floating in place pointing at nothing.",
- "settable": true
- },
- {
- "name": "Title",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentTextBlock",
- "fullName": "FluentKit.Primitives.FluentTextBlock",
- "category": "Primitives",
- "sample": "/Primitives/TextBlock",
- "genericParameters": [],
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Class",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Tag",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Overrides the default HTML tag for this variant. Supported: span, div, p, h1-h6.",
- "settable": true
- },
- {
- "name": "Variant",
- "type": "FluentKit.Primitives.TextBlockVariant",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentTextBox",
- "fullName": "FluentKit.Primitives.FluentTextBox",
- "category": "Primitives",
- "sample": "/Primitives/TextBox",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s TextBox. The focus/underline behavior (an accent-colored bottom edge that only appears once focused) is pure CSS via :focus-within on the container in FluentTextBox.razor.css \u2014 see TextBox.scss in fluent-svelte for the reference implementation this was ported from (that one uses an ::after pseudo-element the same way).",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Buttons",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "summary": "Optional right-aligned button slot (mirrors fluent-svelte\u0027s TextBox slot=\u0022buttons\u0022). Used e.g. by NumberBox for its spin up/down buttons, or by applications for a clear/search icon.",
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Header",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Placeholder",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Type",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Value",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "ValueChanged",
- "type": "EventCallback\u003CString\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentTextBoxButton",
- "fullName": "FluentKit.Primitives.FluentTextBoxButton",
- "category": "Primitives",
- "sample": "/Primitives/TextBox",
- "genericParameters": [],
- "summary": "Small icon-only button meant to live inside FluentTextBox\u0027s slot \u2014 mirrors fluent-svelte\u0027s TextBoxButton.svelte. Reuses the subtle-fill ramp (same as FluentButton\u0027s Subtle variant) rather than the full bordered Button chrome, since it sits inside the textbox\u0027s own border. Also supports press-and-hold repeat (used by NumberBox\u0027s spin buttons): holding the pointer down re-invokes repeatedly after an initial delay, accelerating the interval on each repeat down to a floor, same \u0022hold to speed up\u0022 behavior as WinUI\u0027s own NumberBox / OS spinner controls. A plain tap still only fires OnClick once (the immediate @onclick), since the repeat loop\u0027s first extra invocation only lands after .",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "InitialHoldDelayMs",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Delay before hold-repeat kicks in after pointerdown, in milliseconds.",
- "settable": true
- },
- {
- "name": "OnClick",
- "type": "EventCallback\u003CMicrosoft.AspNetCore.Components.Web.MouseEventArgs\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "RepeatAccelerationFactor",
- "type": "Double",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Multiplier applied to the interval after every repeat (below 1 = speeding up).",
- "settable": true
- },
- {
- "name": "RepeatMinIntervalMs",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Floor the repeat interval accelerates down to, in milliseconds.",
- "settable": true
- },
- {
- "name": "RepeatStartIntervalMs",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Interval between repeats at the start of a hold, in milliseconds.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentTimePicker",
- "fullName": "FluentKit.Composite.FluentTimePicker",
- "category": "Composite",
- "sample": "/Composite/TimePicker",
- "genericParameters": [],
- "summary": "A brand-new control, not a fluent-svelte port (fluent-svelte has no TimePicker) \u2014 modeled directly on WinUI 3\u0027s TimePicker \u002B TimePickerFlyout (see WinUI-Gallery\u0027s TimePickerPage.xaml and microsoft-ui-xaml\u0027s control template) rather than any existing web time-picker convention. Key WinUI behaviors reproduced: - Three snap-scrolling columns (Hour, Minute, and AM/PM unless is TwentyFourHour) with the vertically-centered row being the \u0022selected\u0022 one \u2014 WinUI\u0027s flyout uses a LoopingSelector per column; the web has no equivalent primitive, so this is built as a plain scroll-snap-align:center list per column with IntersectionObserver-free selection tracking done by comparing each item\u0027s scroll offset against the container\u0027s on scroll (see the JS interop module) \u2014 good enough fidelity without inventing a full virtualized looping selector for a first cut. - Values are staged locally while the flyout is open (WinUI: dragging the columns doesn\u0027t commit until you tap the accept/checkmark glyph) and only pushed to on Accept; Cancel (or Escape, or outside click) discards the in-progress scroll position and reverts to the last committed value. - matches WinUI\u0027s property of the same name \u2014 restricts the minute column to that step (e.g. 15 =\u003E :00/:15/:30/:45 only). Architecture matches this repo\u0027s other trigger\u002Bpopout composites (FluentCalendarDatePicker, FluentComboBox): self-contained absolutely-positioned popout, not routed through IOverlayService.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ClockIdentifier",
- "type": "FluentKit.Composite.TimeClockIdentifier",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "12-hour (default, with AM/PM column) or 24-hour. Matches WinUI\u0027s ClockIdentifier.",
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Header",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Optional label shown above the trigger button, matching WinUI TimePicker\u0027s Header.",
- "settable": true
- },
- {
- "name": "Locale",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "MinuteIncrement",
- "type": "Int32",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Restricts the minute column to this step. Matches WinUI\u0027s MinuteIncrement (legal range there is 1-30; enforced the same way here via ).",
- "settable": true
- },
- {
- "name": "Placeholder",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "SelectedTime",
- "type": "Nullable\u003CTimeSpan\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "summary": "The committed time-of-day. Two-way bindable. Null means \u0022no time picked yet\u0022 and shows on the trigger button, matching WinUI\u0027s own unset TimePicker.",
- "settable": true
- },
- {
- "name": "SelectedTimeChanged",
- "type": "EventCallback\u003CNullable\u003CTimeSpan\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "TimeChanged",
- "type": "EventCallback\u003CNullable\u003CTimeSpan\u003E\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "summary": "Fires only when the user accepts a new value via the flyout\u0027s checkmark button \u2014 mirrors WinUI TimePicker\u0027s SelectedTimeChanged event semantics (as distinct from the two-way bind itself), matching FluentCalendarView\u0027s own ValueChanged/Change split convention.",
- "settable": true
- }
- ]
- },
- {
- "name": "FluentToggleButton",
- "fullName": "FluentKit.Primitives.FluentToggleButton",
- "category": "Primitives",
- "sample": "/Primitives/ToggleButton",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s ToggleButton \u2014 a button that stays visually \u0022pressed\u0022 once activated, distinct from (which always pairs its state with On/Off text per Fluent\u0027s accessibility guidance) and from plain (momentary, no persisted state). Typical uses: formatting toolbars (Bold/Italic), a single standalone \u0022Mute\u0022 button, etc.",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Checked",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "CheckedChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentToggleSwitch",
- "fullName": "FluentKit.Primitives.FluentToggleSwitch",
- "category": "Primitives",
- "sample": "/Primitives/ToggleSwitch",
- "genericParameters": [],
- "summary": "Mirrors WinUI\u0027s ToggleSwitch, including the optional Header/OnContent/OffContent text WinUI shows alongside the physical switch (unlike a plain CheckBox, ToggleSwitch always communicates its current state via text, not just position, per Fluent\u0027s accessibility guidance).",
- "parameters": [
- {
- "name": "AdditionalAttributes",
- "type": "IReadOnlyDictionary\u003CString, Object\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Checked",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": true,
- "settable": true
- },
- {
- "name": "CheckedChanged",
- "type": "EventCallback\u003CBoolean\u003E",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Disabled",
- "type": "Boolean",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Header",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "OffContent",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- },
- {
- "name": "OnContent",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "FluentTooltip",
- "fullName": "FluentKit.Composite.FluentTooltip",
- "category": "Composite",
- "sample": "/Composite/Tooltip",
- "genericParameters": [],
- "summary": "Deliberately the simplest possible use of IOverlayService/FluentOverlayHost \u2014 this exists to prove the overlay infrastructure end to end (Phase 3 exit criteria), not as the final Tooltip. Missing, on purpose, for now: show/hide delay debouncing, and re-showing on scroll.",
- "parameters": [
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- },
- {
- "name": "Text",
- "type": "String",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "OverlaySurface",
- "fullName": "FluentKit.Overlay.OverlaySurface",
- "category": "Infrastructure",
- "sample": "/Composite/Flyout",
- "genericParameters": [],
- "parameters": [
- {
- "name": "Entry",
- "type": "FluentKit.Overlay.OverlayEntry",
- "cascading": false,
- "renderFragment": false,
- "bindable": false,
- "settable": true
- }
- ]
- },
- {
- "name": "ThemeProvider",
- "fullName": "FluentKit.Theming.ThemeProvider",
- "category": "Infrastructure",
- "sample": "/Design/Colors",
- "genericParameters": [],
- "parameters": [
- {
- "name": "ChildContent",
- "type": "Microsoft.AspNetCore.Components.RenderFragment",
- "cascading": false,
- "renderFragment": true,
- "bindable": false,
- "settable": true
- }
- ]
- }
- ],
- "types": [
- {
- "name": "SizableComponentBase",
- "fullName": "FluentKit.Common.SizableComponentBase",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "summary": "Base class for controls that expose XAML-style Padding/Width/Height/ MinWidth/MinHeight/MaxWidth/MaxHeight overrides (mirrors FrameworkElement\u0027s sizing properties in WPF/WinUI). Every property here is nullable and by default, meaning \u0022unset \u2014 fall back to the control\u0027s own stylesheet default\u0022; only the ones an application actually sets get written into . Inheritors render style=\u0022@SizingStyle\u0022 on their root element (in addition to, not instead of, their existing CSS classes) so overrides win via normal inline-style specificity without needing !important or duplicated CSS.",
- "members": [
- {
- "name": "Height",
- "kind": "property",
- "type": "Nullable\u003CDouble\u003E"
- },
- {
- "name": "MaxHeight",
- "kind": "property",
- "type": "Nullable\u003CDouble\u003E"
- },
- {
- "name": "MaxWidth",
- "kind": "property",
- "type": "Nullable\u003CDouble\u003E"
- },
- {
- "name": "MinHeight",
- "kind": "property",
- "type": "Nullable\u003CDouble\u003E"
- },
- {
- "name": "MinWidth",
- "kind": "property",
- "type": "Nullable\u003CDouble\u003E"
- },
- {
- "name": "Padding",
- "kind": "property",
- "type": "Nullable\u003CFluentKit.Common.Thickness\u003E",
- "summary": "Inner spacing between the border and content, all sides. Unset = the control\u0027s CSS default."
- },
- {
- "name": "Width",
- "kind": "property",
- "type": "Nullable\u003CDouble\u003E"
- }
- ]
- },
- {
- "name": "Thickness",
- "fullName": "FluentKit.Common.Thickness",
- "kind": "struct",
- "genericParameters": [],
- "values": [],
- "summary": "Mirrors XAML\u0027s Thickness (used for Padding/Margin in WPF/WinUI) \u2014 same three constructor shapes (uniform, horizontal/vertical, four-value), same Left/Top/Right/Bottom field names. Values are CSS pixels. Renders to a CSS padding shorthand via \u2014 note CSS shorthand order is top/right/bottom/left, not left/top/right/bottom, so the constructor argument order and the emitted order intentionally differ; that\u0027s a CSS convention mismatch inherited from the platform, not a bug here.",
- "members": [
- {
- "name": "Bottom",
- "kind": "property",
- "type": "Double"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "GetHashCode",
- "kind": "method",
- "type": "Int32"
- },
- {
- "name": "Left",
- "kind": "property",
- "type": "Double"
- },
- {
- "name": "Right",
- "kind": "property",
- "type": "Double"
- },
- {
- "name": "ToCss",
- "kind": "method",
- "type": "String",
- "summary": "CSS padding shorthand value, e.g. \u00224px 8px 4px 8px\u0022 (top right bottom left)."
- },
- {
- "name": "Top",
- "kind": "property",
- "type": "Double"
- },
- {
- "name": "Zero",
- "kind": "property",
- "type": "FluentKit.Common.Thickness"
- }
- ]
- },
- {
- "name": "AutoSuggestBoxItem\u00601",
- "fullName": "FluentKit.Composite.AutoSuggestBoxItem\u00601",
- "kind": "class",
- "genericParameters": [
- "TValue"
- ],
- "values": [],
- "summary": "One suggestion row in a . Same shape as (Name/Value/Disabled) \u2014 kept as its own type rather than reusing ComboBoxItem directly since AutoSuggestBox\u0027s \u0022Value\u0022 is conceptually a query result payload rather than a fixed option, even though the record looks identical today.",
- "members": [
- {
- "name": "Deconstruct",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "Disabled",
- "kind": "property",
- "type": "Boolean"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "GetHashCode",
- "kind": "method",
- "type": "Int32"
- },
- {
- "name": "Name",
- "kind": "property",
- "type": "String"
- },
- {
- "name": "ToString",
- "kind": "method",
- "type": "String"
- },
- {
- "name": "Value",
- "kind": "property",
- "type": "TValue"
- }
- ]
- },
- {
- "name": "CalendarSelectionMode",
- "fullName": "FluentKit.Composite.CalendarSelectionMode",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Multiple",
- "Range",
- "Single"
- ],
- "summary": "Selection behavior for and . Not part of fluent-svelte\u0027s original API (it only ever exposed a multiple boolean) \u2014 added here as a superset so applications can opt into a third, common date-range picking pattern (\u00E0 la Fluent UI React\u0027s DatePicker range selection) without overloading a bool.",
- "members": []
- },
- {
- "name": "CalendarViewItemVariant",
- "fullName": "FluentKit.Composite.CalendarViewItemVariant",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Day",
- "MonthYear"
- ],
- "summary": "Matches fluent-svelte\u0027s CalendarViewItem variant prop \u2014 day cells are round/small (40px), month/year cells are round/larger (56px), per CalendarViewItem.scss\u0027s two .type- sizes.",
- "members": []
- },
- {
- "name": "CalendarViewMode",
- "fullName": "FluentKit.Composite.CalendarViewMode",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Days",
- "Months",
- "Years"
- ],
- "summary": "The three drill levels fluent-svelte\u0027s CalendarView cycles through \u2014 clicking the header goes Days \u2192 Months \u2192 Years (and Years is a dead end, its header button is disabled); picking a month drops back to Days, picking a year drops back to Months.",
- "members": []
- },
- {
- "name": "ComboBoxItem\u00601",
- "fullName": "FluentKit.Composite.ComboBoxItem\u00601",
- "kind": "class",
- "genericParameters": [
- "TValue"
- ],
- "values": [],
- "summary": "One selectable entry in a . Mirrors fluent-svelte\u0027s ComboBox Item interface (name/value/disabled) \u2014 Name is the display text used both for the visible label and (in mode) the starts-with search match against .",
- "members": [
- {
- "name": "Deconstruct",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "Disabled",
- "kind": "property",
- "type": "Boolean"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "GetHashCode",
- "kind": "method",
- "type": "Int32"
- },
- {
- "name": "Name",
- "kind": "property",
- "type": "String"
- },
- {
- "name": "ToString",
- "kind": "method",
- "type": "String"
- },
- {
- "name": "Value",
- "kind": "property",
- "type": "TValue"
- }
- ]
- },
- {
- "name": "ContentDialogSize",
- "fullName": "FluentKit.Composite.ContentDialogSize",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Max",
- "Min",
- "Standard"
- ],
- "summary": "Visual size of a , mirroring fluent-svelte\u0027s ContentDialog \u0060size\u0060 prop (min/standard/max) and WinUI\u0027s own three dialog width presets.",
- "members": []
- },
- {
- "name": "MenuBarContext",
- "fullName": "FluentKit.Composite.MenuBarContext",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "summary": "Shared cascading state for a FluentMenuBar tree \u2014 ported from fluent-svelte\u0027s currentMenu writable store (MenuBar/flyoutState.ts) plus MenuBar.svelte\u0027s sideNavigation context. Tracks which top-level item currently has its dropdown open (so hovering a sibling switches the open menu instead of requiring another click, matching svelte\u0027s $currentMenu check) and the left-to-right tab order of registered items (for ArrowLeft/ArrowRight wraparound focus, matching svelte\u0027s tabbable-based sideNavigation handler \u2014 done here via item self-registration instead of a live DOM query, since that\u0027s how this project\u0027s other roving-tabindex controls (CalendarView, NavigationView) already do it).",
- "members": [
- {
- "name": "ClearOpen",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "CurrentOpenItem",
- "kind": "property",
- "type": "FluentKit.Composite.FluentMenuBarItem"
- },
- {
- "name": "FocusAdjacentAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- },
- {
- "name": "Register",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "SetOpen",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "Unregister",
- "kind": "method",
- "type": "Void"
- }
- ]
- },
- {
- "name": "MenuFlyoutCloseContext",
- "fullName": "FluentKit.Composite.MenuFlyoutCloseContext",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "summary": "Shared close-coordination for a MenuFlyout/ContextMenu tree. A single instance is created by the root (FluentMenuFlyout or FluentContextMenu) and cascaded down through every nested submenu level. Each open submenu subscribes its own close action to so that selecting a leaf item anywhere in the tree collapses the whole chain at once \u2014 mirroring fluent-svelte\u0027s single \u0060closeFlyout\u0060 context, just fanned out to however many submenu levels are currently open (each one is a *separate* IOverlayService entry, so there\u0027s no automatic parent/ child unmounting to piggyback on the way there would be with plain nested DOM).",
- "members": [
- {
- "name": "Closable",
- "kind": "property",
- "type": "Boolean",
- "summary": "Mirrors fluent-svelte\u0027s \u0060closable\u0060 \u2014 whether the tree can be dismissed by conventional interaction (selecting an item, clicking outside, Escape) at all."
- },
- {
- "name": "CloseAll",
- "kind": "method",
- "type": "Void",
- "summary": "Called by any item\u0027s click handler once it decides selection should close the tree."
- },
- {
- "name": "CloseOnSelect",
- "kind": "property",
- "type": "Boolean",
- "summary": "Mirrors fluent-svelte\u0027s \u0060closeOnSelect\u0060 \u2014 whether selecting a standard/radio/toggle item closes the tree. When false, items still raise their click/select callbacks but the menu stays open (e.g. a multi-toggle menu the user checks several boxes in before dismissing)."
- },
- {
- "name": "Subscribe",
- "kind": "method",
- "type": "IDisposable"
- }
- ]
- },
- {
- "name": "MenuFlyoutIconGutterContext",
- "fullName": "FluentKit.Composite.MenuFlyoutIconGutterContext",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "summary": "Per-menu-level (one instance per FluentMenuFlyoutSurface) tally of how many of its FluentMenuFlyoutItem children currently render an . Exists so a menu with NO icons anywhere doesn\u0027t reserve the leading icon gutter on every row \u2014 WinUI/fluent-svelte only indent labels to line up with icons when at least one sibling actually has one; a menu that\u0027s icon-free throughout should have its labels start flush left. This is inherently a two-pass problem: an item can\u0027t know at its own render time whether some *later* sibling has an icon, since siblings render in document order within the same pass. Items register/unregister as their own Icon presence changes; the count transitioning 0-\u003E1 (or back) raises , which FluentMenuFlyoutSurface subscribes to and reacts to with its own StateHasChanged() \u2014 forcing a second render pass in which every item now sees the final, correct value. A menu that never gets any icons only ever needs the one pass; a menu whose first-rendered item already has an icon effectively self-corrects on the second pass too, harmlessly.",
- "members": [
- {
- "name": "HasAnyIcon",
- "kind": "property",
- "type": "Boolean"
- },
- {
- "name": "RegisterIcon",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "UnregisterIcon",
- "kind": "method",
- "type": "Void"
- }
- ]
- },
- {
- "name": "MenuFlyoutItemVariant",
- "fullName": "FluentKit.Composite.MenuFlyoutItemVariant",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Radio",
- "Standard",
- "Toggle"
- ],
- "members": []
- },
- {
- "name": "NavigationViewContext",
- "fullName": "FluentKit.Composite.NavigationViewContext",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "members": [
- {
- "name": "Dispose",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "SelectValue",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "SelectedValue",
- "kind": "property",
- "type": "Object"
- }
- ]
- },
- {
- "name": "NavigationViewDisplayMode",
- "fullName": "FluentKit.Composite.NavigationViewDisplayMode",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Compact",
- "Expanded",
- "Minimal"
- ],
- "members": []
- },
- {
- "name": "NavigationViewPaneDisplayMode",
- "fullName": "FluentKit.Composite.NavigationViewPaneDisplayMode",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Auto",
- "Left",
- "LeftCompact",
- "LeftMinimal",
- "Top"
- ],
- "members": []
- },
- {
- "name": "NumberBoxSpinButtonMode",
- "fullName": "FluentKit.Composite.NumberBoxSpinButtonMode",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Compact",
- "Expanded"
- ],
- "summary": "Controls how NumberBox\u0027s spin buttons are presented \u2014 see FluentNumberBox.razor.cs remarks.",
- "members": []
- },
- {
- "name": "PivotContext",
- "fullName": "FluentKit.Composite.PivotContext",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "summary": "Shared registration list for a FluentPivot tree. Pivot items are data-only \u2014 they render nothing themselves (see FluentPivotItem) and just register their Title/TabTemplate/ChildContent here; FluentPivot owns all actual DOM output (the tab strip buttons \u002B the single active content pane), reading straight out of this list by index rather than each item rendering itself hidden/shown. Registration happens during the items\u0027 own OnInitialized, which runs one render pass after FluentPivot\u0027s own \u2014 triggers that follow-up render so the tab strip picks the items up as soon as they\u0027ve registered, same pattern MenuBarContext uses for its list.",
- "members": [
- {
- "name": "Items",
- "kind": "property",
- "type": "List\u003CFluentKit.Composite.FluentPivotItem\u003E"
- },
- {
- "name": "NotifyChanged",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "Register",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "Unregister",
- "kind": "method",
- "type": "Void"
- }
- ]
- },
- {
- "name": "SettingsCardContentAlignment",
- "fullName": "FluentKit.Composite.SettingsCardContentAlignment",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Auto",
- "Left",
- "Right",
- "Vertical"
- ],
- "summary": "The alignment of a FluentSettingsCard\u0027s ChildContent.",
- "members": []
- },
- {
- "name": "TimeClockIdentifier",
- "fullName": "FluentKit.Composite.TimeClockIdentifier",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "TwelveHour",
- "TwentyFourHour"
- ],
- "summary": "Mirrors WinUI TimePicker/TimePickerFlyout\u0027s ClockIdentifier property \u2014 which in WinUI is a loosely-typed string (\u002212HourClock\u0022 / \u002224HourClock\u0022) since it\u0027s really a Windows.Globalization clock system identifier. Re-exposed here as a proper enum (FluentKit convention \u2014 see , ) since a string parameter with exactly two legal values isn\u0027t worth the stringly-typed footgun for a net-new (non-ported) control that owes WinUI fidelity, not fluent-svelte source compatibility.",
- "members": []
- },
- {
- "name": "AcrylicKind",
- "fullName": "FluentKit.Effects.AcrylicKind",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Base",
- "Thin"
- ],
- "summary": "Approximates WinUI\u0027s in-app Acrylic (AcrylicBrush / DesktopAcrylicBackdrop). This is a genuinely different material from Mica (see FluentMicaPanel) \u2014 Acrylic is TRANSLUCENT and reacts live to whatever\u0027s actually rendered behind it (in-app content scrolling underneath, other panels, etc.), not a static sample of the desktop wallpaper. On the web the correct primitive for that is CSS \u0060backdrop-filter\u0060, which live-samples the page content behind the element and blurs it in real time. This component used to be (incorrectly) named FluentMicaPanel \u2014 that name now belongs to the actual wallpaper-based static-blur material, which needed a from-scratch implementation instead.",
- "members": []
- },
- {
- "name": "MicaVariant",
- "fullName": "FluentKit.Effects.MicaVariant",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Base",
- "BaseAlt"
- ],
- "summary": "Approximates WinUI\u0027s Mica material \u2014 rebuilt from scratch against the actual recipe in microsoft-ui-xaml\u0027s SystemBackdropComponentInternal::BuildMicaEffectBrush (dev/dll/SystemBackdrop/SystemBackdropBrushFactory.cpp). This is NOT the same thing as Acrylic (see FluentAcrylicBrush) \u2014 Acrylic live-blurs whatever\u0027s actually behind it via backdrop-filter; Mica does not. PERFORMANCE NOTE: this used to run the blur (CSS \u0060filter\u0060) and the two blend passes (CSS \u0060mix-blend-mode\u0060) live, every repaint. That\u0027s expensive for a large/fixed-viewport element and was the source of noticeable jank. It\u0027s now baked ONCE per (image, variant, theme) into a static raster via an offscreen \u003Ccanvas\u003E (see wwwroot/Effects/mica-interop.js) and cached \u2014 after that, rendering Mica is just displaying a static image, no different in cost from any other photo background. The recipe itself (blur -\u003E luminosity blend -\u003E color blend) is unchanged, just baked ahead of time instead of recomputed on every paint. 1. Start from a heavily blurred copy of the desktop wallpaper (ICompositorWithBlurredWallpaper- BackdropBrush \u2014 an OS compositor API that samples the wallpaper only, not other windows). Browsers can\u0027t reach the real wallpaper, so stands in for it \u2014 supply any image (ideally the user\u0027s own wallpaper file) and it\u0027s blurred the same way. 2. LUMINOSITY blend pass: blend TintColor (alpha = LuminosityOpacity) over the blurred wallpaper using true \u0022luminosity\u0022 blend mode \u2014 keeps the wallpaper\u0027s hue/saturation but replaces its per-pixel luminosity with the tint\u0027s. Mica\u0027s defaults set LuminosityOpacity to 1.0, so this pass ends up governing almost the entire final look. 3. COLOR blend pass: blend TintColor (alpha = TintOpacity) over the result of step 2 using true \u0022color\u0022 blend mode \u2014 keeps the TINT\u0027s hue/saturation but takes luminosity from step 2\u0027s result. This is what gives Mica its \u0022muted, personalized\u0022 gradient-ish look: the wallpaper\u0027s light/dark shape survives, but its color is replaced by the theme tint. With no BackgroundImageUrl supplied (or a format the browser can\u0027t decode, e.g. HEIC outside Safari), this degrades to WinUI\u0027s own documented fallback: a flat SolidBackgroundFillColorBase (Alt) fill \u2014 the same state real Mica falls back to under RDP, low-power GPUs, or Battery Saver.",
- "members": []
- },
- {
- "name": "IOverlayService",
- "fullName": "FluentKit.Overlay.IOverlayService",
- "kind": "interface",
- "genericParameters": [],
- "values": [],
- "summary": "Register as Scoped. Components never position themselves \u2014 they call Show() with their own ElementReference as the anchor and let FluentOverlayHost (mounted once, near the app root) render the content at a fixed top-level position. This is the Blazor answer to React\u0027s createPortal: render-tree teleportation via a cascading service, not DOM manipulation.",
- "members": [
- {
- "name": "Active",
- "kind": "property",
- "type": "IReadOnlyList\u003CFluentKit.Overlay.OverlayEntry\u003E"
- },
- {
- "name": "Close",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "CloseAll",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "CompleteClose",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "RefreshContent",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "Show",
- "kind": "method",
- "type": "Guid"
- },
- {
- "name": "ShowDetached",
- "kind": "method",
- "type": "Guid"
- },
- {
- "name": "Update",
- "kind": "method",
- "type": "Boolean"
- }
- ]
- },
- {
- "name": "OverlayEntry",
- "fullName": "FluentKit.Overlay.OverlayEntry",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "members": [
- {
- "name": "Anchor",
- "kind": "property",
- "type": "Microsoft.AspNetCore.Components.ElementReference"
- },
- {
- "name": "Bare",
- "kind": "property",
- "type": "Boolean",
- "summary": "When true, OverlaySurface renders with no chrome of its own (no background, blur, border, shadow, or padding) \u2014 for components like NavigationView\u0027s Compact/Minimal overlay pane that supply their own surface material (e.g. FluentAcrylicBrush) and would otherwise get a second, redundant blur/background stacked underneath theirs."
- },
- {
- "name": "ComputedStyle",
- "kind": "property",
- "type": "String",
- "summary": "Inline \u0022position: fixed; top: \u2026px; left: \u2026px\u0022 string, filled in by FluentOverlayHost after JS interop reports the anchor\u0027s measured position. Empty until first measured, so the overlay renders off-screen-but-in-the-DOM for one frame rather than flashing at (0,0)."
- },
- {
- "name": "Content",
- "kind": "property",
- "type": "Microsoft.AspNetCore.Components.RenderFragment"
- },
- {
- "name": "Id",
- "kind": "property",
- "type": "Guid"
- },
- {
- "name": "IsClosing",
- "kind": "property",
- "type": "Boolean",
- "summary": "True once has been requested for this entry. Deliberately NOT what removes the entry from \u2014 that only happens once OverlaySurface has actually played its exit animation and called . Kept mutable (not init-only like the rest of this record) specifically so Close() can flip it on the same entry instance already in the Active list, rather than needing to replace it \u2014 FluentOverlayHost\u0027s @key=\u0022entry.Id\u0022 then keeps the same OverlaySurface component instance mounted across that mutation instead of tearing it down and losing the chance to animate out."
- },
- {
- "name": "IsDetached",
- "kind": "property",
- "type": "Boolean",
- "summary": "True for overlays created via \u2014 no anchor element, positioned relative to the viewport instead. OverlaySurface uses this to skip the anchor-measurement JS round-trip entirely (ComputedStyle is already filled in by OverlayService by the time this is true) and to register light-dismiss without an anchor exclusion zone."
- },
- {
- "name": "LightDismiss",
- "kind": "property",
- "type": "Boolean"
- },
- {
- "name": "MatchAnchorWidth",
- "kind": "property",
- "type": "Boolean",
- "summary": "When true, the popup is resized to exactly match the anchor\u0027s width before being positioned (e.g. DropDownButton/SplitButton\u0027s menu, which should read as \u0022attached to\u0022 the trigger rather than an independently-sized flyout) \u2014 set on overlay-interop.js\u0027s computePosition call, since the anchor\u0027s width isn\u0027t known until the JS side measures it via getBoundingClientRect."
- },
- {
- "name": "PreferredPlacement",
- "kind": "property",
- "type": "FluentKit.Overlay.OverlayPlacement"
- },
- {
- "name": "ScrollAnchorIntoView",
- "kind": "property",
- "type": "Boolean",
- "summary": "When true, the anchor is scrolled into view (centered, instant) before its position is measured \u2014 for overlays like TeachingTip where the anchor may not be the same element the person just clicked (see FluentTeachingTip\u0027s separate Target parameter), so there\u0027s no guarantee it\u0027s already on screen the way a Flyout/MenuFlyout\u0027s own trigger always is."
- },
- {
- "name": "WatchAnchorRemoved",
- "kind": "property",
- "type": "Boolean",
- "summary": "When true, the overlay auto-closes if its anchor element is later removed from the DOM while still open \u2014 for overlays like TeachingTip\u0027s Target mode, where the anchor isn\u0027t the trigger the person clicked and so isn\u0027t guaranteed to stay mounted for the tip\u0027s lifetime (e.g. it lived inside a list item that got filtered out). Flyout/MenuFlyout don\u0027t need this: their anchor is always their own trigger, which can\u0027t disappear out from under an open overlay it\u0027s the parent of."
- }
- ]
- },
- {
- "name": "OverlayPlacement",
- "fullName": "FluentKit.Overlay.OverlayPlacement",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Bottom",
- "Left",
- "Right",
- "Top"
- ],
- "summary": "Preferred placement relative to the anchor element. The JS interop layer (overlay-interop.js) flips this to the opposite side when there isn\u0027t room in the viewport \u2014 callers get the flipped result back, they don\u0027t need to reason about collision themselves.",
- "members": []
- },
- {
- "name": "OverlayScreenPlacement",
- "fullName": "FluentKit.Overlay.OverlayScreenPlacement",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "BottomCenter",
- "BottomLeft",
- "BottomRight",
- "Center",
- "TopCenter",
- "TopLeft",
- "TopRight"
- ],
- "summary": "Placement for a \u0022detached\u0022 overlay that has no anchor element to position relative to (see ) \u2014 positioned relative to the viewport instead, via a plain CSS \u0060position: fixed\u0060 computed synchronously in (unlike the anchor-based path, this needs no JS measurement round-trip, since there\u0027s no anchor rect to measure \u2014 the overlay can be positioned correctly on its very first render instead of one frame later).",
- "members": []
- },
- {
- "name": "OverlayService",
- "fullName": "FluentKit.Overlay.OverlayService",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "members": [
- {
- "name": "Active",
- "kind": "property",
- "type": "IReadOnlyList\u003CFluentKit.Overlay.OverlayEntry\u003E"
- },
- {
- "name": "Close",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "CloseAll",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "CompleteClose",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "RefreshContent",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "Show",
- "kind": "method",
- "type": "Guid"
- },
- {
- "name": "ShowDetached",
- "kind": "method",
- "type": "Guid"
- },
- {
- "name": "Update",
- "kind": "method",
- "type": "Boolean"
- }
- ]
- },
- {
- "name": "ButtonVariant",
- "fullName": "FluentKit.Primitives.ButtonVariant",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Accent",
- "Hyperlink",
- "Standard",
- "Subtle"
- ],
- "summary": "Mirrors WinUI\u0027s Button / AccentButton (via the IsAccent-equivalent style) / HyperlinkButton split. \u0022Subtle\u0022 matches what WinUI calls a SubtleButton-styled Button (no border, transparent by default).",
- "members": []
- },
- {
- "name": "FluentIconNames",
- "fullName": "FluentKit.Primitives.FluentIconNames",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "summary": "Hand-picked, ergonomic aliases for the most commonly used Fluent System Icons glyphs (e.g. Name=\u0022@FluentIconNames.Home\u0022). For the full, exhaustive set of ~9,600 glyphs (auto-generated from wwwroot/Icons/FluentSystemIcons-Regular.css by tools/generate-icon-names.py), see the other partial of this class in FluentIconNames.g.cs, e.g. FluentIconNames.Beaker20.",
- "members": []
- },
- {
- "name": "FluentSliderOrientation",
- "fullName": "FluentKit.Primitives.FluentSliderOrientation",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Horizontal",
- "Vertical"
- ],
- "members": []
- },
- {
- "name": "FluentSliderTickPlacement",
- "fullName": "FluentKit.Primitives.FluentSliderTickPlacement",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Inline",
- "None",
- "Outside"
- ],
- "members": []
- },
- {
- "name": "IFocusableListItem",
- "fullName": "FluentKit.Primitives.IFocusableListItem",
- "kind": "interface",
- "genericParameters": [],
- "values": [],
- "summary": "Minimal contract FluentListView needs to drive roving arrow-key focus across its children \u2014 deliberately NOT tied to FluentListViewItem\u0027s concrete type, so other composites (e.g. FluentNavigationViewItem, which owns its own selection via NavigationViewContext instead of FluentListViewItem\u0027s plain Selected/OnSelect) can register into the same list for keyboard nav without adopting FluentListViewItem\u0027s markup or selection model.",
- "members": [
- {
- "name": "Disabled",
- "kind": "property",
- "type": "Boolean"
- },
- {
- "name": "FocusAsync",
- "kind": "method",
- "type": "Threading.Tasks.ValueTask"
- }
- ]
- },
- {
- "name": "IListViewHost",
- "fullName": "FluentKit.Primitives.IListViewHost",
- "kind": "interface",
- "genericParameters": [],
- "values": [],
- "summary": "Non-generic contract FluentListViewItem cascades against. FluentListView\u003CTValue\u003E is a different closed generic type per TValue (FluentListView\u003Cstring\u003E and FluentListView\u003Cint\u003E don\u0027t share a type), so a plain [CascadingParameter] FluentListView\u003CTValue\u003E? on FluentListViewItem could never bind to it \u2014 FluentListViewItem itself has no TValue to close over. Cascading this interface instead sidesteps that: any FluentListView\u003CTValue\u003E implements it the same way regardless of TValue.",
- "members": [
- {
- "name": "FocusAdjacentAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- },
- {
- "name": "FocusEndAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- },
- {
- "name": "Register",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "Unregister",
- "kind": "method",
- "type": "Void"
- }
- ]
- },
- {
- "name": "InfoBadgeSeverity",
- "fullName": "FluentKit.Primitives.InfoBadgeSeverity",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Attention",
- "Caution",
- "Critical",
- "Informational",
- "Success"
- ],
- "members": []
- },
- {
- "name": "ListViewItem\u00601",
- "fullName": "FluentKit.Primitives.ListViewItem\u00601",
- "kind": "class",
- "genericParameters": [
- "TValue"
- ],
- "values": [],
- "summary": "One row of data in a data-bound \u2014 same Name/Value/Disabled shape as and , kept as its own type (rather than shared) since ListView lives in Primitives while those two are Composite, and each already treats its own record as the \u0022one row of data\u0022 contract for that control.",
- "members": [
- {
- "name": "Deconstruct",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "Disabled",
- "kind": "property",
- "type": "Boolean"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "GetHashCode",
- "kind": "method",
- "type": "Int32"
- },
- {
- "name": "Name",
- "kind": "property",
- "type": "String"
- },
- {
- "name": "ToString",
- "kind": "method",
- "type": "String"
- },
- {
- "name": "Value",
- "kind": "property",
- "type": "TValue"
- }
- ]
- },
- {
- "name": "ListViewSelectionMode",
- "fullName": "FluentKit.Primitives.ListViewSelectionMode",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Multiple",
- "None",
- "Single"
- ],
- "summary": "Mirrors WinUI ListView\u0027s SelectionMode \u2014 how many rows can be selected at once, and what visual affordance each row gets. WinUI also has \u0022Extended\u0022 (click = single, Ctrl/Shift-click = range/multi), which is a desktop mouse-modifier interaction model that doesn\u0027t map cleanly onto a plain click/tap row \u2014 left out here in favor of the two modes that do (Single/Multiple), same reasoning ComboBox uses for not exposing multi-select.",
- "members": []
- },
- {
- "name": "RadioGroupContext",
- "fullName": "FluentKit.Primitives.RadioGroupContext",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "summary": "Cascaded context a FluentRadioGroup exposes to its child FluentRadioButtons, mirroring the \u0022group\u0022 binding fluent-svelte\u0027s RadioButton exposes via bind:group \u2014 Blazor has no native equivalent to Svelte\u0027s cross-component two-way binding, so a small explicit context does the job.",
- "members": [
- {
- "name": "SelectedValue",
- "kind": "property",
- "type": "Object"
- },
- {
- "name": "SetValueAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- }
- ]
- },
- {
- "name": "TextBlockVariant",
- "fullName": "FluentKit.Primitives.TextBlockVariant",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Body",
- "BodyLarge",
- "BodyStrong",
- "Caption",
- "Display",
- "Subtitle",
- "Title",
- "TitleLarge"
- ],
- "summary": "Mirrors WinUI\u0027s TextBlock / fluent-svelte\u0027s TextBlock: a single component for every text style in the type ramp, each with a sensible default semantic tag (Subtitle -\u003E h4, Body -\u003E span, etc.) that can override \u2014 e.g. force a Title-styled span instead of an h3.",
- "members": []
- },
- {
- "name": "AccentColorService",
- "fullName": "FluentKit.Theming.AccentColorService",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "summary": "See . Same DI lifetime story as (Scoped).",
- "members": [
- {
- "name": "CurrentAccent",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor"
- },
- {
- "name": "CurrentPalette",
- "kind": "property",
- "type": "FluentKit.Theming.AccentPalette"
- },
- {
- "name": "DisposeAsync",
- "kind": "method",
- "type": "Threading.Tasks.ValueTask"
- },
- {
- "name": "InitializeAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- },
- {
- "name": "SetAccentAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- },
- {
- "name": "SetAccentFromImageAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- }
- ]
- },
- {
- "name": "AccentPalette",
- "fullName": "FluentKit.Theming.AccentPalette",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "summary": "The 7-step ramp WinUI derives from a single \u0022system accent color\u0022: the base color plus three lightened steps (used as the accent ramp in dark theme, where fills/text need to get *brighter* than the base to read well on dark backgrounds) and three darkened steps (used in light theme, where fills/text need to get *darker* than the base to read on light backgrounds). NOTE ON FIDELITY: real WinUI (Microsoft.UI.Xaml.Media.AccentColors / the \u0022Fluent XAML Theme Editor\u0022) computes this ramp with an internal, undocumented algorithm that isn\u0027t public source. This is a from-scratch approximation \u2014 simple linear tint/shade mixing in sRGB space \u2014 tuned so the default Windows blue (#0078D4) produces a ramp that\u0027s visually in the right neighborhood of the real one. It will not byte-for-byte match Windows\u0027 own palette for arbitrary colors, but it\u0027s perceptually reasonable, monotonic, and (crucially) works for *any* input color, which a lookup table copied from the default blue would not.",
- "members": [
- {
- "name": "Base",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor"
- },
- {
- "name": "Dark1",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor"
- },
- {
- "name": "Dark2",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor"
- },
- {
- "name": "Dark3",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor"
- },
- {
- "name": "Fallback",
- "kind": "property",
- "type": "FluentKit.Theming.AccentPalette"
- },
- {
- "name": "FallbackBase",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor",
- "summary": "The color WinUI falls back to when there\u0027s no OS/user accent to read \u2014 Windows default blue."
- },
- {
- "name": "FromColor",
- "kind": "method",
- "type": "FluentKit.Theming.AccentPalette"
- },
- {
- "name": "FromHex",
- "kind": "method",
- "type": "FluentKit.Theming.AccentPalette"
- },
- {
- "name": "Light1",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor"
- },
- {
- "name": "Light2",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor"
- },
- {
- "name": "Light3",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor"
- },
- {
- "name": "ToCssVariables",
- "kind": "method",
- "type": "IReadOnlyDictionary\u003CString, String\u003E",
- "summary": "Every ramp entry as a flat \u0022cssVarName -\u003E value\u0022 map: hex for direct use, plus a \u0022-rgb\u0022 sibling per entry holding the comma-separated triplet so callers can build rgba(var(--accent-base-rgb), 0.5) for translucent fills. Applied to the DOM by theme-interop.js\u0027s applyAccentPalette."
- }
- ]
- },
- {
- "name": "IAccentColorService",
- "fullName": "FluentKit.Theming.IAccentColorService",
- "kind": "interface",
- "genericParameters": [],
- "values": [],
- "summary": "DI-registered (same lifetime rules as \u2014 Scoped everywhere) service that owns the app\u0027s accent color: the single base color WinUI derives its whole 7-step accent ramp from (see ), and pushes that ramp into CSS custom properties on :root so every FluentKit component that reads --accent-* updates live. Three ways to set it, matching how Windows itself offers accent color: - \u2014 explicit color, e.g. from a color picker. - \u2014 derive it from a wallpaper/photo, like Windows\u0027 \u0022Automatically pick an accent color from my background\u0022. - Do nothing \u2014 applies (Windows\u0027 own default blue) so the app never renders with an unset accent.",
- "members": [
- {
- "name": "CurrentAccent",
- "kind": "property",
- "type": "FluentKit.Theming.RgbColor",
- "summary": "The current base accent color (before ramp expansion)."
- },
- {
- "name": "CurrentPalette",
- "kind": "property",
- "type": "FluentKit.Theming.AccentPalette",
- "summary": "The full derived 7-step ramp for ."
- },
- {
- "name": "InitializeAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task",
- "summary": "Applies the fallback blue so accent CSS variables exist before any explicit choice is made. Safe to call multiple times."
- },
- {
- "name": "SetAccentAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- },
- {
- "name": "SetAccentFromImageAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- }
- ]
- },
- {
- "name": "IThemeService",
- "fullName": "FluentKit.Theming.IThemeService",
- "kind": "interface",
- "genericParameters": [],
- "values": [],
- "summary": "DI-registered, not component state \u2014 so Blazor Server (one instance can serve many circuits) and MAUI Hybrid (single process, single user) both get correct, independent behavior. Register as Scoped in Server/WASM, Scoped is fine in MAUI Hybrid too (one scope per app instance).",
- "members": [
- {
- "name": "InitializeAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task",
- "summary": "Called once by ThemeProvider on first render to read prefers-color-scheme via JS interop."
- },
- {
- "name": "Mode",
- "kind": "property",
- "type": "FluentKit.Theming.ThemeMode"
- },
- {
- "name": "ResolvedTheme",
- "kind": "property",
- "type": "String",
- "summary": "The resolved theme actually being applied \u2014 \u0022System\u0022 resolves to Light or Dark here."
- },
- {
- "name": "SetModeAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- }
- ]
- },
- {
- "name": "RgbColor",
- "fullName": "FluentKit.Theming.RgbColor",
- "kind": "struct",
- "genericParameters": [],
- "values": [],
- "summary": "A plain sRGB color. Kept deliberately dumb (no HSL fields, no WinUI Windows.UI.Color dependency) so this project has zero platform coupling \u2014 works identically in WASM, Server, and MAUI Hybrid.",
- "members": [
- {
- "name": "B",
- "kind": "property",
- "type": "Byte"
- },
- {
- "name": "Deconstruct",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "Equals",
- "kind": "method",
- "type": "Boolean"
- },
- {
- "name": "G",
- "kind": "property",
- "type": "Byte"
- },
- {
- "name": "GetHashCode",
- "kind": "method",
- "type": "Int32"
- },
- {
- "name": "Parse",
- "kind": "method",
- "type": "FluentKit.Theming.RgbColor"
- },
- {
- "name": "PrefersDarkForeground",
- "kind": "method",
- "type": "Boolean",
- "summary": "True when black text reads better on this color than white text (i.e. the color is \u0022light\u0022)."
- },
- {
- "name": "R",
- "kind": "property",
- "type": "Byte"
- },
- {
- "name": "RelativeLuminance",
- "kind": "method",
- "type": "Double",
- "summary": "WCAG relative luminance (0=black, 1=white). Used to decide black-vs-white text on top of this color."
- },
- {
- "name": "ToHex",
- "kind": "method",
- "type": "String"
- },
- {
- "name": "ToRgbTriplet",
- "kind": "method",
- "type": "String",
- "summary": "\u0022r, g, b\u0022 \u2014 the comma-separated form CSS needs for rgba(var(--x-rgb), alpha)."
- },
- {
- "name": "ToString",
- "kind": "method",
- "type": "String"
- },
- {
- "name": "TryParse",
- "kind": "method",
- "type": "Boolean"
- }
- ]
- },
- {
- "name": "ThemeMode",
- "fullName": "FluentKit.Theming.ThemeMode",
- "kind": "enum",
- "genericParameters": [],
- "values": [
- "Dark",
- "Light",
- "System"
- ],
- "summary": "The three theme modes WinUI itself exposes. \u0022System\u0022 tracks the OS/browser preference live; Light/Dark are explicit overrides the user picked.",
- "members": []
- },
- {
- "name": "ThemeService",
- "fullName": "FluentKit.Theming.ThemeService",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "members": [
- {
- "name": "DisposeAsync",
- "kind": "method",
- "type": "Threading.Tasks.ValueTask"
- },
- {
- "name": "InitializeAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- },
- {
- "name": "Mode",
- "kind": "property",
- "type": "FluentKit.Theming.ThemeMode"
- },
- {
- "name": "OnSystemPreferenceChanged",
- "kind": "method",
- "type": "Void"
- },
- {
- "name": "ResolvedTheme",
- "kind": "property",
- "type": "String"
- },
- {
- "name": "SetModeAsync",
- "kind": "method",
- "type": "Threading.Tasks.Task"
- }
- ]
- },
- {
- "name": "_Imports",
- "fullName": "FluentKit._Imports",
- "kind": "class",
- "genericParameters": [],
- "values": [],
- "members": []
- }
- ],
- "missingSummaries": [
- "P:FluentKit.Common.SizableComponentBase.Height",
- "P:FluentKit.Common.SizableComponentBase.Height",
- "P:FluentKit.Common.SizableComponentBase.MaxHeight",
- "P:FluentKit.Common.SizableComponentBase.MaxHeight",
- "P:FluentKit.Common.SizableComponentBase.MaxWidth",
- "P:FluentKit.Common.SizableComponentBase.MaxWidth",
- "P:FluentKit.Common.SizableComponentBase.MinHeight",
- "P:FluentKit.Common.SizableComponentBase.MinHeight",
- "P:FluentKit.Common.SizableComponentBase.MinWidth",
- "P:FluentKit.Common.SizableComponentBase.MinWidth",
- "P:FluentKit.Common.SizableComponentBase.Width",
- "P:FluentKit.Common.SizableComponentBase.Width",
- "P:FluentKit.Composite.FluentAutoSuggestBox\u00601.Disabled",
- "P:FluentKit.Composite.FluentAutoSuggestBox\u00601.Header",
- "P:FluentKit.Composite.FluentAutoSuggestBox\u00601.Items",
- "P:FluentKit.Composite.FluentAutoSuggestBox\u00601.Placeholder",
- "P:FluentKit.Composite.FluentAutoSuggestBox\u00601.TextChanged",
- "P:FluentKit.Composite.FluentCalendarDatePicker.Blackout",
- "P:FluentKit.Composite.FluentCalendarDatePicker.Disabled",
- "P:FluentKit.Composite.FluentCalendarDatePicker.Locale",
- "P:FluentKit.Composite.FluentCalendarDatePicker.Max",
- "P:FluentKit.Composite.FluentCalendarDatePicker.Min",
- "P:FluentKit.Composite.FluentCalendarDatePicker.Placeholder",
- "P:FluentKit.Composite.FluentCalendarDatePicker.Value",
- "P:FluentKit.Composite.FluentCalendarDatePicker.ValueChanged",
- "P:FluentKit.Composite.FluentCalendarDatePicker.ValuesChanged",
- "P:FluentKit.Composite.FluentCalendarDatePicker.WeekStart",
- "P:FluentKit.Composite.FluentCalendarView.Max",
- "P:FluentKit.Composite.FluentCalendarView.Min",
- "P:FluentKit.Composite.FluentCalendarView.ValueChanged",
- "P:FluentKit.Composite.FluentCalendarView.ValuesChanged",
- "P:FluentKit.Composite.FluentCalendarView.ViewChanged",
- "P:FluentKit.Composite.FluentCalendarViewItem.Blackout",
- "P:FluentKit.Composite.FluentCalendarViewItem.Current",
- "P:FluentKit.Composite.FluentCalendarViewItem.Disabled",
- "P:FluentKit.Composite.FluentCalendarViewItem.OnClick",
- "P:FluentKit.Composite.FluentCalendarViewItem.OnKeyDown",
- "P:FluentKit.Composite.FluentCalendarViewItem.OutOfRange",
- "P:FluentKit.Composite.FluentCalendarViewItem.Selected",
- "P:FluentKit.Composite.FluentCalendarViewItem.TabIndex",
- "P:FluentKit.Composite.FluentCalendarViewItem.Variant",
- "P:FluentKit.Composite.FluentComboBox\u00601.Disabled",
- "P:FluentKit.Composite.FluentComboBox\u00601.Items",
- "P:FluentKit.Composite.FluentComboBox\u00601.Placeholder",
- "P:FluentKit.Composite.FluentComboBox\u00601.SearchValueChanged",
- "P:FluentKit.Composite.FluentComboBox\u00601.ValueChanged",
- "P:FluentKit.Composite.FluentContentDialog.IsOpenChanged",
- "P:FluentKit.Composite.FluentContentDialog.OnBackdropClick",
- "P:FluentKit.Composite.FluentContentDialog.Size",
- "P:FluentKit.Composite.FluentContextMenu.CloseOnSelect",
- "P:FluentKit.Composite.FluentContextMenu.IsOpen",
- "P:FluentKit.Composite.FluentContextMenu.IsOpenChanged",
- "P:FluentKit.Composite.FluentDropDownButton.Disabled",
- "P:FluentKit.Composite.FluentDropDownButton.IsOpen",
- "P:FluentKit.Composite.FluentDropDownButton.IsOpenChanged",
- "P:FluentKit.Composite.FluentDropDownButton.Placement",
- "P:FluentKit.Composite.FluentDropDownButton.Variant",
- "P:FluentKit.Composite.FluentFlyout.IsOpenChanged",
- "P:FluentKit.Composite.FluentMenuBarItem.Disabled",
- "P:FluentKit.Composite.FluentMenuFlyout.IsOpen",
- "P:FluentKit.Composite.FluentMenuFlyout.IsOpenChanged",
- "P:FluentKit.Composite.FluentMenuFlyout.Placement",
- "P:FluentKit.Composite.FluentMenuFlyoutItem.CheckedChanged",
- "P:FluentKit.Composite.FluentMenuFlyoutItem.Disabled",
- "P:FluentKit.Composite.FluentMenuFlyoutItem.Variant",
- "P:FluentKit.Composite.FluentMenuFlyoutSurface.OnKeyDown",
- "P:FluentKit.Composite.FluentNavigationView.AlwaysShowHeader",
- "P:FluentKit.Composite.FluentNavigationView.BackRequested",
- "P:FluentKit.Composite.FluentNavigationView.HeaderContent",
- "P:FluentKit.Composite.FluentNavigationView.IsBackButtonEnabled",
- "P:FluentKit.Composite.FluentNavigationView.IsBackButtonVisible",
- "P:FluentKit.Composite.FluentNavigationView.IsPaneOpen",
- "P:FluentKit.Composite.FluentNavigationView.IsPaneOpenChanged",
- "P:FluentKit.Composite.FluentNavigationView.ItemInvoked",
- "P:FluentKit.Composite.FluentNavigationView.MenuItems",
- "P:FluentKit.Composite.FluentNavigationView.PaneDisplayMode",
- "P:FluentKit.Composite.FluentNavigationView.PaneFooter",
- "P:FluentKit.Composite.FluentNavigationView.PaneHeader",
- "P:FluentKit.Composite.FluentNavigationView.PaneHeaderIcon",
- "P:FluentKit.Composite.FluentNavigationView.SelectedValue",
- "P:FluentKit.Composite.FluentNavigationView.SelectedValueChanged",
- "P:FluentKit.Composite.FluentNavigationView.SettingsRequested",
- "P:FluentKit.Composite.FluentNavigationView.ShowSettingsButton",
- "P:FluentKit.Composite.FluentNavigationViewItem.Disabled",
- "P:FluentKit.Composite.FluentNavigationViewItem.Icon",
- "P:FluentKit.Composite.FluentNavigationViewItem.IsExpanded",
- "P:FluentKit.Composite.FluentNavigationViewItem.IsExpandedChanged",
- "P:FluentKit.Composite.FluentNavigationViewItem.IsSelectable",
- "P:FluentKit.Composite.FluentNavigationViewItem.Text",
- "P:FluentKit.Composite.FluentNavigationViewItem.Value",
- "P:FluentKit.Composite.FluentNavigationViewItemHeader.Text",
- "P:FluentKit.Composite.FluentNumberBox.Disabled",
- "P:FluentKit.Composite.FluentNumberBox.Header",
- "P:FluentKit.Composite.FluentNumberBox.Max",
- "P:FluentKit.Composite.FluentNumberBox.Min",
- "P:FluentKit.Composite.FluentNumberBox.Placeholder",
- "P:FluentKit.Composite.FluentNumberBox.Step",
- "P:FluentKit.Composite.FluentNumberBox.Value",
- "P:FluentKit.Composite.FluentNumberBox.ValueChanged",
- "P:FluentKit.Composite.FluentPivot.SelectedIndex",
- "P:FluentKit.Composite.FluentPivot.SelectedIndexChanged",
- "P:FluentKit.Composite.FluentPivotItem.Disabled",
- "P:FluentKit.Composite.FluentSettingsExpander.IsExpandedChanged",
- "P:FluentKit.Composite.FluentSplitButton.Disabled",
- "P:FluentKit.Composite.FluentSplitButton.IsOpen",
- "P:FluentKit.Composite.FluentSplitButton.IsOpenChanged",
- "P:FluentKit.Composite.FluentSplitButton.OnClick",
- "P:FluentKit.Composite.FluentSplitButton.Placement",
- "P:FluentKit.Composite.FluentSplitButton.Variant",
- "P:FluentKit.Composite.FluentTeachingTip.IsOpen",
- "P:FluentKit.Composite.FluentTeachingTip.IsOpenChanged",
- "P:FluentKit.Composite.FluentTeachingTip.Subtitle",
- "P:FluentKit.Composite.FluentTeachingTip.Title",
- "P:FluentKit.Composite.FluentTimePicker.Disabled",
- "P:FluentKit.Composite.FluentTimePicker.Locale",
- "P:FluentKit.Composite.FluentTimePicker.Placeholder",
- "P:FluentKit.Composite.FluentTimePicker.SelectedTimeChanged",
- "P:FluentKit.Composite.FluentTooltip.Text",
- "P:FluentKit.Effects.FluentAcrylicBrush.Kind",
- "P:FluentKit.Effects.FluentMicaPanel.Variant",
- "P:FluentKit.Overlay.OverlaySurface.Entry",
- "P:FluentKit.Primitives.FluentButton.Disabled",
- "P:FluentKit.Primitives.FluentButton.OnClick",
- "P:FluentKit.Primitives.FluentButton.Variant",
- "P:FluentKit.Primitives.FluentCheckBox.Checked",
- "P:FluentKit.Primitives.FluentCheckBox.CheckedChanged",
- "P:FluentKit.Primitives.FluentCheckBox.Disabled",
- "P:FluentKit.Primitives.FluentCheckBox.IndeterminateChanged",
- "P:FluentKit.Primitives.FluentCheckBox.ThreeState",
- "P:FluentKit.Primitives.FluentDivider.Vertical",
- "P:FluentKit.Primitives.FluentExpander.IsExpanded",
- "P:FluentKit.Primitives.FluentExpander.IsExpandedChanged",
- "P:FluentKit.Primitives.FluentIcon.Name",
- "P:FluentKit.Primitives.FluentIconButton.Disabled",
- "P:FluentKit.Primitives.FluentIconButton.OnClick",
- "P:FluentKit.Primitives.FluentIconButton.Variant",
- "P:FluentKit.Primitives.FluentInfoBadge.Severity",
- "P:FluentKit.Primitives.FluentInfoBar.IsOpen",
- "P:FluentKit.Primitives.FluentInfoBar.IsOpenChanged",
- "P:FluentKit.Primitives.FluentInfoBar.Message",
- "P:FluentKit.Primitives.FluentInfoBar.Severity",
- "P:FluentKit.Primitives.FluentInfoBar.Title",
- "P:FluentKit.Primitives.FluentListViewItem.Disabled",
- "P:FluentKit.Primitives.FluentListViewItem.OnSelect",
- "P:FluentKit.Primitives.FluentListViewItem.Selected",
- "P:FluentKit.Primitives.FluentListView\u00601.SelectedValueChanged",
- "P:FluentKit.Primitives.FluentListView\u00601.SelectedValuesChanged",
- "P:FluentKit.Primitives.FluentPasswordBox.Disabled",
- "P:FluentKit.Primitives.FluentPasswordBox.Header",
- "P:FluentKit.Primitives.FluentPasswordBox.Placeholder",
- "P:FluentKit.Primitives.FluentPasswordBox.Value",
- "P:FluentKit.Primitives.FluentPasswordBox.ValueChanged",
- "P:FluentKit.Primitives.FluentPersonPicture.Src",
- "P:FluentKit.Primitives.FluentProgressBar.IsIndeterminate",
- "P:FluentKit.Primitives.FluentProgressRing.IsIndeterminate",
- "P:FluentKit.Primitives.FluentRadioButton.Checked",
- "P:FluentKit.Primitives.FluentRadioButton.CheckedChanged",
- "P:FluentKit.Primitives.FluentRadioButton.Disabled",
- "P:FluentKit.Primitives.FluentRadioButton.Value",
- "P:FluentKit.Primitives.FluentRadioGroup.Value",
- "P:FluentKit.Primitives.FluentRadioGroup.ValueChanged",
- "P:FluentKit.Primitives.FluentSlider.Disabled",
- "P:FluentKit.Primitives.FluentSlider.Max",
- "P:FluentKit.Primitives.FluentSlider.Min",
- "P:FluentKit.Primitives.FluentSlider.Orientation",
- "P:FluentKit.Primitives.FluentSlider.Step",
- "P:FluentKit.Primitives.FluentSlider.TickPlacement",
- "P:FluentKit.Primitives.FluentSlider.Value",
- "P:FluentKit.Primitives.FluentSlider.ValueChanged",
- "P:FluentKit.Primitives.FluentTextBlock.Class",
- "P:FluentKit.Primitives.FluentTextBlock.Variant",
- "P:FluentKit.Primitives.FluentTextBox.Disabled",
- "P:FluentKit.Primitives.FluentTextBox.Header",
- "P:FluentKit.Primitives.FluentTextBox.Placeholder",
- "P:FluentKit.Primitives.FluentTextBox.Type",
- "P:FluentKit.Primitives.FluentTextBox.Value",
- "P:FluentKit.Primitives.FluentTextBox.ValueChanged",
- "P:FluentKit.Primitives.FluentTextBoxButton.Disabled",
- "P:FluentKit.Primitives.FluentTextBoxButton.OnClick",
- "P:FluentKit.Primitives.FluentToggleButton.Checked",
- "P:FluentKit.Primitives.FluentToggleButton.CheckedChanged",
- "P:FluentKit.Primitives.FluentToggleButton.Disabled",
- "P:FluentKit.Primitives.FluentToggleSwitch.Checked",
- "P:FluentKit.Primitives.FluentToggleSwitch.CheckedChanged",
- "P:FluentKit.Primitives.FluentToggleSwitch.Disabled",
- "P:FluentKit.Primitives.FluentToggleSwitch.Header",
- "P:FluentKit.Primitives.FluentToggleSwitch.OffContent",
- "P:FluentKit.Primitives.FluentToggleSwitch.OnContent",
- "T:FluentKit.Composite.FluentMenuBar",
- "T:FluentKit.Composite.FluentMenuFlyoutDivider",
- "T:FluentKit.Composite.FluentMenuFlyoutSurface",
- "T:FluentKit.Composite.FluentNavigationView",
- "T:FluentKit.Composite.FluentNavigationViewItem",
- "T:FluentKit.Composite.FluentNavigationViewItemHeader",
- "T:FluentKit.Composite.FluentNavigationViewItemSeparator",
- "T:FluentKit.Composite.MenuFlyoutItemVariant",
- "T:FluentKit.Composite.NavigationViewContext",
- "T:FluentKit.Composite.NavigationViewDisplayMode",
- "T:FluentKit.Composite.NavigationViewPaneDisplayMode",
- "T:FluentKit.Effects.FluentAcrylicBrush",
- "T:FluentKit.Effects.FluentMicaPanel",
- "T:FluentKit.Overlay.FluentOverlayHost",
- "T:FluentKit.Overlay.OverlayEntry",
- "T:FluentKit.Overlay.OverlayService",
- "T:FluentKit.Overlay.OverlaySurface",
- "T:FluentKit.Primitives.FluentSliderOrientation",
- "T:FluentKit.Primitives.FluentSliderTickPlacement",
- "T:FluentKit.Primitives.FluentTextBlock",
- "T:FluentKit.Primitives.InfoBadgeSeverity",
- "T:FluentKit.Theming.ThemeProvider",
- "T:FluentKit.Theming.ThemeService",
- "T:FluentKit._Imports"
- ]
-}
diff --git a/fluentkit-api/references/overlays.md b/fluentkit-api/references/overlays.md
deleted file mode 100644
index 4da9334..0000000
--- a/fluentkit-api/references/overlays.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Overlays
-
-Tooltips, flyouts, menus, dialogs, and teaching tips require `IOverlayService` and one
-long-lived `FluentOverlayHost` under `ThemeProvider`. Keep the host mounted across navigation
-and do not create one host per page.
diff --git a/fluentkit-api/references/sample-routes.md b/fluentkit-api/references/sample-routes.md
deleted file mode 100644
index ace7bd2..0000000
--- a/fluentkit-api/references/sample-routes.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# Sample routes
-
-The `sample` field in `api.json` points to the corresponding route in the live FluentKit
-sample. Use it to compare markup and interaction behavior for the exact package version.
diff --git a/fluentkit-api/references/theming-and-tokens.md b/fluentkit-api/references/theming-and-tokens.md
deleted file mode 100644
index 546a808..0000000
--- a/fluentkit-api/references/theming-and-tokens.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Theming and tokens
-
-`ThemeProvider` applies light, dark, or system mode to `` through `data-theme`.
-Link `Tokens/tokens.css` as the public token entry point and use its variables in app CSS.
-Do not link imported token implementation files directly.
diff --git a/fluentkit-api/references/troubleshooting.md b/fluentkit-api/references/troubleshooting.md
deleted file mode 100644
index e13a74f..0000000
--- a/fluentkit-api/references/troubleshooting.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# Troubleshooting
-
-- A 404 under `_content/FluentKit`: clean the library and app `bin`/`obj`, then rebuild.
-- An invisible overlay: verify `IOverlayService` and `FluentOverlayHost` registration.
-- Missing theme variables: link `_content/FluentKit/Tokens/tokens.css` before app CSS.
-- A stale isolated stylesheet: perform a full clean before investigating CSS.
diff --git a/fluentkit-api/scripts/resolve-fluentkit.ps1 b/fluentkit-api/scripts/resolve-fluentkit.ps1
new file mode 100644
index 0000000..6d0007e
--- /dev/null
+++ b/fluentkit-api/scripts/resolve-fluentkit.ps1
@@ -0,0 +1,43 @@
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory = $true, Position = 0)]
+ [string] $Project,
+
+ [Parameter()]
+ [string[]] $Property = @(),
+
+ [Parameter()]
+ [string[]] $Source = @()
+)
+
+$projectPath = [IO.Path]::GetFullPath($Project)
+if (-not (Test-Path -LiteralPath $projectPath -PathType Leaf)) {
+ Write-Error "Consumer project not found: $projectPath"
+ exit 2
+}
+
+$projectDirectory = Split-Path -Parent $projectPath
+$msbuildProperties = @($Property | ForEach-Object { "-p:$_" })
+$restoreSources = @()
+foreach ($sourcePath in $Source) { $restoreSources += @('--source', $sourcePath) }
+if (Test-Path -LiteralPath (Join-Path $projectDirectory 'packages.lock.json') -PathType Leaf) {
+ & dotnet restore $projectPath --locked-mode @restoreSources @msbuildProperties
+} else {
+ & dotnet restore $projectPath @restoreSources @msbuildProperties
+}
+if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+
+$skillPath = (& dotnet msbuild $projectPath -nologo @msbuildProperties -getProperty:FluentKitAgentSkillPath | Select-Object -Last 1).Trim()
+$manifestPath = (& dotnet msbuild $projectPath -nologo @msbuildProperties -getProperty:FluentKitAgentManifestPath | Select-Object -Last 1).Trim()
+
+if ([string]::IsNullOrWhiteSpace($skillPath) -or [string]::IsNullOrWhiteSpace($manifestPath)) {
+ Write-Error 'FluentKit.Blazor does not expose the agent contract. Upgrade to the first contract-bearing release.'
+ exit 3
+}
+if (-not (Test-Path -LiteralPath $skillPath -PathType Leaf) -or -not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
+ Write-Error "FluentKit agent contract is incomplete after restore. Skill: $skillPath; Manifest: $manifestPath"
+ exit 4
+}
+
+Write-Output "FluentKitAgentSkillPath=$skillPath"
+Write-Output "FluentKitAgentManifestPath=$manifestPath"
diff --git a/fluentkit-api/scripts/resolve-fluentkit.sh b/fluentkit-api/scripts/resolve-fluentkit.sh
new file mode 100755
index 0000000..49bdaa7
--- /dev/null
+++ b/fluentkit-api/scripts/resolve-fluentkit.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [[ $# -lt 1 ]]; then
+ echo "Usage: resolve-fluentkit.sh /absolute/path/to/Consumer.csproj [--property Name=Value ...] [--source PATH ...]" >&2
+ exit 2
+fi
+
+project_path="$(cd -- "$(dirname -- "$1")" && pwd)/$(basename -- "$1")"
+shift
+msbuild_properties=()
+restore_sources=()
+while [[ $# -gt 0 ]]; do
+ if [[ $# -lt 2 ]]; then
+ echo "Usage: resolve-fluentkit.sh /absolute/path/to/Consumer.csproj [--property Name=Value ...] [--source PATH ...]" >&2
+ exit 2
+ fi
+ case "$1" in
+ --property) msbuild_properties+=("-p:$2") ;;
+ --source) restore_sources+=("--source" "$2") ;;
+ *)
+ echo "Usage: resolve-fluentkit.sh /absolute/path/to/Consumer.csproj [--property Name=Value ...] [--source PATH ...]" >&2
+ exit 2
+ ;;
+ esac
+ shift 2
+done
+if [[ ! -f "$project_path" ]]; then
+ echo "Consumer project not found: $project_path" >&2
+ exit 2
+fi
+
+project_directory="$(dirname -- "$project_path")"
+restore_command=(dotnet restore "$project_path")
+if [[ -f "$project_directory/packages.lock.json" ]]; then
+ restore_command+=(--locked-mode)
+fi
+if (( ${#restore_sources[@]} > 0 )); then restore_command+=("${restore_sources[@]}"); fi
+if (( ${#msbuild_properties[@]} > 0 )); then restore_command+=("${msbuild_properties[@]}"); fi
+"${restore_command[@]}"
+
+msbuild_command=(dotnet msbuild "$project_path" -nologo)
+if (( ${#msbuild_properties[@]} > 0 )); then msbuild_command+=("${msbuild_properties[@]}"); fi
+skill_path="$("${msbuild_command[@]}" -getProperty:FluentKitAgentSkillPath | tail -n 1 | tr -d '\r')"
+manifest_path="$("${msbuild_command[@]}" -getProperty:FluentKitAgentManifestPath | tail -n 1 | tr -d '\r')"
+
+if [[ -z "$skill_path" || -z "$manifest_path" ]]; then
+ echo "FluentKit.Blazor does not expose the agent contract. Upgrade to the first contract-bearing release." >&2
+ exit 3
+fi
+if [[ ! -f "$skill_path" || ! -f "$manifest_path" ]]; then
+ echo "FluentKit agent contract is incomplete after restore." >&2
+ echo "Skill: $skill_path" >&2
+ echo "Manifest: $manifest_path" >&2
+ exit 4
+fi
+
+printf 'FluentKitAgentSkillPath=%s\n' "$skill_path"
+printf 'FluentKitAgentManifestPath=%s\n' "$manifest_path"
diff --git a/samples/FluentKit.ApiSmoke/FluentKit.ApiSmoke.csproj b/samples/FluentKit.ApiSmoke/FluentKit.ApiSmoke.csproj
index a2c15dd..d53736c 100644
--- a/samples/FluentKit.ApiSmoke/FluentKit.ApiSmoke.csproj
+++ b/samples/FluentKit.ApiSmoke/FluentKit.ApiSmoke.csproj
@@ -3,13 +3,15 @@
net10.0
enable
enable
-
- 0.2.3
+
+
+
+
diff --git a/scripts/README.md b/scripts/README.md
index 1455123..0ff345c 100644
--- a/scripts/README.md
+++ b/scripts/README.md
@@ -1,7 +1,6 @@
# FluentKit maintenance scripts
-Regenerate the API reference and synchronize the skill bundle after changing the
-library API:
+Regenerate the canonical API reference after changing the library API:
```bash
./scripts/update-api-reference.sh
@@ -13,10 +12,13 @@ On Windows, run:
scripts\update-api-reference.bat
```
-Pass `Debug` or `Release` as the first argument to select the build configuration. The script
-builds `FluentKit.dll`, reads its public API and XML summaries, regenerates `docs/reference`,
-copies `api.json` into `fluentkit-api/references`, checks the summary baseline, and verifies
-that the package and skill versions match.
+Pass `Debug` or `Release` as the first argument to select the build configuration. Add
+`--verify` as the second argument to check freshness without writing generated files. The script
+builds `FluentKit.dll`, reads its public API and XML summaries, and regenerates or verifies
+`docs/reference`. The NuGet pack target independently stages the exact same contract from the
+just-built assembly into the package.
-The script does not edit `metadata.json`, create Git tags, commit files, or push anything. Update
-the skill version intentionally, run the script, review the diff, commit, and then tag the release.
+The script does not install skills, edit package versions, create Git tags, commit files, or push
+anything. Use `scripts/install-fluentkit-api-skill.*` once to install the standalone bootstrap.
+CI and release jobs run `test-fluentkit-contract.py` against the local package to verify both
+successful resolution and expected failures for missing, incomplete, stale, and tampered contracts.
diff --git a/scripts/install-fluentkit-api-skill.ps1 b/scripts/install-fluentkit-api-skill.ps1
new file mode 100644
index 0000000..286ec91
--- /dev/null
+++ b/scripts/install-fluentkit-api-skill.ps1
@@ -0,0 +1,49 @@
+[CmdletBinding()]
+param()
+
+$repository = 'https://github.com/VibeNoobNotFound/FluentKit/releases/latest/download'
+$temporaryDirectory = Join-Path ([IO.Path]::GetTempPath()) ('fluentkit-skill-' + [Guid]::NewGuid().ToString('N'))
+$archivePath = Join-Path $temporaryDirectory 'fluentkit-api.zip'
+$checksumPath = Join-Path $temporaryDirectory 'fluentkit-api.zip.sha256'
+$extractDirectory = Join-Path $temporaryDirectory 'extract'
+
+try {
+ New-Item -ItemType Directory -Path $temporaryDirectory, $extractDirectory -Force | Out-Null
+ Invoke-WebRequest -Uri "$repository/fluentkit-api.zip" -OutFile $archivePath
+ Invoke-WebRequest -Uri "$repository/fluentkit-api.zip.sha256" -OutFile $checksumPath
+
+ $expectedHash = (Get-Content -LiteralPath $checksumPath | Where-Object { $_.Trim() } | Select-Object -First 1).Split()[0]
+ $actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash.ToLowerInvariant()
+ if ([string]::IsNullOrWhiteSpace($expectedHash) -or $expectedHash.ToLowerInvariant() -ne $actualHash) {
+ throw 'FluentKit skill checksum verification failed.'
+ }
+
+ Expand-Archive -LiteralPath $archivePath -DestinationPath $extractDirectory -Force
+ $sourceDirectory = Join-Path $extractDirectory 'fluentkit-api'
+ $requiredFiles = @(
+ (Join-Path $sourceDirectory 'SKILL.md'),
+ (Join-Path $sourceDirectory 'agents/openai.yaml'),
+ (Join-Path $sourceDirectory 'scripts/resolve-fluentkit.sh'),
+ (Join-Path $sourceDirectory 'scripts/resolve-fluentkit.ps1')
+ )
+ if ($requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Leaf) }) {
+ throw 'The downloaded FluentKit skill archive is invalid.'
+ }
+
+ $codexRoot = if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $HOME '.codex' }
+ $skillsDirectory = Join-Path $codexRoot 'skills'
+ $destination = Join-Path $skillsDirectory 'fluentkit-api'
+ New-Item -ItemType Directory -Path $skillsDirectory -Force | Out-Null
+ if (Test-Path -LiteralPath $destination) {
+ $backup = "$destination.backup.$(Get-Date -Format yyyyMMddHHmmss).$([Guid]::NewGuid().ToString('N'))"
+ Move-Item -LiteralPath $destination -Destination $backup
+ Write-Output "Backed up the existing skill to $backup"
+ }
+ Move-Item -LiteralPath $sourceDirectory -Destination $destination
+ Write-Output "Installed FluentKit API bootstrap at $destination"
+}
+finally {
+ if (Test-Path -LiteralPath $temporaryDirectory) {
+ Remove-Item -LiteralPath $temporaryDirectory -Recurse -Force
+ }
+}
diff --git a/scripts/install-fluentkit-api-skill.sh b/scripts/install-fluentkit-api-skill.sh
new file mode 100755
index 0000000..686b7ca
--- /dev/null
+++ b/scripts/install-fluentkit-api-skill.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+repository="https://github.com/VibeNoobNotFound/FluentKit/releases/latest/download"
+archive_url="$repository/fluentkit-api.zip"
+checksum_url="$archive_url.sha256"
+temporary_directory="$(mktemp -d)"
+trap 'rm -rf "$temporary_directory"' EXIT
+
+archive_path="$temporary_directory/fluentkit-api.zip"
+checksum_path="$temporary_directory/fluentkit-api.zip.sha256"
+curl --fail --location --silent --show-error "$archive_url" --output "$archive_path"
+curl --fail --location --silent --show-error "$checksum_url" --output "$checksum_path"
+expected_hash="$(awk 'NF { print $1; exit }' "$checksum_path")"
+if command -v sha256sum >/dev/null 2>&1; then
+ actual_hash="$(sha256sum "$archive_path" | awk '{ print $1 }')"
+else
+ actual_hash="$(shasum -a 256 "$archive_path" | awk '{ print $1 }')"
+fi
+if [[ -z "$expected_hash" || "$expected_hash" != "$actual_hash" ]]; then
+ echo "FluentKit skill checksum verification failed." >&2
+ exit 1
+fi
+
+unexpected_entries="$(unzip -Z1 "$archive_path" | awk '$0 !~ /^fluentkit-api(\/|$)/ { print; exit }')"
+if [[ -n "$unexpected_entries" ]]; then
+ echo "The downloaded FluentKit skill archive contains an unexpected path: $unexpected_entries" >&2
+ exit 1
+fi
+unzip -q "$archive_path" -d "$temporary_directory"
+source_directory="$temporary_directory/fluentkit-api"
+if [[ ! -f "$source_directory/SKILL.md" ||
+ ! -f "$source_directory/agents/openai.yaml" ||
+ ! -f "$source_directory/scripts/resolve-fluentkit.sh" ||
+ ! -f "$source_directory/scripts/resolve-fluentkit.ps1" ]]; then
+ echo "The downloaded FluentKit skill archive is invalid." >&2
+ exit 1
+fi
+
+codex_root="${CODEX_HOME:-$HOME/.codex}"
+skills_directory="$codex_root/skills"
+destination="$skills_directory/fluentkit-api"
+mkdir -p "$skills_directory"
+if [[ -e "$destination" ]]; then
+ backup="$destination.backup.$(date +%Y%m%d%H%M%S).$$"
+ mv "$destination" "$backup"
+ echo "Backed up the existing skill to $backup"
+fi
+mv "$source_directory" "$destination"
+chmod +x "$destination/scripts/resolve-fluentkit.sh"
+echo "Installed FluentKit API bootstrap at $destination"
diff --git a/scripts/test-fluentkit-contract.py b/scripts/test-fluentkit-contract.py
new file mode 100644
index 0000000..bda550d
--- /dev/null
+++ b/scripts/test-fluentkit-contract.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+"""Exercise the package contract's success and failure boundaries in a clean temp area."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import tempfile
+import zipfile
+
+
+def run(command: list[str], cwd: Path, expect_success: bool = True, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
+ result = subprocess.run(command, cwd=cwd, env=env, text=True, capture_output=True)
+ if expect_success and result.returncode != 0:
+ raise RuntimeError(f"command failed ({result.returncode}): {' '.join(command)}\n{result.stdout}\n{result.stderr}")
+ if not expect_success and result.returncode == 0:
+ raise RuntimeError(f"command unexpectedly succeeded: {' '.join(command)}\n{result.stdout}\n{result.stderr}")
+ return result
+
+
+def resolver_command(resolver: Path, project: Path, version: str | None = None, source: Path | None = None) -> list[str]:
+ if resolver.suffix.lower() == ".ps1":
+ command = ["pwsh", "-NoProfile", "-File", str(resolver), "-Project", str(project)]
+ if version:
+ command += ["-Property", f"FluentKitSmokeVersion={version}"]
+ if source:
+ command += ["-Source", str(source)]
+ return command
+ command = ["bash", str(resolver), str(project)]
+ if version:
+ command += ["--property", f"FluentKitSmokeVersion={version}"]
+ if source:
+ command += ["--source", str(source)]
+ return command
+
+
+def write_project(directory: Path, package_reference: bool = False) -> Path:
+ project = directory / "Contract Fixture.csproj"
+ package = (
+ ' \n'
+ ' \n'
+ ' \n'
+ if package_reference else ""
+ )
+ project.write_text(
+ '\n'
+ ' net10.0\n'
+ f"{package}"
+ '\n',
+ encoding="utf-8",
+ )
+ return project
+
+
+def rewrite_package(source: Path, destination: Path, replacements: dict[str, bytes], omit: set[str] | None = None) -> None:
+ omit = omit or set()
+ with zipfile.ZipFile(source) as input_zip, zipfile.ZipFile(destination, "w", zipfile.ZIP_DEFLATED) as output_zip:
+ for entry in input_zip.infolist():
+ if entry.filename in omit:
+ continue
+ output_zip.writestr(entry, replacements.get(entry.filename, input_zip.read(entry.filename)))
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--root", type=Path, required=True)
+ parser.add_argument("--package", type=Path, required=True)
+ parser.add_argument("--version", required=True)
+ parser.add_argument("--source", type=Path, required=True)
+ parser.add_argument("--smoke-project", type=Path, required=True)
+ parser.add_argument("--resolver", type=Path, required=True)
+ parser.add_argument("--generator", type=Path, required=True)
+ parser.add_argument("--assembly", type=Path, required=True)
+ parser.add_argument("--xml", type=Path, required=True)
+ parser.add_argument("--manifest", type=Path, required=True)
+ args = parser.parse_args()
+
+ root = args.root.resolve()
+ resolver = args.resolver.resolve()
+ package = args.package.resolve()
+ source = args.source.resolve()
+ smoke_project = args.smoke_project.resolve()
+ generator = args.generator.resolve()
+ assembly = args.assembly.resolve()
+ xml = args.xml.resolve()
+ manifest = args.manifest.resolve()
+ base_env = os.environ.copy()
+
+ with tempfile.TemporaryDirectory(prefix="fluentkit contract ") as temporary:
+ temp = Path(temporary)
+
+ # No package reference (including a package from before the contract release) is a
+ # supported resolver failure, not an opportunity to use a fallback API snapshot.
+ no_package_project = write_project(temp / "no-package") if (temp / "no-package").mkdir() is None else None
+ result = run(resolver_command(resolver, no_package_project), root, expect_success=False, env=base_env)
+ if result.returncode != 3:
+ raise RuntimeError(f"missing contract should return 3, got {result.returncode}: {result.stderr}")
+
+ # Properties can exist while the package payload is incomplete; this must be reported
+ # separately so a consumer never receives a false successful resolution.
+ missing_contract = temp / "missing contract"
+ missing_contract.mkdir()
+ missing_props = missing_contract / "Directory.Build.props"
+ missing_props.write_text(
+ '\n'
+ ' missing/SKILL.md\n'
+ ' missing/manifest.json\n'
+ '\n',
+ encoding="utf-8",
+ )
+ missing_project = write_project(missing_contract)
+ result = run(resolver_command(resolver, missing_project), root, expect_success=False, env=base_env)
+ if result.returncode != 4:
+ raise RuntimeError(f"incomplete contract should return 4, got {result.returncode}: {result.stderr}")
+
+ # Restore the real smoke project from scratch in a directory containing spaces. This also
+ # proves the resolver performs the initial restore rather than depending on assets files.
+ spaced_directory = temp / "consumer with spaces"
+ spaced_directory.mkdir()
+ spaced_project = spaced_directory / smoke_project.name
+ shutil.copy2(smoke_project, spaced_project)
+ run(resolver_command(resolver, spaced_project, args.version, source), root, env=base_env)
+
+ # Generate a lock file, then invalidate it. The resolver must use locked mode and fail
+ # rather than silently changing the dependency graph.
+ locked_directory = temp / "locked consumer"
+ locked_directory.mkdir()
+ locked_project = locked_directory / smoke_project.name
+ shutil.copy2(smoke_project, locked_project)
+ locked_env = base_env.copy()
+ run(
+ ["dotnet", "restore", str(locked_project), "--use-lock-file", "--source", str(source), "-p:FluentKitSmokeVersion=" + args.version],
+ root,
+ env=locked_env,
+ )
+ lock_path = locked_directory / "packages.lock.json"
+ lock = json.loads(lock_path.read_text(encoding="utf-8"))
+ dependency = lock["dependencies"]["net10.0"]["FluentKit.Blazor"]
+ dependency["requested"] = "99.99.99"
+ lock_path.write_text(json.dumps(lock, indent=2) + "\n", encoding="utf-8")
+ run(resolver_command(resolver, locked_project, args.version, source), root, expect_success=False, env=locked_env)
+
+ generator_command = [
+ "dotnet", "run", "--project", str(generator), "-c", "Release", "--no-build", "--no-restore", "--",
+ "--verify-package", str(package), "--assembly", str(assembly), "--xml", str(xml), "--manifest", str(manifest),
+ ]
+ run(generator_command, root, env=base_env)
+
+ # A package with a missing contract file must fail before any API is consumed.
+ missing_package = temp / "missing-contract.nupkg"
+ rewrite_package(package, missing_package, {}, {"fluentkit/agent/v1/SKILL.md"})
+ missing_command = generator_command.copy()
+ missing_command[missing_command.index(str(package))] = str(missing_package)
+ run(missing_command, root, expect_success=False, env=base_env)
+
+ with zipfile.ZipFile(package) as archive:
+ package_manifest = json.loads(archive.read("fluentkit/agent/v1/manifest.json"))
+ package_api = json.loads(archive.read("fluentkit/agent/v1/api.json"))
+
+ package_manifest["packageVersion"] = "99.99.99"
+ mismatched_manifest = temp / "mismatched-manifest.nupkg"
+ rewrite_package(
+ package,
+ mismatched_manifest,
+ {"fluentkit/agent/v1/manifest.json": (json.dumps(package_manifest, indent=2) + "\n").encode()},
+ )
+ mismatch_command = generator_command.copy()
+ mismatch_command[mismatch_command.index(str(package))] = str(mismatched_manifest)
+ run(mismatch_command, root, expect_success=False, env=base_env)
+
+ package_api["version"] = "99.99.99"
+ mismatched_api = temp / "mismatched-api.nupkg"
+ rewrite_package(
+ package,
+ mismatched_api,
+ {"fluentkit/agent/v1/api.json": (json.dumps(package_api, indent=2) + "\n").encode()},
+ )
+ mismatch_command = generator_command.copy()
+ mismatch_command[mismatch_command.index(str(package))] = str(mismatched_api)
+ run(mismatch_command, root, expect_success=False, env=base_env)
+
+ print("FluentKit package contract success and failure scenarios passed.")
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except (OSError, RuntimeError, KeyError, json.JSONDecodeError) as error:
+ print(f"contract scenario test failed: {error}", file=sys.stderr)
+ raise SystemExit(1)
diff --git a/scripts/update-api-reference.bat b/scripts/update-api-reference.bat
index 0e5fde2..b9e347b 100644
--- a/scripts/update-api-reference.bat
+++ b/scripts/update-api-reference.bat
@@ -1,26 +1,20 @@
@echo off
setlocal EnableExtensions
-rem Rebuild FluentKit, regenerate the API reference, copy the skill JSON, and verify freshness.
+rem Rebuild FluentKit and regenerate or verify the canonical API reference.
set "CONFIGURATION=%~1"
if not defined CONFIGURATION set "CONFIGURATION=Release"
+set "MODE=%~2"
+if /I "%~1"=="--verify" (
+ set "CONFIGURATION=Release"
+ set "MODE=--verify"
+)
for %%I in ("%~dp0..") do set "ROOT_DIR=%%~fI"
pushd "%ROOT_DIR%"
dotnet build src\FluentKit\FluentKit.csproj -c "%CONFIGURATION%"
if errorlevel 1 goto :fail
-dotnet run --project tools\FluentKit.ApiReferenceGenerator -c "%CONFIGURATION%" -- ^
- --assembly "src\FluentKit\bin\%CONFIGURATION%\net10.0\FluentKit.dll" ^
- --xml "src\FluentKit\bin\%CONFIGURATION%\net10.0\FluentKit.xml" ^
- --manifest docs\integration\manifest.json ^
- --json docs\reference\api.json ^
- --markdown docs\reference\api.md
-if errorlevel 1 goto :fail
-
-copy /Y docs\reference\api.json fluentkit-api\references\api.json >nul
-if errorlevel 1 goto :fail
-
dotnet run --project tools\FluentKit.ApiReferenceGenerator -c "%CONFIGURATION%" -- ^
--assembly "src\FluentKit\bin\%CONFIGURATION%\net10.0\FluentKit.dll" ^
--xml "src\FluentKit\bin\%CONFIGURATION%\net10.0\FluentKit.xml" ^
@@ -28,18 +22,10 @@ dotnet run --project tools\FluentKit.ApiReferenceGenerator -c "%CONFIGURATION%"
--json docs\reference\api.json ^
--markdown docs\reference\api.md ^
--summary-baseline docs\reference\summary-baseline.json ^
- --check-summaries --verify
-if errorlevel 1 goto :fail
-
-fc /b docs\reference\api.json fluentkit-api\references\api.json >nul
+ --check-summaries %MODE%
if errorlevel 1 goto :fail
for /f "delims=" %%V in ('dotnet msbuild src\FluentKit\FluentKit.csproj -getProperty:Version -nologo') do set "PACKAGE_VERSION=%%V"
-for /f "delims=" %%V in ('powershell -NoProfile -Command "(Get-Content -Raw ''fluentkit-api/metadata.json'' | ConvertFrom-Json).fluentkitVersion"') do set "SKILL_VERSION=%%V"
-if /I not "%PACKAGE_VERSION%"=="%SKILL_VERSION%" (
- echo Version mismatch: package=%PACKAGE_VERSION% skill=%SKILL_VERSION%
- goto :fail
-)
echo API reference is current for FluentKit %PACKAGE_VERSION%.
popd
diff --git a/scripts/update-api-reference.sh b/scripts/update-api-reference.sh
index 7436ddd..b44543c 100755
--- a/scripts/update-api-reference.sh
+++ b/scripts/update-api-reference.sh
@@ -1,25 +1,19 @@
#!/usr/bin/env bash
-# Rebuild FluentKit, regenerate the API reference, copy the machine-readable reference into
-# the API skill, and run the same freshness checks used by CI.
+# Rebuild FluentKit and regenerate or verify the canonical API reference.
set -euo pipefail
CONFIGURATION="${1:-Release}"
+MODE="${2:-}"
+if [[ "$CONFIGURATION" == "--verify" ]]; then
+ CONFIGURATION="Release"
+ MODE="--verify"
+fi
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
dotnet build src/FluentKit/FluentKit.csproj -c "$CONFIGURATION"
-dotnet run --project tools/FluentKit.ApiReferenceGenerator \
- -c "$CONFIGURATION" -- \
- --assembly "src/FluentKit/bin/$CONFIGURATION/net10.0/FluentKit.dll" \
- --xml "src/FluentKit/bin/$CONFIGURATION/net10.0/FluentKit.xml" \
- --manifest docs/integration/manifest.json \
- --json docs/reference/api.json \
- --markdown docs/reference/api.md
-
-cp docs/reference/api.json fluentkit-api/references/api.json
-
dotnet run --project tools/FluentKit.ApiReferenceGenerator \
-c "$CONFIGURATION" -- \
--assembly "src/FluentKit/bin/$CONFIGURATION/net10.0/FluentKit.dll" \
@@ -28,15 +22,7 @@ dotnet run --project tools/FluentKit.ApiReferenceGenerator \
--json docs/reference/api.json \
--markdown docs/reference/api.md \
--summary-baseline docs/reference/summary-baseline.json \
- --check-summaries --verify
-
-cmp --silent docs/reference/api.json fluentkit-api/references/api.json
+ --check-summaries $MODE
PACKAGE_VERSION="$(dotnet msbuild src/FluentKit/FluentKit.csproj -getProperty:Version -nologo)"
-SKILL_VERSION="$(sed -nE 's/.*"fluentkitVersion"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' fluentkit-api/metadata.json)"
-if [[ "$PACKAGE_VERSION" != "$SKILL_VERSION" ]]; then
- printf 'Version mismatch: package=%s skill=%s\n' "$PACKAGE_VERSION" "$SKILL_VERSION" >&2
- exit 1
-fi
-
printf 'API reference is current for FluentKit %s.\n' "$PACKAGE_VERSION"
diff --git a/scripts/validate-fluentkit-api-skill.py b/scripts/validate-fluentkit-api-skill.py
new file mode 100644
index 0000000..03994d5
--- /dev/null
+++ b/scripts/validate-fluentkit-api-skill.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+"""Validate the standalone bootstrap and the package-contract source layout."""
+
+from pathlib import Path
+import re
+import sys
+
+
+def fail(message: str) -> None:
+ print(f"skill validation failed: {message}", file=sys.stderr)
+ raise SystemExit(1)
+
+
+def require_file(path: Path) -> None:
+ if not path.is_file():
+ fail(f"missing file: {path}")
+
+
+def validate_frontmatter(path: Path, expected_name: str) -> None:
+ text = path.read_text(encoding="utf-8-sig")
+ if not text.startswith("---\n") or "\n---\n" not in text[4:]:
+ fail(f"{path} has no valid YAML frontmatter")
+ frontmatter = text[4:text.index("\n---\n", 4)]
+ name = re.search(r"^name:\s*([^\s]+)\s*$", frontmatter, re.MULTILINE)
+ description = re.search(r"^description:\s*(\S.*)$", frontmatter, re.MULTILINE)
+ if not name or name.group(1) != expected_name:
+ fail(f"{path} must declare name: {expected_name}")
+ if not description or len(description.group(1)) < 20:
+ fail(f"{path} needs a useful description")
+
+
+def main() -> None:
+ root = Path(__file__).resolve().parent.parent
+ bootstrap = root / "fluentkit-api"
+ contract_skill = root / "docs" / "integration" / "agent-skill.md"
+
+ bootstrap_skill = bootstrap / "SKILL.md"
+ require_file(bootstrap_skill)
+ validate_frontmatter(bootstrap_skill, "fluentkit-api")
+ bootstrap_text = bootstrap_skill.read_text(encoding="utf-8-sig")
+ for required in (
+ "FluentKitAgentSkillPath",
+ "FluentKitAgentManifestPath",
+ "dotnet msbuild",
+ "more than one valid project",
+ "locked",
+ "predates the agent contract",
+ "Do not fall back",
+ ):
+ if required not in bootstrap_text:
+ fail(f"bootstrap does not describe {required}")
+ for script in (bootstrap / "scripts" / "resolve-fluentkit.sh", bootstrap / "scripts" / "resolve-fluentkit.ps1"):
+ require_file(script)
+ require_file(bootstrap / "agents" / "openai.yaml")
+ if (bootstrap / "metadata.json").exists() or (bootstrap / "references" / "api.json").exists():
+ fail("legacy versioned metadata/API snapshot still exists")
+
+ require_file(contract_skill)
+ validate_frontmatter(contract_skill, "fluentkit-api")
+ for reference in ("README.md", "setup.md", "component-selection.md", "theming-and-tokens.md", "overlays.md", "icons.md", "troubleshooting.md", "sample-routes.md"):
+ require_file(root / "docs" / "integration" / reference)
+
+ print("FluentKit bootstrap and contract source are valid.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/FluentKit/Agent/FluentKit.Blazor.Agent.props b/src/FluentKit/Agent/FluentKit.Blazor.Agent.props
new file mode 100644
index 0000000..8ac0108
--- /dev/null
+++ b/src/FluentKit/Agent/FluentKit.Blazor.Agent.props
@@ -0,0 +1,9 @@
+
+
+
+ $(MSBuildThisFileDirectory)../fluentkit/agent/v1
+ $(FluentKitAgentContractRoot)/manifest.json
+ $(FluentKitAgentContractRoot)/SKILL.md
+
+
diff --git a/src/FluentKit/Agent/FluentKit.Blazor.props b/src/FluentKit/Agent/FluentKit.Blazor.props
new file mode 100644
index 0000000..27dee27
--- /dev/null
+++ b/src/FluentKit/Agent/FluentKit.Blazor.props
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/FluentKit/FluentKit.csproj b/src/FluentKit/FluentKit.csproj
index 8ed9130..f983297 100644
--- a/src/FluentKit/FluentKit.csproj
+++ b/src/FluentKit/FluentKit.csproj
@@ -20,7 +20,7 @@
FluentKit.Blazor
- 0.2.5
+ 0.2.6
NoobNotFound
A token-accurate Fluent Design (WinUI 3) component library for Blazor WASM, Server, and MAUI Blazor Hybrid.
@@ -38,6 +38,21 @@
icon.png
+
+ $(MSBuildProjectDirectory)/obj/$(Configuration)/$(TargetFramework)/fluentkit-agent/v1
+ $(MSBuildProjectDirectory)/../../tools/FluentKit.ApiReferenceGenerator/FluentKit.ApiReferenceGenerator.csproj
+ $(MSBuildProjectDirectory)/../../docs/integration/manifest.json
+ $(MSBuildProjectDirectory)/../../docs/integration/agent-skill.md
+ $(MSBuildProjectDirectory)/../../docs/integration
+
+
+ true
+
+
+ $(TargetsForTfmSpecificContentInPackage);GetFluentKitAgentPackageFiles
+
true
true
@@ -70,6 +85,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ fluentkit/agent/v1/%(RecursiveDir)%(Filename)%(Extension)
+ None
+
+
+
+
diff --git a/tools/FluentKit.ApiReferenceGenerator/Program.cs b/tools/FluentKit.ApiReferenceGenerator/Program.cs
index 6966529..3735d45 100644
--- a/tools/FluentKit.ApiReferenceGenerator/Program.cs
+++ b/tools/FluentKit.ApiReferenceGenerator/Program.cs
@@ -1,4 +1,6 @@
using System.Reflection;
+using System.IO.Compression;
+using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -9,8 +11,8 @@
internal static class ApiReferenceGenerator
{
- // Keep generated JSON stable so it can be compared in CI and copied byte-for-byte into
- // the versioned API skill bundle.
+ // Keep generated JSON stable so it can be compared in CI and embedded byte-for-byte in
+ // the package-local agent contract.
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
@@ -34,6 +36,11 @@ public static async Task RunAsync(string[] args)
return SelfTest();
}
+ if (options.TryGetValue("verify-package", out var packagePath))
+ {
+ return VerifyPackage(packagePath, options);
+ }
+
if (!options.TryGetValue("assembly", out var assemblyPath) ||
!options.TryGetValue("manifest", out var manifestPath) ||
!options.TryGetValue("json", out var jsonPath) ||
@@ -79,12 +86,27 @@ public static async Task RunAsync(string[] args)
}
var json = JsonSerializer.Serialize(model, JsonOptions) + Environment.NewLine;
var markdown = RenderMarkdown(model);
+ var contractOutput = options.GetValueOrDefault("contract-output");
+ var contract = contractOutput is null
+ ? null
+ : CreateAgentContract(
+ model,
+ options.GetValueOrDefault("package-id") ?? "FluentKit.Blazor",
+ options.GetValueOrDefault("package-version") ?? throw new InvalidOperationException("--package-version is required with --contract-output."),
+ options.GetValueOrDefault("skill-source") ?? throw new InvalidOperationException("--skill-source is required with --contract-output."),
+ options.GetValueOrDefault("references-source") ?? throw new InvalidOperationException("--references-source is required with --contract-output."),
+ json,
+ markdown);
if (options.ContainsKey("verify"))
{
// Verification never writes tracked files. It is used by PR/release CI to catch
// developers who changed the assembly without regenerating the checked-in output.
var ok = CompareFile(jsonPath, json) & CompareFile(markdownPath, markdown);
+ if (contract is not null)
+ {
+ ok &= VerifyAgentContract(contract, contractOutput!);
+ }
return ok ? 0 : 1;
}
@@ -92,9 +114,329 @@ public static async Task RunAsync(string[] args)
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(markdownPath))!);
await File.WriteAllTextAsync(jsonPath, json, Encoding.UTF8);
await File.WriteAllTextAsync(markdownPath, markdown, Encoding.UTF8);
+ if (contract is not null)
+ {
+ WriteAgentContract(contract, contractOutput!);
+ }
return 0;
}
+ private static AgentContract CreateAgentContract(
+ ReferenceModel model,
+ string packageId,
+ string packageVersion,
+ string skillSource,
+ string referencesSource,
+ string json,
+ string markdown)
+ {
+ if (!File.Exists(skillSource))
+ throw new InvalidOperationException($"Agent skill source not found: {skillSource}");
+ if (!Directory.Exists(referencesSource))
+ throw new InvalidOperationException($"Agent references directory not found: {referencesSource}");
+
+ ValidatePackageVersion(model.Version, packageVersion);
+
+ var referenceNames = new[]
+ {
+ "setup",
+ "component-selection",
+ "theming-and-tokens",
+ "overlays",
+ "icons",
+ "troubleshooting"
+ };
+ var referenceSources = referenceNames.ToDictionary(
+ name => name,
+ name => Path.Combine(referencesSource, name + ".md"),
+ StringComparer.Ordinal);
+ foreach (var source in referenceSources.Values)
+ {
+ if (!File.Exists(source))
+ throw new InvalidOperationException($"Agent reference source not found: {source}");
+ }
+ var references = referenceSources.ToDictionary(
+ x => x.Key,
+ x => "references/" + Path.GetFileName(x.Value),
+ StringComparer.Ordinal);
+ var files = new[] { "SKILL.md", "api.json", "api.md" }
+ .Concat(references.Values)
+ .ToDictionary(
+ path => path,
+ path => path switch
+ {
+ "SKILL.md" => Sha256(skillSource),
+ "api.json" => Sha256(Utf8Bytes(json)),
+ "api.md" => Sha256(Utf8Bytes(markdown)),
+ _ => Sha256(referenceSources.Single(x => references[x.Key] == path).Value)
+ },
+ StringComparer.Ordinal);
+
+ return new AgentContract(
+ new AgentContractManifest(
+ 1,
+ packageId,
+ packageVersion,
+ model.Assembly,
+ "SKILL.md",
+ "api.json",
+ "api.md",
+ references,
+ "https://vibenoobnotfound.github.io/FluentKit/",
+ "https://github.com/VibeNoobNotFound/FluentKit/tree/main/docs/integration",
+ files),
+ json,
+ markdown,
+ skillSource,
+ referenceSources);
+ }
+
+ private static void WriteAgentContract(AgentContract contract, string outputDirectory)
+ {
+ Directory.CreateDirectory(outputDirectory);
+ File.WriteAllText(Path.Combine(outputDirectory, "api.json"), contract.Json, Encoding.UTF8);
+ File.WriteAllText(Path.Combine(outputDirectory, "api.md"), contract.Markdown, Encoding.UTF8);
+ File.Copy(contract.SkillSource, Path.Combine(outputDirectory, "SKILL.md"), true);
+ var referenceDirectory = Path.Combine(outputDirectory, "references");
+ Directory.CreateDirectory(referenceDirectory);
+ foreach (var source in contract.ReferenceSources.Values)
+ {
+ File.Copy(source, Path.Combine(referenceDirectory, Path.GetFileName(source)), true);
+ }
+ var manifestPath = Path.Combine(outputDirectory, "manifest.json");
+ File.WriteAllText(manifestPath, JsonSerializer.Serialize(contract.Manifest, JsonOptions) + Environment.NewLine, Encoding.UTF8);
+ }
+
+ private static bool VerifyAgentContract(AgentContract contract, string outputDirectory)
+ {
+ var ok = true;
+ ok &= CompareFile(Path.Combine(outputDirectory, "api.json"), contract.Json);
+ ok &= CompareFile(Path.Combine(outputDirectory, "api.md"), contract.Markdown);
+ ok &= CompareFile(Path.Combine(outputDirectory, "SKILL.md"), File.ReadAllText(contract.SkillSource));
+ foreach (var source in contract.ReferenceSources)
+ {
+ ok &= CompareFile(
+ Path.Combine(outputDirectory, contract.Manifest.References[source.Key].Replace('/', Path.DirectorySeparatorChar)),
+ File.ReadAllText(source.Value));
+ }
+ var manifestPath = Path.Combine(outputDirectory, "manifest.json");
+ if (!File.Exists(manifestPath))
+ {
+ Console.Error.WriteLine($"Agent contract manifest is missing: {manifestPath}");
+ return false;
+ }
+
+ var expected = JsonSerializer.Serialize(contract.Manifest, JsonOptions) + Environment.NewLine;
+ ok &= CompareFile(manifestPath, expected);
+ foreach (var path in contract.Manifest.Files.Keys)
+ {
+ var fullPath = Path.Combine(outputDirectory, path.Replace('/', Path.DirectorySeparatorChar));
+ if (!File.Exists(fullPath))
+ {
+ Console.Error.WriteLine($"Agent contract file is missing: {fullPath}");
+ ok = false;
+ continue;
+ }
+ if (!string.Equals(Sha256(fullPath), contract.Manifest.Files[path], StringComparison.OrdinalIgnoreCase))
+ {
+ Console.Error.WriteLine($"Agent contract file has the wrong hash: {fullPath}");
+ ok = false;
+ }
+ }
+ return ok;
+ }
+
+ private static int VerifyPackage(string packagePath, IReadOnlyDictionary options)
+ {
+ if (!File.Exists(packagePath))
+ {
+ Console.Error.WriteLine($"NuGet package not found: {packagePath}");
+ return 2;
+ }
+
+ try
+ {
+ using var archive = ZipFile.OpenRead(packagePath);
+ var entries = archive.Entries.ToDictionary(x => x.FullName, StringComparer.Ordinal);
+ var required = new[]
+ {
+ "fluentkit/agent/v1/manifest.json",
+ "fluentkit/agent/v1/SKILL.md",
+ "fluentkit/agent/v1/api.json",
+ "fluentkit/agent/v1/api.md",
+ "buildTransitive/FluentKit.Blazor.props",
+ "buildTransitive/FluentKit.Blazor.Agent.props"
+ };
+ var ok = true;
+ foreach (var path in required)
+ {
+ if (!entries.ContainsKey(path))
+ {
+ Console.Error.WriteLine($"NuGet package is missing: {path}");
+ ok = false;
+ }
+ }
+ if (!ok) return 1;
+
+ var manifest = ReadJson(entries["fluentkit/agent/v1/manifest.json"]);
+ if (manifest.SchemaVersion != 1 || string.IsNullOrWhiteSpace(manifest.PackageId) ||
+ string.IsNullOrWhiteSpace(manifest.PackageVersion) || string.IsNullOrWhiteSpace(manifest.AssemblyName) ||
+ !string.Equals(manifest.PackageId, "FluentKit.Blazor", StringComparison.Ordinal) ||
+ !string.Equals(manifest.Skill, "SKILL.md", StringComparison.Ordinal) ||
+ !string.Equals(manifest.ApiJson, "api.json", StringComparison.Ordinal) ||
+ !string.Equals(manifest.ApiMarkdown, "api.md", StringComparison.Ordinal) ||
+ string.IsNullOrWhiteSpace(manifest.SampleBaseUrl) ||
+ string.IsNullOrWhiteSpace(manifest.DocumentationBaseUrl) ||
+ manifest.Files is null || manifest.Files.Count == 0 || manifest.References is null)
+ {
+ Console.Error.WriteLine("Agent contract manifest is invalid.");
+ return 1;
+ }
+ if (manifest.Files.Keys.Any(path => !IsSafeRelativePath(path)) ||
+ manifest.References.Values.Any(path => !IsSafeRelativePath(path) || !path.StartsWith("references/", StringComparison.Ordinal)))
+ {
+ Console.Error.WriteLine("Agent contract manifest contains an unsafe relative path.");
+ return 1;
+ }
+
+ using var nuspecStream = entries.Values.FirstOrDefault(x => x.FullName.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase))?.Open();
+ if (nuspecStream is null)
+ {
+ Console.Error.WriteLine("NuGet package does not contain a nuspec.");
+ return 1;
+ }
+ var nuspec = XDocument.Load(nuspecStream);
+ var metadata = nuspec.Descendants().FirstOrDefault(x => x.Name.LocalName == "metadata");
+ var packageId = metadata?.Elements().FirstOrDefault(x => x.Name.LocalName == "id")?.Value;
+ var packageVersion = metadata?.Elements().FirstOrDefault(x => x.Name.LocalName == "version")?.Value;
+ if (!string.Equals(packageId, manifest.PackageId, StringComparison.Ordinal) ||
+ !string.Equals(packageVersion, manifest.PackageVersion, StringComparison.Ordinal))
+ {
+ Console.Error.WriteLine("Agent contract package identity does not match the nuspec.");
+ ok = false;
+ }
+
+ var apiJsonPath = "fluentkit/agent/v1/" + manifest.ApiJson;
+ var apiMarkdownPath = "fluentkit/agent/v1/" + manifest.ApiMarkdown;
+ if (!entries.ContainsKey(apiJsonPath) || !entries.ContainsKey(apiMarkdownPath))
+ {
+ Console.Error.WriteLine("Agent contract API paths are missing from the package.");
+ return 1;
+ }
+ var model = ReadJson(entries[apiJsonPath]);
+ if (!string.Equals(model.Assembly, manifest.AssemblyName, StringComparison.Ordinal) ||
+ !string.Equals(model.Version, StripPrerelease(manifest.PackageVersion), StringComparison.Ordinal))
+ {
+ Console.Error.WriteLine("Agent contract API identity does not match the manifest.");
+ ok = false;
+ }
+ var markdown = ReadEntry(entries[apiMarkdownPath]);
+ if (!string.Equals(markdown, RenderMarkdown(model), StringComparison.Ordinal))
+ {
+ Console.Error.WriteLine("Packaged api.md is not generated from packaged api.json.");
+ ok = false;
+ }
+
+ if (options.TryGetValue("assembly", out var builtAssemblyPath))
+ {
+ if (!options.TryGetValue("manifest", out var builtManifestPath))
+ throw new InvalidOperationException("--manifest is required when --assembly is supplied with --verify-package.");
+ var builtXmlPath = options.GetValueOrDefault("xml") ?? Path.ChangeExtension(builtAssemblyPath, ".xml");
+ var builtModel = BuildModel(builtAssemblyPath, builtXmlPath, builtManifestPath);
+ var builtJson = JsonSerializer.Serialize(builtModel, JsonOptions) + Environment.NewLine;
+ var builtMarkdown = RenderMarkdown(builtModel);
+ if (!string.Equals(ReadEntry(entries[apiJsonPath]), builtJson, StringComparison.Ordinal))
+ {
+ Console.Error.WriteLine("Packaged api.json differs from the freshly generated built-assembly API.");
+ ok = false;
+ }
+ if (!string.Equals(markdown, builtMarkdown, StringComparison.Ordinal))
+ {
+ Console.Error.WriteLine("Packaged api.md differs from the freshly generated built-assembly API.");
+ ok = false;
+ }
+ }
+
+ foreach (var file in manifest.Files)
+ {
+ var entryPath = "fluentkit/agent/v1/" + file.Key;
+ if (!entries.TryGetValue(entryPath, out var entry))
+ {
+ Console.Error.WriteLine($"Manifest file is missing from package: {file.Key}");
+ ok = false;
+ continue;
+ }
+ using var stream = entry.Open();
+ if (!string.Equals(Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant(), file.Value, StringComparison.OrdinalIgnoreCase))
+ {
+ Console.Error.WriteLine($"Manifest hash mismatch: {file.Key}");
+ ok = false;
+ }
+ }
+
+ var props = ReadEntry(entries["buildTransitive/FluentKit.Blazor.Agent.props"]);
+ var propsDocument = XDocument.Parse(props);
+ foreach (var property in new[] { "FluentKitAgentContractRoot", "FluentKitAgentManifestPath", "FluentKitAgentSkillPath" })
+ {
+ if (!propsDocument.Descendants().Any(x => x.Name.LocalName == property))
+ {
+ Console.Error.WriteLine($"Agent props does not expose {property}.");
+ ok = false;
+ }
+ }
+ var wrapperProps = ReadEntry(entries["buildTransitive/FluentKit.Blazor.props"]);
+ _ = XDocument.Parse(wrapperProps);
+ if (!wrapperProps.Contains("FluentKit.Blazor.Agent.props", StringComparison.Ordinal) ||
+ !wrapperProps.Contains("../buildMultiTargeting/FluentKit.Blazor.props", StringComparison.Ordinal))
+ {
+ Console.Error.WriteLine("Conventional buildTransitive props do not import the static-assets and agent props.");
+ ok = false;
+ }
+ return ok ? 0 : 1;
+ }
+ catch (Exception ex) when (ex is InvalidDataException or JsonException or InvalidOperationException or System.Xml.XmlException)
+ {
+ Console.Error.WriteLine($"NuGet package contract verification failed: {ex.Message}");
+ return 1;
+ }
+ }
+
+ private static T ReadJson(ZipArchiveEntry entry)
+ {
+ using var stream = entry.Open();
+ return JsonSerializer.Deserialize(stream, JsonOptions) ?? throw new InvalidDataException($"Empty JSON entry: {entry.FullName}");
+ }
+
+ private static string ReadEntry(ZipArchiveEntry entry)
+ {
+ using var reader = new StreamReader(entry.Open(), Encoding.UTF8);
+ return reader.ReadToEnd();
+ }
+
+ private static string Sha256(string path) => Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(path))).ToLowerInvariant();
+
+ private static string Sha256(byte[] bytes) => Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
+
+ private static byte[] Utf8Bytes(string text) => Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(text)).ToArray();
+
+ private static void ValidatePackageVersion(string assemblyVersion, string packageVersion)
+ {
+ var assemblyCore = StripPrerelease(assemblyVersion);
+ var packageCore = StripPrerelease(packageVersion);
+ if (!string.Equals(assemblyCore, packageCore, StringComparison.Ordinal))
+ {
+ throw new InvalidOperationException($"Package version {packageVersion} does not match assembly version {assemblyVersion}.");
+ }
+ }
+
+ private static string StripPrerelease(string version) => version.Split('-', '+')[0];
+
+ private static bool IsSafeRelativePath(string path) =>
+ !string.IsNullOrWhiteSpace(path) &&
+ !path.StartsWith("/", StringComparison.Ordinal) &&
+ !path.Contains("\\", StringComparison.Ordinal) &&
+ !path.Split('/').Any(segment => segment is "" or "." or "..");
+
private static ReferenceModel BuildModel(string assemblyPath, string xmlPath, string manifestPath)
{
// Load the built assembly instead of source files: Razor-generated component classes,
@@ -367,7 +709,7 @@ private static int SelfTest()
return 0;
}
- private static void PrintUsage() => Console.WriteLine("Usage: --assembly PATH --manifest PATH --json PATH --markdown PATH [--xml PATH] [--verify] | --self-test");
+ private static void PrintUsage() => Console.WriteLine("Usage: --assembly PATH --manifest PATH --json PATH --markdown PATH [--xml PATH] [--verify] [--contract-output PATH --package-id ID --package-version VERSION --skill-source PATH --references-source PATH] | --verify-package PATH [--assembly PATH --xml PATH --manifest PATH] | --self-test");
private sealed class XmlDocs(IReadOnlyDictionary members)
{
@@ -386,6 +728,24 @@ private sealed class ManifestEntry
}
private sealed record ReferenceModel(string Assembly, string Version, ComponentReference[] Components, TypeReference[] Types, string[] MissingSummaries);
+ private sealed record AgentContract(
+ AgentContractManifest Manifest,
+ string Json,
+ string Markdown,
+ string SkillSource,
+ Dictionary ReferenceSources);
+ private sealed record AgentContractManifest(
+ int SchemaVersion,
+ string PackageId,
+ string PackageVersion,
+ string AssemblyName,
+ string Skill,
+ string ApiJson,
+ string ApiMarkdown,
+ Dictionary References,
+ string SampleBaseUrl,
+ string DocumentationBaseUrl,
+ Dictionary Files);
private sealed record ComponentReference(string Name, string FullName, string Category, string Sample, string[] GenericParameters, string? Summary, ParameterReference[] Parameters);
private sealed record ParameterReference(string Name, string Type, bool Cascading, bool RenderFragment, bool Bindable, string? Summary, bool Settable);
private sealed record TypeReference(string Name, string FullName, string Kind, string[] GenericParameters, string[] Values, string? Summary, MemberReference[] Members);
diff --git a/tools/README.md b/tools/README.md
index 582f92f..b5fb3fd 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -11,15 +11,19 @@ dotnet run --project tools/FluentKit.ApiReferenceGenerator -c Release -- \
--manifest docs/integration/manifest.json \
--json docs/reference/api.json \
--markdown docs/reference/api.md \
- --summary-baseline docs/reference/summary-baseline.json
+ --summary-baseline docs/reference/summary-baseline.json \
+ --check-summaries
```
Use `--verify --check-summaries` in CI. `--self-test` exercises generic parameters,
bindable callbacks, child content, cascading parameters, and fixture reflection without
-modifying the generated files.
+modifying the generated files. `scripts/test-fluentkit-contract.py` exercises resolver and
+package-verifier failure scenarios against a freshly packed local package.
-The generated JSON must be copied to
-`fluentkit-api/references/api.json`; CI compares the two copies byte-for-byte.
+Use `--contract-output` with `--package-id`, `--package-version`, `--skill-source`, and
+`--references-source` to stage the package-local agent contract. Use `--verify-package PATH`
+to inspect a completed `.nupkg`, including its manifest, generated API, hashes, and
+`buildTransitive` discovery props. `dotnet pack` invokes this staging flow automatically.
When an API change is intentional, update `PublicAPI.Unshipped.txt` during development,
review the compatibility impact, and promote the reviewed entries to
From f887095a090db190055cc39c9a850c4a99f129bd Mon Sep 17 00:00:00 2001
From: NoobNotFound <82730163+NoobNotFound@users.noreply.github.com>
Date: Sun, 30 Aug 2026 02:38:46 +0530
Subject: [PATCH 2/4] fix(release): mark prerelease tags
Tags with SemVer prerelease suffixes must not be published as stable GitHub releases.
---
.github/workflows/release.yml | 14 +++++++++++++-
README.md | 4 +++-
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9068efd..1736625 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -85,6 +85,11 @@ jobs:
{
echo "RELEASE_TAG=$release_tag"
echo "CREATE_TAG=$create_tag"
+ if [[ "$release_tag" == *-* ]]; then
+ echo "RELEASE_IS_PRERELEASE=true"
+ else
+ echo "RELEASE_IS_PRERELEASE=false"
+ fi
} >> "$GITHUB_ENV"
- name: Verify release identity
@@ -223,9 +228,16 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
+ release_args=(--title "FluentKit ${RELEASE_TAG}" --generate-notes)
+ if [[ "$RELEASE_IS_PRERELEASE" == "true" ]]; then
+ release_args+=(--prerelease)
+ fi
if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
gh release upload "$RELEASE_TAG" fluentkit-api.zip fluentkit-api.zip.sha256 --clobber
+ if [[ "$RELEASE_IS_PRERELEASE" == "true" ]]; then
+ gh release edit "$RELEASE_TAG" --prerelease
+ fi
else
gh release create "$RELEASE_TAG" fluentkit-api.zip fluentkit-api.zip.sha256 \
- --title "FluentKit ${RELEASE_TAG}" --generate-notes
+ "${release_args[@]}"
fi
diff --git a/README.md b/README.md
index a4f4a78..5c540bc 100644
--- a/README.md
+++ b/README.md
@@ -368,7 +368,9 @@ from the selected branch. The project version must already match that version. E
publishes NuGet and a standalone
[`fluentkit-api.zip`](https://github.com/VibeNoobNotFound/FluentKit/releases/latest/download/fluentkit-api.zip)
containing the permanent bootstrap skill. The package itself contains the version-specific
-agent contract, and the release also publishes a checksum for the ZIP.
+agent contract, and the release also publishes a checksum for the ZIP. Versions/tags with a
+SemVer prerelease suffix, such as `0.2.6-alpha` / `v0.2.6-alpha`, are published as prereleases
+on NuGet and GitHub Releases.
## Contributing
From 4ad13264d00ef6ac0c73e36067e3a2316bd21a6f Mon Sep 17 00:00:00 2001
From: NoobNotFound <82730163+NoobNotFound@users.noreply.github.com>
Date: Sun, 30 Aug 2026 02:47:38 +0530
Subject: [PATCH 3/4] fix(ci): normalize Windows NuGet feeds
Pass local and remote restore sources through RestoreSources to avoid Windows NuGet treating the URL as a relative path.
---
.github/workflows/ci.yml | 2 +-
fluentkit-api/scripts/resolve-fluentkit.ps1 | 12 ++++++++----
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e0e9050..ac9ee53 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -144,7 +144,7 @@ jobs:
$env:NUGET_PACKAGES = $packages
"NUGET_PACKAGES=$packages" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"LOCAL_NUGET_FEED=$feed" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- dotnet restore $env:SMOKE_PROJECT -p:FluentKitSmokeVersion=$env:PACKAGE_VERSION --source $feed --source https://api.nuget.org/v3/index.json
+ dotnet restore $env:SMOKE_PROJECT -p:FluentKitSmokeVersion=$env:PACKAGE_VERSION -p:RestoreSources="$feed;https://api.nuget.org/v3/index.json"
dotnet build $env:SMOKE_PROJECT -c Release --no-restore
- name: Resolve package contract (Windows)
diff --git a/fluentkit-api/scripts/resolve-fluentkit.ps1 b/fluentkit-api/scripts/resolve-fluentkit.ps1
index 6d0007e..a5e9527 100644
--- a/fluentkit-api/scripts/resolve-fluentkit.ps1
+++ b/fluentkit-api/scripts/resolve-fluentkit.ps1
@@ -18,12 +18,16 @@ if (-not (Test-Path -LiteralPath $projectPath -PathType Leaf)) {
$projectDirectory = Split-Path -Parent $projectPath
$msbuildProperties = @($Property | ForEach-Object { "-p:$_" })
-$restoreSources = @()
-foreach ($sourcePath in $Source) { $restoreSources += @('--source', $sourcePath) }
+$restoreProperties = @()
+if ($Source.Count -gt 0) {
+ # RestoreSources is a semicolon-delimited MSBuild property. It avoids the Windows NuGet
+ # command-line edge case where a repeated --source URL can be normalized as a relative path.
+ $restoreProperties += "-p:RestoreSources=$($Source -join ';')"
+}
if (Test-Path -LiteralPath (Join-Path $projectDirectory 'packages.lock.json') -PathType Leaf) {
- & dotnet restore $projectPath --locked-mode @restoreSources @msbuildProperties
+ & dotnet restore $projectPath --locked-mode @restoreProperties @msbuildProperties
} else {
- & dotnet restore $projectPath @restoreSources @msbuildProperties
+ & dotnet restore $projectPath @restoreProperties @msbuildProperties
}
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
From 1ab0ed621b202ce01e42e1a23cd45c0cd9919d84 Mon Sep 17 00:00:00 2001
From: NoobNotFound <82730163+NoobNotFound@users.noreply.github.com>
Date: Sun, 30 Aug 2026 02:54:02 +0530
Subject: [PATCH 4/4] fix(ci): isolate Windows NuGet sources
Use an explicit temporary NuGet.Config so Windows preserves remote URLs alongside the local package feed.
---
.github/workflows/ci.yml | 13 +++++++-
fluentkit-api/SKILL.md | 2 +-
fluentkit-api/scripts/resolve-fluentkit.ps1 | 35 ++++++++++++++++-----
3 files changed, 40 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ac9ee53..19328dd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -144,7 +144,18 @@ jobs:
$env:NUGET_PACKAGES = $packages
"NUGET_PACKAGES=$packages" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"LOCAL_NUGET_FEED=$feed" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- dotnet restore $env:SMOKE_PROJECT -p:FluentKitSmokeVersion=$env:PACKAGE_VERSION -p:RestoreSources="$feed;https://api.nuget.org/v3/index.json"
+ $nugetConfig = Join-Path $env:RUNNER_TEMP 'fluentkit-smoke.NuGet.Config'
+ @"
+
+
+
+
+
+
+
+
+ "@ | Set-Content -LiteralPath $nugetConfig -Encoding utf8
+ dotnet restore $env:SMOKE_PROJECT -p:FluentKitSmokeVersion=$env:PACKAGE_VERSION --configfile $nugetConfig
dotnet build $env:SMOKE_PROJECT -c Release --no-restore
- name: Resolve package contract (Windows)
diff --git a/fluentkit-api/SKILL.md b/fluentkit-api/SKILL.md
index 4beeb6a..70fbd64 100644
--- a/fluentkit-api/SKILL.md
+++ b/fluentkit-api/SKILL.md
@@ -27,7 +27,7 @@ never use a release ZIP, online "latest" API snapshot, or a different package ve
4. If the project requires MSBuild properties or a private/local NuGet source to evaluate its
package references, forward them with `--property Name=Value` / `-Property Name=Value` or
- `--source PATH` / `-Source PATH`.
+ `--source PATH` / `-Source PATH[,PATH]`.
5. The resolver queries `FluentKitAgentManifestPath` and `FluentKitAgentSkillPath` through
`dotnet msbuild`. Read the returned package-local `SKILL.md` completely before acting.
diff --git a/fluentkit-api/scripts/resolve-fluentkit.ps1 b/fluentkit-api/scripts/resolve-fluentkit.ps1
index a5e9527..66860de 100644
--- a/fluentkit-api/scripts/resolve-fluentkit.ps1
+++ b/fluentkit-api/scripts/resolve-fluentkit.ps1
@@ -18,18 +18,37 @@ if (-not (Test-Path -LiteralPath $projectPath -PathType Leaf)) {
$projectDirectory = Split-Path -Parent $projectPath
$msbuildProperties = @($Property | ForEach-Object { "-p:$_" })
+$temporaryConfig = $null
$restoreProperties = @()
if ($Source.Count -gt 0) {
- # RestoreSources is a semicolon-delimited MSBuild property. It avoids the Windows NuGet
- # command-line edge case where a repeated --source URL can be normalized as a relative path.
- $restoreProperties += "-p:RestoreSources=$($Source -join ';')"
+ # A temporary NuGet.Config avoids a Windows NuGet command-line edge case where a repeated
+ # --source URL can be normalized as a relative path. It also preserves paths containing spaces.
+ $temporaryConfig = Join-Path ([IO.Path]::GetTempPath()) ('fluentkit-resolver-' + [Guid]::NewGuid().ToString('N') + '.NuGet.Config')
+ $configuration = New-Object System.Xml.XmlDocument
+ $configuration.LoadXml('')
+ $packageSources = $configuration.SelectSingleNode('/configuration/packageSources')
+ for ($index = 0; $index -lt $Source.Count; $index++) {
+ $sourceElement = $configuration.CreateElement('add')
+ $sourceElement.SetAttribute('key', "fluentkit-source-$index")
+ $sourceElement.SetAttribute('value', $Source[$index])
+ [void]$packageSources.AppendChild($sourceElement)
+ }
+ $configuration.Save($temporaryConfig)
+ $restoreProperties += @('--configfile', $temporaryConfig)
}
-if (Test-Path -LiteralPath (Join-Path $projectDirectory 'packages.lock.json') -PathType Leaf) {
- & dotnet restore $projectPath --locked-mode @restoreProperties @msbuildProperties
-} else {
- & dotnet restore $projectPath @restoreProperties @msbuildProperties
+try {
+ if (Test-Path -LiteralPath (Join-Path $projectDirectory 'packages.lock.json') -PathType Leaf) {
+ & dotnet restore $projectPath --locked-mode @restoreProperties @msbuildProperties
+ } else {
+ & dotnet restore $projectPath @restoreProperties @msbuildProperties
+ }
+ $restoreExitCode = $LASTEXITCODE
+} finally {
+ if ($temporaryConfig -and (Test-Path -LiteralPath $temporaryConfig)) {
+ Remove-Item -LiteralPath $temporaryConfig -Force
+ }
}
-if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+if ($restoreExitCode -ne 0) { exit $restoreExitCode }
$skillPath = (& dotnet msbuild $projectPath -nologo @msbuildProperties -getProperty:FluentKitAgentSkillPath | Select-Object -Last 1).Trim()
$manifestPath = (& dotnet msbuild $projectPath -nologo @msbuildProperties -getProperty:FluentKitAgentManifestPath | Select-Object -Last 1).Trim()