@paged-media/plugin-sdk 0.2.27-canary.0 → 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 +509 -21
- 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,10 +48,31 @@ function toDisposable(fn) {
|
|
|
42
48
|
};
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
// src/react-optional.ts
|
|
52
|
+
var createElement = null;
|
|
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
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
45
70
|
// src/widgets-fallback.tsx
|
|
46
|
-
import { createElement } from "react";
|
|
47
71
|
function TextareaCodeEditor(props) {
|
|
48
|
-
|
|
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", {
|
|
49
76
|
value: props.value,
|
|
50
77
|
readOnly: props.readOnly,
|
|
51
78
|
spellCheck: false,
|
|
@@ -71,7 +98,11 @@ var FALLBACK_WIDGETS = {
|
|
|
71
98
|
};
|
|
72
99
|
|
|
73
100
|
// src/schema-panel.tsx
|
|
74
|
-
|
|
101
|
+
function h() {
|
|
102
|
+
return requireCreateElement(
|
|
103
|
+
"plugin-sdk: rendering a schema panel needs React, which is not installed."
|
|
104
|
+
);
|
|
105
|
+
}
|
|
75
106
|
function resolveGate(gate, lookup) {
|
|
76
107
|
if (gate === void 0) return true;
|
|
77
108
|
if (typeof gate === "boolean") return gate;
|
|
@@ -82,7 +113,7 @@ function makeSchemaPanelComponent(contribution, bindings, renderer) {
|
|
|
82
113
|
const { schema } = contribution;
|
|
83
114
|
if (renderer) {
|
|
84
115
|
let SchemaPanel2 = function(_props) {
|
|
85
|
-
return
|
|
116
|
+
return h()(Renderer, { schema, bindings });
|
|
86
117
|
};
|
|
87
118
|
var SchemaPanel = SchemaPanel2;
|
|
88
119
|
const Renderer = renderer;
|
|
@@ -90,7 +121,7 @@ function makeSchemaPanelComponent(contribution, bindings, renderer) {
|
|
|
90
121
|
return SchemaPanel2;
|
|
91
122
|
}
|
|
92
123
|
function SchemaPanelSeam(_props) {
|
|
93
|
-
return
|
|
124
|
+
return h()(
|
|
94
125
|
"div",
|
|
95
126
|
{
|
|
96
127
|
"data-schema-panel-seam": schema.id,
|
|
@@ -118,6 +149,14 @@ var HOST_FEATURES = [
|
|
|
118
149
|
"contribute.overlay@1",
|
|
119
150
|
"contribute.editContext@1",
|
|
120
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",
|
|
121
160
|
"contribute.importer@1",
|
|
122
161
|
"contribute.exporter@1",
|
|
123
162
|
"document.mutate@1",
|
|
@@ -131,6 +170,12 @@ var HOST_FEATURES = [
|
|
|
131
170
|
"document.elementProperties@1",
|
|
132
171
|
"document.placeholders@1",
|
|
133
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",
|
|
134
179
|
"document.onDidChange@1",
|
|
135
180
|
"document.getMetadata@1",
|
|
136
181
|
"document.setMetadata@1",
|
|
@@ -139,6 +184,13 @@ var HOST_FEATURES = [
|
|
|
139
184
|
"overlay.toolPreview@1",
|
|
140
185
|
"storage@1",
|
|
141
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",
|
|
142
194
|
// C-5 / I-04 (core v42): the placed-image bytes read is engine-served
|
|
143
195
|
// through the wire (no injected source), so it is unconditionally
|
|
144
196
|
// implemented at the pinned canvas-wasm — unlike assets.fonts@1, which
|
|
@@ -147,7 +199,17 @@ var HOST_FEATURES = [
|
|
|
147
199
|
// .paged container parts door (file-format.md) — the engine (canvas-wasm
|
|
148
200
|
// v51) is the writer, so it is live whenever a bundle runs (the worker
|
|
149
201
|
// handshake guarantees a v51-protocol worker).
|
|
150
|
-
"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"
|
|
151
213
|
];
|
|
152
214
|
var PluginApiNotImplemented = class extends Error {
|
|
153
215
|
constructor(member, pointer) {
|
|
@@ -158,15 +220,15 @@ var PluginApiNotImplemented = class extends Error {
|
|
|
158
220
|
}
|
|
159
221
|
};
|
|
160
222
|
var PluginCapabilityError = class extends Error {
|
|
161
|
-
/** The host door that was called (e.g. `"contribute.tool"`). */
|
|
162
|
-
door;
|
|
163
|
-
/** The manifest declaration that would authorize it (e.g.
|
|
164
|
-
* `'contributes.tools[] must include "media.paged.web.tool.pen"'`). */
|
|
165
|
-
missingDeclaration;
|
|
166
223
|
constructor(door, missingDeclaration, pluginId) {
|
|
167
224
|
super(
|
|
168
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.`
|
|
169
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");
|
|
170
232
|
this.name = "PluginCapabilityError";
|
|
171
233
|
this.door = door;
|
|
172
234
|
this.missingDeclaration = missingDeclaration;
|
|
@@ -259,6 +321,144 @@ function createDataProviderRegistry() {
|
|
|
259
321
|
}
|
|
260
322
|
};
|
|
261
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
|
+
}
|
|
262
462
|
function createBundleHost(getEditor, manifest, options) {
|
|
263
463
|
const store = new DisposableStore();
|
|
264
464
|
const sink = options?.console ?? console;
|
|
@@ -312,6 +512,43 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
312
512
|
}
|
|
313
513
|
return reason;
|
|
314
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
|
+
};
|
|
315
552
|
const contribute = {
|
|
316
553
|
tool(c) {
|
|
317
554
|
assertNamespaced(c.id, "tool");
|
|
@@ -404,9 +641,30 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
404
641
|
"contribute.editContext",
|
|
405
642
|
`contributes.editContexts[] must declare { type: "${c.type}" }`
|
|
406
643
|
);
|
|
644
|
+
registeredContexts.add(c.type);
|
|
407
645
|
const stamped = {
|
|
408
646
|
...c,
|
|
409
|
-
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
|
+
} : {}
|
|
410
668
|
};
|
|
411
669
|
const reg = getEditor().registries.editContexts;
|
|
412
670
|
const recorded = options?.onEditContextRegistered?.(stamped);
|
|
@@ -452,6 +710,69 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
452
710
|
return store.add(toDisposable(() => {
|
|
453
711
|
}));
|
|
454
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
|
+
},
|
|
455
776
|
// K-2 / S-06 — document IO. Mirrors `command`: namespaced id the
|
|
456
777
|
// manifest must list, routed to the shell registry. The registry is
|
|
457
778
|
// OPTIONAL on the handle (a host that hasn't wired it stays
|
|
@@ -550,6 +871,24 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
550
871
|
door,
|
|
551
872
|
"capabilities.document.openNative must be declared"
|
|
552
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
|
+
};
|
|
553
892
|
const document = {
|
|
554
893
|
async mutate(mutation) {
|
|
555
894
|
const denied = denyWrite(
|
|
@@ -653,6 +992,30 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
653
992
|
return null;
|
|
654
993
|
}
|
|
655
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
|
+
},
|
|
656
1019
|
async tree() {
|
|
657
1020
|
requireDocRead("document.tree");
|
|
658
1021
|
const reply = await getEditor().client.send({
|
|
@@ -660,6 +1023,30 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
660
1023
|
});
|
|
661
1024
|
return reply.kind === "sceneTree" ? reply.payload.roots : [];
|
|
662
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
|
+
},
|
|
663
1050
|
async getMetadata(id) {
|
|
664
1051
|
requireDocRead("document.getMetadata");
|
|
665
1052
|
const key = metadataKey(manifest);
|
|
@@ -701,6 +1088,12 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
701
1088
|
});
|
|
702
1089
|
return reply.kind === "frameChainResult" ? reply.payload.links : [];
|
|
703
1090
|
},
|
|
1091
|
+
async storyContent(_storyId) {
|
|
1092
|
+
throw new PluginApiNotImplemented(
|
|
1093
|
+
"document.storyContent",
|
|
1094
|
+
"awaiting @paged-media/canvas-wasm v54 publish + sync-wire; core op RequestStoryContent already exists"
|
|
1095
|
+
);
|
|
1096
|
+
},
|
|
704
1097
|
onDidChange(listener) {
|
|
705
1098
|
requireDocRead("document.onDidChange");
|
|
706
1099
|
const off = getEditor().client.subscribe((msg) => {
|
|
@@ -761,6 +1154,15 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
761
1154
|
ascender: sizePt * 0.8,
|
|
762
1155
|
descender: -sizePt * 0.2
|
|
763
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;
|
|
764
1166
|
}
|
|
765
1167
|
};
|
|
766
1168
|
const overlay = {
|
|
@@ -771,6 +1173,27 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
771
1173
|
'capabilities.rendering must include "overlay"'
|
|
772
1174
|
);
|
|
773
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);
|
|
774
1197
|
}
|
|
775
1198
|
};
|
|
776
1199
|
const backing = options?.storage ?? defaultStorageBacking();
|
|
@@ -939,19 +1362,40 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
939
1362
|
return store.add(toDisposable(() => bindingListeners.delete(listener)));
|
|
940
1363
|
}
|
|
941
1364
|
};
|
|
942
|
-
const
|
|
1365
|
+
const shellBackend = options?.shell;
|
|
1366
|
+
const shell = {
|
|
943
1367
|
openPanel(panelId) {
|
|
1368
|
+
if (shellBackend) {
|
|
1369
|
+
shellBackend.openPanel(panelId);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
944
1372
|
log.warn(
|
|
945
1373
|
`shell.openPanel("${panelId}") ignored \u2014 the host app provided no shell actions (probe with supports("shell.openPanel@1"))`
|
|
946
1374
|
);
|
|
947
1375
|
},
|
|
948
|
-
closePanel() {
|
|
1376
|
+
closePanel(panelId) {
|
|
1377
|
+
shellBackend?.closePanel(panelId);
|
|
949
1378
|
},
|
|
950
|
-
async pickFile() {
|
|
1379
|
+
async pickFile(pickOptions) {
|
|
1380
|
+
if (shellBackend) return shellBackend.pickFile(pickOptions);
|
|
951
1381
|
log.warn(
|
|
952
1382
|
`shell.pickFile() ignored \u2014 the host app provided no shell actions (probe with supports("shell.pickFile@1"))`
|
|
953
1383
|
);
|
|
954
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;
|
|
955
1399
|
}
|
|
956
1400
|
};
|
|
957
1401
|
const widgets = options?.widgets ?? FALLBACK_WIDGETS;
|
|
@@ -1410,12 +1854,18 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1410
1854
|
if (getEditor().sceneLayers) {
|
|
1411
1855
|
featureSet.add("rendering.sceneLayer@1");
|
|
1412
1856
|
}
|
|
1857
|
+
if (typeof getEditor().overlaySignals?.setToolPreviews === "function") {
|
|
1858
|
+
featureSet.add("overlay.multiPreview@1");
|
|
1859
|
+
}
|
|
1413
1860
|
if (getEditor().images) {
|
|
1414
1861
|
featureSet.add("rendering.resourceProvider@1");
|
|
1415
1862
|
}
|
|
1416
1863
|
if (options?.shell) {
|
|
1417
1864
|
featureSet.add("shell.openPanel@1");
|
|
1418
1865
|
featureSet.add("shell.pickFile@1");
|
|
1866
|
+
if (typeof options.shell.saveFile === "function") {
|
|
1867
|
+
featureSet.add("shell.saveFile@1");
|
|
1868
|
+
}
|
|
1419
1869
|
}
|
|
1420
1870
|
if (options?.widgets) {
|
|
1421
1871
|
featureSet.add("widgets.codeEditor@1");
|
|
@@ -1435,12 +1885,18 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1435
1885
|
if (options?.dataProviders) {
|
|
1436
1886
|
featureSet.add("dataProviders@1");
|
|
1437
1887
|
}
|
|
1888
|
+
if (options?.bindingProviders) {
|
|
1889
|
+
featureSet.add("bindings.provider@1");
|
|
1890
|
+
}
|
|
1438
1891
|
if (options?.blobStore) {
|
|
1439
1892
|
featureSet.add("storage.blob@1");
|
|
1440
1893
|
}
|
|
1441
1894
|
if (options?.clipboard) {
|
|
1442
1895
|
featureSet.add("clipboard@1");
|
|
1443
1896
|
}
|
|
1897
|
+
if (options?.textCaret) {
|
|
1898
|
+
featureSet.add("text.caret@1");
|
|
1899
|
+
}
|
|
1444
1900
|
if (options?.nativeDocument) {
|
|
1445
1901
|
featureSet.add("document.readNative@1");
|
|
1446
1902
|
featureSet.add("document.openNative@1");
|
|
@@ -1690,7 +2146,7 @@ function inMemorySecretStore() {
|
|
|
1690
2146
|
};
|
|
1691
2147
|
}
|
|
1692
2148
|
var seqCounter = 1;
|
|
1693
|
-
function makeEngineEditor(worker, recorder, onToolPreview) {
|
|
2149
|
+
function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews) {
|
|
1694
2150
|
const protocol = worker.protocolVersion;
|
|
1695
2151
|
const listeners = /* @__PURE__ */ new Set();
|
|
1696
2152
|
const fanOut = (reply) => {
|
|
@@ -1738,6 +2194,7 @@ function makeEngineEditor(worker, recorder, onToolPreview) {
|
|
|
1738
2194
|
};
|
|
1739
2195
|
let elementSelection = [];
|
|
1740
2196
|
let toolPreview = null;
|
|
2197
|
+
let toolPreviews = null;
|
|
1741
2198
|
const client = {
|
|
1742
2199
|
async mutate(mutation) {
|
|
1743
2200
|
const reply = dispatch("mutate", mutation);
|
|
@@ -1827,6 +2284,18 @@ function makeEngineEditor(worker, recorder, onToolPreview) {
|
|
|
1827
2284
|
setToolPreview(value) {
|
|
1828
2285
|
toolPreview = value;
|
|
1829
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);
|
|
1830
2299
|
}
|
|
1831
2300
|
},
|
|
1832
2301
|
// No tool spine + no content caret headlessly — both are inert
|
|
@@ -1844,21 +2313,31 @@ async function createHeadlessHost(options = {}) {
|
|
|
1844
2313
|
const worker = engine.worker;
|
|
1845
2314
|
const contributions = [];
|
|
1846
2315
|
let lastPreview = null;
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
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
|
+
);
|
|
1850
2327
|
let active = null;
|
|
1851
2328
|
let currentHost = null;
|
|
1852
2329
|
let disposed = false;
|
|
1853
2330
|
const blobStore = options.blobStore ?? inMemoryBlobStore();
|
|
1854
2331
|
const clipboard = options.clipboard ?? inMemoryClipboard();
|
|
1855
2332
|
const secrets = options.secrets ?? inMemorySecretStore();
|
|
2333
|
+
const bindingProviders = options.bindingProviders ?? createBindingProviderRegistry();
|
|
1856
2334
|
const buildHost = (manifest, mode) => createBundleHost(() => editor, manifest, {
|
|
1857
2335
|
console: options.console,
|
|
1858
2336
|
storage: options.storage,
|
|
1859
2337
|
blobStore,
|
|
1860
2338
|
clipboard,
|
|
1861
2339
|
secrets,
|
|
2340
|
+
bindingProviders,
|
|
1862
2341
|
capabilityMode: mode,
|
|
1863
2342
|
// W-06 — a recordable fake asset source the conformance harness
|
|
1864
2343
|
// can pass so a bundle's `@font-face` byte path is exercisable
|
|
@@ -1975,6 +2454,10 @@ async function createHeadlessHost(options = {}) {
|
|
|
1975
2454
|
lastToolPreview() {
|
|
1976
2455
|
return lastPreview;
|
|
1977
2456
|
},
|
|
2457
|
+
lastToolPreviews() {
|
|
2458
|
+
return lastPreviews;
|
|
2459
|
+
},
|
|
2460
|
+
bindingProviders,
|
|
1978
2461
|
async load(idml) {
|
|
1979
2462
|
const raw = worker.loadDocumentDirect(seqCounter++, idml);
|
|
1980
2463
|
const reply = JSON.parse(raw);
|
|
@@ -2272,6 +2755,9 @@ function contributeEditContext(host, contribution) {
|
|
|
2272
2755
|
function contributeObjectType(host, contribution) {
|
|
2273
2756
|
return host.contribute.objectType(contribution);
|
|
2274
2757
|
}
|
|
2758
|
+
function contributeBindingProvider(host, contextType, provider) {
|
|
2759
|
+
return host.contribute.bindingProvider(contextType, provider);
|
|
2760
|
+
}
|
|
2275
2761
|
export {
|
|
2276
2762
|
API_VERSION,
|
|
2277
2763
|
ASSET_BUDGETS,
|
|
@@ -2287,11 +2773,13 @@ export {
|
|
|
2287
2773
|
WORKER_BUDGETS,
|
|
2288
2774
|
beginPageDrag,
|
|
2289
2775
|
commitAndSelect,
|
|
2776
|
+
contributeBindingProvider,
|
|
2290
2777
|
contributeEditContext,
|
|
2291
2778
|
contributeObjectType,
|
|
2292
2779
|
contributePanel,
|
|
2293
2780
|
contributeSchemaPanel,
|
|
2294
2781
|
contributeTool,
|
|
2782
|
+
createBindingProviderRegistry,
|
|
2295
2783
|
createBundleHost,
|
|
2296
2784
|
createDataProviderRegistry,
|
|
2297
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
|
}
|