npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

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

About

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

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

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

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

Open Software & Tools

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

© 2026 – Pkg Stats / Ryan Hefner

@project-sunbird/sunbird-questionset-editor-web-component-react

v0.1.19

Published

React-based QuML QuestionSet Editor — usable as a React component or as a <sb-questionset-editor> web component in any framework.

Downloads

958

Readme

@project-sunbird/sunbird-questionset-editor-web-component-react

A React-based authoring editor for Sunbird QuML QuestionSets (question banks organized into sections/hierarchy). It is distributed as:

  • a native custom element<sb-questionset-editor> — that can be embedded in any framework or plain HTML, and
  • a React component (QuestionsetEditor) for apps that are already on React.

It replaces the older Angular questionset-editor-library and is built to be embedded by a host app (e.g. the Sunbird portal), not run standalone.


Installation

npm install @project-sunbird/sunbird-questionset-editor-web-component-react react react-dom

react and react-dom are peer dependencies (^18.0.0 || ^19.0.0) — they are not bundled and must be provided by the host page/app. The published bundle externalizes react, react-dom, react-dom/client, and react/jsx-runtime, so your app (or an import map, if you're not using a bundler) must resolve these.

The package ships as an ES module only:

| package.json field | value | |---|---| | module / entry | dist/index.js | | types | dist/index.d.ts | | stylesheet | dist/style.css (import path: @project-sunbird/sunbird-questionset-editor-web-component-react/dist/style.css) | | files | dist (everything published lives under dist/) |

The component renders in the light DOM (no Shadow DOM), so it relies on the page loading dist/style.css — it does not inject its own styles.

Static assets that must be served

The dist/ folder contains more than just index.js/style.css; it also includes assets that are referenced at fixed, root-relative runtime paths, not resolved via the module graph. Your app must make these reachable at the paths below (e.g. copy them into your public/static directory, or serve dist/ itself at your app root):

  • dist/assets/sunbird-quml-player.js and dist/assets/sunbird-quml-player-styles.css — the QuML player used for question/questionset preview, lazy-loaded at runtime from /assets/sunbird-quml-player.js by default (override via config.playerScriptUrl).
  • dist/assets/libs/mathEquation/** — the MathQuill/KaTeX equation-editor modal (iframe), loaded from the fixed path /assets/libs/mathEquation/plugin/mathModal/index.html. Copy the whole mathEquation folder as-is, including its nested fonts/ (KaTeX) and css/js subfolders — its index.html, katex.min.css, and mathquill.css all load these siblings by relative path, so they must stay nested exactly as shipped, not flattened or split across other static folders.
  • dist/ckeditor/ckeditor.js — CKEditor 4 classic build used for rich-text question/option fields. This is not loaded automatically; the host page must load it as a global <script> tag before the editor mounts (it registers window.ClassicEditor, which CKEditorField looks for).

Quick start — Web component (framework-agnostic)

Register the custom element once, then create it like any other DOM element and set its configuration as element properties (not string attributes) so React receives real objects:

<script src="/ckeditor/ckeditor.js"></script>
<link rel="stylesheet" href="/style.css" />

<div id="root"></div>

<script type="module">
  import { registerQuestionsetEditor } from '@project-sunbird/sunbird-questionset-editor-web-component-react';
  registerQuestionsetEditor(); // defines <sb-questionset-editor>, idempotent

  const editor = document.createElement('sb-questionset-editor');

  editor.context = {
    authToken: '',
    userId: 'user-001',
    channel: 'my-channel',
    pdata: { id: 'sunbird.portal', ver: '1.0' },
    env: 'questionset_editor',
    contentId: 'do_123',
    identifier: 'do_123',
    framework: 'NCF',
  };

  editor.config = {
    mode: 'edit',
    objectType: 'QuestionSet',
    primaryCategory: 'Practice Question Set',
    maxDepth: 3,
  };

  editor.style.cssText = 'display:block;width:100%;height:100%;';
  document.getElementById('root').appendChild(editor);
</script>

The tag name is fixed: sb-questionset-editor. registerQuestionsetEditor() guards against double-registration, so it's safe to call more than once.


Quick start — React

import { QuestionsetEditor } from '@project-sunbird/sunbird-questionset-editor-web-component-react';
import '@project-sunbird/sunbird-questionset-editor-web-component-react/dist/style.css';

<QuestionsetEditor
  context={context}
  config={config}
  onQuestionSaved={(question) => console.log('saved', question)}
  onHierarchySaved={(hierarchy) => console.log('hierarchy saved', hierarchy)}
  onError={(err) => console.error(err)}
/>

API

<QuestionsetEditor> props

| Prop | Type | Required | Description | |---|---|---|---| | context | IContext | ✅ | Runtime context — user, session, channel, content identifier | | config | IConfig | ✅ | Editor mode and behaviour | | metadata | Record<string, unknown> | — | Pre-loaded content metadata | | data | unknown | — | Reserved/advanced pass-through data | | apiBaseUrl | string | — | Base URL for all API calls. Omit when using a server-side proxy | | onToolbarEvent | (e: { action: ToolbarAction; data?: unknown }) => void | — | Fired on every toolbar action | | onQuestionSaved | (question: unknown) => void | — | Fired after a question is saved | | onHierarchySaved | (hierarchy: unknown) => void | — | Fired after a successful hierarchy save | | onError | (error: Error) => void | — | Fired on unrecoverable editor errors |

On the web component, set the same fields as element properties (e.g. editor.onQuestionSaved = (q) => ...), the same way context/config are set.

IContext

Identifies the current user/session and the content being edited. sid, did, channel, and pdata are required; everything else is optional.

interface IContext {
  authToken?: string;
  userId?: string;
  user?: { id: string; fullName?: string; firstName?: string; lastName?: string; orgIds?: string[] };
  sid: string;
  did: string;
  uid?: string;
  channel: string;
  pdata: { id: string; ver: string; pid?: string };
  env?: string;
  contentId?: string;
  identifier?: string;      // same value as contentId is also accepted
  framework?: string;
  targetFWIds?: string[];
  rollup?: Record<string, string>;
  contextRollup?: Record<string, string>;
  objectRollup?: Record<string, string>;
  cdata?: Array<Record<string, unknown>>;
  tags?: string[];
  host?: string;             // telemetry host/endpoint
  endpoint?: string;
  timeDiff?: number;
  defaultLicense?: string;
  enableReviewEdit?: boolean;   // allow reviewer edits in orgreview/sourcingreview
  uiLanguage?: string;          // 'en' | 'ar' | 'fr' | 'hi' | 'pt'
  labels?: Record<string, string>;  // overrides for editor button labels
  cloudStorage?: { provider?: string; presigned_headers?: Record<string, string> };
  cloudStorageUrls?: string[];  // blob-storage origins to rewrite to /assets/public/
}

IConfig

Controls editor mode and behaviour. mode and objectType are required.

interface IConfig {
  mode: 'edit' | 'review' | 'read' | 'orgreview' | 'sourcingreview';
  objectType: string;               // e.g. 'QuestionSet'
  hideSubmitForReviewBtn?: boolean;
  playerScriptUrl?: string;         // default '/assets/sunbird-quml-player.js'
  apiSlug?: string;                 // API path prefix, default '/api'
  questionSet?: { maxQuestionsLimit?: number };
  showAddCollaborator?: boolean;
  primaryCategory?: string;
  framework?: string[];
  targetFWIds?: string[];
  toolbarConfig?: Record<string, unknown>;
  hierarchy?: Record<string, unknown>;
  children?: unknown[];
  defaultFields?: Record<string, unknown>;
  maxDepth?: number;
  questionTypes?: string[];         // restrict which question types are offered
  maxQuestions?: number;
  showSolutions?: boolean;
  showHints?: boolean;
  showTimer?: boolean;
  categoryDefinitionApiVersion?: 'v1' | 'v4';  // default 'v4'
}

ToolbarAction

type ToolbarAction =
  | 'back' | 'preview' | 'sendForReview' | 'onFormValueChange' | 'onFormStatusChange'
  | 'saveContent' | 'publish' | 'reject';

API base URL

By default all API calls are made relative to the page origin (works with a server-side proxy). To point directly at a backend, either pass the prop:

<QuestionsetEditor {...config} apiBaseUrl="https://api.your-sunbird-instance.com" />

or call the exported setters directly (also lets you override the /api path prefix independently):

import { setApiBaseUrl, setApiSlug } from '@project-sunbird/sunbird-questionset-editor-web-component-react';

setApiBaseUrl('https://api.your-sunbird-instance.com');
setApiSlug('/api'); // default

Stores (advanced)

The editor exposes its internal Zustand stores for advanced integration scenarios — for example, reading the current tree selection or editor mode from outside the component:

import { useEditorStore, useTreeStore, useQuestionStore, useUiStore } from '@project-sunbird/sunbird-questionset-editor-web-component-react';

// Inside a React component
const selectedNodeId = useTreeStore((s) => s.selectedNodeId);
const editorMode      = useEditorStore((s) => s.editorMode);

Styles

The stylesheet must be imported once in your app — it is not auto-injected (the component renders in the light DOM, not Shadow DOM):

import '@project-sunbird/sunbird-questionset-editor-web-component-react/dist/style.css';

Supported question types

Registered out of the box (matching the old editor's six built-in QuML types):

| Type | qType | primaryCategory | Description | |---|---|---|---| | Multiple Choice | MCQ | Multiple Choice Question | Pick one correct option from a list | | Subjective | SA (aliases: VSA, LA) | Subjective Question | Free-form written or long answer | | Fill in the Blank | FTB | FTB Question | Hide words in a sentence with [[ ]] | | Match the Following | MTF | Match The Following Question | Pair items across two columns | | Sequence | SEQ | Sequence Question | Arrange items in the correct order | | Reorder | REO | Reorder Question | Rearrange shuffled words into a sentence | | True / False | BOOL | Boolean Question | Pick between true or false choices |

config.questionTypes can be used to restrict which of these are offered when adding a question. Host apps can also register additional/custom types before mounting the editor via the exported registry functions: registerQuestionType, resolveQuestionType, resolveByQType, resolveByCategory, resolveByInteractionType, allQuestionTypes.


Development setup

# Clone the repo
git clone https://github.com/Sunbird-inQuiry/editor-v2.git
cd editor

# Install
npm install

# Dev server with mock data — no .env needed, just run:
npm run dev

To point the dev server at a real backend instead of mock data, copy .env.example to .env and fill in your credentials (BASE_URL, AUTH_TOKEN, USER_TOKEN, CONTENT_ID, etc.):

cp .env.example .env
# edit .env with your credentials

npm run build   # runs tsc -b && vite build
npm run serve   # standalone server.js, reads .env
# Run tests
npm test

Compatibility

| Dependency | Version | |---|---| | React | 18.x or 19.x | | React DOM | 18.x or 19.x |


License

MIT — see the repository root for the full licence text.


Notes and caveats

  • CKEditor must be loaded manually. Add <script src=".../ckeditor.js"></script> (from this package's dist/ckeditor/ckeditor.js) to the host page before the editor mounts; rich-text fields look for window.ClassicEditor and log an error if it isn't present.
  • No Shadow DOM. The custom element renders into the light DOM so the Sunbird design system CSS (dist/style.css) applies directly — make sure it's loaded on the page.
  • QuML player script for previews defaults to the root-relative path /assets/sunbird-quml-player.js; override with config.playerScriptUrl if you serve it elsewhere.
  • Localized UI labels are available for en, ar, fr, hi, pt via context.uiLanguage.
  • This package is ESM-only ("type": "module") and built for evergreen browsers; there is no UMD/CJS build.