feat: add custom widget flow workbench
This commit is contained in:
1 parent
c32a5317ca
commit
a6bdbbb4ea
259 files changed
+23875
-1293
No files matched your search
@@ -98,3 +98,28 @@ and focused references under `/api/custom-widgets/`.
|
||||
|
||||
Use [Custom Widget agent authoring](../custom-widgets/agent-authoring) for the lifecycle and
|
||||
[MCP](../mcp) when the client supports Model Context Protocol.
|
||||
|
||||
Workshop installation through `customWidget.workshopInstall` and the in-app catalog uses the same server path. Supply
|
||||
`expectedRevision` from `customWidget.workshopGet` to install only the package you reviewed. Source URL overrides and
|
||||
credentials are local; the installed definition retains its Workshop provenance.
|
||||
For native capabilities, pass local integration selections as `integrations: { "optionId": "integrationId" }`; portable
|
||||
defaults do not carry another Homarr instance's integration IDs.
|
||||
|
||||
When renaming a saved source through `customWidget.update` or `customWidget.previewCreate`, include
|
||||
`sourceRenames: { "previousId": "newId" }` to preserve its credentials. The server checks the original and resulting
|
||||
source bindings; changed origin, network scope, or authentication requires re-entered credentials. A rename moves
|
||||
encrypted credentials transactionally and does not copy them into the portable definition.
|
||||
|
||||
For a linked installation, `customWidget.workshopUpdateReview` returns the current and incoming definitions, source-binding
|
||||
changes, local-modification status, and review tokens. Pass its `expectedFingerprint`, `expectedState`,
|
||||
`upstreamFingerprint`, and `revision` unchanged to `customWidget.workshopUpdateInstall`. Both procedures require admin
|
||||
access and are exposed through MCP with an underscore replacing the dot. An intervening edit or upstream revision
|
||||
returns `CONFLICT`; fetch a fresh review instead of retrying the old update. Source/authentication changes and local
|
||||
customizations require a separate installation.
|
||||
|
||||
Shared widget content uses `customWidget.contentRead({ itemId })` and
|
||||
`customWidget.contentWrite({ itemId, name, expectedRevision, value })`. Reads require access to the board; writes require
|
||||
authentication and the declaration's `modify` or `full` board permission. Each value has an independent revision; `0`
|
||||
means the declared default has not been saved. A stale revision returns `CONFLICT`; read again before saving. Content is
|
||||
scoped to the placed item and its selected definition. Workbench previews keep content changes in memory and cannot
|
||||
persist them through these procedures. These procedures are not exposed as MCP tools.
|
||||
@@ -26,7 +26,7 @@ Equivalent HTTP resources are available under `/api/custom-widgets/` for authent
|
||||
## Authoring lifecycle
|
||||
|
||||
1. Load the skill and only the references needed for the design.
|
||||
2. Read the external API documentation and build the definition.
|
||||
2. Read the external API documentation or discover Homarr capabilities with `customWidget_nativeCapabilities`, then build the definition.
|
||||
3. Validate the JSX, then create a preview from the complete definition.
|
||||
4. Run every returned query and simulate every relevant action against the current preview revision.
|
||||
5. Inspect the preview and redacted journal. Revise the existing preview for JSX-only changes.
|
||||
@@ -34,7 +34,9 @@ Equivalent HTTP resources are available under `/api/custom-widgets/` for authent
|
||||
7. Configure deployment-specific source URLs and credentials, then preview the saved definition again.
|
||||
|
||||
Use `templateLines` for multiline JSX tool input. A preview must have current evidence for every query and action before
|
||||
it can be saved. Simulation counts as action evidence without performing the mutation.
|
||||
it can be saved. Simulation counts as action evidence without performing the mutation. V3 native queries and actions use
|
||||
`customWidget_nativeQuery` and `customWidget_nativeAction` with the owned preview session ID; their evidence is recorded
|
||||
under `native:<id>`. Native capabilities must also have current evidence before preview-based persistence.
|
||||
|
||||
If the agent does not have a source URL or credential, it can request a short-lived Homarr configuration URL. An
|
||||
authenticated administrator completes it in Homarr; the agent receives the status, never the plaintext credential.
|
||||
@@ -43,3 +45,10 @@ Agents can also search, inspect, install, and configure existing Workshop widget
|
||||
|
||||
See [MCP](/docs/management/mcp) for client configuration and [requests and security](./requests-and-security) for the
|
||||
runtime limits.
|
||||
|
||||
## Editing an open workbench
|
||||
|
||||
Homarr's built-in Assistant can use `workbench_read`, `workbench_patch`, and `workbench_preview` to change the visible draft.
|
||||
Read before patching, supply the returned draft ID and revision, and describe one coherent change. Credentials are excluded from these
|
||||
tools. The workbench validates the resulting definition and rejects stale patches; one Undo reverses an accepted patch.
|
||||
Browser draft tools require an open workbench and do not grant permission to save, publish, or run live actions.
|
||||
@@ -1,29 +1,67 @@
|
||||
# Create a Custom Widget
|
||||
|
||||
The workbench provides editors for general metadata, API sources, requests, widget options, JSX, and preview data. Raw
|
||||
JSON is available when direct editing is faster.
|
||||
The definition remains the source of truth in the workbench. Existing widgets open without converting or rewriting their
|
||||
JSX. Visual changes use targeted source edits; complex expressions remain editable in code.
|
||||
|
||||
## Workflow
|
||||
## Definition and layout
|
||||
|
||||
1. Configure each API source, network scope, authentication method, and credential.
|
||||
2. Add named queries and actions. Values can reference widget options or invocation parameters.
|
||||
3. Define widget options and their defaults.
|
||||
4. Write JSX using `data`, `status`, `options`, and temporary `inputs`.
|
||||
5. Run **Test and preview**, inspect the response data and redacted request journal, then save.
|
||||
A static widget needs only a JSX template: set `sources` and `requests` to `{}`. It makes no service request.
|
||||
|
||||
Changing the definition, options, or credentials invalidates the previous preview. Preview actions are simulated unless
|
||||
**Run actions against the configured API** is enabled for that preview session.
|
||||
HTTP requests run on the Homarr server. Native capabilities use Homarr's installed integrations and existing
|
||||
permissions. Embeds open in the viewer's browser and need a URL reachable from that browser.
|
||||
|
||||
Widget options support text, textarea, number, switch, select, multi-select, slider, date, time, color, icon, URL,
|
||||
duration, time zone, and JSON controls. Dynamic choices can come from a load query.
|
||||
Node positions and groups are stored separately from the runtime definition. Groups do not change execution. Source
|
||||
associations, data references, action invocations, and cache invalidations are distinct relationships; an invalidation edge
|
||||
does not create an execution dependency. Referenced objects cannot be removed until their dependants are resolved.
|
||||
|
||||
See the [safe JSX runtime](./custom-jsx), [component reference](./component-reference), and
|
||||
[request reference](./requests-and-security) for the authoring contract.
|
||||
## Visual bindings
|
||||
|
||||
## Create with another AI service
|
||||
Connecting a query, option, or action to the Widget opens a binding configuration. Queries can insert text, JSON, lists,
|
||||
or tables; request parameters can use options, temporary controls, or fixed values. Connecting options to a request changes
|
||||
its parameter bindings. These edits do not run queries or actions.
|
||||
|
||||
**Create or fix with any AI** copies a self-contained prompt for an ordinary ChatGPT or Claude conversation. The model
|
||||
must return one fenced `json` block containing the complete definition. Paste that block into the workbench, add
|
||||
credentials separately, and validate it against a real preview.
|
||||
Generated JSX includes ownership comments. The canvas can disconnect or replace unchanged generated bindings as one Undo
|
||||
operation. Editing a generated block or mixing it with authored references makes the relationship code-owned; resolve it
|
||||
in JSX to preserve your changes. Removing ownership comments also removes visual ownership, without changing rendering.
|
||||
|
||||
For an MCP-connected agent, use the [Homarr Custom Widget skill](./agent-authoring) instead.
|
||||
## Preview evidence
|
||||
|
||||
Moving nodes, zooming, and opening inspectors do not execute requests or invalidate the preview. Editing the definition,
|
||||
options, or credentials marks execution evidence stale. Valid JSX renders against the last data; invalid JSX leaves the
|
||||
last valid preview visible with a stale label. Queries run deliberately, not on every keystroke.
|
||||
|
||||
Preview actions simulate by default. Enabling **Run actions against the configured API** applies only to that preview
|
||||
session. Native actions also require declared confirmation and their normal integration permissions. Undo changes the draft;
|
||||
it never undoes or repeats a service action.
|
||||
|
||||
## Undo and draft recovery
|
||||
|
||||
Undo/Redo covers code, structured edits, renames, node movement, and Assistant patches. Typing is grouped and a drag or
|
||||
coherent Assistant change is one operation. The bounded history excludes credentials, responses, selection, and viewport
|
||||
movement. **Reset to saved** restores the saved definition and layout. Saving from an outdated workbench tab reports a
|
||||
conflict rather than overwriting a newer save.
|
||||
Temporary test parameters and unfinished request-body JSON keep local input Undo, so editing them does not undo an unrelated
|
||||
definition change. Credential fields also retain their own input history.
|
||||
|
||||
Optional browser draft recovery keeps a credential-free draft and editor layout. Recovery is local to the browser and
|
||||
requires a valid recoverable configuration. It is not a revision archive or a backup of service data.
|
||||
|
||||
## Assistant changes
|
||||
|
||||
The Assistant reads the visible draft and selection, edits through shared draft commands, and can run preview queries. A stale patch is
|
||||
rejected when the draft has changed. Saving, live service actions, and Workshop publication remain separate operations.
|
||||
|
||||
Form view retains **Create or fix with any AI** for external AI services. Configure credentials locally after importing
|
||||
the returned definition and validate the result.
|
||||
|
||||
## Advanced capabilities
|
||||
|
||||
Native capabilities, scoped CSS, reusable view fragments, typed preferences, and shared content use
|
||||
`homarr-custom-widget-v3`; ordinary HTTP and static widgets remain valid v2 definitions. Native input bindings must match
|
||||
the installed capability's schema. See the [runtime](./custom-jsx) for the expression language and extension contracts.
|
||||
|
||||
V3 adds an integration picker with optional `integrationKinds`. The integration list respects the
|
||||
current user's access. Portable exports clear local integration selections so installers choose their own services.
|
||||
|
||||
See the [runtime](./custom-jsx), [component reference](./component-reference),
|
||||
[native capabilities](./native-capabilities), and [request reference](./requests-and-security).
|
||||
@@ -7,7 +7,7 @@ Templates are JSX expressions interpreted inside a widget-local safety and rende
|
||||
- `options.name` — saved widget configuration
|
||||
- `inputs.name` — temporary values from declarative `bind`
|
||||
|
||||
The v2 manifest requires a `default` source and supports up to eight sources, 64 requests, and 64 options. Source IDs are
|
||||
Manifests support up to eight sources, 64 requests, and 64 options. A static widget can use empty `sources` and `requests`; every HTTP request must name an existing source. Source IDs are
|
||||
case-insensitively unique. Each request allows up to 32 static headers and 32 invalidation targets; a widget can contain
|
||||
up to 24 secrets, each up to 8,192 characters, and templates are limited to 50,000 characters. Validation reports the
|
||||
exact field when a limit is exceeded.
|
||||
@@ -41,7 +41,7 @@ before it reaches `data`, so treat it as untrusted input even when the source is
|
||||
</Stack>
|
||||
```
|
||||
|
||||
Bound values live only in React memory while the widget is mounted. They are not saved to the database, board options, exports, browser storage, or `localStorage`. Remounting resets them. Components can share a binding when their adapter types agree.
|
||||
Without an explicit v3 `persist` binding, bound values live only in React memory while the widget is mounted. They are not saved to the database, board options, exports, browser storage, or `localStorage`. Remounting resets them. Components can share a binding when their adapter types agree.
|
||||
|
||||
Bindable controls accept a scalar `resetKey`. When it changes, Homarr restores that control to its declared `defaultValue` or `defaultChecked` without running a request. This keeps dependent pagination correct while a manual `SubFetch` waits for the user to submit the new query.
|
||||
|
||||
@@ -74,3 +74,38 @@ than one variable quantifier are rejected.
|
||||
- `Icon`/`TablerIcon` resolves installed safe icons.
|
||||
|
||||
Always include responsive loading, empty, error, and success states. Runtime failures remain inside the widget tile.
|
||||
|
||||
## Version 3 capabilities
|
||||
|
||||
Use `$schema: "homarr-custom-widget-v3"` for `extensions`, integration options, scoped classes, and the components below. Version 2 definitions continue to render; opening them in the workbench does not upgrade their runtime schema. Editor positions are Homarr metadata and are excluded from the portable definition.
|
||||
|
||||
`extensions.stylesheet` accepts up to 30,000 characters. `className` references local classes; selectors and keyframe names are scoped per widget instance, including content opened through `WidgetModal` and `WidgetDrawer`. Use flat rules, `@media`, `@supports`, `@container`, and local `@keyframes`. Resource-loading CSS (`url()`, imports, fonts), global registration rules, and unresolved animation names are rejected. Theme CSS variables remain available. Inline style restrictions still apply.
|
||||
|
||||
Templates can read `theme.colorScheme`, `theme.primaryColor`, `theme.colors`, `theme.spacing`, `theme.radius`, and `theme.fontFamily`. `container.width` and `container.height` report the widget content size in CSS pixels, so responsive composition can follow its tile rather than the browser viewport.
|
||||
|
||||
Declare reusable JSX in `extensions.fragments` and render it with `<View name="metric" values={{ label: "CPU" }} />`. The fragment reads `props.label` and inherits the normal data bindings. Fragment names are literal, recursion is rejected, and all expansions share the interpreter's existing operation budget. Up to 32 fragments and 100,000 combined JSX characters are supported.
|
||||
|
||||
```json
|
||||
{
|
||||
"preferences": {
|
||||
"filter": { "type": "string", "defaultValue": "" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The fragment above belongs under `extensions`. A matching `<TextInput bind="filter" persist="filter" label="Filter" />` exposes the value through both `inputs.filter` and `preferences.filter`. Preferences are typed, scoped to the board item, and saved only in that browser; another account or device gets its own values. Preview preferences remain in memory. Exports include the declarations and defaults, never the user's saved values. Storage-disabled browsers retain an in-memory fallback. `resetKey` resets a persisted control to its declared preference default.
|
||||
|
||||
`WidgetModal` and `WidgetDrawer` require `title` and `triggerLabel`; Homarr owns their open/close behavior, focus handling, and portal boundary. They allow detailed views without authored callbacks.
|
||||
Supported selects, menus, tooltips, and popovers use Homarr-owned dropdown targets to escape tile clipping while preserving
|
||||
widget styles and the preview theme. Nested dropdowns stay within their parent overlay. Authored event handlers, refs, and
|
||||
portal targets remain blocked; Mantine's own trigger handlers work through the component adapters.
|
||||
|
||||
`AppEmbed` requires an absolute HTTP(S) `src` and an accessible `title`. Its URL is reached by the browser, whereas HTTP sources are reached by the Homarr server; a Docker service hostname usually cannot be used for both. The embedded service must permit framing. The iframe uses an isolated origin and restricts navigation; apps requiring same-origin storage or unrestricted browser capabilities need their normal external link instead. `allowForms` explicitly enables embedded form submission.
|
||||
|
||||
`extensions.native` maps local IDs to curated Homarr capabilities. Integration-backed entries reference a saved `integration` option through `integrationOption`; the server applies the existing integration and board permissions. `<NativeQuery nativeId="systems">{(systems, meta) => <Text>{systems.length}</Text>}</NativeQuery>` uses the same loading/error behavior as `SubFetch`. Use `trigger="manual"` for invocation parameters and `live` for a supported subscription. Automatic results populate `data` and `status` under their native ID; manual results remain local to the component.
|
||||
|
||||
Native action entries require `kind: "action"`, `trigger: "manual"`, a `modify` or `full` permission, and a `confirmation` object containing `title` and `message`. Invoke them through `NativeActionButton`. Preview actions use the existing simulation mode; declaring an action does not authorize a live mutation. Native capability IDs and HTTP request IDs must be distinct.
|
||||
|
||||
The [Beszel systems studio definition](/custom-widgets/beszel-systems-studio-v3.json) combines systems, configurable columns, a saved system selection, historical charts, and live detail views. Select a local Beszel integration after importing it. The example uses `preferences` directly for values needed before a control mounts; `inputs` appears only after its bound control mounts. Live failures keep the last received event and expose `meta.stale` alongside Retry.
|
||||
|
||||
Shared editable content uses a separate `extensions.content` declaration with the same types/defaults as preferences plus `permission: "modify"` or `"full"`. `<Textarea bind="note" content="note" />` edits a local draft. `<ContentSaveButton name="note">Save note</ContentSaveButton>` commits it to the board item; `<ContentResetButton name="note">Reload note</ContentResetButton>` discards the draft and reloads the current shared value. Templates read `content.note` and `contentStatus.note` (`loading`, `dirty`, `saving`, `error`). Saves use the last read revision, so a stale draft cannot silently replace another saved value. Preview saves stay in memory; exports include declarations/defaults but exclude saved board content.
|
||||
@@ -15,8 +15,9 @@ Custom Widgets may change during the v2 release cycle. Share feedback in the
|
||||
|
||||
:::
|
||||
|
||||
A **Custom Widget** is a validated `homarr-custom-widget-v2` definition containing API sources, queries and actions,
|
||||
optional settings, and a safe JSX template.
|
||||
A **Custom Widget** is a validated definition with a safe JSX template, optional settings, and optional HTTP sources,
|
||||
queries and actions. Existing definitions use `homarr-custom-widget-v2`; advanced composition and native capabilities use
|
||||
`homarr-custom-widget-v3`. Static widgets need no API source.
|
||||
|
||||
Homarr executes requests on the server and renders the template in a restricted runtime. Source credentials are
|
||||
encrypted separately and are not included in exports, prompts, browser data, or Workshop submissions.
|
||||
@@ -49,7 +50,7 @@ and simulate relevant actions before saving. Live preview actions are opt-in bec
|
||||
|
||||
## Import and Workshop
|
||||
|
||||
Use **Import from Workshop** while adding board content or import a v2 JSON definition directly. Review the source,
|
||||
Use **Import from Workshop** while adding board content or import a supported v2 or v3 JSON definition directly. Review the source,
|
||||
requested permissions, API hosts, and actions before installation, then configure deployment-specific URLs and
|
||||
credentials in Homarr.
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# Native capabilities
|
||||
|
||||
V3 Custom Widgets can call a curated set of Homarr capabilities through `NativeQuery` and `NativeActionButton`.
|
||||
These use the same integration procedures, caches, partial-failure responses, and permissions as their native counterparts.
|
||||
A definition declares each capability explicitly; JSX cannot call arbitrary tRPC procedures.
|
||||
|
||||
## Available capabilities
|
||||
|
||||
| Capability | Behavior | Required input |
|
||||
| ----------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| `beszel.systems` | Systems overview, status, CPU, memory, disk, network and other reported metrics | None |
|
||||
| `beszel.history` | Historical system and optional container statistics | `systemId`; optional `timePeriod`, `includeDocker` |
|
||||
| `beszel.alerts` | Alerts and optional history | Optional `includeHistory`, `maxHistoryItems` |
|
||||
| `beszel.live` | Initial short history or a live statistics stream | `systemId` |
|
||||
| `calendar.events` | Calendar events from supported integrations | `year`, `month`; optional `releaseType`, `showUnmonitored` |
|
||||
| `media.requests` | Media requests and partial integration errors | Optional `statuses`, `recentDays` |
|
||||
| `media.stats` | Request totals and users | None |
|
||||
| `media.respond` | Approve or decline a media request | `requestId`, `answer` |
|
||||
| `smartHome.state` | Home Assistant entity state | `entityId` |
|
||||
| `smartHome.details` | Entity state, attributes and timestamps | `entityId` |
|
||||
| `smartHome.switch` | Toggle an entity | `entityId` |
|
||||
| `smartHome.automation` | Trigger an automation | `automationId` |
|
||||
| `docker.containers` | Container inventory and resource use | Optional `endpointIds` |
|
||||
| `docker.start`, `docker.stop`, `docker.restart` | Control a container | `endpointId`, `containerId` |
|
||||
|
||||
Use `customWidget_nativeCapabilities` to discover the exact input schema for the installed Homarr version.
|
||||
Docker capabilities retain the existing administrator requirement and endpoint capability checks.
|
||||
|
||||
## Declare and select an integration
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "homarr-custom-widget-v3",
|
||||
"name": "My systems",
|
||||
"sources": {},
|
||||
"requests": {},
|
||||
"options": {
|
||||
"beszel": {
|
||||
"label": "Beszel integration",
|
||||
"control": "integration",
|
||||
"integrationKinds": ["beszel"],
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"native": {
|
||||
"systems": {
|
||||
"capability": "beszel.systems",
|
||||
"integrationOption": "beszel",
|
||||
"kind": "query",
|
||||
"trigger": "load",
|
||||
"permission": "view",
|
||||
"input": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"template": "<NativeQuery nativeId=\"systems\">{(services) => <Stack>{services.map((service) => <Stack key={service.integrationId}><Text fw={600}>{service.integrationName}</Text>{service.systems.map((system) => <Text key={system.id}>{system.name}</Text>)}</Stack>)}</Stack>}</NativeQuery>"
|
||||
}
|
||||
```
|
||||
|
||||
Choose a local integration in the workbench, during installation, or in board configuration. Portable exports clear the
|
||||
local selection. `integrationOption` reads saved configuration, not invocation parameters; a browser cannot replace the
|
||||
integration ID by sending a different runtime input. An integration still needs to be accessible to the viewer under
|
||||
Homarr's integration permissions. Placing a custom widget does not grant general integration access.
|
||||
|
||||
## Inputs, detail views and live data
|
||||
|
||||
A capability's `input` supports the same recursive `$option` and `$param` references as HTTP request bodies. Invocation
|
||||
parameters are primitive values. Use `trigger: "manual"` when an input depends on a selected row or a temporary input;
|
||||
load capabilities cannot depend on invocation parameters.
|
||||
|
||||
The workbench reads each installed capability's input schema and offers fixed values, saved-option bindings, and invocation
|
||||
parameters. Switching an input to a parameter also switches its trigger to manual. Complex nested inputs retain a JSON
|
||||
editor; scalar and list inputs do not require editing the extensions manifest.
|
||||
|
||||
Use `<NativeQuery nativeId="details" trigger="manual" params={{ systemId: inputs.systemId }}>` for a selected-system
|
||||
query. For a declared `beszel.live` capability, add `live` to receive events. Historical and live responses retain the
|
||||
underlying Beszel response shapes; a live event contains its event type and record. Compose those results with supported
|
||||
charts, tables, fragments, modals, or drawers. Live subscriptions reconnect periodically to renew authorization and bounded
|
||||
request capacity, and stop when their widget unmounts or a preview session expires.
|
||||
|
||||
## Actions and preview
|
||||
|
||||
Native actions require `kind: "action"`, `trigger: "manual"`, `permission: "modify"` or `"full"`, and a `confirmation`
|
||||
object containing `title` and `message`. Render them with `NativeActionButton`. The server checks both board permission and
|
||||
the native integration's interaction permission; Docker retains its administrator policy.
|
||||
|
||||
**Test and preview** runs declared load queries. Manual queries and actions are exercised through preview controls. Native
|
||||
actions simulate until live preview actions are explicitly enabled. The journal records native entries as `native:<id>`;
|
||||
preview-based creation requires current evidence for every declared HTTP and native capability.
|
||||
|
||||
Individual capability tests use the current preview session. Their parameter values and responses remain local to the
|
||||
editor and are not saved into the definition. Action tests in the capability inspector always simulate; they are disabled
|
||||
while the preview has live actions enabled. Use the rendered preview's confirmation flow to exercise live actions.
|
||||
|
||||
This is the initial native capability library. Other self-hosted services can use HTTP sources or browser embeds. Services
|
||||
without an accessible HTTP interface, supported embed, or curated Homarr capability need an external bridge. Custom Widgets
|
||||
do not run authored backend code, shell commands, imports, or arbitrary event callbacks.
|
||||
@@ -1,12 +1,18 @@
|
||||
# Configure requests and security
|
||||
|
||||
Most widgets use a single source named `default`; the manifest must contain it. Extra keyed sources are available for widgets
|
||||
that combine APIs. Source IDs are case-insensitively unique. Base URLs must use HTTP(S), contain no embedded credentials,
|
||||
HTTP widgets often use a source named `default`. Every request must reference an existing source; an omitted request source
|
||||
resolves to `default`. Static and native-only widgets can use empty `sources` and `requests`. Extra keyed sources let widgets
|
||||
combine APIs. Source IDs are case-insensitively unique. Base URLs must use HTTP(S), contain no embedded credentials,
|
||||
query string, or fragment, and sources can be `public`, `private`, or `loopback`. Authentication is `none`, `bearer`,
|
||||
`basic`, an API-key header, or an API-key query parameter. Credentials belong to the widget but remain outside its manifest.
|
||||
Source origins are fixed by the definition and source-setup flow; widget options and invocation parameters cannot redirect a
|
||||
request to a different origin.
|
||||
|
||||
Source URLs are fetched by the Homarr server. In a container, `127.0.0.1` refers to that container; Docker service names such
|
||||
as `sonarr` must resolve from Homarr. LAN addresses and loopback services are supported with the appropriate explicit scope.
|
||||
The workbench suggests a likely local scope from the hostname; server DNS and redirect checks remain authoritative.
|
||||
Browser navigation and app embeds use browser-reachable URLs instead.
|
||||
|
||||
## Named requests
|
||||
|
||||
Requests are keyed by ID. `source`, `kind`, `method`, `trigger`, `auth`, and `permission` have sensible defaults, so a load query can be as small as:
|
||||
|
||||
@@ -31,12 +31,37 @@ from Custom Widget and CSS submissions.
|
||||
- Install a Custom Widget from **Management → Custom Widgets → Import from Workshop**.
|
||||
- Import dashboard CSS from **Board settings → Custom CSS → Import from Workshop**.
|
||||
|
||||
Custom Widget credentials are configured in your Homarr instance and are not part of the Workshop submission.
|
||||
Custom Widget credentials stay in your Homarr instance. Ordinary installation does not load the workbench; **Remix in
|
||||
workbench** installs a separate local widget and opens its draft. Widgets without data sources need no URL or credentials.
|
||||
|
||||
Installation records the Workshop endpoint, submission, revision, and content fingerprint. A JSON file import has no
|
||||
Workshop identity, even when its name matches a submission. Editor layout and credentials are not part of the portable
|
||||
definition. Native integration selections are also local: exports clear their default IDs, and installation binds them
|
||||
to existing integrations in your instance. Leaving a selection empty defers setup to each board widget's configuration.
|
||||
|
||||
## Updates and rollback
|
||||
|
||||
The workbench's Workshop panel compares a linked installation with its latest revision. Applying an update preserves
|
||||
local service URLs, network scopes, and credentials. Changed source identities or authentication destinations require
|
||||
installing a separate copy. Locally modified definitions also require a separate copy; Homarr does not merge authored JSX.
|
||||
Compatible native integration selections are retained; new options and options whose allowed integration kinds change
|
||||
start unconfigured. The review also includes shared-content declarations and their write permissions. Stored board content
|
||||
and each user's preferences remain in Homarr and are not included in a submission.
|
||||
|
||||
Each update retains one previous installed package for rollback. Rollback restores the definition, preserves current
|
||||
service URLs and credentials, and works without contacting Workshop. It refuses changed source authentication bindings.
|
||||
Changing the configured Workshop endpoint disables upstream updates for installations linked to the previous endpoint.
|
||||
|
||||
## Publish content
|
||||
|
||||
Publish a saved Custom Widget from its action menu under **Management → Custom Widgets**. Custom CSS and manual
|
||||
submissions can be published from the Workshop website.
|
||||
Publish a saved Custom Widget from its action menu or workbench. The reviewed saved definition must still match when
|
||||
publication starts. Updating a linked submission requires its Workshop author account and preserves its identity,
|
||||
screenshots, votes, and comments. A remix creates a new submission with attribution. Publishing succeeds independently of
|
||||
linking the resulting submission back to your local widget; a linking failure must not be retried as a new publication.
|
||||
|
||||
Custom JSX v2 remains supported. Scoped styles, fragments, preferences, shared content, and native capabilities use
|
||||
`homarr-custom-widget-v3`; Workshop clients and the Workshop service must support that schema before publishing or
|
||||
updating such submissions. Custom CSS and manual submissions can be published from the Workshop website.
|
||||
|
||||
Sign in to the Workshop and open **Yours** to update or delete submissions and review reports. Signed-in users can vote,
|
||||
comment, and report content; Workshop moderators can remove content. Workshop moderation is separate from administration
|
||||
|
||||
@@ -50,7 +50,7 @@ const placeholders: Record<SubmissionType, string> = {
|
||||
customCss: ".grid-stack-item-content {\n border-radius: 16px;\n}",
|
||||
};
|
||||
const contentLabels: Record<SubmissionType, string> = {
|
||||
customWidget: "Widget JSON (homarr-custom-widget-v2)",
|
||||
customWidget: "Widget JSON (homarr-custom-widget-v2 or v3)",
|
||||
customCss: "Custom CSS",
|
||||
};
|
||||
|
||||
@@ -314,7 +314,7 @@ const typeCards: { type: SubmissionType; icon: typeof IconBraces; label: string;
|
||||
type: "customWidget",
|
||||
icon: IconBraces,
|
||||
label: "Custom Widget",
|
||||
desc: "A JSON-based widget using the homarr-custom-widget-v2 schema",
|
||||
desc: "A JSON-based widget using the homarr-custom-widget-v2 or v3 schema",
|
||||
},
|
||||
{
|
||||
type: "customCss",
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"$schema": "homarr-custom-widget-v3",
|
||||
"name": "Beszel systems studio",
|
||||
"description": "Systems overview, configurable columns, selected-system history and live details using your existing Beszel integration. No HTTP source or copied API token required.",
|
||||
"sources": {},
|
||||
"requests": {},
|
||||
"options": {
|
||||
"beszel": {
|
||||
"label": "Beszel integration",
|
||||
"control": "integration",
|
||||
"integrationKinds": ["beszel"],
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"stylesheet": ".metric { padding: 14px; border-radius: 12px; background: var(--mantine-color-default-hover); } .system-table { font-variant-numeric: tabular-nums; }",
|
||||
"preferences": {
|
||||
"system": {
|
||||
"type": "string",
|
||||
"defaultValue": ""
|
||||
},
|
||||
"period": {
|
||||
"type": "string",
|
||||
"defaultValue": "1h"
|
||||
},
|
||||
"columns": {
|
||||
"type": "string[]",
|
||||
"defaultValue": ["cpu", "memory", "disk"]
|
||||
}
|
||||
},
|
||||
"fragments": {
|
||||
"metric": "<Stack className=\"metric\" gap={2}><Text size=\"xs\" c=\"dimmed\">{props.label}</Text><Text fw={700} size=\"xl\">{Number(props.value ?? 0).toFixed(1)}{props.suffix ?? \"%\"}</Text></Stack>",
|
||||
"containers": "<Stack gap=\"xs\">\n <Text size=\"xs\" c=\"dimmed\">{props.items.length} containers \u00b7 {Date.toLocaleTimeString(props.updatedAt)}</Text>\n {!props.items.length && <Text c=\"dimmed\">No containers reported.</Text>}\n {props.items.length > 0 && <ScrollArea><Table striped highlightOnHover>\n <Table.Thead><Table.Tr><Table.Th>Container</Table.Th><Table.Th>CPU</Table.Th><Table.Th>Memory</Table.Th><Table.Th>Sent</Table.Th><Table.Th>Received</Table.Th></Table.Tr></Table.Thead>\n <Table.Tbody>{props.items.map((row) => <Table.Tr key={row.n}>\n <Table.Td><Text fw={500}>{row.n}</Text></Table.Td>\n <Table.Td>{Number(row.c ?? 0).toFixed(1)}%</Table.Td>\n <Table.Td>{Number(row.m ?? 0).toFixed(1)} MiB</Table.Td>\n <Table.Td>{Number((row.b?.[0] ?? (row.ns ?? 0) * 1048576) / 1024).toFixed(1)} KiB/s</Table.Td>\n <Table.Td>{Number((row.b?.[1] ?? (row.nr ?? 0) * 1048576) / 1024).toFixed(1)} KiB/s</Table.Td>\n </Table.Tr>)}</Table.Tbody>\n </Table></ScrollArea>}\n</Stack>"
|
||||
},
|
||||
"native": {
|
||||
"systems": {
|
||||
"capability": "beszel.systems",
|
||||
"integrationOption": "beszel",
|
||||
"kind": "query",
|
||||
"permission": "view",
|
||||
"trigger": "load",
|
||||
"input": {}
|
||||
},
|
||||
"history": {
|
||||
"capability": "beszel.history",
|
||||
"integrationOption": "beszel",
|
||||
"kind": "query",
|
||||
"permission": "view",
|
||||
"trigger": "manual",
|
||||
"input": {
|
||||
"systemId": {
|
||||
"$param": "systemId"
|
||||
},
|
||||
"timePeriod": {
|
||||
"$param": "period"
|
||||
},
|
||||
"includeDocker": true
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"capability": "beszel.live",
|
||||
"integrationOption": "beszel",
|
||||
"kind": "query",
|
||||
"permission": "view",
|
||||
"trigger": "manual",
|
||||
"input": {
|
||||
"systemId": {
|
||||
"$param": "systemId"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"template": "<NativeQuery nativeId=\"systems\" refreshInterval={30}>{(services) => (\n <Stack p=\"md\" gap=\"md\">\n <Group justify=\"space-between\"><Title order={3}>Systems</Title><Badge variant=\"light\">{services.flatMap((service) => service.systems).length} hosts</Badge></Group>\n <Group grow align=\"flex-start\">\n <TextInput label=\"Filter systems\" bind=\"filter\" />\n <Select label=\"Selected system\" bind=\"system\" persist=\"system\" searchable clearable data={services.flatMap((service) => service.systems.map((system) => ({ value: system.id, label: system.name })))} />\n <MultiSelect label=\"Visible columns\" bind=\"columns\" persist=\"columns\" data={[{value:\"cpu\",label:\"CPU\"},{value:\"memory\",label:\"Memory\"},{value:\"disk\",label:\"Disk\"}]} />\n </Group>\n {services.map((service) => <Stack key={service.integrationId} gap=\"xs\">\n <Group justify=\"space-between\"><Text fw={600}>{service.integrationName}</Text><Text size=\"xs\" c=\"dimmed\">Updated {Date.toLocaleTimeString(service.updatedAt)}</Text></Group>\n {service.error && <Alert color=\"yellow\" title=\"Integration unavailable\">{service.error}</Alert>}\n <ScrollArea><Table className=\"system-table\" striped highlightOnHover>\n <Table.Thead><Table.Tr><Table.Th>System</Table.Th><Table.Th>Status</Table.Th>{preferences.columns.includes(\"cpu\") && <Table.Th>CPU</Table.Th>}{preferences.columns.includes(\"memory\") && <Table.Th>Memory</Table.Th>}{preferences.columns.includes(\"disk\") && <Table.Th>Disk</Table.Th>}</Table.Tr></Table.Thead>\n <Table.Tbody>{service.systems.filter((system) => !inputs.filter || system.name.toLowerCase().includes(inputs.filter.toLowerCase())).map((system) => <Table.Tr key={system.id}>\n <Table.Td><Text fw={600}>{system.name}</Text><Text size=\"xs\" c=\"dimmed\">{system.hostname}</Text></Table.Td>\n <Table.Td><Badge variant=\"light\">{system.status}</Badge></Table.Td>\n {preferences.columns.includes(\"cpu\") && <Table.Td>{Number(system.cpu ?? 0).toFixed(1)}%</Table.Td>}\n {preferences.columns.includes(\"memory\") && <Table.Td>{Number(system.memory ?? 0).toFixed(1)}%</Table.Td>}\n {preferences.columns.includes(\"disk\") && <Table.Td>{Number(system.disk ?? 0).toFixed(1)}%</Table.Td>}\n </Table.Tr>)}</Table.Tbody>\n </Table></ScrollArea>\n {!service.systems.length && !service.error && <Text c=\"dimmed\">No systems reported.</Text>}\n </Stack>)}\n {!preferences.system && <Text size=\"sm\" c=\"dimmed\">Select a system to open historical and live details.</Text>}\n {preferences.system && <WidgetDrawer title=\"Selected system\" triggerLabel=\"Open system details\" size=\"xl\">\n <Stack gap=\"md\">\n {services.flatMap((service) => service.systems).filter((system) => system.id === preferences.system).map((system) => <Stack key={system.id}>\n <Title order={3}>{system.name}</Title><Text size=\"sm\" c=\"dimmed\">{system.osName} \u00b7 {system.cpuModel} \u00b7 {system.cores} cores</Text>\n <SimpleGrid cols={{base:1,sm:3}}><View name=\"metric\" values={{label:\"CPU\",value:system.cpu}}/><View name=\"metric\" values={{label:\"Memory\",value:system.memory}}/><View name=\"metric\" values={{label:\"Disk\",value:system.disk}}/></SimpleGrid>\n </Stack>)}\n <Select label=\"History period\" bind=\"period\" persist=\"period\" data={[{value:\"1h\",label:\"Last hour\"},{value:\"12h\",label:\"12 hours\"},{value:\"24h\",label:\"24 hours\"},{value:\"1w\",label:\"Week\"},{value:\"30d\",label:\"30 days\"}]} />\n <NativeQuery nativeId=\"history\" params={{systemId:preferences.system,period:preferences.period}}>{(history) => <Stack>\n <Text fw={600}>Resource history</Text>\n {!history.systemStats.length && <Text c=\"dimmed\">No history in this period.</Text>}\n {history.systemStats.length > 0 && <LineChart h={230} data={history.systemStats.slice().sort((a,b) => a.created.localeCompare(b.created)).map((point) => ({time:Date.toLocaleTimeString(point.created),cpu:point.stats.cpu,memory:point.stats.mp,disk:point.stats.dp}))} dataKey=\"time\" series={[{name:\"cpu\",color:\"blue.6\"},{name:\"memory\",color:\"violet.6\"},{name:\"disk\",color:\"teal.6\"}]} withLegend unit=\"%\" />}\n <Text fw={600}>Latest container statistics</Text>\n {!history.containerStats.length && <Text c=\"dimmed\">No container history in this period.</Text>}\n {history.containerStats.slice().sort((a,b) => b.created.localeCompare(a.created)).slice(0,1).map((record) => <View key={record.id} name=\"containers\" values={{items:record.stats,updatedAt:record.created}} />)}\n </Stack>}</NativeQuery>\n <NativeQuery nativeId=\"live\" live params={{systemId:preferences.system}}>{(event,meta) => <Stack gap=\"xs\">\n <Group><Text fw={600}>Live event</Text>{meta.stale && <Badge color=\"yellow\">Last received data</Badge>}</Group>\n Line truncated
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"schemaVersion":1,"mantineVersion":"9.6.0","customWidgetVersion":"2.0.0","types":["\"\" | \"anonymous\" | \"use-credentials\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"auto\" | \"fixed\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"auto\" | \"all\" | \"fill\" | \"stroke\" | \"painted\" | \"visible\" | \"visibleFill\" | \"visiblePainted\" | \"visibleStroke\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"block\" | \"inline\" | \"both\" | \"horizontal\" | \"vertical\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"contain\" | \"cover\" | \"fill\" | \"scale-down\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"wrap\" | \"nowrap\" | \"wrap-reverse\"","\"12h\" | \"24h\"","\"activity\" | \"display-none\"","\"all\" | \"parent\" | \"child\"","\"all\" | \"segment\"","\"always\" | \"hover\" | \"not-hover\" | \"never\"","\"area\" | \"radial\" | \"scatter\"","\"auto\" | \"scroll\" | \"always\" | \"hover\" | \"never\"","\"auto-fit\" | \"auto-fill\"","\"block\" | \"grid\"","\"both\" | \"rightSection\" | \"clear\"","\"bottom\" | \"top\"","\"bottom\" | \"top\" | \"baseline\" | \"middle\"","\"bottom\" | \"top\" | \"middle\"","\"bottom-end\" | \"bottom-start\" | \"top-end\" | \"top-start\" | \"bottom-center\" | \"top-center\" | \"middle-center\" | \"middle-end\" | \"middle-start\"","\"button\" | \"submit\" | \"reset\"","\"center\" | \"bottom\"","\"center\" | \"left\" | \"right\"","\"center\" | \"left\" | \"right\" | \"justify\" | \"char\"","\"center\" | \"side\" | \"merge\"","\"checkbox\" | \"radio\"","\"dashed\" | \"dotted\" | \"solid\"","\"default\" | \"gradient\"","\"default\" | \"responsive\"","\"default\" | \"stacked\" | \"percent\" | \"split\" | \"stream\"","\"default\" | \"stacked\" | \"percent\" | \"waterfall\"","\"div\" | \"label\"","\"end\" | \"start\"","\"filled\" | \"light\"","\"fixed\" | \"absolute\"","\"hex\" | \"hexa\" | \"rgba\" | \"rgb\" | \"hsl\" | \"hsla\"","\"horizontal\" | \"vertical\"","\"hover\" | \"click\"","\"hover\" | \"click\" | \"click-hover\"","\"inherit\" | \"auto\" | \"baseline\" | \"before-edge\" | \"text-before-edge\" | \"middle\" | \"central\" | \"after-edge\" | \"text-after-edge\" | \"ideographic\" | \"alphabetic\" | \"hanging\" | \"mathematical\"","\"inherit\" | \"auto\" | \"sRGB\" | \"linearRGB\"","\"inherit\" | \"auto\" | \"text-before-edge\" | \"middle\" | \"central\" | \"text-after-edge\" | \"ideographic\" | \"alphabetic\" | \"hanging\" | \"mathematical\" | \"use-script\" | \"no-change\" | \"reset-size\"","\"inherit\" | \"end\" | \"start\" | \"middle\"","\"inherit\" | \"nonzero\" | \"evenodd\"","\"inherit\" | \"round\" | \"butt\" | \"square\"","\"inherit\" | \"round\" | \"miter\" | \"bevel\"","\"initial\" | \"medial\" | \"terminal\" | \"isolated\"","\"inside\" | \"outside\"","\"left\" | \"right\"","\"left\" | \"right\" | \"bottom\" | \"top\"","\"left\" | \"right\" | \"bottom\" | \"top\" | \"left-end\" | \"left-start\" | \"right-end\" | \"right-start\" | \"bottom-end\" | \"bottom-start\" | \"top-end\" | \"top-start\"","\"left\" | \"right\" | \"inside\"","\"link\" | \"none\" | \"copy\" | \"execute\" | \"move\" | \"popup\"","\"media\" | \"container\"","\"month\" | \"year\" | \"decade\"","\"native\" | \"scrollarea\"","\"none\" | \"all\" | \"rows\" | \"groups\" | \"columns\"","\"none\" | \"all\" | \"with-indicators\"","\"none\" | \"ascending\" | \"descending\" | \"other\"","\"none\" | \"inline\" | \"list\" | \"both\"","\"none\" | \"strict\" | \"blur\"","\"none\" | \"sum\"","\"none\" | \"thousand\" | \"lakh\" | \"wan\"","\"none\" | \"x\" | \"y\" | \"xy\"","\"number\" | RegExp | \"alphanumeric\"","\"off\" | \"assertive\" | \"polite\"","\"off\" | \"on\"","\"ordered\" | \"unordered\"","\"right\" | \"bottom\"","\"right-to-left\" | \"left-to-right\"","\"right-to-left\" | \"left-to-right\" | \"top-to-bottom\" | \"bottom-to-top\"","\"search\" | \"enter\" | \"done\" | \"go\" | \"next\" | \"previous\" | \"send\"","\"search\" | \"text\" | \"none\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\"","\"step\" | \"bump\" | \"linear\" | \"natural\" | \"monotone\" | \"stepBefore\" | \"stepAfter\"","\"stepInactive\" | \"stepProgress\" | \"stepCompleted\"","\"sum\" | \"replace\"","\"td\" | \"th\"","\"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"all\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\"","\"text\" | \"tel\" | \"password\"","\"time\" | \"duration\"","\"up\" | \"down\"","\"value\" | \"name\" | \"percent\"","\"viewport\"","\"wrap\" | \"nowrap\" | \"balance\" | \"pretty\" | \"stable\"","\"x\" | \"y\"","\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\"","\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | (string & {})","\"yes\" | \"no\"","'Text' | 'Line truncated
|
||||
{"schemaVersion":1,"mantineVersion":"9.6.0","customWidgetVersion":"3.0.0","types":["\"\" | \"anonymous\" | \"use-credentials\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"auto\" | \"fixed\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"auto\" | \"all\" | \"fill\" | \"stroke\" | \"painted\" | \"visible\" | \"visibleFill\" | \"visiblePainted\" | \"visibleStroke\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"block\" | \"inline\" | \"both\" | \"horizontal\" | \"vertical\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"none\" | \"contain\" | \"cover\" | \"fill\" | \"scale-down\"","\"-moz-initial\" | \"inherit\" | \"initial\" | \"revert\" | \"revert-layer\" | \"unset\" | \"wrap\" | \"nowrap\" | \"wrap-reverse\"","\"12h\" | \"24h\"","\"activity\" | \"display-none\"","\"all\" | \"parent\" | \"child\"","\"all\" | \"segment\"","\"always\" | \"hover\" | \"not-hover\" | \"never\"","\"area\" | \"radial\" | \"scatter\"","\"auto\" | \"scroll\" | \"always\" | \"hover\" | \"never\"","\"auto-fit\" | \"auto-fill\"","\"block\" | \"grid\"","\"both\" | \"rightSection\" | \"clear\"","\"bottom\" | \"top\"","\"bottom\" | \"top\" | \"baseline\" | \"middle\"","\"bottom\" | \"top\" | \"middle\"","\"bottom-end\" | \"bottom-start\" | \"top-end\" | \"top-start\" | \"bottom-center\" | \"top-center\" | \"middle-center\" | \"middle-end\" | \"middle-start\"","\"button\" | \"submit\" | \"reset\"","\"center\" | \"bottom\"","\"center\" | \"left\" | \"right\"","\"center\" | \"left\" | \"right\" | \"justify\" | \"char\"","\"center\" | \"side\" | \"merge\"","\"checkbox\" | \"radio\"","\"dashed\" | \"dotted\" | \"solid\"","\"default\" | \"gradient\"","\"default\" | \"responsive\"","\"default\" | \"stacked\" | \"percent\" | \"split\" | \"stream\"","\"default\" | \"stacked\" | \"percent\" | \"waterfall\"","\"div\" | \"label\"","\"end\" | \"start\"","\"filled\" | \"light\"","\"fixed\" | \"absolute\"","\"hex\" | \"hexa\" | \"rgba\" | \"rgb\" | \"hsl\" | \"hsla\"","\"horizontal\" | \"vertical\"","\"hover\" | \"click\"","\"hover\" | \"click\" | \"click-hover\"","\"inherit\" | \"auto\" | \"baseline\" | \"before-edge\" | \"text-before-edge\" | \"middle\" | \"central\" | \"after-edge\" | \"text-after-edge\" | \"ideographic\" | \"alphabetic\" | \"hanging\" | \"mathematical\"","\"inherit\" | \"auto\" | \"sRGB\" | \"linearRGB\"","\"inherit\" | \"auto\" | \"text-before-edge\" | \"middle\" | \"central\" | \"text-after-edge\" | \"ideographic\" | \"alphabetic\" | \"hanging\" | \"mathematical\" | \"use-script\" | \"no-change\" | \"reset-size\"","\"inherit\" | \"end\" | \"start\" | \"middle\"","\"inherit\" | \"nonzero\" | \"evenodd\"","\"inherit\" | \"round\" | \"butt\" | \"square\"","\"inherit\" | \"round\" | \"miter\" | \"bevel\"","\"initial\" | \"medial\" | \"terminal\" | \"isolated\"","\"inside\" | \"outside\"","\"left\" | \"right\"","\"left\" | \"right\" | \"bottom\" | \"top\"","\"left\" | \"right\" | \"bottom\" | \"top\" | \"left-end\" | \"left-start\" | \"right-end\" | \"right-start\" | \"bottom-end\" | \"bottom-start\" | \"top-end\" | \"top-start\"","\"left\" | \"right\" | \"inside\"","\"link\" | \"none\" | \"copy\" | \"execute\" | \"move\" | \"popup\"","\"media\" | \"container\"","\"month\" | \"year\" | \"decade\"","\"native\" | \"scrollarea\"","\"none\" | \"all\" | \"rows\" | \"groups\" | \"columns\"","\"none\" | \"all\" | \"with-indicators\"","\"none\" | \"ascending\" | \"descending\" | \"other\"","\"none\" | \"inline\" | \"list\" | \"both\"","\"none\" | \"strict\" | \"blur\"","\"none\" | \"sum\"","\"none\" | \"thousand\" | \"lakh\" | \"wan\"","\"none\" | \"x\" | \"y\" | \"xy\"","\"number\" | RegExp | \"alphanumeric\"","\"off\" | \"assertive\" | \"polite\"","\"off\" | \"on\"","\"ordered\" | \"unordered\"","\"right\" | \"bottom\"","\"right-to-left\" | \"left-to-right\"","\"right-to-left\" | \"left-to-right\" | \"top-to-bottom\" | \"bottom-to-top\"","\"search\" | \"enter\" | \"done\" | \"go\" | \"next\" | \"previous\" | \"send\"","\"search\" | \"text\" | \"none\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\"","\"step\" | \"bump\" | \"linear\" | \"natural\" | \"monotone\" | \"stepBefore\" | \"stepAfter\"","\"stepInactive\" | \"stepProgress\" | \"stepCompleted\"","\"sum\" | \"replace\"","\"td\" | \"th\"","\"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"all\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\"","\"text\" | \"tel\" | \"password\"","\"time\" | \"duration\"","\"up\" | \"down\"","\"value\" | \"name\" | \"percent\"","\"viewport\"","\"wrap\" | \"nowrap\" | \"balance\" | \"pretty\" | \"stable\"","\"x\" | \"y\"","\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\"","\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | (string & {})","\"yes\" | \"no\"","'Text' | 'Line truncated
|
||||
Reference in new issue
Block a user