Guide and reference
Item player API
Attributes, properties, methods, events, authoring and backend contracts.
From the pie-players documentation · View source(opens in new tab)
<pie-item-player> renders one PIE item: it loads the PIE elements the item
names, renders the item's markup with them, and reports the learner's session to
the host. One element covers delivery, evaluation and authoring through its
mode, and loads elements by one of three strategies (iife, esm,
preloaded). PIE stands for Portable Interactions and Elements. This README is
the element's API reference, for hosts that render items;
Getting started renders a first item, and the
architecture page covers the session
pipeline.
The package replaces @pie-framework/pie-player-components, with one element
in place of its <pie-player> and <pie-author>
(migration guide).
Install
npm install @pie-players/pie-item-player
# or
bun add @pie-players/pie-item-player
import "@pie-players/pie-item-player";
The import registers <pie-item-player>. The root entry imports no bare
specifier, so a page without a build step loads it from an npm CDN as a module
script (Loading from a CDN). Pin the
version exactly:
<script type="module" src="https://cdn.jsdelivr.net/npm/@pie-players/pie-item-player@x.y.z/dist/pie-item-player.js"></script>
The package is browser-only: it defines a DOM custom element and does not load under Node.js. The Node-safe entry points are listed in Packages and entry points.
Quick start
<pie-item-player id="player"></pie-item-player>
<script type="module">
const player = document.getElementById("player");
player.config = {
elements: { "multiple-choice": "@pie-element/multiple-choice@14.0.3" },
models: [
{
id: "q1",
element: "multiple-choice",
prompt: "Which city is the capital of France?",
choiceMode: "radio",
choices: [
{ label: "Paris", value: "paris", correct: true },
{ label: "London", value: "london" },
],
},
],
markup: '<multiple-choice id="q1"></multiple-choice>',
};
player.env = { mode: "gather", role: "student" };
player.session = { id: "session-1", data: [] };
player.addEventListener("session-changed", (event) => {
if (event.detail.session) console.log(event.detail.session);
});
</script>
config, env and session are properties; set as attributes, they take JSON.
Content styles
Authored content relies on shared classes that belong to no single component:
passage markup (.numbered-paragraph, .p-number, div.passage-title /
-subtitle / -author), the legacy kds-* content classes, and the
pie-answer-eliminator-* / pie-answer-masked-* families. They live in
@pie-players/pie-theme/components.css.
The player installs that stylesheet itself, so importing the element is all a
host needs, CDN hosts included. The stylesheet is bundled into the player as
text and installed once per document at import, before any instance renders. It
is prepended to <head>, so host CSS that comes later wins at equal
specificity. Installation is idempotent: several player packages, or several
copies of one, yield a single copy.
Content rules apply inside a [data-pie-content] element only. The player sets
the attribute on the root it renders into, so the stylesheet's bare h1–h6,
table, th, .table and .center selectors do not restyle the page around
it. Each selector is wrapped in :where(), which keeps its specificity. A host
that renders authored markup itself, outside a player, puts data-pie-content on
that container to give it the same styles.
Rules whose selector requires a kds-* or Kds* class, MathJax output
(mjx-*, TEX-*) or a legacy content class such as .frac or .noprint stay
document-wide. Elements portal menus and popovers holding authored markup to
<body>, outside the player, and an inline-dropdown's choices keep their
kds-fraction and MathJax glyph fixes there.
Upgrading from a manual import
An app that already imports @pie-players/pie-theme/components.css, scoped or
not, keeps working unchanged. The player detects a host copy by its
--pie-content-styles sentinel: when one is present it installs nothing, and
when one arrives after the player installed its own, the player removes its
copy. The host's copy is then the only one in effect, in the position the host
chose.
Taking ownership of the stylesheet
The sentinel check covers any copy the page can read. A cross-origin <link>
cannot be read, so a host loading the stylesheet that way declares ownership on
the root element before the player script runs:
<html data-pie-content-styles="host"></html>
import "@pie-players/pie-theme/components.css"; // owned by the host
The player then installs nothing. If no content stylesheet turns out to be
present, it logs a one-time console.warn naming the missing import. Presence is
detected through --pie-content-styles, a sentinel components.css declares
with no themeable value: it is only ever checked for being non-empty, so do not
style with it. A host that owns the stylesheet declares @pie-players/pie-theme
in its own package.json, because the player inlines its copy at build time and
does not install the package.
This stylesheet holds only the shared content styles. The --pie-* tokens, the
<pie-theme> host element, and color-scheme and font-size theming are in
@pie-players/pie-theme. All of it is optional for correct
rendering, since every var(--pie-*) in components.css has a fallback.
Custom elements
pie-item-player- Export:
@pie-players/pie-item-player - Description: the player element.
- Export:
pie-item-player-session-debugger- Export:
@pie-players/pie-item-player/components/item-session-debugger-element - Description: a floating, draggable panel showing the live session, the
environment and the controller-filtered models. Set its
hostedto the player's: the panel runs no controller over a hosted player's models. The entry installs the content stylesheet itself.
- Export:
definePieItemPlayer(tagName?) from the root entry registers the player under
another tag. An already-registered tag is left alone, so a second copy of the
package loading into the same document leaves the first copy rendering every
item.
Attributes
config:Object, defaultnull. Item config withelements,modelsandmarkupfields.session:Object, default{ id: "", data: [] }. The session container, kept live as the player writes into it; assigning a new value applies it (session management).env:Object, default{ mode: "gather", role: "student" }.env.modeisgather(the learner responds),view(read-only) orevaluate(scored feedback);env.roleisstudentorinstructor.env.locale, when the delivery knows it, names the language the item's content is written in: the player stamps it, canonicalized with its direction, on the content root, and asserts nothing when it is absent — the interfacelocaleattribute is the chrome, this is the content. Neither is a security boundary (delivery integrity).strategy:String, default"iife". Loading strategy:"iife","esm"or"preloaded"(loading strategies).mode:String, default"view"."view"loads delivery elements."author"loads each element's configure element (<tag>-config), passes it the model and the resolvedconfiguration, emitsmodel-loadedandmodel-updated, and enablesvalidateModels()and the media hooks.authoring-backend:String, default"demo"."demo"installs demo media handlers;"required"requires the host to supply all four.hosted:Boolean, defaultfalse, ortruewhenbackend.deliveryis enabled. A hosted player renders server-processed models and runs no element controller in the browser; underiifeit loads theplayer.jsbundle.add-correct-response:Boolean, defaultfalse. Populate correct responses into the session.show-bottom-border:Boolean, defaultfalse. Add a bottom border in evaluate mode.render-stimulus:Boolean, defaulttrue. Render the item's passage (stimulus);falserenders the item alone.passage-layout:String, default"vertical"."vertical"stacks the passage above the item."splitpane"puts the passage beside the item, and stacks them below a player width of 768px.allowed-resize:Boolean, defaultfalse. Underpassage-layout="splitpane", a keyboard-operable divider between passage and item resizes the split; otherwise the passage container resizes horizontally.autoplay-audio-enabled:Boolean, default unset, which leaves each model'sautoplayAudioEnabledas it is. Set, it writesautoplayAudioEnabledonto every top-level model in the config, overwriting or adding the field, inviewmode only. Inside a<pie-item-scope>the toolkit'sautoplayAudiodecision outranks it: a hold writesautoplayAudioHeld: trueonto every model and each part declaringautoplayAudioEnabled, and a grant writesautoplayAudioEnabled: truethere (Item autoplay). A change after load commits pending sessions and re-renders the item. Inviewmode the player removesautoplayattributes from the item and passage markup,trust-markupandsanitizeMarkupincluded, so this setting is the only start for the item's audio.idle-save-delay-ms:Number, default unset, which leaves each element its own delay. How long the learner has to pause before a response editor commits without losing focus;0commits on blur only. Elements read it fromsessionCommitContext(Idle commit).base-heading-level:Number(1–6), default unset. The level of the item's visually hidden heading, which PIE elements emit; authored headings nest one level below it. Reflected, because elements read it off the player.include-sr-heading:Boolean, defaulttrue. Emit the item's visually hidden heading. Reflected.locale:String, default""(rendersen-US). BCP 47 interface locale for the player's own UI. The toolkit and toolbar inside the player stamp it on their own roots; the content root asserts nolang/dir, so unnamed content keeps the browser voice for read-aloud. The item's content language is separate: content that names its ownlang/dirkeeps it. The player answersinterfaceLocaleContext(@pie-players/pie-context) from it for the elements inside, as{ locale, direction }.i18n:I18nProvider, optional. A provider the host composed — the generated locale tier, host messages, or the host's own — used in place of the one the player builds.localestill names the locale, and a supplied provider is driven only by it (Interface i18n). A catalog that fails to load dispatches a recoverableframework-errorevent on the player (detail.kindisi18n-locale-load); the player renders through the English fallback chain, and failed loads are not cached, so re-settinglocaleretries.debug:String, default"". Any value other than"false"(any case),"0"or the empty string turns on the player's debug logging. The player writes the result towindow.PIE_DEBUG, the flag the page's other PIE loggers read, so the default writesfalse(logging).custom-class-name:String, default"". CSS scope class applied to the player container.container-class:String, default"". Extra class on the inner item container.passage-container-class:String, default"". Extra class on the passage container;itemConfig.resources.passageContainerClassoverrides it.external-style-urls:String, default"". Comma-separated stylesheet URLs the player loads alongsideconfig.resources.stylesheets. URLs must behttp:orhttps:, and same-origin unless their origin is named inallowed-style-origins. A same-origin sheet is fetched and its selectors are rewritten to.pie-item-player.<scope class>, so it styles this player only.allowed-style-origins:String, default"". Comma-separated origin allow-list forexternal-style-urlsandconfig.resources.stylesheets[*].url. Unset, only same-origin stylesheets load; set, only the listed origins load, so the list names the page's own origin when it serves stylesheets too. A cross-origin sheet cannot be read to scope it, so the player adds it as a<link>in<head>and its rules reach the whole page (external stylesheets).loader-config:Object, default below. Retry and instrumentation settings:iifeBundleRetry:{ retryDelayMs: 3000, timeoutMs: 120000 }. How long the IIFE adapter retries a bundle the bundle host is still building, reported asbundle-retry-status.maxResourceRetries:3, andresourceRetryDelay:500(ms, the first delay of an exponential backoff). Retries of failed images, audio and video.trackPageActions:false. Report player events to the instrumentation provider.instrumentationProvider: unset. The provider events go to; unset withtrackPageActionson falls back to New Relic, andnulldisables instrumentation (instrumentation providers).
configuration:Object, default{}. Settings passed to configure elements (authoring configuration).trust-markup:Boolean, defaultfalse. Skip the built-in markup sanitizer (content trust boundary).session-snapshot:ObjectorBoolean, default off. Opt into a device-local copy of each committed session, offered back after a crash. It needs abackend.deliverysessionIdor an explicitsessionSnapshot.key(session snapshot).
A Boolean attribute set to "false", "0", "off" or "no", in any case,
reads as false.
Properties (JS only)
These are set from JavaScript; they have no attribute.
loaderOptions:{ bundleHost?: string, esmCdnUrl?: string, esmCdnProvider?: string | object, moduleResolution?: "url" | "import-map", view?: string, loadControllers?: boolean, runtimeSupportCheck?: "off" | "on", elementPackagePolicy?: { allowedPackages: string[], requireExactVersions?: boolean } }. Strategy-specific loader settings, with their defaults and the ESM CDN providers, inloaderOptions.elementPackagePolicylimits theconfig.elementspackages that may execute to exact names orname@versionspecs (escape hatches).sanitizeMarkup:(markup: string) => string. Replaces the built-in sanitizer (custom sanitizer). Ignored whentrust-markupis set.backend:{ delivery?, authoring? }. Loading, saving and scoring through the PIE API backend (backend support). A custombackend.delivery.clientforwardscontext.requestOptions.keepalivetofetch; without it the save that runs as the page unloads is an ordinary request the browser may drop.sessionSnapshot:boolean | { enabled?: boolean, store?: SessionSnapshotStore, key?: string }. The property form ofsession-snapshot.storereplaces the defaultsessionStoragebacking, and its owner owns the retention consequences.onInsertImage,onDeleteImage,onInsertSound,onDeleteSound: the authoring media hooks.
Methods
provideScore(): Promise<false | Array<Record<string, unknown> | undefined>>scores in the browser with the elements' controllers and returns one result slot per scored model, orfalsewhen no item is loaded. A hosted player runs no controllers and leaves every slotundefined(scoring).validateModels(): Promise<AuthoringValidationResult>runs each rendered configure element's controllervalidate(model, configuration)and returns{ hasErrors, validatedModels }. Each validated model is{ ...model, errors }, whereerrorsis the controller's field → message map, and each configure element receives the same map asmodel.errorsto render its inline messages. Outside author mode it returns{ hasErrors: false, validatedModels: [] }; it throws when called in author mode before the authoring elements have loaded.loadFromBackend(scope?: "delivery" | "authoring"): Promise<void>loads the configured backend's config and session into the player.saveSession(): Promise<void>saves the current session throughbackend.delivery.score(options?: BackendScoreOptions): Promise<unknown>scores on the server throughbackend.delivery.saveContent(options?: BackendSaveContentOptions): Promise<string>saves the current config throughbackend.authoringand resolves with the content id.releaseContent(options?: BackendAuthoringReleaseOptions): Promise<string>releases the content throughbackend.authoringand resolves with the content id.commitPendingElementSessions(): voidcommits every mounted element's pendingsession-changednow. A host that removes the player calls it first: the player's own destroy runs after the element is detached, so the event it produces does not reachdocument(session commit).getPendingSessionSnapshot(): SessionSnapshotRecord | nullreturns the snapshotsession-snapshot-availableannounced, for a host that attached its listener after the event fired.
A backend method rejects its promise on failure and dispatches no
backend-error.
Events
load-complete: the PIE elements have loaded and rendered, and the math in the item's own markup is typeset, which the player waits for at most two seconds (load completion).session-changed: an element's session or completion changed. An element's announcement is forwarded when itscompleteor its session differs from what that element last announced, so each element reaches the host once at load and one response produces one event. The detail has one of two shapes:{ complete, component, elementId, session: { id, data } }when the item session changed.sessionis the whole item session.{ complete, component, elementId, session: null, intent: "metadata-only" }when only metadata changed.
completeis the announcing element's own,componentits tag andelementIdits model id. Every event carriescompleteandcomponent: where the element supplied neither (an event synthesized for an element that cannot commit itself, or correct responses the player populated),componentis the element's tag andcompleteis whether its session record holds a response. State an element's controller writes into the session, such as a shuffled choice order, dispatches no event of its own: thesessioncontainer holds it at once, and the next event carries it.Focus leaving the player delivers any pending announcement at once, ahead of the click or key that moved focus. A commit at a teardown, navigation or page-hidden seam has the first shape plus
sessionCommitReason("teardown" | "navigate" | "page-hidden"). A host that re-pushesconfigin response to this event ignores a commit: the commit reports a response the host is about to lose and asks for no reload (session commit).player-error:PieItemPlayerErrorDetail,{ code, message, recoverable, stage?, strategy?, mode?, cause? }, for exampleAUTHORING_BACKEND_CONFIG_ERRORorITEM_PLAYER_LOAD_ERROR.recoverableistruewhen the item stays usable: a failed update leaves it as it was, and a failed controller falls back to the authored model (ITEM_PLAYER_UPDATE_ERROR,PIE_CONTROLLER_RUNTIME_ERROR,PIE_CONTROLLER_CONTRACT_ERROR). It isfalsewhen the player has no item to show, so a host can treat the error as fatal onrecoverablealone. When elements fail to register,causenames each one and why, such as the module URL that failed to load; the error is reported as soon as every missing element's load has failed.feature-decision-applied:{ featureId, granted, rule, overrode? }, once per feature before the elements render with a toolkit decision written onto their models.featureIdis the feature, today only"autoplayAudio";grantedandruleare the decision's verdict and deciding rule;overrodeis"attribute"or"authored"when the decision displaced the player's own setting, such asautoplay-audio-enabled, or an authored value.<pie-item-scope>relays it to its toolkit.bundle-retry-status:{ state, url, attempt, elapsedMs, timeoutMs, retryDelayMs?, reason? }, withstateone of"retrying","completed","timeout"or"cancelled". The IIFE adapter is retrying a bundle the bundle host is still building, underloader-config'siifeBundleRetry; the player shows a "still building" message meanwhile.model-updated:{ update, reset }, author mode. A configure element changed its model;updateis that model, with itsid. The player applies it to its own copy of the config and leaves the host's object as it was: a host that keeps the config mergesupdateinto the model with the sameid, replacing the model whenresetis true.model-loaded:{ models, configuration }, author mode. Emitted once per renderer initialization, after the configure elements receive their models and configuration.correct-responses-populated:{ itemId?, mode?, role?, bundleType?, populatedCount, elements }. Correct responses were written into the session.elementsholds theconfig.models[].elementnames (for examplemultiple-choice--version-latest), and the detail carries no session entries. It lets a host detect a population it did not ask for. Population needs an element controller in the browser, which a player that is not hosted gets from aclient-player.jsbundle underiife, from the controller modulesesmloads, or from a controller registered withregisterPreloadedElements. Preventing it takes a hosted player (delivery integrity). It is also forwarded to a configured instrumentation provider aspie-item-correct-responses-populated, the only item-player event on that bridge: forwardingsession-changed, which carries learner responses, to telemetry is the host's decision.session-snapshot-available:{ key, session, timestamp }. On load, withsession-snapshotenabled, device storage holds a snapshot for this sitting. The player never applies it; the host decides.backend-load-complete:{ scope, operation: "load", metadata, ... }.backend.deliveryloaded the item, withsessionin the detail, orbackend.authoringloaded content, withcontentIdandconfig.backend-model-complete:{ scope: "delivery", operation: "model", metadata }. The backend returned refreshed models for a hosted player.backend-session-saved:{ scope, operation, sessionId, session }.saveSession()or an autosave succeeded.backend-score-complete:{ scope, operation: "score", sessionId, score }.score()succeeded.backend-content-saved,backend-content-released:{ scope: "authoring", operation, contentId }.saveContent()orreleaseContent()succeeded.backend-error:{ scope: "delivery", operation, message, error }, withoperationone of"load","model"or"saveSession". An automatic delivery load, model refresh or autosave failed.
Backend delivery
backend.delivery loads the item and session from the PIE API backend,
autosaves, and scores on the server; backend.authoring loads, saves and
releases authored content. Backend support
covers both, with a runnable local demo.
PIE element packaging contract
strategy="esm" and strategy="preloaded" load what an element package
publishes under the
PIE element contract
in pie-elements-ng. Loading strategies
covers how the player consumes it: the static browser ESM files, the exact
shared dependencies, the shared editor runtime, the runtime-support check, and
registering npm-installed elements with registerPreloadedElements.
Authoring configuration
In mode="author", <pie-item-player> loads the configure elements and passes
each one a resolved configuration object.
Delivery and shared settings sit at the top level of configuration, keyed by
package spec, package name or element tag. Authoring-only settings belong under
configuration.authoring, so they do not affect delivery mode. Authoring keys
resolve by specificity:
- Full versioned PIE tag, for example
multiple-choice--version-14-0-3 - Package spec, for example
@pie-element/multiple-choice@14.0.3 - Package name, for example
@pie-element/multiple-choice - Package base name, for example
multiple-choice
The top-level settings are merged first, then the matching
configuration.authoring settings override them, in author mode only.
const el = document.querySelector("pie-item-player");
el.mode = "author";
el.configuration = {
"@pie-element/multiple-choice@14.0.3": {
sharedSetting: true,
},
authoring: {
"multiple-choice--version-14-0-3": {
authoringOnlySetting: true,
},
},
};
Authoring media hooks
In mode="author", the player handles image and sound upload and deletion
through four handler properties:
onInsertImage(handler: ImageHandler): voidonDeleteImage(src: string, done: (err?: Error) => void): voidonInsertSound(handler: SoundHandler): voidonDeleteSound(src: string, done: (err?: Error) => void): void
authoring-backend="demo" installs demo handlers until the host supplies any
handler; from then on only the supplied handlers run, so a host supplies all four
or none. authoring-backend="required" requires all four; a missing handler
blocks the authoring UI and emits a player-error with code
AUTHORING_BACKEND_CONFIG_ERROR.
const el = document.querySelector("pie-item-player");
el.mode = "author";
el.authoringBackend = "required";
el.onInsertImage = (handler) => {
handler.done(undefined, "https://example.com/uploaded-image.png");
};
el.onDeleteImage = (_src, done) => done();
el.onInsertSound = (handler) => {
handler.done(undefined, "https://example.com/uploaded-sound.mp3");
};
el.onDeleteSound = (_src, done) => done();
Exports
| Entry | Exports |
|---|---|
@pie-players/pie-item-player |
Registers <pie-item-player> and installs the content stylesheet at import. Exports definePieItemPlayer(tagName?), ensureItemPlayerMathRenderingReady(), preloadItemElements(), itemPlayerBackend(), itemPlayerElementTags() and the types below |
@pie-players/pie-item-player/preloaded |
registerPreloadedElements, ensureItemPlayerMathRenderingReady(), and the types PreloadedElement, PreloadedController, PreloadedRegistrationOptions and MathAssetOptions. Defines no element and installs no stylesheet |
@pie-players/pie-item-player/components/item-session-debugger-element |
Registers <pie-item-player-session-debugger> |
ensureItemPlayerMathRenderingReady() installs the math renderer IIFE element
bundles read from window, unless the page holds one. The player calls it
before loading IIFE bundles; a host that loads IIFE element bundles itself calls
it first (math rendering).
{ assetRoot }, an npm root, names where the renderer's speech rules load from,
jsDelivr by default; the first call's options apply.
preloadItemElements(configs, inputs, options?) registers the elements of
several items in one load, through the backend and under the tags a player with
the same strategy, loaderOptions, loaderConfig, mode and hosted would
use, so players that mount afterwards find their elements defined. It does
nothing under preloaded, and rejects when the load fails. options takes an
instrumentationProvider in place of the player's and an onBundleRetryStatus
callback for IIFE bundle-build retries. <pie-item-summary> calls it for the
rows that come into view together, and the section player for its pre-warm.
itemPlayerBackend(inputs, options?) returns that backend, null under
preloaded, and itemPlayerElementTags(elements, inputs) the tags, which under
preloaded are the ones each player asserts.
import type {
PieItemPlayerElement,
PieItemSessionDebuggerElement,
PieItemPlayerLoaderOptions,
PieItemPlayerErrorDetail,
AuthoringBackendMode,
AuthoringValidationResult,
ImageHandler,
SoundHandler,
DeleteDone,
BackendConfig,
BackendDeliveryConfig,
BackendAuthoringConfig,
} from "@pie-players/pie-item-player";
The root entry also exports every type in
src/backend/types.ts: the endpoint, identity,
request, context, client and result types BackendConfig is built from.
Content trust boundary
<pie-item-player> renders the markup of config, and of an attached
passage, through a default-on sanitizer built on
DOMPurify. It keeps pie-* tags and the
item's own element tags, and its guarantees are in the
security model.
The player renders into light DOM, which does not contain authored CSS
(light DOM),
so an authored position: absolute overlay can paint over the host page. That
residual is accepted by design
(accepted residual).
What the deployment must set, from allowed-style-origins to a
Content-Security-Policy, is in
host obligations.
Reflow wrappers
After sanitizing, the player wraps every authored <img> and <table> outside
a pie-* element in a horizontally scrollable region, so content wider than
its column scrolls instead of being clipped by an overflow-x: hidden ancestor
(WCAG 1.4.10 Reflow at 400% zoom). Images get
<span class="pie-image-scroll">, plus pie-image-scroll-block when the image
is laid out as a block; tables get <div class="pie-table-scroll">. A
wrapper that scrolls is keyboard-scrollable (tabindex="0", role="region")
and takes its aria-label from the image's alt, or from the table's
aria-label, aria-labelledby or <caption>; the player drops those
attributes while the content fits, so a wrapper that does not scroll adds no tab
stop. The wrapper takes the node's inline vertical margins, which it would
otherwise clip. A post-render pass wraps the images and
tables an element paints into its own light DOM the same way. The wrapper CSS
is part of the content styles.
Opt out (trusted content)
A host that already validates item markup, such as an authoring pipeline that renders only markup its own servers produced, disables sanitization:
<pie-item-player trust-markup config='...' session='...'></pie-item-player>
el.trustMarkup = true;
Provide a custom sanitizer
sanitizeMarkup replaces the built-in sanitizer, to extend its allow-list or to
apply a stricter one. The function receives the markup only, without the
allow-list of the item's element tags the default builds, so it allow-lists them
itself. The player has rewritten the markup's tags to versioned tags by then
(multiple-choice--version-14-0-3), and to <tag>-config in author mode:
import { sanitizeItemMarkup } from "@pie-players/pie-players-shared/security";
import { parseVersionedTagName } from "@pie-players/pie-players-shared/pie/tag-names";
const itemElements = new Set(Object.keys(config.elements));
const TAG = /<([a-z][\w.-]*)/gi;
el.sanitizeMarkup = (markup: string) => {
const itemTags = [...markup.matchAll(TAG)]
.map(([, tag]) => tag.toLowerCase())
.filter((tag) =>
itemElements.has(parseVersionedTagName(tag.replace(/-config$/, "")).baseName),
);
return sanitizeItemMarkup(markup, {
allowedCustomElements: [...itemTags, "my-custom-widget"],
});
};
When trust-markup is set, sanitizeMarkup is ignored. A custom sanitizer owns
every guarantee the default gives
(escape hatches).