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

@arrisa/schema

v1.0.29

Published

Arrisa — schema

Readme

@arrisa/schema

Ready-made Arrisa editor schema extensions — document roots, blocks, lists, marks, links, colors, and images wired with menus, keymaps, and input rules on top of @arrisa/types.

This package is editor chrome, not the low-level Schema class (that lives in @arrisa/doc). Node/mark types come from @arrisa/types; factories here register those types and attach UI behavior.

Install

pnpm add @arrisa/schema @arrisa/editor @arrisa/state @arrisa/doc
# or npm / yarn

Peer-style stack used by the factories: @arrisa/command, @arrisa/history, @arrisa/phrases, @arrisa/types (installed as package dependencies).

Quick start

Document editor with a lightweight preset:

import {Arrisa, menuBar} from "@arrisa/editor"
import {basicSchema} from "@arrisa/schema"
import {history} from "@arrisa/history"

let parent = document.getElementById("editor")!

let editor = Arrisa.create({
    parent,
    config: [basicSchema(), history(), menuBar()],
})

editor.focus()

Full rich-text (blocks, lists, marks, media, resize):

import {fullSchema} from "@arrisa/schema"

let editor = Arrisa.create({
    parent,
    config: [fullSchema(), history(), menuBar()],
})

Compose factories instead of a preset:

import {
    blockDoc,
    paragraph,
    heading,
    bulletList,
    strong,
    emphasis,
    link,
} from "@arrisa/schema"

let config = [
    blockDoc(),
    paragraph(),
    heading(),
    bulletList(),
    strong(),
    emphasis(),
    link(),
]

Features / concepts

| Concept | Role | |---------|------| | Presets | basicSchema, fullSchema, inlineSchema, composeSchema, messengerSchema assemble extensions for common hosts | | Factories | paragraph(), strong(), image(), … each returns an EditorState.Extension (or array) | | Namespaces | Merged namespaces expose pieces for re-ranking/omission: paragraph.button, link.tooltip, image.dropHandler, … | | Types vs chrome | Schema elements from @arrisa/types; this package adds menu, keys, input rules, dialogs, themes |

Presets

| Preset | Typical use | |--------|-------------| | basicSchema() | Block Doc, paragraphs, headings, line breaks, strong/em/link | | inlineSchema() | InlineDoc, basic marks, inline images, line breaks | | composeSchema(config?) | Block chat field: Doc, paragraphs, lists, quotes, code blocks, compose marks (no spoiler/headings/media) | | messengerSchema(config?) | Inline/spoiler chat: InlineDoc + marks (default); codeBlocks: true → block Doc + CodeBlock | | fullSchema() | Blocks, lists, quotes, HR, full inline marks, image/figure, image resize |

Mark bundles (usable alone or inside presets):

  • basicMarks() — strong, emphasis, link
  • composeMarks() — strong, em, underline, strike, code, link, clear-formatting button (no spoiler)
  • messengerMarks() — spoiler, strong, em, underline, strike, code, link
  • inlineMarks() — basic + code, underline, strike, spoiler, super/sub, text/background color

Input rules (selection)

  • Headings: # … ######
  • Code block: ``` or ```lang then space (language → CodeBlockLanguage)
  • Blockquote: >
  • Horizontal rule: --- on an empty textblock
  • Bullet list: optional space + -
  • Ordered list: optional space + N.

Shortcuts (selection)

| Binding | Action | |---------|--------| | Mod-b / Mod-i / Mod-u / Mod-/ / Mod-` | Strong / em / underline / strike / code | | Mod-Shift-p | Spoiler | | Mod-. / Mod-, | Super / subscript | | Mod-k | Toggle link (floating prompt when none selected) | | Ctrl-Shift-0…6 | Paragraph / heading levels | | Ctrl-Shift-\ | Code block | | Mod-Shift-l/r/e | Align left / right / center | | Ctrl-Alt-i (Mac: Ctrl-Cmd-i) | Insert/update image dialog |

Main public API

Document roots and blocks

import {
    blockDoc,
    inlineDoc,
    paragraph,
    heading,
    codeBlock,
    alignment,
    direction,
    blockquote,
    horizontalRule,
    lineBreak,
} from "@arrisa/schema"
  • blockDoc() / inlineDoc() — register Doc or InlineDoc
  • paragraph(), heading(), codeBlock() — textblocks + chrome (codeBlock registers CodeBlock + CodeBlockLanguage, fence input rule, theme, and token colors). Tab inside a code block inserts four spaces; Shift-Tab removes up to four spaces before the caret or at the start of the line; elsewhere Tab moves focus.
  • alignment(), direction() — textblock marks + menu submenus
  • blockquote(), horizontalRule() — structure + input rules
  • lineBreak() — hard break leaf (br)

Lists

import {bulletList, orderedList} from "@arrisa/schema"

bulletList()                       // block list items (default)
bulletList({blockItems: false})    // inline-only list items
orderedList()

Marks

import {
    strong,
    emphasis,
    code,
    underline,
    strikethrough,
    spoiler,
    superscript,
    subscript,
    basicMarks,
    composeMarks,
    messengerMarks,
    inlineMarks,
    clearFormattingButton,
} from "@arrisa/schema"

Link

import {
    link,
    isLinkPasteUrl,
    type LinkConfig,
    type LinkPrompt,
    type LinkPromptRequest,
} from "@arrisa/schema"

link() // mark + Mod-k + cursor tooltip + paste URL over selection or at caret
link({prompt: false}) // remove-only (no add-link UI)
let prompt: LinkPrompt = (req: LinkPromptRequest) => {
    // host UI; req.apply sanitizes via applyLink — never raw Link.of
    req.apply("https://example.com")
}
let linkConf: LinkConfig = {prompt}
link(linkConf)

if (isLinkPasteUrl(text)) {
    // absolute http(s) / mailto / xmpp, no spaces
}

Pasting a lone absolute URL (isLinkPasteUrl) at an empty caret inserts the URL as a Link; a non-empty selection is wrapped and the URL string is not inserted. HTML <a href> paste is unchanged.

Default add-link UI is a floating tooltip form (prompt: "floating"), not a Dialog panel. The default toggle removes existing links or opens add UI; it does not prefill href (remove-first). cancel dismisses the built-in floating field and focuses the editor; custom prompts still own their DOM. req.apply / form submit run applyLink (sanitizeLinkHref). The read-only cursor-inside-link tooltip is unchanged.

Color

import {color, backgroundColor, ColorPicker} from "@arrisa/schema"

color()
backgroundColor()

// Optional palette layout (facets on ColorPicker)
// ColorPicker.options / ColorPicker.width

Values must pass isSafeCssColor from @arrisa/types (defense in depth in the menu control).

Image / figure

import {
    image,
    figure,
    imageResizing,
    imageUploader,
    activeImage,
    insertImage,
    imageDialog,
} from "@arrisa/schema"

// Register types + insert chrome
image()
figure({captioned: true})
imageResizing()

// Provide an upload handler for file picker / drop
imageUploader.of(async (file, editor, progress) => {
    progress(50)
    return await uploadToCdn(file)
})
  • image() — inline Image + dialog, menu, drop handler
  • figure({captioned?}) — block Figure, optionally CaptionedFigure
  • imageResizing() — ImageSize mark, drag handle, enlarge/shrink keys
  • insertImage / imageDialog — open/close insert panel
  • activeImage(sel) — active image/figure tag at selection, if any

Presets and messenger config

import {
    basicSchema,
    fullSchema,
    inlineSchema,
    composeSchema,
    messengerSchema,
    type ComposeSchemaConfig,
    type MessengerSchemaConfig,
} from "@arrisa/schema"

composeSchema({
    exclusivity: "none", // | "code-strike" | {isolating: Mark.Type[]}
    link: {prompt: "floating"}, // | false | (req) => { req.apply(href) }
})

messengerSchema({
    exclusivity: "none", // | "code-strike" | {isolating: Mark.Type[]}
    codeBlocks: false, // true → Doc + paragraphs + CodeBlock (fence rule + language)
})

messengerSchema remains the inline/spoiler path. Use composeSchema for a block chat field (lists, quotes, fenced code, no spoiler).

Chat field hosts should prefer @arrisa/message’s composeField (block lists/quote/code) or messengerCompose (inline/spoiler).

Code token roles

import {highlightCode, type CodeRole, type CodeSpan} from "@arrisa/schema"

let spans = highlightCode(source, "ts")

highlightCode(text, language?) returns CodeSpan[]. from and to are UTF-16 indexes into text (from inclusive, to exclusive).

Spans are a view. They are not part of PreEntity or the dump.

Blocks over 32_000 UTF-16 units or 400 lines return [].

Roles: kw-flow, kw-decl, modifier, type, function, property, string, interpolation, escape, number, constant, comment, directive, tag, attribute, regex.

Doc comments set emphasis: "bold". Other spans omit emphasis.

Language ids are case-folded. Aliases: typescript → ts, javascript → js, python → py, kotlin → kt, yml → yaml. A missing, empty, or unknown language does not add keywords.

codeBlock() paints those spans on the fenced block. Each token is a decoration with classes arrisa-tok and arrisa-tok-<role>. Doc comments (emphasis: "bold") also get arrisa-tok-bold. Inline code marks are not tokenized. Painting does not change dump text or pre offsets.

Colors use two layers. Set --arrisa-code-<name> (no -default) on an ancestor to override a role. Scheme defaults are --arrisa-code-<name>-default, chosen by Arrisa.colorScheme (&light / &dark). Neither path re-lexes. Names: violet, indigo, lavender, sky, blue, cyan, green, lime, mint, peach, orange, seagreen, coral, red, yellow, pink.

Layering / related packages

@arrisa/types  →  @arrisa/schema  →  host app
                      ↑
              @arrisa/editor, @arrisa/command, …

| Package | Relationship | |---------|----------------| | @arrisa/types | Schema elements (Paragraph, Strong, Link, …) | | @arrisa/doc | Low-level Schema, document tree — not this package | | @arrisa/editor | View: menus, input rules, dialogs, themes | | @arrisa/command | Commands (toggleMark, setTextblockType, …) and menu model | | @arrisa/message | Messenger compose preset on top of schema marks | | @arrisa/table | Table chrome; combine with fullSchema() / custom blocks |

Security

Schema registration is not an XSS boundary. Untrusted HTML paste/load needs app sanitization (Arrisa.htmlSanitize / sanitizeHTML). Link and image helpers reject dangerous schemes (javascript:, unsafe data:, etc.). See SECURITY.md.

License

MIT © inshinrei