@paged-media/plugin-sdk 0.2.27-canary.1 → 0.2.28-canary.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/dist/index.d.ts +131 -5
- package/dist/index.js +498 -37
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PagedBundle, FontFaceAsset, BundleHost, ClipboardPayload, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot,
|
|
1
|
+
import { PagedBundle, BindingProvider, Disposable, ActiveBindingProvider, BindingPropertyRequest, BindingReadResult, BindingPropertyWrite, BindingWriteResult, BindingCollectionRequest, BindingCollectionResult, MutationInput, FontFaceAsset, BundleHost, ClipboardPayload, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, ShellSurface, WidgetSurface, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, TextCaret, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome, BindingProviderHandle } from '@paged-media/plugin-api';
|
|
2
2
|
import { ComponentType } from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -192,6 +192,23 @@ interface ClipboardBackend {
|
|
|
192
192
|
read(): Promise<ClipboardPayload | null>;
|
|
193
193
|
write(payload: ClipboardPayload): Promise<void>;
|
|
194
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* The reader the editor injects to back `host.text.caret()` (C-9): a
|
|
197
|
+
* synchronous read of the user's ACTIVE text caret from the editor's
|
|
198
|
+
* text-editing/content-selection state (the caret lives in EDITOR
|
|
199
|
+
* state, not the engine). The SDK adapter owns nothing here but the
|
|
200
|
+
* pass-through + the honest no-backend default — `read()` answers the
|
|
201
|
+
* caret in the engine text-op offset convention (`ContentSelection`
|
|
202
|
+
* story-local offsets; see `TextCaret`), `null` when no caret is
|
|
203
|
+
* active. The editor's backend also answers `null` for a
|
|
204
|
+
* cell-qualified caret (cell-local offsets must not leak as
|
|
205
|
+
* story-local — the documented v1 gap). When absent,
|
|
206
|
+
* `host.text.caret()` is always `null` and `supports("text.caret@1")`
|
|
207
|
+
* is false.
|
|
208
|
+
*/
|
|
209
|
+
interface TextCaretBackend {
|
|
210
|
+
read(): TextCaret | null;
|
|
211
|
+
}
|
|
195
212
|
/**
|
|
196
213
|
* The backend the editor injects to back `host.nativeDocument` (ADR-021 /
|
|
197
214
|
* document-model direction): the isolate-safe replacement for an importer/
|
|
@@ -225,6 +242,58 @@ interface DataProviderBackend {
|
|
|
225
242
|
* providers and consumers (different plugin hosts) meet through it. Reference
|
|
226
243
|
* implementation — an RPC/isolate host swaps a proxy with the same contract. */
|
|
227
244
|
declare function createDataProviderRegistry(): DataProviderBackend;
|
|
245
|
+
/** One provider as the registry holds it (the adapter's registration
|
|
246
|
+
* payload — the bundle identity the contract's answers carry as
|
|
247
|
+
* `provider`, plus the borrowed context type). */
|
|
248
|
+
interface RegisteredBindingProvider {
|
|
249
|
+
plugin: string;
|
|
250
|
+
contextType: string;
|
|
251
|
+
provider: BindingProvider;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The shared binding-provider registry (ADR-023 phase A). TWO audiences,
|
|
255
|
+
* deliberately on one object (like {@link DataProviderBackend}):
|
|
256
|
+
*
|
|
257
|
+
* · the SDK ADAPTER calls `register` / `setContextActive` — a bundle
|
|
258
|
+
* never touches these; it reaches the registry only through
|
|
259
|
+
* `host.contribute.bindingProvider`;
|
|
260
|
+
* · the HOST APP (the editor's shared panels, phase C) calls
|
|
261
|
+
* `activeProviders` / `readProperty` / `writeProperty` /
|
|
262
|
+
* `readCollection` / `applyMutation` / `onDidChange`.
|
|
263
|
+
*
|
|
264
|
+
* FALL-THROUGH IS THE HOST'S JOB, not the registry's: every resolution
|
|
265
|
+
* answers either a claim or a typed `resolved: false` / `handled: false`
|
|
266
|
+
* refusal, and the host reads/writes CORE on the refusal. The registry
|
|
267
|
+
* holds no editor handle and therefore cannot — and must not — fall
|
|
268
|
+
* through on the host's behalf; keeping the two separable is what lets
|
|
269
|
+
* the isolate implementation be an RPC proxy of exactly this shape.
|
|
270
|
+
*/
|
|
271
|
+
interface BindingProviderBackend {
|
|
272
|
+
/** Register a provider. Disposing removes it without touching the
|
|
273
|
+
* edit context whose activation it borrows. */
|
|
274
|
+
register(entry: RegisteredBindingProvider): Disposable;
|
|
275
|
+
/** Report an edit context entering (`active: true`) or leaving. Called
|
|
276
|
+
* by the adapter from the shell's own `onEnter`/`onExit`. */
|
|
277
|
+
setContextActive(plugin: string, contextType: string, elementId: string | null, active: boolean): void;
|
|
278
|
+
/** A provider announced `invalidate()` — fan out to host subscribers. */
|
|
279
|
+
notifyChanged(plugin: string, contextType: string): void;
|
|
280
|
+
/** The active provider stack, INNERMOST FIRST (= precedence order). */
|
|
281
|
+
activeProviders(): readonly ActiveBindingProvider[];
|
|
282
|
+
readProperty(request: BindingPropertyRequest): Promise<BindingReadResult>;
|
|
283
|
+
writeProperty(request: BindingPropertyWrite): Promise<BindingWriteResult>;
|
|
284
|
+
readCollection(request: BindingCollectionRequest): Promise<BindingCollectionResult>;
|
|
285
|
+
/** Offer a structural mutation to the active providers before sending
|
|
286
|
+
* it to core (`layerMove`, `createSwatch`, …). */
|
|
287
|
+
applyMutation(mutation: MutationInput): Promise<BindingWriteResult>;
|
|
288
|
+
/** Fires when the active stack changes OR a provider invalidates —
|
|
289
|
+
* the host panel's re-read signal. */
|
|
290
|
+
onDidChange(listener: () => void): Disposable;
|
|
291
|
+
}
|
|
292
|
+
/** Build a shared binding-provider registry (ADR-023 phase A). The editor
|
|
293
|
+
* holds ONE and injects it into every `createBundleHost` call as
|
|
294
|
+
* `options.bindingProviders`. Reference implementation — an
|
|
295
|
+
* RPC/isolate host swaps a proxy with the same contract. */
|
|
296
|
+
declare function createBindingProviderRegistry(): BindingProviderBackend;
|
|
228
297
|
/**
|
|
229
298
|
* Trust posture the HOST asserts for a bundle at the load path
|
|
230
299
|
* (`plugin-trust-line.md`). Same-realm execution — full `BundleHost`
|
|
@@ -241,6 +310,16 @@ declare function createDataProviderRegistry(): DataProviderBackend;
|
|
|
241
310
|
* still does not load until the isolate/RPC host gate is built.
|
|
242
311
|
*/
|
|
243
312
|
type BundleTrust = "first-party";
|
|
313
|
+
/**
|
|
314
|
+
* What the HOST APP injects as `shell` (K-10). Structurally the
|
|
315
|
+
* bundle-facing {@link ShellSurface}, except that doors added AFTER a
|
|
316
|
+
* host app adopted the option are optional here — today `saveFile`,
|
|
317
|
+
* which the editor gained one release after `pickFile`. A host that
|
|
318
|
+
* implements the full surface is assignable unchanged; a host that
|
|
319
|
+
* omits `saveFile` still injects a valid backend and
|
|
320
|
+
* `supports("shell.saveFile@1")` reports the truth.
|
|
321
|
+
*/
|
|
322
|
+
type ShellBackend = Omit<ShellSurface, "saveFile"> & Partial<Pick<ShellSurface, "saveFile">>;
|
|
244
323
|
interface CreateBundleHostOptions {
|
|
245
324
|
/**
|
|
246
325
|
* The host's trust assertion for this bundle (`plugin-trust-line.md`).
|
|
@@ -262,12 +341,23 @@ interface CreateBundleHostOptions {
|
|
|
262
341
|
* through it. When absent, `host.dataProviders` discover() is empty +
|
|
263
342
|
* register() is a no-op and `supports("dataProviders@1")` is false. */
|
|
264
343
|
dataProviders?: DataProviderBackend;
|
|
344
|
+
/** Host-provided SHARED BINDING-PROVIDER registry (ADR-023 phase A): the
|
|
345
|
+
* editor creates ONE (`createBindingProviderRegistry`) and injects the SAME
|
|
346
|
+
* instance into every plugin host, so its own panels can ask "who resolves
|
|
347
|
+
* this path / collection / op right now?" across all loaded bundles. When
|
|
348
|
+
* absent, `host.contribute.bindingProvider` warns + returns an inert handle
|
|
349
|
+
* (nothing would ever consult the provider) and
|
|
350
|
+
* `supports("bindings.provider@1")` is false. */
|
|
351
|
+
bindingProviders?: BindingProviderBackend;
|
|
265
352
|
/** Console sink override (tests). */
|
|
266
353
|
console?: Pick<Console, "debug" | "info" | "warn" | "error">;
|
|
267
354
|
/** Shell actions the HOST APP owns (the cockpit's panel placement).
|
|
268
355
|
* When absent, `host.shell` warns and no-ops, and
|
|
269
|
-
* `supports("shell.openPanel@1")` answers false.
|
|
270
|
-
|
|
356
|
+
* `supports("shell.openPanel@1")` answers false. Typed as
|
|
357
|
+
* {@link ShellBackend}, not `ShellSurface`: doors the host app has
|
|
358
|
+
* not adopted yet are OPTIONAL here and each answers its own honest
|
|
359
|
+
* no-backend value through the wrapper. */
|
|
360
|
+
shell?: ShellBackend;
|
|
271
361
|
/** Host-provided panel widgets (W-04): the real code editor lives in
|
|
272
362
|
* the editor's UI package and is injected here. When absent,
|
|
273
363
|
* `host.widgets` is the plain-textarea fallback and
|
|
@@ -316,6 +406,12 @@ interface CreateBundleHostOptions {
|
|
|
316
406
|
* When absent, reads answer empty and writes reject (the honest
|
|
317
407
|
* no-store door). */
|
|
318
408
|
blobStore?: BlobStore;
|
|
409
|
+
/** Host-provided TEXT-CARET reader (C-9). When present,
|
|
410
|
+
* `host.text.caret()` reads the user's active text caret through it
|
|
411
|
+
* and `supports("text.caret@1")` answers true. When absent, `caret()`
|
|
412
|
+
* always answers `null` (the honest no-reader door — a read door like
|
|
413
|
+
* `text.measureString`, no capability gate). */
|
|
414
|
+
textCaret?: TextCaretBackend;
|
|
319
415
|
/** Host-provided CLIPBOARD backend (K-6 / S-14). When present,
|
|
320
416
|
* `host.clipboard.read/write` go through it (capability-gated on
|
|
321
417
|
* `capabilities.clipboard`) and `supports("clipboard@1")` answers true.
|
|
@@ -441,7 +537,7 @@ interface RecordedContribution {
|
|
|
441
537
|
id: string;
|
|
442
538
|
value: ToolContribution | PanelContribution | SchemaPanelContribution | CommandContribution | KeybindingContribution | OverlayContribution | EditContextContribution | ObjectTypeContribution | ImporterContribution | ExporterContribution;
|
|
443
539
|
}
|
|
444
|
-
interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore" | "clipboard" | "secrets"> {
|
|
540
|
+
interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore" | "clipboard" | "secrets" | "bindingProviders"> {
|
|
445
541
|
}
|
|
446
542
|
/** What `createHeadlessHost` resolves to: a real engine-backed host plus
|
|
447
543
|
* the conformance affordances (load an IDML, read the contribution log,
|
|
@@ -481,6 +577,21 @@ interface HeadlessHost {
|
|
|
481
577
|
* Béziers) rather than a flattened polyline. `null` until set, and
|
|
482
578
|
* reset to `null` when the bundle clears its preview. */
|
|
483
579
|
lastToolPreview(): ToolPreviewShape | null;
|
|
580
|
+
/** K-9 — the last LIST a bundle pushed through
|
|
581
|
+
* `host.overlay.setToolPreviews`, or `null` when it cleared / never
|
|
582
|
+
* used the multi-shape door. The harness wires the multi-shape sink,
|
|
583
|
+
* so `supports("overlay.multiPreview@1")` is true headlessly and a
|
|
584
|
+
* bundle's "geometry AND label at once" claim is assertable without a
|
|
585
|
+
* browser. `lastToolPreview()` tracks the list's FIRST shape. */
|
|
586
|
+
lastToolPreviews(): readonly ToolPreviewShape[] | null;
|
|
587
|
+
/** ADR-023 phase A — the SHARED binding-provider registry this host
|
|
588
|
+
* injected. It is the HOST side of the seam, so a conformance test
|
|
589
|
+
* plays the part the editor's shared panel plays in phase C: enter
|
|
590
|
+
* the bundle's edit context, then `readProperty` / `readCollection` /
|
|
591
|
+
* `applyMutation` through here and assert the bundle answered (and
|
|
592
|
+
* that a path it does not declare comes back as a typed refusal to
|
|
593
|
+
* fall through to core). */
|
|
594
|
+
readonly bindingProviders: BindingProviderBackend;
|
|
484
595
|
/** Load an IDML package into the headless document. Resolves to the
|
|
485
596
|
* loaded page ids (or throws on a parse failure). */
|
|
486
597
|
load(idml: Uint8Array): Promise<string[]>;
|
|
@@ -709,5 +820,20 @@ declare function contributeEditContext(host: BundleHost, contribution: EditConte
|
|
|
709
820
|
* matching element enters its `editContextType` instead of group
|
|
710
821
|
* descent. Capability-gated on `contributes.objectTypes`. */
|
|
711
822
|
declare function contributeObjectType(host: BundleHost, contribution: ObjectTypeContribution): Disposable;
|
|
823
|
+
/**
|
|
824
|
+
* ADR-023 phase A — register a BINDING PROVIDER: while `contextType` is
|
|
825
|
+
* the ACTIVE edit context, this bundle resolves what the HOST's own
|
|
826
|
+
* panels bind to (typed paths, named collections, structural ops; reads
|
|
827
|
+
* and writes both), so one host-owned panel serves many content types
|
|
828
|
+
* instead of each plugin minting its own copy.
|
|
829
|
+
*
|
|
830
|
+
* Lives here rather than in its own module deliberately: a provider
|
|
831
|
+
* borrows its edit context's ACTIVATION, so the two doors belong to one
|
|
832
|
+
* lifecycle and reading them side by side is the point. `contextType`
|
|
833
|
+
* must be a type this bundle already passed to
|
|
834
|
+
* {@link contributeEditContext} — the ordering is enforced, not
|
|
835
|
+
* conventional.
|
|
836
|
+
*/
|
|
837
|
+
declare function contributeBindingProvider(host: BundleHost, contextType: string, provider: BindingProvider): BindingProviderHandle;
|
|
712
838
|
|
|
713
|
-
export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, type BundleTrust, CANVAS_WASM_PKG, CLICK_DRAG_THRESHOLD_PX, type ClipboardBackend, type ConsentBackend, type CreateBundleHostOptions, type DataProviderBackend, type DiagnosticsSink, DisposableStore, FALLBACK_WIDGETS, HOST_FEATURES, type HarnessOptions, type HeadlessCanvasWorker, type HeadlessHost, type HeadlessHostHandle, type LoadBundleWasmOptions, type LoadHeadlessEngineOptions, type LoadedBundle, type LoadedBundleWasm, type LoadedEngine, type NativeDocumentBackend, type PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type SecretStoreBackend, type SeededFace, type SpawnedWorker, type StorageBacking, WASM_BUDGETS, WORKER_BUDGETS, type WorkerBackend, beginPageDrag, commitAndSelect, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
|
|
839
|
+
export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BindingProviderBackend, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, type BundleTrust, CANVAS_WASM_PKG, CLICK_DRAG_THRESHOLD_PX, type ClipboardBackend, type ConsentBackend, type CreateBundleHostOptions, type DataProviderBackend, type DiagnosticsSink, DisposableStore, FALLBACK_WIDGETS, HOST_FEATURES, type HarnessOptions, type HeadlessCanvasWorker, type HeadlessHost, type HeadlessHostHandle, type LoadBundleWasmOptions, type LoadHeadlessEngineOptions, type LoadedBundle, type LoadedBundleWasm, type LoadedEngine, type NativeDocumentBackend, type PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type RegisteredBindingProvider, type SecretStoreBackend, type SeededFace, type ShellBackend, type SpawnedWorker, type StorageBacking, type TextCaretBackend, WASM_BUDGETS, WORKER_BUDGETS, type WorkerBackend, beginPageDrag, commitAndSelect, contributeBindingProvider, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBindingProviderRegistry, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
1
5
|
// src/define-bundle.ts
|
|
2
6
|
function defineBundle(bundle) {
|
|
3
7
|
return bundle;
|
|
@@ -5,8 +9,10 @@ function defineBundle(bundle) {
|
|
|
5
9
|
|
|
6
10
|
// src/disposables.ts
|
|
7
11
|
var DisposableStore = class {
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
constructor() {
|
|
13
|
+
__publicField(this, "items", []);
|
|
14
|
+
__publicField(this, "disposed", false);
|
|
15
|
+
}
|
|
10
16
|
get isDisposed() {
|
|
11
17
|
return this.disposed;
|
|
12
18
|
}
|
|
@@ -42,19 +48,31 @@ function toDisposable(fn) {
|
|
|
42
48
|
};
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
// src/
|
|
51
|
+
// src/react-optional.ts
|
|
46
52
|
var createElement = null;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
var state = "pending";
|
|
54
|
+
void import("react").then(
|
|
55
|
+
(react) => {
|
|
56
|
+
createElement = react.createElement;
|
|
57
|
+
state = "ready";
|
|
58
|
+
},
|
|
59
|
+
() => {
|
|
60
|
+
state = "absent";
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
function requireCreateElement(absent) {
|
|
64
|
+
if (createElement) return createElement;
|
|
65
|
+
throw new Error(
|
|
66
|
+
state === "pending" ? `${absent} (React had not finished resolving when this rendered.)` : absent
|
|
67
|
+
);
|
|
50
68
|
}
|
|
69
|
+
|
|
70
|
+
// src/widgets-fallback.tsx
|
|
51
71
|
function TextareaCodeEditor(props) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
return createElement("textarea", {
|
|
72
|
+
const createElement2 = requireCreateElement(
|
|
73
|
+
"plugin-sdk: host.widgets.CodeEditor fell back to the built-in textarea, but React is not installed. Install react, or inject a widget catalog via createBundleHost({ widgets })."
|
|
74
|
+
);
|
|
75
|
+
return createElement2("textarea", {
|
|
58
76
|
value: props.value,
|
|
59
77
|
readOnly: props.readOnly,
|
|
60
78
|
spellCheck: false,
|
|
@@ -80,18 +98,10 @@ var FALLBACK_WIDGETS = {
|
|
|
80
98
|
};
|
|
81
99
|
|
|
82
100
|
// src/schema-panel.tsx
|
|
83
|
-
var createElement2 = null;
|
|
84
|
-
try {
|
|
85
|
-
({ createElement: createElement2 } = await import("react"));
|
|
86
|
-
} catch {
|
|
87
|
-
}
|
|
88
101
|
function h() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
return createElement2;
|
|
102
|
+
return requireCreateElement(
|
|
103
|
+
"plugin-sdk: rendering a schema panel needs React, which is not installed."
|
|
104
|
+
);
|
|
95
105
|
}
|
|
96
106
|
function resolveGate(gate, lookup) {
|
|
97
107
|
if (gate === void 0) return true;
|
|
@@ -139,6 +149,14 @@ var HOST_FEATURES = [
|
|
|
139
149
|
"contribute.overlay@1",
|
|
140
150
|
"contribute.editContext@1",
|
|
141
151
|
"contribute.objectType@1",
|
|
152
|
+
// ADR-023 phase A — the binding-provider DOOR exists in this SDK (a
|
|
153
|
+
// bundle may call `host.contribute.bindingProvider` without a
|
|
154
|
+
// TypeError). STATIC, and it deliberately does NOT claim that anything
|
|
155
|
+
// will consult the provider — that is the separate, DYNAMIC
|
|
156
|
+
// `bindings.provider@1`, which is true only when the host wired a
|
|
157
|
+
// shared registry. Same static-door / dynamic-backing split as
|
|
158
|
+
// `contribute.schemaPanel@1` + `schemaPanel.renderer@1`.
|
|
159
|
+
"contribute.bindingProvider@1",
|
|
142
160
|
"contribute.importer@1",
|
|
143
161
|
"contribute.exporter@1",
|
|
144
162
|
"document.mutate@1",
|
|
@@ -152,6 +170,12 @@ var HOST_FEATURES = [
|
|
|
152
170
|
"document.elementProperties@1",
|
|
153
171
|
"document.placeholders@1",
|
|
154
172
|
"document.tree@1",
|
|
173
|
+
// C-16 — the per-element parentage read. STATIC like `document.tree@1`
|
|
174
|
+
// and for the same reason: it is served ENTIRELY by this SDK from the
|
|
175
|
+
// scene tree the existing `requestSceneTree` wire query already
|
|
176
|
+
// returns, so there is no engine version to gate on (no core door was
|
|
177
|
+
// needed — DESIGN.md §4.3d).
|
|
178
|
+
"document.parentOf@1",
|
|
155
179
|
"document.onDidChange@1",
|
|
156
180
|
"document.getMetadata@1",
|
|
157
181
|
"document.setMetadata@1",
|
|
@@ -160,6 +184,13 @@ var HOST_FEATURES = [
|
|
|
160
184
|
"overlay.toolPreview@1",
|
|
161
185
|
"storage@1",
|
|
162
186
|
"diagnostics@1",
|
|
187
|
+
// The overlay TEXT primitive (RFI "overlay carries shapes only" —
|
|
188
|
+
// consumer: paged.draw's Measure readout). A pure pass-through on the
|
|
189
|
+
// same tool-preview channel; the renderer that knows the variant ships
|
|
190
|
+
// in the same host release that bumps this SDK, so the flag is static
|
|
191
|
+
// like the channel's own `overlay.toolPreview@1`. The headless harness
|
|
192
|
+
// records the shape verbatim.
|
|
193
|
+
"overlay.text@1",
|
|
163
194
|
// C-5 / I-04 (core v42): the placed-image bytes read is engine-served
|
|
164
195
|
// through the wire (no injected source), so it is unconditionally
|
|
165
196
|
// implemented at the pinned canvas-wasm — unlike assets.fonts@1, which
|
|
@@ -168,7 +199,17 @@ var HOST_FEATURES = [
|
|
|
168
199
|
// .paged container parts door (file-format.md) — the engine (canvas-wasm
|
|
169
200
|
// v51) is the writer, so it is live whenever a bundle runs (the worker
|
|
170
201
|
// handshake guarantees a v51-protocol worker).
|
|
171
|
-
"storage.parts@1"
|
|
202
|
+
"storage.parts@1",
|
|
203
|
+
// B-22 (protocol v57) — the planar-region read door. STATIC, and the
|
|
204
|
+
// flag means exactly one thing: this SDK's document surface IMPLEMENTS
|
|
205
|
+
// `planarRegions` and forwards it (retiring the `host.editor.client`
|
|
206
|
+
// escape hatch). It does NOT claim the host's engine carries v57 — the
|
|
207
|
+
// vendored wire is still 0.51, so that answer is per-call: a pre-v57
|
|
208
|
+
// engine (or any host that doesn't answer the query) comes back as
|
|
209
|
+
// `{ found: false, reason }`, never a fake empty face list. Same
|
|
210
|
+
// honesty split as the protocol-ahead mutations (mutations.ts): the
|
|
211
|
+
// engine-level gate is the worker handshake, not `supports()`.
|
|
212
|
+
"document.planarRegions@1"
|
|
172
213
|
];
|
|
173
214
|
var PluginApiNotImplemented = class extends Error {
|
|
174
215
|
constructor(member, pointer) {
|
|
@@ -179,15 +220,15 @@ var PluginApiNotImplemented = class extends Error {
|
|
|
179
220
|
}
|
|
180
221
|
};
|
|
181
222
|
var PluginCapabilityError = class extends Error {
|
|
182
|
-
/** The host door that was called (e.g. `"contribute.tool"`). */
|
|
183
|
-
door;
|
|
184
|
-
/** The manifest declaration that would authorize it (e.g.
|
|
185
|
-
* `'contributes.tools[] must include "media.paged.web.tool.pen"'`). */
|
|
186
|
-
missingDeclaration;
|
|
187
223
|
constructor(door, missingDeclaration, pluginId) {
|
|
188
224
|
super(
|
|
189
225
|
`plugin-api: ${pluginId} called ${door} without declaring it \u2014 ${missingDeclaration}. Manifest capabilities are ENFORCED (trust-line W0.11): declare the use or the host refuses it.`
|
|
190
226
|
);
|
|
227
|
+
/** The host door that was called (e.g. `"contribute.tool"`). */
|
|
228
|
+
__publicField(this, "door");
|
|
229
|
+
/** The manifest declaration that would authorize it (e.g.
|
|
230
|
+
* `'contributes.tools[] must include "media.paged.web.tool.pen"'`). */
|
|
231
|
+
__publicField(this, "missingDeclaration");
|
|
191
232
|
this.name = "PluginCapabilityError";
|
|
192
233
|
this.door = door;
|
|
193
234
|
this.missingDeclaration = missingDeclaration;
|
|
@@ -280,6 +321,144 @@ function createDataProviderRegistry() {
|
|
|
280
321
|
}
|
|
281
322
|
};
|
|
282
323
|
}
|
|
324
|
+
function createBindingProviderRegistry() {
|
|
325
|
+
const providers = /* @__PURE__ */ new Map();
|
|
326
|
+
const stack = [];
|
|
327
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
328
|
+
const key = (plugin, contextType) => `${plugin}\0${contextType}`;
|
|
329
|
+
const emit = () => {
|
|
330
|
+
for (const l of [...listeners]) l();
|
|
331
|
+
};
|
|
332
|
+
const active = () => {
|
|
333
|
+
const out = [];
|
|
334
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
335
|
+
const frame = stack[i];
|
|
336
|
+
for (const entry of providers.get(key(frame.plugin, frame.contextType)) ?? []) {
|
|
337
|
+
out.push({ frame, entry });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return out;
|
|
341
|
+
};
|
|
342
|
+
const declaresPath = (e, path) => e.provider.provides.paths?.includes(path) ?? false;
|
|
343
|
+
const declaresWritablePath = (e, path) => {
|
|
344
|
+
const w = e.provider.provides.writablePaths;
|
|
345
|
+
if (w === void 0) return declaresPath(e, path);
|
|
346
|
+
return declaresPath(e, path) && w.includes(path);
|
|
347
|
+
};
|
|
348
|
+
const declaresCollection = (e, collection) => e.provider.provides.collections?.includes(collection) ?? false;
|
|
349
|
+
return {
|
|
350
|
+
register(entry) {
|
|
351
|
+
const k = key(entry.plugin, entry.contextType);
|
|
352
|
+
const list = providers.get(k) ?? [];
|
|
353
|
+
list.push(entry);
|
|
354
|
+
providers.set(k, list);
|
|
355
|
+
emit();
|
|
356
|
+
return toDisposable(() => {
|
|
357
|
+
const cur = providers.get(k);
|
|
358
|
+
if (!cur) return;
|
|
359
|
+
const i = cur.indexOf(entry);
|
|
360
|
+
if (i >= 0) cur.splice(i, 1);
|
|
361
|
+
if (cur.length === 0) providers.delete(k);
|
|
362
|
+
emit();
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
setContextActive(plugin, contextType, elementId, isActive) {
|
|
366
|
+
if (isActive) {
|
|
367
|
+
stack.push({ plugin, contextType, elementId });
|
|
368
|
+
} else {
|
|
369
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
370
|
+
if (stack[i].plugin === plugin && stack[i].contextType === contextType) {
|
|
371
|
+
stack.splice(i, 1);
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
emit();
|
|
377
|
+
},
|
|
378
|
+
notifyChanged() {
|
|
379
|
+
emit();
|
|
380
|
+
},
|
|
381
|
+
activeProviders() {
|
|
382
|
+
return active().map(({ frame, entry }) => ({
|
|
383
|
+
plugin: frame.plugin,
|
|
384
|
+
contextType: frame.contextType,
|
|
385
|
+
elementId: frame.elementId,
|
|
386
|
+
provides: entry.provider.provides
|
|
387
|
+
}));
|
|
388
|
+
},
|
|
389
|
+
async readProperty(request) {
|
|
390
|
+
const rowScoped = request.target.kind === "row" ? request.target.collection : null;
|
|
391
|
+
for (const { entry } of active()) {
|
|
392
|
+
if (rowScoped !== null && !declaresCollection(entry, rowScoped)) continue;
|
|
393
|
+
if (!declaresPath(entry, request.path)) {
|
|
394
|
+
if (rowScoped === null) continue;
|
|
395
|
+
return {
|
|
396
|
+
resolved: true,
|
|
397
|
+
provider: entry.plugin,
|
|
398
|
+
read: {
|
|
399
|
+
kind: "absent",
|
|
400
|
+
reason: `${entry.plugin} owns collection "${rowScoped}" but does not serve "${request.path}" for its rows`
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (!entry.provider.readProperty) continue;
|
|
405
|
+
const read = await entry.provider.readProperty(request);
|
|
406
|
+
if (read.kind === "decline") continue;
|
|
407
|
+
return { resolved: true, provider: entry.plugin, read };
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
resolved: false,
|
|
411
|
+
reason: `no active binding provider claimed "${request.path}"`
|
|
412
|
+
};
|
|
413
|
+
},
|
|
414
|
+
async writeProperty(request) {
|
|
415
|
+
const rowScoped = request.target.kind === "row" ? request.target.collection : null;
|
|
416
|
+
for (const { entry } of active()) {
|
|
417
|
+
if (rowScoped !== null && !declaresCollection(entry, rowScoped)) continue;
|
|
418
|
+
if (!declaresWritablePath(entry, request.path)) continue;
|
|
419
|
+
if (!entry.provider.writeProperty) continue;
|
|
420
|
+
const write = await entry.provider.writeProperty(request);
|
|
421
|
+
if (write.kind === "decline") continue;
|
|
422
|
+
return { handled: true, provider: entry.plugin, outcome: write.outcome };
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
handled: false,
|
|
426
|
+
reason: `no active binding provider claimed a write to "${request.path}"`
|
|
427
|
+
};
|
|
428
|
+
},
|
|
429
|
+
async readCollection(request) {
|
|
430
|
+
for (const { entry } of active()) {
|
|
431
|
+
if (!declaresCollection(entry, request.collection)) continue;
|
|
432
|
+
if (!entry.provider.readCollection) continue;
|
|
433
|
+
const read = await entry.provider.readCollection(request);
|
|
434
|
+
if (read.kind === "decline") continue;
|
|
435
|
+
return { resolved: true, provider: entry.plugin, rows: read.rows };
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
resolved: false,
|
|
439
|
+
reason: `no active binding provider claimed collection "${request.collection}"`
|
|
440
|
+
};
|
|
441
|
+
},
|
|
442
|
+
async applyMutation(mutation) {
|
|
443
|
+
for (const { entry } of active()) {
|
|
444
|
+
if (!(entry.provider.provides.ops?.includes(mutation.op) ?? false))
|
|
445
|
+
continue;
|
|
446
|
+
if (!entry.provider.applyMutation) continue;
|
|
447
|
+
const write = await entry.provider.applyMutation(mutation);
|
|
448
|
+
if (write.kind === "decline") continue;
|
|
449
|
+
return { handled: true, provider: entry.plugin, outcome: write.outcome };
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
handled: false,
|
|
453
|
+
reason: `no active binding provider claimed the "${mutation.op}" op`
|
|
454
|
+
};
|
|
455
|
+
},
|
|
456
|
+
onDidChange(listener) {
|
|
457
|
+
listeners.add(listener);
|
|
458
|
+
return toDisposable(() => listeners.delete(listener));
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
283
462
|
function createBundleHost(getEditor, manifest, options) {
|
|
284
463
|
const store = new DisposableStore();
|
|
285
464
|
const sink = options?.console ?? console;
|
|
@@ -333,6 +512,43 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
333
512
|
}
|
|
334
513
|
return reason;
|
|
335
514
|
};
|
|
515
|
+
const registeredContexts = /* @__PURE__ */ new Set();
|
|
516
|
+
const bindingRegistry = options?.bindingProviders;
|
|
517
|
+
const providerLaneGaps = (p) => {
|
|
518
|
+
const gaps = [];
|
|
519
|
+
if ((p.provides.paths?.length ?? 0) > 0 && !p.readProperty)
|
|
520
|
+
gaps.push("provides.paths declared without readProperty()");
|
|
521
|
+
if ((p.provides.collections?.length ?? 0) > 0 && !p.readCollection)
|
|
522
|
+
gaps.push("provides.collections declared without readCollection()");
|
|
523
|
+
if ((p.provides.ops?.length ?? 0) > 0 && !p.applyMutation)
|
|
524
|
+
gaps.push("provides.ops declared without applyMutation()");
|
|
525
|
+
const dupes = (label, xs) => {
|
|
526
|
+
const seen = /* @__PURE__ */ new Set();
|
|
527
|
+
for (const x of xs ?? []) {
|
|
528
|
+
if (seen.has(x)) gaps.push(`provides.${label} lists "${x}" twice`);
|
|
529
|
+
seen.add(x);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
dupes("paths", p.provides.paths);
|
|
533
|
+
dupes("collections", p.provides.collections);
|
|
534
|
+
dupes("ops", p.provides.ops);
|
|
535
|
+
dupes("writablePaths", p.provides.writablePaths);
|
|
536
|
+
for (const w of p.provides.writablePaths ?? []) {
|
|
537
|
+
if (!(p.provides.paths ?? []).includes(w)) {
|
|
538
|
+
gaps.push(
|
|
539
|
+
`provides.writablePaths lists "${w}", which is not in provides.paths`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (p.provides.writablePaths !== void 0 && !p.writeProperty) {
|
|
544
|
+
if (p.provides.writablePaths.length > 0)
|
|
545
|
+
gaps.push("provides.writablePaths declared without writeProperty()");
|
|
546
|
+
}
|
|
547
|
+
if ((p.provides.paths?.length ?? 0) === 0 && (p.provides.collections?.length ?? 0) === 0 && (p.provides.ops?.length ?? 0) === 0) {
|
|
548
|
+
gaps.push("provides declares nothing \u2014 the provider could never answer");
|
|
549
|
+
}
|
|
550
|
+
return gaps;
|
|
551
|
+
};
|
|
336
552
|
const contribute = {
|
|
337
553
|
tool(c) {
|
|
338
554
|
assertNamespaced(c.id, "tool");
|
|
@@ -425,9 +641,30 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
425
641
|
"contribute.editContext",
|
|
426
642
|
`contributes.editContexts[] must declare { type: "${c.type}" }`
|
|
427
643
|
);
|
|
644
|
+
registeredContexts.add(c.type);
|
|
428
645
|
const stamped = {
|
|
429
646
|
...c,
|
|
430
|
-
metadataKey: metadataKey(manifest)
|
|
647
|
+
metadataKey: metadataKey(manifest),
|
|
648
|
+
...bindingRegistry ? {
|
|
649
|
+
onEnter: (ctx) => {
|
|
650
|
+
bindingRegistry.setContextActive(
|
|
651
|
+
manifest.id,
|
|
652
|
+
c.type,
|
|
653
|
+
typeof ctx.id?.id === "string" ? ctx.id.id : null,
|
|
654
|
+
true
|
|
655
|
+
);
|
|
656
|
+
c.onEnter?.(ctx);
|
|
657
|
+
},
|
|
658
|
+
onExit: (ctx) => {
|
|
659
|
+
c.onExit?.(ctx);
|
|
660
|
+
bindingRegistry.setContextActive(
|
|
661
|
+
manifest.id,
|
|
662
|
+
c.type,
|
|
663
|
+
typeof ctx.id?.id === "string" ? ctx.id.id : null,
|
|
664
|
+
false
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
} : {}
|
|
431
668
|
};
|
|
432
669
|
const reg = getEditor().registries.editContexts;
|
|
433
670
|
const recorded = options?.onEditContextRegistered?.(stamped);
|
|
@@ -473,6 +710,69 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
473
710
|
return store.add(toDisposable(() => {
|
|
474
711
|
}));
|
|
475
712
|
},
|
|
713
|
+
// ADR-023 phase A — the BINDING-PROVIDER door. Three gates, in
|
|
714
|
+
// order, all loud:
|
|
715
|
+
// 1. the CAPABILITY gate, borrowed whole from the edit context the
|
|
716
|
+
// provider attaches to (`contributes.editContexts[]` must
|
|
717
|
+
// declare the type). No new manifest field and no new
|
|
718
|
+
// capability: the authority a provider exercises IS the
|
|
719
|
+
// authority the active context already holds over the selection
|
|
720
|
+
// it owns, so a second declaration would be ceremony, not a
|
|
721
|
+
// gate. (Precedent: `EditContextContribution.toolIds` swaps the
|
|
722
|
+
// whole tool rail and is not separately declared either.)
|
|
723
|
+
// 2. the ORDERING rule — the context must already be registered by
|
|
724
|
+
// THIS bundle. A provider on an unregistered type can never
|
|
725
|
+
// activate; accepting it would be the fake-interactive failure
|
|
726
|
+
// the platform refuses.
|
|
727
|
+
// 3. the DECLARATION shape — a lane declared without its callback,
|
|
728
|
+
// a repeated entry, or an empty `provides` is an authoring bug
|
|
729
|
+
// that would make the provider silently unreachable.
|
|
730
|
+
// Then the honest no-backend door: no registry wired → warn + an
|
|
731
|
+
// inert handle (probe `supports("bindings.provider@1")`).
|
|
732
|
+
bindingProvider(contextType, provider) {
|
|
733
|
+
requireDeclared(
|
|
734
|
+
declaresType(declared?.editContexts, contextType),
|
|
735
|
+
"contribute.bindingProvider",
|
|
736
|
+
`contributes.editContexts[] must declare { type: "${contextType}" } (a binding provider borrows its edit context's declaration)`
|
|
737
|
+
);
|
|
738
|
+
if (!registeredContexts.has(contextType)) {
|
|
739
|
+
throw new Error(
|
|
740
|
+
`plugin-api: contribute.bindingProvider("${contextType}") \u2014 no such edit context is registered by ${manifest.id}. A provider BORROWS its context's activation (ADR-023): call contribute.editContext({ type: "${contextType}", \u2026 }) first, or the provider could never be consulted.`
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
const gaps = providerLaneGaps(provider);
|
|
744
|
+
if (gaps.length > 0) {
|
|
745
|
+
throw new Error(
|
|
746
|
+
`plugin-api: contribute.bindingProvider("${contextType}") \u2014 ${gaps.join("; ")}. A declared lane the registry can never call is a silently dead provider, so the declaration is refused.`
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
if (!bindingRegistry) {
|
|
750
|
+
log.warn(
|
|
751
|
+
`contribute.bindingProvider("${contextType}") registered nothing \u2014 the host wired no binding-provider registry, so no panel will ever consult it (probe supports("bindings.provider@1"))`
|
|
752
|
+
);
|
|
753
|
+
return store.add({
|
|
754
|
+
invalidate() {
|
|
755
|
+
},
|
|
756
|
+
dispose() {
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
const entry = {
|
|
761
|
+
plugin: manifest.id,
|
|
762
|
+
contextType,
|
|
763
|
+
provider
|
|
764
|
+
};
|
|
765
|
+
const reg = store.add(bindingRegistry.register(entry));
|
|
766
|
+
const handle = {
|
|
767
|
+
invalidate() {
|
|
768
|
+
bindingRegistry.notifyChanged(manifest.id, contextType);
|
|
769
|
+
},
|
|
770
|
+
dispose() {
|
|
771
|
+
reg.dispose();
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
return handle;
|
|
775
|
+
},
|
|
476
776
|
// K-2 / S-06 — document IO. Mirrors `command`: namespaced id the
|
|
477
777
|
// manifest must list, routed to the shell registry. The registry is
|
|
478
778
|
// OPTIONAL on the handle (a host that hasn't wired it stays
|
|
@@ -571,6 +871,24 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
571
871
|
door,
|
|
572
872
|
"capabilities.document.openNative must be declared"
|
|
573
873
|
);
|
|
874
|
+
let parentIndex = null;
|
|
875
|
+
let parentIndexWired = false;
|
|
876
|
+
const buildParentIndex = (roots) => {
|
|
877
|
+
const index = /* @__PURE__ */ new Map();
|
|
878
|
+
const walk = (nodes, parent) => {
|
|
879
|
+
for (const node of nodes) {
|
|
880
|
+
const id = node.id ?? null;
|
|
881
|
+
if (id && typeof id.id === "string") {
|
|
882
|
+
if (parent) index.set(id.id, parent);
|
|
883
|
+
walk(node.children ?? [], id);
|
|
884
|
+
} else {
|
|
885
|
+
walk(node.children ?? [], parent);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
walk(roots, null);
|
|
890
|
+
return index;
|
|
891
|
+
};
|
|
574
892
|
const document = {
|
|
575
893
|
async mutate(mutation) {
|
|
576
894
|
const denied = denyWrite(
|
|
@@ -674,6 +992,30 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
674
992
|
return null;
|
|
675
993
|
}
|
|
676
994
|
},
|
|
995
|
+
async planarRegions(elementIds, point) {
|
|
996
|
+
requireDocRead("document.planarRegions");
|
|
997
|
+
const refusal = (reason) => ({
|
|
998
|
+
found: false,
|
|
999
|
+
faces: [],
|
|
1000
|
+
inputCount: elementIds.length,
|
|
1001
|
+
complete: false,
|
|
1002
|
+
reason
|
|
1003
|
+
});
|
|
1004
|
+
try {
|
|
1005
|
+
const reply = await getEditor().client.send({
|
|
1006
|
+
kind: "requestPlanarRegions",
|
|
1007
|
+
payload: point ? { elementIds, point } : { elementIds }
|
|
1008
|
+
});
|
|
1009
|
+
if (reply.kind !== "planarRegions") {
|
|
1010
|
+
return refusal(
|
|
1011
|
+
"this host did not answer requestPlanarRegions \u2014 its engine predates the planar-region read door (protocol v57)"
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
return reply.payload?.result ?? refusal("the engine answered planarRegions with no result");
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
return refusal(`the region read door failed: ${String(error)}`);
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
677
1019
|
async tree() {
|
|
678
1020
|
requireDocRead("document.tree");
|
|
679
1021
|
const reply = await getEditor().client.send({
|
|
@@ -681,6 +1023,30 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
681
1023
|
});
|
|
682
1024
|
return reply.kind === "sceneTree" ? reply.payload.roots : [];
|
|
683
1025
|
},
|
|
1026
|
+
async parentOf(id) {
|
|
1027
|
+
requireDocRead("document.parentOf");
|
|
1028
|
+
if (typeof id.id !== "string") return null;
|
|
1029
|
+
if (!parentIndexWired) {
|
|
1030
|
+
parentIndexWired = true;
|
|
1031
|
+
const off = getEditor().client.subscribe((msg) => {
|
|
1032
|
+
if (msg.kind === "mutationApplied" || msg.kind === "undoApplied" || msg.kind === "redoApplied" || // A newly opened document invalidates everything; the kind
|
|
1033
|
+
// is matched defensively (not every host posts it).
|
|
1034
|
+
msg.kind === "documentLoaded") {
|
|
1035
|
+
parentIndex = null;
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
store.add(toDisposable(off));
|
|
1039
|
+
}
|
|
1040
|
+
if (!parentIndex) {
|
|
1041
|
+
const reply = await getEditor().client.send({
|
|
1042
|
+
kind: "requestSceneTree"
|
|
1043
|
+
});
|
|
1044
|
+
parentIndex = buildParentIndex(
|
|
1045
|
+
reply.kind === "sceneTree" ? reply.payload.roots : []
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
return parentIndex.get(id.id) ?? null;
|
|
1049
|
+
},
|
|
684
1050
|
async getMetadata(id) {
|
|
685
1051
|
requireDocRead("document.getMetadata");
|
|
686
1052
|
const key = metadataKey(manifest);
|
|
@@ -788,6 +1154,15 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
788
1154
|
ascender: sizePt * 0.8,
|
|
789
1155
|
descender: -sizePt * 0.2
|
|
790
1156
|
};
|
|
1157
|
+
},
|
|
1158
|
+
// C-9 — the caret read door. A read of EDITOR state (the
|
|
1159
|
+
// text-editing caret), injected by the host app like clipboard /
|
|
1160
|
+
// consent; no capability gate (a read door, like measureString /
|
|
1161
|
+
// viewport). No backend → the honest null (probe
|
|
1162
|
+
// supports("text.caret@1") to distinguish "no caret" from
|
|
1163
|
+
// "no reader").
|
|
1164
|
+
caret() {
|
|
1165
|
+
return options?.textCaret?.read() ?? null;
|
|
791
1166
|
}
|
|
792
1167
|
};
|
|
793
1168
|
const overlay = {
|
|
@@ -798,6 +1173,27 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
798
1173
|
'capabilities.rendering must include "overlay"'
|
|
799
1174
|
);
|
|
800
1175
|
getEditor().overlaySignals.setToolPreview(shape);
|
|
1176
|
+
},
|
|
1177
|
+
// K-9 — the MULTI-shape write on the SAME slot + the SAME gate. When
|
|
1178
|
+
// the host wired a multi-shape sink we hand the list over verbatim
|
|
1179
|
+
// (the adapter never re-shapes a preview — the renderer owns layout).
|
|
1180
|
+
// When it did not, the honest degradation is the pre-K-9 behaviour a
|
|
1181
|
+
// bundle used to hand-code: the FIRST shape through the single slot,
|
|
1182
|
+
// never a throw. `null`/empty clears, so a bundle's teardown path is
|
|
1183
|
+
// the same either way.
|
|
1184
|
+
setToolPreviews(shapes) {
|
|
1185
|
+
requireDeclared(
|
|
1186
|
+
hasRendering("overlay"),
|
|
1187
|
+
"overlay.setToolPreviews",
|
|
1188
|
+
'capabilities.rendering must include "overlay"'
|
|
1189
|
+
);
|
|
1190
|
+
const signals = getEditor().overlaySignals;
|
|
1191
|
+
const list = shapes && shapes.length > 0 ? shapes : null;
|
|
1192
|
+
if (signals.setToolPreviews) {
|
|
1193
|
+
signals.setToolPreviews(list);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
signals.setToolPreview(list ? list[0] : null);
|
|
801
1197
|
}
|
|
802
1198
|
};
|
|
803
1199
|
const backing = options?.storage ?? defaultStorageBacking();
|
|
@@ -966,19 +1362,40 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
966
1362
|
return store.add(toDisposable(() => bindingListeners.delete(listener)));
|
|
967
1363
|
}
|
|
968
1364
|
};
|
|
969
|
-
const
|
|
1365
|
+
const shellBackend = options?.shell;
|
|
1366
|
+
const shell = {
|
|
970
1367
|
openPanel(panelId) {
|
|
1368
|
+
if (shellBackend) {
|
|
1369
|
+
shellBackend.openPanel(panelId);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
971
1372
|
log.warn(
|
|
972
1373
|
`shell.openPanel("${panelId}") ignored \u2014 the host app provided no shell actions (probe with supports("shell.openPanel@1"))`
|
|
973
1374
|
);
|
|
974
1375
|
},
|
|
975
|
-
closePanel() {
|
|
1376
|
+
closePanel(panelId) {
|
|
1377
|
+
shellBackend?.closePanel(panelId);
|
|
976
1378
|
},
|
|
977
|
-
async pickFile() {
|
|
1379
|
+
async pickFile(pickOptions) {
|
|
1380
|
+
if (shellBackend) return shellBackend.pickFile(pickOptions);
|
|
978
1381
|
log.warn(
|
|
979
1382
|
`shell.pickFile() ignored \u2014 the host app provided no shell actions (probe with supports("shell.pickFile@1"))`
|
|
980
1383
|
);
|
|
981
1384
|
return [];
|
|
1385
|
+
},
|
|
1386
|
+
// K-10 — the write half. No saver wired (headless, or a host app
|
|
1387
|
+
// older than the door) answers FALSE, never a throw: "the bytes were
|
|
1388
|
+
// not delivered" is a result a bundle can act on (keep the Export
|
|
1389
|
+
// Center path, disable the menu item), which a rejection would turn
|
|
1390
|
+
// into an unhandled promise in a click handler.
|
|
1391
|
+
async saveFile(saveOptions) {
|
|
1392
|
+
if (shellBackend?.saveFile) {
|
|
1393
|
+
return shellBackend.saveFile(saveOptions);
|
|
1394
|
+
}
|
|
1395
|
+
log.warn(
|
|
1396
|
+
`shell.saveFile("${saveOptions.suggestedName}") ignored \u2014 the host app wired no file saver (probe with supports("shell.saveFile@1"))`
|
|
1397
|
+
);
|
|
1398
|
+
return false;
|
|
982
1399
|
}
|
|
983
1400
|
};
|
|
984
1401
|
const widgets = options?.widgets ?? FALLBACK_WIDGETS;
|
|
@@ -1437,12 +1854,18 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1437
1854
|
if (getEditor().sceneLayers) {
|
|
1438
1855
|
featureSet.add("rendering.sceneLayer@1");
|
|
1439
1856
|
}
|
|
1857
|
+
if (typeof getEditor().overlaySignals?.setToolPreviews === "function") {
|
|
1858
|
+
featureSet.add("overlay.multiPreview@1");
|
|
1859
|
+
}
|
|
1440
1860
|
if (getEditor().images) {
|
|
1441
1861
|
featureSet.add("rendering.resourceProvider@1");
|
|
1442
1862
|
}
|
|
1443
1863
|
if (options?.shell) {
|
|
1444
1864
|
featureSet.add("shell.openPanel@1");
|
|
1445
1865
|
featureSet.add("shell.pickFile@1");
|
|
1866
|
+
if (typeof options.shell.saveFile === "function") {
|
|
1867
|
+
featureSet.add("shell.saveFile@1");
|
|
1868
|
+
}
|
|
1446
1869
|
}
|
|
1447
1870
|
if (options?.widgets) {
|
|
1448
1871
|
featureSet.add("widgets.codeEditor@1");
|
|
@@ -1462,12 +1885,18 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1462
1885
|
if (options?.dataProviders) {
|
|
1463
1886
|
featureSet.add("dataProviders@1");
|
|
1464
1887
|
}
|
|
1888
|
+
if (options?.bindingProviders) {
|
|
1889
|
+
featureSet.add("bindings.provider@1");
|
|
1890
|
+
}
|
|
1465
1891
|
if (options?.blobStore) {
|
|
1466
1892
|
featureSet.add("storage.blob@1");
|
|
1467
1893
|
}
|
|
1468
1894
|
if (options?.clipboard) {
|
|
1469
1895
|
featureSet.add("clipboard@1");
|
|
1470
1896
|
}
|
|
1897
|
+
if (options?.textCaret) {
|
|
1898
|
+
featureSet.add("text.caret@1");
|
|
1899
|
+
}
|
|
1471
1900
|
if (options?.nativeDocument) {
|
|
1472
1901
|
featureSet.add("document.readNative@1");
|
|
1473
1902
|
featureSet.add("document.openNative@1");
|
|
@@ -1717,7 +2146,7 @@ function inMemorySecretStore() {
|
|
|
1717
2146
|
};
|
|
1718
2147
|
}
|
|
1719
2148
|
var seqCounter = 1;
|
|
1720
|
-
function makeEngineEditor(worker, recorder, onToolPreview) {
|
|
2149
|
+
function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews) {
|
|
1721
2150
|
const protocol = worker.protocolVersion;
|
|
1722
2151
|
const listeners = /* @__PURE__ */ new Set();
|
|
1723
2152
|
const fanOut = (reply) => {
|
|
@@ -1765,6 +2194,7 @@ function makeEngineEditor(worker, recorder, onToolPreview) {
|
|
|
1765
2194
|
};
|
|
1766
2195
|
let elementSelection = [];
|
|
1767
2196
|
let toolPreview = null;
|
|
2197
|
+
let toolPreviews = null;
|
|
1768
2198
|
const client = {
|
|
1769
2199
|
async mutate(mutation) {
|
|
1770
2200
|
const reply = dispatch("mutate", mutation);
|
|
@@ -1854,6 +2284,18 @@ function makeEngineEditor(worker, recorder, onToolPreview) {
|
|
|
1854
2284
|
setToolPreview(value) {
|
|
1855
2285
|
toolPreview = value;
|
|
1856
2286
|
onToolPreview(toolPreview);
|
|
2287
|
+
},
|
|
2288
|
+
// K-9 — the MULTI-shape sink. Wiring it headlessly is what makes
|
|
2289
|
+
// `supports("overlay.multiPreview@1")` true in the harness, so a
|
|
2290
|
+
// bundle's "geometry AND label at once" claim is conformance-
|
|
2291
|
+
// assertable without a browser. One slot: the list also updates
|
|
2292
|
+
// `lastToolPreview()` (to its first shape) so an older assertion
|
|
2293
|
+
// still reads something true rather than a stale value.
|
|
2294
|
+
setToolPreviews(value) {
|
|
2295
|
+
toolPreviews = value && value.length > 0 ? value : null;
|
|
2296
|
+
toolPreview = toolPreviews ? toolPreviews[0] : null;
|
|
2297
|
+
onToolPreviews(toolPreviews);
|
|
2298
|
+
onToolPreview(toolPreview);
|
|
1857
2299
|
}
|
|
1858
2300
|
},
|
|
1859
2301
|
// No tool spine + no content caret headlessly — both are inert
|
|
@@ -1871,21 +2313,31 @@ async function createHeadlessHost(options = {}) {
|
|
|
1871
2313
|
const worker = engine.worker;
|
|
1872
2314
|
const contributions = [];
|
|
1873
2315
|
let lastPreview = null;
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2316
|
+
let lastPreviews = null;
|
|
2317
|
+
const editor = makeEngineEditor(
|
|
2318
|
+
worker,
|
|
2319
|
+
contributions,
|
|
2320
|
+
(value) => {
|
|
2321
|
+
lastPreview = value;
|
|
2322
|
+
},
|
|
2323
|
+
(value) => {
|
|
2324
|
+
lastPreviews = value;
|
|
2325
|
+
}
|
|
2326
|
+
);
|
|
1877
2327
|
let active = null;
|
|
1878
2328
|
let currentHost = null;
|
|
1879
2329
|
let disposed = false;
|
|
1880
2330
|
const blobStore = options.blobStore ?? inMemoryBlobStore();
|
|
1881
2331
|
const clipboard = options.clipboard ?? inMemoryClipboard();
|
|
1882
2332
|
const secrets = options.secrets ?? inMemorySecretStore();
|
|
2333
|
+
const bindingProviders = options.bindingProviders ?? createBindingProviderRegistry();
|
|
1883
2334
|
const buildHost = (manifest, mode) => createBundleHost(() => editor, manifest, {
|
|
1884
2335
|
console: options.console,
|
|
1885
2336
|
storage: options.storage,
|
|
1886
2337
|
blobStore,
|
|
1887
2338
|
clipboard,
|
|
1888
2339
|
secrets,
|
|
2340
|
+
bindingProviders,
|
|
1889
2341
|
capabilityMode: mode,
|
|
1890
2342
|
// W-06 — a recordable fake asset source the conformance harness
|
|
1891
2343
|
// can pass so a bundle's `@font-face` byte path is exercisable
|
|
@@ -2002,6 +2454,10 @@ async function createHeadlessHost(options = {}) {
|
|
|
2002
2454
|
lastToolPreview() {
|
|
2003
2455
|
return lastPreview;
|
|
2004
2456
|
},
|
|
2457
|
+
lastToolPreviews() {
|
|
2458
|
+
return lastPreviews;
|
|
2459
|
+
},
|
|
2460
|
+
bindingProviders,
|
|
2005
2461
|
async load(idml) {
|
|
2006
2462
|
const raw = worker.loadDocumentDirect(seqCounter++, idml);
|
|
2007
2463
|
const reply = JSON.parse(raw);
|
|
@@ -2299,6 +2755,9 @@ function contributeEditContext(host, contribution) {
|
|
|
2299
2755
|
function contributeObjectType(host, contribution) {
|
|
2300
2756
|
return host.contribute.objectType(contribution);
|
|
2301
2757
|
}
|
|
2758
|
+
function contributeBindingProvider(host, contextType, provider) {
|
|
2759
|
+
return host.contribute.bindingProvider(contextType, provider);
|
|
2760
|
+
}
|
|
2302
2761
|
export {
|
|
2303
2762
|
API_VERSION,
|
|
2304
2763
|
ASSET_BUDGETS,
|
|
@@ -2314,11 +2773,13 @@ export {
|
|
|
2314
2773
|
WORKER_BUDGETS,
|
|
2315
2774
|
beginPageDrag,
|
|
2316
2775
|
commitAndSelect,
|
|
2776
|
+
contributeBindingProvider,
|
|
2317
2777
|
contributeEditContext,
|
|
2318
2778
|
contributeObjectType,
|
|
2319
2779
|
contributePanel,
|
|
2320
2780
|
contributeSchemaPanel,
|
|
2321
2781
|
contributeTool,
|
|
2782
|
+
createBindingProviderRegistry,
|
|
2322
2783
|
createBundleHost,
|
|
2323
2784
|
createDataProviderRegistry,
|
|
2324
2785
|
createHeadlessHost,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paged-media/plugin-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.28-canary.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@paged-media/plugin-api": "0.2.
|
|
14
|
+
"@paged-media/plugin-api": "0.2.28-canary.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"react": "^18.3.0"
|
|
@@ -46,6 +46,6 @@
|
|
|
46
46
|
"scripts": {
|
|
47
47
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
48
48
|
"test": "vitest run",
|
|
49
|
-
"build": "tsup src/index.ts --format esm --dts --out-dir dist --clean"
|
|
49
|
+
"build": "tsup src/index.ts --format esm --target es2020 --dts --out-dir dist --clean"
|
|
50
50
|
}
|
|
51
51
|
}
|