@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.
- package/README.ja.md +9 -4
- package/README.md +7 -4
- package/dist/build-info.json +3 -3
- package/dist/descendants.js +1 -0
- package/dist/preview.d.ts +3 -0
- package/dist/preview.js +100 -0
- package/dist/registry.d.ts +3 -0
- package/dist/registry.js +38 -9
- package/dist/rjsf.d.ts +2 -1
- package/dist/rjsf.js +10 -0
- package/dist/session.d.ts +1 -0
- package/dist/session.js +6 -0
- package/dist/turbowarp/bridge.js +42 -1
- package/dist/turbowarp/fixed-fields.d.ts +1 -1
- package/dist/turbowarp/fixed-fields.js +7 -2
- package/dist/turbowarp/icon.d.ts +3 -1
- package/dist/turbowarp/icon.js +5 -1
- package/dist/turbowarp.d.ts +2 -0
- package/dist/types.d.ts +8 -0
- package/dist/view.js +1 -0
- package/dist/webpack4/index.js +1 -1
- package/dist/webpack4/metafile.json +40 -27
- package/dist/webpack4/rjsf.js +13 -1
- package/dist/webpack4/rjsf.js.map +2 -2
- package/dist/webpack4/{shared-QIOZ73LT.js → shared-FW4MRLXW.js} +148 -8
- package/dist/webpack4/shared-FW4MRLXW.js.map +7 -0
- package/dist/webpack4/turbowarp.js +55 -5
- package/dist/webpack4/turbowarp.js.map +2 -2
- package/docs/README.md +3 -3
- package/docs/block-editor-descendants.md +1 -1
- package/docs/block-editor-e2e.md +6 -122
- package/docs/block-editor.md +1 -1
- package/docs/block-preview.md +45 -0
- package/docs/ci-success-gate.md +9 -29
- package/docs/fixed-input-contracts.md +3 -1
- package/docs/github-pages.md +21 -57
- package/docs/package-distribution.md +3 -3
- package/docs/shared-extension-security.ja.md +6 -6
- package/package.json +7 -6
- 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
|
-
[
|
|
14
|
-
|
|
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
|
|
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)
|
|
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
|
|
14
|
-
|
|
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
|
|
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
|
|
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
|
|
package/dist/build-info.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubohiroya/turbowarp-extension-data-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"repository": "https://github.com/kubohiroya/turbowarp-extension-data-editor",
|
|
5
|
-
"revision": "
|
|
5
|
+
"revision": "91533dbbffa9eb9d3b50be8084b85dae5184f9dc",
|
|
6
6
|
"sourceState": "commit",
|
|
7
|
-
"sourceSha256": "
|
|
7
|
+
"sourceSha256": "6d60745c40f7b4258f2c80c1e9ec316988e2d7cf8d68042130e156a76255d95d",
|
|
8
8
|
"capabilities": {
|
|
9
9
|
"preparedSource": 1
|
|
10
10
|
}
|
package/dist/descendants.js
CHANGED
|
@@ -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;
|
package/dist/preview.js
ADDED
|
@@ -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/registry.d.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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(),
|
|
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
|
}
|
package/dist/turbowarp/bridge.js
CHANGED
|
@@ -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);
|
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/turbowarp/icon.d.ts
CHANGED
|
@@ -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;
|
package/dist/turbowarp/icon.js
CHANGED
|
@@ -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]
|
package/dist/turbowarp.d.ts
CHANGED
|
@@ -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();
|
package/dist/webpack4/index.js
CHANGED