@kubohiroya/turbowarp-extension-data-editor 0.5.0 → 0.7.0

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.
Files changed (48) hide show
  1. package/README.ja.md +9 -4
  2. package/README.md +7 -4
  3. package/dist/build-info.json +3 -3
  4. package/dist/descendants.js +1 -0
  5. package/dist/dialog-window.d.ts +8 -0
  6. package/dist/dialog-window.js +155 -0
  7. package/dist/preview.d.ts +3 -0
  8. package/dist/preview.js +100 -0
  9. package/dist/react.css +70 -0
  10. package/dist/react.d.ts +3 -0
  11. package/dist/react.js +12 -4
  12. package/dist/registry.d.ts +3 -0
  13. package/dist/registry.js +38 -9
  14. package/dist/rjsf.d.ts +2 -1
  15. package/dist/rjsf.js +10 -0
  16. package/dist/session.d.ts +1 -0
  17. package/dist/session.js +6 -0
  18. package/dist/turbowarp/bridge.js +42 -1
  19. package/dist/turbowarp/fixed-fields.d.ts +1 -1
  20. package/dist/turbowarp/fixed-fields.js +7 -2
  21. package/dist/turbowarp/icon.d.ts +3 -1
  22. package/dist/turbowarp/icon.js +5 -1
  23. package/dist/turbowarp.d.ts +2 -0
  24. package/dist/types.d.ts +8 -0
  25. package/dist/view.js +1 -0
  26. package/dist/webpack4/index.js +1 -1
  27. package/dist/webpack4/metafile.json +57 -31
  28. package/dist/webpack4/react.js +173 -4
  29. package/dist/webpack4/react.js.map +3 -3
  30. package/dist/webpack4/rjsf.js +13 -1
  31. package/dist/webpack4/rjsf.js.map +2 -2
  32. package/dist/webpack4/{shared-QIOZ73LT.js → shared-FW4MRLXW.js} +148 -8
  33. package/dist/webpack4/shared-FW4MRLXW.js.map +7 -0
  34. package/dist/webpack4/turbowarp.js +55 -5
  35. package/dist/webpack4/turbowarp.js.map +2 -2
  36. package/docs/README.md +3 -3
  37. package/docs/block-editor-descendants.md +1 -1
  38. package/docs/block-editor-e2e.md +6 -122
  39. package/docs/block-editor.md +1 -1
  40. package/docs/block-preview.md +45 -0
  41. package/docs/ci-success-gate.md +9 -29
  42. package/docs/dialog-window.md +50 -0
  43. package/docs/fixed-input-contracts.md +3 -1
  44. package/docs/github-pages.md +21 -57
  45. package/docs/package-distribution.md +3 -3
  46. package/docs/shared-extension-security.ja.md +6 -6
  47. package/package.json +2 -6
  48. package/dist/webpack4/shared-QIOZ73LT.js.map +0 -7
package/README.ja.md CHANGED
@@ -10,8 +10,13 @@ TurboWarp拡張が扱う構造化データを、フォームや独自UIで編集
10
10
 
11
11
  タブ・グループ・グリッドは[共通レイアウトガイド](docs/common-layout.ja.md)を参照してください。標準RJSFとSource/Formで同じJSON宣言を使用し、ホストの起動時フラグ `commonLayout` で有効にします(既定OFF)。
12
12
 
13
- [公開エディター・日英案内](https://kubohiroya.github.io/turbowarp-extension-data-editor/) と
14
- [公開・検証の運用Issue #26](https://github.com/kubohiroya/turbowarp-extension-data-editor/issues/26) も本リポジトリが所有します。
13
+ 既存フォームと下書きを共有する視覚プレビュー、アイコンのホバー表示は[ブロックプレビュー](docs/block-preview.md)を参照してください。
14
+
15
+ 本パッケージはライブラリです。統合したEditorのビルド・検証・GitHub Pages配信は、下流の [TurboWarp-Plus-Editor](https://github.com/kubohiroya/turbowarp-plus-editor) が所有します。本リポジトリのGitHub Pages提供は廃止し、旧URLでEditorやリダイレクトを提供しません。
16
+
17
+ <p><a class="twpe-edit-button" href="https://kubohiroya.github.io/turbowarp-plus-editor/editor.html?lang=ja"><img src="https://kubohiroya.github.io/turbowarp-plus-editor/assets/icon.svg" width="32" height="32" alt=""> TurboWarp-Plus-Editorで編集</a></p>
18
+
19
+ ボタンのHTMLとスタイルは [TurboWarp-Plus-EditorのREADME](https://github.com/kubohiroya/turbowarp-plus-editor#edit-button) を参照してください。
15
20
 
16
21
  ## 標準の JSON Schema エディター
17
22
 
@@ -127,12 +132,12 @@ npm install --save-exact @kubohiroya/turbowarp-extension-data-editor@0.2.2
127
132
 
128
133
  対応GUIはダイアログ、ソースフォーム、共通レイアウト、固定入力、子孫編集、一括セキュリティ確認を標準で提供します。
129
134
  通常の `editor.html?lang=ja` で利用でき、有効化用URLスイッチは不要です。低水準APIのhost能力交渉は維持します。
130
- 切戻しはGUIの変更と公開ページのGUI pinを一組でrevertします。
135
+ 切戻しはGUIの変更とTurboWarp-Plus-Editorの固定ソース設定を対応させてrevertします。
131
136
  確定値は既存引数へ保存し、UI・下書き・関数・revision をプロジェクトデータへ追加しません。
132
137
 
133
138
  ## webpack 4 / E2E
134
139
 
135
- [webpack 4配布物と設定API](docs/webpack4.md)、[GUI接続E2EとCIの実行手順](docs/block-editor-e2e.md)を参照。専用編集UIから互換ビルド・E2Eまで共通側で管理し、GUIには明示的なホスト接続とCI呼出しを残す。
140
+ [webpack 4配布物と設定API](docs/webpack4.md)、[GUI接続E2EとCIの実行手順](docs/block-editor-e2e.md)を参照。本リポジトリは専用編集UIと互換ビルドを管理します。GUIはホスト接続、TurboWarp-Plus-Editorは統合ビルド・E2E・配信を管理します。
136
141
 
137
142
  ## Schemaの共通型と診断変換
138
143
 
package/README.md CHANGED
@@ -10,8 +10,11 @@ Japanese guides for usage, APIs, GUI integration, publishing, and validation rec
10
10
 
11
11
  For tabs, groups, and grids, see the [shared layout guide (Japanese)](docs/common-layout.ja.md). Standard RJSF and Source/Form use the same JSON declarations, enabled by the host's startup flag `commonLayout` (off by default).
12
12
 
13
- This repository also owns the [public editor and English/Japanese guides](https://kubohiroya.github.io/turbowarp-extension-data-editor/) and
14
- [deployment and validation tracking issue #26](https://github.com/kubohiroya/turbowarp-extension-data-editor/issues/26).
13
+ This package is a library. The downstream [TurboWarp-Plus-Editor](https://github.com/kubohiroya/turbowarp-plus-editor) owns the integrated Editor build, validation and GitHub Pages distribution. This repository no longer provides GitHub Pages; its former URL does not serve an Editor or a redirect.
14
+
15
+ <p><a class="twpe-edit-button" href="https://kubohiroya.github.io/turbowarp-plus-editor/editor.html?lang=en"><img src="https://kubohiroya.github.io/turbowarp-plus-editor/assets/icon.svg" width="32" height="32" alt=""> Edit in TurboWarp-Plus-Editor</a></p>
16
+
17
+ See the [TurboWarp-Plus-Editor README](https://github.com/kubohiroya/turbowarp-plus-editor#edit-button) for the reusable button HTML and stylesheet.
15
18
 
16
19
  ## Standard JSON Schema editor
17
20
 
@@ -127,12 +130,12 @@ For `.sb3` saving, reloading through `project_url` and the OS file picker, Engli
127
130
 
128
131
  Supported GUIs provide dialogs, source forms, shared layouts, fixed inputs, descendant editing, and batch security confirmation by default.
129
132
  These are available at the normal `editor.html?lang=ja` URL without activation switches. Low-level API host capability negotiation remains in place.
130
- To roll back, revert the GUI changes together with the public page's GUI pin.
133
+ To roll back, revert the GUI changes together with TurboWarp-Plus-Editor's pinned source configuration.
131
134
  Committed values are stored in existing arguments. UI state, drafts, functions, and revisions are not added to project data.
132
135
 
133
136
  ## webpack 4 / E2E
134
137
 
135
- See the [webpack 4 distribution and configuration API (Japanese)](docs/webpack4.md) and [GUI integration E2E and CI procedures (Japanese)](docs/block-editor-e2e.md). This shared repository manages the custom editor UI, compatibility builds, and E2E tests; the GUI retains explicit host integration and CI calls.
138
+ See the [webpack 4 distribution and configuration API (Japanese)](docs/webpack4.md) and [GUI integration E2E and CI procedures (Japanese)](docs/block-editor-e2e.md). This shared repository manages the custom editor UI and compatibility builds. The GUI owns host integration, and TurboWarp-Plus-Editor owns integrated builds, E2E tests and distribution.
136
139
 
137
140
  ## Shared Schema types and diagnostic conversion
138
141
 
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-extension-data-editor",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "repository": "https://github.com/kubohiroya/turbowarp-extension-data-editor",
5
- "revision": "df1e78b6982ccc0c1ddb8a741a8d57848519c74d",
5
+ "revision": "d96532a91bc6046d4a3fda878425b30d6d10483e",
6
6
  "sourceState": "commit",
7
- "sourceSha256": "a1a4a1937cb033e4eee92fbd626511f76c8872506bdb81e921b65719d46c199b",
7
+ "sourceSha256": "daf5c14dd2a7eafbc5208413f168eb42fc817978e8517a66c31838dc48aa1841",
8
8
  "capabilities": {
9
9
  "preparedSource": 1
10
10
  }
@@ -260,6 +260,7 @@ _DescendantSession_snapshot = new WeakMap(), _DescendantSession_host = new WeakM
260
260
  const listeners = new Set();
261
261
  const editor = Object.freeze({
262
262
  reference: descriptor.reference,
263
+ getSnapshot: () => snapshot,
263
264
  getDraft: () => { __classPrivateFieldGet(this, _DescendantSession_instances, "m", _DescendantSession_assertOpen).call(this); return cloneJson(record.draft); },
264
265
  setDraft: (draft) => {
265
266
  __classPrivateFieldGet(this, _DescendantSession_instances, "m", _DescendantSession_assertEditable).call(this);
@@ -0,0 +1,8 @@
1
+ interface WindowLabels {
2
+ readonly headerClassName?: string | undefined;
3
+ readonly maximize?: string | undefined;
4
+ readonly restore?: string | undefined;
5
+ }
6
+ /** Add window controls to the injected host frame without depending on GUI internals. */
7
+ export declare function attachEditorDialogWindow(body: HTMLElement, labels: WindowLabels): (() => void) | undefined;
8
+ export {};
@@ -0,0 +1,155 @@
1
+ /** Add window controls to the injected host frame without depending on GUI internals. */
2
+ export function attachEditorDialogWindow(body, labels) {
3
+ var _a;
4
+ const dialog = body.closest('[role="dialog"]');
5
+ const header = (labels.headerClassName ? dialog === null || dialog === void 0 ? void 0 : dialog.getElementsByClassName(labels.headerClassName)[0] :
6
+ dialog === null || dialog === void 0 ? void 0 : dialog.querySelector('header'));
7
+ const content = body.closest('[data-block-editor-body]');
8
+ if (!dialog || !header || !content || !((_a = header.parentElement) === null || _a === void 0 ? void 0 : _a.contains(content)))
9
+ return;
10
+ const doc = dialog.ownerDocument, win = doc.defaultView;
11
+ if (!win)
12
+ return;
13
+ const frame = header.parentElement;
14
+ const initialStyle = dialog.style.cssText;
15
+ const language = doc.documentElement.lang || new URLSearchParams(win.location.search).get('lang') || win.navigator.language;
16
+ const japanese = language.startsWith('ja');
17
+ const maximizeLabel = labels.maximize || (japanese ? '全画面化' : 'Maximize');
18
+ const restoreLabel = labels.restore || (japanese ? '元のサイズに戻す' : 'Restore window');
19
+ let restoreBounds = null;
20
+ let gesture = null;
21
+ const handles = [];
22
+ dialog.setAttribute('data-block-editor-window', '');
23
+ if (frame !== dialog)
24
+ frame.setAttribute('data-block-editor-window-frame', '');
25
+ header.setAttribute('data-block-editor-titlebar', '');
26
+ const maximize = doc.createElement('button');
27
+ maximize.type = 'button';
28
+ maximize.setAttribute('data-block-editor-maximize', '');
29
+ const closeContainer = header.lastElementChild;
30
+ if (closeContainer === null || closeContainer === void 0 ? void 0 : closeContainer.querySelector('button, [role="button"]'))
31
+ closeContainer.prepend(maximize);
32
+ else
33
+ header.insertBefore(maximize, closeContainer);
34
+ const viewport = () => ({ width: win.innerWidth, height: win.innerHeight });
35
+ const bounds = () => {
36
+ const { x, y, width, height } = dialog.getBoundingClientRect();
37
+ return { x, y, width, height };
38
+ };
39
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
40
+ function fit(rect) {
41
+ const view = viewport();
42
+ const width = clamp(rect.width, Math.min(360, view.width), view.width);
43
+ const height = clamp(rect.height, Math.min(280, view.height), view.height);
44
+ return { width, height, x: clamp(rect.x, 0, view.width - width), y: clamp(rect.y, 0, view.height - height) };
45
+ }
46
+ function place(rect) {
47
+ Object.assign(dialog.style, { margin: '0', inset: 'auto', transform: 'none', maxHeight: '100dvh',
48
+ left: `${rect.x}px`, top: `${rect.y}px`, width: `${rect.width}px`, height: `${rect.height}px` });
49
+ }
50
+ function stop(event) {
51
+ if (!gesture || (event && 'pointerId' in event && event.pointerId !== gesture.id))
52
+ return;
53
+ const { target, id } = gesture;
54
+ gesture = null;
55
+ if (target.hasPointerCapture(id))
56
+ target.releasePointerCapture(id);
57
+ dialog.removeAttribute('data-block-editor-dragging');
58
+ }
59
+ function start(event, edge) {
60
+ if (restoreBounds || gesture || event.button !== 0 || !event.isPrimary)
61
+ return;
62
+ if (!edge && event.target.closest('button, a, input, select, textarea, [role="button"]'))
63
+ return;
64
+ event.preventDefault();
65
+ const target = event.currentTarget;
66
+ gesture = { id: event.pointerId, target, edge, x: event.clientX, y: event.clientY, rect: fit(bounds()) };
67
+ place(gesture.rect);
68
+ target.setPointerCapture(event.pointerId);
69
+ dialog.setAttribute('data-block-editor-dragging', '');
70
+ }
71
+ function move(event) {
72
+ if (!gesture || event.pointerId !== gesture.id)
73
+ return;
74
+ const { rect, edge } = gesture, view = viewport();
75
+ const dx = event.clientX - gesture.x, dy = event.clientY - gesture.y;
76
+ if (!edge) {
77
+ place(fit({ ...rect, x: rect.x + dx, y: rect.y + dy }));
78
+ return;
79
+ }
80
+ const minWidth = Math.min(360, view.width), minHeight = Math.min(280, view.height);
81
+ let left = rect.x, top = rect.y, right = rect.x + rect.width, bottom = rect.y + rect.height;
82
+ if (edge.includes('w'))
83
+ left = clamp(left + dx, 0, right - minWidth);
84
+ if (edge.includes('e'))
85
+ right = clamp(right + dx, left + minWidth, view.width);
86
+ if (edge.includes('n'))
87
+ top = clamp(top + dy, 0, bottom - minHeight);
88
+ if (edge.includes('s'))
89
+ bottom = clamp(bottom + dy, top + minHeight, view.height);
90
+ place({ x: left, y: top, width: right - left, height: bottom - top });
91
+ }
92
+ const startMove = (event) => start(event, '');
93
+ header.addEventListener('pointerdown', startMove);
94
+ for (const edge of ['n', 'e', 's', 'w', 'ne', 'se', 'sw', 'nw']) {
95
+ const handle = doc.createElement('div');
96
+ handle.setAttribute('data-block-editor-resize', edge);
97
+ handle.setAttribute('aria-hidden', 'true');
98
+ handle.addEventListener('pointerdown', event => start(event, edge));
99
+ dialog.append(handle);
100
+ handles.push(handle);
101
+ }
102
+ function updateButton() {
103
+ const label = restoreBounds ? restoreLabel : maximizeLabel;
104
+ maximize.setAttribute('aria-label', label);
105
+ maximize.setAttribute('aria-pressed', String(Boolean(restoreBounds)));
106
+ maximize.title = label;
107
+ maximize.textContent = restoreBounds ? '❐' : '⛶';
108
+ dialog.toggleAttribute('data-block-editor-maximized', Boolean(restoreBounds));
109
+ }
110
+ function toggle() {
111
+ stop();
112
+ if (restoreBounds) {
113
+ place(fit(restoreBounds));
114
+ restoreBounds = null;
115
+ }
116
+ else {
117
+ restoreBounds = bounds();
118
+ place({ x: 0, y: 0, ...viewport() });
119
+ }
120
+ updateButton();
121
+ }
122
+ function resize() {
123
+ stop();
124
+ if (restoreBounds)
125
+ place({ x: 0, y: 0, ...viewport() });
126
+ else
127
+ place(fit(bounds()));
128
+ }
129
+ maximize.addEventListener('click', toggle);
130
+ win.addEventListener('pointermove', move);
131
+ win.addEventListener('pointerup', stop);
132
+ win.addEventListener('pointercancel', stop);
133
+ win.addEventListener('blur', stop);
134
+ win.addEventListener('resize', resize);
135
+ dialog.addEventListener('lostpointercapture', stop);
136
+ updateButton();
137
+ return () => {
138
+ stop();
139
+ header.removeEventListener('pointerdown', startMove);
140
+ win.removeEventListener('pointermove', move);
141
+ win.removeEventListener('pointerup', stop);
142
+ win.removeEventListener('pointercancel', stop);
143
+ win.removeEventListener('blur', stop);
144
+ win.removeEventListener('resize', resize);
145
+ dialog.removeEventListener('lostpointercapture', stop);
146
+ maximize.remove();
147
+ handles.forEach(handle => handle.remove());
148
+ dialog.style.cssText = initialStyle;
149
+ for (const name of ['window', 'maximized', 'dragging'])
150
+ dialog.removeAttribute(`data-block-editor-${name}`);
151
+ if (frame !== dialog)
152
+ frame.removeAttribute('data-block-editor-window-frame');
153
+ header.removeAttribute('data-block-editor-titlebar');
154
+ };
155
+ }
@@ -0,0 +1,3 @@
1
+ import type { BlockPreviewDefinition, EditorDefinition } from './types.js';
2
+ /** Compose presentation only; the original definition retains sole authority over persistence. */
3
+ export declare function withBlockPreview(original: EditorDefinition | undefined, preview: BlockPreviewDefinition, opcode: string): EditorDefinition;
@@ -0,0 +1,100 @@
1
+ const cache = new WeakMap();
2
+ /** Compose presentation only; the original definition retains sole authority over persistence. */
3
+ export function withBlockPreview(original, preview, opcode) {
4
+ let entries = cache.get(preview);
5
+ if (!entries) {
6
+ entries = new Map();
7
+ cache.set(preview, entries);
8
+ }
9
+ const previous = entries.get(opcode);
10
+ if (previous && previous.original === original)
11
+ return previous.result;
12
+ const fallback = {
13
+ apiVersion: 1, extensionId: preview.extensionId, editorId: 'block-preview', opcodes: [opcode], editableArguments: [],
14
+ read: () => ({}), validate: () => [], createPatch: () => []
15
+ };
16
+ const result = Object.freeze({ ...original !== null && original !== void 0 ? original : fallback, mount(container, context) {
17
+ var _a;
18
+ const snapshot = (_a = context.getSnapshot) === null || _a === void 0 ? void 0 : _a.call(context);
19
+ if (!snapshot)
20
+ throw new TypeError('Block preview requires a captured snapshot.');
21
+ const document = container.ownerDocument;
22
+ const layout = document.createElement('div');
23
+ layout.dataset.blockPreviewLayout = '';
24
+ layout.dataset.blockPreviewHasForm = String(typeof (original === null || original === void 0 ? void 0 : original.mount) === 'function');
25
+ const style = document.createElement('style');
26
+ style.textContent = `
27
+ [data-block-preview-layout] {
28
+ display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px;
29
+ min-width: 0; width: 100%; align-items: start;
30
+ }
31
+ [data-block-preview-layout] > [data-block-preview-visual],
32
+ [data-block-preview-layout] > [data-block-preview-form] {
33
+ min-width: 0; max-width: 100%; overflow-wrap: anywhere;
34
+ }
35
+ @media (min-width: 900px) {
36
+ [data-block-preview-layout][data-block-preview-has-form="true"] {
37
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
38
+ }
39
+ [data-block-preview-layout][data-block-preview-has-form="true"] > [data-block-preview-visual] {
40
+ position: sticky; top: 0; align-self: start;
41
+ }
42
+ }
43
+ `;
44
+ const visual = document.createElement('div');
45
+ visual.dataset.blockPreviewVisual = '';
46
+ const form = document.createElement('div');
47
+ form.dataset.blockPreviewForm = '';
48
+ form.hidden = typeof (original === null || original === void 0 ? void 0 : original.mount) !== 'function';
49
+ layout.append(visual, form);
50
+ container.append(style, layout);
51
+ const removeLayout = () => { layout.remove(); style.remove(); };
52
+ let removeForm;
53
+ let removePreview;
54
+ try {
55
+ if (original === null || original === void 0 ? void 0 : original.mount) {
56
+ const cleanup = original.mount(form, context);
57
+ if (typeof cleanup !== 'function')
58
+ throw new TypeError('mount() must return a cleanup function.');
59
+ removeForm = cleanup;
60
+ }
61
+ const cleanup = preview.mount(visual, context, snapshot);
62
+ if (typeof cleanup !== 'function')
63
+ throw new TypeError('preview.mount() must return a cleanup function.');
64
+ removePreview = cleanup;
65
+ }
66
+ catch (error) {
67
+ try {
68
+ removePreview === null || removePreview === void 0 ? void 0 : removePreview();
69
+ }
70
+ finally {
71
+ try {
72
+ removeForm === null || removeForm === void 0 ? void 0 : removeForm();
73
+ }
74
+ finally {
75
+ removeLayout();
76
+ }
77
+ }
78
+ throw error;
79
+ }
80
+ let active = true;
81
+ return () => {
82
+ if (!active)
83
+ return;
84
+ active = false;
85
+ try {
86
+ removePreview === null || removePreview === void 0 ? void 0 : removePreview();
87
+ }
88
+ finally {
89
+ try {
90
+ removeForm === null || removeForm === void 0 ? void 0 : removeForm();
91
+ }
92
+ finally {
93
+ removeLayout();
94
+ }
95
+ }
96
+ };
97
+ } });
98
+ entries.set(opcode, { original, result });
99
+ return result;
100
+ }
package/dist/react.css CHANGED
@@ -349,3 +349,73 @@
349
349
  color: var(--block-editor-error-text, #a61b1b);
350
350
  margin-bottom: 0;
351
351
  }
352
+
353
+ /* Window controls also work with a host modal rendered through a portal. */
354
+ :global([data-block-editor-window]) {
355
+ position: fixed;
356
+ top: min(60px, 5vh);
357
+ left: 50%;
358
+ transform: translateX(-50%);
359
+ display: flex;
360
+ flex-direction: column;
361
+ box-sizing: border-box;
362
+ margin: 0;
363
+ max-width: 100vw;
364
+ max-height: calc(100dvh - min(60px, 5vh) - 16px);
365
+ }
366
+ :global([data-block-editor-window-frame]) {
367
+ display: flex;
368
+ flex: 1 1 auto;
369
+ flex-direction: column;
370
+ min-width: 0;
371
+ min-height: 0;
372
+ }
373
+ :global([data-block-editor-titlebar]) {
374
+ flex: 0 0 auto;
375
+ cursor: move;
376
+ touch-action: none;
377
+ user-select: none;
378
+ }
379
+ :global([data-block-editor-titlebar]) > div:first-child { cursor: inherit; }
380
+ :global([data-block-editor-window]) :global([data-block-editor-body]) {
381
+ flex: 1 1 auto;
382
+ min-height: 0;
383
+ max-height: none;
384
+ box-sizing: border-box;
385
+ }
386
+ :global([data-block-editor-window]) .editor-body { flex: 1 1 auto; }
387
+ :global([data-block-editor-maximize]) {
388
+ display: inline-grid;
389
+ flex: 0 0 auto;
390
+ place-items: center;
391
+ width: 34px;
392
+ height: 34px;
393
+ margin-inline-end: 8px;
394
+ padding: 0;
395
+ border: 1px solid currentColor;
396
+ border-radius: 5px;
397
+ background: transparent;
398
+ color: inherit;
399
+ font: 24px/1 system-ui, sans-serif;
400
+ cursor: pointer;
401
+ }
402
+ :global([data-block-editor-maximize]):focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
403
+ :global([data-block-editor-maximized]) { border-radius: 0; max-height: 100dvh; }
404
+ :global([data-block-editor-maximized]) :global([data-block-editor-titlebar]) { cursor: default; }
405
+ :global([data-block-editor-dragging]), :global([data-block-editor-dragging]) * { user-select: none; }
406
+ :global([data-block-editor-resize]) { position: absolute; z-index: 10; touch-action: none; }
407
+ :global([data-block-editor-resize="n"]), :global([data-block-editor-resize="s"]) { left: 9px; right: 9px; height: 7px; cursor: ns-resize; }
408
+ :global([data-block-editor-resize="n"]) { top: 0; }
409
+ :global([data-block-editor-resize="s"]) { bottom: 0; }
410
+ :global([data-block-editor-resize="e"]), :global([data-block-editor-resize="w"]) { top: 9px; bottom: 9px; width: 7px; cursor: ew-resize; }
411
+ :global([data-block-editor-resize="e"]) { right: 0; }
412
+ :global([data-block-editor-resize="w"]) { left: 0; }
413
+ :global([data-block-editor-resize="ne"]), :global([data-block-editor-resize="se"]),
414
+ :global([data-block-editor-resize="sw"]), :global([data-block-editor-resize="nw"]) { width: 10px; height: 10px; }
415
+ :global([data-block-editor-resize="ne"]), :global([data-block-editor-resize="nw"]) { top: 0; }
416
+ :global([data-block-editor-resize="se"]), :global([data-block-editor-resize="sw"]) { bottom: 0; }
417
+ :global([data-block-editor-resize="ne"]), :global([data-block-editor-resize="se"]) { right: 0; }
418
+ :global([data-block-editor-resize="nw"]), :global([data-block-editor-resize="sw"]) { left: 0; }
419
+ :global([data-block-editor-resize="ne"]), :global([data-block-editor-resize="sw"]) { cursor: nesw-resize; }
420
+ :global([data-block-editor-resize="nw"]), :global([data-block-editor-resize="se"]) { cursor: nwse-resize; }
421
+ :global([data-block-editor-maximized]) > :global([data-block-editor-resize]) { display: none; }
package/dist/react.d.ts CHANGED
@@ -35,6 +35,8 @@ export interface EditorDialogMessages {
35
35
  readonly structuralNode: string;
36
36
  /** Fixed forms retain technical diagnostics in the API while showing a localized summary. */
37
37
  readonly invalidInput?: string;
38
+ readonly maximize?: string;
39
+ readonly restore?: string;
38
40
  }
39
41
  export interface EditorNodeAppearance {
40
42
  readonly name?: string;
@@ -75,6 +77,7 @@ export declare class BlockEditorDialog extends React.Component<BlockEditorDialog
75
77
  private removeListener;
76
78
  private unmountBody;
77
79
  private stopFieldErrors;
80
+ private stopWindow;
78
81
  constructor(props: BlockEditorDialogProps);
79
82
  componentDidMount(): void;
80
83
  componentWillUnmount(): void;
package/dist/react.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import bindBlockEditorFieldErrors from './field-errors.js';
2
+ import { attachEditorDialogWindow } from './dialog-window.js';
2
3
  export { default as bindBlockEditorFieldErrors } from './field-errors.js';
3
4
  import * as React from 'react';
4
5
  import { BlockEditorConflictError } from './types.js';
@@ -164,18 +165,19 @@ export class BlockEditorDialog extends React.Component {
164
165
  this.mountBody();
165
166
  }
166
167
  componentWillUnmount() {
167
- var _a, _b, _c, _d;
168
+ var _a, _b, _c, _d, _e;
168
169
  this.unmounting = true;
169
170
  this.mounted = false;
170
171
  if (this.focusFrame !== undefined)
171
172
  cancelAnimationFrame(this.focusFrame);
172
173
  this.removeListener();
173
174
  (_a = this.stopFieldErrors) === null || _a === void 0 ? void 0 : _a.call(this);
175
+ (_b = this.stopWindow) === null || _b === void 0 ? void 0 : _b.call(this);
174
176
  try {
175
- (_b = this.unmountBody) === null || _b === void 0 ? void 0 : _b.call(this);
177
+ (_c = this.unmountBody) === null || _c === void 0 ? void 0 : _c.call(this);
176
178
  }
177
179
  catch (error) {
178
- (_d = (_c = this.props).onError) === null || _d === void 0 ? void 0 : _d.call(_c, error);
180
+ (_e = (_d = this.props).onError) === null || _e === void 0 ? void 0 : _e.call(_d, error);
179
181
  }
180
182
  finally {
181
183
  this.props.session.dispose();
@@ -185,6 +187,12 @@ export class BlockEditorDialog extends React.Component {
185
187
  if (!this.body || this.unmountBody || this.state.mountFailed)
186
188
  return;
187
189
  try {
190
+ if (!this.stopWindow)
191
+ this.stopWindow = attachEditorDialogWindow(this.body, {
192
+ headerClassName: this.props.styles.header,
193
+ maximize: this.props.messages.maximize,
194
+ restore: this.props.messages.restore
195
+ });
188
196
  if (!this.stopFieldErrors)
189
197
  this.stopFieldErrors = bindBlockEditorFieldErrors(this.body);
190
198
  if (typeof this.props.session.getNodes !== 'function')
@@ -224,7 +232,7 @@ export class BlockEditorDialog extends React.Component {
224
232
  /ブロック|block/i.test(messages.title) ? messages.title.replace(/ブロック|block/i, () => name) :
225
233
  `${name}: ${messages.title}` : messages.title;
226
234
  return React.createElement(ModalComponent, { className: `${styles.modalContent || ''} ${hierarchy ? styles.hierarchyModal : ''} ${this.props.className || ''}`, contentLabel: title, ...((appearance === null || appearance === void 0 ? void 0 : appearance.iconURI) ? { headerImage: appearance.iconURI } : {}), headerClassName: styles.header, id: "blockEditorModal", onRequestClose: this.handleClose },
227
- React.createElement("div", { "aria-busy": saving, className: styles.body, onCompositionStart: () => { this.composing = true; }, onCompositionEnd: () => { this.composing = false; }, onKeyDownCapture: event => {
235
+ React.createElement("div", { "aria-busy": saving, className: styles.body, "data-block-editor-body": "", onCompositionStart: () => { this.composing = true; }, onCompositionEnd: () => { this.composing = false; }, onKeyDownCapture: event => {
228
236
  if (this.composing || event.nativeEvent.isComposing || event.keyCode === 229)
229
237
  event.stopPropagation();
230
238
  } },
@@ -1,3 +1,4 @@
1
+ import type { BlockPreviewDefinition } from './types.js';
1
2
  import { EditorSession } from './session.js';
2
3
  import { DescendantSession } from './descendants.js';
3
4
  import type { DescendantLimits } from './descendant-types.js';
@@ -6,6 +7,8 @@ import type { PreparedEditorDefinition } from './prepared-types.js';
6
7
  import type { FixedInputDeclaration, FixedInputEditorDefinition } from './fixed-input-types.js';
7
8
  export declare class EditorRegistry {
8
9
  #private;
10
+ registerPreview(definition: BlockPreviewDefinition): () => void;
11
+ resolvePreview(opcode: string): BlockPreviewDefinition | undefined;
9
12
  register(definition: EditorDefinition): () => void;
10
13
  registerPrepared(definition: PreparedEditorDefinition): () => void;
11
14
  registerFixed<C = null>(definition: FixedInputEditorDefinition<C>): () => void;
package/dist/registry.js CHANGED
@@ -3,8 +3,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
3
3
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
4
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
5
  };
6
- var _EditorRegistry_instances, _EditorRegistry_editors, _EditorRegistry_opcodes, _EditorRegistry_fixedDeclarations, _EditorRegistry_changes, _EditorRegistry_register, _EditorRegistry_notifyChanges;
6
+ var _EditorRegistry_instances, _EditorRegistry_previews, _EditorRegistry_editors, _EditorRegistry_opcodes, _EditorRegistry_fixedDeclarations, _EditorRegistry_changes, _EditorRegistry_definition, _EditorRegistry_register, _EditorRegistry_notifyChanges;
7
7
  import { captureReference, captureSnapshot } from './data.js';
8
+ import { withBlockPreview } from './preview.js';
8
9
  import { EditorSession } from './session.js';
9
10
  import { DescendantSession } from './descendants.js';
10
11
  import { supportsDescendantEditing } from './descendant-types.js';
@@ -17,11 +18,35 @@ import { supportsFixedInputEditing } from './fixed-input-types.js';
17
18
  export class EditorRegistry {
18
19
  constructor() {
19
20
  _EditorRegistry_instances.add(this);
21
+ _EditorRegistry_previews.set(this, new Map());
20
22
  _EditorRegistry_editors.set(this, new Map());
21
23
  _EditorRegistry_opcodes.set(this, new Map());
22
24
  _EditorRegistry_fixedDeclarations.set(this, new Map());
23
25
  _EditorRegistry_changes.set(this, new Set());
24
26
  }
27
+ registerPreview(definition) {
28
+ if (!definition || !/^[a-z0-9]+$/i.test(definition.extensionId) ||
29
+ !Array.isArray(definition.opcodes) || !definition.opcodes.length ||
30
+ new Set(definition.opcodes).size !== definition.opcodes.length ||
31
+ definition.opcodes.some(op => typeof op !== 'string' || !op.startsWith(`${definition.extensionId}_`) ||
32
+ op === `${definition.extensionId}_` || __classPrivateFieldGet(this, _EditorRegistry_previews, "f").has(op)) ||
33
+ typeof definition.mount !== 'function' ||
34
+ (definition.hover !== undefined && typeof definition.hover !== 'function')) {
35
+ throw new TypeError('Invalid or duplicate block preview.');
36
+ }
37
+ const entry = Object.freeze({ ...definition, opcodes: Object.freeze([...definition.opcodes]) });
38
+ for (const op of entry.opcodes)
39
+ __classPrivateFieldGet(this, _EditorRegistry_previews, "f").set(op, entry);
40
+ __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_notifyChanges).call(this);
41
+ return () => {
42
+ if (__classPrivateFieldGet(this, _EditorRegistry_previews, "f").get(entry.opcodes[0]) !== entry)
43
+ return;
44
+ for (const op of entry.opcodes)
45
+ __classPrivateFieldGet(this, _EditorRegistry_previews, "f").delete(op);
46
+ __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_notifyChanges).call(this);
47
+ };
48
+ }
49
+ resolvePreview(opcode) { return __classPrivateFieldGet(this, _EditorRegistry_previews, "f").get(opcode); }
25
50
  register(definition) {
26
51
  if (isFixedInputEditor(definition))
27
52
  throw new TypeError('Fixed input editors require registerFixed().');
@@ -39,7 +64,7 @@ export class EditorRegistry {
39
64
  return __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_register).call(this, captureFixedInputEditorDefinition(definition));
40
65
  }
41
66
  resolve(opcode) {
42
- const entry = __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(opcode);
67
+ const entry = __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_definition).call(this, opcode);
43
68
  if (!entry)
44
69
  return undefined;
45
70
  return Object.freeze({
@@ -57,7 +82,7 @@ export class EditorRegistry {
57
82
  const live = host.readBlock(capturedReference);
58
83
  if (!live)
59
84
  return undefined;
60
- const entry = __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(live.opcode);
85
+ const entry = __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_definition).call(this, live.opcode);
61
86
  if (!entry) {
62
87
  // Preserve legacy snapshot validation even when no editor is registered.
63
88
  captureSnapshot(live, capturedReference);
@@ -70,14 +95,14 @@ export class EditorRegistry {
70
95
  if (!fixed || fixed.opcode !== live.opcode)
71
96
  return undefined;
72
97
  const snapshot = captureFixedSnapshot(fixed, capturedReference, entry);
73
- return new EditorSession(entry, snapshot, host, () => __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(snapshot.opcode) === entry, listener => {
98
+ return new EditorSession(entry, snapshot, host, () => __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_definition).call(this, snapshot.opcode) === entry, listener => {
74
99
  __classPrivateFieldGet(this, _EditorRegistry_changes, "f").add(listener);
75
100
  return () => { __classPrivateFieldGet(this, _EditorRegistry_changes, "f").delete(listener); };
76
101
  });
77
102
  }
78
103
  const snapshot = captureSnapshot(live, capturedReference);
79
- return new EditorSession(entry, snapshot, host, isPreparedEditor(entry)
80
- ? () => __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(snapshot.opcode) === entry : undefined);
104
+ return new EditorSession(entry, snapshot, host, isPreparedEditor(entry) || __classPrivateFieldGet(this, _EditorRegistry_previews, "f").has(snapshot.opcode)
105
+ ? () => __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_definition).call(this, snapshot.opcode) === entry : undefined);
81
106
  }
82
107
  openDescendants(reference, host, limits) {
83
108
  if (!supportsDescendantEditing(host))
@@ -89,11 +114,11 @@ export class EditorRegistry {
89
114
  if (!live)
90
115
  return undefined;
91
116
  const captured = captureSubtree(live, capturedReference, resolvedLimits);
92
- const root = __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(captured.snapshot.nodes[0].block.opcode);
117
+ const root = __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_definition).call(this, captured.snapshot.nodes[0].block.opcode);
93
118
  if (!root || isFixedInputEditor(root))
94
119
  return undefined;
95
120
  return new DescendantSession(captured, port, opcode => {
96
- const entry = __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(opcode);
121
+ const entry = __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_definition).call(this, opcode);
97
122
  return entry && !isFixedInputEditor(entry) ? entry : undefined;
98
123
  }, listener => {
99
124
  __classPrivateFieldGet(this, _EditorRegistry_changes, "f").add(listener);
@@ -101,7 +126,11 @@ export class EditorRegistry {
101
126
  }, resolvedLimits);
102
127
  }
103
128
  }
104
- _EditorRegistry_editors = new WeakMap(), _EditorRegistry_opcodes = new WeakMap(), _EditorRegistry_fixedDeclarations = new WeakMap(), _EditorRegistry_changes = new WeakMap(), _EditorRegistry_instances = new WeakSet(), _EditorRegistry_register = function _EditorRegistry_register(definition) {
129
+ _EditorRegistry_previews = new WeakMap(), _EditorRegistry_editors = new WeakMap(), _EditorRegistry_opcodes = new WeakMap(), _EditorRegistry_fixedDeclarations = new WeakMap(), _EditorRegistry_changes = new WeakMap(), _EditorRegistry_instances = new WeakSet(), _EditorRegistry_definition = function _EditorRegistry_definition(opcode) {
130
+ const original = __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(opcode);
131
+ const preview = __classPrivateFieldGet(this, _EditorRegistry_previews, "f").get(opcode);
132
+ return preview ? withBlockPreview(original, preview, opcode) : original;
133
+ }, _EditorRegistry_register = function _EditorRegistry_register(definition) {
105
134
  if (definition.apiVersion !== EDITOR_API_VERSION)
106
135
  throw new TypeError('Unsupported editor API version.');
107
136
  if (!/^[a-z0-9]+$/i.test(definition.extensionId) ||
package/dist/rjsf.d.ts CHANGED
@@ -16,7 +16,7 @@ import type { SchemaBundle, SchemaJsonObject, SchemaValidationContext, SyncOwner
16
16
  import type { SourceFormat } from './codec.js';
17
17
  import { EditorRegistry } from './registry.js';
18
18
  import { EDITOR_API_VERSION } from './types.js';
19
- import type { BlockSnapshot, EditorDefinition, JsonValue, ValidationIssue } from './types.js';
19
+ import type { BlockPreviewDefinition, BlockSnapshot, EditorDefinition, JsonValue, ValidationIssue } from './types.js';
20
20
  export interface SchemaEditorOptions {
21
21
  readonly apiVersion: typeof EDITOR_API_VERSION;
22
22
  readonly extensionId: string;
@@ -41,6 +41,7 @@ export declare function createSchemaBundleEditor<C>(options: SchemaBundleEditorO
41
41
  export interface BlockEditorRegistrationAPI {
42
42
  readonly apiVersion: typeof EDITOR_API_VERSION;
43
43
  register(definition: EditorDefinition | SchemaEditorOptions): () => void;
44
+ registerPreview?(definition: BlockPreviewDefinition): () => void;
44
45
  registerSchema(definition: SchemaEditorOptions): () => void;
45
46
  registerSchemaBundle?<C>(definition: SchemaBundleEditorOptions<C>): () => void;
46
47
  registerSourceEditor?<C = null>(definition: SourceEditorOptions<C>): () => void;