diff --git a/.github/workflows/angular-smoke.yml b/.github/workflows/angular-smoke.yml new file mode 100644 index 0000000..235a022 --- /dev/null +++ b/.github/workflows/angular-smoke.yml @@ -0,0 +1,55 @@ +name: angular-smoke + +# Validates that @studiolxd/scorm/angular compiles in a real Angular AOT/production +# build — the acceptance criterion for the decorator-free, plain-ESM adapter +# (docs/PLAN-agnostic-core.md §5/§11). Targets the documented floor, Angular 17. + +on: + push: + branches: [main, feat/agnostic-core] + paths: + - 'packages/scorm/**' + - 'tests/angular-smoke/**' + - '.github/workflows/angular-smoke.yml' + pull_request: + paths: + - 'packages/scorm/**' + - 'tests/angular-smoke/**' + - '.github/workflows/angular-smoke.yml' + +jobs: + aot-build: + name: Angular 17 AOT build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install monorepo deps + # Remove the lockfile before installing to sidestep npm's cross-platform + # optional-dependency bug (npm/cli#4828): a lockfile generated on macOS makes + # npm skip the Linux rollup native binary tsup/rollup need on the runner. + # A fresh install resolves the correct platform binaries. + run: | + rm -f package-lock.json + npm install --no-audit --no-fund + + - name: Build @studiolxd/scorm + run: npm run build --workspace=packages/scorm + + - name: Pack the built library into the fixture + run: npm pack --workspace=packages/scorm --pack-destination tests/angular-smoke + + - name: Install the Angular fixture (Angular 17 + the packed tarball) + working-directory: tests/angular-smoke + run: | + npm install + npm install ./studiolxd-scorm-*.tgz + + - name: Angular production (AOT) build + working-directory: tests/angular-smoke + run: npx ng build --configuration production diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..afc7e50 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +# AGENTS.md — working in this repository + +Guidance for AI coding agents (Claude Code, Cursor, Copilot, etc.) operating in this +monorepo. For how to *use* the published library, see `packages/scorm/llms.txt`. + +## What this is + +`@studiolxd/scorm` — a headless SCORM 1.2 / 2004 runtime. npm workspaces monorepo: + +- `packages/scorm/` — the published library (`@studiolxd/scorm`). + - `src/` core is **framework-agnostic** (no framework imports outside `src/react`, + `src/vue`, `src/angular`, `src/svelte`, `src/wc`). + - Entry points map to subpaths: `.` (core+vanilla), `./react`, `./vue`, + `./angular`, `./svelte`, `./wc`. Built with `tsup` (ESM + CJS + IIFE). +- `example/` — the single demo app (React + Vite). Not published. +- `docs/` — planning + design docs. + +## Commands (run from repo root) + +- `npm run build` — build the library. +- `npm run test` — run the library test suite (vitest). +- `npm run dev:example` — run the demo at http://localhost:5173. +- Per-package: `npm run typecheck --workspace=packages/scorm`. + +Always run `typecheck` + `test:run` + `build` in `packages/scorm` before committing. + +## Conventions + +- **TypeScript strict.** Every public symbol has JSDoc, ideally with an `@example`. +- **`Result`** is the universal return type — never throw across the + public API (except `noLmsBehavior: 'throw'`). Check `.ok` before `.value`. +- **Framework isolation.** Code in `src/` (outside the adapter folders) must not + import any framework. Adapters are thin bridges over `createScormSession` and the + vanilla `autoTerminate`/`autoCommit` helpers — put shared logic in the core, not + in an adapter. +- **SSR-safe.** Guard any `window`/`document`/`HTMLElement` access with + `typeof … === 'undefined'`. +- **Tests** live in `packages/scorm/tests/`, mirroring `src/`. Use mock mode + (`noLmsBehavior: 'mock'`) — no real LMS needed. + +## Adding a framework adapter + +1. Create `src//index.ts` — bridge `session.on('change')` to the + framework's reactivity; reuse `autoTerminate`/`autoCommit`. +2. Add the entry to `tsup.config.ts` and an `exports` subpath in `package.json`. +3. Add the framework as an **optional** peerDependency (+ devDependency for builds). +4. Add a test under `tests/adapters/`. + +## Release workflow + +See `docs/PLAN-agnostic-core.md` §7 and the version-bump steps: update CHANGELOG → +`npm version` → commit → (publish is manual and gated on npm auth). + +## Do not + +- Do not introduce a framework dependency into the core. +- Do not bundle framework deps (they are externalized/optional peers). +- Do not read `.value` without checking `.ok`. diff --git a/README.de.md b/README.de.md index 36e2683..6072497 100644 --- a/README.de.md +++ b/README.de.md @@ -1,47 +1,66 @@ 🌐 [English](README.md) · [Español](README.es.md) · [Français](README.fr.md) · [Português](README.pt.md) · [Deutsch](README.de.md) · [Polski](README.pl.md) -# @studiolxd/react-scorm +# @studiolxd/scorm -Monorepo, das die headless-Bibliothek `@studiolxd/react-scorm` für die SCORM-Laufzeitumgebung und eine interaktive Demo-App enthält, die alle Funktionen der Bibliothek zeigt. +Monorepo für `@studiolxd/scorm` — eine Headless-Laufzeitumgebung für SCORM 1.2 / 2004 mit einem **frameworkunabhängigen Kern** und Adaptern für **React, Vue, Angular, Svelte, Web Components** sowie reines Vanilla-JS — inklusive einer interaktiven Demo-App. + +> Vormals `@studiolxd/react-scorm`. Die React-API befindet sich jetzt im Unterpfad `@studiolxd/scorm/react`. ## Pakete -| Paket | Beschreibung | -|-------|--------------| -| [`@studiolxd/react-scorm`](./packages/react-scorm/) | Headless React + TypeScript Laufzeitbibliothek für SCORM 1.2 / 2004 | [README](./packages/react-scorm/README.md) | -| [`example`](./example/) | Interaktive Demo-App — zeigt alle Funktionen der Bibliothek | [README](./example/README.md) | +| Paket | Beschreibung | Doku | +|---------|-------------|------| +| [`@studiolxd/scorm`](./packages/scorm/) | Headless-Laufzeitumgebung für SCORM 1.2 / 2004 — unabhängiger Kern + Framework-Adapter | [README](./packages/scorm/README.md) | +| [`example`](./example/) | Interaktive Demo-App — präsentiert jede Funktion der Bibliothek | [README](./example/README.md) | ## Erste Schritte ```bash -npm install # alle Workspaces vom Root aus installieren -npm run dev:lib # Bibliothek im Watch-Modus bauen -npm run dev:example # Entwicklungsserver der Demo starten (http://localhost:5173) +npm install # install all workspaces from the root +npm run dev:lib # build the library in watch mode +npm run dev:example # start the example dev server (http://localhost:5173) ``` -Weitere Skripte, die vom Root aus verfügbar sind: +Weitere Skripte, die vom Stammverzeichnis aus verfügbar sind: - `npm run build` — baut die Bibliothek -- `npm run test` — führt die Test-Suite der Bibliothek aus +- `npm run test` — führt die Testsuite der Bibliothek aus + +## Einstiegspunkte + +Die Bibliothek ist ein einzelnes Paket mit Unterpfad-Exporten — importiere nur das, was du verwendest: + +| Import | Für | +|--------|-----| +| `@studiolxd/scorm` | Frameworkunabhängiger Kern + Vanilla (`createScormSession`) | +| `@studiolxd/scorm/react` | React (`ScormProvider`, `useScorm`, …) | +| `@studiolxd/scorm/vue` | Vue 3.3+ (`useScorm`) | +| `@studiolxd/scorm/angular` | Angular 17+ (`provideScorm`, `SCORM`) | +| `@studiolxd/scorm/svelte` | Svelte 4+ (`createScormStore`) | +| `@studiolxd/scorm/wc` | `` Web Component | +| `window.Scorm` (CDN ` + + +`} + + + + ); +} diff --git a/example/src/sections/StatusSection.tsx b/example/src/sections/StatusSection.tsx index 6df8586..fce9cd4 100644 --- a/example/src/sections/StatusSection.tsx +++ b/example/src/sections/StatusSection.tsx @@ -13,7 +13,7 @@ * (cmi.core.lesson_status). SCORM 2004 separates them into two fields. */ import { useSessionContext } from '../SessionContext'; -import type { Result, ScormError } from '@studiolxd/react-scorm'; +import type { Result, ScormError } from '@studiolxd/scorm/react'; import { useState } from 'react'; type LogEntry = { text: string; ok: boolean }; diff --git a/package-lock.json b/package-lock.json index 5f4caf2..f58372f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,10 @@ { - "name": "@studiolxd/react-scorm-monorepo", + "name": "@studiolxd/scorm-monorepo", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@studiolxd/react-scorm-monorepo", + "name": "@studiolxd/scorm-monorepo", "workspaces": [ "packages/*", "example" @@ -14,10 +14,10 @@ } }, "example": { - "name": "my-app", + "name": "scorm-example", "version": "0.0.0", "dependencies": { - "@studiolxd/react-scorm": "^1.0.1", + "@studiolxd/scorm": "*", "react": "^19.2.0", "react-dom": "^19.2.0" }, @@ -36,19 +36,6 @@ "vite": "^7.3.1" } }, - "example/node_modules/@babel/code-frame": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "example/node_modules/@babel/compat-data": { "version": "7.29.0", "dev": true, @@ -238,21 +225,6 @@ "node": ">=6.9.0" } }, - "example/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, "example/node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "dev": true, @@ -446,18 +418,6 @@ "dev": true, "license": "MIT" }, - "example/node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, "example/node_modules/@types/babel__core": { "version": "7.20.5", "dev": true, @@ -508,22 +468,6 @@ "undici-types": "~7.16.0" } }, - "example/node_modules/@types/react": { - "version": "19.2.14", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "example/node_modules/@types/react-dom": { - "version": "19.2.3", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, "example/node_modules/@typescript-eslint/eslint-plugin": { "version": "8.56.1", "dev": true, @@ -806,17 +750,6 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "example/node_modules/acorn": { - "version": "8.16.0", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "example/node_modules/acorn-jsx": { "version": "5.3.2", "dev": true, @@ -963,11 +896,6 @@ "dev": true, "license": "MIT" }, - "example/node_modules/csstype": { - "version": "3.2.3", - "dev": true, - "license": "MIT" - }, "example/node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -978,46 +906,6 @@ "dev": true, "license": "ISC" }, - "example/node_modules/esbuild": { - "version": "0.27.3", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, "example/node_modules/escalade": { "version": "3.2.0", "dev": true, @@ -1352,11 +1240,6 @@ "node": ">=0.10.0" } }, - "example/node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, "example/node_modules/js-yaml": { "version": "4.1.1", "dev": true, @@ -1544,23 +1427,6 @@ "node": ">= 0.8.0" } }, - "example/node_modules/react": { - "version": "19.2.4", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "example/node_modules/react-dom": { - "version": "19.2.4", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, "example/node_modules/react-refresh": { "version": "0.18.0", "dev": true, @@ -1577,53 +1443,6 @@ "node": ">=4" } }, - "example/node_modules/rollup": { - "version": "4.59.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "example/node_modules/scheduler": { - "version": "0.27.0", - "license": "MIT" - }, "example/node_modules/semver": { "version": "6.3.1", "dev": true, @@ -1665,18 +1484,6 @@ "node": ">= 0.8.0" } }, - "example/node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "example/node_modules/typescript-eslint": { "version": "8.56.1", "dev": true, @@ -1857,10 +1664,35 @@ "zod": "^3.25.0 || ^4.0.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular/core": { + "version": "17.3.12", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.14.0" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "dev": true, "license": "MIT", "dependencies": { @@ -1871,10 +1703,21 @@ "lru-cache": "^10.4.3" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", "dev": true, "license": "MIT", "engines": { @@ -1882,9 +1725,7 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", "dev": true, "license": "MIT", "engines": { @@ -1892,13 +1733,11 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.7", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1907,24 +1746,36 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", "dev": true, "funding": [ { @@ -1943,8 +1794,6 @@ }, "node_modules/@csstools/css-calc": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "dev": true, "funding": [ { @@ -1967,8 +1816,6 @@ }, "node_modules/@csstools/css-color-parser": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", "dev": true, "funding": [ { @@ -1995,8 +1842,6 @@ }, "node_modules/@csstools/css-parser-algorithms": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "dev": true, "funding": [ { @@ -2018,8 +1863,6 @@ }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "dev": true, "funding": [ { @@ -2036,44 +1879,23 @@ "node": ">=18" } }, - "node_modules/@emnapi/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -2083,8 +1905,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -2093,15 +1913,11 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -2109,37 +1925,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, "node_modules/@oxc-project/types": { "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@rolldown/binding-android-arm64": { + "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.11.tgz", - "integrity": "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==", "cpu": [ "arm64" ], @@ -2147,16 +1942,19 @@ "license": "MIT", "optional": true, "os": [ - "android" + "darwin" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-darwin-arm64": { + "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.11.tgz", - "integrity": "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", "cpu": [ "arm64" ], @@ -2165,472 +1963,370 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.11.tgz", - "integrity": "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==", - "cpu": [ - "x64" - ], + "node_modules/@standard-schema/spec": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "license": "MIT" }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.11.tgz", - "integrity": "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==", - "cpu": [ - "x64" - ], + "node_modules/@studiolxd/scorm": { + "resolved": "packages/scorm", + "link": true + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.11.tgz", - "integrity": "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==", - "cpu": [ - "arm" - ], + "node_modules/@testing-library/react": { + "version": "16.3.2", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/runtime": "^7.12.5" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.11.tgz", - "integrity": "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.11.tgz", - "integrity": "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/aria-query": { + "version": "5.0.4", "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "license": "MIT" }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.11.tgz", - "integrity": "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/chai": { + "version": "5.2.3", "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.11.tgz", - "integrity": "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==", - "cpu": [ - "s390x" - ], + "node_modules/@types/deep-eql": { + "version": "4.0.2", "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "license": "MIT" }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.11.tgz", - "integrity": "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==", - "cpu": [ - "x64" - ], + "node_modules/@types/estree": { + "version": "1.0.9", "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "license": "MIT" }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.11.tgz", - "integrity": "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==", - "cpu": [ - "x64" - ], + "node_modules/@types/react": { + "version": "19.2.17", "dev": true, - "libc": [ - "musl" - ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "csstype": "^3.2.2" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.11.tgz", - "integrity": "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==", - "cpu": [ - "arm64" - ], + "node_modules/@types/react-dom": { + "version": "19.2.3", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peerDependencies": { + "@types/react": "^19.2.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.11.tgz", - "integrity": "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==", - "cpu": [ - "wasm32" - ], + "node_modules/@vue/compiler-core": { + "version": "3.5.38", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.11.tgz", - "integrity": "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.38", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.11.tgz", - "integrity": "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==", - "cpu": [ - "x64" - ], + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "7.0.1", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "license": "BSD-2-Clause", "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.11.tgz", - "integrity": "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", "dev": true, "license": "MIT" }, - "node_modules/@studiolxd/react-scorm": { - "resolved": "packages/react-scorm", - "link": true - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "node_modules/@vue/compiler-dom": { + "version": "3.5.38", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@vue/compiler-core": "3.5.38", + "@vue/shared": "3.5.38" } }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "node_modules/@vue/compiler-sfc": { + "version": "3.5.38", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.38", + "@vue/compiler-dom": "3.5.38", + "@vue/compiler-ssr": "3.5.38", + "@vue/shared": "3.5.38", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" } }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", "dev": true, "license": "MIT" }, - "node_modules/@vitest/expect": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz", - "integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==", + "node_modules/@vue/compiler-ssr": { + "version": "3.5.38", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.1", - "@vitest/utils": "4.1.1", - "chai": "^6.2.2", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@vue/compiler-dom": "3.5.38", + "@vue/shared": "3.5.38" } }, - "node_modules/@vitest/mocker": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz", - "integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==", + "node_modules/@vue/reactivity": { + "version": "3.5.38", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.1", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "@vue/shared": "3.5.38" } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz", - "integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==", + "node_modules/@vue/runtime-core": { + "version": "3.5.38", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@vue/reactivity": "3.5.38", + "@vue/shared": "3.5.38" } }, - "node_modules/@vitest/runner": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz", - "integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==", + "node_modules/@vue/runtime-dom": { + "version": "3.5.38", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.1", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@vue/reactivity": "3.5.38", + "@vue/runtime-core": "3.5.38", + "@vue/shared": "3.5.38", + "csstype": "^3.2.3" } }, - "node_modules/@vitest/snapshot": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz", - "integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==", + "node_modules/@vue/server-renderer": { + "version": "3.5.38", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.1", - "@vitest/utils": "4.1.1", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" + "@vue/compiler-ssr": "3.5.38", + "@vue/shared": "3.5.38" }, - "funding": { - "url": "https://opencollective.com/vitest" + "peerDependencies": { + "vue": "3.5.38" } }, - "node_modules/@vitest/spy": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz", - "integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==", + "node_modules/@vue/shared": { + "version": "3.5.38", "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } + "license": "MIT" }, - "node_modules/@vitest/utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz", - "integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==", + "node_modules/acorn": { + "version": "8.17.0", "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.1", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.0.3" + "bin": { + "acorn": "bin/acorn" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=0.4.0" } }, "node_modules/agent-base": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 14" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", "dev": true, "license": "MIT" }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/aria-query": { + "version": "5.3.0", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" + "dequal": "^2.0.3" } }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "node_modules/assertion-error": { + "version": "2.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/color-convert": { + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/code-red": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, + "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2642,15 +2338,11 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", "dependencies": { @@ -2660,17 +2352,34 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -2682,10 +2391,20 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/cssstyle": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", "dev": true, "license": "MIT", "dependencies": { @@ -2698,15 +2417,16 @@ }, "node_modules/cssstyle/node_modules/rrweb-cssom": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", "dev": true, "license": "MIT" }, "node_modules/data-urls": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, "license": "MIT", "dependencies": { @@ -2719,8 +2439,6 @@ }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -2737,35 +2455,40 @@ }, "node_modules/decimal.js": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true, "license": "MIT" }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "dev": true, + "license": "MIT" + }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", "dependencies": { @@ -2779,8 +2502,6 @@ }, "node_modules/entities": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -2792,8 +2513,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", "engines": { @@ -2802,8 +2521,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", "engines": { @@ -2812,15 +2529,11 @@ }, "node_modules/es-module-lexer": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "license": "MIT", "dependencies": { @@ -2832,8 +2545,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { @@ -2846,10 +2557,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.27.7", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -2858,8 +2607,6 @@ }, "node_modules/expect-type": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2868,8 +2615,6 @@ }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -2884,10 +2629,18 @@ } } }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, "node_modules/form-data": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { @@ -2903,10 +2656,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -2918,8 +2668,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", "funding": { @@ -2928,8 +2676,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2953,8 +2699,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", "dependencies": { @@ -2967,8 +2711,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", "engines": { @@ -2980,8 +2722,6 @@ }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -2990,8 +2730,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -3003,8 +2741,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { @@ -3019,8 +2755,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3032,8 +2766,6 @@ }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3045,15 +2777,11 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/http-proxy-agent": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -3066,8 +2794,6 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { @@ -3080,8 +2806,6 @@ }, "node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3093,22 +2817,24 @@ }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, "license": "MIT" }, + "node_modules/is-reference": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3117,8 +2843,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3132,8 +2856,6 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3144,10 +2866,21 @@ "node": ">=8" } }, + "node_modules/joycon": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/jsdom": { "version": "25.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", - "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dev": true, "license": "MIT", "dependencies": { @@ -3187,8 +2920,6 @@ }, "node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -3215,31 +2946,8 @@ "lightningcss-win32-x64-msvc": "1.32.0" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -3257,228 +2965,68 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], + "node_modules/lilconfig": { + "version": "3.1.3", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=14" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], + "node_modules/lines-and-columns": { + "version": "1.2.4", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "license": "MIT" }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], + "node_modules/load-tsconfig": { + "version": "0.2.5", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], + "node_modules/locate-character": { + "version": "3.0.0", "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "license": "MIT" }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], + "node_modules/lru-cache": { + "version": "10.4.3", "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "license": "ISC" }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], + "node_modules/lz-string": { + "version": "1.5.0", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" } }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -3493,18 +3041,19 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", "engines": { @@ -3513,8 +3062,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { @@ -3524,21 +3071,34 @@ "node": ">= 0.6" } }, + "node_modules/mlly": { + "version": "1.8.2", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, - "node_modules/my-app": { - "resolved": "example", - "link": true + "node_modules/mz": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", "dev": true, "funding": [ { @@ -3556,15 +3116,19 @@ }, "node_modules/nwsapi": { "version": "2.2.23", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/obug": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -3574,8 +3138,6 @@ }, "node_modules/parse5": { "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dev": true, "license": "MIT", "dependencies": { @@ -3587,8 +3149,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -3597,22 +3157,26 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, + "node_modules/periscopic": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -3622,10 +3186,26 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pirates": { + "version": "4.0.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.15", "dev": true, "funding": [ { @@ -3643,7 +3223,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3651,20 +3231,112 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/react": { + "version": "19.2.7", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/rolldown": { "version": "1.0.0-rc.11", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.11.tgz", - "integrity": "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==", "dev": true, "license": "MIT", "dependencies": { @@ -3695,24 +3367,69 @@ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" } }, + "node_modules/rollup": { + "version": "4.62.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" + } + }, "node_modules/rrweb-cssom": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", "dev": true, "license": "MIT" }, + "node_modules/rxjs": { + "version": "7.8.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "license": "MIT" }, "node_modules/saxes": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "license": "ISC", "dependencies": { @@ -3722,10 +3439,16 @@ "node": ">=v12.22.7" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "license": "MIT" + }, + "node_modules/scorm-example": { + "resolved": "example", + "link": true + }, "node_modules/semver": { "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3737,8 +3460,6 @@ }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -3750,8 +3471,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -3760,15 +3479,19 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, + "node_modules/source-map": { + "version": "0.7.6", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3777,22 +3500,37 @@ }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", "dev": true, "license": "MIT" }, + "node_modules/sucrase": { + "version": "3.35.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -3802,24 +3540,61 @@ "node": ">=8" } }, + "node_modules/svelte": { + "version": "4.2.20", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.4", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true, "license": "MIT" }, + "node_modules/thenify": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "dev": true, "license": "MIT", "engines": { @@ -3828,8 +3603,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3845,8 +3618,6 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -3855,8 +3626,6 @@ }, "node_modules/tldts": { "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3868,15 +3637,11 @@ }, "node_modules/tldts-core": { "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "dev": true, "license": "MIT" }, "node_modules/tough-cookie": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3888,8 +3653,6 @@ }, "node_modules/tr46": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, "license": "MIT", "dependencies": { @@ -3899,18 +3662,99 @@ "node": ">=18" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" + }, + "node_modules/tsup": { + "version": "8.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/tinyexec": { + "version": "0.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "dev": true, + "license": "MIT" }, "node_modules/vite": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.2.tgz", - "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==", "dev": true, "license": "MIT", "dependencies": { @@ -3985,92 +3829,28 @@ } } }, - "node_modules/vitest": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz", - "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", + "node_modules/vue": { + "version": "3.5.38", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.1", - "@vitest/mocker": "4.1.1", - "@vitest/pretty-format": "4.1.1", - "@vitest/runner": "4.1.1", - "@vitest/snapshot": "4.1.1", - "@vitest/spy": "4.1.1", - "@vitest/utils": "4.1.1", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@vue/compiler-dom": "3.5.38", + "@vue/compiler-sfc": "3.5.38", + "@vue/runtime-dom": "3.5.38", + "@vue/server-renderer": "3.5.38", + "@vue/shared": "3.5.38" }, "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.1", - "@vitest/browser-preview": "4.1.1", - "@vitest/browser-webdriverio": "4.1.1", - "@vitest/ui": "4.1.1", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "typescript": "*" }, "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { + "typescript": { "optional": true - }, - "vite": { - "optional": false } } }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "license": "MIT", "dependencies": { @@ -4082,8 +3862,6 @@ }, "node_modules/webidl-conversions": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -4092,9 +3870,6 @@ }, "node_modules/whatwg-encoding": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "dev": true, "license": "MIT", "dependencies": { @@ -4106,8 +3881,6 @@ }, "node_modules/whatwg-mimetype": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, "license": "MIT", "engines": { @@ -4116,8 +3889,6 @@ }, "node_modules/whatwg-url": { "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "dev": true, "license": "MIT", "dependencies": { @@ -4130,8 +3901,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -4146,8 +3915,6 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -4163,8 +3930,6 @@ }, "node_modules/ws": { "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "dev": true, "license": "MIT", "engines": { @@ -4185,8 +3950,6 @@ }, "node_modules/xml-name-validator": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4195,16 +3958,21 @@ }, "node_modules/xmlchars": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" }, - "packages/react-scorm": { - "name": "@studiolxd/react-scorm", - "version": "1.1.0", + "node_modules/zone.js": { + "version": "0.14.10", + "dev": true, + "license": "MIT", + "peer": true + }, + "packages/scorm": { + "name": "@studiolxd/scorm", + "version": "2.0.0", "license": "MIT", "devDependencies": { + "@angular/core": "^17.3.0", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.0.0", "@types/react": "^19.0.0", @@ -4213,729 +3981,253 @@ "jsdom": "^25.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "rxjs": "^7.8.0", + "svelte": "^4.2.0", "tsup": "^8.0.0", "typescript": "^5.5.0", - "vitest": "^4.1.1" + "vitest": "^4.1.1", + "vue": "^3.4.0" }, "peerDependencies": { + "@angular/core": ">=17.0.0", "react": ">=18.0.0", - "react-dom": ">=18.0.0" + "react-dom": ">=18.0.0", + "svelte": ">=4.0.0", + "vue": ">=3.3.0" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } } }, - "packages/react-scorm/node_modules/@babel/code-frame": { - "version": "7.29.0", + "packages/scorm/node_modules/@vitest/coverage-v8": { + "version": "4.1.9", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.9", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">=6.9.0" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.9", + "vitest": "4.1.9" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "packages/react-scorm/node_modules/@babel/runtime": { - "version": "7.28.6", + "packages/scorm/node_modules/@vitest/expect": { + "version": "4.1.9", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "packages/react-scorm/node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", + "packages/scorm/node_modules/@vitest/pretty-format": { + "version": "4.1.9", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "packages/react-scorm/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "cpu": [ - "arm64" - ], + "packages/scorm/node_modules/@vitest/runner": { + "version": "4.1.9", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@vitest/utils": "4.1.9", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "packages/react-scorm/node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "packages/react-scorm/node_modules/@testing-library/dom": { - "version": "10.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "packages/react-scorm/node_modules/@testing-library/react": { - "version": "16.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@testing-library/dom": "^10.0.0", - "@types/react": "^18.0.0 || ^19.0.0", - "@types/react-dom": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "packages/react-scorm/node_modules/@types/aria-query": { - "version": "5.0.4", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/@types/react": { - "version": "19.2.14", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "packages/react-scorm/node_modules/@types/react-dom": { - "version": "19.2.3", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "packages/react-scorm/node_modules/@vitest/coverage-v8": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.1", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.1.1", - "vitest": "4.1.1" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "packages/react-scorm/node_modules/acorn": { - "version": "8.16.0", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "packages/react-scorm/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "packages/react-scorm/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "packages/react-scorm/node_modules/any-promise": { - "version": "1.3.0", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/aria-query": { - "version": "5.3.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "packages/react-scorm/node_modules/ast-v8-to-istanbul": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "packages/react-scorm/node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "10.0.0", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/bundle-require": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "load-tsconfig": "^0.2.3" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "esbuild": ">=0.18" - } - }, - "packages/react-scorm/node_modules/cac": { - "version": "6.7.14", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "packages/react-scorm/node_modules/chokidar": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/react-scorm/node_modules/commander": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "packages/react-scorm/node_modules/confbox": { - "version": "0.1.8", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/consola": { - "version": "3.4.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "packages/react-scorm/node_modules/csstype": { - "version": "3.2.3", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/dequal": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "packages/react-scorm/node_modules/dom-accessibility-api": { - "version": "0.5.16", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/esbuild": { - "version": "0.27.3", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "packages/react-scorm/node_modules/fix-dts-default-cjs-exports": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.17", - "mlly": "^1.7.4", - "rollup": "^4.34.8" - } - }, - "packages/react-scorm/node_modules/joycon": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "packages/react-scorm/node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/lilconfig": { - "version": "3.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "packages/react-scorm/node_modules/lines-and-columns": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/load-tsconfig": { - "version": "0.2.5", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "packages/react-scorm/node_modules/lz-string": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, - "packages/react-scorm/node_modules/magicast": { - "version": "0.5.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "packages/react-scorm/node_modules/mlly": { - "version": "1.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "packages/react-scorm/node_modules/mz": { - "version": "2.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "packages/react-scorm/node_modules/object-assign": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "packages/react-scorm/node_modules/pirates": { - "version": "4.0.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "packages/react-scorm/node_modules/pkg-types": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "packages/react-scorm/node_modules/postcss-load-config": { - "version": "6.0.1", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "packages/react-scorm/node_modules/pretty-format": { - "version": "27.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "packages/react-scorm/node_modules/react": { - "version": "19.2.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "packages/react-scorm/node_modules/react-dom": { - "version": "19.2.4", + "packages/scorm/node_modules/@vitest/snapshot": { + "version": "4.1.9", "dev": true, "license": "MIT", "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, - "packages/react-scorm/node_modules/react-is": { - "version": "17.0.2", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/readdirp": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "url": "https://opencollective.com/vitest" } }, - "packages/react-scorm/node_modules/resolve-from": { - "version": "5.0.0", + "packages/scorm/node_modules/@vitest/spy": { + "version": "4.1.9", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "packages/react-scorm/node_modules/rollup": { - "version": "4.59.0", + "packages/scorm/node_modules/@vitest/utils": { + "version": "4.1.9", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "@vitest/pretty-format": "4.1.9", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "packages/react-scorm/node_modules/scheduler": { - "version": "0.27.0", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/source-map": { - "version": "0.7.6", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "packages/react-scorm/node_modules/sucrase": { - "version": "3.35.1", + "packages/scorm/node_modules/vitest": { + "version": "4.1.9", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" }, "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "packages/react-scorm/node_modules/thenify": { - "version": "3.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "packages/react-scorm/node_modules/thenify-all": { - "version": "1.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" + "vitest": "vitest.mjs" }, "engines": { - "node": ">=0.8" - } - }, - "packages/react-scorm/node_modules/tinyexec": { - "version": "0.3.2", - "dev": true, - "license": "MIT" - }, - "packages/react-scorm/node_modules/tree-kill": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "packages/react-scorm/node_modules/ts-interface-checker": { - "version": "0.1.13", - "dev": true, - "license": "Apache-2.0" - }, - "packages/react-scorm/node_modules/tsup": { - "version": "8.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-require": "^5.1.0", - "cac": "^6.7.14", - "chokidar": "^4.0.3", - "consola": "^3.4.0", - "debug": "^4.4.0", - "esbuild": "^0.27.0", - "fix-dts-default-cjs-exports": "^1.0.0", - "joycon": "^3.1.1", - "picocolors": "^1.1.1", - "postcss-load-config": "^6.0.1", - "resolve-from": "^5.0.0", - "rollup": "^4.34.8", - "source-map": "^0.7.6", - "sucrase": "^3.35.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.11", - "tree-kill": "^1.2.2" - }, - "bin": { - "tsup": "dist/cli-default.js", - "tsup-node": "dist/cli-node.js" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@microsoft/api-extractor": "^7.36.0", - "@swc/core": "^1", - "postcss": "^8.4.12", - "typescript": ">=4.5.0" + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { - "@microsoft/api-extractor": { + "@edge-runtime/vm": { "optional": true }, - "@swc/core": { + "@opentelemetry/api": { "optional": true }, - "postcss": { + "@types/node": { "optional": true }, - "typescript": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, - "packages/react-scorm/node_modules/typescript": { - "version": "5.9.3", + "packages/scorm/node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.9", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" }, - "engines": { - "node": ">=14.17" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } - }, - "packages/react-scorm/node_modules/ufo": { - "version": "1.6.3", - "dev": true, - "license": "MIT" } } } diff --git a/package.json b/package.json index c870995..bec4656 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@studiolxd/react-scorm-monorepo", + "name": "@studiolxd/scorm-monorepo", "private": true, "workspaces": [ "packages/*", @@ -9,9 +9,9 @@ "jsdom": "^25.0.0" }, "scripts": { - "build": "npm run build --workspace=packages/react-scorm", - "test": "npm run test:run --workspace=packages/react-scorm", - "dev:lib": "npm run dev --workspace=packages/react-scorm", + "build": "npm run build --workspace=packages/scorm", + "test": "npm run test:run --workspace=packages/scorm", + "dev:lib": "npm run dev --workspace=packages/scorm", "dev:example": "npm run dev --workspace=example" } } diff --git a/packages/react-scorm/package.json b/packages/react-scorm/package.json deleted file mode 100644 index d664595..0000000 --- a/packages/react-scorm/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "@studiolxd/react-scorm", - "version": "1.1.0", - "description": "Headless React + TypeScript SCORM 1.2/2004 runtime integration library", - "type": "module", - "main": "./dist/index.cjs", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } - } - }, - "files": [ - "dist" - ], - "scripts": { - "build": "tsup", - "dev": "tsup --watch", - "test": "vitest", - "test:run": "vitest run", - "test:coverage": "vitest run --coverage", - "typecheck": "tsc --noEmit", - "prepublishOnly": "npm run typecheck && npm run test:run && npm run build" - }, - "peerDependencies": { - "react": ">=18.0.0", - "react-dom": ">=18.0.0" - }, - "devDependencies": { - "@testing-library/dom": "^10.4.1", - "@testing-library/react": "^16.0.0", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", - "@vitest/coverage-v8": "^4.1.1", - "jsdom": "^25.0.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "tsup": "^8.0.0", - "typescript": "^5.5.0", - "vitest": "^4.1.1" - }, - "keywords": [ - "scorm", - "scorm-1.2", - "scorm-2004", - "react", - "lms", - "e-learning", - "headless" - ], - "license": "MIT", - "publishConfig": { - "access": "public" - } -} diff --git a/packages/react-scorm/src/react/scorm-provider.tsx b/packages/react-scorm/src/react/scorm-provider.tsx deleted file mode 100644 index 4f8007c..0000000 --- a/packages/react-scorm/src/react/scorm-provider.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useMemo, useRef } from 'react'; -import { ScormContext, type ScormContextValue } from './scorm-context'; -import { createDriver } from '../core/create-driver'; -import { ScormApi } from '../api/scorm-api'; -import { createLogger } from '../debug/logger'; -import type { ScormProviderProps } from '../types/options'; -import type { ScormStatus } from '../types/status'; - -/** - * SCORM Provider component. - * - * Locates the SCORM API object and makes the driver + high-level API - * available to descendants via React context. - * - * **Does NOT auto-initialize.** The consumer must call `api.initialize()` explicitly. - * - * **`status.initialized` is always `false`** in the context value — the provider does - * not track runtime state. Consumers who need reactive initialized/terminated status - * should maintain their own `useState` after calling `api.initialize()`. - * - * **`noLmsBehavior: 'throw'`** will throw synchronously during render. Wrap the - * provider in a React Error Boundary to handle this gracefully. - * - * @example - * ```tsx - * - * - * - * ``` - */ -export function ScormProvider({ version, options = {}, children }: ScormProviderProps) { - // Keep loggerRef in sync with options.debug so toggling debug at runtime - // (e.g. in dev tools) takes effect without requiring a remount. - const loggerRef = useRef(createLogger(options.debug ?? false)); - loggerRef.current = createLogger(options.debug ?? false); - - const contextValue = useMemo(() => { - const driverResult = createDriver(version, options, loggerRef.current); - - // Resolve the concrete version to report in status. When 'auto' resolves to a - // real driver we use the detected version; otherwise fall back to fallbackVersion. - const fallbackVersion = options.fallbackVersion ?? '2004'; - - if (driverResult.ok) { - const driver = driverResult.value; - const api = new ScormApi(driver); - const status: ScormStatus = { - version: driver.version, - apiFound: true, - initialized: false, - terminated: false, - noLmsBehavior: options.noLmsBehavior ?? 'error', - }; - return { status, raw: driver, api }; - } - - // API not found and noLmsBehavior === 'error' - const status: ScormStatus = { - version: version === 'auto' ? fallbackVersion : version, - apiFound: false, - initialized: false, - terminated: false, - noLmsBehavior: options.noLmsBehavior ?? 'error', - }; - return { status, raw: null, api: null }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [version, options.noLmsBehavior, options.maxParentDepth, options.checkOpener, options.debug, options.fallbackVersion]); - - return ( - - {children} - - ); -} diff --git a/packages/react-scorm/src/react/use-scorm-auto-commit.ts b/packages/react-scorm/src/react/use-scorm-auto-commit.ts deleted file mode 100644 index 425429b..0000000 --- a/packages/react-scorm/src/react/use-scorm-auto-commit.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useEffect } from 'react'; -import { useScorm } from './use-scorm'; - -/** - * Opt-in hook that periodically calls `api.commit()` to persist progress. - * - * Many LMS implementations only durably store data on commit, so long sessions - * risk losing progress if the tab is closed abruptly between manual commits. - * This hook flushes on a fixed interval as a safety net. It complements (and does - * not replace) the commit performed by {@link useScormAutoTerminate} on unload. - * - * Pass `0` (or a negative number) to disable — useful for toggling at runtime. - * Commits issued before `initialize()` are harmless: the driver returns an error - * Result, which is intentionally ignored here. - * - * @param intervalMs How often to commit, in milliseconds. Default: 60000 (1 min). - * - * @example - * ```tsx - * function CourseContent() { - * useScormAutoCommit(30_000); // flush every 30s - * } - * ``` - */ -export function useScormAutoCommit(intervalMs = 60_000): void { - const { api } = useScorm(); - - useEffect(() => { - if (!api || !intervalMs || intervalMs <= 0) return; - - const id = setInterval(() => { - // Commit errors are intentionally swallowed — this is a best-effort background - // flush. Surfacing a transient commit failure here would be noise. - api.commit(); - }, intervalMs); - - return () => clearInterval(id); - }, [api, intervalMs]); -} diff --git a/packages/react-scorm/src/react/use-scorm-auto-terminate.ts b/packages/react-scorm/src/react/use-scorm-auto-terminate.ts deleted file mode 100644 index d6659ce..0000000 --- a/packages/react-scorm/src/react/use-scorm-auto-terminate.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { useEffect, useRef } from 'react'; -import { useScorm } from './use-scorm'; - -/** Options for the auto-terminate hook. */ -export interface AutoTerminateOptions { - /** Track session time and set it before terminate. Default: true. */ - trackSessionTime?: boolean; - /** Listen to beforeunload/pagehide events. Default: true. */ - handleUnload?: boolean; - /** Listen to the 'freeze' event (Page Lifecycle API). Default: true. */ - handleFreeze?: boolean; -} - -/** - * Opt-in hook that handles SCORM lifecycle events automatically. - * - * - Calls `api.initialize()` on mount. - * - On unmount or page unload/freeze: commits data, sets session time, and terminates. - * - * This is purely opt-in — if you don't call this hook, nothing happens automatically. - * - * @example - * ```tsx - * function CourseContent() { - * const { api } = useScorm(); - * useScormAutoTerminate({ trackSessionTime: true }); - * // api is auto-initialized, will auto-terminate on unmount/unload - * } - * ``` - */ -export function useScormAutoTerminate(options: AutoTerminateOptions = {}): void { - const { api } = useScorm(); - const sessionStart = useRef(null); - const hasTerminated = useRef(false); - const { - trackSessionTime = true, - handleUnload = true, - handleFreeze = true, - } = options; - - useEffect(() => { - if (!api) return; - - // Reset termination guard so a re-initialized session (e.g. api identity changed) - // can terminate correctly. Without this reset, doTerminate would be a no-op if - // the effect re-runs after the first session was terminated. - hasTerminated.current = false; - sessionStart.current = null; - - const initResult = api.initialize(); - if (initResult.ok) { - sessionStart.current = Date.now(); - } - - const doTerminate = () => { - if (hasTerminated.current) return; - hasTerminated.current = true; - - if (trackSessionTime && sessionStart.current !== null) { - const elapsed = Date.now() - sessionStart.current; - api.setSessionTime(elapsed); - } - // Commit errors are intentionally not re-thrown here — we are in an unload/ - // cleanup path where throwing would swallow the terminate call entirely. - api.commit(); - api.terminate(); - }; - - if (handleUnload) { - window.addEventListener('beforeunload', doTerminate); - window.addEventListener('pagehide', doTerminate, { capture: true }); - } - if (handleFreeze) { - window.addEventListener('freeze', doTerminate); - } - - return () => { - doTerminate(); - if (handleUnload) { - window.removeEventListener('beforeunload', doTerminate); - window.removeEventListener('pagehide', doTerminate, { capture: true }); - } - if (handleFreeze) { - window.removeEventListener('freeze', doTerminate); - } - }; - }, [api, trackSessionTime, handleUnload, handleFreeze]); -} diff --git a/packages/react-scorm/src/react/use-scorm-session.ts b/packages/react-scorm/src/react/use-scorm-session.ts deleted file mode 100644 index 9123e42..0000000 --- a/packages/react-scorm/src/react/use-scorm-session.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { useState, useCallback } from 'react'; -import { useScorm } from './use-scorm'; -import type { ScormContextValue } from './scorm-context'; -import type { Result } from '../result/result'; -import type { ScormError } from '../errors/scorm-error'; - -export interface ScormSessionValue extends ScormContextValue { - /** - * Whether `initialize()` has been called and succeeded in this session. - * Reactive — triggers re-render when it changes. - */ - initialized: boolean; - /** - * Whether `terminate()` has been called and succeeded in this session. - * Reactive — triggers re-render when it changes. - */ - terminated: boolean; - /** - * Call `api.initialize()` and update reactive `initialized` state on success. - * Returns `undefined` when no SCORM API is available (`noLmsBehavior: 'error'`). - */ - initialize: () => Result | undefined; - /** - * Call `api.terminate()` and update reactive `initialized`/`terminated` state on success. - * Returns `undefined` when no SCORM API is available. - */ - terminate: () => Result | undefined; - /** - * Call `api.commit()`. State is not affected. - * Returns `undefined` when no SCORM API is available. - */ - commit: () => Result | undefined; -} - -/** - * Drop-in replacement for `useScorm()` that tracks reactive initialization state. - * - * `useScorm()` intentionally keeps `status.initialized` as a static snapshot — - * the provider does not own lifecycle state. This hook wraps it and exposes - * `initialized` and `terminated` as local React state that updates on successful - * `initialize()` / `terminate()` calls. - * - * Use this hook when you need to re-render based on session state without - * managing that state yourself. - * - * @example - * ```tsx - * function Course() { - * const { initialized, initialize, terminate, api } = useScormSession(); - * - * useEffect(() => { initialize(); }, [initialize]); - * - * if (!initialized) return

Connecting…

; - * return ; - * } - * ``` - */ -export function useScormSession(): ScormSessionValue { - const context = useScorm(); - const { api } = context; - const [initialized, setInitialized] = useState(false); - const [terminated, setTerminated] = useState(false); - - const initialize = useCallback((): Result | undefined => { - if (!api) return undefined; - const result = api.initialize(); - if (result.ok) setInitialized(true); - return result; - }, [api]); - - const terminate = useCallback((): Result | undefined => { - if (!api) return undefined; - const result = api.terminate(); - if (result.ok) { - setInitialized(false); - setTerminated(true); - } - return result; - }, [api]); - - const commit = useCallback((): Result | undefined => { - if (!api) return undefined; - return api.commit(); - }, [api]); - - return { ...context, initialized, terminated, initialize, terminate, commit }; -} diff --git a/packages/react-scorm/tsup.config.ts b/packages/react-scorm/tsup.config.ts deleted file mode 100644 index c70f5ea..0000000 --- a/packages/react-scorm/tsup.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from 'tsup'; - -export default defineConfig({ - entry: ['src/index.ts'], - format: ['esm', 'cjs'], - dts: true, - sourcemap: true, - clean: true, - splitting: false, - treeshake: true, - external: ['react', 'react-dom'], - minify: false, -}); diff --git a/packages/react-scorm/.github/workflows/ci.yml b/packages/scorm/.github/workflows/ci.yml similarity index 100% rename from packages/react-scorm/.github/workflows/ci.yml rename to packages/scorm/.github/workflows/ci.yml diff --git a/packages/react-scorm/.gitignore b/packages/scorm/.gitignore similarity index 100% rename from packages/react-scorm/.gitignore rename to packages/scorm/.gitignore diff --git a/packages/react-scorm/.npmignore b/packages/scorm/.npmignore similarity index 100% rename from packages/react-scorm/.npmignore rename to packages/scorm/.npmignore diff --git a/packages/react-scorm/CHANGELOG.md b/packages/scorm/CHANGELOG.md similarity index 74% rename from packages/react-scorm/CHANGELOG.md rename to packages/scorm/CHANGELOG.md index 5fe0222..f6cc302 100644 --- a/packages/react-scorm/CHANGELOG.md +++ b/packages/scorm/CHANGELOG.md @@ -1,6 +1,32 @@ # Changelog -All notable changes to `@studiolxd/react-scorm` are documented here. +All notable changes to `@studiolxd/scorm` are documented here. + +## [2.0.0] - 2026-06-15 + +### Changed (BREAKING) +- **Renamed** `@studiolxd/react-scorm` → `@studiolxd/scorm`. The React API moved to the + `@studiolxd/scorm/react` subpath (`import { ScormProvider, useScorm } from '@studiolxd/scorm/react'`). +- The package root (`@studiolxd/scorm`) is now **framework-agnostic** — no React imports. + +### Added +- **Framework-agnostic core**: `createScormSession(version, options)` — an observable + session with `on('change')` / `off` / `destroy` and reactive `status`. +- **Vanilla lifecycle helpers**: `autoTerminate(session, opts)` and `autoCommit(session, ms)`, + each returning a `dispose()` (the React hooks now delegate to these). +- **Adapters as subpaths**: + - `@studiolxd/scorm/vue` — `useScorm()` composable (Vue 3.3+) + - `@studiolxd/scorm/angular` — `provideScorm()` + `SCORM` token (Angular 17+, decorator-free) + - `@studiolxd/scorm/svelte` — `createScormStore()` (Svelte 4+) + - `@studiolxd/scorm/wc` — `` Web Component +- **IIFE/global build** for plain ` +{#if $status.initialized}live{/if} +``` + +**Web Component / reines HTML** +```html + + + +``` + +**CDN ` + +``` + ## ScormProvider Die ``-Komponente sucht die LMS-API und stellt sie über den Kontext bereit. Sie initialisiert **nicht** automatisch — du musst `api.initialize()` explizit aufrufen. @@ -81,9 +158,9 @@ Die ``-Komponente sucht die LMS-API und stellt sie über den Kont ### `noLmsBehavior`-Optionen | Wert | Verhalten | -|------|-----------| +|-------|----------| | `"error"` (Standard) | `api` ist `null`, `status.apiFound` ist `false`. Operationen können nicht aufgerufen werden. | -| `"mock"` | Verwendet eine In-Memory-mock-SCORM-API. Ideal für lokale Entwicklung und Tests. | +| `"mock"` | Verwendet eine In-Memory-Mock-SCORM-API. Ideal für lokale Entwicklung und Tests. | | `"throw"` | Wirft einen `ScormError` beim Rendern. Mit einer React **Error Boundary** umschließen. | > **`"throw"`-Modus** wirft synchron innerhalb von `useMemo`. Ohne eine Error Boundary propagiert der Fehler nach oben und bringt den gesamten Teilbaum zum Absturz. Umschließe `` bei Verwendung dieses Modus immer mit einer Error Boundary. @@ -95,12 +172,12 @@ const { status, api, raw } = useScorm(); ``` | Feld | Typ | Beschreibung | -|------|-----|--------------| +|-------|------|-------------| | `status` | `ScormStatus` | Verbindungsstatus: `apiFound`, `initialized`*, `terminated`*, `version`, `noLmsBehavior` | | `api` | `IScormApi \| null` | Versionsneutrale High-Level-API. `null`, wenn bei `"error"`-Verhalten keine API gefunden wurde. | -| `raw` | `IScormDriver \| null` | Low-Level-driver für direkte API-Aufrufe (Notausstieg). | +| `raw` | `IScormDriver \| null` | Low-Level-Treiber für direkte API-Aufrufe (Notausstieg). | -> **Hinweis:** `status.initialized` und `status.terminated` sind im Kontextwert immer `false` — der provider verfolgt den Laufzeitzustand nicht. Wenn du reaktiven initialisierten/terminierten Zustand benötigst, verwalte ihn selbst mit `useState` nach dem Aufruf von `api.initialize()` und `api.terminate()`. +> **Hinweis:** Der `status` aus `useScorm()` ist ein zur Renderzeit gelesener Schnappschuss. Für **reaktiven** `initialized`/`terminated`-Zustand (und umschlossene `initialize`/`terminate`/`commit`) verwende **`useScormSession()`**, das die zugrunde liegende Session über `useSyncExternalStore` abonniert. ## High-Level-API @@ -268,14 +345,14 @@ if (result.ok) { Hilfsfunktionen: `isOk()`, `isErr()`, `unwrap()`, `unwrapOr()`. -> **Sicherheit:** `errorString` und `diagnostic` sind Zeichenketten, die direkt vom LMS stammen. Rendere sie niemals über `innerHTML` oder eine nicht-bereinigte DOM-API — behandle sie als nicht vertrauenswürdige Eingaben und escape HTML vor jeder DOM-Einfügung. +> **Sicherheit:** `errorString` und `diagnostic` sind Zeichenketten, die direkt vom LMS stammen. Rendere sie niemals über `innerHTML` oder eine nicht bereinigte DOM-API — behandle sie als nicht vertrauenswürdige Eingaben und escape HTML vor jeder DOM-Einfügung. ## useScormSession (reaktiver Zustand, opt-in) -`useScorm()` hält `status.initialized` bewusst als statischen Schnappschuss — der provider verfolgt den Lifecycle-Zustand nicht. Wenn du `initialized` und `terminated` als reaktiven React-Zustand benötigst (um Re-Renders auszulösen), verwende stattdessen `useScormSession()`. +`useScorm()` hält `status.initialized` bewusst als statischen Schnappschuss — der Provider verfolgt den Lifecycle-Zustand nicht. Wenn du `initialized` und `terminated` als reaktiven React-Zustand benötigst (um Re-Renders auszulösen), verwende stattdessen `useScormSession()`. ```tsx -import { useScormSession } from '@studiolxd/react-scorm'; +import { useScormSession } from '@studiolxd/scorm/react'; function Course() { const { initialized, initialize, terminate, api } = useScormSession(); @@ -290,7 +367,7 @@ function Course() { `useScormSession()` ist eine Obermenge von `useScorm()` — es gibt alles zurück, was `useScorm()` zurückgibt (`api`, `status`, `raw`), plus: | Feld | Typ | Beschreibung | -|------|-----|--------------| +|-------|------|-------------| | `initialized` | `boolean` | `true` nach erfolgreichem `initialize()`-Aufruf. Reaktiv. | | `terminated` | `boolean` | `true` nach erfolgreichem `terminate()`-Aufruf. Reaktiv. | | `initialize()` | `Result \| undefined` | Ruft `api.initialize()` auf und aktualisiert den Zustand. `undefined` wenn keine API vorhanden. | @@ -322,7 +399,7 @@ function CourseContent() { Typisierte Hilfsfunktionen zum Erstellen indizierter CMI-Pfade: ```ts -import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/react-scorm'; +import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/scorm/react'; scorm12ObjectivePath(0, 'score.raw') // "cmi.objectives.0.score.raw" scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_response" @@ -331,7 +408,7 @@ scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_ ## Zeit-Formatierer ```ts -import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/react-scorm'; +import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/scorm/react'; formatScorm12Time(90000) // "00:01:30.00" formatScorm2004Time(90000) // "PT1M30S" @@ -339,7 +416,7 @@ formatScorm2004Time(90000) // "PT1M30S" ## Tests -Verwende `noLmsBehavior: 'mock'` für Tests. Der mock nutzt einen In-Memory-Speicher mit echtem driver-Verhalten. +Verwende `noLmsBehavior: 'mock'` für Tests. Der Mock nutzt einen In-Memory-Speicher mit echtem Treiber-Verhalten. ```tsx // In tests @@ -348,10 +425,10 @@ Verwende `noLmsBehavior: 'mock'` für Tests. Der mock nutzt einen In-Memory-Spei ``` -Du kannst die mock-Klassen auch direkt verwenden: +Du kannst die Mock-Klassen auch direkt verwenden: ```ts -import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/react-scorm'; +import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/scorm/react'; const mockApi = new MockScorm12Api(); const driver = new Scorm12Driver(mockApi, createLogger(false)); @@ -365,7 +442,7 @@ api.initialize(); Alle CMI-Pfade sind streng typisiert: ```ts -import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/react-scorm'; +import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/scorm/react'; // These types catch typos at compile time: const path: Scorm12CmiPath = 'cmi.core.lesson_status'; // OK @@ -375,7 +452,7 @@ const path: Scorm12CmiPath = 'cmi.core.lesson_status'; // OK ## Weitere Dokumentation - [SCORM 1.2 vs 2004 Mapping-Tabelle](./docs/scorm-mapping-table.md) -- [Testen mit dem mock-Modus](./docs/mock-mode.md) +- [Testen mit dem Mock-Modus](./docs/mock-mode.md) - [Integrationsleitfaden](./docs/integration-guide.md) ## Lizenz diff --git a/packages/react-scorm/README.es.md b/packages/scorm/README.es.md similarity index 75% rename from packages/react-scorm/README.es.md rename to packages/scorm/README.es.md index ed28816..a4d5531 100644 --- a/packages/react-scorm/README.es.md +++ b/packages/scorm/README.es.md @@ -1,30 +1,57 @@ 🌐 [English](README.md) · [Español](README.es.md) · [Français](README.fr.md) · [Português](README.pt.md) · [Deutsch](README.de.md) · [Polski](README.pl.md) -# @studiolxd/react-scorm +# @studiolxd/scorm -Una librería headless de React + TypeScript para la integración del runtime SCORM. Proporciona un componente `` y un hook `useScorm()` para comunicarse con un LMS mediante SCORM 1.2 o SCORM 2004 (4.ª edición). +Una librería TypeScript headless para la integración del runtime SCORM. Un **núcleo agnóstico al framework** se comunica con un LMS mediante SCORM 1.2 o SCORM 2004 (4.ª edición), con adaptadores ligeros para **React, Vue, Angular, Svelte, Web Components** y JavaScript vanilla / ` +{#if $status.initialized}live{/if} +``` + +**Web Component / HTML plano** +```html + + + +``` + +**CDN ` + +``` + ## ScormProvider El componente `` localiza la API del LMS y la pone a disposición a través del contexto. **No** inicializa automáticamente — debes llamar a `api.initialize()` de forma explícita. @@ -100,7 +177,7 @@ const { status, api, raw } = useScorm(); | `api` | `IScormApi \| null` | API de alto nivel independiente de la versión. `null` cuando no se encuentra la API con el comportamiento `"error"`. | | `raw` | `IScormDriver \| null` | Driver de bajo nivel para llamadas directas a la API (vía de escape). | -> **Nota:** `status.initialized` y `status.terminated` son siempre `false` en el valor del contexto — el provider no rastrea el estado del runtime. Si necesitas estado reactivo de initialized/terminated, mantenlo tú mismo con `useState` después de llamar a `api.initialize()` y `api.terminate()`. +> **Nota:** el `status` de `useScorm()` es una instantánea leída en el momento del renderizado. Para estado **reactivo** de `initialized`/`terminated` (y métodos envueltos `initialize`/`terminate`/`commit`), usa **`useScormSession()`**, que se suscribe a la sesión subyacente mediante `useSyncExternalStore`. ## API de alto nivel @@ -275,7 +352,7 @@ Funciones de ayuda: `isOk()`, `isErr()`, `unwrap()`, `unwrapOr()`. `useScorm()` mantiene intencionalmente `status.initialized` como una instantánea estática — el provider no rastrea el estado del ciclo de vida. Si necesitas `initialized` y `terminated` como estado reactivo de React (para disparar re-renderizados), usa `useScormSession()` en su lugar. ```tsx -import { useScormSession } from '@studiolxd/react-scorm'; +import { useScormSession } from '@studiolxd/scorm/react'; function Course() { const { initialized, initialize, terminate, api } = useScormSession(); @@ -322,7 +399,7 @@ function CourseContent() { Funciones de ayuda tipadas para construir rutas CMI indexadas: ```ts -import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/react-scorm'; +import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/scorm/react'; scorm12ObjectivePath(0, 'score.raw') // "cmi.objectives.0.score.raw" scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_response" @@ -331,7 +408,7 @@ scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_ ## Formateadores de tiempo ```ts -import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/react-scorm'; +import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/scorm/react'; formatScorm12Time(90000) // "00:01:30.00" formatScorm2004Time(90000) // "PT1M30S" @@ -351,7 +428,7 @@ Usa `noLmsBehavior: 'mock'` para las pruebas. El mock utiliza un almacén en mem También puedes usar las clases mock directamente: ```ts -import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/react-scorm'; +import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/scorm/react'; const mockApi = new MockScorm12Api(); const driver = new Scorm12Driver(mockApi, createLogger(false)); @@ -365,7 +442,7 @@ api.initialize(); Todas las rutas CMI están tipadas de forma estricta: ```ts -import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/react-scorm'; +import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/scorm/react'; // These types catch typos at compile time: const path: Scorm12CmiPath = 'cmi.core.lesson_status'; // OK diff --git a/packages/react-scorm/README.fr.md b/packages/scorm/README.fr.md similarity index 71% rename from packages/react-scorm/README.fr.md rename to packages/scorm/README.fr.md index 673e76a..09d20f6 100644 --- a/packages/react-scorm/README.fr.md +++ b/packages/scorm/README.fr.md @@ -1,30 +1,57 @@ 🌐 [English](README.md) · [Español](README.es.md) · [Français](README.fr.md) · [Português](README.pt.md) · [Deutsch](README.de.md) · [Polski](README.pl.md) -# @studiolxd/react-scorm +# @studiolxd/scorm -Une bibliothèque React + TypeScript sans interface graphique pour l'intégration du runtime SCORM. Elle fournit un composant `` et un hook `useScorm()` permettant de communiquer avec un LMS via SCORM 1.2 ou SCORM 2004 (4e édition). +Une bibliothèque TypeScript headless pour l'intégration du runtime SCORM. Un **cœur agnostique vis-à-vis du framework** communique avec un LMS via SCORM 1.2 ou SCORM 2004 (4e édition), accompagné d'adaptateurs légers pour **React, Vue, Angular, Svelte, Web Components** et du JavaScript vanilla / ` +{#if $status.initialized}live{/if} +``` + +**Web Component / HTML simple** +```html + + + +``` + +**CDN ` + +``` + ## ScormProvider -Le composant `` localise l'API du LMS et la rend disponible via le contexte React. Il **n'initialise pas automatiquement** — vous devez appeler `api.initialize()` explicitement. +Le composant `` localise l'API du LMS et la rend disponible via le contexte. Il **n'initialise pas automatiquement** — vous devez appeler `api.initialize()` explicitement. ```tsx ` localise l'API du LMS et la rend disponible via l ### Options de `noLmsBehavior` | Valeur | Comportement | -|--------|--------------| +|-------|----------| | `"error"` (défaut) | `api` vaut `null`, `status.apiFound` vaut `false`. Aucune opération ne peut être appelée. | | `"mock"` | Utilise une API SCORM mock en mémoire. Idéal pour le développement local et les tests. | | `"throw"` | Lève une `ScormError` pendant le rendu. À encapsuler dans une **Error Boundary** React. | -> **Mode `"throw"`** : l'erreur est levée de manière synchrone dans `useMemo`. Sans Error Boundary, elle se propage vers le haut et fait planter tout le sous-arbre. Encapsulez toujours `` dans une Error Boundary lorsque vous utilisez ce mode. +> **Le mode `"throw"`** lève l'exception de manière synchrone à l'intérieur de `useMemo`. Sans Error Boundary, l'erreur se propage vers le haut et fait planter tout le sous-arbre. Encapsulez toujours `` dans une Error Boundary lorsque vous utilisez ce mode. ## useScorm() @@ -100,7 +177,7 @@ const { status, api, raw } = useScorm(); | `api` | `IScormApi \| null` | API de haut niveau indépendante de la version. `null` si aucune API n'est trouvée avec le comportement `"error"`. | | `raw` | `IScormDriver \| null` | Driver bas niveau pour les appels directs à l'API (accès de secours). | -> **Remarque :** `status.initialized` et `status.terminated` sont toujours `false` dans la valeur de contexte — le provider ne suit pas l'état du runtime. Si vous avez besoin d'un état réactif pour `initialized`/`terminated`, gérez-le vous-même avec `useState` après les appels à `api.initialize()` et `api.terminate()`. +> **Remarque :** le `status` retourné par `useScorm()` est un instantané lu au moment du rendu. Pour un état **réactif** `initialized`/`terminated` (et des méthodes `initialize`/`terminate`/`commit` encapsulées), utilisez **`useScormSession()`**, qui s'abonne à la session sous-jacente via `useSyncExternalStore`. ## API de haut niveau @@ -275,7 +352,7 @@ Fonctions utilitaires : `isOk()`, `isErr()`, `unwrap()`, `unwrapOr()`. `useScorm()` maintient intentionnellement `status.initialized` comme un instantané statique — le provider ne suit pas l'état du cycle de vie. Si vous avez besoin de `initialized` et `terminated` comme état React réactif (pour déclencher des rendus), utilisez `useScormSession()` à la place. ```tsx -import { useScormSession } from '@studiolxd/react-scorm'; +import { useScormSession } from '@studiolxd/scorm/react'; function Course() { const { initialized, initialize, terminate, api } = useScormSession(); @@ -322,7 +399,7 @@ function CourseContent() { Fonctions utilitaires typées pour construire des chemins CMI indexés : ```ts -import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/react-scorm'; +import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/scorm/react'; scorm12ObjectivePath(0, 'score.raw') // "cmi.objectives.0.score.raw" scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_response" @@ -331,7 +408,7 @@ scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_ ## Formateurs de temps ```ts -import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/react-scorm'; +import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/scorm/react'; formatScorm12Time(90000) // "00:01:30.00" formatScorm2004Time(90000) // "PT1M30S" @@ -351,7 +428,7 @@ Utilisez `noLmsBehavior: 'mock'` pour les tests. Le mock utilise un store en mé Vous pouvez également utiliser les classes mock directement : ```ts -import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/react-scorm'; +import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/scorm/react'; const mockApi = new MockScorm12Api(); const driver = new Scorm12Driver(mockApi, createLogger(false)); @@ -365,7 +442,7 @@ api.initialize(); Tous les chemins CMI sont strictement typés : ```ts -import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/react-scorm'; +import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/scorm/react'; // These types catch typos at compile time: const path: Scorm12CmiPath = 'cmi.core.lesson_status'; // OK diff --git a/packages/react-scorm/README.md b/packages/scorm/README.md similarity index 74% rename from packages/react-scorm/README.md rename to packages/scorm/README.md index df77457..867e76a 100644 --- a/packages/react-scorm/README.md +++ b/packages/scorm/README.md @@ -1,30 +1,57 @@ 🌐 [English](README.md) · [Español](README.es.md) · [Français](README.fr.md) · [Português](README.pt.md) · [Deutsch](README.de.md) · [Polski](README.pl.md) -# @studiolxd/react-scorm +# @studiolxd/scorm -A headless React + TypeScript library for SCORM runtime integration. Provides a `` and `useScorm()` hook to communicate with an LMS via SCORM 1.2 or SCORM 2004 (4th Edition). +A headless TypeScript library for SCORM runtime integration. A **framework-agnostic core** talks to an LMS via SCORM 1.2 or SCORM 2004 (4th Edition), with thin adapters for **React, Vue, Angular, Svelte, Web Components**, and plain vanilla JS / ` +{#if $status.initialized}live{/if} +``` + +**Web Component / plain HTML** +```html + + + +``` + +**CDN ` + +``` + ## ScormProvider The `` component locates the LMS API and makes it available via context. It does **not** auto-initialize — you must call `api.initialize()` explicitly. @@ -100,7 +177,7 @@ const { status, api, raw } = useScorm(); | `api` | `IScormApi \| null` | High-level version-agnostic API. `null` when no API found with `"error"` behavior. | | `raw` | `IScormDriver \| null` | Low-level driver for direct API calls (escape hatch). | -> **Note:** `status.initialized` and `status.terminated` are always `false` in the context value — the provider does not track runtime state. If you need reactive initialized/terminated state, maintain it yourself with `useState` after calling `api.initialize()` and `api.terminate()`. +> **Note:** the `status` from `useScorm()` is a snapshot read at render time. For **reactive** `initialized`/`terminated` state (and wrapped `initialize`/`terminate`/`commit`), use **`useScormSession()`**, which subscribes to the underlying session via `useSyncExternalStore`. ## High-Level API @@ -275,7 +352,7 @@ Helper functions: `isOk()`, `isErr()`, `unwrap()`, `unwrapOr()`. `useScorm()` intentionally keeps `status.initialized` as a static snapshot — the provider does not track lifecycle state. If you need `initialized` and `terminated` as reactive React state (to trigger re-renders), use `useScormSession()` instead. ```tsx -import { useScormSession } from '@studiolxd/react-scorm'; +import { useScormSession } from '@studiolxd/scorm/react'; function Course() { const { initialized, initialize, terminate, api } = useScormSession(); @@ -322,7 +399,7 @@ function CourseContent() { Typed helper functions for building indexed CMI paths: ```ts -import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/react-scorm'; +import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/scorm/react'; scorm12ObjectivePath(0, 'score.raw') // "cmi.objectives.0.score.raw" scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_response" @@ -331,7 +408,7 @@ scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_ ## Time Formatters ```ts -import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/react-scorm'; +import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/scorm/react'; formatScorm12Time(90000) // "00:01:30.00" formatScorm2004Time(90000) // "PT1M30S" @@ -351,7 +428,7 @@ Use `noLmsBehavior: 'mock'` for testing. The mock uses an in-memory store with r You can also use the mock classes directly: ```ts -import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/react-scorm'; +import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/scorm/react'; const mockApi = new MockScorm12Api(); const driver = new Scorm12Driver(mockApi, createLogger(false)); @@ -365,7 +442,7 @@ api.initialize(); All CMI paths are strictly typed: ```ts -import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/react-scorm'; +import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/scorm/react'; // These types catch typos at compile time: const path: Scorm12CmiPath = 'cmi.core.lesson_status'; // OK diff --git a/packages/react-scorm/README.pl.md b/packages/scorm/README.pl.md similarity index 74% rename from packages/react-scorm/README.pl.md rename to packages/scorm/README.pl.md index dcd0dd4..564407f 100644 --- a/packages/react-scorm/README.pl.md +++ b/packages/scorm/README.pl.md @@ -1,30 +1,57 @@ 🌐 [English](README.md) · [Español](README.es.md) · [Français](README.fr.md) · [Português](README.pt.md) · [Deutsch](README.de.md) · [Polski](README.pl.md) -# @studiolxd/react-scorm +# @studiolxd/scorm -Bezinterfejsowa biblioteka React + TypeScript do integracji ze środowiskiem uruchomieniowym SCORM. Udostępnia komponent `` oraz hook `useScorm()`, umożliwiające komunikację z LMS za pośrednictwem SCORM 1.2 lub SCORM 2004 (4. edycja). +Bezgłowa biblioteka TypeScript do integracji ze środowiskiem uruchomieniowym SCORM. **Rdzeń niezależny od frameworka** komunikuje się z LMS za pośrednictwem SCORM 1.2 lub SCORM 2004 (4. edycja), z cienkimi adapterami dla **React, Vue, Angular, Svelte, Web Components** oraz czystego JavaScriptu (vanilla) / ` +{#if $status.initialized}live{/if} +``` + +**Web Component / czysty HTML** +```html + + + +``` + +**CDN ` + +``` + ## ScormProvider Komponent `` lokalizuje API LMS i udostępnia je przez kontekst. **Nie** inicjalizuje się automatycznie — musisz jawnie wywołać `api.initialize()`. @@ -81,7 +158,7 @@ Komponent `` lokalizuje API LMS i udostępnia je przez kontekst. ### Opcje `noLmsBehavior` | Wartość | Zachowanie | -|---------|------------| +|-------|----------| | `"error"` (domyślna) | `api` jest `null`, `status.apiFound` wynosi `false`. Operacje nie mogą być wywoływane. | | `"mock"` | Używa in-memory mock SCORM API. Idealne do lokalnego developmentu i testów. | | `"throw"` | Rzuca `ScormError` podczas renderowania. Opakuj komponent w React **Error Boundary**. | @@ -95,12 +172,12 @@ const { status, api, raw } = useScorm(); ``` | Pole | Typ | Opis | -|------|-----|------| +|-------|------|-------------| | `status` | `ScormStatus` | Stan połączenia: `apiFound`, `initialized`*, `terminated`*, `version`, `noLmsBehavior` | | `api` | `IScormApi \| null` | Wysokopoziomowe API niezależne od wersji. `null`, gdy nie znaleziono API przy zachowaniu `"error"`. | | `raw` | `IScormDriver \| null` | Niskopoziomowy driver do bezpośrednich wywołań API (wyjście awaryjne). | -> **Uwaga:** `status.initialized` i `status.terminated` mają zawsze wartość `false` w wartości kontekstu — provider nie śledzi stanu środowiska uruchomieniowego. Jeśli potrzebujesz reaktywnego stanu `initialized`/`terminated`, zarządzaj nim samodzielnie przez `useState` po wywołaniu `api.initialize()` i `api.terminate()`. +> **Uwaga:** `status` z `useScorm()` to migawka odczytana w momencie renderowania. Aby uzyskać **reaktywny** stan `initialized`/`terminated` (oraz opakowane `initialize`/`terminate`/`commit`), użyj **`useScormSession()`**, który subskrybuje bazową sesję za pośrednictwem `useSyncExternalStore`. ## Wysokopoziomowe API @@ -275,7 +352,7 @@ Funkcje pomocnicze: `isOk()`, `isErr()`, `unwrap()`, `unwrapOr()`. `useScorm()` celowo utrzymuje `status.initialized` jako statyczną migawkę — provider nie śledzi stanu cyklu życia. Jeśli potrzebujesz `initialized` i `terminated` jako reaktywnego stanu React (wyzwalającego ponowne renderowanie), użyj zamiast tego `useScormSession()`. ```tsx -import { useScormSession } from '@studiolxd/react-scorm'; +import { useScormSession } from '@studiolxd/scorm/react'; function Course() { const { initialized, initialize, terminate, api } = useScormSession(); @@ -290,7 +367,7 @@ function Course() { `useScormSession()` jest nadzbiorem `useScorm()` — zwraca wszystko, co `useScorm()` (`api`, `status`, `raw`), a dodatkowo: | Pole | Typ | Opis | -|------|-----|------| +|-------|------|-------------| | `initialized` | `boolean` | `true` po pomyślnym wywołaniu `initialize()`. Reaktywne. | | `terminated` | `boolean` | `true` po pomyślnym wywołaniu `terminate()`. Reaktywne. | | `initialize()` | `Result \| undefined` | Wywołuje `api.initialize()` i aktualizuje stan. `undefined`, gdy brak API. | @@ -322,7 +399,7 @@ function CourseContent() { Typowane funkcje pomocnicze do budowania indeksowanych ścieżek CMI: ```ts -import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/react-scorm'; +import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/scorm/react'; scorm12ObjectivePath(0, 'score.raw') // "cmi.objectives.0.score.raw" scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_response" @@ -331,7 +408,7 @@ scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_ ## Formatery czasu ```ts -import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/react-scorm'; +import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/scorm/react'; formatScorm12Time(90000) // "00:01:30.00" formatScorm2004Time(90000) // "PT1M30S" @@ -351,7 +428,7 @@ Do testów używaj `noLmsBehavior: 'mock'`. Mock korzysta z magazynu in-memory z Możesz też bezpośrednio korzystać z klas mock: ```ts -import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/react-scorm'; +import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/scorm/react'; const mockApi = new MockScorm12Api(); const driver = new Scorm12Driver(mockApi, createLogger(false)); @@ -365,7 +442,7 @@ api.initialize(); Wszystkie ścieżki CMI są ściśle typowane: ```ts -import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/react-scorm'; +import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/scorm/react'; // These types catch typos at compile time: const path: Scorm12CmiPath = 'cmi.core.lesson_status'; // OK diff --git a/packages/react-scorm/README.pt.md b/packages/scorm/README.pt.md similarity index 55% rename from packages/react-scorm/README.pt.md rename to packages/scorm/README.pt.md index ec893e0..69e5ea1 100644 --- a/packages/react-scorm/README.pt.md +++ b/packages/scorm/README.pt.md @@ -1,30 +1,57 @@ 🌐 [English](README.md) · [Español](README.es.md) · [Français](README.fr.md) · [Português](README.pt.md) · [Deutsch](README.de.md) · [Polski](README.pl.md) -# @studiolxd/react-scorm +# @studiolxd/scorm -Uma biblioteca headless React + TypeScript para integração com o runtime SCORM. Fornece um `` e o hook `useScorm()` para comunicação com um LMS via SCORM 1.2 ou SCORM 2004 (4ª Edição). +Uma biblioteca TypeScript headless para integração com o runtime SCORM. Um **núcleo agnóstico de framework** comunica com um LMS através de SCORM 1.2 ou SCORM 2004 (4ª Edição), com adaptadores leves para **React, Vue, Angular, Svelte, Web Components** e JavaScript vanilla / ` +{#if $status.initialized}live{/if} +``` + +**Web Component / HTML simples** +```html + + + +``` + +**CDN ` + +``` + ## ScormProvider -O componente `` localiza a API do LMS e a disponibiliza via contexto. Ele **não** inicializa automaticamente — você deve chamar `api.initialize()` explicitamente. +O componente `` localiza a API do LMS e disponibiliza-a através do contexto. **Não** inicializa automaticamente — tem de chamar `api.initialize()` explicitamente. ```tsx ` localiza a API do LMS e a disponibiliza via conte ### Opções de `noLmsBehavior` | Valor | Comportamento | -|-------|---------------| -| `"error"` (padrão) | `api` é `null`, `status.apiFound` é `false`. As operações não podem ser chamadas. | +|-------|----------| +| `"error"` (predefinição) | `api` é `null`, `status.apiFound` é `false`. As operações não podem ser chamadas. | | `"mock"` | Usa uma API SCORM mock em memória. Ideal para desenvolvimento local e testes. | -| `"throw"` | Lança um `ScormError` durante a renderização. Envolva com um **Error Boundary** do React. | +| `"throw"` | Lança um `ScormError` durante o render. Envolva com um **Error Boundary** do React. | -> **Modo `"throw"`** lança de forma síncrona dentro do `useMemo`. Sem um Error Boundary, o erro se propagará e derrubará toda a subárvore. Sempre envolva o `` em um Error Boundary ao usar esse modo. +> O **modo `"throw"`** lança a exceção de forma síncrona dentro de `useMemo`. Sem um Error Boundary, o erro propaga-se para cima e faz crash de toda a subárvore. Envolva sempre `` num Error Boundary ao usar este modo. ## useScorm() @@ -95,18 +172,18 @@ const { status, api, raw } = useScorm(); ``` | Campo | Tipo | Descrição | -|-------|------|-----------| -| `status` | `ScormStatus` | Status da conexão: `apiFound`, `initialized`*, `terminated`*, `version`, `noLmsBehavior` | -| `api` | `IScormApi \| null` | API de alto nível agnóstica à versão. `null` quando nenhuma API é encontrada com o comportamento `"error"`. | +|-------|------|-------------| +| `status` | `ScormStatus` | Estado da ligação: `apiFound`, `initialized`*, `terminated`*, `version`, `noLmsBehavior` | +| `api` | `IScormApi \| null` | API de alto nível agnóstica de versão. `null` quando não é encontrada API com o comportamento `"error"`. | | `raw` | `IScormDriver \| null` | Driver de baixo nível para chamadas diretas à API (escape hatch). | -> **Nota:** `status.initialized` e `status.terminated` são sempre `false` no valor de contexto — o provider não rastreia o estado do runtime. Se você precisar de estado reativo de inicialização/encerramento, mantenha-o você mesmo com `useState` após chamar `api.initialize()` e `api.terminate()`. +> **Nota:** o `status` de `useScorm()` é um snapshot lido no momento do render. Para estado **reativo** de `initialized`/`terminated` (e métodos `initialize`/`terminate`/`commit` envolvidos), use **`useScormSession()`**, que subscreve a sessão subjacente através de `useSyncExternalStore`. ## API de Alto Nível -Todos os métodos retornam `Result` — verifique `result.ok` antes de acessar o valor. +Todos os métodos devolvem `Result` — verifique `result.ok` antes de aceder ao valor. -### Ciclo de Vida +### Ciclo de vida ```ts api.initialize() // Result @@ -114,7 +191,7 @@ api.terminate() // Result api.commit() // Result ``` -### Status +### Estado ```ts api.setComplete() // 1.2: cmi.core.lesson_status="completed" | 2004: cmi.completion_status="completed" @@ -132,7 +209,7 @@ api.setScore({ raw: 85, min: 0, max: 100, scaled: 0.85 }) // scaled is 2004 onl api.getScore() // Result ``` -**Validação:** `raw`, `min` e `max` devem ser números finitos (NaN/Infinity são rejeitados com o código de erro 405). `scaled` deve estar no intervalo `[-1, 1]` (código de erro 407 se fora do intervalo). Para SCORM 1.2, `scaled` é silenciosamente ignorado. +**Validação:** `raw`, `min` e `max` têm de ser números finitos (NaN/Infinity são rejeitados com o código de erro 405). `scaled` tem de estar no intervalo `[-1, 1]` (código de erro 407 se estiver fora do intervalo). No SCORM 1.2, `scaled` é silenciosamente ignorado. ### Localização e Dados de Suspensão @@ -150,7 +227,7 @@ api.setSessionTime(90000) // 1.2: "00:01:30.00" | 2004: "PT1M30S" api.getTotalTime() ``` -### Informações do Aprendiz (somente leitura) +### Informação do Aluno (apenas leitura) ```ts api.getLearnerId() @@ -211,7 +288,7 @@ api.setPreference('language', 'en') api.getPreferences() // Result, ScormError> ``` -### Progresso (somente SCORM 2004) +### Progresso (apenas SCORM 2004) ```ts api.setProgressMeasure(0.75) // value must be in range [0, 1] — error 407 otherwise @@ -225,7 +302,7 @@ api.setExit('suspend') // 1.2: cmi.core.exit | 2004: cmi.exit // Common values: 'suspend', 'logout', 'time-out', '' ``` -### Dados do Aluno (somente leitura, definido pelo LMS) +### Dados do Estudante (apenas leitura, definidos pelo LMS) ```ts api.getMasteryScore() // 1.2: cmi.student_data.mastery_score | 2004: cmi.scaled_passing_score @@ -233,7 +310,7 @@ api.getMaxTimeAllowed() // 1.2: cmi.student_data.max_time_allowed | 2004: cmi. api.getTimeLimitAction() // 1.2: cmi.student_data.time_limit_action | 2004: cmi.time_limit_action ``` -### Navegação (somente SCORM 2004) +### Navegação (apenas SCORM 2004) ```ts api.setNavRequest('continue') // no-op (returns ok) for SCORM 1.2 @@ -241,7 +318,7 @@ api.getNavRequestValid('continue') // 'continue' | 'previous' api.getNavRequestValid('previous') ``` -### Acesso Direto (escape hatch) +### Acesso Raw (escape hatch) ```ts api.getRaw('cmi.core.lesson_status') @@ -250,7 +327,7 @@ api.setRaw('cmi.core.lesson_status', 'completed') ## Tratamento de Erros -Todas as operações retornam `Result` em vez de lançar exceções: +Todas as operações devolvem `Result` em vez de lançar exceções: ```ts const result = api.setComplete(); @@ -268,14 +345,14 @@ if (result.ok) { Funções auxiliares: `isOk()`, `isErr()`, `unwrap()`, `unwrapOr()`. -> **Segurança:** `errorString` e `diagnostic` são strings provenientes diretamente do LMS. Não as renderize via `innerHTML` ou qualquer API DOM sem sanitização — trate-as como entrada não confiável e faça escape de HTML antes de qualquer inserção no DOM. +> **Segurança:** `errorString` e `diagnostic` são strings provenientes diretamente do LMS. Não as renderize através de `innerHTML` ou de qualquer API do DOM não sanitizada — trate-as como entrada não confiável e faça escape de HTML antes de qualquer inserção no DOM. ## useScormSession (estado reativo opcional) -`useScorm()` mantém intencionalmente `status.initialized` como um snapshot estático — o provider não rastreia o estado do ciclo de vida. Se você precisar de `initialized` e `terminated` como estado reativo do React (para acionar re-renderizações), use `useScormSession()` no lugar. +`useScorm()` mantém intencionalmente `status.initialized` como um snapshot estático — o provider não rastreia o estado do ciclo de vida. Se precisar de `initialized` e `terminated` como estado reativo do React (para despoletar re-renders), use antes `useScormSession()`. ```tsx -import { useScormSession } from '@studiolxd/react-scorm'; +import { useScormSession } from '@studiolxd/scorm/react'; function Course() { const { initialized, initialize, terminate, api } = useScormSession(); @@ -287,21 +364,21 @@ function Course() { } ``` -`useScormSession()` é um superconjunto de `useScorm()` — retorna tudo que `useScorm()` retorna (`api`, `status`, `raw`) mais: +`useScormSession()` é um superconjunto de `useScorm()` — devolve tudo o que `useScorm()` devolve (`api`, `status`, `raw`) mais: | Campo | Tipo | Descrição | -|-------|------|-----------| -| `initialized` | `boolean` | `true` após `initialize()` ser executado com sucesso. Reativo. | -| `terminated` | `boolean` | `true` após `terminate()` ser executado com sucesso. Reativo. | +|-------|------|-------------| +| `initialized` | `boolean` | `true` após `initialize()` ser bem-sucedido. Reativo. | +| `terminated` | `boolean` | `true` após `terminate()` ser bem-sucedido. Reativo. | | `initialize()` | `Result \| undefined` | Chama `api.initialize()` e atualiza o estado. `undefined` se não houver API. | | `terminate()` | `Result \| undefined` | Chama `api.terminate()` e atualiza o estado. `undefined` se não houver API. | | `commit()` | `Result \| undefined` | Chama `api.commit()`. `undefined` se não houver API. | -> **Nota:** Quando `noLmsBehavior` é `'error'` e nenhum LMS é encontrado, `api` é `null` e os três métodos retornam `undefined`. Verifique `status.apiFound` se precisar distinguir esse caso. +> **Nota:** Quando `noLmsBehavior` é `'error'` e não é encontrado nenhum LMS, `api` é `null` e os três métodos devolvem `undefined`. Verifique `status.apiFound` se precisar de distinguir este caso. ## useScormAutoTerminate (opcional) -Gerencia o ciclo de vida SCORM automaticamente: +Gere o ciclo de vida SCORM automaticamente: ```tsx function CourseContent() { @@ -322,7 +399,7 @@ function CourseContent() { Funções auxiliares tipadas para construir caminhos CMI indexados: ```ts -import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/react-scorm'; +import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/scorm/react'; scorm12ObjectivePath(0, 'score.raw') // "cmi.objectives.0.score.raw" scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_response" @@ -331,7 +408,7 @@ scorm2004InteractionPath(1, 'learner_response') // "cmi.interactions.1.learner_ ## Formatadores de Tempo ```ts -import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/react-scorm'; +import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/scorm/react'; formatScorm12Time(90000) // "00:01:30.00" formatScorm2004Time(90000) // "PT1M30S" @@ -339,7 +416,7 @@ formatScorm2004Time(90000) // "PT1M30S" ## Testes -Use `noLmsBehavior: 'mock'` para testes. O mock utiliza um armazenamento em memória com comportamento real do driver. +Use `noLmsBehavior: 'mock'` para testes. O mock usa um armazenamento em memória com comportamento real do driver. ```tsx // In tests @@ -348,10 +425,10 @@ Use `noLmsBehavior: 'mock'` para testes. O mock utiliza um armazenamento em mem ``` -Você também pode usar as classes mock diretamente: +Também pode usar as classes mock diretamente: ```ts -import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/react-scorm'; +import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/scorm/react'; const mockApi = new MockScorm12Api(); const driver = new Scorm12Driver(mockApi, createLogger(false)); @@ -365,7 +442,7 @@ api.initialize(); Todos os caminhos CMI são estritamente tipados: ```ts -import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/react-scorm'; +import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/scorm/react'; // These types catch typos at compile time: const path: Scorm12CmiPath = 'cmi.core.lesson_status'; // OK @@ -375,7 +452,7 @@ const path: Scorm12CmiPath = 'cmi.core.lesson_status'; // OK ## Documentação Adicional - [Tabela de Mapeamento SCORM 1.2 vs 2004](./docs/scorm-mapping-table.md) -- [Testes com o Modo Mock](./docs/mock-mode.md) +- [Testes com Modo Mock](./docs/mock-mode.md) - [Guia de Integração](./docs/integration-guide.md) ## Licença diff --git a/packages/react-scorm/docs/integration-guide.md b/packages/scorm/docs/integration-guide.md similarity index 100% rename from packages/react-scorm/docs/integration-guide.md rename to packages/scorm/docs/integration-guide.md diff --git a/packages/react-scorm/docs/mock-mode.md b/packages/scorm/docs/mock-mode.md similarity index 100% rename from packages/react-scorm/docs/mock-mode.md rename to packages/scorm/docs/mock-mode.md diff --git a/packages/react-scorm/docs/scorm-mapping-table.md b/packages/scorm/docs/scorm-mapping-table.md similarity index 100% rename from packages/react-scorm/docs/scorm-mapping-table.md rename to packages/scorm/docs/scorm-mapping-table.md diff --git a/packages/scorm/llms.txt b/packages/scorm/llms.txt new file mode 100644 index 0000000..71a7d57 --- /dev/null +++ b/packages/scorm/llms.txt @@ -0,0 +1,108 @@ +# @studiolxd/scorm + +> Headless SCORM 1.2 / 2004 runtime for the browser. Framework-agnostic core with +> a built-in mock mode (run without a real LMS) and adapters for React, Vue, +> Angular, Svelte, and Web Components. Every API call returns a typed Result. + +Install: `npm i @studiolxd/scorm` + +## Entry points (subpath imports) + +- `@studiolxd/scorm` — framework-agnostic core + vanilla (`createScormSession`, `autoTerminate`, `autoCommit`, `ScormApi`, `Result`, `ScormError`, locator, mock). +- `@studiolxd/scorm/react` — `ScormProvider`, `useScorm`, `useScormSession`, `useScormAutoTerminate`, `useScormAutoCommit` (re-exports the core too). +- `@studiolxd/scorm/vue` — `useScorm()` composable. +- `@studiolxd/scorm/angular` — `provideScorm()` + `SCORM` token (Angular >= 17). +- `@studiolxd/scorm/svelte` — `createScormStore()`. +- `@studiolxd/scorm/wc` — registers the `` custom element. +- CDN ` + +``` + +## Gotchas (read these) + +- Always check `.ok` before `.value`. On the session, lifecycle methods return + `undefined` when no SCORM API was found (`noLmsBehavior: 'error'`). +- SCORM 1.2 interactions are WRITE-ONLY: `getInteraction()` returns an error (code + 404) in 1.2; only 2004 can read them back. +- SCORM 1.2 `setScore` raw/min/max must be 0–100 (validated, error 405 otherwise). + SCORM 2004 `scaled` must be -1..1. +- `setProgressMeasure` is a no-op in 1.2. +- A terminated session/driver cannot be re-initialized (SCORM rule). To start a new + session, create a new one (or remount the React provider with a changed `key`). +- SSR-safe: API discovery is guarded against missing `window`. The `/wc` import is + a no-op under SSR. Import `/wc` client-side only. +- `setSessionTime(ms)` takes milliseconds and formats internally. diff --git a/packages/scorm/package.json b/packages/scorm/package.json new file mode 100644 index 0000000..67db077 --- /dev/null +++ b/packages/scorm/package.json @@ -0,0 +1,104 @@ +{ + "name": "@studiolxd/scorm", + "version": "2.0.0", + "description": "Headless SCORM 1.2/2004 runtime — framework-agnostic core with React, Vue, Angular, Svelte and Web Component adapters", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "unpkg": "./dist/scorm.global.js", + "jsdelivr": "./dist/scorm.global.js", + "exports": { + ".": { + "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }, + "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } + }, + "./react": { + "import": { "types": "./dist/react.d.ts", "default": "./dist/react.js" }, + "require": { "types": "./dist/react.d.cts", "default": "./dist/react.cjs" } + }, + "./wc": { + "import": { "types": "./dist/wc.d.ts", "default": "./dist/wc.js" }, + "require": { "types": "./dist/wc.d.cts", "default": "./dist/wc.cjs" } + }, + "./vue": { + "import": { "types": "./dist/vue.d.ts", "default": "./dist/vue.js" }, + "require": { "types": "./dist/vue.d.cts", "default": "./dist/vue.cjs" } + }, + "./angular": { + "import": { "types": "./dist/angular.d.ts", "default": "./dist/angular.js" }, + "require": { "types": "./dist/angular.d.cts", "default": "./dist/angular.cjs" } + }, + "./svelte": { + "import": { "types": "./dist/svelte.d.ts", "default": "./dist/svelte.js" }, + "require": { "types": "./dist/svelte.d.cts", "default": "./dist/svelte.cjs" } + }, + "./package.json": "./package.json" + }, + "sideEffects": [ + "./dist/wc.js", + "./dist/wc.cjs" + ], + "files": [ + "dist", + "llms.txt" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "test": "vitest", + "test:run": "vitest run", + "test:coverage": "vitest run --coverage", + "typecheck": "tsc --noEmit", + "prepublishOnly": "npm run typecheck && npm run test:run && npm run build" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0", + "vue": ">=3.3.0", + "@angular/core": ">=17.0.0", + "svelte": ">=4.0.0" + }, + "peerDependenciesMeta": { + "react": { "optional": true }, + "react-dom": { "optional": true }, + "vue": { "optional": true }, + "@angular/core": { "optional": true }, + "svelte": { "optional": true } + }, + "devDependencies": { + "@angular/core": "^17.3.0", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitest/coverage-v8": "^4.1.1", + "jsdom": "^25.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "rxjs": "^7.8.0", + "svelte": "^4.2.0", + "tsup": "^8.0.0", + "typescript": "^5.5.0", + "vitest": "^4.1.1", + "vue": "^3.4.0" + }, + "keywords": [ + "scorm", + "scorm-1.2", + "scorm-2004", + "react", + "vue", + "angular", + "svelte", + "web-component", + "vanilla", + "lms", + "e-learning", + "headless" + ], + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/scorm/src/angular/index.ts b/packages/scorm/src/angular/index.ts new file mode 100644 index 0000000..e5f1740 --- /dev/null +++ b/packages/scorm/src/angular/index.ts @@ -0,0 +1,73 @@ +// ───────────────────────────────────────────────────────────────────────────── +// @studiolxd/scorm/angular — Angular adapter (Angular >= 17) +// +// Decorator-free by design: ships only an InjectionToken + a functional provider, +// so it builds as plain ESM (no ng-packagr / Angular Package Format required) and +// is consumed by the app's own compiler. Requires Angular 17+ (stable signals, +// esbuild builder, standalone APIs). +// ───────────────────────────────────────────────────────────────────────────── +import { InjectionToken, inject, DestroyRef, signal, type Signal, type Provider } from '@angular/core'; +import { createScormSession, type ScormSession } from '../session/create-scorm-session'; +import { autoTerminate } from '../lifecycle/auto-terminate'; +import { autoCommit } from '../lifecycle/auto-commit'; +import type { ScormStatus } from '../types/status'; +import type { ScormVersion } from '../types/common'; +import type { ScormProviderOptions } from '../types/options'; + +/** What {@link SCORM} injects: the session plus a reactive status signal. */ +export interface ScormHandle { + session: ScormSession; + /** Reactive status signal — call `status()` to read; updates on lifecycle changes. */ + status: Signal; +} + +/** Injection token for the SCORM session handle. Inject with `inject(SCORM)`. */ +export const SCORM = new InjectionToken('@studiolxd/scorm'); + +/** Extra options for {@link provideScorm}. */ +export interface ProvideScormOptions extends ScormProviderOptions { + /** Initialize on creation and terminate on `DestroyRef` / unload. Default: false. */ + autoTerminate?: boolean; + /** Commit every N ms. 0 disables. Default: 0. */ + autoCommitMs?: number; +} + +/** + * Provide a SCORM session to an Angular application or component. + * + * @example + * ```ts + * bootstrapApplication(App, { + * providers: [provideScorm('auto', { noLmsBehavior: 'mock', autoTerminate: true })], + * }); + * // in a component: + * const { session, status } = inject(SCORM); // status() is a signal + * ``` + */ +export function provideScorm( + version: ScormVersion | 'auto', + options: ProvideScormOptions = {}, +): Provider { + const { autoTerminate: enableAutoTerminate = false, autoCommitMs = 0, ...providerOptions } = options; + + return { + provide: SCORM, + useFactory: (): ScormHandle => { + const session = createScormSession(version, providerOptions); + const status = signal(session.status); + const off = session.on('change', (s) => status.set(s)); + + const disposers: Array<() => void> = []; + if (enableAutoTerminate) disposers.push(autoTerminate(session)); + if (autoCommitMs > 0) disposers.push(autoCommit(session, autoCommitMs)); + + inject(DestroyRef).onDestroy(() => { + off(); + for (const dispose of disposers) dispose(); + session.destroy(); + }); + + return { session, status }; + }, + }; +} diff --git a/packages/react-scorm/src/api/path-builders.ts b/packages/scorm/src/api/path-builders.ts similarity index 100% rename from packages/react-scorm/src/api/path-builders.ts rename to packages/scorm/src/api/path-builders.ts diff --git a/packages/react-scorm/src/api/scorm-api.ts b/packages/scorm/src/api/scorm-api.ts similarity index 100% rename from packages/react-scorm/src/api/scorm-api.ts rename to packages/scorm/src/api/scorm-api.ts diff --git a/packages/react-scorm/src/api/time-format.ts b/packages/scorm/src/api/time-format.ts similarity index 100% rename from packages/react-scorm/src/api/time-format.ts rename to packages/scorm/src/api/time-format.ts diff --git a/packages/react-scorm/src/core/create-driver.ts b/packages/scorm/src/core/create-driver.ts similarity index 100% rename from packages/react-scorm/src/core/create-driver.ts rename to packages/scorm/src/core/create-driver.ts diff --git a/packages/react-scorm/src/core/locator.ts b/packages/scorm/src/core/locator.ts similarity index 100% rename from packages/react-scorm/src/core/locator.ts rename to packages/scorm/src/core/locator.ts diff --git a/packages/react-scorm/src/core/scorm12-driver.ts b/packages/scorm/src/core/scorm12-driver.ts similarity index 100% rename from packages/react-scorm/src/core/scorm12-driver.ts rename to packages/scorm/src/core/scorm12-driver.ts diff --git a/packages/react-scorm/src/core/scorm2004-driver.ts b/packages/scorm/src/core/scorm2004-driver.ts similarity index 100% rename from packages/react-scorm/src/core/scorm2004-driver.ts rename to packages/scorm/src/core/scorm2004-driver.ts diff --git a/packages/react-scorm/src/debug/logger.ts b/packages/scorm/src/debug/logger.ts similarity index 100% rename from packages/react-scorm/src/debug/logger.ts rename to packages/scorm/src/debug/logger.ts diff --git a/packages/react-scorm/src/errors/error-codes.ts b/packages/scorm/src/errors/error-codes.ts similarity index 100% rename from packages/react-scorm/src/errors/error-codes.ts rename to packages/scorm/src/errors/error-codes.ts diff --git a/packages/react-scorm/src/errors/scorm-error.ts b/packages/scorm/src/errors/scorm-error.ts similarity index 100% rename from packages/react-scorm/src/errors/scorm-error.ts rename to packages/scorm/src/errors/scorm-error.ts diff --git a/packages/react-scorm/src/index.ts b/packages/scorm/src/index.ts similarity index 64% rename from packages/react-scorm/src/index.ts rename to packages/scorm/src/index.ts index 588961a..80f7f86 100644 --- a/packages/react-scorm/src/index.ts +++ b/packages/scorm/src/index.ts @@ -1,12 +1,11 @@ -// React integration -export { ScormProvider } from './react/scorm-provider'; -export { useScorm } from './react/use-scorm'; -export { useScormAutoTerminate } from './react/use-scorm-auto-terminate'; -export { useScormAutoCommit } from './react/use-scorm-auto-commit'; -export { useScormSession } from './react/use-scorm-session'; -export type { ScormContextValue } from './react/scorm-context'; -export type { AutoTerminateOptions } from './react/use-scorm-auto-terminate'; -export type { ScormSessionValue } from './react/use-scorm-session'; +// ───────────────────────────────────────────────────────────────────────────── +// @studiolxd/scorm — framework-agnostic core (subpath ".") +// +// Framework adapters live in their own subpaths: +// @studiolxd/scorm/react @studiolxd/scorm/vue @studiolxd/scorm/angular +// @studiolxd/scorm/svelte @studiolxd/scorm/wc +// This entry has ZERO framework imports. +// ───────────────────────────────────────────────────────────────────────────── // High-level API export { ScormApi } from './api/scorm-api'; @@ -19,6 +18,15 @@ export type { CommentRecord, } from './types/api'; +// Observable session (vanilla-friendly, basis for every adapter) +export { createScormSession } from './session/create-scorm-session'; +export type { ScormSession, ScormSessionEvent } from './session/create-scorm-session'; + +// Lifecycle helpers (framework-agnostic) +export { autoTerminate } from './lifecycle/auto-terminate'; +export type { AutoTerminateOptions } from './lifecycle/auto-terminate'; +export { autoCommit } from './lifecycle/auto-commit'; + // Path builders export { scorm12ObjectivePath, @@ -70,7 +78,7 @@ export { Scorm12Driver } from './core/scorm12-driver'; export { Scorm2004Driver } from './core/scorm2004-driver'; export { createDriver } from './core/create-driver'; -// Mock (for testing) +// Mock (for testing / no-LMS dev) export { MockScorm12Api } from './mock/mock-scorm12-api'; export { MockScorm2004Api } from './mock/mock-scorm2004-api'; export { createMockDriver } from './mock/mock-driver'; diff --git a/packages/scorm/src/lifecycle/auto-commit.ts b/packages/scorm/src/lifecycle/auto-commit.ts new file mode 100644 index 0000000..df7fa0e --- /dev/null +++ b/packages/scorm/src/lifecycle/auto-commit.ts @@ -0,0 +1,22 @@ +import type { ScormSession } from '../session/create-scorm-session'; + +/** + * Framework-agnostic periodic commit for a {@link ScormSession}. + * + * Calls `session.commit()` every `intervalMs` as a best-effort flush so long + * sessions don't lose progress if the tab closes abruptly. Returns a `dispose()` + * that clears the timer. Pass `0` (or a negative number) to disable (no-op). + * + * Commit errors are intentionally swallowed — this is a background flush. + * + * @param intervalMs Interval in milliseconds. Default: 60000 (1 min). + */ +export function autoCommit(session: ScormSession, intervalMs = 60_000): () => void { + if (!session.api || !intervalMs || intervalMs <= 0) return () => {}; + + const id = setInterval(() => { + session.commit(); + }, intervalMs); + + return () => clearInterval(id); +} diff --git a/packages/scorm/src/lifecycle/auto-terminate.ts b/packages/scorm/src/lifecycle/auto-terminate.ts new file mode 100644 index 0000000..f4b3416 --- /dev/null +++ b/packages/scorm/src/lifecycle/auto-terminate.ts @@ -0,0 +1,68 @@ +import type { ScormSession } from '../session/create-scorm-session'; + +/** Options for {@link autoTerminate}. */ +export interface AutoTerminateOptions { + /** Track elapsed time and set session time before terminate. Default: true. */ + trackSessionTime?: boolean; + /** Listen to beforeunload/pagehide events. Default: true. */ + handleUnload?: boolean; + /** Listen to the Page Lifecycle `freeze` event. Default: true. */ + handleFreeze?: boolean; +} + +/** + * Framework-agnostic auto-terminate wiring for a {@link ScormSession}. + * + * - Calls `session.initialize()` immediately. + * - On page unload (`beforeunload`/`pagehide`), `freeze`, or when the returned + * dispose function runs: sets session time, commits, and terminates. + * + * Returns a `dispose()` that terminates and removes listeners. Safe under SSR + * (no `window`): it initializes but attaches no listeners. + * + * Adapters (React `useScormAutoTerminate`, Vue, Angular, …) call this inside their + * lifecycle and invoke the returned function on teardown. + */ +export function autoTerminate(session: ScormSession, options: AutoTerminateOptions = {}): () => void { + const { trackSessionTime = true, handleUnload = true, handleFreeze = true } = options; + if (!session.api) return () => {}; + + let terminated = false; + let sessionStart: number | null = null; + + const initResult = session.initialize(); + if (initResult && initResult.ok) sessionStart = Date.now(); + + const doTerminate = (): void => { + if (terminated) return; + terminated = true; + + if (trackSessionTime && sessionStart !== null && session.api) { + session.api.setSessionTime(Date.now() - sessionStart); + } + // Commit/terminate errors are intentionally swallowed — this runs on unload/ + // teardown paths where throwing would skip the terminate call entirely. + session.commit(); + session.terminate(); + }; + + const hasWindow = typeof window !== 'undefined'; + if (hasWindow && handleUnload) { + window.addEventListener('beforeunload', doTerminate); + window.addEventListener('pagehide', doTerminate, { capture: true }); + } + if (hasWindow && handleFreeze) { + window.addEventListener('freeze', doTerminate); + } + + return () => { + doTerminate(); + if (hasWindow && handleUnload) { + window.removeEventListener('beforeunload', doTerminate); + window.removeEventListener('pagehide', doTerminate, { capture: true }); + } + if (hasWindow && handleFreeze) { + window.removeEventListener('freeze', doTerminate); + } + }; +} diff --git a/packages/react-scorm/src/mock/mock-driver.ts b/packages/scorm/src/mock/mock-driver.ts similarity index 100% rename from packages/react-scorm/src/mock/mock-driver.ts rename to packages/scorm/src/mock/mock-driver.ts diff --git a/packages/react-scorm/src/mock/mock-scorm12-api.ts b/packages/scorm/src/mock/mock-scorm12-api.ts similarity index 100% rename from packages/react-scorm/src/mock/mock-scorm12-api.ts rename to packages/scorm/src/mock/mock-scorm12-api.ts diff --git a/packages/react-scorm/src/mock/mock-scorm2004-api.ts b/packages/scorm/src/mock/mock-scorm2004-api.ts similarity index 100% rename from packages/react-scorm/src/mock/mock-scorm2004-api.ts rename to packages/scorm/src/mock/mock-scorm2004-api.ts diff --git a/packages/scorm/src/react/index.ts b/packages/scorm/src/react/index.ts new file mode 100644 index 0000000..8f0d15d --- /dev/null +++ b/packages/scorm/src/react/index.ts @@ -0,0 +1,19 @@ +// ───────────────────────────────────────────────────────────────────────────── +// @studiolxd/scorm/react — React adapter +// +// Re-exports the framework-agnostic core so consumers can import everything from +// a single entry: `import { ScormProvider, useScorm, ScormError } from '@studiolxd/scorm/react'`. +// ───────────────────────────────────────────────────────────────────────────── + +// React integration +export { ScormProvider } from './scorm-provider'; +export { useScorm } from './use-scorm'; +export { useScormAutoTerminate } from './use-scorm-auto-terminate'; +export { useScormAutoCommit } from './use-scorm-auto-commit'; +export { useScormSession } from './use-scorm-session'; +export type { ScormContextValue } from './scorm-context'; +export type { AutoTerminateOptions } from './use-scorm-auto-terminate'; +export type { ScormSessionValue } from './use-scorm-session'; + +// Re-export the whole core for convenience +export * from '../index'; diff --git a/packages/react-scorm/src/react/scorm-context.ts b/packages/scorm/src/react/scorm-context.ts similarity index 69% rename from packages/react-scorm/src/react/scorm-context.ts rename to packages/scorm/src/react/scorm-context.ts index ca1fad5..cbf2fbf 100644 --- a/packages/react-scorm/src/react/scorm-context.ts +++ b/packages/scorm/src/react/scorm-context.ts @@ -2,15 +2,18 @@ import { createContext } from 'react'; import type { IScormApi } from '../types/api'; import type { ScormStatus } from '../types/status'; import type { IScormDriver } from '../types/driver'; +import type { ScormSession } from '../session/create-scorm-session'; /** The value provided by ScormProvider via React context. */ export interface ScormContextValue { - /** Current SCORM connection status. */ + /** Current SCORM connection status (snapshot at render time). */ status: ScormStatus; /** The raw low-level driver (escape hatch for direct API calls). */ raw: IScormDriver | null; /** The high-level version-agnostic API. */ api: IScormApi | null; + /** The underlying observable session (used by the lifecycle hooks). */ + session: ScormSession | null; } export const ScormContext = createContext(null); diff --git a/packages/scorm/src/react/scorm-provider.tsx b/packages/scorm/src/react/scorm-provider.tsx new file mode 100644 index 0000000..7622e75 --- /dev/null +++ b/packages/scorm/src/react/scorm-provider.tsx @@ -0,0 +1,45 @@ +import { useMemo } from 'react'; +import { ScormContext, type ScormContextValue } from './scorm-context'; +import { createScormSession } from '../session/create-scorm-session'; +import type { ScormProviderProps } from '../types/options'; + +/** + * SCORM Provider component. + * + * Creates an observable {@link ScormSession} (locating the SCORM API object) and + * makes the session + driver + high-level API available to descendants via context. + * + * **Does NOT auto-initialize.** Call `initialize()` explicitly, or use + * `useScormAutoTerminate()` which initializes on mount. + * + * For reactive `initialized`/`terminated` state use `useScormSession()`; the + * `status` exposed by `useScorm()` is a snapshot read at render time. + * + * **`noLmsBehavior: 'throw'`** will throw synchronously during render. Wrap the + * provider in a React Error Boundary to handle this gracefully. + * + * @example + * ```tsx + * + * + * + * ``` + */ +export function ScormProvider({ version, options = {}, children }: ScormProviderProps) { + const session = useMemo( + () => createScormSession(version, options), + // eslint-disable-next-line react-hooks/exhaustive-deps + [version, options.noLmsBehavior, options.maxParentDepth, options.checkOpener, options.debug, options.fallbackVersion], + ); + + const contextValue = useMemo( + () => ({ status: session.status, raw: session.raw, api: session.api, session }), + [session], + ); + + return ( + + {children} + + ); +} diff --git a/packages/scorm/src/react/use-scorm-auto-commit.ts b/packages/scorm/src/react/use-scorm-auto-commit.ts new file mode 100644 index 0000000..d4a31ff --- /dev/null +++ b/packages/scorm/src/react/use-scorm-auto-commit.ts @@ -0,0 +1,28 @@ +import { useEffect } from 'react'; +import { useScorm } from './use-scorm'; +import { autoCommit } from '../lifecycle/auto-commit'; + +/** + * Opt-in hook that periodically commits to persist progress. + * + * Thin React wrapper over the framework-agnostic {@link autoCommit}. Complements + * (does not replace) the commit performed by {@link useScormAutoTerminate} on unload. + * Pass `0` (or a negative number) to disable. + * + * @param intervalMs How often to commit, in milliseconds. Default: 60000 (1 min). + * + * @example + * ```tsx + * function CourseContent() { + * useScormAutoCommit(30_000); // flush every 30s + * } + * ``` + */ +export function useScormAutoCommit(intervalMs = 60_000): void { + const { session } = useScorm(); + + useEffect(() => { + if (!session) return; + return autoCommit(session, intervalMs); + }, [session, intervalMs]); +} diff --git a/packages/scorm/src/react/use-scorm-auto-terminate.ts b/packages/scorm/src/react/use-scorm-auto-terminate.ts new file mode 100644 index 0000000..9381160 --- /dev/null +++ b/packages/scorm/src/react/use-scorm-auto-terminate.ts @@ -0,0 +1,30 @@ +import { useEffect } from 'react'; +import { useScorm } from './use-scorm'; +import { autoTerminate, type AutoTerminateOptions } from '../lifecycle/auto-terminate'; + +export type { AutoTerminateOptions }; + +/** + * Opt-in hook that handles the SCORM lifecycle automatically. + * + * Thin React wrapper over the framework-agnostic {@link autoTerminate}: on mount it + * initializes the session; on unmount, page unload (`beforeunload`/`pagehide`), or + * `freeze` it sets session time, commits, and terminates. + * + * @example + * ```tsx + * function CourseContent() { + * useScormAutoTerminate({ trackSessionTime: true }); + * // session is auto-initialized, will auto-terminate on unmount/unload + * } + * ``` + */ +export function useScormAutoTerminate(options: AutoTerminateOptions = {}): void { + const { session } = useScorm(); + const { trackSessionTime = true, handleUnload = true, handleFreeze = true } = options; + + useEffect(() => { + if (!session) return; + return autoTerminate(session, { trackSessionTime, handleUnload, handleFreeze }); + }, [session, trackSessionTime, handleUnload, handleFreeze]); +} diff --git a/packages/scorm/src/react/use-scorm-session.ts b/packages/scorm/src/react/use-scorm-session.ts new file mode 100644 index 0000000..f65259b --- /dev/null +++ b/packages/scorm/src/react/use-scorm-session.ts @@ -0,0 +1,68 @@ +import { useCallback, useSyncExternalStore } from 'react'; +import { useScorm } from './use-scorm'; +import type { ScormContextValue } from './scorm-context'; +import type { Result } from '../result/result'; +import type { ScormError } from '../errors/scorm-error'; + +export interface ScormSessionValue extends ScormContextValue { + /** + * Whether `initialize()` has succeeded and the session is live. + * Reactive — triggers re-render when it changes. + */ + initialized: boolean; + /** + * Whether `terminate()` has succeeded in this session. + * Reactive — triggers re-render when it changes. + */ + terminated: boolean; + /** Initialize the session (updates reactive state). `undefined` when no API. */ + initialize: () => Result | undefined; + /** Terminate the session (updates reactive state). `undefined` when no API. */ + terminate: () => Result | undefined; + /** Commit pending data. State is not affected. `undefined` when no API. */ + commit: () => Result | undefined; +} + +/** + * `useScorm()` plus reactive `initialized`/`terminated` state. + * + * Subscribes to the underlying session via `useSyncExternalStore`, so the component + * re-renders whenever the lifecycle state changes — including when another component + * sharing the same provider initializes or terminates. + * + * @example + * ```tsx + * function Course() { + * const { initialized, initialize, terminate, api } = useScormSession(); + * useEffect(() => { initialize(); }, [initialize]); + * if (!initialized) return

Connecting…

; + * return ; + * } + * ``` + */ +export function useScormSession(): ScormSessionValue { + const context = useScorm(); + const { session } = context; + + const subscribe = useCallback( + (onStoreChange: () => void) => (session ? session.on('change', onStoreChange) : () => {}), + [session], + ); + const getSnapshot = useCallback(() => session?.status ?? context.status, [session, context.status]); + + const status = useSyncExternalStore(subscribe, getSnapshot, getSnapshot); + + const initialize = useCallback(() => session?.initialize(), [session]); + const terminate = useCallback(() => session?.terminate(), [session]); + const commit = useCallback(() => session?.commit(), [session]); + + return { + ...context, + status, + initialized: status.initialized, + terminated: status.terminated, + initialize, + terminate, + commit, + }; +} diff --git a/packages/react-scorm/src/react/use-scorm.ts b/packages/scorm/src/react/use-scorm.ts similarity index 100% rename from packages/react-scorm/src/react/use-scorm.ts rename to packages/scorm/src/react/use-scorm.ts diff --git a/packages/react-scorm/src/result/result.ts b/packages/scorm/src/result/result.ts similarity index 100% rename from packages/react-scorm/src/result/result.ts rename to packages/scorm/src/result/result.ts diff --git a/packages/scorm/src/session/create-scorm-session.ts b/packages/scorm/src/session/create-scorm-session.ts new file mode 100644 index 0000000..2d3bcd8 --- /dev/null +++ b/packages/scorm/src/session/create-scorm-session.ts @@ -0,0 +1,138 @@ +import { createDriver } from '../core/create-driver'; +import { ScormApi } from '../api/scorm-api'; +import { createLogger } from '../debug/logger'; +import type { IScormApi } from '../types/api'; +import type { IScormDriver } from '../types/driver'; +import type { ScormStatus } from '../types/status'; +import type { ScormVersion } from '../types/common'; +import type { ScormProviderOptions } from '../types/options'; +import type { Result } from '../result/result'; +import type { ScormError } from '../errors/scorm-error'; + +/** Events emitted by a {@link ScormSession}. */ +export type ScormSessionEvent = 'change'; + +type ChangeListener = (status: ScormStatus) => void; + +/** + * Framework-agnostic, observable SCORM session. + * + * Wraps API discovery + the high-level {@link IScormApi} and exposes reactive + * lifecycle state through a tiny subscription model (`on('change')`). This is the + * basis every adapter builds on (React, Vue, Angular, Svelte, Web Component) and + * the ergonomic entry point for vanilla JS. + * + * @example + * ```ts + * const session = createScormSession('auto', { noLmsBehavior: 'mock' }); + * const off = session.on('change', (s) => console.log('initialized:', s.initialized)); + * session.initialize(); + * session.api?.setComplete(); + * session.commit(); + * session.terminate(); + * off(); + * ``` + */ +export interface ScormSession { + /** The high-level API, or null when no SCORM API was found (`noLmsBehavior: 'error'`). */ + readonly api: IScormApi | null; + /** The raw low-level driver (escape hatch), or null. */ + readonly raw: IScormDriver | null; + /** Current status snapshot. Reference is stable between `change` emissions. */ + readonly status: ScormStatus; + /** Initialize the session. Updates reactive status and emits `change` on success. */ + initialize(): Result | undefined; + /** Commit pending data. Does not change status. */ + commit(): Result | undefined; + /** Terminate the session. Updates reactive status and emits `change` on success. */ + terminate(): Result | undefined; + /** Subscribe to status changes. Returns an unsubscribe function. */ + on(event: ScormSessionEvent, cb: ChangeListener): () => void; + /** Remove a previously registered listener. */ + off(event: ScormSessionEvent, cb: ChangeListener): void; + /** Release all listeners. Call when the session is no longer needed. */ + destroy(): void; +} + +/** + * Create an observable SCORM session. + * + * Locates the SCORM API (or applies `noLmsBehavior`) and returns a session whose + * `initialize`/`terminate` keep a reactive `status` and notify subscribers. + * + * @param version `'1.2'`, `'2004'`, or `'auto'` (detect the host LMS at runtime). + * @param options Provider options (`noLmsBehavior`, `fallbackVersion`, `debug`, …). + */ +export function createScormSession( + version: ScormVersion | 'auto', + options: ScormProviderOptions = {}, +): ScormSession { + const logger = createLogger(options.debug ?? false); + const driverResult = createDriver(version, options, logger); + + let driver: IScormDriver | null = null; + let api: IScormApi | null = null; + let apiFound = false; + let resolvedVersion: ScormVersion; + + if (driverResult.ok) { + driver = driverResult.value; + api = new ScormApi(driver); + apiFound = true; + resolvedVersion = driver.version; + } else { + resolvedVersion = version === 'auto' ? (options.fallbackVersion ?? '2004') : version; + } + + let status: ScormStatus = { + version: resolvedVersion, + apiFound, + initialized: false, + terminated: false, + noLmsBehavior: options.noLmsBehavior ?? 'error', + }; + + const listeners = new Set(); + const setStatus = (patch: Partial): void => { + status = { ...status, ...patch }; + for (const listener of listeners) listener(status); + }; + + return { + get api() { return api; }, + get raw() { return driver; }, + get status() { return status; }, + + initialize() { + if (!api) return undefined; + const result = api.initialize(); + if (result.ok && !status.initialized) setStatus({ initialized: true, terminated: false }); + return result; + }, + + commit() { + if (!api) return undefined; + return api.commit(); + }, + + terminate() { + if (!api) return undefined; + const result = api.terminate(); + if (result.ok && !status.terminated) setStatus({ initialized: false, terminated: true }); + return result; + }, + + on(_event, cb) { + listeners.add(cb); + return () => listeners.delete(cb); + }, + + off(_event, cb) { + listeners.delete(cb); + }, + + destroy() { + listeners.clear(); + }, + }; +} diff --git a/packages/scorm/src/svelte/index.ts b/packages/scorm/src/svelte/index.ts new file mode 100644 index 0000000..4699c36 --- /dev/null +++ b/packages/scorm/src/svelte/index.ts @@ -0,0 +1,71 @@ +// ───────────────────────────────────────────────────────────────────────────── +// @studiolxd/scorm/svelte — Svelte store adapter +// ───────────────────────────────────────────────────────────────────────────── +import { readable, type Readable } from 'svelte/store'; +import { createScormSession, type ScormSession } from '../session/create-scorm-session'; +import { autoTerminate } from '../lifecycle/auto-terminate'; +import { autoCommit } from '../lifecycle/auto-commit'; +import type { ScormStatus } from '../types/status'; +import type { ScormVersion } from '../types/common'; +import type { ScormProviderOptions } from '../types/options'; + +/** Extra options for {@link createScormStore}. */ +export interface ScormStoreOptions extends ScormProviderOptions { + /** Initialize immediately and terminate on `destroy()` / unload. Default: false. */ + autoTerminate?: boolean; + /** Commit every N ms. 0 disables. Default: 0. */ + autoCommitMs?: number; +} + +/** Return value of {@link createScormStore}. */ +export interface ScormStore { + session: ScormSession; + /** A Svelte readable store of the session status (use with `$status`). */ + status: Readable; + /** Tear down lifecycle wiring and the session. */ + destroy(): void; +} + +/** + * Create a SCORM session exposed as a Svelte store. + * + * @example + * ```svelte + * + * {#if $status.initialized}live{/if} + * ``` + */ +export function createScormStore( + version: ScormVersion | 'auto', + options: ScormStoreOptions = {}, +): ScormStore { + const { autoTerminate: enableAutoTerminate = false, autoCommitMs = 0, ...providerOptions } = options; + + const session = createScormSession(version, providerOptions); + const disposers: Array<() => void> = []; + if (enableAutoTerminate) disposers.push(autoTerminate(session)); + if (autoCommitMs > 0) disposers.push(autoCommit(session, autoCommitMs)); + + const status = readable(session.status, (set) => { + // Push the *current* status to a (possibly late) subscriber, then track changes — + // otherwise a subscriber that attaches after an early change would see the stale + // creation-time snapshot until the next change. + set(session.status); + return session.on('change', set); + }); + + return { + session, + status, + destroy() { + for (const dispose of disposers) dispose(); + session.destroy(); + }, + }; +} diff --git a/packages/react-scorm/src/types/api.ts b/packages/scorm/src/types/api.ts similarity index 100% rename from packages/react-scorm/src/types/api.ts rename to packages/scorm/src/types/api.ts diff --git a/packages/react-scorm/src/types/common.ts b/packages/scorm/src/types/common.ts similarity index 100% rename from packages/react-scorm/src/types/common.ts rename to packages/scorm/src/types/common.ts diff --git a/packages/react-scorm/src/types/driver.ts b/packages/scorm/src/types/driver.ts similarity index 100% rename from packages/react-scorm/src/types/driver.ts rename to packages/scorm/src/types/driver.ts diff --git a/packages/react-scorm/src/types/options.ts b/packages/scorm/src/types/options.ts similarity index 100% rename from packages/react-scorm/src/types/options.ts rename to packages/scorm/src/types/options.ts diff --git a/packages/react-scorm/src/types/scorm12-cmi.ts b/packages/scorm/src/types/scorm12-cmi.ts similarity index 100% rename from packages/react-scorm/src/types/scorm12-cmi.ts rename to packages/scorm/src/types/scorm12-cmi.ts diff --git a/packages/react-scorm/src/types/scorm2004-cmi.ts b/packages/scorm/src/types/scorm2004-cmi.ts similarity index 100% rename from packages/react-scorm/src/types/scorm2004-cmi.ts rename to packages/scorm/src/types/scorm2004-cmi.ts diff --git a/packages/react-scorm/src/types/status.ts b/packages/scorm/src/types/status.ts similarity index 100% rename from packages/react-scorm/src/types/status.ts rename to packages/scorm/src/types/status.ts diff --git a/packages/scorm/src/vue/index.ts b/packages/scorm/src/vue/index.ts new file mode 100644 index 0000000..a5af4e7 --- /dev/null +++ b/packages/scorm/src/vue/index.ts @@ -0,0 +1,86 @@ +// ───────────────────────────────────────────────────────────────────────────── +// @studiolxd/scorm/vue — Vue 3 composable +// ───────────────────────────────────────────────────────────────────────────── +import { shallowRef, onScopeDispose, getCurrentScope, type ShallowRef } from 'vue'; +import { createScormSession, type ScormSession } from '../session/create-scorm-session'; +import { autoTerminate } from '../lifecycle/auto-terminate'; +import { autoCommit } from '../lifecycle/auto-commit'; +import type { ScormStatus } from '../types/status'; +import type { ScormVersion } from '../types/common'; +import type { ScormProviderOptions } from '../types/options'; +import type { Result } from '../result/result'; +import type { ScormError } from '../errors/scorm-error'; + +/** Extra options for {@link useScorm}. */ +export interface UseScormOptions extends ScormProviderOptions { + /** Initialize on setup and terminate on scope dispose / unload. Default: false. */ + autoTerminate?: boolean; + /** Commit every N ms while mounted. 0 disables. Default: 0. */ + autoCommitMs?: number; +} + +/** Return value of {@link useScorm}. */ +export interface UseScormReturn { + session: ScormSession; + /** Reactive status. Updates on every lifecycle change. */ + status: ShallowRef; + initialize: () => Result | undefined; + commit: () => Result | undefined; + terminate: () => Result | undefined; + /** + * Tear down listeners and destroy the session. Called automatically on scope + * dispose when used inside a component `setup()`. Call it manually when using + * this composable outside an effect scope. + */ + destroy: () => void; +} + +/** + * Vue composable that creates an observable SCORM session bound to the component + * scope. The session is destroyed automatically on `onScopeDispose`. + * + * @example + * ```vue + * + * + * ``` + */ +export function useScorm( + version: ScormVersion | 'auto', + options: UseScormOptions = {}, +): UseScormReturn { + const { autoTerminate: enableAutoTerminate = false, autoCommitMs = 0, ...providerOptions } = options; + + const session = createScormSession(version, providerOptions); + const status = shallowRef(session.status); + const off = session.on('change', (s) => { status.value = s; }); + + const disposers: Array<() => void> = []; + if (enableAutoTerminate) disposers.push(autoTerminate(session)); + if (autoCommitMs > 0) disposers.push(autoCommit(session, autoCommitMs)); + + let destroyed = false; + const destroy = () => { + if (destroyed) return; + destroyed = true; + off(); + for (const dispose of disposers) dispose(); + session.destroy(); + }; + + // Auto-clean only when there is an active effect scope (i.e. inside setup()). + // Outside a scope, onScopeDispose is a no-op, so the caller uses `destroy()`. + if (getCurrentScope()) onScopeDispose(destroy); + + return { + session, + status, + initialize: () => session.initialize(), + commit: () => session.commit(), + terminate: () => session.terminate(), + destroy, + }; +} diff --git a/packages/scorm/src/wc/index.ts b/packages/scorm/src/wc/index.ts new file mode 100644 index 0000000..adeab7f --- /dev/null +++ b/packages/scorm/src/wc/index.ts @@ -0,0 +1,103 @@ +// ───────────────────────────────────────────────────────────────────────────── +// @studiolxd/scorm/wc — Web Component +// +// Importing this module registers the custom element (browser only). Use from any +// framework or plain HTML: +// +// +// el.addEventListener('change', (e) => console.log(e.detail.status)) +// el.session.api?.setComplete() +// ───────────────────────────────────────────────────────────────────────────── +import { createScormSession, type ScormSession } from '../session/create-scorm-session'; +import { autoTerminate } from '../lifecycle/auto-terminate'; +import { autoCommit } from '../lifecycle/auto-commit'; +import type { ScormVersion } from '../types/common'; +import type { NoLmsBehavior } from '../types/options'; + +/** + * Register the `` custom element. + * + * Called automatically on import (browser only). No-op under SSR or if the element + * is already registered. Pass a custom `tag` to register under a different name. + * + * Supported attributes: + * - `version` — `"1.2" | "2004" | "auto"` (default `"auto"`) + * - `no-lms-behavior` — `"error" | "mock" | "throw"` (default `"error"`) + * - `fallback-version` — `"1.2" | "2004"` for `version="auto"` with no API + * - `debug` — boolean attribute, enables console logging + * - `auto-terminate` — boolean attribute, wires init-on-connect + terminate-on-disconnect/unload + * - `auto-commit` — number of ms; periodic commit while connected + * + * Emits a `change` CustomEvent (`detail: { status }`) on connect and on every + * lifecycle change. The element also exposes `.session`, `.api`, and + * `initialize()/commit()/terminate()` for imperative use. + */ +export function defineScormSession(tag = 'scorm-session'): void { + if (typeof HTMLElement === 'undefined' || typeof customElements === 'undefined') return; + if (customElements.get(tag)) return; + + class ScormSessionElement extends HTMLElement { + /** The underlying observable session (null before connect). */ + session: ScormSession | null = null; + private disposers: Array<() => void> = []; + private offChange: (() => void) | null = null; + + get api() { + return this.session?.api ?? null; + } + + connectedCallback(): void { + const version = (this.getAttribute('version') as ScormVersion | 'auto' | null) ?? 'auto'; + const noLmsBehavior = (this.getAttribute('no-lms-behavior') as NoLmsBehavior | null) ?? undefined; + const fallbackVersion = (this.getAttribute('fallback-version') as ScormVersion | null) ?? undefined; + const debug = this.hasAttribute('debug'); + + const session = createScormSession(version, { + ...(noLmsBehavior ? { noLmsBehavior } : {}), + ...(fallbackVersion ? { fallbackVersion } : {}), + debug, + }); + this.session = session; + + this.offChange = session.on('change', (status) => { + this.dispatchEvent(new CustomEvent('change', { detail: { status } })); + }); + + // Announce the initial state first, before any auto-wiring. This way + // `auto-terminate` (which initializes) produces a distinct follow-up `change` + // event rather than a duplicate of the initial one. + this.dispatchEvent(new CustomEvent('change', { detail: { status: session.status } })); + + if (this.hasAttribute('auto-terminate')) { + this.disposers.push(autoTerminate(session)); + } + const commitMs = Number(this.getAttribute('auto-commit')); + if (commitMs > 0) { + this.disposers.push(autoCommit(session, commitMs)); + } + } + + disconnectedCallback(): void { + for (const dispose of this.disposers) dispose(); + this.disposers = []; + this.offChange?.(); + this.offChange = null; + this.session?.destroy(); + } + + initialize() { + return this.session?.initialize(); + } + commit() { + return this.session?.commit(); + } + terminate() { + return this.session?.terminate(); + } + } + + customElements.define(tag, ScormSessionElement); +} + +// Auto-register on import (browser only). +defineScormSession(); diff --git a/packages/scorm/tests/adapters/angular.test.ts b/packages/scorm/tests/adapters/angular.test.ts new file mode 100644 index 0000000..55cb342 --- /dev/null +++ b/packages/scorm/tests/adapters/angular.test.ts @@ -0,0 +1,14 @@ +import { describe, it, expect } from 'vitest'; +import { provideScorm, SCORM } from '../../src/angular/index'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// Structural test only. The factory uses inject(DestroyRef), which requires an +// Angular injection context — exercised by the Angular CI smoke test, not here. +describe('angular adapter — provideScorm', () => { + it('returns a provider bound to the SCORM token with a factory', () => { + const provider = provideScorm('2004', { noLmsBehavior: 'mock' }) as any; + expect(provider.provide).toBe(SCORM); + expect(typeof provider.useFactory).toBe('function'); + }); +}); diff --git a/packages/scorm/tests/adapters/svelte.test.ts b/packages/scorm/tests/adapters/svelte.test.ts new file mode 100644 index 0000000..f6c1bb3 --- /dev/null +++ b/packages/scorm/tests/adapters/svelte.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect } from 'vitest'; +import type { ScormStatus } from '../../src/types/status'; +import { createScormStore } from '../../src/svelte/index'; + +describe('svelte adapter — createScormStore', () => { + it('store reflects lifecycle changes for active subscribers', () => { + const store = createScormStore('2004', { noLmsBehavior: 'mock' }); + + const seen: ScormStatus[] = []; + const unsub = store.status.subscribe((v) => seen.push(v)); + const last = () => seen[seen.length - 1]; + expect(last().initialized).toBe(false); + + store.session.initialize(); + expect(last().initialized).toBe(true); + + store.session.terminate(); + expect(last().terminated).toBe(true); + + unsub(); + store.destroy(); + }); + + it('a late subscriber sees the current status, not the creation-time snapshot', () => { + const store = createScormStore('2004', { noLmsBehavior: 'mock' }); + // Change state BEFORE anyone subscribes. + store.session.initialize(); + + let first: ScormStatus | undefined; + const unsub = store.status.subscribe((v) => { first ??= v; }); + expect(first?.initialized).toBe(true); // current state, not stale + + unsub(); + store.destroy(); + }); +}); diff --git a/packages/scorm/tests/adapters/vue.test.ts b/packages/scorm/tests/adapters/vue.test.ts new file mode 100644 index 0000000..91df953 --- /dev/null +++ b/packages/scorm/tests/adapters/vue.test.ts @@ -0,0 +1,35 @@ +import { describe, it, expect } from 'vitest'; +import { effectScope } from 'vue'; +import { useScorm } from '../../src/vue/index'; + +describe('vue adapter — useScorm', () => { + it('creates a session with reactive status', () => { + const scope = effectScope(); + const result = scope.run(() => useScorm('2004', { noLmsBehavior: 'mock' }))!; + + expect(result.session.api).not.toBeNull(); + expect(result.status.value.initialized).toBe(false); + + result.initialize(); + expect(result.status.value.initialized).toBe(true); + + result.terminate(); + expect(result.status.value.terminated).toBe(true); + + scope.stop(); // triggers onScopeDispose → session.destroy() + }); + + it('works outside an effect scope and exposes destroy() for manual cleanup', () => { + // No effectScope: onScopeDispose is a no-op, so the caller must use destroy(). + const result = useScorm('2004', { noLmsBehavior: 'mock' }); + expect(typeof result.destroy).toBe('function'); + + result.initialize(); + expect(result.status.value.initialized).toBe(true); + + // destroy() detaches the change listener: further changes don't update status. + result.destroy(); + result.session.terminate(); + expect(result.status.value.terminated).toBe(false); // not observed after destroy + }); +}); diff --git a/packages/react-scorm/tests/api/improvements.test.ts b/packages/scorm/tests/api/improvements.test.ts similarity index 100% rename from packages/react-scorm/tests/api/improvements.test.ts rename to packages/scorm/tests/api/improvements.test.ts diff --git a/packages/react-scorm/tests/api/path-builders.test.ts b/packages/scorm/tests/api/path-builders.test.ts similarity index 100% rename from packages/react-scorm/tests/api/path-builders.test.ts rename to packages/scorm/tests/api/path-builders.test.ts diff --git a/packages/react-scorm/tests/api/scorm-api-extended.test.ts b/packages/scorm/tests/api/scorm-api-extended.test.ts similarity index 100% rename from packages/react-scorm/tests/api/scorm-api-extended.test.ts rename to packages/scorm/tests/api/scorm-api-extended.test.ts diff --git a/packages/react-scorm/tests/api/scorm-api.test.ts b/packages/scorm/tests/api/scorm-api.test.ts similarity index 100% rename from packages/react-scorm/tests/api/scorm-api.test.ts rename to packages/scorm/tests/api/scorm-api.test.ts diff --git a/packages/react-scorm/tests/api/time-format.test.ts b/packages/scorm/tests/api/time-format.test.ts similarity index 100% rename from packages/react-scorm/tests/api/time-format.test.ts rename to packages/scorm/tests/api/time-format.test.ts diff --git a/packages/react-scorm/tests/core/create-driver.test.ts b/packages/scorm/tests/core/create-driver.test.ts similarity index 100% rename from packages/react-scorm/tests/core/create-driver.test.ts rename to packages/scorm/tests/core/create-driver.test.ts diff --git a/packages/react-scorm/tests/core/driver-edge-cases.test.ts b/packages/scorm/tests/core/driver-edge-cases.test.ts similarity index 100% rename from packages/react-scorm/tests/core/driver-edge-cases.test.ts rename to packages/scorm/tests/core/driver-edge-cases.test.ts diff --git a/packages/react-scorm/tests/core/locator-extended.test.ts b/packages/scorm/tests/core/locator-extended.test.ts similarity index 100% rename from packages/react-scorm/tests/core/locator-extended.test.ts rename to packages/scorm/tests/core/locator-extended.test.ts diff --git a/packages/react-scorm/tests/core/locator.test.ts b/packages/scorm/tests/core/locator.test.ts similarity index 100% rename from packages/react-scorm/tests/core/locator.test.ts rename to packages/scorm/tests/core/locator.test.ts diff --git a/packages/react-scorm/tests/core/scorm12-driver.test.ts b/packages/scorm/tests/core/scorm12-driver.test.ts similarity index 100% rename from packages/react-scorm/tests/core/scorm12-driver.test.ts rename to packages/scorm/tests/core/scorm12-driver.test.ts diff --git a/packages/react-scorm/tests/core/scorm2004-driver.test.ts b/packages/scorm/tests/core/scorm2004-driver.test.ts similarity index 100% rename from packages/react-scorm/tests/core/scorm2004-driver.test.ts rename to packages/scorm/tests/core/scorm2004-driver.test.ts diff --git a/packages/react-scorm/tests/debug/logger.test.ts b/packages/scorm/tests/debug/logger.test.ts similarity index 100% rename from packages/react-scorm/tests/debug/logger.test.ts rename to packages/scorm/tests/debug/logger.test.ts diff --git a/packages/react-scorm/tests/errors/scorm-error.test.ts b/packages/scorm/tests/errors/scorm-error.test.ts similarity index 100% rename from packages/react-scorm/tests/errors/scorm-error.test.ts rename to packages/scorm/tests/errors/scorm-error.test.ts diff --git a/packages/react-scorm/tests/integration/full-lifecycle-12.test.ts b/packages/scorm/tests/integration/full-lifecycle-12.test.ts similarity index 100% rename from packages/react-scorm/tests/integration/full-lifecycle-12.test.ts rename to packages/scorm/tests/integration/full-lifecycle-12.test.ts diff --git a/packages/react-scorm/tests/integration/full-lifecycle-2004.test.ts b/packages/scorm/tests/integration/full-lifecycle-2004.test.ts similarity index 100% rename from packages/react-scorm/tests/integration/full-lifecycle-2004.test.ts rename to packages/scorm/tests/integration/full-lifecycle-2004.test.ts diff --git a/packages/scorm/tests/lifecycle/lifecycle.test.ts b/packages/scorm/tests/lifecycle/lifecycle.test.ts new file mode 100644 index 0000000..e35fb4c --- /dev/null +++ b/packages/scorm/tests/lifecycle/lifecycle.test.ts @@ -0,0 +1,80 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { autoTerminate } from '../../src/lifecycle/auto-terminate'; +import { autoCommit } from '../../src/lifecycle/auto-commit'; +import { createScormSession } from '../../src/session/create-scorm-session'; + +describe('autoTerminate (vanilla)', () => { + it('initializes immediately and terminates on dispose', () => { + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + const dispose = autoTerminate(session, { trackSessionTime: false }); + expect(session.status.initialized).toBe(true); + dispose(); + expect(session.status.terminated).toBe(true); + }); + + it('registers and unregisters unload/freeze listeners', () => { + const addSpy = vi.spyOn(window, 'addEventListener'); + const removeSpy = vi.spyOn(window, 'removeEventListener'); + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + + const dispose = autoTerminate(session, { trackSessionTime: false }); + const added = addSpy.mock.calls.map((c) => c[0]); + expect(added).toContain('beforeunload'); + expect(added).toContain('pagehide'); + expect(added).toContain('freeze'); + + dispose(); + const removed = removeSpy.mock.calls.map((c) => c[0]); + expect(removed).toContain('beforeunload'); + expect(removed).toContain('pagehide'); + expect(removed).toContain('freeze'); + + addSpy.mockRestore(); + removeSpy.mockRestore(); + }); + + it('sets session time before terminate when trackSessionTime=true', () => { + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + const spy = vi.spyOn(session.api!, 'setSessionTime'); + const dispose = autoTerminate(session, { trackSessionTime: true }); + dispose(); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('no-op when there is no API', () => { + const session = createScormSession('1.2', { noLmsBehavior: 'error' }); + const dispose = autoTerminate(session); + expect(session.status.initialized).toBe(false); + expect(() => dispose()).not.toThrow(); + }); +}); + +describe('autoCommit (vanilla)', () => { + afterEach(() => vi.useRealTimers()); + + it('commits on the configured interval and clears on dispose', () => { + vi.useFakeTimers(); + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + session.initialize(); + const spy = vi.spyOn(session.api!, 'commit'); + + const dispose = autoCommit(session, 1000); + expect(spy).not.toHaveBeenCalled(); + vi.advanceTimersByTime(3000); + expect(spy).toHaveBeenCalledTimes(3); + + dispose(); + vi.advanceTimersByTime(3000); + expect(spy).toHaveBeenCalledTimes(3); // no more after dispose + }); + + it('disabled with interval <= 0', () => { + vi.useFakeTimers(); + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + session.initialize(); + const spy = vi.spyOn(session.api!, 'commit'); + autoCommit(session, 0); + vi.advanceTimersByTime(10000); + expect(spy).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/react-scorm/tests/mock/mock-api-extended.test.ts b/packages/scorm/tests/mock/mock-api-extended.test.ts similarity index 100% rename from packages/react-scorm/tests/mock/mock-api-extended.test.ts rename to packages/scorm/tests/mock/mock-api-extended.test.ts diff --git a/packages/react-scorm/tests/mock/mock-driver.test.ts b/packages/scorm/tests/mock/mock-driver.test.ts similarity index 100% rename from packages/react-scorm/tests/mock/mock-driver.test.ts rename to packages/scorm/tests/mock/mock-driver.test.ts diff --git a/packages/react-scorm/tests/mock/mock-scorm12-api.test.ts b/packages/scorm/tests/mock/mock-scorm12-api.test.ts similarity index 100% rename from packages/react-scorm/tests/mock/mock-scorm12-api.test.ts rename to packages/scorm/tests/mock/mock-scorm12-api.test.ts diff --git a/packages/react-scorm/tests/mock/mock-scorm2004-api.test.ts b/packages/scorm/tests/mock/mock-scorm2004-api.test.ts similarity index 100% rename from packages/react-scorm/tests/mock/mock-scorm2004-api.test.ts rename to packages/scorm/tests/mock/mock-scorm2004-api.test.ts diff --git a/packages/react-scorm/tests/react/react-extended.test.tsx b/packages/scorm/tests/react/react-extended.test.tsx similarity index 100% rename from packages/react-scorm/tests/react/react-extended.test.tsx rename to packages/scorm/tests/react/react-extended.test.tsx diff --git a/packages/react-scorm/tests/react/scorm-provider.test.tsx b/packages/scorm/tests/react/scorm-provider.test.tsx similarity index 100% rename from packages/react-scorm/tests/react/scorm-provider.test.tsx rename to packages/scorm/tests/react/scorm-provider.test.tsx diff --git a/packages/react-scorm/tests/react/use-scorm-auto-commit.test.tsx b/packages/scorm/tests/react/use-scorm-auto-commit.test.tsx similarity index 100% rename from packages/react-scorm/tests/react/use-scorm-auto-commit.test.tsx rename to packages/scorm/tests/react/use-scorm-auto-commit.test.tsx diff --git a/packages/react-scorm/tests/react/use-scorm-auto-terminate.test.tsx b/packages/scorm/tests/react/use-scorm-auto-terminate.test.tsx similarity index 100% rename from packages/react-scorm/tests/react/use-scorm-auto-terminate.test.tsx rename to packages/scorm/tests/react/use-scorm-auto-terminate.test.tsx diff --git a/packages/react-scorm/tests/react/use-scorm-session.test.tsx b/packages/scorm/tests/react/use-scorm-session.test.tsx similarity index 100% rename from packages/react-scorm/tests/react/use-scorm-session.test.tsx rename to packages/scorm/tests/react/use-scorm-session.test.tsx diff --git a/packages/react-scorm/tests/react/use-scorm.test.tsx b/packages/scorm/tests/react/use-scorm.test.tsx similarity index 100% rename from packages/react-scorm/tests/react/use-scorm.test.tsx rename to packages/scorm/tests/react/use-scorm.test.tsx diff --git a/packages/react-scorm/tests/result/result.test.ts b/packages/scorm/tests/result/result.test.ts similarity index 100% rename from packages/react-scorm/tests/result/result.test.ts rename to packages/scorm/tests/result/result.test.ts diff --git a/packages/scorm/tests/session/create-scorm-session.test.ts b/packages/scorm/tests/session/create-scorm-session.test.ts new file mode 100644 index 0000000..78375dc --- /dev/null +++ b/packages/scorm/tests/session/create-scorm-session.test.ts @@ -0,0 +1,83 @@ +import { describe, it, expect, vi } from 'vitest'; +import { createScormSession } from '../../src/session/create-scorm-session'; + +describe('createScormSession', () => { + it('mock mode: exposes api and starts uninitialized', () => { + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + expect(session.api).not.toBeNull(); + expect(session.status.initialized).toBe(false); + expect(session.status.terminated).toBe(false); + expect(session.status.apiFound).toBe(true); + }); + + it('initialize() updates status and emits change', () => { + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + const cb = vi.fn(); + session.on('change', cb); + + const r = session.initialize(); + expect(r?.ok).toBe(true); + expect(session.status.initialized).toBe(true); + expect(cb).toHaveBeenCalledTimes(1); + expect(cb).toHaveBeenCalledWith(expect.objectContaining({ initialized: true })); + }); + + it('terminate() updates status and emits change', () => { + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + session.initialize(); + const cb = vi.fn(); + session.on('change', cb); + + const r = session.terminate(); + expect(r?.ok).toBe(true); + expect(session.status.terminated).toBe(true); + expect(session.status.initialized).toBe(false); + expect(cb).toHaveBeenCalledTimes(1); + }); + + it('status reference is stable between changes, new after a change', () => { + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + const before = session.status; + expect(session.status).toBe(before); // stable + session.initialize(); + expect(session.status).not.toBe(before); // new object after change + }); + + it('on() returns an unsubscribe; off() also removes', () => { + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + const cb = vi.fn(); + const off = session.on('change', cb); + off(); + session.initialize(); + expect(cb).not.toHaveBeenCalled(); + + const cb2 = vi.fn(); + session.on('change', cb2); + session.off('change', cb2); + session.terminate(); + expect(cb2).not.toHaveBeenCalled(); + }); + + it('destroy() clears all listeners', () => { + const session = createScormSession('2004', { noLmsBehavior: 'mock' }); + const cb = vi.fn(); + session.on('change', cb); + session.destroy(); + session.initialize(); + expect(cb).not.toHaveBeenCalled(); + }); + + it('no API (error behavior): api null, lifecycle methods return undefined', () => { + const session = createScormSession('1.2', { noLmsBehavior: 'error' }); + expect(session.api).toBeNull(); + expect(session.status.apiFound).toBe(false); + expect(session.initialize()).toBeUndefined(); + expect(session.commit()).toBeUndefined(); + expect(session.terminate()).toBeUndefined(); + }); + + it("version 'auto' with no API falls back to fallbackVersion in status", () => { + const session = createScormSession('auto', { noLmsBehavior: 'mock', fallbackVersion: '1.2' }); + expect(session.status.version).toBe('1.2'); + }); +}); diff --git a/packages/react-scorm/tests/setup.ts b/packages/scorm/tests/setup.ts similarity index 100% rename from packages/react-scorm/tests/setup.ts rename to packages/scorm/tests/setup.ts diff --git a/packages/scorm/tests/wc/scorm-session.test.ts b/packages/scorm/tests/wc/scorm-session.test.ts new file mode 100644 index 0000000..f3afa88 --- /dev/null +++ b/packages/scorm/tests/wc/scorm-session.test.ts @@ -0,0 +1,80 @@ +import { describe, it, expect, vi } from 'vitest'; +import '../../src/wc/index'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +describe(' web component', () => { + it('registers the custom element on import', () => { + expect(customElements.get('scorm-session')).toBeTruthy(); + }); + + it('creates a session on connect and emits an initial change event', () => { + const el = document.createElement('scorm-session') as any; + el.setAttribute('version', '2004'); + el.setAttribute('no-lms-behavior', 'mock'); + const onChange = vi.fn(); + el.addEventListener('change', onChange); + + document.body.appendChild(el); + expect(el.session).not.toBeNull(); + expect(el.api).not.toBeNull(); + expect(onChange).toHaveBeenCalled(); + + document.body.removeChild(el); + }); + + it('initialize/terminate proxy to the session', () => { + const el = document.createElement('scorm-session') as any; + el.setAttribute('version', '2004'); + el.setAttribute('no-lms-behavior', 'mock'); + document.body.appendChild(el); + + const r = el.initialize(); + expect(r.ok).toBe(true); + expect(el.session.status.initialized).toBe(true); + el.terminate(); + expect(el.session.status.terminated).toBe(true); + + document.body.removeChild(el); + }); + + it('emits exactly one initial change event without auto-terminate', () => { + const el = document.createElement('scorm-session') as any; + el.setAttribute('version', '2004'); + el.setAttribute('no-lms-behavior', 'mock'); + const events: boolean[] = []; + el.addEventListener('change', (e: Event) => events.push((e as CustomEvent).detail.status.initialized)); + + document.body.appendChild(el); + expect(events).toEqual([false]); // single initial event, uninitialized + + document.body.removeChild(el); + }); + + it('auto-terminate emits a distinct initial then initialized event (no duplicate)', () => { + const el = document.createElement('scorm-session') as any; + el.setAttribute('version', '2004'); + el.setAttribute('no-lms-behavior', 'mock'); + el.setAttribute('auto-terminate', ''); + const events: boolean[] = []; + el.addEventListener('change', (e: Event) => events.push((e as CustomEvent).detail.status.initialized)); + + document.body.appendChild(el); + // initial (uninitialized) then the auto-terminate initialize (initialized) + expect(events).toEqual([false, true]); + + document.body.removeChild(el); + }); + + it('auto-terminate: initializes on connect, terminates on disconnect', () => { + const el = document.createElement('scorm-session') as any; + el.setAttribute('version', '2004'); + el.setAttribute('no-lms-behavior', 'mock'); + el.setAttribute('auto-terminate', ''); + document.body.appendChild(el); + expect(el.session.status.initialized).toBe(true); + + document.body.removeChild(el); + expect(el.session.status.terminated).toBe(true); + }); +}); diff --git a/packages/react-scorm/tsconfig.json b/packages/scorm/tsconfig.json similarity index 100% rename from packages/react-scorm/tsconfig.json rename to packages/scorm/tsconfig.json diff --git a/packages/scorm/tsup.config.ts b/packages/scorm/tsup.config.ts new file mode 100644 index 0000000..3cca654 --- /dev/null +++ b/packages/scorm/tsup.config.ts @@ -0,0 +1,37 @@ +import { defineConfig } from 'tsup'; + +// Framework deps are never bundled — they are optional peer dependencies. +// Regexes cover subpath imports like 'svelte/store' and '@angular/core/...'. +const external = ['react', 'react-dom', 'vue', /^@angular\//, /^svelte($|\/)/]; + +export default defineConfig([ + // ── ESM + CJS library, one entry per subpath ────────────────────────────── + { + entry: { + index: 'src/index.ts', + react: 'src/react/index.ts', + wc: 'src/wc/index.ts', + vue: 'src/vue/index.ts', + angular: 'src/angular/index.ts', + svelte: 'src/svelte/index.ts', + }, + format: ['esm', 'cjs'], + dts: true, + sourcemap: true, + clean: true, + splitting: false, + treeshake: true, + external, + }, + // ── IIFE / global build for