Skip to main content

Regions and views

A region is an area of the shell where plugins inject views. A view is an HTML element, usually a Web Component, registered by a plugin.

The shell creates the regions and gives each plugin a regionManager in its API. This page covers how plugins use it. To create regions in your own shell, see Building a shell.

Harmonix uses the @uxland/regions library. The core re-exports createRegionManager and createRegionHost. The @region decorator and the adapters come from @uxland/regions, which the shell depends on directly.

HarmonixRegionManager​

export interface HarmonixRegionManager {
registerView(regionName: string, view: HarmonixViewDefinition): Promise<void>;
removeView(regionName: string, viewId: string): Promise<void>;
activateView(regionName: string, viewId: string): Promise<void>;
deactivateView(regionName: string, viewId: string): Promise<void>;
getRegion(regionName: string): Promise<IRegion>;
isViewActive(regionName: string, viewId: string): Promise<boolean>;
containsView(regionName: string, viewId: string): Promise<boolean>;
}
MethodWhat it does
registerView(region, view)Registers a view in a region
removeView(region, viewId)Removes the view from the region
activateView(region, viewId)Shows the view. In a single-active region, it hides the view that was active
deactivateView(region, viewId)Hides the view
getRegion(region)The IRegion object of @uxland/regions
isViewActive(region, viewId)Whether the view is shown
containsView(region, viewId)Whether the view is registered in the region

A plugin can register views in several regions.

View definition​

export interface HarmonixViewDefinition {
id: string;
factory: (regionContext?: unknown) => Promise<HTMLElement>;
sortHint?: string;
isDefault?: boolean;
removeFromDomWhenDeactivated?: boolean;
}
FieldDescription
idId of the view. Used by activateView, removeView and the other methods
factory(regionContext?)Async function that creates the element. It runs the first time the view is activated. regionContext is the context the shell set on the region, if any
sortHintOptional. Sort key for the views of a multiple-active region
isDefaultOptional. In a single-active region, the view is activated when it is added if no other view is active, and again whenever the active view is deactivated
removeFromDomWhenDeactivatedOptional. Removes the element from the DOM when the view is deactivated, instead of hiding it

View ids​

In the demo shell, a view id only has to be unique within the plugin: the shell stores each view as pluginId::viewId. If your shell does not namespace ids this way, prefix them with api.pluginInfo.pluginId.

Region types​

The shell chooses how each region behaves:

  • Single-active regions show one view at a time. Activating a view hides the previous one. The main content area is usually single-active.
  • Multiple-active regions show all their views at once, for example a header or a menu. A view is activated as soon as it is registered.

Shell helpers​

A shell can add shortcuts so that plugins do not need to know region names. The demo shell adds:

HelperWhat it does
regionsThe region names: regions.header, regions.sideMenu, regions.main
registerMainView(view)registerView in the main region
activateMainView(viewId)activateView in the main region
registerNavigationItem({ id, label, icon?, mainViewId })Adds an item to the side menu that activates the main view mainViewId

These helpers belong to the demo shell, not to the core. Other shells can offer different ones.

Complete example​

First, the plugin defines the element it will show:

class ExampleComponent extends HTMLElement {
connectedCallback() {
const root = this.shadowRoot ?? this.attachShadow({ mode: "open" });
root.innerHTML = `<h1>Hello from ${this.getAttribute("plugin-id")}</h1>`;
}
}

if (!customElements.get("example-plugin-view")) {
customElements.define("example-plugin-view", ExampleComponent);
}

Then the plugin registers it in initialize and removes it in dispose. This example uses the demo shell:

import type { DemoShellApi } from "@uxland/harmonix-demo-shell";

export const initialize = async (api: DemoShellApi) => {
// Generic: a view in any region.
await api.regionManager.registerView(api.regionManager.regions.main, {
id: "plugin-main-view",
factory: async () => {
const element = new ExampleComponent();
element.setAttribute("plugin-id", api.pluginInfo.pluginId);
return element;
},
});

// Helper: an item in the side menu that activates that view.
await api.regionManager.registerNavigationItem({
id: "plugin-menu",
label: "Example",
mainViewId: "plugin-main-view",
});

// Show it.
await api.regionManager.activateMainView("plugin-main-view");
};

export const dispose = async (api: DemoShellApi) => {
const { regions } = api.regionManager;
await api.regionManager.removeView(regions.sideMenu, "plugin-menu");
await api.regionManager.removeView(regions.main, "plugin-main-view");
};