@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.
- package/README.md +9 -6
- package/dist/build-info.json +3 -3
- package/dist/fixed-input-branches.d.ts +12 -0
- package/dist/fixed-input-branches.js +180 -0
- package/dist/fixed-input-editor.d.ts +39 -0
- package/dist/fixed-input-editor.js +538 -0
- package/dist/fixed-input-schema.d.ts +15 -0
- package/dist/fixed-input-schema.js +21 -0
- package/dist/fixed-input-snapshot.d.ts +7 -0
- package/dist/fixed-input-snapshot.js +180 -0
- package/dist/fixed-input-types.d.ts +138 -0
- package/dist/fixed-input-types.js +23 -0
- package/dist/fixed-input-view.d.ts +14 -0
- package/dist/fixed-input-view.js +168 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/react.css +168 -0
- package/dist/react.d.ts +8 -0
- package/dist/react.js +19 -2
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +45 -7
- package/dist/rjsf-templates.d.ts +11 -0
- package/dist/rjsf-templates.js +129 -0
- package/dist/rjsf.d.ts +24 -1
- package/dist/rjsf.js +35 -3
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +1 -0
- package/dist/session.d.ts +3 -1
- package/dist/session.js +205 -7
- package/dist/turbowarp/bridge.d.ts +2 -1
- package/dist/turbowarp/bridge.js +37 -5
- package/dist/turbowarp/fixed-fields.d.ts +82 -0
- package/dist/turbowarp/fixed-fields.js +311 -0
- package/dist/turbowarp/fixed-transaction.d.ts +18 -0
- package/dist/turbowarp/fixed-transaction.js +460 -0
- package/dist/turbowarp/host.d.ts +3 -8
- package/dist/turbowarp/host.js +68 -1
- package/dist/turbowarp.d.ts +4 -0
- package/dist/turbowarp.js +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/view.js +4 -0
- package/dist/webpack4/index.js +22 -5
- package/dist/webpack4/metafile.json +592 -299
- package/dist/webpack4/react.js +20 -6
- package/dist/webpack4/react.js.map +2 -2
- package/dist/webpack4/rjsf.js +450 -60
- package/dist/webpack4/rjsf.js.map +4 -4
- package/dist/webpack4/schema.js +6 -3
- package/dist/webpack4/{shared-KQOUMWJ4.js → shared-75QIWLZY.js} +1129 -1244
- package/dist/webpack4/{shared-KQOUMWJ4.js.map → shared-75QIWLZY.js.map} +4 -4
- package/dist/webpack4/shared-FYUILHED.js +1 -0
- package/dist/webpack4/shared-GQLPPRT2.js +210 -0
- package/dist/webpack4/shared-GQLPPRT2.js.map +7 -0
- package/dist/webpack4/shared-P2JBRZFP.js +2206 -0
- package/dist/webpack4/shared-P2JBRZFP.js.map +7 -0
- package/dist/webpack4/turbowarp.js +913 -9
- package/dist/webpack4/turbowarp.js.map +4 -4
- package/docs/api.md +7 -0
- package/docs/fixed-input-contracts.md +77 -0
- package/docs/package-distribution.md +5 -0
- package/docs/stage-2-publication.ja.md +6 -0
- package/package.json +2 -1
- package/dist/webpack4/shared-CQB3NG2A.js +0 -1
- package/dist/webpack4/shared-MRPGBJIH.js +0 -75
- package/dist/webpack4/shared-MRPGBJIH.js.map +0 -7
- package/dist/webpack4/shared-N6YQE2X6.js +0 -1029
- package/dist/webpack4/shared-N6YQE2X6.js.map +0 -7
- /package/dist/webpack4/{shared-CQB3NG2A.js.map → shared-FYUILHED.js.map} +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { snapshotJson } from './schema-json.js';
|
|
2
|
+
import { captureFixedBranchRules } from './fixed-input-branches.js';
|
|
3
|
+
function fail() { throw new TypeError('Invalid fixed-input contract or snapshot.'); }
|
|
4
|
+
/** Read each own data property once; accessors and prototype values cannot supply data. */
|
|
5
|
+
function own(value, key) {
|
|
6
|
+
if (!value || typeof value !== 'object')
|
|
7
|
+
return fail();
|
|
8
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
9
|
+
if (!descriptor || !('value' in descriptor))
|
|
10
|
+
return fail();
|
|
11
|
+
return descriptor.value;
|
|
12
|
+
}
|
|
13
|
+
function text(value) {
|
|
14
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
15
|
+
return fail();
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function record(value) {
|
|
19
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
20
|
+
return fail();
|
|
21
|
+
const prototype = Object.getPrototypeOf(value);
|
|
22
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
23
|
+
return fail();
|
|
24
|
+
if (Object.getOwnPropertySymbols(value).length)
|
|
25
|
+
return fail();
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function names(value) {
|
|
29
|
+
if (!Array.isArray(value))
|
|
30
|
+
return fail();
|
|
31
|
+
const items = [];
|
|
32
|
+
for (let i = 0; i < value.length; i++)
|
|
33
|
+
items.push(text(own(value, String(i))));
|
|
34
|
+
if (new Set(items).size !== items.length)
|
|
35
|
+
return fail();
|
|
36
|
+
return Object.freeze(items);
|
|
37
|
+
}
|
|
38
|
+
function dependencies(value, editable) {
|
|
39
|
+
if (!Array.isArray(value))
|
|
40
|
+
return fail();
|
|
41
|
+
const selectors = new Set();
|
|
42
|
+
const result = [];
|
|
43
|
+
for (let i = 0; i < value.length; i++) {
|
|
44
|
+
const item = record(own(value, String(i)));
|
|
45
|
+
const selector = text(own(item, 'selector'));
|
|
46
|
+
const dependents = names(own(item, 'dependents'));
|
|
47
|
+
if (!editable.has(selector) || selectors.has(selector) || dependents.length === 0 ||
|
|
48
|
+
dependents.some(name => name === selector || !editable.has(name)))
|
|
49
|
+
return fail();
|
|
50
|
+
selectors.add(selector);
|
|
51
|
+
result.push(Object.freeze({ selector, dependents }));
|
|
52
|
+
}
|
|
53
|
+
return Object.freeze(result);
|
|
54
|
+
}
|
|
55
|
+
export function captureFixedInputDeclaration(value) {
|
|
56
|
+
const policy = Object.getOwnPropertyDescriptor(value, 'branchRules');
|
|
57
|
+
if (policy && !('value' in policy))
|
|
58
|
+
return fail();
|
|
59
|
+
const captured = record(snapshotJson({
|
|
60
|
+
contractId: own(value, 'contractId'), contractRevision: own(value, 'contractRevision'),
|
|
61
|
+
editableArguments: own(value, 'editableArguments'), readOnlyArguments: own(value, 'readOnlyArguments'),
|
|
62
|
+
dependencies: own(value, 'dependencies')
|
|
63
|
+
}));
|
|
64
|
+
const editableArguments = names(captured.editableArguments);
|
|
65
|
+
const readOnlyArguments = names(captured.readOnlyArguments);
|
|
66
|
+
const editable = new Set(editableArguments);
|
|
67
|
+
if (editable.size === 0 || readOnlyArguments.some(name => editable.has(name)))
|
|
68
|
+
return fail();
|
|
69
|
+
const groups = dependencies(captured.dependencies, editable);
|
|
70
|
+
const branchRules = captureFixedBranchRules(policy === null || policy === void 0 ? void 0 : policy.value, groups, readOnlyArguments);
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
contractId: text(captured.contractId), contractRevision: text(captured.contractRevision),
|
|
73
|
+
editableArguments, readOnlyArguments, dependencies: groups,
|
|
74
|
+
...((policy === null || policy === void 0 ? void 0 : policy.value) === undefined ? {} : { branchRules })
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export const validateFixedInputDeclaration = captureFixedInputDeclaration;
|
|
78
|
+
function canonical(value) {
|
|
79
|
+
if (value === null || typeof value !== 'object')
|
|
80
|
+
return JSON.stringify(value);
|
|
81
|
+
if (Array.isArray(value))
|
|
82
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
83
|
+
const item = value;
|
|
84
|
+
return `{${Object.keys(item).sort().map(key => `${JSON.stringify(key)}:${canonical(item[key])}`).join(',')}}`;
|
|
85
|
+
}
|
|
86
|
+
function sameNames(left, right) {
|
|
87
|
+
return left.length === right.length && left.every(name => right.includes(name));
|
|
88
|
+
}
|
|
89
|
+
/** Preserve raw literals with Object.is semantics while copying metadata as finite JSON. */
|
|
90
|
+
export function captureFixedSnapshot(value, reference, declaration) {
|
|
91
|
+
record(value);
|
|
92
|
+
const targetId = text(own(value, 'targetId'));
|
|
93
|
+
const blockId = text(own(value, 'blockId'));
|
|
94
|
+
if (reference && (targetId !== own(reference, 'targetId') || blockId !== own(reference, 'blockId')))
|
|
95
|
+
return fail();
|
|
96
|
+
const opcode = text(own(value, 'opcode'));
|
|
97
|
+
const revision = text(own(value, 'revision'));
|
|
98
|
+
const inputArguments = record(own(value, 'arguments'));
|
|
99
|
+
const argumentsCopy = Object.create(null);
|
|
100
|
+
for (const name of Object.keys(inputArguments)) {
|
|
101
|
+
const item = record(own(inputArguments, name));
|
|
102
|
+
const kind = own(item, 'kind');
|
|
103
|
+
if (kind === 'reporter')
|
|
104
|
+
argumentsCopy[name] = Object.freeze({ kind });
|
|
105
|
+
else if (kind === 'literal') {
|
|
106
|
+
const raw = own(item, 'value');
|
|
107
|
+
if (typeof raw !== 'string' && typeof raw !== 'number' && typeof raw !== 'boolean')
|
|
108
|
+
return fail();
|
|
109
|
+
argumentsCopy[name] = Object.freeze({ kind, value: raw });
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
return fail();
|
|
113
|
+
}
|
|
114
|
+
const context = record(snapshotJson(own(value, 'fixedContext')));
|
|
115
|
+
const contractId = text(context.contractId);
|
|
116
|
+
const contractRevision = text(context.contractRevision);
|
|
117
|
+
const generation = text(context.generation);
|
|
118
|
+
const readOnlyArguments = names(context.readOnlyArguments);
|
|
119
|
+
const inputMetadata = record(context.inputs);
|
|
120
|
+
const editable = new Set();
|
|
121
|
+
const inputs = Object.create(null);
|
|
122
|
+
for (const name of Object.keys(inputMetadata)) {
|
|
123
|
+
const item = record(inputMetadata[name]);
|
|
124
|
+
if (item.role !== 'editable' && item.role !== 'readonly')
|
|
125
|
+
return fail();
|
|
126
|
+
const role = item.role;
|
|
127
|
+
if ((role === 'readonly') !== readOnlyArguments.includes(name))
|
|
128
|
+
return fail();
|
|
129
|
+
if (role === 'editable')
|
|
130
|
+
editable.add(name);
|
|
131
|
+
const source = item.source;
|
|
132
|
+
if (source !== 'shadow' && source !== 'field' && source !== 'reporter' && source !== 'unsupported' && source !== 'missing')
|
|
133
|
+
return fail();
|
|
134
|
+
const slot = argumentsCopy[name];
|
|
135
|
+
if ((source === 'shadow' || source === 'field') && (slot === null || slot === void 0 ? void 0 : slot.kind) !== 'literal')
|
|
136
|
+
return fail();
|
|
137
|
+
if (source === 'reporter' && (slot === null || slot === void 0 ? void 0 : slot.kind) !== 'reporter')
|
|
138
|
+
return fail();
|
|
139
|
+
if (source === 'missing' && (slot === null || slot === void 0 ? void 0 : slot.kind) === 'literal')
|
|
140
|
+
return fail();
|
|
141
|
+
let field;
|
|
142
|
+
if (source === 'shadow' || source === 'field') {
|
|
143
|
+
const capturedField = record(item.field);
|
|
144
|
+
field = Object.freeze({ blockId: text(capturedField.blockId), name: text(capturedField.name), type: text(capturedField.type) });
|
|
145
|
+
}
|
|
146
|
+
else if (item.field !== undefined)
|
|
147
|
+
return fail();
|
|
148
|
+
if (item.reason !== undefined && typeof item.reason !== 'string')
|
|
149
|
+
return fail();
|
|
150
|
+
if ((source === 'unsupported' || source === 'missing') && !item.reason)
|
|
151
|
+
return fail();
|
|
152
|
+
inputs[name] = Object.freeze({ role, source, identity: text(item.identity),
|
|
153
|
+
...(field ? { field } : {}), ...(item.reason === undefined ? {} : { reason: item.reason }) });
|
|
154
|
+
}
|
|
155
|
+
if (readOnlyArguments.some(name => !Object.hasOwn(inputs, name)))
|
|
156
|
+
return fail();
|
|
157
|
+
const capturedDependencies = dependencies(context.dependencies, editable);
|
|
158
|
+
if (declaration) {
|
|
159
|
+
const expected = captureFixedInputDeclaration(declaration);
|
|
160
|
+
if (contractId !== expected.contractId || contractRevision !== expected.contractRevision ||
|
|
161
|
+
!sameNames([...editable], expected.editableArguments) || !sameNames(readOnlyArguments, expected.readOnlyArguments) ||
|
|
162
|
+
canonical(capturedDependencies) !== canonical(expected.dependencies))
|
|
163
|
+
return fail();
|
|
164
|
+
}
|
|
165
|
+
return Object.freeze({ targetId, blockId, opcode, revision, arguments: Object.freeze(argumentsCopy),
|
|
166
|
+
fixedContext: Object.freeze({ contractId, contractRevision, generation, readOnlyArguments,
|
|
167
|
+
dependencies: capturedDependencies, inputs: Object.freeze(inputs) }) });
|
|
168
|
+
}
|
|
169
|
+
export function sameFixedSnapshot(left, right) {
|
|
170
|
+
const a = captureFixedSnapshot(left);
|
|
171
|
+
const b = captureFixedSnapshot(right);
|
|
172
|
+
const keys = Object.keys(a.arguments);
|
|
173
|
+
return a.targetId === b.targetId && a.blockId === b.blockId && a.opcode === b.opcode && a.revision === b.revision &&
|
|
174
|
+
canonical(a.fixedContext) === canonical(b.fixedContext) && keys.length === Object.keys(b.arguments).length &&
|
|
175
|
+
keys.every(name => {
|
|
176
|
+
const x = a.arguments[name];
|
|
177
|
+
const y = b.arguments[name];
|
|
178
|
+
return !!y && x.kind === y.kind && (x.kind === 'reporter' || (y.kind === 'literal' && Object.is(x.value, y.value)));
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { BlockEditorHost, BlockReference, BlockSnapshot, EditorDefinition, JsonObject, JsonValue, LiteralArgumentChange, ValidationIssue } from './types.js';
|
|
2
|
+
import type { SchemaJsonValue, SchemaValidationContext } from './schema-types.js';
|
|
3
|
+
export declare const FIXED_INPUTS_CAPABILITY: "fixed-inputs-v1";
|
|
4
|
+
export interface FixedInputDependency {
|
|
5
|
+
readonly selector: string;
|
|
6
|
+
readonly dependents: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
/** Finite owner-declared branch rules; the host never evaluates owner code. */
|
|
9
|
+
export interface FixedInputBranchCase {
|
|
10
|
+
readonly values: readonly string[];
|
|
11
|
+
readonly activeDependents: readonly string[];
|
|
12
|
+
readonly requireOnSelectorChange?: readonly {
|
|
13
|
+
readonly argument: string;
|
|
14
|
+
readonly rule: 'literal' | 'nonempty-string';
|
|
15
|
+
}[];
|
|
16
|
+
}
|
|
17
|
+
export interface FixedInputBranchRule {
|
|
18
|
+
readonly selector: string;
|
|
19
|
+
readonly normalize?: {
|
|
20
|
+
readonly trim?: boolean;
|
|
21
|
+
readonly lowercase?: boolean;
|
|
22
|
+
};
|
|
23
|
+
readonly cases: readonly FixedInputBranchCase[];
|
|
24
|
+
}
|
|
25
|
+
export interface FixedInputControl {
|
|
26
|
+
readonly kind: 'text' | 'number' | 'enum' | 'color';
|
|
27
|
+
readonly label?: string;
|
|
28
|
+
readonly choices?: readonly {
|
|
29
|
+
readonly value: JsonValue;
|
|
30
|
+
readonly label?: string;
|
|
31
|
+
}[];
|
|
32
|
+
readonly reset?: JsonValue;
|
|
33
|
+
}
|
|
34
|
+
export interface FixedInputViewInput {
|
|
35
|
+
readonly state: 'editable' | 'readonly' | 'hidden';
|
|
36
|
+
readonly control: FixedInputControl['kind'];
|
|
37
|
+
readonly label?: string;
|
|
38
|
+
readonly choices?: FixedInputControl['choices'];
|
|
39
|
+
readonly reset?: JsonValue;
|
|
40
|
+
readonly valueType?: 'number' | 'string';
|
|
41
|
+
readonly reason?: string;
|
|
42
|
+
readonly rawDisplay?: string;
|
|
43
|
+
readonly unverified?: true;
|
|
44
|
+
}
|
|
45
|
+
export interface FixedInputBranchState {
|
|
46
|
+
readonly selector: string;
|
|
47
|
+
readonly status: 'known' | 'alias' | 'unknown' | 'reporter' | 'unsupported' | 'missing';
|
|
48
|
+
readonly value?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface FixedInputViewDescription {
|
|
51
|
+
readonly inputs: Readonly<Record<string, FixedInputViewInput>>;
|
|
52
|
+
readonly issues: readonly ValidationIssue[];
|
|
53
|
+
readonly branches: readonly FixedInputBranchState[];
|
|
54
|
+
}
|
|
55
|
+
export interface FixedInputDeclaration {
|
|
56
|
+
readonly contractId: string;
|
|
57
|
+
readonly contractRevision: string;
|
|
58
|
+
readonly editableArguments: readonly string[];
|
|
59
|
+
readonly readOnlyArguments: readonly string[];
|
|
60
|
+
readonly dependencies: readonly FixedInputDependency[];
|
|
61
|
+
readonly branchRules?: readonly FixedInputBranchRule[];
|
|
62
|
+
}
|
|
63
|
+
/** Host-owned field identity only; live VM and Blockly objects never cross this port. */
|
|
64
|
+
export interface FixedInputMetadata {
|
|
65
|
+
readonly role: 'editable' | 'readonly';
|
|
66
|
+
readonly source: 'shadow' | 'field' | 'reporter' | 'unsupported' | 'missing';
|
|
67
|
+
readonly identity: string;
|
|
68
|
+
readonly field?: {
|
|
69
|
+
readonly blockId: string;
|
|
70
|
+
readonly name: string;
|
|
71
|
+
readonly type: string;
|
|
72
|
+
};
|
|
73
|
+
readonly reason?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface FixedBlockSnapshot extends BlockSnapshot {
|
|
76
|
+
readonly fixedContext: {
|
|
77
|
+
readonly contractId: string;
|
|
78
|
+
readonly contractRevision: string;
|
|
79
|
+
readonly generation: string;
|
|
80
|
+
readonly readOnlyArguments: readonly string[];
|
|
81
|
+
readonly dependencies: readonly FixedInputDependency[];
|
|
82
|
+
readonly inputs: Readonly<Record<string, FixedInputMetadata>>;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** The form projection never replaces the raw literals retained in the snapshot. */
|
|
86
|
+
export type FixedInputDraftEntry = {
|
|
87
|
+
readonly value: JsonValue;
|
|
88
|
+
readonly changed: boolean;
|
|
89
|
+
readonly buffer?: string;
|
|
90
|
+
readonly repair?: true;
|
|
91
|
+
} | {
|
|
92
|
+
readonly readOnly: true;
|
|
93
|
+
};
|
|
94
|
+
export interface FixedInputDraft extends JsonObject {
|
|
95
|
+
readonly inputs: Readonly<Record<string, FixedInputDraftEntry>>;
|
|
96
|
+
}
|
|
97
|
+
export type FixedPreparedSave<C = null> = {
|
|
98
|
+
readonly kind: 'ready';
|
|
99
|
+
readonly changes: readonly LiteralArgumentChange[];
|
|
100
|
+
readonly context: SchemaValidationContext<C>;
|
|
101
|
+
} | {
|
|
102
|
+
readonly kind: 'invalid';
|
|
103
|
+
readonly issues: readonly ValidationIssue[];
|
|
104
|
+
};
|
|
105
|
+
export interface FixedInputEditorDefinition<C = null> extends EditorDefinition, FixedInputDeclaration {
|
|
106
|
+
readonly saveContract: 'prepared-fixed-v1';
|
|
107
|
+
readFixed(snapshot: FixedBlockSnapshot): JsonObject;
|
|
108
|
+
prepareFixedSave(draft: JsonObject, snapshot: FixedBlockSnapshot): FixedPreparedSave<C>;
|
|
109
|
+
describeFixed?(draft: JsonObject, snapshot: FixedBlockSnapshot): FixedInputViewDescription;
|
|
110
|
+
}
|
|
111
|
+
export interface FixedCommitRequest<C = null> {
|
|
112
|
+
readonly expected: FixedBlockSnapshot;
|
|
113
|
+
readonly changes: readonly LiteralArgumentChange[];
|
|
114
|
+
readonly context: SchemaValidationContext<C>;
|
|
115
|
+
readonly signal: AbortSignal;
|
|
116
|
+
}
|
|
117
|
+
/** Erase owner settings to JSON, never to unknown (whose JsonSettings type is never). */
|
|
118
|
+
export interface FixedHostCommitRequest extends Omit<FixedCommitRequest, 'context'> {
|
|
119
|
+
readonly context: {
|
|
120
|
+
readonly consumerId: string;
|
|
121
|
+
readonly nowMs: number;
|
|
122
|
+
readonly settings: SchemaJsonValue;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export interface FixedInputEditorHost {
|
|
126
|
+
readonly capability: typeof FIXED_INPUTS_CAPABILITY;
|
|
127
|
+
/** Include editable inputs and read-only dependencies without evaluating reporters. */
|
|
128
|
+
readBlock(reference: BlockReference, declaration: FixedInputDeclaration): FixedBlockSnapshot | null;
|
|
129
|
+
/** Recheck all identities and dependencies at the actual atomic write boundary. */
|
|
130
|
+
commit(request: FixedHostCommitRequest): void | Promise<void>;
|
|
131
|
+
/** Optional lifetime notification; ordinary same-scope edits remain save-time conflicts. */
|
|
132
|
+
subscribeInvalidation?(reference: BlockReference, declaration: FixedInputDeclaration, listener: (error: unknown) => void): () => void;
|
|
133
|
+
}
|
|
134
|
+
/** Inspect methods without evaluating accessor-backed capability advertisements. */
|
|
135
|
+
export declare function supportsFixedInputs(host: BlockEditorHost): host is BlockEditorHost & {
|
|
136
|
+
fixedInputs: FixedInputEditorHost;
|
|
137
|
+
};
|
|
138
|
+
export declare const supportsFixedInputEditing: typeof supportsFixedInputs;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const FIXED_INPUTS_CAPABILITY = 'fixed-inputs-v1';
|
|
2
|
+
/** Inspect methods without evaluating accessor-backed capability advertisements. */
|
|
3
|
+
export function supportsFixedInputs(host) {
|
|
4
|
+
const data = (object, key) => {
|
|
5
|
+
let item = object;
|
|
6
|
+
for (let depth = 0; item && (typeof item === 'object' || typeof item === 'function') && depth < 64; depth++) {
|
|
7
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
8
|
+
if (descriptor)
|
|
9
|
+
return 'value' in descriptor ? descriptor.value : undefined;
|
|
10
|
+
item = Object.getPrototypeOf(item);
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
};
|
|
14
|
+
try {
|
|
15
|
+
const port = data(host, 'fixedInputs');
|
|
16
|
+
return data(port, 'capability') === FIXED_INPUTS_CAPABILITY &&
|
|
17
|
+
typeof data(port, 'readBlock') === 'function' && typeof data(port, 'commit') === 'function';
|
|
18
|
+
}
|
|
19
|
+
catch (_a) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export const supportsFixedInputEditing = supportsFixedInputs;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EditorViewContext } from './types.js';
|
|
2
|
+
export interface FixedInputViewMessages {
|
|
3
|
+
readonly reset: string;
|
|
4
|
+
readonly readOnly: string;
|
|
5
|
+
readonly unverified: string;
|
|
6
|
+
readonly currentValue: string;
|
|
7
|
+
readonly colorPicker: string;
|
|
8
|
+
readonly invalidValue: string;
|
|
9
|
+
readonly inactiveDraft: string;
|
|
10
|
+
}
|
|
11
|
+
export type FixedInputMessageSource = Partial<FixedInputViewMessages> | (() => Partial<FixedInputViewMessages>);
|
|
12
|
+
export declare function captureFixedInputMessages(input?: Partial<FixedInputViewMessages>): FixedInputViewMessages;
|
|
13
|
+
export declare function resolveFixedInputMessages(source?: FixedInputMessageSource): FixedInputViewMessages;
|
|
14
|
+
export declare function mountFixedInputView(container: HTMLElement, context: EditorViewContext, messages?: FixedInputViewMessages): () => void;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as ReactDOM from 'react-dom';
|
|
3
|
+
import { parseFixedNumberInput } from './fixed-input-schema.js';
|
|
4
|
+
const defaultMessages = Object.freeze({
|
|
5
|
+
reset: 'Reset', readOnly: 'This input cannot be edited safely.',
|
|
6
|
+
unverified: 'The expression is retained without evaluation. Its runtime validity has not been checked.',
|
|
7
|
+
currentValue: 'Current value', colorPicker: 'color picker', invalidValue: 'Check this value before saving.',
|
|
8
|
+
inactiveDraft: 'Hidden edits are retained in this dialog and will not be saved while hidden.'
|
|
9
|
+
});
|
|
10
|
+
export function captureFixedInputMessages(input) {
|
|
11
|
+
if (input === undefined)
|
|
12
|
+
return defaultMessages;
|
|
13
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) ||
|
|
14
|
+
(Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null) || Object.getOwnPropertySymbols(input).length)
|
|
15
|
+
throw new TypeError('Invalid fixed form messages.');
|
|
16
|
+
const result = { ...defaultMessages };
|
|
17
|
+
for (const key of Object.getOwnPropertyNames(input)) {
|
|
18
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
19
|
+
if (!Object.hasOwn(defaultMessages, key) || !('value' in descriptor) || typeof descriptor.value !== 'string' || !descriptor.value)
|
|
20
|
+
throw new TypeError('Invalid fixed form messages.');
|
|
21
|
+
Object.defineProperty(result, key, { value: descriptor.value, enumerable: true });
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze(result);
|
|
24
|
+
}
|
|
25
|
+
export function resolveFixedInputMessages(source) {
|
|
26
|
+
const value = typeof source === 'function' ? source() : source;
|
|
27
|
+
try {
|
|
28
|
+
return captureFixedInputMessages(value);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
try {
|
|
32
|
+
void Promise.prototype.then.call(value, () => undefined, () => undefined);
|
|
33
|
+
}
|
|
34
|
+
catch ( /* UI providers are synchronous; never inspect thenables. */_a) { /* UI providers are synchronous; never inspect thenables. */ }
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
39
|
+
const display = (value) => typeof value === 'string' ? value
|
|
40
|
+
: typeof value === 'number' && Object.is(value, -0) ? '-0' : value === undefined ? '' : JSON.stringify(value);
|
|
41
|
+
const serialized = (value) => value === undefined ? 'unavailable' : JSON.stringify(value);
|
|
42
|
+
function preview(value) {
|
|
43
|
+
if (/^#[\da-f]{6}$/iu.test(value))
|
|
44
|
+
return value.toLowerCase();
|
|
45
|
+
return /^#[\da-f]{3}$/iu.test(value) ? `#${value.slice(1).split('').map(part => part + part).join('').toLowerCase()}` : undefined;
|
|
46
|
+
}
|
|
47
|
+
function describe(context) {
|
|
48
|
+
if (typeof context.getFixedInputView !== 'function')
|
|
49
|
+
throw new TypeError('A fixed form requires a fixed session.');
|
|
50
|
+
const result = context.getFixedInputView();
|
|
51
|
+
if (!result)
|
|
52
|
+
throw new TypeError('A fixed form requires a fixed session.');
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
let nextFixedViewId = 0;
|
|
56
|
+
/** The session owns every buffer, including controls temporarily absent from the DOM. */
|
|
57
|
+
function FixedInputView({ context, messages }) {
|
|
58
|
+
const [, redraw] = React.useState(0);
|
|
59
|
+
const [viewId] = React.useState(() => `block-editor-fixed-${++nextFixedViewId}`);
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
const stop = context.subscribe(() => redraw(value => value + 1));
|
|
62
|
+
if (context.getState().status === 'editing' && !context.getState().issues.length) {
|
|
63
|
+
context.setValidationIssues(describe(context).issues);
|
|
64
|
+
}
|
|
65
|
+
return stop;
|
|
66
|
+
}, [context]);
|
|
67
|
+
const state = context.getState();
|
|
68
|
+
if (state.status !== 'editing' && state.status !== 'saving')
|
|
69
|
+
return React.createElement(React.Fragment, null);
|
|
70
|
+
const description = describe(context);
|
|
71
|
+
const draft = context.getDraft();
|
|
72
|
+
const inputs = record(draft.inputs) ? draft.inputs : {};
|
|
73
|
+
const update = (argument, value, buffer) => {
|
|
74
|
+
if (context.getState().status !== 'editing')
|
|
75
|
+
return;
|
|
76
|
+
const latest = describe(context).inputs[argument];
|
|
77
|
+
if (!latest || latest.state !== 'editable')
|
|
78
|
+
return;
|
|
79
|
+
const next = context.getDraft();
|
|
80
|
+
if (!record(next.inputs))
|
|
81
|
+
throw new TypeError('Invalid fixed form draft.');
|
|
82
|
+
const previous = next.inputs[argument];
|
|
83
|
+
const entry = { value, changed: true };
|
|
84
|
+
if (buffer !== undefined)
|
|
85
|
+
entry.buffer = buffer;
|
|
86
|
+
if (record(previous) && (previous.readOnly === true || previous.repair === true))
|
|
87
|
+
entry.repair = true;
|
|
88
|
+
Object.defineProperty(next.inputs, argument, { value: entry, enumerable: true, configurable: true, writable: true });
|
|
89
|
+
context.setDraft(next);
|
|
90
|
+
context.setValidationIssues(describe(context).issues);
|
|
91
|
+
};
|
|
92
|
+
const hasInactiveEdits = Object.entries(description.inputs).some(([argument, input]) => input.state === 'hidden' && record(inputs[argument]) && inputs[argument].changed === true);
|
|
93
|
+
return React.createElement("div", { className: "block-editor-fixed-inputs" },
|
|
94
|
+
hasInactiveEdits && React.createElement("p", { className: "block-editor-fixed-inactive", "data-fixed-inactive-draft": "", role: "status" }, messages.inactiveDraft),
|
|
95
|
+
Object.entries(description.inputs).map(([argument, input], index) => {
|
|
96
|
+
var _a, _b, _c, _d, _e;
|
|
97
|
+
if (input.state === 'hidden')
|
|
98
|
+
return null;
|
|
99
|
+
const entry = record(inputs[argument]) ? inputs[argument] : {};
|
|
100
|
+
const value = Object.hasOwn(entry, 'value') ? entry.value : undefined;
|
|
101
|
+
const label = (_a = input.label) !== null && _a !== void 0 ? _a : argument;
|
|
102
|
+
const text = typeof entry.buffer === 'string' ? entry.buffer : value === undefined ? (_b = input.rawDisplay) !== null && _b !== void 0 ? _b : '' : display(value);
|
|
103
|
+
const disabled = state.status !== 'editing' || input.state !== 'editable';
|
|
104
|
+
const path = `/${argument.replace(/~/g, '~0').replace(/\//g, '~1')}`;
|
|
105
|
+
const invalid = [...state.issues, ...description.issues].some(issue => { var _a; return issue.path === path || ((_a = issue.path) === null || _a === void 0 ? void 0 : _a.startsWith(`${path}/`)); });
|
|
106
|
+
const errorId = `${viewId}-${index}-error`;
|
|
107
|
+
const reasonId = `${viewId}-${index}-reason`;
|
|
108
|
+
const unverifiedId = `${viewId}-${index}-unverified`;
|
|
109
|
+
const originalId = `${viewId}-${index}-original`;
|
|
110
|
+
const showOriginal = input.control === 'enum' && input.rawDisplay !== undefined && entry.changed !== true && input.rawDisplay !== display(value);
|
|
111
|
+
const descriptions = [input.state === 'readonly' ? reasonId : '', invalid ? errorId : '', input.unverified ? unverifiedId : '', showOriginal ? originalId : ''].filter(Boolean).join(' ');
|
|
112
|
+
const attributes = { 'aria-invalid': invalid || undefined, 'data-block-editor-invalid': invalid ? 'true' : undefined,
|
|
113
|
+
'aria-describedby': descriptions || undefined };
|
|
114
|
+
let control;
|
|
115
|
+
if (input.state === 'readonly') {
|
|
116
|
+
control = React.createElement(React.Fragment, null,
|
|
117
|
+
React.createElement("output", { "aria-label": label, ...attributes }, (_c = input.rawDisplay) !== null && _c !== void 0 ? _c : text),
|
|
118
|
+
React.createElement("p", { id: reasonId, className: "block-editor-fixed-readonly" }, messages.readOnly));
|
|
119
|
+
}
|
|
120
|
+
else if (input.control === 'enum') {
|
|
121
|
+
const choices = (_d = input.choices) !== null && _d !== void 0 ? _d : [];
|
|
122
|
+
const current = serialized(value);
|
|
123
|
+
const known = choices.some(choice => serialized(choice.value) === current);
|
|
124
|
+
control = React.createElement("select", { "aria-label": label, ...attributes, value: known ? current : 'existing-raw', disabled: disabled, onChange: event => {
|
|
125
|
+
const choice = choices.find(item => serialized(item.value) === event.target.value);
|
|
126
|
+
if (choice)
|
|
127
|
+
update(argument, choice.value);
|
|
128
|
+
} },
|
|
129
|
+
!known && React.createElement("option", { value: "existing-raw", disabled: true }, ((_e = input.rawDisplay) !== null && _e !== void 0 ? _e : text) || messages.currentValue),
|
|
130
|
+
choices.map((choice, index) => { var _a; return React.createElement("option", { key: index, value: serialized(choice.value) }, (_a = choice.label) !== null && _a !== void 0 ? _a : display(choice.value)); }));
|
|
131
|
+
}
|
|
132
|
+
else if (input.control === 'number') {
|
|
133
|
+
control = React.createElement("input", { "aria-label": label, ...attributes, type: "text", inputMode: "decimal", value: text, disabled: disabled, onChange: event => {
|
|
134
|
+
const buffer = event.target.value;
|
|
135
|
+
if (input.valueType === 'string' || (input.valueType === undefined && typeof value === 'string')) {
|
|
136
|
+
update(argument, buffer);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const parsed = parseFixedNumberInput(buffer);
|
|
140
|
+
update(argument, parsed.kind === 'valid' ? parsed.value : value !== null && value !== void 0 ? value : null, buffer);
|
|
141
|
+
}
|
|
142
|
+
} });
|
|
143
|
+
}
|
|
144
|
+
else if (input.control === 'color') {
|
|
145
|
+
const color = preview(text);
|
|
146
|
+
control = React.createElement("div", { className: "block-editor-fixed-color" },
|
|
147
|
+
React.createElement("input", { "aria-label": label, ...attributes, type: "text", value: text, disabled: disabled, onChange: event => update(argument, event.target.value) }),
|
|
148
|
+
color !== undefined && React.createElement("input", { type: "color", "aria-label": `${label} ${messages.colorPicker}`, ...attributes, value: color, disabled: disabled, onChange: event => update(argument, event.target.value) }));
|
|
149
|
+
}
|
|
150
|
+
else
|
|
151
|
+
control = React.createElement("textarea", { "aria-label": label, ...attributes, value: text, disabled: disabled, onChange: event => update(argument, event.target.value) });
|
|
152
|
+
return React.createElement("fieldset", { key: argument, "data-fixed-argument": argument, disabled: disabled },
|
|
153
|
+
React.createElement("legend", null, label),
|
|
154
|
+
control,
|
|
155
|
+
input.state === 'editable' && input.reset !== undefined && React.createElement("button", { type: "button", disabled: disabled, "aria-label": `${label} ${messages.reset}`, onClick: () => update(argument, input.reset) }, messages.reset),
|
|
156
|
+
showOriginal && React.createElement("p", { id: originalId, className: "block-editor-fixed-original", "data-fixed-original-value": "" },
|
|
157
|
+
messages.currentValue,
|
|
158
|
+
': ',
|
|
159
|
+
React.createElement("output", null, input.rawDisplay)),
|
|
160
|
+
invalid && React.createElement("p", { id: errorId, className: "block-editor-fixed-error" }, messages.invalidValue),
|
|
161
|
+
input.unverified && React.createElement("p", { id: unverifiedId, className: "block-editor-fixed-unverified" }, messages.unverified));
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
export function mountFixedInputView(container, context, messages = defaultMessages) {
|
|
165
|
+
describe(context);
|
|
166
|
+
ReactDOM.render(React.createElement(FixedInputView, { context: context, messages: messages }), container);
|
|
167
|
+
return () => { ReactDOM.unmountComponentAtNode(container); };
|
|
168
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,3 +4,9 @@ export { EditorSession } from './session.js';
|
|
|
4
4
|
export * from './descendant-types.js';
|
|
5
5
|
export { DescendantSession } from './descendants.js';
|
|
6
6
|
export type * from './prepared-types.js';
|
|
7
|
+
export { parseFixedNumberInput } from './fixed-input-schema.js';
|
|
8
|
+
export type { FixedNumberInputResult } from './fixed-input-schema.js';
|
|
9
|
+
export * from './fixed-input-types.js';
|
|
10
|
+
export { captureFixedInputDeclaration, captureFixedSnapshot, sameFixedSnapshot } from './fixed-input-snapshot.js';
|
|
11
|
+
export { createFixedInputEditor } from './fixed-input-editor.js';
|
|
12
|
+
export type { FixedInputBinding, FixedInputBranchPolicy, FixedInputEditorOptions } from './fixed-input-editor.js';
|
package/dist/index.js
CHANGED
|
@@ -3,3 +3,7 @@ export { EditorRegistry } from './registry.js';
|
|
|
3
3
|
export { EditorSession } from './session.js';
|
|
4
4
|
export * from './descendant-types.js';
|
|
5
5
|
export { DescendantSession } from './descendants.js';
|
|
6
|
+
export { parseFixedNumberInput } from './fixed-input-schema.js';
|
|
7
|
+
export * from './fixed-input-types.js';
|
|
8
|
+
export { captureFixedInputDeclaration, captureFixedSnapshot, sameFixedSnapshot } from './fixed-input-snapshot.js';
|
|
9
|
+
export { createFixedInputEditor } from './fixed-input-editor.js';
|