diff --git a/packages/breadcrumb-trail/spec/flow-api.md b/packages/breadcrumb-trail/spec/flow-api.md new file mode 100644 index 00000000000..3ec44a91b07 --- /dev/null +++ b/packages/breadcrumb-trail/spec/flow-api.md @@ -0,0 +1,330 @@ +# BreadcrumbTrail Flow Developer API + + + +## 1. Displaying the ancestor trail as links + +Covers requirement(s): 1, 2 + +```java +BreadcrumbTrail breadcrumbTrail = new BreadcrumbTrail(Mode.MANUAL); +breadcrumbTrail.add( + new BreadcrumbItem("Home", HomeView.class), + new BreadcrumbItem("Developer Guide", DeveloperGuideView.class), + new BreadcrumbItem("API Reference", ApiReferenceView.class), + new BreadcrumbItem("Authentication", AuthenticationView.class), + new BreadcrumbItem("OAuth2")); // last item without a path is the current page +add(breadcrumbTrail); +``` + +```java +// String-path overload for cases with no Flow route class +breadcrumbTrail.add( + new BreadcrumbItem("Home", "/"), + new BreadcrumbItem("External", "https://example.com/docs")); +``` + +**Why this shape:** `BreadcrumbTrail` has an explicit `Mode` that determines who owns the trail. The nested enum `BreadcrumbTrail.Mode` has two values: `ROUTER` (default — auto-populated from Flow routing metadata, see section 8) and `MANUAL` (the application manages the items). The mode is chosen at construction — `new BreadcrumbTrail()` defaults to `ROUTER`, `new BreadcrumbTrail(Mode.MANUAL)` is explicit. Adding or removing children while in `ROUTER` mode throws `IllegalStateException`, so the two models never silently mix. In `MANUAL` mode, `BreadcrumbTrail` is a standard Flow container — it implements `HasComponentsOfType` and accepts items through the inherited `add(BreadcrumbItem...)` / `addComponentAsFirst(BreadcrumbItem)` / `addComponentAtIndex(int, BreadcrumbItem)` / `remove(BreadcrumbItem...)` / `removeAll()` methods, with compile-time enforcement that only `BreadcrumbItem` instances can be added. On the web-component side the `items` JS property takes data objects, but that is a client-side concern; in Flow, items are `BreadcrumbItem` components. `BreadcrumbItem` offers the same constructor overloads as `SideNavItem`: `(label)` for the current page (no path), `(label, String path)` for hand-managed paths, `(label, Class view)` as the type-safe primary form required by `DESIGN_GUIDELINES.md` "Integrate with Flow Router", and `(label, Class view, RouteParameters routeParameters)` for parameterised routes. Each path-taking overload also has a prefix-component variant ending in `Component prefixComponent` (see section 4). The "current" distinction needs no extra API — an item without a path is the current item, matching the web component's declarative convention. + +--- + +## 2. Optionally omitting the current page + +Covers requirement(s): 3 + +```java +// All items linkable — no current-page item included in the trail +BreadcrumbTrail breadcrumbTrail = new BreadcrumbTrail(Mode.MANUAL); +breadcrumbTrail.add( + new BreadcrumbItem("Home", HomeView.class), + new BreadcrumbItem("Developer Guide", DeveloperGuideView.class), + new BreadcrumbItem("API Reference", ApiReferenceView.class)); +``` + +**Why this shape:** No dedicated API. When every added item has a path, no current-page indicator appears — the application simply decides whether to include a final no-path item. The Flow API inherits this from the web component's declarative convention, keeping the wrapper thin. + +--- + +## 3. Overflow collapse and expansion + +Covers requirement(s): 6, 7 + +```java +BreadcrumbTrail breadcrumbTrail = new BreadcrumbTrail(Mode.MANUAL); +breadcrumbTrail.add( + new BreadcrumbItem("Home", HomeView.class), + new BreadcrumbItem("Documents", DocumentsView.class), + new BreadcrumbItem("Projects", ProjectsView.class), + new BreadcrumbItem("2026", YearView.class, new RouteParameters("year", "2026")), + new BreadcrumbItem("Q1", QuarterView.class, new RouteParameters("quarter", "q1")), + new BreadcrumbItem("Reports", ReportsView.class), + new BreadcrumbItem("Summary")); + +// Localise the overflow button's accessible label +breadcrumbTrail.setI18n(new BreadcrumbTrailI18n().setMoreItems("Show hidden items")); +``` + +**Why this shape:** Overflow collapse and the expansion menu opened by the overflow indicator (req 7) are handled entirely inside the web component — the collapsed items reappear as menu rows sourced from the same `BreadcrumbItem` components the container already holds, so no Flow-side surface is needed to wire them up. The only Flow-visible concern is the overflow indicator's accessible label, localised via a nested `BreadcrumbTrailI18n` class following the `SideNavI18n` / `MenuBarI18n` convention: `Serializable`, `@JsonInclude(JsonInclude.Include.NON_NULL)`, fluent setters. Exposed via `setI18n(BreadcrumbTrailI18n)` / `getI18n()` on `BreadcrumbTrail`. + +--- + +## 4. Icons alongside item text + +Covers requirement(s): 8 + +```java +// Inline with construction — one line per item +BreadcrumbTrail breadcrumbTrail = new BreadcrumbTrail(Mode.MANUAL); +breadcrumbTrail.add( + new BreadcrumbItem("Home", HomeView.class, new Icon(VaadinIcon.HOME)), + new BreadcrumbItem("Documents", DocumentsView.class, new Icon(VaadinIcon.FOLDER)), + new BreadcrumbItem("Report.pdf")); +``` + +```java +// Or set the prefix after construction +BreadcrumbItem home = new BreadcrumbItem("Home", HomeView.class); +home.setPrefixComponent(new Icon(VaadinIcon.HOME)); +``` + +**Why this shape:** `BreadcrumbItem` implements `HasPrefix` from `vaadin-flow-components-base`, giving every item `setPrefixComponent` / `getPrefixComponent`. In addition, each path-taking constructor has a prefix-component variant — `(label, String path, Component prefix)`, `(label, Class view, Component prefix)`, `(label, Class view, RouteParameters params, Component prefix)` — matching `SideNavItem` exactly so the icon-with-path case (by far the common one) reads as a single line. The web component uses `slot="prefix"` on `` and mirroring that through `HasPrefix` reuses the surface Flow developers already know. web-component-api.md explicitly does not support icons via the programmatic `items` property; Flow sidesteps that limitation because its programmatic API is component-based (`add(BreadcrumbItem...)`), not a data-object array, so icons work the same way whether the prefix is passed through a constructor or assigned via `setPrefixComponent` after construction. + +--- + +## 5. Dynamic trail updates + +Covers requirement(s): 9 + +```java +// Imperative form — rebuild the trail when the browsed category changes +BreadcrumbTrail breadcrumbTrail = new BreadcrumbTrail(Mode.MANUAL); +breadcrumbTrail.add( + new BreadcrumbItem("Home", HomeView.class), + new BreadcrumbItem("Electronics", CategoryView.class, new RouteParameters("slug", "electronics")), + new BreadcrumbItem("Laptops", CategoryView.class, new RouteParameters("slug", "laptops")), + new BreadcrumbItem("Gaming")); + +categorySelector.addValueChangeListener(event -> { + Category category = event.getValue(); + breadcrumbTrail.removeAll(); + breadcrumbTrail.add( + new BreadcrumbItem("Home", HomeView.class), + new BreadcrumbItem(category.getName(), CategoryView.class, + new RouteParameters("slug", category.getSlug())), + new BreadcrumbItem(category.getChild().getName())); +}); +``` + +```java +// Reactive form — run an effect that rebuilds the children when a signal changes +ValueSignal current = new ValueSignal<>(initialCategory); + +BreadcrumbTrail breadcrumbTrail = new BreadcrumbTrail(Mode.MANUAL); +Signal.effect(breadcrumbTrail, () -> { + Category category = current.get(); + breadcrumbTrail.removeAll(); + breadcrumbTrail.add( + new BreadcrumbItem("Home", HomeView.class), + new BreadcrumbItem(category.getName(), CategoryView.class, + new RouteParameters("slug", category.getSlug())), + new BreadcrumbItem(category.getChild().getName())); +}); + +// Any code that updates the signal flows through to the breadcrumb +current.set(nextCategory); +``` + +**Why this shape:** In `Mode.MANUAL`, imperative updates use the standard component-tree primitives `add(...)`, `remove(...)`, `removeAll()` inherited from `HasComponentsOfType` — the same API a Flow developer uses for any container, with the generic parameter ensuring only `BreadcrumbItem` instances can be passed. Reactive updates use `Signal.effect(component, Runnable)`, Flow core's primitive for running a callback whenever the observed signals change; the effect is the right granularity here because the trail is rebuilt as a tree operation (`removeAll` + `add`), not a single property set. This avoids inventing a component-specific `bindItems` surface when the generic effect primitive already covers the case. The web component itself accepts only `` light-DOM children (no parallel `items` data-array property — see web-component-api.md §6), so the Flow wrapper has nothing else to map. + +--- + +## 6. Navigation landmark + +Covers requirement(s): 10 + +```java +BreadcrumbTrail breadcrumbTrail = new BreadcrumbTrail(Mode.MANUAL); +breadcrumbTrail.setAriaLabel("Product navigation"); +breadcrumbTrail.add( + new BreadcrumbItem("Home", HomeView.class), + new BreadcrumbItem("Products", ProductsView.class), + new BreadcrumbItem("Laptops")); +``` + +**Why this shape:** `BreadcrumbTrail` implements `HasAriaLabel` from Flow core — the standard Vaadin way to expose an accessible name. The web component already renders itself as a `