npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@longsightgroup/qti3-player

v0.7.2

Published

Style-neutral web component player for rendering and scoring QTI 3 assessment items.

Readme

@longsightgroup/qti3-player

Style-neutral web component player for QTI 3 assessment items.

This package renders one QTI item at a time, captures responses, validates responses, scores attempts through @longsightgroup/qti3-core, and emits host-readable state events.

Install

npm install @longsightgroup/qti3-player

Use

import { defineQtiAssessmentItemPlayer } from "@longsightgroup/qti3-player";

defineQtiAssessmentItemPlayer();
<qti-assessment-item-player id="player"></qti-assessment-item-player>
const player = document.querySelector("qti-assessment-item-player");

await player?.loadXml(xml, {
  status: "interacting",
  sessionControl: {
    validateResponses: true,
    showFeedback: true,
  },
});

player?.addEventListener("qti-statechange", (event) => {
  console.log(event.detail.state);
});

Scoring Trust Boundary

The player can score attempts locally through @longsightgroup/qti3-core, but browser scoring is a convenience for validation, feedback, previews, and response snapshots. High-stakes assessment systems must treat browser outcomes as untrusted and recompute scores server-side from authoritative QTI XML and trusted response variables.

Player Chrome Messages (host-owned i18n)

The player keeps authored QTI content (prompts, choice labels, title on end-attempt) separate from player chrome (remove buttons, aria labels, live-region status, gap labels).

Only English ships in the package. language-of-interface="sv-SE" does not change chrome by itself; the host loads a locale file.

Locale files (recommended)

Harbor or an LMS maintains JSON (or ICU/Fluent exported to JSON) with the shape PlayerMessageCatalog: a flat strings map, optional interactionTypes, and directions. Copy defaultPlayerMessageCatalog from the package as the template; omit keys you do not need to translate (missing keys fall back to English).

import type { PlayerMessageCatalog } from "@longsightgroup/qti3-player";

const sv = (await fetch("/locales/player/sv-SE.json")).json() as PlayerMessageCatalog;

player.languageOfInterface = "sv-SE";
player.messageCatalog = sv;
await player.loadXml(xml);

Example entries:

{
  "locale": "sv-SE",
  "strings": {
    "remove": "Ta bort",
    "removePair": "Ta bort {label}",
    "associationPairLabel": "{source} med {target}",
    "extendedTextCounter": "{count} av {expectedLength}",
    "associationsMade.one": "{count} koppling skapad.",
    "associationsMade.other": "{count} kopplingar skapade."
  },
  "interactionTypes": {
    "graphicOrder": "Grafisk ordning"
  },
  "directions": { "up": "upp", "down": "ner", "left": "vänster", "right": "höger" }
}

Templates use {placeholder} names. For English-style singular/plural, use messageKey.one and messageKey.other (for example associationsMade.one). Languages that do not inflect by count can use a single key for plural message ids.

Validate locale files in CI with validatePlayerMessageCatalog() — pass JSON.parse output directly (unknown). Shape errors (non-object root, missing strings, numeric templates, bad directions / interactionTypes) return diagnostics instead of throwing:

import { validatePlayerMessageCatalog } from "@longsightgroup/qti3-player";

const raw = JSON.parse(await readFile("locales/player/sv-SE.json", "utf8"));
const result = validatePlayerMessageCatalog(raw);
if (!result.valid) {
  for (const issue of result.diagnostics) {
    console.error(`${issue.code} ${issue.key}: ${issue.message}`);
  }
  process.exit(1);
}

Use requireAllKeys: true only for complete locale files forked from defaultPlayerMessageCatalog. Partial delivery catalogs should omit that flag.

Reference exports:

  • PLAYER_MESSAGE_KEYS — message ids from PLAYER_MESSAGE_MANIFEST
  • PLAYER_MESSAGE_STRING_KEYS — all keys in defaultPlayerMessageCatalog.strings
  • defaultPlayerMessageCatalog — English template to fork
  • allowedCatalogPlaceholders(entry) — placeholders a template may use
  • requiredCatalogPlaceholders(catalogKey) — placeholders required for that key per English default
  • createPlayerMessageResolver(catalog) — canonical key-driven runtime API
  • PlayerMessageParams<K> — typed params for message(key, params)

Resolver kinds in the manifest (hosts only edit strings; behavior is fixed):

| Resolver | Meaning | | ------------------- | ---------------------------------------------------- | | plain | Static string | | template | {placeholder} interpolation from params | | plural | Uses key.one / key.other when count is present | | typeLabel | Interaction type short name from interactionTypes | | typeTemplate | Template with {typeName} derived from type | | directionTemplate | Template with localized {direction} |

Runtime API

Chrome resolves to a key-driven PlayerMessageResolver:

const messages = resolvePlayerMessages(locale, {}, catalog);

messages.message("remove");
messages.message("removePair", { label: pairLabel });
messages.message("associationsMade", { count: 2 });

createPlayerMessageResolver(catalog) builds the same resolver directly from a locale file.

Per-message overrides

player.messages accepts QtiPlayerMessageOverrides — each key uses the same param types as message(key, params) (for example removePair: ({ label }) => ...). Use only when a catalog entry is not enough; composed strings (for example removePair using associationPairLabel text) are easy to break.

Item language vs interface language

Do not copy item xml:lang onto <qti-assessment-item-player lang="..."> unless you intentionally want the player element's lang attribute to influence defaultPlayerLocale(). Prefer player.messageCatalog for UI chrome.

Portable Custom Interactions

For qti-portable-custom-interaction, the player renders a qti3-portable-custom-host element, passes small module/configuration metadata through dataset attributes, and emits qti-portable-custom-mount with the full parsed definition. Host code can attach a PCI runtime and send response/state updates back with qti3-portable-custom-response. Production sandboxing, CSP, origin policy, and audit logging belong to the host delivery system.

Framework adapters

Optional React and Preact TSX wrappers ship as separate packages. They keep the web component as the rendering primitive and only handle framework lifecycle wiring:

  • @longsightgroup/qti3-player-react
  • @longsightgroup/qti3-player-preact

Use the native element directly when you do not need React or Preact integration.

Local manual proof for the React adapter: from the repo root run pnpm dev:adapter-react and open /adapter-react.html (linked from the main manual harness).

Clearing a loaded item

clearItem() (or omitting declarative xml on the adapters) removes rendered content and in-memory session state. It does not emit qti-statechange or other player events because no item is loaded. Hosts should treat the prop transition or imperative call as the source of truth.

Framework adapters treat xml={undefined} as a clear and xml="" as a load attempt. An empty string shows the parse error view when the XML is invalid.

Restored loadOptions.state reload keys use JSON serialization: equivalent content with different object references does not reload, but key order follows construction order and in-place mutation without a reload key change is not detected.

Styling

The player uses light DOM and is style-neutral by design. Host applications can style the rendered qti3-* classes directly while preserving the item author's QTI shared vocabulary classes.

Keyword emphasis

qti-keyword-emphasis is candidate-conditional. The player preserves the authored class without applying special visual styling by default. After the host delivery system resolves the candidate's AfA/PNP and finds keyword-emphasis, opt in before or after loading the item:

player.keywordEmphasisEnabled = true;
// Equivalent DOM API:
player.setAttribute("data-keyword-emphasis", "true");

When enabled, the rendered .qti3-player root receives data-keyword-emphasis="true" and the bundled stylesheet visibly emphasizes .qti-keyword-emphasis. Set player.keywordEmphasisEnabled = false or remove data-keyword-emphasis to return to the default inert presentation.

Screen-reader status lines (.qti3-selection-summary, aria-live="polite") are visually hidden by default so LMS shells do not show reorder or selection announcements to sighted users. They remain available to assistive technology. Set data-show-live-regions on qti-assessment-item-player only in local debug or harness pages when you want those messages visible on screen.

See the main repository README for the support matrix and release notes: https://github.com/LongsightGroup/qti3