@paged-media/plugin-sdk 0.2.41-canary.0 → 0.2.42-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, BindingProvider, Disposable, ActiveBindingProvider, BindingPropertyRequest, BindingReadResult, BindingPropertyWrite, BindingWriteResult, BindingCollectionRequest, BindingCollectionResult, MutationInput, FontFaceAsset, BundleHost, ClipboardPayload, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, JournalRecord, ShellSurface, WidgetSurface, WillSaveEvent, ToolSettingValue, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, TextCaret, OpenDocumentResult, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome, BindingProviderHandle } from '@paged-media/plugin-api';
1
+ import { PagedBundle, BindingProvider, Disposable, ActiveBindingProvider, BindingPropertyRequest, BindingReadResult, BindingPropertyWrite, BindingWriteResult, BindingCollectionRequest, BindingCollectionResult, MutationInput, FontFaceAsset, BundleHost, ClipboardPayload, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, JournalRecord, ShellSurface, WidgetSurface, WillSaveEvent, ToolSettingValue, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, TextCaret, OpenDocumentResult, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, EnteredEditContext, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome, BindingProviderHandle } from '@paged-media/plugin-api';
2
2
  import { ComponentType } from 'react';
3
3
 
4
4
  /**
@@ -104,6 +104,18 @@ interface BundleAssetProvider {
104
104
  /** The bytes of a document font face, or `null` when the host has
105
105
  * none. Style-agnostic when `style` is omitted. */
106
106
  getFontFace(family: string, style?: string): Promise<FontFaceAsset | null>;
107
+ /** W-15 — register a face in the engine's SCENE-LAYER face table
108
+ * (`registerFont` with `scope: "sceneLayer"`): scene-layer text
109
+ * resolves it, the document never sees it. The SDK calls it once per
110
+ * `host.assets.registerFont` and again when it replays the live faces
111
+ * after a disposal. Optional: a host without it (with
112
+ * `clearSceneFonts`) leaves `supports("assets.registerFont@1")` false. */
113
+ registerFont?(family: string, bytes: Uint8Array, style?: string): Promise<void>;
114
+ /** W-15 — drop every scene-layer face (`clearFontRegistry` with
115
+ * `scope: "sceneLayer"`), leaving the document's faces alone. The SDK
116
+ * owns which faces are live, so disposing one face is "clear, then
117
+ * register the rest again". */
118
+ clearSceneFonts?(): Promise<void>;
107
119
  }
108
120
  /** Blob-store budgets (K-4 / S-08). The default per-plugin quota when a
109
121
  * manifest requests none; a manifest's `storage.quotaBytes` may only
@@ -423,6 +435,13 @@ interface CreateBundleHostOptions {
423
435
  * holds listeners that never run and `supports("document.onWillSave@1")`
424
436
  * is false. */
425
437
  willSave?: WillSaveBackend;
438
+ /** W-19 — the host's shell fills `pageId` / `pagePoint` /
439
+ * `contentPoint` on the `EnteredEditContext` of a POINTER entry (the
440
+ * double-click, the Type-tool click on owned content). The SDK passes
441
+ * the entered context through untouched either way; this flag only
442
+ * makes `supports("editContext.enterPoint@1")` true, so it is the HOST
443
+ * that vouches for delivery. */
444
+ editContextEnterPoint?: boolean;
426
445
  /** The host's tool-options store. When absent, `host.tools.settings`
427
446
  * answers `{}` and `supports("tools.settings@1")` is false. */
428
447
  toolSettings?: ToolSettingsBackend;
@@ -699,6 +718,19 @@ interface HeadlessHost {
699
718
  * bundle's "geometry AND label at once" claim is assertable without a
700
719
  * browser. `lastToolPreview()` tracks the list's FIRST shape. */
701
720
  lastToolPreviews(): readonly ToolPreviewShape[] | null;
721
+ /** W-20 — every LIVE overlay layer (`host.overlay.layer`), in stack
722
+ * order (bottom-most first), keyed `<manifest id>/<layer id>`, with the
723
+ * shapes it holds now (`[]` once cleared). A disposed layer is gone. */
724
+ overlayLayers(): ReadonlyArray<{
725
+ key: string;
726
+ shapes: readonly ToolPreviewShape[];
727
+ }>;
728
+ /** W-19 — play the SHELL's part on a pointer entry: call the registered
729
+ * edit context's `onEnter` with `ctx` (which may carry `pageId` /
730
+ * `pagePoint` / `contentPoint`, as the editor fills them). The harness
731
+ * advertises `editContext.enterPoint@1`. Returns `false` when no
732
+ * context of `ctx.type` is registered. */
733
+ enterEditContext(ctx: EnteredEditContext): boolean;
702
734
  /** ADR-023 phase A — the SHARED binding-provider registry this host
703
735
  * injected. It is the HOST side of the seam, so a conformance test
704
736
  * plays the part the editor's shared panel plays in phase C: enter
package/dist/index.js CHANGED
@@ -228,6 +228,10 @@ var HOST_FEATURES = [
228
228
  // needed — DESIGN.md §4.3d).
229
229
  "document.parentOf@1",
230
230
  "document.onDidChange@1",
231
+ // W-22 — the document-opened event. STATIC: it is the client's
232
+ // `documentLoaded` broadcast filtered, and every host posts that on
233
+ // every load (File ▸ Open, File ▸ New, a native-document open).
234
+ "document.onDidOpen@1",
231
235
  "document.getMetadata@1",
232
236
  "document.setMetadata@1",
233
237
  // Protocol 69 — the document-scoped label. Static like the snap door:
@@ -333,6 +337,15 @@ var ASSET_BUDGETS = {
333
337
  /** Largest font face the door will serve, in bytes (8 MiB). */
334
338
  maxFontFaceBytes: 8 * 1024 * 1024
335
339
  };
340
+ var sceneFaceLedgers = /* @__PURE__ */ new WeakMap();
341
+ function sceneFaceLedger(provider) {
342
+ let ledger = sceneFaceLedgers.get(provider);
343
+ if (!ledger) {
344
+ ledger = { entries: [], queue: Promise.resolve() };
345
+ sceneFaceLedgers.set(provider, ledger);
346
+ }
347
+ return ledger;
348
+ }
336
349
  var BLOB_BUDGETS = {
337
350
  /** Default per-plugin blob ceiling, in bytes (64 MiB). */
338
351
  defaultQuotaBytes: 64 * 1024 * 1024
@@ -1104,7 +1117,7 @@ function createBundleHost(getEditor, manifest, options) {
1104
1117
  }
1105
1118
  };
1106
1119
  const foreignMetadataKey = (m) => {
1107
- if (m.op === "setPluginMetadata" || m.op === "setDocumentMetadata") {
1120
+ if (m.op === "setPluginMetadata" || m.op === "setDocumentMetadata" || m.op === "setPageMetadata") {
1108
1121
  return m.args.key === metadataKey(manifest) ? null : m.args.key;
1109
1122
  }
1110
1123
  if (m.op === "batch") {
@@ -1442,6 +1455,28 @@ function createBundleHost(getEditor, manifest, options) {
1442
1455
  });
1443
1456
  return store.add(toDisposable(off));
1444
1457
  },
1458
+ // W-22 — the raw `documentLoaded` broadcast, narrowed to a clonable
1459
+ // snapshot (the handle's stats / ruler guides stay host-side).
1460
+ onDidOpen(listener) {
1461
+ requireDocRead("document.onDidOpen");
1462
+ const off = getEditor().client.subscribe((msg) => {
1463
+ if (msg.kind !== "documentLoaded") return;
1464
+ const h2 = msg.payload;
1465
+ try {
1466
+ listener({
1467
+ docId: h2.docId,
1468
+ pageCount: h2.pageCount,
1469
+ pageIds: [...h2.pageIds],
1470
+ pageSizesPt: h2.pageSizesPt.map(
1471
+ ([w, hgt]) => [w, hgt]
1472
+ )
1473
+ });
1474
+ } catch (err) {
1475
+ log.error(`document.onDidOpen listener failed: ${String(err)}`);
1476
+ }
1477
+ });
1478
+ return store.add(toDisposable(off));
1479
+ },
1445
1480
  // Protocol 66 batch — the save hook. The host awaits every
1446
1481
  // registered listener before it serialises the document; the wrapper
1447
1482
  // makes a bundle's failure a logged event, never a failed save.
@@ -1536,6 +1571,9 @@ function createBundleHost(getEditor, manifest, options) {
1536
1571
  );
1537
1572
  }
1538
1573
  };
1574
+ let overlayLayerSeq = 0;
1575
+ let overlayLayerWarned = false;
1576
+ const liveOverlayLayers = /* @__PURE__ */ new Set();
1539
1577
  const overlay = {
1540
1578
  setToolPreview(shape) {
1541
1579
  requireDeclared(
@@ -1565,6 +1603,56 @@ function createBundleHost(getEditor, manifest, options) {
1565
1603
  return;
1566
1604
  }
1567
1605
  signals.setToolPreview(list ? list[0] : null);
1606
+ },
1607
+ // W-20 — retained per-bundle layers. Each layer is a host-wide key
1608
+ // `<manifest id>/<layer id>`; registering it with `[]` at creation
1609
+ // fixes its place in the stack (creation order), so a later `set`
1610
+ // never reorders it. Without a host sink the handle is inert (warned
1611
+ // once per bundle) — never a throw, never a write into the shared
1612
+ // tool-preview slot, which would erase the active tool's preview.
1613
+ layer(id) {
1614
+ requireDeclared(
1615
+ hasRendering("overlay"),
1616
+ "overlay.layer",
1617
+ 'capabilities.rendering must include "overlay"'
1618
+ );
1619
+ const layerId = id ?? `layer-${++overlayLayerSeq}`;
1620
+ if (liveOverlayLayers.has(layerId)) {
1621
+ throw new Error(
1622
+ `host.overlay.layer("${layerId}"): a layer with this id is already live in ${manifest.id} \u2014 dispose it first or pick another id`
1623
+ );
1624
+ }
1625
+ const key = `${manifest.id}/${layerId}`;
1626
+ const signals = getEditor().overlaySignals;
1627
+ const sink2 = typeof signals?.setOverlayLayer === "function" && typeof signals?.removeOverlayLayer === "function" ? signals : null;
1628
+ if (!sink2 && !overlayLayerWarned) {
1629
+ overlayLayerWarned = true;
1630
+ log.warn(
1631
+ 'host.overlay.layer \u2014 this host renders no overlay layers (supports("overlay.layers@1") is false); shapes are dropped'
1632
+ );
1633
+ }
1634
+ liveOverlayLayers.add(layerId);
1635
+ sink2?.setOverlayLayer(key, []);
1636
+ let disposed = false;
1637
+ const handle = {
1638
+ id: layerId,
1639
+ set(shapes) {
1640
+ if (disposed) return;
1641
+ sink2?.setOverlayLayer(key, [...shapes]);
1642
+ },
1643
+ clear() {
1644
+ if (disposed) return;
1645
+ sink2?.setOverlayLayer(key, []);
1646
+ },
1647
+ dispose() {
1648
+ if (disposed) return;
1649
+ disposed = true;
1650
+ liveOverlayLayers.delete(layerId);
1651
+ sink2?.removeOverlayLayer(key);
1652
+ }
1653
+ };
1654
+ store.add(handle);
1655
+ return handle;
1568
1656
  }
1569
1657
  };
1570
1658
  const backing = options?.storage ?? defaultStorageBacking();
@@ -1829,6 +1917,65 @@ function createBundleHost(getEditor, manifest, options) {
1829
1917
  }
1830
1918
  return face;
1831
1919
  },
1920
+ // W-15 — the one WRITE: a scene-layer face. The engine has a single
1921
+ // scene table that `clearFontRegistry` can only empty as a whole, so
1922
+ // the SDK keeps the list of live faces per provider (across bundles)
1923
+ // and disposes one face by clearing and replaying the others. Every
1924
+ // engine call goes through the ledger's queue, so a replay never
1925
+ // interleaves with a registration.
1926
+ async registerFont(bytes, family, style) {
1927
+ requireDeclared(
1928
+ hasAsset("fonts"),
1929
+ "assets.registerFont",
1930
+ 'capabilities.assets must include "fonts"'
1931
+ );
1932
+ const label = `assets.registerFont("${family}"${style ? `, "${style}"` : ""})`;
1933
+ if (bytes.byteLength > ASSET_BUDGETS.maxFontFaceBytes) {
1934
+ throw new Error(
1935
+ `${label}: ${bytes.byteLength} bytes is over the ${ASSET_BUDGETS.maxFontFaceBytes}-byte per-face cap`
1936
+ );
1937
+ }
1938
+ const provider = assetSource;
1939
+ const register = provider?.registerFont?.bind(provider);
1940
+ const clear = provider?.clearSceneFonts?.bind(provider);
1941
+ if (!provider || !register || !clear) {
1942
+ log.warn(
1943
+ `${label} ignored \u2014 the host cannot register scene faces (probe supports("assets.registerFont@1")); scene text keeps its fallback face`
1944
+ );
1945
+ return toDisposable(() => {
1946
+ });
1947
+ }
1948
+ const ledger = sceneFaceLedger(provider);
1949
+ const entry = style === void 0 ? { family, bytes: bytes.slice() } : { family, style, bytes: bytes.slice() };
1950
+ const run = (job) => {
1951
+ const next = ledger.queue.then(job);
1952
+ ledger.queue = next.catch(() => {
1953
+ });
1954
+ return next;
1955
+ };
1956
+ await run(async () => {
1957
+ await register(entry.family, entry.bytes, entry.style);
1958
+ ledger.entries.push(entry);
1959
+ });
1960
+ let disposed = false;
1961
+ return store.add(
1962
+ toDisposable(() => {
1963
+ if (disposed) return;
1964
+ disposed = true;
1965
+ void run(async () => {
1966
+ const i = ledger.entries.indexOf(entry);
1967
+ if (i < 0) return;
1968
+ ledger.entries.splice(i, 1);
1969
+ await clear();
1970
+ for (const live of ledger.entries) {
1971
+ await register(live.family, live.bytes, live.style);
1972
+ }
1973
+ }).catch((error) => {
1974
+ log.warn(`${label}: dropping the face failed: ${String(error)}`);
1975
+ });
1976
+ })
1977
+ );
1978
+ },
1832
1979
  // C-5 / I-04 (core v42): a placed DOCUMENT image's ORIGINAL bytes,
1833
1980
  // straight from the engine's resolver/parse cache through the
1834
1981
  // `requestPlacedAssetBytes` wire query — no injected source needed
@@ -2356,6 +2503,12 @@ function createBundleHost(getEditor, manifest, options) {
2356
2503
  if (typeof getEditor().overlaySignals?.setToolPreviews === "function") {
2357
2504
  featureSet.add("overlay.multiPreview@1");
2358
2505
  }
2506
+ if (typeof getEditor().overlaySignals?.setOverlayLayer === "function" && typeof getEditor().overlaySignals?.removeOverlayLayer === "function") {
2507
+ featureSet.add("overlay.layers@1");
2508
+ }
2509
+ if (options?.editContextEnterPoint === true) {
2510
+ featureSet.add("editContext.enterPoint@1");
2511
+ }
2359
2512
  if (getEditor().images) {
2360
2513
  featureSet.add("rendering.resourceProvider@1");
2361
2514
  }
@@ -2399,6 +2552,9 @@ function createBundleHost(getEditor, manifest, options) {
2399
2552
  if (options?.assetSource) {
2400
2553
  featureSet.add("assets.fonts@1");
2401
2554
  }
2555
+ if (typeof options?.assetSource?.registerFont === "function" && typeof options?.assetSource?.clearSceneFonts === "function") {
2556
+ featureSet.add("assets.registerFont@1");
2557
+ }
2402
2558
  if (options?.consent) {
2403
2559
  featureSet.add("network.consent@1");
2404
2560
  }
@@ -2703,7 +2859,7 @@ function inMemorySecretStore() {
2703
2859
  };
2704
2860
  }
2705
2861
  var seqCounter = 1;
2706
- function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews, engineShaper = false) {
2862
+ function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews, overlayLayers, engineShaper = false) {
2707
2863
  const protocol = worker.protocolVersion;
2708
2864
  const listeners = /* @__PURE__ */ new Set();
2709
2865
  const fanOut = (reply) => {
@@ -2853,6 +3009,15 @@ function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews, engin
2853
3009
  toolPreview = toolPreviews ? toolPreviews[0] : null;
2854
3010
  onToolPreviews(toolPreviews);
2855
3011
  onToolPreview(toolPreview);
3012
+ },
3013
+ // W-20 — the retained layers. A Map keeps first-insertion order,
3014
+ // which is the stack order the editor renders; a re-set keeps the
3015
+ // key's place.
3016
+ setOverlayLayer(key, shapes) {
3017
+ overlayLayers.set(key, shapes);
3018
+ },
3019
+ removeOverlayLayer(key) {
3020
+ overlayLayers.delete(key);
2856
3021
  }
2857
3022
  },
2858
3023
  // No tool spine + no content caret headlessly — both are inert
@@ -2887,6 +3052,7 @@ async function createHeadlessHost(options = {}) {
2887
3052
  const contributions = [];
2888
3053
  let lastPreview = null;
2889
3054
  let lastPreviews = null;
3055
+ const overlayLayerMap = /* @__PURE__ */ new Map();
2890
3056
  const { editor, fanOut } = makeEngineEditor(
2891
3057
  worker,
2892
3058
  contributions,
@@ -2896,6 +3062,7 @@ async function createHeadlessHost(options = {}) {
2896
3062
  (value) => {
2897
3063
  lastPreviews = value;
2898
3064
  },
3065
+ overlayLayerMap,
2899
3066
  options.engineShaper === true
2900
3067
  );
2901
3068
  const loadDirect = (bytes) => {
@@ -2967,6 +3134,9 @@ async function createHeadlessHost(options = {}) {
2967
3134
  // headlessly (the editor's real adapter currently serves null;
2968
3135
  // DESIGN.md §13.4). Absent → the no-bytes door.
2969
3136
  assetSource: options.assetSource,
3137
+ // W-19 — `enterEditContext()` below delivers the entering point the
3138
+ // way the editor shell does, so the harness vouches for it.
3139
+ editContextEnterPoint: true,
2970
3140
  documents: documentsBackend,
2971
3141
  // Record the SCHEMA verbatim at registration — the panel registry
2972
3142
  // only ever sees the synthesized React panel, so the conformance
@@ -3082,12 +3252,26 @@ async function createHeadlessHost(options = {}) {
3082
3252
  lastToolPreviews() {
3083
3253
  return lastPreviews;
3084
3254
  },
3255
+ overlayLayers() {
3256
+ return [...overlayLayerMap].map(([key, shapes]) => ({ key, shapes }));
3257
+ },
3258
+ enterEditContext(ctx) {
3259
+ const entry = contributions.find(
3260
+ (c) => c.kind === "editContext" && c.id === ctx.type
3261
+ );
3262
+ if (!entry) return false;
3263
+ entry.value.onEnter?.(ctx);
3264
+ return true;
3265
+ },
3085
3266
  bindingProviders,
3086
3267
  willSave,
3087
3268
  toolSettings,
3088
3269
  async load(idml) {
3089
3270
  const reply = loadDirect(idml);
3090
- if (reply.kind === "documentLoaded") return reply.payload.pageIds;
3271
+ if (reply.kind === "documentLoaded") {
3272
+ fanOut(reply);
3273
+ return reply.payload.pageIds;
3274
+ }
3091
3275
  throw loadFailure(reply);
3092
3276
  },
3093
3277
  openedDocuments() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paged-media/plugin-sdk",
3
- "version": "0.2.41-canary.0",
3
+ "version": "0.2.42-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.41-canary.0"
14
+ "@paged-media/plugin-api": "0.2.42-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.69.0",
25
+ "@paged-media/canvas-wasm": "0.70.0",
26
26
  "@types/node": "20.19.39",
27
27
  "@types/react": "^18.3.31",
28
28
  "react": "^18.3.1",