feat(16.2-01): host plugin field type elements in the admin SPA

- loadFieldTypes fetches GET /field-types after sign-in, loads each script
  once and adds the styles as global links activateStyles never disables
- PluginField creates the type's element, sets the value property and the
  contract attributes, takes summer-change as the form value and never
  writes back a value the element just emitted
- rendererForField and isRegisteredField route any field with an element
  to PluginField in forms, previews and the save payload
- rebuilt modules/boardwalk/dist
This commit is contained in:
2026-10-10 11:10:41 +02:00
parent 717d46ec3d
commit 83fa2f7b2c
16 changed files with 471 additions and 30 deletions

View File

@@ -7,7 +7,10 @@
// every navigation to another controller or to a screen that is not a
// controller view, so plugin CSS never styles a view it was not written for.
// Only the router picks the enabled controller; a schema response that
// arrives after its view was left adds its links disabled.
// arrives after its view was left adds its links disabled. Stylesheets of
// plugin field types are global instead (GLOBAL_STYLE_OWNER): a field type's
// element can appear in any controller's form, so its links are never
// disabled on navigation.
// Files load only through script and link elements; nothing here fetches.
import type { ControllerAssets } from '../api/types'
import { runtime } from './runtime'
@@ -15,6 +18,9 @@ import { runtime } from './runtime'
/** Attribute naming the controller that owns a plugin stylesheet link. */
export const OWNER_ATTRIBUTE = 'data-summer-controller'
/** Owner of stylesheet links that stay enabled on every screen (field types). */
export const GLOBAL_STYLE_OWNER = '*'
const scripts = new Map<string, Promise<void>>()
// Links by controller and URL: two controllers of one plugin may share a
// file, and each keeps its own link so disabling one never hides the other.
@@ -90,7 +96,7 @@ export function loadStyles(controllerId: string, urls: readonly string[]): void
const link = document.createElement('link')
link.rel = 'stylesheet'
link.setAttribute(OWNER_ATTRIBUTE, controllerId)
link.disabled = controllerId !== active
link.disabled = !styleEnabled(controllerId)
link.href = url
styles.set(key, { owner: controllerId, link })
document.head.appendChild(link)
@@ -105,10 +111,23 @@ export function loadStyles(controllerId: string, urls: readonly string[]): void
export function activateStyles(controllerId: string): void {
active = controllerId
for (const { owner, link } of styles.values()) {
link.disabled = owner !== active
link.disabled = !styleEnabled(owner)
}
}
/** Whether links of this owner are enabled now: the active controller's and the global ones. */
function styleEnabled(owner: string): boolean {
return owner === GLOBAL_STYLE_OWNER || owner === active
}
/**
* Adds stylesheet links that stay enabled on every screen: the styles of
* plugin field types, whose elements may render in any controller's form.
*/
export function loadGlobalStyles(urls: readonly string[]): void {
loadStyles(GLOBAL_STYLE_OWNER, urls)
}
/**
* Adds the controller's stylesheet links and loads its scripts. The links
* are enabled only while the router shows that controller, so a response