@kubohiroya/turbowarp-extension-data-editor 0.2.0 → 0.2.2

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 (68) hide show
  1. package/README.md +9 -6
  2. package/dist/build-info.json +3 -3
  3. package/dist/fixed-input-branches.d.ts +12 -0
  4. package/dist/fixed-input-branches.js +180 -0
  5. package/dist/fixed-input-editor.d.ts +39 -0
  6. package/dist/fixed-input-editor.js +538 -0
  7. package/dist/fixed-input-schema.d.ts +15 -0
  8. package/dist/fixed-input-schema.js +21 -0
  9. package/dist/fixed-input-snapshot.d.ts +7 -0
  10. package/dist/fixed-input-snapshot.js +180 -0
  11. package/dist/fixed-input-types.d.ts +138 -0
  12. package/dist/fixed-input-types.js +23 -0
  13. package/dist/fixed-input-view.d.ts +14 -0
  14. package/dist/fixed-input-view.js +168 -0
  15. package/dist/index.d.ts +6 -0
  16. package/dist/index.js +4 -0
  17. package/dist/react.css +168 -0
  18. package/dist/react.d.ts +8 -0
  19. package/dist/react.js +19 -2
  20. package/dist/registry.d.ts +4 -0
  21. package/dist/registry.js +45 -7
  22. package/dist/rjsf-templates.d.ts +11 -0
  23. package/dist/rjsf-templates.js +129 -0
  24. package/dist/rjsf.d.ts +24 -1
  25. package/dist/rjsf.js +35 -3
  26. package/dist/schema.d.ts +2 -0
  27. package/dist/schema.js +1 -0
  28. package/dist/session.d.ts +3 -1
  29. package/dist/session.js +205 -7
  30. package/dist/turbowarp/bridge.d.ts +2 -1
  31. package/dist/turbowarp/bridge.js +37 -5
  32. package/dist/turbowarp/fixed-fields.d.ts +82 -0
  33. package/dist/turbowarp/fixed-fields.js +311 -0
  34. package/dist/turbowarp/fixed-transaction.d.ts +18 -0
  35. package/dist/turbowarp/fixed-transaction.js +460 -0
  36. package/dist/turbowarp/host.d.ts +3 -8
  37. package/dist/turbowarp/host.js +68 -1
  38. package/dist/turbowarp.d.ts +4 -0
  39. package/dist/turbowarp.js +1 -1
  40. package/dist/types.d.ts +5 -0
  41. package/dist/view.js +4 -0
  42. package/dist/webpack4/index.js +22 -5
  43. package/dist/webpack4/metafile.json +592 -299
  44. package/dist/webpack4/react.js +20 -6
  45. package/dist/webpack4/react.js.map +2 -2
  46. package/dist/webpack4/rjsf.js +450 -60
  47. package/dist/webpack4/rjsf.js.map +4 -4
  48. package/dist/webpack4/schema.js +6 -3
  49. package/dist/webpack4/{shared-KQOUMWJ4.js → shared-75QIWLZY.js} +1129 -1244
  50. package/dist/webpack4/{shared-KQOUMWJ4.js.map → shared-75QIWLZY.js.map} +4 -4
  51. package/dist/webpack4/shared-FYUILHED.js +1 -0
  52. package/dist/webpack4/shared-GQLPPRT2.js +210 -0
  53. package/dist/webpack4/shared-GQLPPRT2.js.map +7 -0
  54. package/dist/webpack4/shared-P2JBRZFP.js +2206 -0
  55. package/dist/webpack4/shared-P2JBRZFP.js.map +7 -0
  56. package/dist/webpack4/turbowarp.js +913 -9
  57. package/dist/webpack4/turbowarp.js.map +4 -4
  58. package/docs/api.md +7 -0
  59. package/docs/fixed-input-contracts.md +77 -0
  60. package/docs/package-distribution.md +5 -0
  61. package/docs/stage-2-publication.ja.md +6 -0
  62. package/package.json +2 -1
  63. package/dist/webpack4/shared-CQB3NG2A.js +0 -1
  64. package/dist/webpack4/shared-MRPGBJIH.js +0 -75
  65. package/dist/webpack4/shared-MRPGBJIH.js.map +0 -7
  66. package/dist/webpack4/shared-N6YQE2X6.js +0 -1029
  67. package/dist/webpack4/shared-N6YQE2X6.js.map +0 -7
  68. /package/dist/webpack4/{shared-CQB3NG2A.js.map → shared-FYUILHED.js.map} +0 -0
@@ -0,0 +1,460 @@
1
+ import { BlockEditorConflictError, FIXED_INPUTS_CAPABILITY } from '../index.js';
2
+ import { captureFixedInputDeclaration, captureFixedSnapshot, sameFixedSnapshot } from '../fixed-input-snapshot.js';
3
+ import { initializeFixedFields, inspectFixedArgument, prepareFixedChange, restoreFixedField, captureFixedFieldState } from './fixed-fields.js';
4
+ import { snapshotJson } from '../schema-json.js';
5
+ import { validateFixedBranchChanges } from '../fixed-input-branches.js';
6
+ const own = (object, name) => {
7
+ const descriptor = object && Object.getOwnPropertyDescriptor(object, name);
8
+ if (!descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
9
+ throw new TypeError('Expected an own data property.');
10
+ }
11
+ return descriptor.value;
12
+ };
13
+ const inheritedData = (object, key) => {
14
+ for (let item = object; item; item = Object.getPrototypeOf(item)) {
15
+ const descriptor = Object.getOwnPropertyDescriptor(item, key);
16
+ if (descriptor) {
17
+ if (!('value' in descriptor))
18
+ throw new TypeError('Unsupported undo configuration.');
19
+ return descriptor.value;
20
+ }
21
+ }
22
+ return undefined;
23
+ };
24
+ const ensureNativeHistory = stack => {
25
+ if (!Array.isArray(stack) || !Object.isExtensible(stack) ||
26
+ Object.getOwnPropertySymbols(stack).length || !Object.getOwnPropertyDescriptor(stack, 'length').writable) {
27
+ throw new TypeError('Unsupported undo history.');
28
+ }
29
+ if (Object.getOwnPropertyNames(stack).length !== stack.length + 1)
30
+ throw new TypeError('Unsupported undo history.');
31
+ for (let index = 0; index < stack.length; index++) {
32
+ const descriptor = Object.getOwnPropertyDescriptor(stack, String(index));
33
+ if (!descriptor || !('value' in descriptor) || !descriptor.writable || !descriptor.configurable) {
34
+ throw new TypeError('Unsupported undo history.');
35
+ }
36
+ }
37
+ };
38
+ const copyHistory = stack => {
39
+ const copy = [];
40
+ for (let index = 0; index < stack.length; index++)
41
+ copy.push(own(stack, String(index)));
42
+ return copy;
43
+ };
44
+ const replaceHistory = (stack, values) => {
45
+ stack.length = 0;
46
+ for (const value of values)
47
+ Array.prototype.push.call(stack, value);
48
+ };
49
+ const conflict = () => { throw new BlockEditorConflictError(); };
50
+ const aborted = Object.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted').get;
51
+ const isAborted = signal => aborted.call(signal);
52
+ const optionalOwn = (object, name) => {
53
+ const descriptor = object && Object.getOwnPropertyDescriptor(object, name);
54
+ if (descriptor && !Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
55
+ throw new TypeError('VM state must contain data properties.');
56
+ }
57
+ return descriptor && descriptor.value;
58
+ };
59
+ const stateToken = (value, identify, seen = new Set()) => {
60
+ if (typeof value === 'number')
61
+ return ['number', Object.is(value, -0) ? '-0' : String(value)];
62
+ if (value === undefined)
63
+ return ['undefined'];
64
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
65
+ return value;
66
+ if (typeof value !== 'object' || seen.has(value) || seen.size > 64)
67
+ throw new TypeError('Unsupported VM state.');
68
+ seen.add(value);
69
+ const result = Object.getOwnPropertyNames(value).sort().map(key => [key, stateToken(own(value, key), identify, seen)]);
70
+ seen.delete(value);
71
+ return ['object', identify(value), result];
72
+ };
73
+ const untouchedState = (block, vmBlock, target, bindings, identify) => {
74
+ const omitted = new Map();
75
+ for (const binding of bindings.values()) {
76
+ if (!omitted.has(binding.vmBlock))
77
+ omitted.set(binding.vmBlock, new Set());
78
+ omitted.get(binding.vmBlock).add(binding.fieldName);
79
+ }
80
+ const seen = new Set();
81
+ const records = [];
82
+ const capture = item => {
83
+ if (!item || seen.has(item))
84
+ return;
85
+ seen.add(item);
86
+ const fields = item.fields || {};
87
+ records.push([identify(item), ['id', 'opcode', 'parent', 'next', 'inputs', 'mutation', 'shadow', 'topLevel']
88
+ .map(key => [key, stateToken(optionalOwn(item, key), identify)]), identify(fields), Object.keys(fields).sort()
89
+ .map(key => {
90
+ var _a;
91
+ const field = own(fields, key);
92
+ if (!((_a = omitted.get(item)) === null || _a === void 0 ? void 0 : _a.has(key)))
93
+ return [key, stateToken(field, identify)];
94
+ return [key, identify(field), Object.getOwnPropertyNames(field).sort()
95
+ .filter(property => property !== 'value').map(property => [property, stateToken(own(field, property), identify)])];
96
+ })]);
97
+ };
98
+ capture(vmBlock);
99
+ const connections = [];
100
+ for (const input of block.inputList) {
101
+ const connected = input.connection && input.connection.targetBlock();
102
+ connections.push([input.name || '', identify(input), input.connection ? identify(input.connection) : null, connected ? [connected.id, connected.type, identify(connected)] : null]);
103
+ if (connected)
104
+ capture(target.blocks.getBlock(connected.id));
105
+ }
106
+ const statementConnections = ['nextConnection', 'previousConnection', 'outputConnection'].map(name => {
107
+ const connection = block[name];
108
+ const connected = connection && connection.targetBlock();
109
+ return [name, connection ? identify(connection) : null,
110
+ connection && connection.targetConnection ? identify(connection.targetConnection) : null,
111
+ connected ? [connected.id, connected.type, identify(connected)] : null];
112
+ });
113
+ return JSON.stringify([records, connections, statementConnections]);
114
+ };
115
+ const sameDeclaration = (left, right) => JSON.stringify(captureFixedInputDeclaration(left)) ===
116
+ JSON.stringify(captureFixedInputDeclaration(right));
117
+ // Fixed inputs deliberately use a separate transaction from the legacy TEXT adapter.
118
+ export default function createFixedInputPort({ ScratchBlocks, workspace, vm, registry, identify, getState, begin, markEvent, finish, onError, subscribeScope }) {
119
+ initializeFixedFields(ScratchBlocks);
120
+ const inspect = (reference, suppliedDeclaration) => {
121
+ const state = getState();
122
+ const target = vm.editingTarget;
123
+ if (state.disposed || !target || target.id !== reference.targetId || workspace.isFlyout ||
124
+ workspace.options.readOnly || (workspace.isDragging && workspace.isDragging()))
125
+ return null;
126
+ const block = workspace.getBlockById(reference.blockId);
127
+ if (!block || block.isInFlyout || block.isShadow() || !block.isEditable())
128
+ return null;
129
+ const descriptor = registry.resolve(block.type);
130
+ const declaration = registry.resolveFixed(block.type);
131
+ if (!descriptor || !declaration || !sameDeclaration(declaration, suppliedDeclaration))
132
+ return null;
133
+ const vmBlock = target.blocks.getBlock(block.id);
134
+ const nextBlock = block.nextConnection && block.nextConnection.targetBlock();
135
+ if (!vmBlock || (nextBlock ? nextBlock.id : null) !== (vmBlock.next || null) || vmBlock.opcode !== block.type ||
136
+ (block.getParent() ? block.getParent().id : null) !== (vmBlock.parent || null))
137
+ return null;
138
+ const arguments_ = Object.create(null);
139
+ const inputs = Object.create(null);
140
+ const bindings = new Map();
141
+ const signature = [state.revision, identify(target), identify(block), identify(vmBlock), identify(declaration)];
142
+ for (const [role, names] of [['editable', declaration.editableArguments], ['readonly', declaration.readOnlyArguments]]) {
143
+ for (const argument of names) {
144
+ const item = inspectFixedArgument({ ScratchBlocks, block, vmBlock, target, argument, role,
145
+ identify, extensionId: descriptor.extensionId });
146
+ if (!item)
147
+ return null;
148
+ arguments_[argument] = item.slot;
149
+ inputs[argument] = item.metadata;
150
+ if (role === 'editable' && item.binding)
151
+ bindings.set(argument, item.binding);
152
+ signature.push(argument, item.signatureParts);
153
+ }
154
+ }
155
+ const untouched = untouchedState(block, vmBlock, target, bindings, identify);
156
+ signature.push(untouched);
157
+ const snapshot = captureFixedSnapshot({ targetId: target.id, blockId: block.id, opcode: block.type,
158
+ revision: JSON.stringify(signature), arguments: arguments_, fixedContext: {
159
+ contractId: declaration.contractId, contractRevision: declaration.contractRevision,
160
+ generation: String(state.generation), readOnlyArguments: declaration.readOnlyArguments,
161
+ dependencies: declaration.dependencies, inputs
162
+ } }, reference, declaration);
163
+ return { snapshot, bindings, declaration, target, block, vmBlock, state, untouched, runtime: vm.runtime };
164
+ };
165
+ const commit = request => {
166
+ var _a;
167
+ const expectedInput = own(request, 'expected');
168
+ const changesInput = own(request, 'changes');
169
+ const signal = own(request, 'signal');
170
+ try {
171
+ const contextInput = own(request, 'context');
172
+ const context = snapshotJson({ consumerId: own(contextInput, 'consumerId'),
173
+ nowMs: own(contextInput, 'nowMs'), settings: own(contextInput, 'settings') });
174
+ if (typeof context.consumerId !== 'string' || !context.consumerId ||
175
+ typeof context.nowMs !== 'number' || !Number.isFinite(context.nowMs)) {
176
+ throw new TypeError('Invalid fixed validation context.');
177
+ }
178
+ }
179
+ catch (_b) {
180
+ throw new TypeError('Invalid fixed validation context.');
181
+ }
182
+ if (isAborted(signal))
183
+ conflict();
184
+ const expected = captureFixedSnapshot(expectedInput);
185
+ const current = inspect(expected, registry.resolveFixed(expected.opcode));
186
+ if (!current || !sameFixedSnapshot(expected, current.snapshot))
187
+ conflict();
188
+ const declaration = current.declaration;
189
+ captureFixedSnapshot(expected, expected, declaration);
190
+ if (!Array.isArray(changesInput))
191
+ throw new TypeError('Expected a fixed argument patch array.');
192
+ const prepared = [];
193
+ const seenNames = new Set();
194
+ const seenFields = new Set();
195
+ for (let i = 0; i < changesInput.length; i++) {
196
+ const change = own(changesInput, String(i));
197
+ const name = own(change, 'argument');
198
+ const value = own(change, 'value');
199
+ if (typeof name !== 'string' || seenNames.has(name) ||
200
+ !declaration.editableArguments.includes(name) || !current.bindings.has(name)) {
201
+ throw new TypeError('Only distinct editable standard fields may be changed.');
202
+ }
203
+ seenNames.add(name);
204
+ const binding = current.bindings.get(name);
205
+ if (seenFields.has(binding.field))
206
+ throw new TypeError('Duplicate field write.');
207
+ seenFields.add(binding.field);
208
+ if (Object.is(value, binding.value))
209
+ continue;
210
+ prepared.push({ ...prepareFixedChange(binding, value), argument: name });
211
+ }
212
+ if (validateFixedBranchChanges(declaration, expected, prepared.map(item => ({ argument: item.argument, value: item.next }))).length) {
213
+ throw new TypeError('Fixed branch changes require a valid canonical branch policy.');
214
+ }
215
+ const guard = () => {
216
+ const state = getState();
217
+ if (isAborted(signal) || state.disposed || state.generation !== current.state.generation ||
218
+ state.externalRevision !== current.state.externalRevision || vm.editingTarget !== current.target ||
219
+ vm.runtime !== current.runtime ||
220
+ registry.resolveFixed(expected.opcode) !== declaration)
221
+ conflict();
222
+ };
223
+ guard();
224
+ const beforeWrite = inspect(expected, declaration);
225
+ if (!beforeWrite || !sameFixedSnapshot(expected, beforeWrite.snapshot))
226
+ conflict();
227
+ guard();
228
+ if (!prepared.length)
229
+ return;
230
+ const Events = ScratchBlocks.Events;
231
+ if (!Events.isEnabled() || !Events.recordUndo || Events.getGroup() ||
232
+ !Array.isArray(workspace.undoStack_) || !Array.isArray(workspace.redoStack_)) {
233
+ throw new Error('The workspace is busy or its undo API is unsupported.');
234
+ }
235
+ const undoStack = workspace.undoStack_;
236
+ const redoStack = workspace.redoStack_;
237
+ ensureNativeHistory(undoStack);
238
+ ensureNativeHistory(redoStack);
239
+ const configuredMaxUndo = inheritedData(workspace, 'MAX_UNDO');
240
+ const maxUndo = configuredMaxUndo === undefined ? Infinity : configuredMaxUndo;
241
+ if (maxUndo !== Infinity && (!Number.isSafeInteger(maxUndo) || maxUndo < 0)) {
242
+ throw new TypeError('Unsupported undo configuration.');
243
+ }
244
+ const undo = copyHistory(undoStack);
245
+ const redo = copyHistory(redoStack);
246
+ const oldGroup = Events.getGroup();
247
+ const runtime = current.runtime;
248
+ const dirty = runtime.emitProjectChanged;
249
+ const dirtyDescriptor = Object.getOwnPropertyDescriptor(runtime, 'emitProjectChanged');
250
+ const savedVmFields = prepared.map(item => ({ binding: item, block: item.vmBlock, name: item.fieldName,
251
+ fields: item.vmBlock.fields, field: item.vmBlock.fields[item.fieldName],
252
+ descriptors: Object.getOwnPropertyDescriptors(item.vmBlock.fields[item.fieldName]) }));
253
+ let succeeded = false;
254
+ let failure;
255
+ let activeEvent = null;
256
+ const pendingHistory = [];
257
+ const externalWrites = new Map();
258
+ const byField = new Map(prepared.map(item => [JSON.stringify([item.block.id, item.fieldName]), item]));
259
+ let externalDirty = false;
260
+ const ownedEvents = new WeakSet();
261
+ const originalDelivery = workspace.fireChangeListener;
262
+ const deliveryDescriptor = Object.getOwnPropertyDescriptor(workspace, 'fireChangeListener');
263
+ const stableScope = () => {
264
+ const state = getState();
265
+ return state.generation === current.state.generation && vm.editingTarget === current.target && vm.runtime === runtime;
266
+ };
267
+ let begun = false;
268
+ try {
269
+ runtime.emitProjectChanged = () => {
270
+ if (!activeEvent || !ownedEvents.has(activeEvent) || !stableScope())
271
+ externalDirty = true;
272
+ };
273
+ workspace.fireChangeListener = function (event) {
274
+ const previous = activeEvent;
275
+ const external = event && !event.isUiEvent && !ownedEvents.has(event);
276
+ const edited = external && event.type === 'change' && event.element === 'field' &&
277
+ byField.get(JSON.stringify([event.blockId, event.name]));
278
+ if (edited && !externalWrites.has(edited.field)) {
279
+ // An external undo must not revive this editor's uncommitted candidate.
280
+ if (Object.is(event.oldValue, edited.next))
281
+ event.oldValue = edited.value;
282
+ externalWrites.set(edited.field, null);
283
+ }
284
+ activeEvent = event;
285
+ try {
286
+ return originalDelivery.call(this, event);
287
+ }
288
+ finally {
289
+ if (edited) {
290
+ const fields = edited.vmBlock.fields;
291
+ const field = fields && fields[edited.fieldName];
292
+ externalWrites.set(edited.field, { binding: captureFixedFieldState(edited),
293
+ fields, field, descriptors: field && Object.getOwnPropertyDescriptors(field) });
294
+ }
295
+ activeEvent = previous;
296
+ }
297
+ };
298
+ Events.setGroup(true);
299
+ Events.disable();
300
+ begin();
301
+ begun = true;
302
+ guard();
303
+ for (const item of prepared) {
304
+ guard();
305
+ item.field.setValue(item.next);
306
+ if (!Object.is(item.field.getValue(), item.next))
307
+ throw new Error('The field rejected its value.');
308
+ guard();
309
+ }
310
+ for (const item of prepared) {
311
+ guard();
312
+ const event = new Events.Change(item.block, 'field', item.fieldName, item.value, item.next);
313
+ ownedEvents.add(event);
314
+ markEvent(event);
315
+ // Stage only editor-owned undo records; external events retain normal semantics.
316
+ const recordUndo = event.recordUndo;
317
+ event.recordUndo = false;
318
+ try {
319
+ workspace.fireChangeListener(event);
320
+ }
321
+ finally {
322
+ event.recordUndo = recordUndo;
323
+ }
324
+ if (recordUndo)
325
+ pendingHistory.push(event);
326
+ guard();
327
+ }
328
+ const after = inspect(expected, declaration);
329
+ if (!after || after.untouched !== current.untouched)
330
+ conflict();
331
+ const wantedArguments = { ...expected.arguments };
332
+ for (const item of prepared)
333
+ wantedArguments[item.argument] = { kind: 'literal', value: item.next };
334
+ if (!sameFixedSnapshot({ ...expected, revision: after.snapshot.revision, arguments: wantedArguments }, after.snapshot))
335
+ conflict();
336
+ guard();
337
+ if (workspace.undoStack_ !== undoStack || workspace.redoStack_ !== redoStack ||
338
+ undoStack.length !== undo.length || redoStack.length !== redo.length ||
339
+ undo.some((entry, index) => undoStack[index] !== entry) ||
340
+ redo.some((entry, index) => redoStack[index] !== entry))
341
+ conflict();
342
+ ensureNativeHistory(undoStack);
343
+ ensureNativeHistory(redoStack);
344
+ // This section contains no vendor callbacks; both native arrays are writable dense data.
345
+ try {
346
+ for (const event of pendingHistory)
347
+ Array.prototype.push.call(undoStack, event);
348
+ if (undoStack.length > maxUndo) {
349
+ Array.prototype.copyWithin.call(undoStack, 0, undoStack.length - maxUndo);
350
+ undoStack.length = maxUndo;
351
+ }
352
+ redoStack.length = 0;
353
+ }
354
+ catch (error) {
355
+ replaceHistory(undoStack, undo);
356
+ replaceHistory(redoStack, redo);
357
+ throw error;
358
+ }
359
+ succeeded = true;
360
+ }
361
+ catch (error) {
362
+ failure = error;
363
+ for (const item of begun ? prepared : []) {
364
+ try {
365
+ restoreFixedField(((_a = externalWrites.get(item.field)) === null || _a === void 0 ? void 0 : _a.binding) || item);
366
+ }
367
+ catch (restoreError) {
368
+ // Continue restoring every independent field before reporting a failure.
369
+ try {
370
+ onError(restoreError);
371
+ }
372
+ catch ( /* Observer failures do not interrupt rollback. */_c) { /* Observer failures do not interrupt rollback. */ }
373
+ }
374
+ }
375
+ for (const saved of begun ? savedVmFields : []) {
376
+ const outside = externalWrites.get(saved.binding.field);
377
+ const fields = saved.block.fields;
378
+ if (!fields)
379
+ continue;
380
+ const field = outside ? outside.field : saved.field;
381
+ const descriptors = outside ? outside.descriptors : saved.descriptors;
382
+ if (!field) {
383
+ delete fields[saved.name];
384
+ continue;
385
+ }
386
+ fields[saved.name] = field;
387
+ for (const key of Object.keys(field))
388
+ if (!Object.hasOwn(descriptors, key))
389
+ delete field[key];
390
+ Object.defineProperties(field, descriptors);
391
+ }
392
+ if (begun && typeof current.target.blocks.resetCache === 'function')
393
+ current.target.blocks.resetCache();
394
+ // Pending editor events never entered either history. External histories are authoritative.
395
+ }
396
+ finally {
397
+ try {
398
+ if (begun)
399
+ finish(succeeded);
400
+ }
401
+ finally {
402
+ try {
403
+ if (!Events.isEnabled())
404
+ Events.enable();
405
+ }
406
+ finally {
407
+ try {
408
+ Events.setGroup(oldGroup);
409
+ }
410
+ finally {
411
+ if (deliveryDescriptor)
412
+ Object.defineProperty(workspace, 'fireChangeListener', deliveryDescriptor);
413
+ else
414
+ delete workspace.fireChangeListener;
415
+ if (dirtyDescriptor)
416
+ Object.defineProperty(runtime, 'emitProjectChanged', dirtyDescriptor);
417
+ else
418
+ delete runtime.emitProjectChanged;
419
+ }
420
+ }
421
+ }
422
+ }
423
+ if (succeeded || externalDirty) {
424
+ try {
425
+ dirty.call(runtime);
426
+ }
427
+ catch (error) {
428
+ try {
429
+ onError(error);
430
+ }
431
+ catch ( /* Dirty observers do not change the transaction result. */_d) { /* Dirty observers do not change the transaction result. */ }
432
+ }
433
+ }
434
+ if (!succeeded)
435
+ throw failure;
436
+ };
437
+ return Object.freeze({ capability: FIXED_INPUTS_CAPABILITY,
438
+ readBlock: (reference, declaration) => { var _a; return ((_a = inspect(reference, declaration)) === null || _a === void 0 ? void 0 : _a.snapshot) || null; }, commit,
439
+ subscribeInvalidation: (reference, declaration, listener) => {
440
+ const captured = inspect(reference, declaration);
441
+ if (!captured || typeof subscribeScope !== 'function') {
442
+ listener(new BlockEditorConflictError());
443
+ return () => { };
444
+ }
445
+ let active = true;
446
+ const check = () => {
447
+ const state = getState();
448
+ if (!active || (!state.disposed && state.generation === captured.state.generation &&
449
+ vm.editingTarget === captured.target && vm.runtime === captured.runtime &&
450
+ workspace.getBlockById(reference.blockId) === captured.block &&
451
+ captured.target.blocks.getBlock(reference.blockId) === captured.vmBlock &&
452
+ registry.resolveFixed(captured.snapshot.opcode) === captured.declaration))
453
+ return;
454
+ active = false;
455
+ listener(new BlockEditorConflictError());
456
+ };
457
+ const unsubscribe = subscribeScope(check);
458
+ return () => { active = false; unsubscribe(); };
459
+ } });
460
+ }
@@ -1,20 +1,15 @@
1
1
  export default createBlockEditorHost;
2
- declare function createBlockEditorHost({ ScratchBlocks, workspace, vm, registry, enableDescendants, onError }: {
2
+ declare function createBlockEditorHost({ ScratchBlocks, workspace, vm, registry, enableDescendants, enableFixedInputs, onError }: {
3
3
  ScratchBlocks: any;
4
4
  workspace: any;
5
5
  vm: any;
6
6
  registry: any;
7
7
  enableDescendants?: boolean | undefined;
8
+ enableFixedInputs?: boolean | undefined;
8
9
  onError?: ((...data: any[]) => void) | undefined;
9
10
  }): {
10
11
  isCommitting: () => boolean;
11
- readBlock: (reference: any) => {
12
- targetId: any;
13
- blockId: any;
14
- opcode: any;
15
- revision: string;
16
- arguments: {};
17
- } | null;
12
+ readBlock: (reference: any) => any;
18
13
  commit: ({ expected, changes }: {
19
14
  expected: any;
20
15
  changes: any;
@@ -1,16 +1,33 @@
1
1
  import { BlockEditorConflictError, DESCENDANT_EDITING_CAPABILITY } from '../index.js';
2
2
  import collectBlockEditorSubtree from './subtree.js';
3
+ import createFixedInputPort from './fixed-transaction.js';
3
4
  const owns = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
4
5
  // Adapt only standard, unvalidated text shadows; never evaluate connected reporters.
5
- const createBlockEditorHost = ({ ScratchBlocks, workspace, vm, registry, enableDescendants = false, onError = console.error }) => {
6
+ const createBlockEditorHost = ({ ScratchBlocks, workspace, vm, registry, enableDescendants = false, enableFixedInputs = false, onError = console.error }) => {
6
7
  let disposed = false;
7
8
  let committing = false;
8
9
  let revision = 0;
9
10
  let identity = 0;
10
11
  let generation = 1;
12
+ let externalRevision = 0;
13
+ let fixedTransaction = null;
11
14
  let lastTarget = vm.editingTarget;
12
15
  const versions = new Map();
13
16
  const invalidations = new Set();
17
+ const fixedScopeListeners = new Set();
18
+ const notifyFixedScopes = () => {
19
+ for (const listener of [...fixedScopeListeners]) {
20
+ try {
21
+ listener();
22
+ }
23
+ catch (error) {
24
+ try {
25
+ onError(error);
26
+ }
27
+ catch ( /* Scope observers cannot interrupt native events. */_a) { /* Scope observers cannot interrupt native events. */ }
28
+ }
29
+ }
30
+ };
14
31
  let notifyInvalidations = () => { };
15
32
  const identities = new WeakMap();
16
33
  const identify = object => {
@@ -22,6 +39,17 @@ const createBlockEditorHost = ({ ScratchBlocks, workspace, vm, registry, enableD
22
39
  if (event && event.isUiEvent)
23
40
  return;
24
41
  revision++;
42
+ if (fixedTransaction && event && fixedTransaction.events.has(event)) {
43
+ fixedTransaction.revisions++;
44
+ if (enableDescendants) {
45
+ for (const id of new Set([event.blockId, ...(event.ids || [])])) {
46
+ if (id)
47
+ fixedTransaction.versions.set(id, (fixedTransaction.versions.get(id) || 0) + 1);
48
+ }
49
+ }
50
+ }
51
+ else
52
+ externalRevision++;
25
53
  if (enableDescendants && event) {
26
54
  const ids = new Set([event.blockId, ...(event.ids || [])]);
27
55
  for (const id of ids)
@@ -30,6 +58,7 @@ const createBlockEditorHost = ({ ScratchBlocks, workspace, vm, registry, enableD
30
58
  }
31
59
  if (!committing)
32
60
  notifyInvalidations();
61
+ notifyFixedScopes();
33
62
  };
34
63
  const renew = () => {
35
64
  generation++;
@@ -56,6 +85,8 @@ const createBlockEditorHost = ({ ScratchBlocks, workspace, vm, registry, enableD
56
85
  if (!block || block.isInFlyout || block.isShadow())
57
86
  return null;
58
87
  const descriptor = registry.resolve(block.type);
88
+ if (typeof registry.resolveFixed === 'function' && registry.resolveFixed(block.type))
89
+ return null;
59
90
  const vmBlock = target.blocks.getBlock(block.id);
60
91
  if ((!structural && !descriptor) || (!block.isEditable() && (!structural || descriptor)) ||
61
92
  !vmBlock || vmBlock.opcode !== block.type)
@@ -238,10 +269,17 @@ const createBlockEditorHost = ({ ScratchBlocks, workspace, vm, registry, enableD
238
269
  const host = {
239
270
  isCommitting: () => committing,
240
271
  readBlock: reference => {
272
+ const block = workspace.getBlockById(reference.blockId);
273
+ const declaration = block && typeof registry.resolveFixed === 'function' && registry.resolveFixed(block.type);
274
+ if (declaration)
275
+ return host.fixedInputs ? host.fixedInputs.readBlock(reference, declaration) : null;
241
276
  const result = inspect(reference);
242
277
  return result ? result.snapshot : null;
243
278
  },
244
279
  commit: ({ expected, changes }) => {
280
+ if (typeof registry.resolveFixed === 'function' && registry.resolveFixed(expected.opcode)) {
281
+ throw new TypeError('Fixed input editors require the fixed transaction port.');
282
+ }
245
283
  const current = inspect(expected);
246
284
  if (!current || JSON.stringify(current.snapshot) !== JSON.stringify(expected)) {
247
285
  throw new BlockEditorConflictError();
@@ -251,6 +289,8 @@ const createBlockEditorHost = ({ ScratchBlocks, workspace, vm, registry, enableD
251
289
  invalidate: renew,
252
290
  dispose: () => {
253
291
  disposed = true;
292
+ notifyFixedScopes();
293
+ fixedScopeListeners.clear();
254
294
  notifyInvalidations();
255
295
  invalidations.clear();
256
296
  workspace.removeChangeListener(invalidate);
@@ -284,6 +324,33 @@ const createBlockEditorHost = ({ ScratchBlocks, workspace, vm, registry, enableD
284
324
  }
285
325
  };
286
326
  }
327
+ if (enableFixedInputs === true) {
328
+ host.fixedInputs = createFixedInputPort({ ScratchBlocks, workspace, vm, registry, identify, onError,
329
+ subscribeScope: listener => {
330
+ fixedScopeListeners.add(listener);
331
+ return () => { fixedScopeListeners.delete(listener); };
332
+ },
333
+ getState: () => ({ disposed, revision, generation, externalRevision }),
334
+ begin: () => {
335
+ committing = true;
336
+ fixedTransaction = { events: new WeakSet(), revisions: 0, versions: new Map() };
337
+ },
338
+ markEvent: event => fixedTransaction.events.add(event),
339
+ finish: succeeded => {
340
+ if (!succeeded) {
341
+ revision -= fixedTransaction.revisions;
342
+ for (const [id, delta] of fixedTransaction.versions) {
343
+ const remaining = (versions.get(id) || 0) - delta;
344
+ if (remaining > 0)
345
+ versions.set(id, remaining);
346
+ else
347
+ versions.delete(id);
348
+ }
349
+ }
350
+ fixedTransaction = null;
351
+ committing = false;
352
+ } });
353
+ }
287
354
  return host;
288
355
  };
289
356
  export default createBlockEditorHost;
@@ -15,6 +15,8 @@ export interface TurboWarpHostOptions {
15
15
  readonly vm: object;
16
16
  readonly registry: EditorRegistry;
17
17
  readonly enableDescendants?: boolean;
18
+ /** Opt in to standard fixed fields; GUI binding remains a separate capability. */
19
+ readonly enableFixedInputs?: boolean;
18
20
  readonly onError?: (error: unknown) => void;
19
21
  }
20
22
  export interface TurboWarpBlockEditorHost extends BlockEditorHost {
@@ -28,6 +30,8 @@ export interface TurboWarpBlockEditorOptions {
28
30
  /** Only true attaches listeners or decorates icons. Read feature flags in the host at startup. */
29
31
  readonly enabled?: boolean;
30
32
  readonly descendants?: boolean;
33
+ /** Opt in to fixed argument editors independently of SourceForms and Descendants. */
34
+ readonly fixedInputs?: boolean;
31
35
  readonly ScratchBlocks: object;
32
36
  readonly workspace: object;
33
37
  readonly vm: object;
package/dist/turbowarp.js CHANGED
@@ -8,7 +8,7 @@ export function attachTurboWarpBlockEditors(options) {
8
8
  var _a;
9
9
  if (options.enabled !== true)
10
10
  return Object.freeze({ refresh() { }, dispose() { } });
11
- const bridge = createBridge({ ...options, descendants: options.descendants === true,
11
+ const bridge = createBridge({ ...options, descendants: options.descendants === true, fixedInputs: options.fixedInputs === true,
12
12
  onError: (_a = options.onError) !== null && _a !== void 0 ? _a : console.error });
13
13
  let disposed = false;
14
14
  return Object.freeze({
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ValidationIssue } from './schema-types.js';
2
2
  export type { ValidationIssue } from './schema-types.js';
3
3
  import type { DescendantEditorHost } from './descendant-types.js';
4
+ import type { FixedInputEditorHost, FixedInputViewDescription } from './fixed-input-types.js';
4
5
  export declare const EDITOR_API_VERSION: 1;
5
6
  export type LiteralValue = string | number | boolean;
6
7
  export type JsonValue = null | LiteralValue | JsonObject | JsonValue[];
@@ -32,6 +33,8 @@ export interface CommitRequest {
32
33
  readonly changes: readonly LiteralArgumentChange[];
33
34
  }
34
35
  export interface BlockEditorHost {
36
+ /** Optional fixed-field capability; legacy hosts do not need this port. */
37
+ readonly fixedInputs?: FixedInputEditorHost;
35
38
  /** Optional additional capability; the single-block contract remains unchanged. */
36
39
  readonly descendants?: DescendantEditorHost;
37
40
  /** Return null for missing, inactive or non-editable blocks. Never evaluate reporters. */
@@ -52,6 +55,8 @@ export interface SessionState {
52
55
  }
53
56
  export interface EditorViewContext {
54
57
  getDraft(): JsonObject;
58
+ /** Fixed views obtain only safe presentation data, never live host objects. */
59
+ getFixedInputView?(): FixedInputViewDescription | undefined;
55
60
  /** Updates are rejected after this view unmounts, even when its session stays open. */
56
61
  setDraft(draft: JsonObject): void;
57
62
  /** Report live UI validation; save() always independently validates again. */