@tatamicks/core 0.3.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,81 +1,518 @@
1
- # @tatamicks/core
2
-
3
- A headless, extensible document editor for React with grid-based layout system.
4
-
5
- ## Features
6
-
7
- - 🎯 **Headless Architecture** - Complete control over UI rendering
8
- - 🔌 **Plugin System** - Extensible block types through plugins
9
- - 📐 **Grid-based Layout** - Precise positioning with grid system
10
- - 🎨 **CSS Modules** - Scoped styling without conflicts
11
- - 📝 **TypeScript** - Full type safety
12
- - ⚛️ **React 18+** - Modern React features
13
-
14
- ## Installation
15
-
16
- ```bash
17
- npm install @tatamicks/core react react-dom
18
- ```
19
-
20
- ## Quick Start
21
-
22
- waiting for documentation...
23
-
24
- ## Basic Usage
25
-
26
- waiting for documentation...
27
-
28
- ### With Plugins
29
-
30
- waiting for documentation...
31
-
32
- ## API Reference
33
-
34
- ### NoteEditor Props
35
-
36
- | Prop | Type | Required | Description |
37
- |------|------|----------|-------------|
38
- | `initialBlocks` | `Block[]` | Yes | Initial block data |
39
- | `onChange` | `(blocks: Block[]) => void` | Yes | Callback when blocks change |
40
- | `plugins` | `Plugin[]` | No | Array of plugins to use |
41
- | `gridSize` | `number` | No | Grid cell size in pixels (default: 20) |
42
- | `className` | `string` | No | Additional CSS class |
43
-
44
- ## Architecture
45
-
46
- This library follows a headless architecture pattern, providing:
47
-
48
- - **Core Components**: `NoteForm`, `NoteEdit`, `NoteView`
49
- - **Plugin System**: Extensible block types
50
- - **State Management**: Controlled component pattern
51
- - **Grid System**: Precise layout control
52
-
53
- ## Development
54
-
55
- ```bash
56
- # Install dependencies
57
- npm install
58
-
59
- # Run development server
60
- npm run dev
61
-
62
- # Build library
63
- npm run build
64
-
65
- # Run tests
66
- npm test
67
-
68
- # Type checking
69
- npm run typecheck
70
- ```
71
-
72
- ## License
73
-
74
- MIT © 2025 yonpachi (株式会社<a href="https://yatch.pro">torchi</a>) – yatch
75
-
76
- ## Links
77
-
78
- - [GitHub Repository](https://github.com/torchi-net/tatamicks)
79
- - [Documentation](https://github.com/torchi-net/tatamicks/tree/main/docs)
80
- - [Issues](https://github.com/torchi-net/tatamicks/issues)
81
- - 株式会社<a href="https://yatch.pro">torchi</a>
1
+ # @tatamicks/core
2
+
3
+ React 向けのヘッドレスで拡張可能なドキュメントエディターです。グリッドベースのレイアウト、複数ページの `Book` スキーマ、プラグインによるブロック拡張、編集 UI を組み合わせるための ActionBar / Sidebar を提供します。
4
+
5
+ ## 特徴
6
+
7
+ - **グリッドレイアウト** 列/行グリッドにブロックを配置。`fr`、`mm`、`px` などの単位を扱えます。
8
+ - **3 つのモード** `FORM` はテンプレート作成、`EDIT` は値入力、`VIEW` は読み取り専用表示です。
9
+ - **プラグインシステム** 組み込みブロックに加えて、独自の `BlockPlugin` `createPluginRegistry([...plugins])` に渡すだけで登録できます。
10
+ - **コンポーザブル UI** `ActionBar`、`Sidebar`、`DefaultSelectionActionBarOverlay` をそのまま使うか、必要なパネルだけ差し替えられます。
11
+ - **Undo / Redo** `useNoteContext` が履歴、選択、ページ移動、値管理をまとめて配線します。
12
+ - **バリデーションと印刷** 入力検証の表示、複数ページ印刷、印刷用 CSS 生成に対応します。
13
+
14
+ ## インストール
15
+
16
+ ```bash
17
+ npm install @tatamicks/core react react-dom
18
+ ```
19
+
20
+ アプリのエントリーポイントで CSS を読み込んでください。
21
+
22
+ ```ts
23
+ import "@tatamicks/core/styles";
24
+ ```
25
+
26
+ Next.js App Router では `app/layout.tsx`、Pages Router では `pages/_app.tsx` のようなグローバル CSS を読み込める場所で import します。Vite / SPA では `main.tsx` や `App.tsx` で読み込めます。
27
+
28
+ ## クイックスタート
29
+
30
+ ```tsx
31
+ import { useState } from "react";
32
+ import {
33
+ ActionBar,
34
+ CheckboxPlugin,
35
+ DEFAULT_BOOK,
36
+ DefaultSelectionActionBarOverlay,
37
+ Note,
38
+ NoteMode,
39
+ SelectPlugin,
40
+ Sidebar,
41
+ TextPlugin,
42
+ createPluginRegistry,
43
+ getDefaultActionBarSections,
44
+ getDefaultSidebarTabs,
45
+ useNoteContext,
46
+ } from "@tatamicks/core";
47
+ import "@tatamicks/core/styles";
48
+
49
+ const pluginRegistry = createPluginRegistry([
50
+ TextPlugin,
51
+ CheckboxPlugin,
52
+ SelectPlugin,
53
+ ]);
54
+
55
+ export default function App() {
56
+ const [mode, setMode] = useState<NoteMode>(NoteMode.FORM);
57
+ const { context } = useNoteContext({
58
+ initialBook: DEFAULT_BOOK,
59
+ pluginRegistry,
60
+ });
61
+
62
+ return (
63
+ <div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
64
+ <ActionBar sections={getDefaultActionBarSections({ context })} />
65
+
66
+ <div style={{ display: "flex", gap: 8, padding: 8 }}>
67
+ <button type="button" onClick={() => setMode(NoteMode.FORM)}>
68
+ テンプレート作成
69
+ </button>
70
+ <button type="button" onClick={() => setMode(NoteMode.EDIT)}>
71
+ 値入力
72
+ </button>
73
+ <button type="button" onClick={() => setMode(NoteMode.VIEW)}>
74
+ 閲覧
75
+ </button>
76
+ </div>
77
+
78
+ <div style={{ display: "flex", flex: 1, overflow: "hidden" }}>
79
+ <div style={{ flex: 1, overflow: "auto", padding: 16 }}>
80
+ <div ref={context.containerRef} style={{ position: "relative" }}>
81
+ <Note mode={mode} context={context} />
82
+ <DefaultSelectionActionBarOverlay context={context} />
83
+ </div>
84
+ </div>
85
+
86
+ <Sidebar tabs={getDefaultSidebarTabs({ context })} />
87
+ </div>
88
+ </div>
89
+ );
90
+ }
91
+ ```
92
+
93
+ ## 中心 API
94
+
95
+ ### `Note`
96
+
97
+ `Note` は `mode` に応じて `NoteForm` / `NoteEdit` / `NoteView` を切り替えるトップレベルコンポーネントです。状態は `context` に集約します。
98
+
99
+ ```tsx
100
+ <Note
101
+ mode={NoteMode.FORM}
102
+ context={context}
103
+ showValidation={false}
104
+ scale={1}
105
+ className="my-note"
106
+ />
107
+ ```
108
+
109
+ `NoteForm`、`NoteEdit`、`NoteView` も export されています。`Note` を使うと実行時にモードを切り替えられますが、ページ固定のモードが分かっている場合は各コンポーネントを直接使う方がシンプルです。
110
+
111
+ ### `useNoteContext`
112
+
113
+ フルエディターを組み立てるための統合フックです。Book 履歴、入力値、バインディング、アクション、ページ移動、選択状態をまとめた `context` を返します。
114
+
115
+ ```ts
116
+ const { context } = useNoteContext({
117
+ initialBook,
118
+ pluginRegistry,
119
+ defaultValues,
120
+ extra,
121
+ });
122
+ ```
123
+
124
+ `containerRef` は `context.containerRef` 経由でアクセスします。独自の履歴層だけが必要な場合は、低レベル API として `useBookHistory({ initialBook, maxHistory })` も利用できます。
125
+
126
+ ## データモデル
127
+
128
+ ### `Book`
129
+
130
+ ```ts
131
+ interface Book {
132
+ paper: Paper;
133
+ pages: [Page, ...Page[]];
134
+ metaData?: Record<string, Value>;
135
+ }
136
+ ```
137
+
138
+ ### `Page`
139
+
140
+ ```ts
141
+ interface Page {
142
+ grid: Grid;
143
+ blocks: Block[];
144
+ blockDefaults?: BlockDefaults;
145
+ metaData?: Record<string, Value>;
146
+ }
147
+ ```
148
+
149
+ ### `Paper`
150
+
151
+ ```ts
152
+ interface Paper {
153
+ size: PaperSize;
154
+ margin: PaperMargin;
155
+ orientation?: boolean;
156
+ autoWidth?: boolean;
157
+ autoHeight?: boolean;
158
+ }
159
+ ```
160
+
161
+ ### `Block`
162
+
163
+ ```ts
164
+ interface Block<P = Record<string, Value>> {
165
+ id: string;
166
+ kind: string;
167
+ layout: { x: number; y: number; w: number; h: number };
168
+ props: P;
169
+ style?: BlockStyle;
170
+ behavior?: BlockBehavior;
171
+ hiddenBinding?: HiddenBinding;
172
+ initValue?: Value;
173
+ }
174
+ ```
175
+
176
+ `props` の解決順序は低い順に `PropDef.defaultProps`、`page.blockDefaults[kind]`、`block.props` です。後からマージされる値ほど優先されます。
177
+
178
+ ## プラグイン
179
+
180
+ ### 組み込みプラグイン
181
+
182
+ `TextPlugin`、`CheckboxPlugin`、`SelectPlugin` はメインエントリから import できます。
183
+ `ButtonPlugin`、`StepperPlugin`、`NoteBlockPlugin` は Advanced プラグインとして `@tatamicks/core/canvas` からのみ公開されています。
184
+
185
+ ```ts
186
+ // 基本プラグイン(@tatamicks/core)
187
+ import {
188
+ CheckboxPlugin,
189
+ SelectPlugin,
190
+ TextPlugin,
191
+ createPluginRegistry,
192
+ } from "@tatamicks/core";
193
+
194
+ // Advanced プラグイン(@tatamicks/core/canvas)
195
+ import {
196
+ ButtonPlugin,
197
+ NoteBlockPlugin,
198
+ StepperPlugin,
199
+ } from "@tatamicks/core/canvas";
200
+
201
+ const pluginRegistry = createPluginRegistry([
202
+ TextPlugin,
203
+ CheckboxPlugin,
204
+ SelectPlugin,
205
+ ButtonPlugin,
206
+ StepperPlugin,
207
+ NoteBlockPlugin,
208
+ ]);
209
+ ```
210
+
211
+ `createPluginRegistry` には `BlockPlugin` を直接渡します。追加の wrapper 型や registry 用 plugin 型をユーザー側で用意する必要はありません。
212
+
213
+ ### カスタムプラグイン
214
+
215
+ ```tsx
216
+ import { forwardRef, useImperativeHandle, useRef } from "react";
217
+ import {
218
+ alignmentProp,
219
+ paddingProp,
220
+ placeholderProp,
221
+ } from "@tatamicks/core/canvas";
222
+ import type {
223
+ BaseBlockProps,
224
+ BlockPlugin,
225
+ BlockRef,
226
+ RendererProps,
227
+ } from "@tatamicks/core/canvas";
228
+
229
+ interface LabelProps extends BaseBlockProps {
230
+ label: string;
231
+ placeholder: string;
232
+ }
233
+
234
+ const LabelRenderer = forwardRef<
235
+ BlockRef,
236
+ RendererProps<LabelProps, string>
237
+ >(({ props, value, onChange, readOnly }, ref) => {
238
+ const inputRef = useRef<HTMLInputElement>(null);
239
+
240
+ useImperativeHandle(ref, () => ({
241
+ focus: () => inputRef.current?.focus(),
242
+ }));
243
+
244
+ return (
245
+ <label style={{ display: "grid", gap: 4 }}>
246
+ <span>{props.label}</span>
247
+ <input
248
+ ref={inputRef}
249
+ value={value ?? ""}
250
+ placeholder={props.placeholder}
251
+ readOnly={readOnly}
252
+ onChange={(event) => onChange(event.target.value)}
253
+ />
254
+ </label>
255
+ );
256
+ });
257
+
258
+ LabelRenderer.displayName = "LabelRenderer";
259
+
260
+ export const LabelPlugin: BlockPlugin<LabelProps, string> = {
261
+ kind: "label",
262
+ meta: {
263
+ displayName: "ラベル入力",
264
+ description: "ラベル付きのテキスト入力",
265
+ defaultSize: { w: 4, h: 2 },
266
+ },
267
+ Renderer: LabelRenderer,
268
+ properties: [
269
+ alignmentProp,
270
+ paddingProp,
271
+ placeholderProp,
272
+ {
273
+ kind: "labelText",
274
+ defaultProps: { label: "Label" },
275
+ },
276
+ ],
277
+ validateProps: (raw): LabelProps => {
278
+ const value =
279
+ typeof raw === "object" && raw !== null
280
+ ? (raw as Record<string, unknown>)
281
+ : {};
282
+ return {
283
+ label: typeof value.label === "string" ? value.label : "Label",
284
+ placeholder:
285
+ typeof value.placeholder === "string" ? value.placeholder : "",
286
+ };
287
+ },
288
+ validateValue: (raw): string | null => {
289
+ return typeof raw === "string" ? raw : null;
290
+ },
291
+ };
292
+ ```
293
+
294
+ ### `PropDef`
295
+
296
+ `properties` に渡す各 `PropDef` は、プロパティパネルに表示する設定単位です。
297
+
298
+ ```ts
299
+ interface PropDef {
300
+ kind: string;
301
+ defaultProps: Record<string, Value>;
302
+ component?: React.ComponentType<{
303
+ value: Value;
304
+ onChange: (value: Value) => void;
305
+ readOnly?: boolean;
306
+ }>;
307
+ }
308
+ ```
309
+
310
+ 組み込みの `alignmentProp`、`paddingProp`、`fontStyleProp`、`placeholderProp`、`requiredProp` はすべて `PropDef` です。独自 UI が必要な場合は `component` を指定し、省略時は既定の編集 UI にフォールバックします。
311
+
312
+ ## ActionBar / Sidebar
313
+
314
+ 既定 UI は `useNoteContext` の `context` を渡すだけで動作します。
315
+
316
+ ```tsx
317
+ <ActionBar sections={getDefaultActionBarSections({ context })} />
318
+
319
+ <Sidebar tabs={getDefaultSidebarTabs({ context })} />
320
+
321
+ <DefaultSelectionActionBarOverlay context={context} />
322
+ ```
323
+
324
+ `getDefaultActionBarSections()` や `getDefaultSidebarTabs()` の戻り値は配列なので、`filter` / `map` で一部を差し替えられます。
325
+
326
+ ## 値の管理
327
+
328
+ `useNoteContext` は入力値(`EDIT` / `VIEW` モードで各ブロックに入力された値)を内部で管理します。
329
+ 現在の全入力値は `context.values`(`Record<string, Value>`)から読み取れます。
330
+
331
+ ### 値の保存
332
+
333
+ 外部に保存したい場合(API 送信・LocalStorage など)は `useEffect` で変化を監視します。
334
+
335
+ ```tsx
336
+ const { context } = useNoteContext({ initialBook, pluginRegistry });
337
+
338
+ // context.values が変わるたびにバックエンドへ送信する例
339
+ useEffect(() => {
340
+ save(context.values);
341
+ }, [context.values]);
342
+ ```
343
+
344
+ ### 値の復元
345
+
346
+ 保存済みの値を復元するには `defaultValues` に渡してコンポーネントをマウントします。
347
+ `defaultValues` はマウント時のみ参照されます(詳細は `useNoteContext` の JSDoc を参照)。
348
+
349
+ ```tsx
350
+ if (!savedValues) return <Loading />;
351
+ return (
352
+ <MyEditor
353
+ key={documentId}
354
+ initialBook={template}
355
+ defaultValues={savedValues}
356
+ />
357
+ );
358
+ ```
359
+
360
+ ### 不要な値のクリーン
361
+
362
+ ブロックを削除すると `context.values` にそのブロックの値が残ります。
363
+ 保存前に `cleanValues` でブロックが存在しないエントリを除去できます。
364
+
365
+ ```ts
366
+ import { cleanValues } from "@tatamicks/core";
367
+
368
+ const valuesToSave = cleanValues(context.book, context.values);
369
+ ```
370
+
371
+ ## シリアライズと検証
372
+
373
+ ```ts
374
+ import {
375
+ deserializeBook,
376
+ parseBook,
377
+ serializeBook,
378
+ validateBook,
379
+ } from "@tatamicks/core";
380
+
381
+ const json = serializeBook(book);
382
+ const restored = deserializeBook(json);
383
+ const parsed = parseBook(JSON.parse(json));
384
+ const errors = validateBook(parsed);
385
+ ```
386
+
387
+ `validateBook` は Book 全体の構造検証ではなく、現在はブロック ID の重複検出とページあたりのブロック数上限チェックを行います。未検証データを `Book` として扱う前には `parseBook` または `deserializeBook` を使ってください。
388
+
389
+ ### 値のシリアライズ
390
+
391
+ `context.values` は JSON シリアライズ可能なので `JSON.stringify` でそのまま保存できます。復元時は `deserializeValues` で検証してから使います。
392
+
393
+ ```ts
394
+ import { deserializeValues } from "@tatamicks/core";
395
+
396
+ // 保存
397
+ const valuesJson = JSON.stringify(context.values);
398
+
399
+ // 復元(JSON が不正または無効な Value が含まれる場合は例外を投げる)
400
+ const restoredValues = deserializeValues(valuesJson);
401
+ ```
402
+
403
+ ## ページ操作
404
+
405
+ ```ts
406
+ import { addPage, movePage, removePage, setPage } from "@tatamicks/core";
407
+
408
+ const withNewPage = addPage(book);
409
+ const moved = movePage(withNewPage, 0, 1);
410
+ const removed = removePage(moved, 1);
411
+ const updated = setPage(removed, 0, nextPage);
412
+ ```
413
+
414
+ ## 印刷
415
+
416
+ ```ts
417
+ import { printNote } from "@tatamicks/core";
418
+ import type { PrintSettings } from "@tatamicks/core";
419
+
420
+ // コンテキストをそのまま渡すだけで印刷できる
421
+ printNote(context);
422
+
423
+ // 用紙サイズ・向き・余白を上書きして印刷する場合
424
+ const settings: PrintSettings = {
425
+ paperSize: "A4",
426
+ orientation: false, // false = 縦向き
427
+ margin: { top: "10mm", bottom: "10mm", left: "15mm", right: "15mm" },
428
+ };
429
+ printNote(context, settings);
430
+ ```
431
+
432
+ 低レベル API として `resolveEffectivePaper()` は `@tatamicks/core/canvas` サブパスから export されています。
433
+
434
+ ```ts
435
+ import { resolveEffectivePaper } from "@tatamicks/core/canvas";
436
+ ```
437
+
438
+ ## TypeScript
439
+
440
+ 主要な型は `@tatamicks/core` から export されています。
441
+
442
+ ```ts
443
+ // @tatamicks/core からインポートできる型
444
+ import type {
445
+ ActionContext,
446
+ BindingContext,
447
+ BuiltinActionId,
448
+ Block,
449
+ BlockBehavior,
450
+ Book,
451
+ Grid,
452
+ NoteContext,
453
+ Page,
454
+ Paper,
455
+ PluginRegistry,
456
+ PrintMargin,
457
+ PrintSettings,
458
+ Value,
459
+ } from "@tatamicks/core";
460
+
461
+ // カスタムプラグイン実装に必要な型は @tatamicks/core/canvas からインポートする
462
+ import type {
463
+ BaseBlockProps,
464
+ BlockPlugin,
465
+ BlockPluginMeta,
466
+ BlockRef,
467
+ PropDef,
468
+ RendererProps,
469
+ } from "@tatamicks/core/canvas";
470
+ ```
471
+
472
+ ## v0.x からの移行
473
+
474
+ 現在の公開スキーマ名は `Book` / `Page` です。古い資料で `NoteBook` / `FormSchema` と呼んでいるものは、それぞれ現在の `Book` / `Page` に相当します。
475
+
476
+ ## 組み込みブロックを追加する(Contributor 向け)
477
+
478
+ 新しいブロックを `src/canvas/blocks/` に追加する手順です。
479
+
480
+ ### ファイル構成
481
+
482
+ `src/canvas/blocks/<kindName>/` ディレクトリを作成し、以下のファイルを追加します。
483
+
484
+ | ファイル | 役割 |
485
+ |---|---|
486
+ | `types.ts` | `<Kind>BlockProps` 型定義 |
487
+ | `props.ts` | `PropDef[]` 定義(プロパティパネル設定) |
488
+ | `renderer.tsx` | `<Kind>Renderer` — `forwardRef` コンポーネント |
489
+ | `plugin.ts` | `<Kind>Plugin: BlockPlugin<...>` 本体 |
490
+ | `index.ts` | `plugin.ts` を re-export |
491
+ | `__tests__/<kind>Plugin.test.ts` | プラグイン単体テスト |
492
+ | `__stories__/<Kind>.stories.tsx` | Storybook ストーリー |
493
+
494
+ ### チェックリスト
495
+
496
+ 1. `types.ts` — `<Kind>BlockProps extends BaseBlockProps` を定義する
497
+ 2. `props.ts` — `properties: PropDef[]` と `defaultProps` を定義する
498
+ 3. `renderer.tsx` — `forwardRef<BlockRef, RendererProps<...>>` で実装し `useImperativeHandle` で `focus()` を公開する
499
+ 4. `plugin.ts` — `BlockPlugin<KindProps, KindValue>` として組み立て、`validateProps` / `validateValue` を実装する(値は `Value` 互換型のみ。日付は `Date` インスタンスではなく ISO 8601 文字列で表現)
500
+ 5. `index.ts` — `export { <Kind>Plugin } from "./plugin";` のみを記述する
501
+ 6. `src/canvas/blocks/index.ts` — `export * from "./<kindName>";` を追記し、`basePlugins.ts` のデフォルトリストにも追加する
502
+ 7. `__tests__/<kind>Plugin.test.ts` — `validateProps` / `validateValue` のユニットテスト(エッジケース含む)を追加する
503
+ 8. `__stories__/<Kind>.stories.tsx` — `Default` など代表ストーリーを最低1件追加する
504
+ 9. typecheck / lint / unit test / build がすべて通ることを確認する
505
+
506
+ 旧 `@tatamicks/text`、`@tatamicks/checkbox`、`@tatamicks/select` は `@tatamicks/core` に統合されています。
507
+
508
+ | 旧 import | 新 import |
509
+ |-----------|-----------|
510
+ | `import { TextPlugin } from "@tatamicks/text"` | `import { TextPlugin } from "@tatamicks/core"` |
511
+ | `import { CheckboxPlugin } from "@tatamicks/checkbox"` | `import { CheckboxPlugin } from "@tatamicks/core"` |
512
+ | `import { SelectPlugin } from "@tatamicks/select"` | `import { SelectPlugin } from "@tatamicks/core"` |
513
+
514
+ 移行の詳細は [docs/20_migration_guide.md](../../docs/20_migration_guide.md) を参照してください。
515
+
516
+ ## ライセンス
517
+
518
+ MIT