Read this document in Japanese / 日本語で読む
Nagarjuna (龍樹) — a tool for Bijakshara (種子), Hentai-kana (変体仮名), Itai-ji (異体字) and Kumi-moji (組み文字).
The project name comes from Nagarjuna (龍樹), a 2nd-century Indian Buddhist monk.
Recent Unicode extensions have made available the character sets needed for surveying stone Buddhist monuments and for digitising old maps and historical documents: Siddham script (Siddham), used for the seed syllables carved on stone Buddhas; Hentai-kana (Supplement, Extended-A); kanji variant forms; and combined characters.
However, entering and displaying data in those extended Unicode ranges is still far from easy: fonts are often missing, and IMEs usually cannot type the characters at all.
This repository is a JavaScript library that makes those characters easier to handle by supporting both their display and their input.
- Hentai-kana (using the NINJAL Hentai-kana font)
- Siddham script (using Noto Sans Siddham)
- Kanji variant forms (using Noto Sans JP)
- Automatic font loading
- Mobile-friendly IME
pnpm add @c4h/nagarjunaUp to version 1.1.0 this package was published under the unscoped name nagarjuna. From the 2026-11 release it is published as @c4h/nagarjuna. The API is unchanged; only the package name differs.
pnpm remove nagarjuna
pnpm add @c4h/nagarjunaThen change the module specifiers in your imports: nagarjuna becomes @c4h/nagarjuna, and nagarjuna/ime becomes @c4h/nagarjuna/ime.
- NagaIME (the new IME UI,
<naga-ime>): https://code4history.dev/Nagarjuna/ - Legacy IME demo (
IMEManager/<ime-ui>, deprecated in 1.2.0): https://code4history.dev/Nagarjuna/legacy.html
When you only need to display the special characters:
import { FontLoader } from '@c4h/nagarjuna';
const fontLoader = new FontLoader();
// Load only the fonts you need
await fontLoader.loadFonts({
hentaigana: true, // use Hentai-kana
siddham: true, // use Siddham script
itaiji: true // use kanji variant forms
});
// Apply the font family
const element = document.getElementById('target');
element.style.fontFamily = fontLoader.getFontFamilyString({
hentaigana: true,
siddham: true,
itaiji: true
});NagaIME is the IME UI for entering Hentai-kana, Siddham, Buddha names, variant forms and combined characters. It listens on the fields you attach it to and does not get in the way of ordinary input: a small 梵 button appears at the top right of the field, and Ctrl+J (⌘J on macOS) opens the popup from the keyboard.
1. From JavaScript
import { NagaIME } from '@c4h/nagarjuna/ime';
const ime = new NagaIME({ categories: ['hentaigana', 'itaiji'] });
ime.attach('#memo');2. Declaratively
import '@c4h/nagarjuna/ime'; // registers <naga-ime> (and the deprecated <ime-ui>)<input id="memo" />
<naga-ime for="memo"></naga-ime>3. Fields inside a shadow root
attach() with a string selector resolves it with document.querySelectorAll, so it cannot see fields inside a shadow root. Pass the Element instead, or put a <naga-ime for> element inside the same shadow root (the for lookup uses that root).
const host = document.querySelector('my-card');
const field = host.shadowRoot.querySelector('input');
ime.attach(field);4. API
Attributes map to properties one to one (<attribute> → <property>); the defaults below apply when neither is set.
| Attribute | Property | Type | Default |
|---|---|---|---|
for |
htmlFor |
space-separated ids | '' |
categories |
categories |
NagaCategoryId[] (space-separated in the attribute) |
all categories |
trigger-label |
triggerLabel |
string |
'梵' |
shortcut |
shortcut |
boolean |
true |
recent-storage-key |
recentStorageKey |
string | null |
'nagarjuna-naga-recents' |
recent-max |
recentMax |
integer ≥ 0 | 14 |
max-candidates |
maxCandidates |
integer ≥ 1 | 60 |
dock-breakpoint |
dockBreakpoint |
number (CSS px) | 560 |
load-fonts |
loadFonts |
boolean |
true |
| Method | What it does |
|---|---|
new NagaIME(options?) |
Creates the element. When it is not connected, attach() appends it to document.body. |
attach(target) |
Attaches to a selector string, an Element, or a list of them. Elements other than input / textarea are ignored. |
detach(target?) |
Detaches from the given target (all targets when omitted). |
destroy() |
Detaches everything, releases the DOM and the listeners, and removes the element. |
open(field) |
Opens the popup for a field that is attached. |
close({ refocus }) |
Closes the popup (refocus: true returns focus to the field). |
isOpen |
Whether the popup is open (read-only). |
| Event | detail |
|---|---|
nagaOpen |
{ field } |
nagaClose |
{ field } |
nagaCommit |
{ field, candidate } — fired after the candidate was inserted and the field's input event was dispatched |
The events are CustomEvents with bubbles: true. To follow what the user typed, listen to the field's own input event (or to nagaCommit when you only need the picked candidate).
The public types are NagaIMEOptions, NagaCandidate, NagaCategoryId and NagaIMEEventMap; NagaIME and those types are exported from @c4h/nagarjuna/ime.
5. Notes
- The trigger button, the popup and the caret mark are placed in document coordinates (
position: absolute), so they scroll together with the page. If<naga-ime>itself is placed inside aposition: fixedancestor, they do not follow page scrolling. new NagaIME()appends the element todocument.bodywhen it is not connected yet.- If two copies of the package are loaded,
<naga-ime>belongs to the copy that registered first;new NagaIME()throws aTypeErrorin that state (check it withcustomElements.get('naga-ime') === NagaIME). - Assigning
htmlFordoes not change theforattribute (unlike<label>). - Recently used characters are kept in
localStorageundernagarjuna-naga-recents;recent-max="0"turns the "recent" tab off and stops reading and writing it.
IMEManager, IIMEManager, IMEOptions, IMEAttachOptions and <ime-ui> are deprecated in 1.2.0, kept in 2.0.0 and will be removed in 3.0.0. Use NagaIME (<naga-ime>) for new code. A single console.warn is printed on the first IMEManager.getInstance() and when an <ime-ui> element is connected.
import { IMEManager } from '@c4h/nagarjuna/ime';
// Initialise the IME manager
const manager = IMEManager.getInstance();
// Attach the IME to an input field
const input = document.querySelector('input');
manager.attach(input, {
options: {
enabledTypes: {
hentaigana: true, // enable Hentai-kana conversion
siddham: true, // enable Siddham conversion
itaiji: true, // enable variant-form conversion
buddha_name: true // enable Buddha-name conversion (a subset of Siddham)
}
}
});
// Update IME options at runtime
manager.updateOptions({
enabledTypes: {
hentaigana: true,
siddham: false,
itaiji: true,
buddha_name: false
}
});IMEManager / <ime-ui> |
NagaIME (<naga-ime>) |
|---|---|
enabledTypes.hentaigana / .siddham / .itaiji |
the same ids in categories (['hentaigana', 'siddham', 'itaiji']) |
enabledTypes.buddha_name |
the category id buddha |
onChange(value) |
the field's input event, or the nagaCommit event |
updateOptions({ enabledTypes }) |
assign categories |
position: 'bottom' | 'cursor' |
none — NagaIME places the popup by itself |
The two IMEs do not behave the same way: NagaIME does not sit permanently in the page (it is opened explicitly with the 梵 button or Ctrl+J), and it supports entering several candidates in a row.
Copyright (c) 2024 Code for History
MIT License
This library uses the following fonts:
- NINJAL Hentai-kana font (Copyright 2022 National Institute for Japanese Language and Linguistics) — Apache License 2.0
- Noto Sans Siddham — SIL Open Font License 1.1
- Noto Sans JP — SIL Open Font License 1.1
# Install dependencies
pnpm install
# Start the development server
pnpm run dev
# Build
pnpm run build
# Test
pnpm testWe thank the National Institute for Japanese Language and Linguistics for providing the NINJAL Hentai-kana font.