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

@tatamicks/core

v1.0.12

Published

A headless, extensible document editor for React with grid-based layout system

Readme

@tatamicks/core

React 向けのヘッドレスで拡張可能なドキュメントエディターです。グリッドベースのレイアウト、複数ページの Book スキーマ、プラグインによるブロック拡張、編集 UI を組み合わせるための ActionBar / Sidebar を提供します。

特徴

  • グリッドレイアウト — 列/行グリッドにブロックを配置。frmmpx などの単位を扱えます。
  • 3 つのモードFORM はテンプレート作成、EDIT は値入力、VIEW は読み取り専用表示です。
  • プラグインシステム — 組み込みブロックに加えて、独自の BlockPlugincreatePluginRegistry([...plugins]) に渡すだけで登録できます。
  • コンポーザブル UIActionBarSidebarDefaultSelectionActionBarOverlay をそのまま使うか、必要なパネルだけ差し替えられます。
  • Undo / RedouseNoteContext が履歴、選択、ページ移動、値管理をまとめて配線します。
  • バリデーションと印刷 — 入力検証の表示、複数ページ印刷、印刷用 CSS 生成に対応します。

インストール

npm install @tatamicks/core react react-dom

アプリのエントリーポイントで CSS を読み込んでください。

import "@tatamicks/core/styles";

Next.js App Router では app/layout.tsx、Pages Router では pages/_app.tsx のようなグローバル CSS を読み込める場所で import します。Vite / SPA では main.tsxApp.tsx で読み込めます。

クイックスタート

import { useState } from "react";
import {
  ActionBar,
  CheckboxPlugin,
  DEFAULT_BOOK,
  DefaultSelectionActionBarOverlay,
  Note,
  NoteMode,
  SelectPlugin,
  Sidebar,
  TextPlugin,
  createPluginRegistry,
  getDefaultActionBarSections,
  getDefaultSidebarTabs,
  useNoteContext,
} from "@tatamicks/core";
import "@tatamicks/core/styles";

const pluginRegistry = createPluginRegistry([
  TextPlugin,
  CheckboxPlugin,
  SelectPlugin,
]);

export default function App() {
  const [mode, setMode] = useState<NoteMode>(NoteMode.FORM);
  const { context } = useNoteContext({
    initialBook: DEFAULT_BOOK,
    pluginRegistry,
  });

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
      <ActionBar sections={getDefaultActionBarSections({ context })} />

      <div style={{ display: "flex", gap: 8, padding: 8 }}>
        <button type="button" onClick={() => setMode(NoteMode.FORM)}>
          テンプレート作成
        </button>
        <button type="button" onClick={() => setMode(NoteMode.EDIT)}>
          値入力
        </button>
        <button type="button" onClick={() => setMode(NoteMode.VIEW)}>
          閲覧
        </button>
      </div>

      <div style={{ display: "flex", flex: 1, overflow: "hidden" }}>
        <div style={{ flex: 1, overflow: "auto", padding: 16 }}>
          <div ref={context.containerRef} style={{ position: "relative" }}>
            <Note mode={mode} context={context} />
            <DefaultSelectionActionBarOverlay context={context} />
          </div>
        </div>

        <Sidebar tabs={getDefaultSidebarTabs({ context })} />
      </div>
    </div>
  );
}

中心 API

Note

Notemode に応じて NoteForm / NoteEdit / NoteView を切り替えるトップレベルコンポーネントです。状態は context に集約します。

<Note
  mode={NoteMode.FORM}
  context={context}
  showValidation={false}
  scale={1}
  className="my-note"
/>

NoteFormNoteEditNoteView も export されています。Note を使うと実行時にモードを切り替えられますが、ページ固定のモードが分かっている場合は各コンポーネントを直接使う方がシンプルです。

useNoteContext

フルエディターを組み立てるための統合フックです。Book 履歴、入力値、バインディング、アクション、ページ移動、選択状態をまとめた context を返します。

const { context } = useNoteContext({
  initialBook,
  pluginRegistry,
  defaultValues,
  extra,
});

containerRefcontext.containerRef 経由でアクセスします。独自の履歴層だけが必要な場合は、低レベル API として useBookHistory({ initialBook, maxHistory }) も利用できます。

データモデル

Book

interface Book {
  paper: Paper;
  pages: [Page, ...Page[]];
  metaData?: Record<string, Value>;
}

Page

interface Page {
  grid: Grid;
  blocks: Block[];
  blockDefaults?: BlockDefaults;
  metaData?: Record<string, Value>;
}

Paper

interface Paper {
  size: PaperSize;
  margin: PaperMargin;
  orientation?: boolean;
  autoWidth?: boolean;
  autoHeight?: boolean;
}

Block

interface Block<P = Record<string, Value>> {
  id: string;
  kind: string;
  layout: { x: number; y: number; w: number; h: number };
  props: P;
  style?: BlockStyle;
  behavior?: BlockBehavior;
  hiddenBinding?: HiddenBinding;
  initValue?: Value;
}

props の解決順序は低い順に PropDef.defaultPropspage.blockDefaults[kind]block.props です。後からマージされる値ほど優先されます。

プラグイン

組み込みプラグイン

TextPluginCheckboxPluginSelectPlugin はメインエントリから import できます。 ButtonPluginStepperPluginNoteBlockPlugin は Advanced プラグインとして @tatamicks/core/canvas からのみ公開されています。

// 基本プラグイン(@tatamicks/core)
import {
  CheckboxPlugin,
  SelectPlugin,
  TextPlugin,
  createPluginRegistry,
} from "@tatamicks/core";

// Advanced プラグイン(@tatamicks/core/canvas)
import {
  ButtonPlugin,
  NoteBlockPlugin,
  StepperPlugin,
} from "@tatamicks/core/canvas";

const pluginRegistry = createPluginRegistry([
  TextPlugin,
  CheckboxPlugin,
  SelectPlugin,
  ButtonPlugin,
  StepperPlugin,
  NoteBlockPlugin,
]);

createPluginRegistry には BlockPlugin を直接渡します。追加の wrapper 型や registry 用 plugin 型をユーザー側で用意する必要はありません。

カスタムプラグイン

import { forwardRef, useImperativeHandle, useRef } from "react";
import {
  alignmentProp,
  paddingProp,
  placeholderProp,
} from "@tatamicks/core/canvas";
import type {
  BaseBlockProps,
  BlockPlugin,
  BlockRef,
  RendererProps,
} from "@tatamicks/core/canvas";

interface LabelProps extends BaseBlockProps {
  label: string;
  placeholder: string;
}

const LabelRenderer = forwardRef<
  BlockRef,
  RendererProps<LabelProps, string>
>(({ props, value, onChange, readOnly }, ref) => {
  const inputRef = useRef<HTMLInputElement>(null);

  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current?.focus(),
  }));

  return (
    <label style={{ display: "grid", gap: 4 }}>
      <span>{props.label}</span>
      <input
        ref={inputRef}
        value={value ?? ""}
        placeholder={props.placeholder}
        readOnly={readOnly}
        onChange={(event) => onChange(event.target.value)}
      />
    </label>
  );
});

LabelRenderer.displayName = "LabelRenderer";

export const LabelPlugin: BlockPlugin<LabelProps, string> = {
  kind: "label",
  meta: {
    displayName: "ラベル入力",
    description: "ラベル付きのテキスト入力",
    defaultSize: { w: 4, h: 2 },
  },
  Renderer: LabelRenderer,
  properties: [
    alignmentProp,
    paddingProp,
    placeholderProp,
    {
      kind: "labelText",
      defaultProps: { label: "Label" },
    },
  ],
  validateProps: (raw): LabelProps => {
    const value =
      typeof raw === "object" && raw !== null
        ? (raw as Record<string, unknown>)
        : {};
    return {
      label: typeof value.label === "string" ? value.label : "Label",
      placeholder:
        typeof value.placeholder === "string" ? value.placeholder : "",
    };
  },
  validateValue: (raw): string | null => {
    return typeof raw === "string" ? raw : null;
  },
};

PropDef

properties に渡す各 PropDef は、プロパティパネルに表示する設定単位です。

interface PropDef {
  kind: string;
  defaultProps: Record<string, Value>;
  component?: React.ComponentType<{
    value: Value;
    onChange: (value: Value) => void;
    readOnly?: boolean;
  }>;
}

組み込みの alignmentProppaddingPropfontStylePropplaceholderProprequiredProp はすべて PropDef です。独自 UI が必要な場合は component を指定し、省略時は既定の編集 UI にフォールバックします。

ActionBar / Sidebar

既定 UI は useNoteContextcontext を渡すだけで動作します。

<ActionBar sections={getDefaultActionBarSections({ context })} />

<Sidebar tabs={getDefaultSidebarTabs({ context })} />

<DefaultSelectionActionBarOverlay context={context} />

getDefaultActionBarSections()getDefaultSidebarTabs() の戻り値は配列なので、filter / map で一部を差し替えられます。

値の管理

useNoteContext は入力値(EDIT / VIEW モードで各ブロックに入力された値)を内部で管理します。 現在の全入力値は context.valuesRecord<string, Value>)から読み取れます。

値の保存

外部に保存したい場合(API 送信・LocalStorage など)は useEffect で変化を監視します。

const { context } = useNoteContext({ initialBook, pluginRegistry });

// context.values が変わるたびにバックエンドへ送信する例
useEffect(() => {
  save(context.values);
}, [context.values]);

値の復元

保存済みの値を復元するには defaultValues に渡してコンポーネントをマウントします。 defaultValues はマウント時のみ参照されます(詳細は useNoteContext の JSDoc を参照)。

if (!savedValues) return <Loading />;
return (
  <MyEditor
    key={documentId}
    initialBook={template}
    defaultValues={savedValues}
  />
);

不要な値のクリーン

ブロックを削除すると context.values にそのブロックの値が残ります。 保存前に cleanValues でブロックが存在しないエントリを除去できます。

import { cleanValues } from "@tatamicks/core";

const valuesToSave = cleanValues(context.book, context.values);

シリアライズと検証

import {
  deserializeBook,
  parseBook,
  serializeBook,
  validateBook,
} from "@tatamicks/core";

const json = serializeBook(book);
const restored = deserializeBook(json);
const parsed = parseBook(JSON.parse(json));
const errors = validateBook(parsed);

validateBook は Book 全体の構造検証ではなく、現在はブロック ID の重複検出とページあたりのブロック数上限チェックを行います。未検証データを Book として扱う前には parseBook または deserializeBook を使ってください。

値のシリアライズ

context.values は JSON シリアライズ可能なので JSON.stringify でそのまま保存できます。復元時は deserializeValues で検証してから使います。

import { deserializeValues } from "@tatamicks/core";

// 保存
const valuesJson = JSON.stringify(context.values);

// 復元(JSON が不正または無効な Value が含まれる場合は例外を投げる)
const restoredValues = deserializeValues(valuesJson);

ページ操作

import { addPage, movePage, removePage, setPage } from "@tatamicks/core";

const withNewPage = addPage(book);
const moved = movePage(withNewPage, 0, 1);
const removed = removePage(moved, 1);
const updated = setPage(removed, 0, nextPage);

印刷

import { printNote } from "@tatamicks/core";
import type { PrintSettings } from "@tatamicks/core";

// コンテキストをそのまま渡すだけで印刷できる
printNote(context);

// 用紙サイズ・向き・余白を上書きして印刷する場合
const settings: PrintSettings = {
  paperSize: "A4",
  orientation: false,    // false = 縦向き
  margin: { top: "10mm", bottom: "10mm", left: "15mm", right: "15mm" },
};
printNote(context, settings);

低レベル API として resolveEffectivePaper()@tatamicks/core/canvas サブパスから export されています。

import { resolveEffectivePaper } from "@tatamicks/core/canvas";

TypeScript

主要な型は @tatamicks/core から export されています。

// @tatamicks/core からインポートできる型
import type {
  ActionContext,
  BindingContext,
  BuiltinActionId,
  Block,
  BlockBehavior,
  Book,
  Grid,
  NoteContext,
  Page,
  Paper,
  PluginRegistry,
  PrintMargin,
  PrintSettings,
  Value,
} from "@tatamicks/core";

// カスタムプラグイン実装に必要な型は @tatamicks/core/canvas からインポートする
import type {
  BaseBlockProps,
  BlockPlugin,
  BlockPluginMeta,
  BlockRef,
  PropDef,
  RendererProps,
} from "@tatamicks/core/canvas";

v0.x からの移行

現在の公開スキーマ名は Book / Page です。古い資料で NoteBook / FormSchema と呼んでいるものは、それぞれ現在の Book / Page に相当します。

組み込みブロックを追加する(Contributor 向け)

新しいブロックを src/canvas/blocks/ に追加する手順です。

ファイル構成

src/canvas/blocks/<kindName>/ ディレクトリを作成し、以下のファイルを追加します。

| ファイル | 役割 | |---|---| | types.ts | <Kind>BlockProps 型定義 | | props.ts | PropDef[] 定義(プロパティパネル設定) | | renderer.tsx | <Kind>RendererforwardRef コンポーネント | | plugin.ts | <Kind>Plugin: BlockPlugin<...> 本体 | | index.ts | plugin.ts を re-export | | __tests__/<kind>Plugin.test.ts | プラグイン単体テスト | | __stories__/<Kind>.stories.tsx | Storybook ストーリー |

チェックリスト

  1. types.ts<Kind>BlockProps extends BaseBlockProps を定義する
  2. props.tsproperties: PropDef[]defaultProps を定義する
  3. renderer.tsxforwardRef<BlockRef, RendererProps<...>> で実装し useImperativeHandlefocus() を公開する
  4. plugin.tsBlockPlugin<KindProps, KindValue> として組み立て、validateProps / validateValue を実装する(値は Value 互換型のみ。日付は Date インスタンスではなく ISO 8601 文字列で表現)
  5. index.tsexport { <Kind>Plugin } from "./plugin"; のみを記述する
  6. src/canvas/blocks/index.tsexport * from "./<kindName>"; を追記し、basePlugins.ts のデフォルトリストにも追加する
  7. __tests__/<kind>Plugin.test.tsvalidateProps / validateValue のユニットテスト(エッジケース含む)を追加する
  8. __stories__/<Kind>.stories.tsxDefault など代表ストーリーを最低1件追加する
  9. typecheck / lint / unit test / build がすべて通ることを確認する

@tatamicks/text@tatamicks/checkbox@tatamicks/select@tatamicks/core に統合されています。

| 旧 import | 新 import | |-----------|-----------| | import { TextPlugin } from "@tatamicks/text" | import { TextPlugin } from "@tatamicks/core" | | import { CheckboxPlugin } from "@tatamicks/checkbox" | import { CheckboxPlugin } from "@tatamicks/core" | | import { SelectPlugin } from "@tatamicks/select" | import { SelectPlugin } from "@tatamicks/core" |

移行の詳細は docs/20_migration_guide.md を参照してください。

ライセンス

MIT