@wincc-oa/wui-ui-elements
v2.0.1
Published
WinCC Open Architecture Dashboard project.
Maintainers
Readme
WinCCOA WebComponent Dashboard
This package is part of the workspace for the WinCC Open Architecture WebComponent Dashboard, built using Lit and managed with Nx.
Usage information and reference details can be found in the WinCC OA documentation.
Components in this package
This package holds presentational WebComponents. Some examples:
wui-grid-layout,wui-list-layout,wui-schematic-layout,wui-map-layout- layout components. Each arranges a list of items and lets the user move, resize, or add to them.wui-layout- picks one of the layout components above based on a render type.wui-layout-host- hosts a layout and wires it to the edit session (dirty tracking, inner layout reference).wui-search-bar,wui-nav-tree,wui-tree-table,wui-datetime-range-picker,wui-datetime-range-button- other standalone UI components.
Using a layout
A layout takes a value (a list of items, each with an id and optional position/size fields
such as x, y, cols, rows) and a settings object for that layout type. An item may carry
any extra data your own code needs - the layout does not look inside it.
To control how each item is drawn, pass a strategy:
import '@wincc-oa/wui-ui-elements/wui-grid-layout/wui-grid-layout.js';
import { LayoutItemStrategyBuilder } from '@wincc-oa/wui-ui-elements/model/layout-item-strategy.js';
import { html } from 'lit';
const strategy = new LayoutItemStrategyBuilder().withRender(({ item }) => html`<div class="tile">${item.data.label}</div>`).build();
const value = [{ id: 'a', x: 0, y: 0, cols: 2, rows: 2, data: { label: 'Hello' } }];<wui-grid-layout .value="${value}" .settings="${{}}" .strategy="${strategy}"></wui-grid-layout>A layout works without a strategy too - each item is then shown as plain text, which is enough
to try a layout out before writing a real renderer for it.
Besides drawing an item, a strategy can also read a dropped item to suggest a size for it, and turn a pasted image into item data. Both are optional and only apply if you need them:
const strategy = new LayoutItemStrategyBuilder()
.withRender(({ item }) => html`<div class="tile">${item.data.label}</div>`)
.withDragInterpreter((raw) => ({ size: { cols: raw.cols, rows: raw.rows } }))
.withImagePaste((image) => ({ label: image.src }))
.build();wui-layout: picking a layout by render type
Most consumers do not pick a concrete layout tag directly. wui-layout renders the matching
layout (grid, schematic, list, or map) based on settings.renderType, and forwards
value, disabled, strategy, and the focus-x/focus-y/focus-z properties to it.
<wui-layout .settings="${settings}" .value="${value}" .strategy="${strategy}"></wui-layout>| Property | Type | Description |
| -------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| settings | WuiLayoutSettings | Layout config; settings.renderType wins over the renderType property |
| value | LayoutItem<D>[] | Item configurations |
| renderType | 'grid' \| 'schematic' \| 'list' \| 'map' | Fallback layout type when settings has none set (default grid) |
| disabled | boolean | Disables editing operations on the inner layout |
| strategy | LayoutItemStrategy<unknown> | Forwarded to the inner layout unchanged |
| focusX / focus-x | number \| undefined | Forwarded to every layout; meaning is layout-specific (e.g. map's latitude). Only a layout with a notion of "current view" acts on it |
| focusY / focus-y | number \| undefined | See focusX |
| focusZ / focus-z | number \| undefined | See focusX |
| focusId / focus-id | string \| undefined | ID of the widget to select on mount, e.g. the widget just saved |
| fitContainer / fit-container | boolean | Grid only: clamp to a fixed-size parent and scroll internally (nested grids). Off = grow with rows (top-level). See "Sizing & Scaling". |
Listen for wui:layoutready (detail { layout }) to reach the inner layout instance for
imperative calls (paste, elementUpdater, etc.).
wui-layout-host: wiring a layout into the edit session
wui-layout-host extends wui-layout with edit-session wiring: it calls session.markDirty()
on the consumed editSessionContext whenever the inner layout dispatches change. disabled
stays a plain inherited property - the caller decides it (e.g. from canEdit, a
dashboard-permission concept wui-layout-host does not know about). The wui:requestadd event
from the inner layout needs no re-dispatch: it already bubbles and crosses shadow-DOM boundaries
on its own (bubbles: true, composed: true).
<wui-layout-host .value="${items}" .settings="${settings}" .strategy="${strategy}"></wui-layout-host>| Property/Event | Description |
| ---------------- | ---------------------------------------------------------------------------------- |
| layout | Getter returning the currently-mounted concrete layout instance |
| wui:requestadd | Bubbles up from the inner layout unchanged, no listener or re-dispatch needed here |
wui-layout-host has no dashboard-specific properties. It has no WuiDashboardLayoutHost
subclass either. The WinCC OA dashboard binds its widget list directly to .value.
Sizing & Scaling
How a layout fills its parent is driven by settings, not CSS:
See docs/knowledge/project/webui-runtime-layouts.md for the per-layout sizing/scaling internals
table.
Give the layout (or wui-layout/wui-layout-host) a parent with an explicit height. Outside the
dashboard shell these components have no implicit size.
Base Class API Reference
WuiBaseLayout<T extends LayoutItem<D, G>, D, TSettings extends LayoutSettingsBase, G>
G is the layout-specific geometry type (for example LayoutItemGridGeometry for grid,
LayoutItemSchematicGeometry for schematic). Each concrete layout intersects its own G onto
LayoutItem so existing field access (item.x, item.cols, and so on) keeps working per layout.
Abstract base class for all concrete layout implementations (WuiGridLayout, WuiListLayout,
WuiSchematicLayout, WuiMapLayout).
Properties
| Property | Type | Description |
| ------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| settings | TSettings | Layout configuration |
| value | T[] \| undefined | Array of layout items |
| defaultValue | T[] \| undefined | Persisted baseline; dirty compares value against this |
| dirty | boolean | Read-only, computed by structural comparison of value vs defaultValue |
| disabled | boolean | Disables all editing operations |
| readonly | boolean | Reflected attribute; layout-specific meaning |
| loading | boolean | Reflected attribute |
| focusX/focusY/focusZ/focusId | number \| string \| undefined | Geometry of the widget just saved, forwarded from the add-widget page; field meaning is layout-specific |
| strategy | LayoutItemStrategy<unknown> \| undefined | Delegates item rendering/drag/paste; see "Using a layout" above |
dirty has no public setter and never dispatches an event on its own - it is purely the result
of comparing value against defaultValue by item id and geometry fields, recomputed on every
read.
Abstract Methods (Must Override)
| Method | Signature | Description |
| ------------------------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------- |
| retrieveValue | (event) => T[] | Returns items for clipboard copy/cut |
| getId | (element: T) => string \| number | Returns unique ID of an item |
| getCapabilities | () => LayoutCapabilities | Describes which features this layout supports |
| getLayoutItems | () => Element[] (protected) | Returns all rendered item elements in the layout |
| getItemIdAttribute | () => string (protected) | Returns the attribute name that holds the item ID |
| getItemContentSelector | () => string (protected) | Returns CSS selector for the content element inside an item |
| pasteWidgetJson | (widget: T) => void (protected) | Inserts a pasted item into the layout |
| buildPastedImageItem | (image: HTMLImageElement, data: unknown) => T (protected) | Combines layout-owned geometry with strategy-supplied data for a pasted image |
LayoutCapabilities describes which interactive features are available at runtime:
interface LayoutCapabilities {
supportsDragDrop: boolean; // widgets can be dragged within the layout
supportsResize: boolean; // widgets have resize handles
supportsRotation: boolean; // widgets can be rotated (typically only schematic)
supportsZIndex: boolean; // widgets support z-index layering
supportsMobileMode: boolean; // layout adapts to mobile screen sizes
supportsAutoPosition: boolean; // new widgets are auto-positioned
supportsOutOfBoundsFix: boolean; // layout implements fixOutOfBounds() to repair items outside the visible area
}Virtual Methods (Can Override)
| Method | Default Behavior | Override When |
| ------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| elementUpdater | Returns list unchanged | Layout needs drag-drop from a selector |
| handleCopy | Writes to clipboard via ClipboardController | Custom copy behavior needed |
| handleCut | Copies then deletes items | Custom cut behavior needed |
| handlePaste | Reads from clipboard controller | Custom paste/positioning needed |
| handleDelete | Removes from value array | Layout-specific cleanup needed |
| handleUpdate | Updates item in value array | Layout-specific update logic |
| fixOutOfBounds | No-op | Layout can end up in an invalid state and needs repair (see LayoutCapabilities.supportsOutOfBoundsFix) |
| toLocalSize | Pass-through (grid-cell units unchanged) | Layout's on-screen unit differs from grid cells |
| onEditModeChanged | No-op | Layout needs to react to disabled changes |
Protected Utilities (Use Directly)
| Member | Description |
| -------------------- | ------------------------------------------------------------ |
| indexById(item: T) | Find array index by item ID |
| isItemSaved(id) | Whether an item with this id is present in defaultValue |
| clipboard | ClipboardController instance for copy/paste |
| reset() | Restores value from defaultValue and dispatches change |
Selection
| Member | Description |
| -------------- | ----------------------------------------------------------------------------------- |
| selectedItem | Getter: T \| undefined. Reads whichever item currently has the marked attribute |
| select event | bubbles: true, composed: true. Fires on every mark and unmark transition |
A consumer can listen for select on wui-layout or wui-layout-host directly. The event
crosses shadow-DOM boundaries on its own, the same composed: true mechanism that lets
editSessionContext reach descendants without an intermediate re-provide.
Layout Comparison
| Feature | Grid Layout | Schematic Layout | List Layout | Map Layout |
| ------------------- | ------------------------- | ------------------ | ----------------------- | ----------------------------------------------------- |
| Positioning | Cell-based (columns/rows) | Pixel-based (x/y) | Order-based (flex) | Geographic (latitude/longitude) |
| Auto-position | Yes (finds empty space) | No (manual only) | Yes (reflow on resize) | No (manual only) |
| Collision detection | Yes | No | No | No |
| Rotation | No | Yes | No | No |
| Z-index | No | Yes | No | No (zoom means base zoom level, see wui-map-layout) |
| Mobile responsive | Yes (single column) | No | Yes (wraps) | No |
| Background image | Color only | Color + Image | Color only | No (tile layer instead) |
| Resize handles | Built-in (GridStack) | Custom SVG handles | None (fixed-size items) | Custom corner handle |
wui-list-layout
A flex-based layout that arranges same-sized widgets. Items reflow automatically when the
container is resized - no JS needed, pure CSS flex-wrap.
Scale Modes
Controlled via ListLayoutConfig.elementScaling:
| Mode | Behavior |
| --------------- | ------------------------------------------------------ |
| fixed-element | Items have fixed pixel size, wrap as container resizes |
| fixed-height | Single horizontal row, container height = item height |
| fixed-width | Single vertical column, container width = item width |
For fixed-element, set flowDirection: 'row' (horizontal wrap, default) or 'column' (vertical wrap).
Example
import '@wincc-oa/wui-ui-elements/wui-list-layout/wui-list-layout.js';
import type { ListLayoutConfig } from '@wincc-oa/wui-ui-elements/model/layout-config.js';
const settings: ListLayoutConfig = {
elementScaling: 'fixed-element',
flowDirection: 'row',
gap: 4,
elementWidth: 160,
elementHeight: 120
};
const value = [{ id: 'gauge-1', data: {/* your own render data */} }];
// In a Lit template:
html`<wui-list-layout .settings=${settings} .value=${value} .strategy=${strategy}></wui-list-layout>`;Removing the default border
wui-list-layout {
--wui-layout-border: none;
}wui-map-layout
A Leaflet-based layout that places widgets on a geographic map by latitude/longitude, with markers that collapse to a marker or cluster when zoomed far out.
Settings
| Setting | Type | Editable in form? | Description |
| ----------------- | --------- | ------------------ | --------------------------------------------------------------------------- |
| lat | number | Yes | Starting latitude. Falls back to 51 if unset. |
| lng | number | Yes | Starting longitude. Falls back to 10 if unset. |
| zoom | number | Yes | Starting zoom level. Falls back to 6 if unset. |
| tileUrl | string | Yes | Tile layer URL template. Falls back to the OpenStreetMap tile URL if unset. |
| tileAttribution | string | Yes | Tile layer attribution text. Falls back to the OpenStreetMap attribution. |
| renderOffscreen | boolean | No (settings-only) | Disables viewport culling (see below). Defaults to false. |
See docs/knowledge/project/webui-runtime-layouts.md for the tile-fallback-ownership and
settings-only-escape-hatch internals behind renderOffscreen and tileUrl.
Background color and image are hidden from the edit form for renderType: 'map', since the tile
layer covers the whole viewport and this layout never applies them.
Required WinCC OA project setting
Map tiles are loaded from a host outside the WinCC OA web server, so the project has to allow
external resources. Without this the tiles are blocked and the map stays empty. Add to the
project's config file:
[wssServer]
allowExternalResources = 1Choosing a tile server
Leaving tileUrl empty uses the OpenStreetMap community tile servers - fine for demos and local
development. For production, point tileUrl at a tile server the installation controls and set
tileAttribution to whatever that source's license requires. Both are editable in the dashboard
edit form, each with a tooltip linking to the
Leaflet TileLayer documentation for the
{z}/{x}/{y} URL format. See docs/knowledge/project/webui-runtime-layouts.md for why a
production deployment should not rely on the OpenStreetMap default.
Example
import '@wincc-oa/wui-ui-elements/wui-map-layout/wui-map-layout.js';
import type { MapLayoutConfig } from '@wincc-oa/wui-ui-elements/model/layout-config.js';
const settings: MapLayoutConfig = {
lat: 48.2082,
lng: 16.3738,
zoom: 12
// tileUrl/tileAttribution omitted: falls back to OpenStreetMap.
};<wui-map-layout .settings="${settings}" .value="${value}"></wui-map-layout>Geometry fields
wui-map-layout uses LayoutItemMapGeometry, its own geometry shape for geographic placement:
| Field | Meaning on the map |
| ---------------- | ------------------------------------------------------------------------------------------ |
| lat | Latitude of the widget's geographic center (the marker is centered, not anchored top-left) |
| lng | Longitude of the widget's geographic center |
| zoom | Base zoom level the widget targets, driving render-mode switching (see below) |
| width/height | Pixel width/height of the widget at its base zoom, centered on lat/lng |
See docs/knowledge/project/webui-runtime-layouts.md for the render-mode-switching logic and
viewport-culling internals.
License
MIT
