@kubohiroya/turbowarp-extension-data-editor 0.4.0 → 0.6.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 (40) 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/preview.d.ts +3 -0
  6. package/dist/preview.js +100 -0
  7. package/dist/registry.d.ts +3 -0
  8. package/dist/registry.js +38 -9
  9. package/dist/rjsf.d.ts +2 -1
  10. package/dist/rjsf.js +10 -0
  11. package/dist/session.d.ts +1 -0
  12. package/dist/session.js +6 -0
  13. package/dist/turbowarp/bridge.js +42 -1
  14. package/dist/turbowarp/fixed-fields.d.ts +1 -1
  15. package/dist/turbowarp/fixed-fields.js +7 -2
  16. package/dist/turbowarp/icon.d.ts +3 -1
  17. package/dist/turbowarp/icon.js +5 -1
  18. package/dist/turbowarp.d.ts +2 -0
  19. package/dist/types.d.ts +8 -0
  20. package/dist/view.js +1 -0
  21. package/dist/webpack4/index.js +1 -1
  22. package/dist/webpack4/metafile.json +40 -27
  23. package/dist/webpack4/rjsf.js +13 -1
  24. package/dist/webpack4/rjsf.js.map +2 -2
  25. package/dist/webpack4/{shared-QIOZ73LT.js → shared-FW4MRLXW.js} +148 -8
  26. package/dist/webpack4/shared-FW4MRLXW.js.map +7 -0
  27. package/dist/webpack4/turbowarp.js +55 -5
  28. package/dist/webpack4/turbowarp.js.map +2 -2
  29. package/docs/README.md +3 -3
  30. package/docs/block-editor-descendants.md +1 -1
  31. package/docs/block-editor-e2e.md +6 -122
  32. package/docs/block-editor.md +1 -1
  33. package/docs/block-preview.md +45 -0
  34. package/docs/ci-success-gate.md +9 -29
  35. package/docs/fixed-input-contracts.md +3 -1
  36. package/docs/github-pages.md +21 -57
  37. package/docs/package-distribution.md +3 -3
  38. package/docs/shared-extension-security.ja.md +6 -6
  39. package/package.json +7 -6
  40. 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.4.0",
3
+ "version": "0.6.0",
4
4
  "repository": "https://github.com/kubohiroya/turbowarp-extension-data-editor",
5
- "revision": "f728dcb8ab0848c7140f97ee15c7894b525dca4c",
5
+ "revision": "91533dbbffa9eb9d3b50be8084b85dae5184f9dc",
6
6
  "sourceState": "commit",
7
- "sourceSha256": "db10dc8cf2cfafa9e0e7275c2f19b66410a4a886c3f021c8c5de1eec301f4b9c",
7
+ "sourceSha256": "6d60745c40f7b4258f2c80c1e9ec316988e2d7cf8d68042130e156a76255d95d",
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,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
+ }
@@ -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;
package/dist/rjsf.js CHANGED
@@ -105,6 +105,16 @@ export function createBlockEditorService(options) {
105
105
  const api = Object.freeze({
106
106
  ...fixed,
107
107
  apiVersion: EDITOR_API_VERSION,
108
+ registerPreview: (definition) => {
109
+ const remove = registry.registerPreview(definition);
110
+ notify();
111
+ let active = true;
112
+ return () => { if (active) {
113
+ active = false;
114
+ remove();
115
+ notify();
116
+ } };
117
+ },
108
118
  register: (definition) => {
109
119
  const custom = definition;
110
120
  const editor = !custom.mount && Object.hasOwn(definition, 'schema')
package/dist/session.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare class EditorSession implements EditorViewContext {
12
12
  setValidationIssues(issues: readonly ValidationIssue[]): void;
13
13
  getState(): SessionState;
14
14
  subscribe(listener: () => void): () => void;
15
+ getSnapshot(): BlockSnapshot;
15
16
  mount(container: HTMLElement): () => void;
16
17
  save(): Promise<SaveResult>;
17
18
  /** Closing is refused while saving; the host must keep the modal open. */
package/dist/session.js CHANGED
@@ -158,6 +158,12 @@ export class EditorSession {
158
158
  __classPrivateFieldGet(this, _EditorSession_listeners, "f").add(listener);
159
159
  return () => { __classPrivateFieldGet(this, _EditorSession_listeners, "f").delete(listener); };
160
160
  }
161
+ getSnapshot() {
162
+ __classPrivateFieldGet(this, _EditorSession_instances, "m", _EditorSession_assertOpen).call(this);
163
+ return isFixedInputEditor(__classPrivateFieldGet(this, _EditorSession_definition, "f"))
164
+ ? captureFixedSnapshot(__classPrivateFieldGet(this, _EditorSession_snapshot, "f"), this.reference, __classPrivateFieldGet(this, _EditorSession_definition, "f"))
165
+ : captureSnapshot(__classPrivateFieldGet(this, _EditorSession_snapshot, "f"), this.reference);
166
+ }
161
167
  mount(container) {
162
168
  return __classPrivateFieldGet(this, _EditorSession_view, "f").mount(container, __classPrivateFieldGet(this, _EditorSession_definition, "f").mount, this);
163
169
  }
@@ -6,10 +6,14 @@ import createBlockEditorErrorSession from './error-session.js';
6
6
  const fixedDeclaration = (registry, snapshot) => snapshot && typeof registry.resolveFixed === 'function' &&
7
7
  registry.resolveFixed(snapshot.opcode);
8
8
  const openBlockEditorSession = ({ registry, reference, host, descendants }) => {
9
+ var _a;
9
10
  const snapshot = host.readBlock ? host.readBlock(reference) : null;
10
11
  // A fixed registration owns only this block; legacy aggregate traversal stays fail-closed.
11
12
  if (fixedDeclaration(registry, snapshot))
12
13
  return supportsFixedInputEditing(host) ? registry.open(reference, host) : undefined;
14
+ // A preview owns this icon's scene/action, not a separate live renderer for every descendant.
15
+ if (snapshot && ((_a = registry.resolvePreview) === null || _a === void 0 ? void 0 : _a.call(registry, snapshot.opcode)))
16
+ return registry.open(reference, host);
13
17
  return descendants && supportsDescendantEditing(host) ?
14
18
  registry.openDescendants(reference, host) : registry.open(reference, host);
15
19
  };
@@ -22,6 +26,19 @@ const createBlockEditorBridge = ({ ScratchBlocks, workspace, vm, service, label,
22
26
  enableFixedInputs: fixedInputs,
23
27
  onError });
24
28
  const decorations = new Map();
29
+ let stopHover = null;
30
+ const clearHover = () => {
31
+ const stop = stopHover;
32
+ stopHover = null;
33
+ if (stop) {
34
+ try {
35
+ stop();
36
+ }
37
+ catch (error) {
38
+ onError(error);
39
+ }
40
+ }
41
+ };
25
42
  let disposed = false;
26
43
  let timer = null;
27
44
  let activeSession = null;
@@ -81,6 +98,7 @@ const createBlockEditorBridge = ({ ScratchBlocks, workspace, vm, service, label,
81
98
  };
82
99
  const open = block => {
83
100
  var _a, _b;
101
+ clearHover();
84
102
  if (activeSession && ['editing', 'saving'].includes(activeSession.getState().status))
85
103
  return;
86
104
  const reference = { targetId: vm.editingTarget.id, blockId: block.id };
@@ -141,10 +159,31 @@ const createBlockEditorBridge = ({ ScratchBlocks, workspace, vm, service, label,
141
159
  element: field.fieldGroup_,
142
160
  label,
143
161
  canOpen: () => available(block),
144
- onOpen: () => open(block)
162
+ onOpen: () => open(block),
163
+ onHover: () => {
164
+ var _a, _b;
165
+ if (!available(block) || activeSession && ['editing', 'saving'].includes(activeSession.getState().status))
166
+ return;
167
+ clearHover();
168
+ const preview = (_b = (_a = service.registry).resolvePreview) === null || _b === void 0 ? void 0 : _b.call(_a, block.type);
169
+ const snapshot = host.readBlock({ targetId: vm.editingTarget.id, blockId: block.id });
170
+ if ((preview === null || preview === void 0 ? void 0 : preview.hover) && snapshot) {
171
+ try {
172
+ const stop = preview.hover(field.fieldGroup_, snapshot);
173
+ if (typeof stop !== 'function')
174
+ throw new TypeError('hover() must return a cleanup function.');
175
+ stopHover = stop;
176
+ }
177
+ catch (error) {
178
+ onError(error);
179
+ }
180
+ }
181
+ },
182
+ onLeave: clearHover
145
183
  });
146
184
  decorations.set(block.id, { field,
147
185
  cleanup: () => {
186
+ clearHover();
148
187
  unbind();
149
188
  if (field.imageElement_)
150
189
  field.imageElement_.tooltip = tooltip;
@@ -158,6 +197,7 @@ const createBlockEditorBridge = ({ ScratchBlocks, workspace, vm, service, label,
158
197
  }
159
198
  };
160
199
  const schedule = () => {
200
+ clearHover();
161
201
  checkSession();
162
202
  clearTimeout(timer);
163
203
  timer = setTimeout(refresh, 0);
@@ -184,6 +224,7 @@ const createBlockEditorBridge = ({ ScratchBlocks, workspace, vm, service, label,
184
224
  refresh,
185
225
  dispose: () => {
186
226
  disposed = true;
227
+ clearHover();
187
228
  clearTimeout(timer);
188
229
  unregister();
189
230
  workspace.removeChangeListener(schedule);
@@ -61,7 +61,7 @@ export function inspectFixedArgument({ ScratchBlocks, block, vmBlock, target, ar
61
61
  };
62
62
  slot: {
63
63
  kind: string;
64
- value: string;
64
+ value: string | number;
65
65
  };
66
66
  metadata: {
67
67
  role: any;
@@ -217,7 +217,12 @@ export function inspectFixedArgument({ ScratchBlocks, block, vmBlock, target, ar
217
217
  if (!safeField(field, Class))
218
218
  return unavailable('unsupported', 'The field class, methods or validator is unsupported.', raw);
219
219
  const value = inherited(Class.prototype, 'getValue').call(field);
220
- if (!Object.is(value, raw))
220
+ // SB3 numeric primitives remain numbers in the VM while FieldNumber displays a string.
221
+ // Preserve the original VM primitive in the snapshot; only equivalent native numeric text
222
+ // is a synchronized representation. Other field families still require exact raw identity.
223
+ const numericPrimitive = kind === 'number' && typeof raw === 'number' && Number.isFinite(raw) &&
224
+ typeof value === 'string' && token.test(value.trim()) && Number(value) === raw;
225
+ if (!Object.is(value, raw) && !numericPrimitive)
221
226
  return null;
222
227
  if (typeof value !== 'string')
223
228
  return unavailable('unsupported', 'The native field value is not a string.', raw);
@@ -231,7 +236,7 @@ export function inspectFixedArgument({ ScratchBlocks, block, vmBlock, target, ar
231
236
  restoreHandles: inherited(Class.prototype, 'updateSliderHandles_'),
232
237
  restoreDom: inherited(Class.prototype, 'updateDom_'),
233
238
  ...(kind === 'color' ? { restoreState: colorState(owner, field) } : {}) };
234
- return { slot: { kind: 'literal', value }, metadata: { role, source: direct ? 'field' : 'shadow',
239
+ return { slot: { kind: 'literal', value: numericPrimitive ? raw : value }, metadata: { role, source: direct ? 'field' : 'shadow',
235
240
  identity: JSON.stringify(sourceIdentity), field: { blockId: owner.id, name, type: { text: 'FieldTextInput', number: 'FieldNumber', menu: 'FieldDropdown', color: 'FieldColourSlider' }[kind] } },
236
241
  ...(role === 'editable' ? { binding } : {}), signatureParts: [...sourceIdentity, observedFields] };
237
242
  }
@@ -1,7 +1,9 @@
1
1
  export default bindBlockEditorIcon;
2
- declare function bindBlockEditorIcon({ element, label, canOpen, onOpen }: {
2
+ declare function bindBlockEditorIcon({ element, label, canOpen, onOpen, onHover, onLeave }: {
3
3
  element: any;
4
4
  label: any;
5
5
  canOpen: any;
6
6
  onOpen: any;
7
+ onHover?: (() => void) | undefined;
8
+ onLeave?: (() => void) | undefined;
7
9
  }): () => void;
@@ -1,5 +1,5 @@
1
1
  // Decorate one known FieldImage; never search or intercept unrelated workspace DOM.
2
- const bindBlockEditorIcon = ({ element, label, canOpen, onOpen }) => {
2
+ const bindBlockEditorIcon = ({ element, label, canOpen, onOpen, onHover = () => { }, onLeave = () => { } }) => {
3
3
  const attributes = ['role', 'tabindex', 'aria-label', 'data-block-editor-icon'];
4
4
  const previous = attributes.map(name => element.getAttribute(name));
5
5
  const oldCursor = element.style.cursor;
@@ -48,7 +48,11 @@ const bindBlockEditorIcon = ({ element, label, canOpen, onOpen }) => {
48
48
  onOpen();
49
49
  }
50
50
  };
51
+ const enter = () => { if (canOpen())
52
+ onHover === null || onHover === void 0 ? void 0 : onHover(); };
53
+ const leave = () => onLeave === null || onLeave === void 0 ? void 0 : onLeave();
51
54
  const listeners = [
55
+ ['pointerenter', enter], ['pointerleave', leave], ['focus', enter], ['blur', leave],
52
56
  ['pointerdown', down], ['pointermove', move], ['pointerup', up], ['pointercancel', cancel],
53
57
  ['mousedown', stop], ['mouseup', stop], ['click', stop], ['dblclick', stop],
54
58
  ['touchstart', stop], ['touchend', stop], ['touchmove', stop], ['keydown', key]
@@ -59,6 +59,8 @@ export declare function bindBlockEditorIcon(options: {
59
59
  readonly label: string;
60
60
  readonly canOpen: () => boolean;
61
61
  readonly onOpen: () => void;
62
+ readonly onHover?: () => void;
63
+ readonly onLeave?: () => void;
62
64
  }): () => void;
63
65
  /** Supply editor arguments to include other literal inputs as parenthesized identifiers. */
64
66
  export declare function readBlockEditorAppearance(block: object | null, ScratchBlocks: object, editableArguments?: readonly string[]): TurboWarpNodeAppearance;
package/dist/types.d.ts CHANGED
@@ -54,6 +54,7 @@ export interface SessionState {
54
54
  readonly error: unknown;
55
55
  }
56
56
  export interface EditorViewContext {
57
+ getSnapshot?(): BlockSnapshot;
57
58
  getDraft(): JsonObject;
58
59
  /** Fixed views obtain only safe presentation data, never live host objects. */
59
60
  getFixedInputView?(): FixedInputViewDescription | undefined;
@@ -72,6 +73,13 @@ export interface EditorDescriptor {
72
73
  readonly opcodes: readonly string[];
73
74
  readonly editableArguments: readonly string[];
74
75
  }
76
+ /** Optional presentation companion. It shares the draft and lifetime of the existing form. */
77
+ export interface BlockPreviewDefinition {
78
+ readonly extensionId: string;
79
+ readonly opcodes: readonly string[];
80
+ mount(container: HTMLElement, context: EditorViewContext, snapshot: BlockSnapshot): () => void;
81
+ hover?(anchor: HTMLElement | SVGElement, snapshot: BlockSnapshot): () => void;
82
+ }
75
83
  export interface EditorDefinition extends EditorDescriptor {
76
84
  read(snapshot: BlockSnapshot): JsonObject;
77
85
  validate(draft: JsonObject, snapshot: BlockSnapshot): readonly ValidationIssue[] | Promise<readonly ValidationIssue[]>;
package/dist/view.js CHANGED
@@ -35,6 +35,7 @@ export class EditorView {
35
35
  subscriptions.clear();
36
36
  };
37
37
  const context = Object.freeze({
38
+ ...(owner.getSnapshot ? { getSnapshot: () => { assertMounted(); return owner.getSnapshot(); } } : {}),
38
39
  getDraft: () => owner.getDraft(),
39
40
  ...(owner.getFixedInputView ? { getFixedInputView: () => {
40
41
  assertMounted();
@@ -10,7 +10,7 @@ import {
10
10
  sameFixedSnapshot,
11
11
  supportsFixedInputEditing,
12
12
  supportsFixedInputs
13
- } from "./shared-QIOZ73LT.js";
13
+ } from "./shared-FW4MRLXW.js";
14
14
  import {
15
15
  BlockEditorConflictError,
16
16
  BlockEditorStructureError,