import { ComponentDescriptor, LimeWebComponent } from '../core';
/**
* Service for registering and routing to web components
*
* The {@link RouteRegistry} enables URL-based routing to web components within
* the Lime platform.
*
* Register URL patterns mapped to component tag names, then navigate to those
* URLs using {@link Navigator}. The routing system will instantiate the
* component and pass URL parameters as props.
*
* A parameter such as `/users/:userId` is extracted and passed as a prop. A
* value that parses as a number arrives as a `number`, anything else stays a
* `string`.
*
* A `limetype` parameter builds a {@link LimeWebComponentContext} for the
* component. An `id` in the same path joins that context, and both are then
* removed from the props. An `id` without a `limetype` creates no context and
* stays an ordinary prop.
*
* ## Reserved Property Names
*
* The following property names are reserved by {@link RouteComponent} and
* cannot be used as path parameters:
* - `platform` - The platform instance
* - `context` - The component context
* - `query` - URL query parameters
* - `hash` - URL hash fragment
* - `state` - Navigation state
* - `onTitleChanged` - Handler bound to the {@link RouteComponent.titleChanged} event
*
* @example Registering a route and navigating to it
* ```ts
* import { PlatformServiceName } from '@limetech/lime-web-components';
*
* const routeRegistry = platform.get(PlatformServiceName.RouteRegistry);
* const navigator = platform.get(PlatformServiceName.Navigator);
*
* routeRegistry.registerRoute('/users/:userId', 'user-profile');
*
* // Renders
* navigator.navigate('/users/123');
* ```
*
* @public
* @group Registering routes
*/
export interface RouteRegistry {
/**
* Register a component for a URL pattern
*
* Maps a URL path pattern to a web component tag name. When navigation
* occurs to a matching path, the component will be instantiated and
* rendered with URL parameters passed as props.
*
* The path pattern uses the URLPattern API syntax. Common patterns:
* - `/static` - Exact match
* - `/users/:id` - Named parameter
* - `/files/:path*` - Wildcard parameter
* - `/admin/:section(users|roles)` - Enum parameter
*
* @param pathPattern - URL path pattern using URLPattern syntax.
* See [MDN URLPattern docs](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API)
* for pattern syntax details.
* @param component - Tag name of the web component to render for this route
*
* @example Registering each kind of pattern
* ```ts
* import { PlatformServiceName } from '@limetech/lime-web-components';
*
* const routeRegistry = platform.get(PlatformServiceName.RouteRegistry);
*
* // Exact match
* routeRegistry.registerRoute('/dashboard', 'app-dashboard');
*
* // Named parameters, passed to the component as props
* routeRegistry.registerRoute('/users/:userId', 'user-profile');
* routeRegistry.registerRoute(
* '/posts/:postId/comments/:commentId',
* 'comment-view'
* );
*
* // Wildcard parameter
* routeRegistry.registerRoute('/files/:path*', 'file-browser');
*
* // Enum parameter
* routeRegistry.registerRoute('/admin/:section(users|roles)', 'admin-panel');
*
* // `limetype` and `id` together give the component a context
* routeRegistry.registerRoute('/object/:limetype/:id', 'object-viewer');
* ```
*/
registerRoute(pathPattern: string, component: string): void;
/**
* Find the component registered for a given path
*
* Matches the path against all registered route patterns and returns the
* component name and extracted parameters if a match is found.
*
* This is typically used internally by the platform routing system, but
* can be useful for testing or custom routing logic.
*
* @param path - The URL path to match (e.g., '/users/123')
* @returns Matched component name and parameters, or `undefined` if no route matches
*
* @example Matching a path
* ```ts
* import { PlatformServiceName } from '@limetech/lime-web-components';
*
* const routeRegistry = platform.get(PlatformServiceName.RouteRegistry);
* routeRegistry.registerRoute('/users/:userId', 'user-profile');
*
* const match = routeRegistry.findComponent('/users/123');
*
* // `undefined` when no registered pattern matches the path
* const componentName = match?.name;
*
* // 123, not '123'
* const userId = match?.props['userId'];
* ```
*/
findComponent(path: string): MatchedComponent | undefined;
}
/**
* Type of a single path parameter after a route has matched
*
* @see {@link RouteRegistry} for how a path is turned into parameters
*
* @public
* @group Registering routes
*/
export type RouteParam = string | number | undefined;
/**
* Result from matching a path to a registered route
*
* Contains the component name and extracted path parameters from the URL.
* Returned by {@link RouteRegistry.findComponent}. The fields are described
* on {@link ComponentDescriptor}, which this narrows so that a match always
* carries `props`, even when the path had no parameters.
*
* Only `props` is narrowed, on purpose. Widening this to every field of
* {@link ComponentDescriptor} would make each field added there required
* here too, which is a contract the router never agreed to.
*
* @example A matched route
* ```ts
* import type { MatchedComponent } from '@limetech/lime-web-components';
*
* // Registered as '/users/:userId/posts/:postId' and matched against
* // '/users/123/posts/456'
* const match: MatchedComponent = {
* name: 'post-viewer',
* props: { userId: 123, postId: 456 },
* };
* ```
*
* @public
* @group Registering routes
*/
export type MatchedComponent = ComponentDescriptor & Required, 'props'>>;
/**
* Interface for components that can be routed to
*
* Components registered with {@link RouteRegistry} should implement this
* interface to receive routing information such as query parameters, hash
* fragments, and navigation state.
*
* This extends {@link LimeWebComponent}, so all routed components also
* receive the standard `platform` and `context` props.
*
* @example A routed component
* ```ts
* import { Event, Prop, type EventEmitter } from '@stencil/core';
* import type {
* LimeWebComponentContext,
* LimeWebComponentPlatform,
* RouteComponent,
* } from '@limetech/lime-web-components';
*
* type SearchOrigin = {
* fromSearch: boolean;
* searchTerm: string;
* };
*
* // Registered for '/products/:productId' and visited as
* // /products/123?category=electronics&inStock=true#reviews
* class ProductDetails implements RouteComponent {
* @Prop()
* public platform: LimeWebComponentPlatform;
*
* @Prop()
* public context: LimeWebComponentContext;
*
* @Prop()
* public query: Record;
*
* @Prop()
* public hash: string;
*
* @Prop()
* public state: unknown;
*
* @Event()
* public titleChanged: EventEmitter;
*
* public componentDidLoad() {
* // 'electronics'
* const category = this.query['category'];
*
* // true, not 'true'
* const inStock = this.query['inStock'];
*
* // '#reviews' -> 'reviews'
* const section = this.hash.slice(1);
* document.querySelector(`#${section}`)?.scrollIntoView();
*
* // Whatever the caller passed to Navigator.navigate
* const origin = this.state as SearchOrigin | undefined;
*
* if (origin?.fromSearch) {
* const term = origin.searchTerm;
* }
*
* this.titleChanged.emit('Product Details');
* }
* }
* ```
*
* @public
* @group Registering routes
*/
export interface RouteComponent extends LimeWebComponent {
/**
* URL query parameters from the current location
*
* Parsed from the URL query string. Values are automatically parsed
* using `JSON.parse()` when possible.
*/
query?: Record;
/**
* URL fragment identifier from the current location
*
* The hash portion of the URL, including the leading `#` character.
*/
hash?: string;
/**
* Navigation state from the current location
*
* Custom state data passed during navigation via {@link Navigator.navigate}.
* This state is stored in the browser's history entry.
*/
state?: unknown;
/**
* Event emitter for changing the document title
*
* Emit this event to update the browser's document title. Emit the name of
* the page on its own, the host decides how to join it with the name of
* the application.
*/
titleChanged?: {
emit: (data?: string | undefined) => CustomEvent;
};
}