@paged-media/plugin-sdk 0.2.14-canary.0 → 0.2.17-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 CHANGED
@@ -1,4 +1,4 @@
1
- import { PagedBundle, FontFaceAsset, BundleHost, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, Disposable, ShellSurface, WidgetSurface, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome } from '@paged-media/plugin-api';
1
+ import { PagedBundle, FontFaceAsset, BundleHost, ClipboardPayload, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, Disposable, ShellSurface, WidgetSurface, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome } from '@paged-media/plugin-api';
2
2
  import { ComponentType } from 'react';
3
3
 
4
4
  /**
@@ -113,6 +113,20 @@ interface BlobStore {
113
113
  /** Total bytes this plugin currently stores. */
114
114
  used(pluginId: string): Promise<number>;
115
115
  }
116
+ /**
117
+ * The backend the editor injects to back `host.clipboard` (K-6 / S-14): a
118
+ * thin read/write pair over the REAL system clipboard (`navigator.clipboard`
119
+ * in-browser; a fake in the headless harness). The SDK adapter owns the
120
+ * capability gate + the `"vector"` tabular-stripping; this backend only does
121
+ * the raw IO. `read` recovers a `{ text?, tabular? }` payload (or `null` when
122
+ * the platform offers nothing / refuses); `write` puts a payload on the
123
+ * clipboard (the editor backend lays down BOTH `text/plain` TSV and a
124
+ * `text/html` `<table>` so a paste into Excel/Sheets/Word lands a real grid).
125
+ */
126
+ interface ClipboardBackend {
127
+ read(): Promise<ClipboardPayload | null>;
128
+ write(payload: ClipboardPayload): Promise<void>;
129
+ }
116
130
  /** The SHARED cross-plugin data-provider registry (paged.data §7.1 / D-09). The
117
131
  * editor creates ONE (`createDataProviderRegistry`) and injects the SAME
118
132
  * instance into every plugin host, so a provider plugin and a consumer plugin
@@ -220,6 +234,12 @@ interface CreateBundleHostOptions {
220
234
  * When absent, reads answer empty and writes reject (the honest
221
235
  * no-store door). */
222
236
  blobStore?: BlobStore;
237
+ /** Host-provided CLIPBOARD backend (K-6 / S-14). When present,
238
+ * `host.clipboard.read/write` go through it (capability-gated on
239
+ * `capabilities.clipboard`) and `supports("clipboard@1")` answers true.
240
+ * When absent, `read` answers `null` and `write` is a no-op (the honest
241
+ * no-clipboard door). */
242
+ clipboard?: ClipboardBackend;
223
243
  /**
224
244
  * How the host treats a declaration↔use mismatch — a bundle that
225
245
  * USES a door (`contribute.tool`, `document.mutate`, …) it did not
@@ -314,7 +334,7 @@ interface RecordedContribution {
314
334
  id: string;
315
335
  value: ToolContribution | PanelContribution | SchemaPanelContribution | CommandContribution | KeybindingContribution | OverlayContribution | EditContextContribution | ObjectTypeContribution | ImporterContribution | ExporterContribution;
316
336
  }
317
- interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore"> {
337
+ interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore" | "clipboard"> {
318
338
  }
319
339
  /** What `createHeadlessHost` resolves to: a real engine-backed host plus
320
340
  * the conformance affordances (load an IDML, read the contribution log,
@@ -583,4 +603,4 @@ declare function contributeEditContext(host: BundleHost, contribution: EditConte
583
603
  * descent. Capability-gated on `contributes.objectTypes`. */
584
604
  declare function contributeObjectType(host: BundleHost, contribution: ObjectTypeContribution): Disposable;
585
605
 
586
- 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 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 PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type SeededFace, type StorageBacking, WASM_BUDGETS, beginPageDrag, commitAndSelect, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
606
+ 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 PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type SeededFace, type StorageBacking, WASM_BUDGETS, beginPageDrag, commitAndSelect, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
package/dist/index.js CHANGED
@@ -272,6 +272,7 @@ function createBundleHost(getEditor, manifest, options) {
272
272
  const hasRendering = (s) => caps?.rendering?.includes(s) ?? false;
273
273
  const hasAsset = (k) => caps?.assets?.includes(k) ?? false;
274
274
  const hasBlobStore = () => caps?.storage?.blob === true;
275
+ const clipboardGrant = () => caps?.clipboard === "full" ? "full" : caps?.clipboard === "vector" ? "vector" : "none";
275
276
  const lists = (arr, id) => arr?.includes(id) ?? false;
276
277
  const declaresType = (arr, type) => arr?.some((e) => e.type === type) ?? false;
277
278
  const requireDeclared = (ok, door, missing) => {
@@ -983,6 +984,87 @@ function createBundleHost(getEditor, manifest, options) {
983
984
  }
984
985
  }
985
986
  };
987
+ const imageChannel = () => getEditor().images;
988
+ const images = {
989
+ claimImageResource(elementId, opts) {
990
+ requireDeclared(
991
+ hasRendering("resourceProvider"),
992
+ "images.claimImageResource",
993
+ 'capabilities.rendering must include "resourceProvider"'
994
+ );
995
+ const channel = imageChannel();
996
+ if (!channel) {
997
+ log.warn(
998
+ `images.claimImageResource("${elementId}") ignored \u2014 the host wired no resource channel (probe supports("rendering.resourceProvider@1"))`
999
+ );
1000
+ return store.add(toDisposable(() => {
1001
+ }));
1002
+ }
1003
+ let released = false;
1004
+ const off = channel.onResourceTilesNeeded((need) => {
1005
+ if (released || need.imageId !== elementId) return;
1006
+ void (async () => {
1007
+ const tiles = [];
1008
+ for (const [x, y] of need.tiles) {
1009
+ let tile;
1010
+ try {
1011
+ tile = await opts.source(need.level, x, y);
1012
+ } catch (err) {
1013
+ log.warn(
1014
+ `images.source(level ${need.level}, ${x}, ${y}) threw \u2014 tile skipped`,
1015
+ err
1016
+ );
1017
+ continue;
1018
+ }
1019
+ if (!tile) continue;
1020
+ tiles.push({
1021
+ x: tile.x,
1022
+ y: tile.y,
1023
+ width: tile.width,
1024
+ height: tile.height,
1025
+ // The wire carries a plain number[]; Array.from copies the
1026
+ // RGBA8 view (isolate-proxy-safe, like the scene-layer path).
1027
+ rgba: Array.from(tile.rgba)
1028
+ });
1029
+ }
1030
+ if (released || tiles.length === 0) return;
1031
+ try {
1032
+ await channel.submitTiles(
1033
+ elementId,
1034
+ need.level,
1035
+ tiles,
1036
+ need.generation
1037
+ );
1038
+ } catch (err) {
1039
+ log.warn(
1040
+ `images.submitTiles("${elementId}", level ${need.level}) failed`,
1041
+ err
1042
+ );
1043
+ }
1044
+ })();
1045
+ });
1046
+ void channel.claim({
1047
+ imageId: elementId,
1048
+ levels: opts.levels,
1049
+ tileSize: opts.tileSize,
1050
+ baseWidth: opts.baseWidth,
1051
+ baseHeight: opts.baseHeight,
1052
+ revision: opts.revision()
1053
+ }).catch((err) => {
1054
+ log.warn(`images.claim("${elementId}") failed`, err);
1055
+ });
1056
+ return store.add(
1057
+ toDisposable(() => {
1058
+ if (released) return;
1059
+ released = true;
1060
+ off();
1061
+ void channel.release(elementId).catch((err) => {
1062
+ log.warn(`images.release("${elementId}") failed`, err);
1063
+ });
1064
+ })
1065
+ );
1066
+ }
1067
+ };
986
1068
  const blobBackend = options?.blobStore;
987
1069
  const blobQuota = Math.min(
988
1070
  BLOB_BUDGETS.defaultQuotaBytes,
@@ -1031,6 +1113,51 @@ function createBundleHost(getEditor, manifest, options) {
1031
1113
  return { used: await blobBackend.used(manifest.id), quota: blobQuota };
1032
1114
  }
1033
1115
  };
1116
+ const clipboardBackend = options?.clipboard;
1117
+ const clipboardGate = (door) => {
1118
+ const grant = clipboardGrant();
1119
+ requireDeclared(
1120
+ grant !== "none",
1121
+ door,
1122
+ 'capabilities.clipboard must be "full" or "vector"'
1123
+ );
1124
+ return grant === "full" ? "full" : "vector";
1125
+ };
1126
+ const clipboard = {
1127
+ async read() {
1128
+ const tier = clipboardGate("clipboard.read");
1129
+ if (!clipboardBackend) return null;
1130
+ let payload;
1131
+ try {
1132
+ payload = await clipboardBackend.read();
1133
+ } catch {
1134
+ return null;
1135
+ }
1136
+ if (!payload) return null;
1137
+ if (tier === "vector" && payload.tabular !== void 0) {
1138
+ const { tabular: _dropped, ...rest } = payload;
1139
+ return rest;
1140
+ }
1141
+ return payload;
1142
+ },
1143
+ async write(payload) {
1144
+ const tier = clipboardGate("clipboard.write");
1145
+ if (!clipboardBackend) return;
1146
+ let toWrite = payload;
1147
+ if (tier === "vector" && payload.tabular !== void 0) {
1148
+ log.warn(
1149
+ 'clipboard.write: dropping the tabular payload \u2014 capabilities.clipboard is "vector" (text only); declare "full" to copy a cell grid'
1150
+ );
1151
+ const { tabular: _dropped, ...rest } = payload;
1152
+ toWrite = rest;
1153
+ }
1154
+ try {
1155
+ await clipboardBackend.write(toWrite);
1156
+ } catch (err) {
1157
+ log.warn("clipboard.write: the platform refused the write", err);
1158
+ }
1159
+ }
1160
+ };
1034
1161
  const featureSet = new Set(HOST_FEATURES);
1035
1162
  if (getEditor().text) {
1036
1163
  featureSet.add("text.measure@1");
@@ -1038,6 +1165,9 @@ function createBundleHost(getEditor, manifest, options) {
1038
1165
  if (getEditor().sceneLayers) {
1039
1166
  featureSet.add("rendering.sceneLayer@1");
1040
1167
  }
1168
+ if (getEditor().images) {
1169
+ featureSet.add("rendering.resourceProvider@1");
1170
+ }
1041
1171
  if (options?.shell) {
1042
1172
  featureSet.add("shell.openPanel@1");
1043
1173
  featureSet.add("shell.pickFile@1");
@@ -1063,6 +1193,9 @@ function createBundleHost(getEditor, manifest, options) {
1063
1193
  if (options?.blobStore) {
1064
1194
  featureSet.add("storage.blob@1");
1065
1195
  }
1196
+ if (options?.clipboard) {
1197
+ featureSet.add("clipboard@1");
1198
+ }
1066
1199
  const host = {
1067
1200
  manifest,
1068
1201
  log,
@@ -1081,6 +1214,8 @@ function createBundleHost(getEditor, manifest, options) {
1081
1214
  bindings,
1082
1215
  widgets,
1083
1216
  assets,
1217
+ images,
1218
+ clipboard,
1084
1219
  supports: (feature) => featureSet.has(feature),
1085
1220
  get editor() {
1086
1221
  return getEditor();
@@ -1255,6 +1390,24 @@ function inMemoryBlobStore() {
1255
1390
  }
1256
1391
  };
1257
1392
  }
1393
+ function inMemoryClipboard() {
1394
+ let slot = null;
1395
+ return {
1396
+ async read() {
1397
+ if (!slot) return null;
1398
+ return {
1399
+ ...slot.text !== void 0 ? { text: slot.text } : {},
1400
+ ...slot.tabular ? { tabular: { rows: slot.tabular.rows.map((r) => [...r]) } } : {}
1401
+ };
1402
+ },
1403
+ async write(payload) {
1404
+ slot = {
1405
+ ...payload.text !== void 0 ? { text: payload.text } : {},
1406
+ ...payload.tabular ? { tabular: { rows: payload.tabular.rows.map((r) => [...r]) } } : {}
1407
+ };
1408
+ }
1409
+ };
1410
+ }
1258
1411
  var seqCounter = 1;
1259
1412
  function makeEngineEditor(worker, recorder, onToolPreview) {
1260
1413
  const protocol = worker.protocolVersion;
@@ -1417,10 +1570,12 @@ async function createHeadlessHost(options = {}) {
1417
1570
  let currentHost = null;
1418
1571
  let disposed = false;
1419
1572
  const blobStore = options.blobStore ?? inMemoryBlobStore();
1573
+ const clipboard = options.clipboard ?? inMemoryClipboard();
1420
1574
  const buildHost = (manifest, mode) => createBundleHost(() => editor, manifest, {
1421
1575
  console: options.console,
1422
1576
  storage: options.storage,
1423
1577
  blobStore,
1578
+ clipboard,
1424
1579
  capabilityMode: mode,
1425
1580
  // W-06 — a recordable fake asset source the conformance harness
1426
1581
  // can pass so a bundle's `@font-face` byte path is exercisable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paged-media/plugin-sdk",
3
- "version": "0.2.14-canary.0",
3
+ "version": "0.2.17-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-canary.0"
14
+ "@paged-media/plugin-api": "0.2.17-canary.0"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "react": "^18.3.0"
@@ -22,7 +22,7 @@
22
22
  }
23
23
  },
24
24
  "devDependencies": {
25
- "@paged-media/canvas-wasm": "0.43.0",
25
+ "@paged-media/canvas-wasm": "0.44.0",
26
26
  "@types/node": "20.19.39",
27
27
  "@types/react": "^18.3.12",
28
28
  "react": "^18.3.0",