diff --git a/src/router.js b/src/router.js
index 057e2922..0eeb7d27 100644
--- a/src/router.js
+++ b/src/router.js
@@ -902,7 +902,7 @@ function getEntryOffset(entry) {
if (config.dialog) return -1;
if (j === state[i].length) {
- offset = state[i].length - 1;
+ return -1;
}
entry = entry.nested;
diff --git a/test/spec/router.js b/test/spec/router.js
index 829dbdde..f3c299e7 100644
--- a/test/spec/router.js
+++ b/test/spec/router.js
@@ -251,7 +251,50 @@ describe("router:", () => {
el.querySelector("a").click();
return resolveTimeout(() => {
- expect(history.state.length).toBe(2);
+ expect(history.state.length).toBe(3);
+ document.body.removeChild(el);
+ });
+ });
+ });
+ });
+
+ it("pushes history entry when navigating between sibling views", () => {
+ const SiblingA = define({
+ tag: "test-router-sibling-a",
+ [router.connect]: { url: "/sibling-a" },
+ render: () => html`B`,
+ });
+
+ const SiblingB = define({
+ tag: "test-router-sibling-b",
+ [router.connect]: { url: "/sibling-b" },
+ render: () => html`A`,
+ });
+
+ define({
+ tag: "test-router-sibling-app",
+ stack: router([SiblingA, SiblingB]),
+ render: ({ stack }) => html`${stack}`,
+ });
+
+ const el = document.createElement("test-router-sibling-app");
+ document.body.appendChild(el);
+
+ return resolveTimeout(() => {
+ expect(hybrids(el.children[0])).toBe(SiblingA);
+ expect(window.history.state.length).toBe(1);
+
+ el.querySelector("#SiblingB").click();
+
+ return resolveTimeout(() => {
+ expect(hybrids(el.children[0])).toBe(SiblingB);
+ expect(window.history.state.length).toBe(2);
+
+ window.history.back();
+
+ return resolveTimeout(() => {
+ expect(hybrids(el.children[0])).toBe(SiblingA);
+ expect(window.history.state.length).toBe(1);
document.body.removeChild(el);
});
});
diff --git a/types/index.d.ts b/types/index.d.ts
index f5086693..6e435bc2 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -3,7 +3,7 @@
* (shorthand syntax), a getter function called with the element instance,
* or a full {@link Descriptor} object.
*
- * @see https://hybrids.js.org/#/component-model/structure.md
+ * @see https://hybrids.js.org/#/component-model/structure
*/
export type Property =
| (V extends string | number | boolean | null | undefined ? V : never)
@@ -14,7 +14,7 @@ export type Property =
* The full syntax of the property definition with the cached `value` and
* optional lifecycle methods and options.
*
- * @see https://hybrids.js.org/#/component-model/structure.md#property-descriptor
+ * @see https://hybrids.js.org/#/component-model/structure?id=property-descriptor
*/
export interface Descriptor {
/**
@@ -66,7 +66,7 @@ export interface RenderFunction {
* The full syntax of the `render` property with the `shadow` option, which
* controls how the template is attached to the element.
*
- * @see https://hybrids.js.org/#/component-model/structure.md#render
+ * @see https://hybrids.js.org/#/component-model/structure?id=render
*/
export interface RenderDescriptor extends Descriptor> {
value: RenderFunction;
@@ -92,7 +92,7 @@ export type ComponentBase = {
* A map of the component properties with a required `tag` name, which
* describes the structure and behavior of the custom element.
*
- * @see https://hybrids.js.org/#/component-model/structure.md
+ * @see https://hybrids.js.org/#/component-model/structure
*/
export type Component = ComponentBase & {
[
@@ -129,7 +129,7 @@ export interface HybridElement {
* @param component - a map of properties with a `tag` name
* @returns the passed component definition
*
- * @see https://hybrids.js.org/#/component-model/definition.md
+ * @see https://hybrids.js.org/#/component-model/definition
*/
export function define(component: Component): typeof component;
@@ -142,7 +142,7 @@ export namespace define {
* @param component - a map of properties without the `tag` name
* @returns a custom element constructor
*
- * @see https://hybrids.js.org/#/component-model/definition.md#external-usage
+ * @see https://hybrids.js.org/#/component-model/definition?id=external-usage
*/
function compile(component: Component): HybridElement;
@@ -162,7 +162,7 @@ export namespace define {
* @param options.prefix - a prefix added to the generated tag names
* @param options.root - a path or list of paths removed from the tag names
*
- * @see https://hybrids.js.org/#/component-model/definition.md#multiple-components
+ * @see https://hybrids.js.org/#/component-model/definition?id=multiple-components
*/
function from(
components: { [path: string]: Component },
@@ -181,7 +181,7 @@ export namespace define {
* @param component - a map of properties without the `tag` name
* @returns a function, which detaches the component from the target
*
- * @see https://hybrids.js.org/#/component-model/definition.md#mounting
+ * @see https://hybrids.js.org/#/component-model/definition?id=mounting
*/
export function mount(
target: HTMLElement,
@@ -199,7 +199,7 @@ export function mount(
* when the checked definition meets the condition
* @returns a property descriptor, which resolves to `null` or an element
*
- * @see https://hybrids.js.org/#/component-model/parent-children.md#parent
+ * @see https://hybrids.js.org/#/component-model/parent-children?id=parent
*/
export function parent(
componentOrFn: Component | ((component: Component) => boolean),
@@ -216,7 +216,7 @@ export function parent(
* @param options.nested - include nested matching children (requires `deep`)
* @returns a property descriptor, which resolves to an array of elements
*
- * @see https://hybrids.js.org/#/component-model/parent-children.md#children
+ * @see https://hybrids.js.org/#/component-model/parent-children?id=children
*/
export function children(
componentOrFn: Component | ((component: Component) => boolean),
@@ -246,7 +246,7 @@ export type NonModelDefinition = { __store__connect__?: never } & object;
* A model definition - a plain object describing the structure of the model
* instance by its default values, with an optional `[store.connect]` storage.
*
- * @see https://hybrids.js.org/#/store/model.md
+ * @see https://hybrids.js.org/#/store/model
*/
export type Model = NonArrayObject & {
[property in keyof Omit]-?: NonNullable<
@@ -303,7 +303,7 @@ export type NestedArrayModel =
* An identifier of the enumerable model instance - a string, or an object map
* of parameters (for example, for a paginated listing).
*
- * @see https://hybrids.js.org/#/store/model.md#identifier
+ * @see https://hybrids.js.org/#/store/model?id=identifier
*/
export type ModelIdentifier =
string | Record | undefined;
@@ -335,7 +335,7 @@ export type StorageResult =
* An external storage definition set in the `[store.connect]` property of the
* model definition, which connects the model to an async data source.
*
- * @see https://hybrids.js.org/#/store/storage.md
+ * @see https://hybrids.js.org/#/store/storage
*/
export type Storage = {
/** Fetches a single model instance by its identifier. */
@@ -400,7 +400,7 @@ export type Storage = {
* which can be saved by the `store.submit()` method
* @returns a property descriptor connected to the store
*
- * @see https://hybrids.js.org/#/store/usage.md#factory
+ * @see https://hybrids.js.org/#/store/usage?id=factory
*/
// Enumerable - This overload must be the first one, then its signature and documentation will be displayed in intelephence by default.
export function store(
@@ -441,7 +441,7 @@ export namespace store {
* A key of the model definition property, which connects the model to an
* external storage.
*
- * @see https://hybrids.js.org/#/store/storage.md#external
+ * @see https://hybrids.js.org/#/store/storage?id=external
*/
const connect = "__store__connect__";
@@ -454,7 +454,7 @@ export namespace store {
* @param id - an identifier of the model instance
* @returns a model instance or its placeholder
*
- * @see https://hybrids.js.org/#/store/usage.md#storeget
+ * @see https://hybrids.js.org/#/store/usage?id=storeget
*/
function get(
Model: Model,
@@ -474,7 +474,7 @@ export namespace store {
* @param values - partial values of the model, or `null` to delete it
* @returns a promise resolving to the model instance
*
- * @see https://hybrids.js.org/#/store/usage.md#storeset
+ * @see https://hybrids.js.org/#/store/usage?id=storeset
*/
function set(
model: Model | M,
@@ -488,7 +488,7 @@ export namespace store {
* @param values - partial values of the model, or `null` to delete it
* @returns a model instance or its placeholder
*
- * @see https://hybrids.js.org/#/store/usage.md#storesync
+ * @see https://hybrids.js.org/#/store/usage?id=storesync
*/
function sync(
model: Model | M,
@@ -502,7 +502,7 @@ export namespace store {
* @param clearValue - when `false`, the value is only marked as expired
* instead of being deleted. Defaults to `true`
*
- * @see https://hybrids.js.org/#/store/usage.md#storeclear
+ * @see https://hybrids.js.org/#/store/usage?id=storeclear
*/
function clear(
model: Model | [Model] | M,
@@ -513,7 +513,7 @@ export namespace store {
* A guard, which returns a promise resolving with the next model value when
* the instance is in the pending state, or `false` otherwise.
*
- * @see https://hybrids.js.org/#/store/usage.md#storepending
+ * @see https://hybrids.js.org/#/store/usage?id=storepending
*/
function pending(model: M): false | Promise;
function pending(
@@ -528,7 +528,7 @@ export namespace store {
* @param propertyName - a property name with a failed validation defined by
* the `store.value()` method, or `null` for the general error message only
*
- * @see https://hybrids.js.org/#/store/usage.md#storeerror
+ * @see https://hybrids.js.org/#/store/usage?id=storeerror
*/
function error(
model: M,
@@ -539,7 +539,7 @@ export namespace store {
* A guard, which returns `true` when the passed model instances contain
* valid values (they are not placeholders).
*
- * @see https://hybrids.js.org/#/store/usage.md#storeready
+ * @see https://hybrids.js.org/#/store/usage?id=storeready
*/
function ready(model: M): boolean;
function ready(...models: Array): boolean;
@@ -552,7 +552,7 @@ export namespace store {
* @param values - optional values merged on top of the draft values
* @returns a promise resolving with the primary model instance
*
- * @see https://hybrids.js.org/#/store/usage.md#draft-mode
+ * @see https://hybrids.js.org/#/store/usage?id=draft-mode
*/
function submit(
draft: M,
@@ -563,7 +563,7 @@ export namespace store {
* Returns a promise, which resolves with the latest model value when it is
* ready, or rejects with the error of the model instance.
*
- * @see https://hybrids.js.org/#/store/usage.md#storeresolve
+ * @see https://hybrids.js.org/#/store/usage?id=storeresolve
*/
function resolve(model: M): Promise;
function resolve(
@@ -582,7 +582,7 @@ export namespace store {
* @param fn - a function returning the property definition
* @returns the passed function, marked for the store
*
- * @see https://hybrids.js.org/#/store/model.md#self-reference--import-cycles
+ * @see https://hybrids.js.org/#/store/model?id=self-reference--import-cycles
*/
function ref(fn: () => T): () => T;
@@ -593,7 +593,7 @@ export namespace store {
* @param value - any supported property value
* @returns a record definition (an empty object)
*
- * @see https://hybrids.js.org/#/store/model.md#record
+ * @see https://hybrids.js.org/#/store/model?id=record
*/
function record(value: V): Record;
function record {}>(value: V): Record>;
@@ -612,7 +612,7 @@ export namespace store {
* default validation fails for falsy values
* @param errorMessage - an error message used when the validation fails
*
- * @see https://hybrids.js.org/#/store/model.md#validation
+ * @see https://hybrids.js.org/#/store/model?id=validation
*/
function value(
defaultValue: string,
@@ -639,7 +639,7 @@ export namespace store {
* the last model instance
* @returns a function, which stops observing
*
- * @see https://hybrids.js.org/#/store/usage.md#storeobserve
+ * @see https://hybrids.js.org/#/store/usage?id=storeobserve
*/
function observe(
model: Model,
@@ -657,7 +657,7 @@ export namespace store {
* A configuration of the view set in the `[router.connect]` property of the
* component definition.
*
- * @see https://hybrids.js.org/#/router/view.md#configuration
+ * @see https://hybrids.js.org/#/router/view?id=configuration
*/
export interface ViewOptions {
/** A URL pattern of the view, for example `"/users/:userId"`. */
@@ -703,7 +703,7 @@ export interface ViewOptions {
* with the type of the transition between views
* @returns a property descriptor, which resolves to an array of elements
*
- * @see https://hybrids.js.org/#/router/usage.md
+ * @see https://hybrids.js.org/#/router/usage
*/
export function router(
views:
@@ -720,7 +720,7 @@ export namespace router {
* A key of the component definition property, which configures the view
* options, like the `url` or the `stack` of nested views.
*
- * @see https://hybrids.js.org/#/router/view.md#configuration
+ * @see https://hybrids.js.org/#/router/view?id=configuration
*/
const connect = "__router__connect__";
@@ -728,7 +728,7 @@ export namespace router {
* Enables logging of the navigation events and exposes the current view in
* the DevTools console via the `$$0` reference.
*
- * @see https://hybrids.js.org/#/router/usage.md#debug-mode
+ * @see https://hybrids.js.org/#/router/usage?id=debug-mode
*/
function debug(value?: boolean): void;
@@ -749,7 +749,7 @@ export namespace router {
* option
* @returns a `URL` instance, or an empty string if the view is not found
*
- * @see https://hybrids.js.org/#/router/usage.md#routerurl
+ * @see https://hybrids.js.org/#/router/usage?id=routerurl
*/
function url(
view: ComponentBase,
@@ -760,20 +760,20 @@ export namespace router {
* Generates a URL to the previous view in the stack. Use the `nested` option
* inside of a nested router to go back within its own stack.
*
- * @see https://hybrids.js.org/#/router/usage.md#routerbackurl
+ * @see https://hybrids.js.org/#/router/usage?id=routerbackurl
*/
function backUrl(options?: { nested?: boolean } & UrlOptions): URL | "";
/**
* Generates a URL to the view, which the user tried to reach before being
* redirected by the `guard` option of the current view.
*
- * @see https://hybrids.js.org/#/router/usage.md#routerguardurl
+ * @see https://hybrids.js.org/#/router/usage?id=routerguardurl
*/
function guardUrl(params?: UrlParams & UrlOptions): URL | "";
/**
* Generates a URL to the current view with updated parameters.
*
- * @see https://hybrids.js.org/#/router/usage.md#routercurrenturl
+ * @see https://hybrids.js.org/#/router/usage?id=routercurrenturl
*/
function currentUrl(params?: UrlParams & UrlOptions): URL | "";
@@ -787,7 +787,7 @@ export namespace router {
* @param params - parameters passed to the view, plus the `scrollToTop`
* option
*
- * @see https://hybrids.js.org/#/router/usage.md#routernavigate
+ * @see https://hybrids.js.org/#/router/usage?id=routernavigate
*/
function navigate(
view: ComponentBase,
@@ -798,7 +798,7 @@ export namespace router {
* Checks if the passed views are currently active. Use the `stack` option to
* also match views, which are parents of the current view.
*
- * @see https://hybrids.js.org/#/router/usage.md#routeractive
+ * @see https://hybrids.js.org/#/router/usage?id=routeractive
*/
function active(
views: ComponentBase | ComponentBase[],
@@ -813,7 +813,7 @@ export namespace router {
* @param promise - a promise, which must resolve to navigate
* @returns a chained promise
*
- * @see https://hybrids.js.org/#/router/usage.md#routerresolve
+ * @see https://hybrids.js.org/#/router/usage?id=routerresolve
*/
function resolve
(event: Event, promise: Promise
): Promise
;
}
@@ -824,7 +824,7 @@ export namespace router {
* A dictionary of translated messages, where keys are the message keys
* (usually the original text content) with an optional `| context` suffix.
*
- * @see https://hybrids.js.org/#/component-model/localization.md#messages
+ * @see https://hybrids.js.org/#/component-model/localization?id=messages
*/
export type Messages = {
[key: string]: {
@@ -854,7 +854,7 @@ export type Messages = {
* the fallback language
* @param messages - a map of translated messages
*
- * @see https://hybrids.js.org/#/component-model/localization.md#messages
+ * @see https://hybrids.js.org/#/component-model/localization?id=messages
*/
export function localize(lang: string, messages: Messages): void;
/**
@@ -866,7 +866,7 @@ export function localize(lang: string, messages: Messages): void;
* @param options.format - transforms keys to a custom format; for now only
* `"chrome.i18n"` is supported
*
- * @see https://hybrids.js.org/#/component-model/localization.md#custom-function
+ * @see https://hybrids.js.org/#/component-model/localization?id=custom-function
*/
export function localize(
translate: (
@@ -894,7 +894,7 @@ export namespace localize {
* html``
* ```
*
- * @see https://hybrids.js.org/#/component-model/localization.md#manual-translation
+ * @see https://hybrids.js.org/#/component-model/localization?id=manual-translation
*/
export function msg(parts: TemplateStringsArray, ...args: unknown[]): string;
@@ -903,7 +903,7 @@ export namespace msg {
* Translates a message with HTML content and returns a nested template.
* The content is not sanitized, so use it only with trusted messages.
*
- * @see https://hybrids.js.org/#/component-model/localization.md#html--svg-content
+ * @see https://hybrids.js.org/#/component-model/localization?id=html--svg-content
*/
function html(
parts: TemplateStringsArray,
@@ -914,7 +914,7 @@ export namespace msg {
* Translates a message with SVG content and returns a nested template.
* The content is not sanitized, so use it only with trusted messages.
*
- * @see https://hybrids.js.org/#/component-model/localization.md#html--svg-content
+ * @see https://hybrids.js.org/#/component-model/localization?id=html--svg-content
*/
function svg(
parts: TemplateStringsArray,
@@ -934,7 +934,7 @@ export namespace msg {
* @returns `false` if the event is cancelable and one of the handlers called
* `preventDefault()`; otherwise `true`
*
- * @see https://hybrids.js.org/#/component-model/events.md
+ * @see https://hybrids.js.org/#/component-model/events
*/
export function dispatch(
host: EventTarget,
@@ -946,7 +946,7 @@ export function dispatch(
* Enables the debug mode of the library, which adds extra logging and error
* messages. Call it before any other code, only in the development build.
*
- * @see https://hybrids.js.org/#/getting-started.md#debug-mode
+ * @see https://hybrids.js.org/#/getting-started?id=debug-mode
*/
export function debug(): void;
@@ -956,35 +956,35 @@ export function debug(): void;
* An update function returned by the `html` and `svg` template engines with
* chainable helper methods.
*
- * @see https://hybrids.js.org/#/component-model/templates.md
+ * @see https://hybrids.js.org/#/component-model/templates
*/
export interface UpdateFunctionWithMethods extends UpdateFunction {
/**
* Sets a unique key for the template, so a list of items can be efficiently
* re-ordered instead of re-created.
*
- * @see https://hybrids.js.org/#/component-model/templates.md#keys
+ * @see https://hybrids.js.org/#/component-model/templates?id=keys
*/
key: (id: any) => this;
/**
* Attaches styles to the template from CSS text contents or from
* `CSSStyleSheet` instances.
*
- * @see https://hybrids.js.org/#/component-model/templates.md#style-element
+ * @see https://hybrids.js.org/#/component-model/templates?id=style-element
*/
style: (...styles: Array) => this;
/**
* Attaches styles to the template written as a tagged template literal with
* support for dynamic expressions.
*
- * @see https://hybrids.js.org/#/component-model/templates.md#css-content
+ * @see https://hybrids.js.org/#/component-model/templates?id=css-content
*/
css: (parts: TemplateStringsArray, ...args: unknown[]) => this;
/**
* Wraps the update function with a plugin, which takes control over the
* update process. It can be chained.
*
- * @see https://hybrids.js.org/#/component-model/templates.md#plugins
+ * @see https://hybrids.js.org/#/component-model/templates?id=plugins
*/
use: (fn: (template: UpdateFunction) => UpdateFunction) => this;
}
@@ -993,7 +993,7 @@ export interface UpdateFunctionWithMethods extends UpdateFunction {
* An event listener attached in the template, which is called with the host
* element instead of the event target.
*
- * @see https://hybrids.js.org/#/component-model/templates.md#event-listeners
+ * @see https://hybrids.js.org/#/component-model/templates?id=event-listeners
*/
export interface EventHandler {
(host: E & HTMLElement, event?: Event): any;
@@ -1009,7 +1009,7 @@ export interface EventHandler {
*
* @returns an update function with chainable helper methods
*
- * @see https://hybrids.js.org/#/component-model/templates.md
+ * @see https://hybrids.js.org/#/component-model/templates
*/
export function html(
parts: TemplateStringsArray,
@@ -1025,7 +1025,7 @@ export namespace html {
* html``
* ```
*
- * @see https://hybrids.js.org/#/component-model/templates.md#form-elements
+ * @see https://hybrids.js.org/#/component-model/templates?id=form-elements
*/
function set(property: keyof E, valueOrPath?: any): EventHandler;
function set(property: M, valueOrPath: string | null): EventHandler;
@@ -1040,7 +1040,7 @@ export namespace html {
* @param delay - a delay in milliseconds before the placeholder is rendered.
* Defaults to `200`
*
- * @see https://hybrids.js.org/#/component-model/templates.md#promises
+ * @see https://hybrids.js.org/#/component-model/templates?id=promises
*/
function resolve(
promise: Promise,
@@ -1056,14 +1056,14 @@ export namespace html {
* render: ({ stack }) => html`${stack}`.use(html.transition)
* ```
*
- * @see https://hybrids.js.org/#/component-model/templates.md#transition-api
+ * @see https://hybrids.js.org/#/component-model/templates?id=transition-api
*/
function transition(template: UpdateFunction): UpdateFunction;
/**
* An alias of the `msg` helper for translating messages inside of templates.
*
- * @see https://hybrids.js.org/#/component-model/localization.md#manual-translation
+ * @see https://hybrids.js.org/#/component-model/localization?id=manual-translation
*/
function msg(parts: TemplateStringsArray, ...args: unknown[]): string;
}
@@ -1075,7 +1075,7 @@ export namespace html {
*
* @returns an update function with chainable helper methods
*
- * @see https://hybrids.js.org/#/component-model/templates.md
+ * @see https://hybrids.js.org/#/component-model/templates
*/
export function svg(
parts: TemplateStringsArray,