@embedpdf/viewer-chrome 3.0.0-next.10 → 3.0.0-next.12

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.js CHANGED
@@ -8,17 +8,20 @@ import { pageEditPlugin } from "@embedpdf/react/page-edit";
8
8
  import { InteractionToken, InteractionToken as InteractionToken$1, feedbackPlugin, interactionPlugin, useTool, useToolCursor, vibrationFeedback } from "@embedpdf/react/interaction";
9
9
  import { SelectionClipboard, SelectionHandles, SelectionLayer, SelectionMenu, SelectionToken, selectionPlugin } from "@embedpdf/react/selection";
10
10
  import { AnnotationLayer, AnnotationToken, AnnotationToken as AnnotationToken$1, annotationPlugin, refKey, useAnnotation, useAnnotationDefaults, useAnnotationSelected, useAnnotationSelection, useCommentThreads, useComments, useCommentsHydration, useFilePickerProvider, useSelectionFlags, useSelectionProps } from "@embedpdf/react/annotation";
11
+ import { StampToken, indexedDbByteStore, persistStampLibraries, restoreStampLibraries, stampPlugin, useArmStampAsset, useStamp, useStampAssetPreviewUrl, useStampAssets, useStampLibraries } from "@embedpdf/react/stamp";
11
12
  import { RedactionToken, RedactionToken as RedactionToken$1, redactionPlugin, usePendingRedactions, useRedaction } from "@embedpdf/react/redaction";
12
- import { FormToken, formPlugin, formWidgetRenderer, useFormScriptingProvider } from "@embedpdf/react/form";
13
+ import { actionsPlugin, useActionsUiAdapter } from "@embedpdf/react/actions";
14
+ import { FormToken, formPlugin, formWidgetRenderer } from "@embedpdf/react/form";
13
15
  import { LinkLayer, linkPlugin } from "@embedpdf/react/link";
14
16
  import { SearchLayer, SearchToken, SearchToken as SearchToken$1, searchPlugin, useSearch, useSearchState, validateSearchQuery } from "@embedpdf/react/search";
15
- import { I18nToken, i18nPlugin, negotiateLocale, useT } from "@embedpdf/react/i18n";
17
+ import { I18nToken, i18nPlugin, negotiateLocale, useLocale, useT } from "@embedpdf/react/i18n";
16
18
  import { CommandsToken, CommandsToken as CommandsToken$1, commandsPlugin, resolvedCommandsEqual, useCommand, useCommandShortcuts, useCommands } from "@embedpdf/react/commands";
17
19
  import { ShellToken, ShellToken as ShellToken$1, shellPlugin, useMenus, useSurface } from "@embedpdf/react/shell";
18
20
  import { Toolbar, addItem, chromeHelpers, chromeHelpers as chromeHelpers$1, custom, defineChrome, group, item, removeItems, replaceItem, useStripView, validateChrome, validateChrome as validateChrome$1 } from "@embedpdf/react/toolbar";
19
21
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
20
22
  import { Scrollbar } from "@embedpdf/react/scrollbar";
21
23
  import { AnnotationMenu } from "@embedpdf/react/annotation-menu";
24
+ import { LOCALES, loadDefaultLibrary } from "@embedpdf/default-stamps/library";
22
25
  import { MetadataToken } from "@embedpdf/react/metadata";
23
26
  //#region src/config/stage.ts
24
27
  /**
@@ -33,24 +36,23 @@ const ThumbsStageToken = createCapabilityToken("stage-thumbs");
33
36
  //#endregion
34
37
  //#region src/config/demo-tools.plugin.ts
35
38
  /**
36
- * Example-only: register the insert / redact authoring tools as INERT
37
- * interaction tools. v3 doesn't yet ship signature or redaction plugins, but
38
- * this is a CHROME demo — its job is selection state and layout, not the
39
- * tool's effect. Registering them as real (behaviourless) tools lets every
40
- * tool button in every mode band go through the SAME path
41
- * (`InteractionToken.activateTool`), so `active` is uniform and honest: the
42
- * button really is the active tool; it just has no handler behind it.
39
+ * Example-only: register the still-unimplemented authoring tools as INERT
40
+ * interaction tools. v3 doesn't yet ship a signature plugin, but this is a
41
+ * CHROME demo — its job is selection state and layout, not the tool's effect.
42
+ * Registering them as real (behaviourless) tools lets every tool button in
43
+ * every mode band go through the SAME path (`InteractionToken.activateTool`),
44
+ * so `active` is uniform and honest: the button really is the active tool; it
45
+ * just has no handler behind it.
43
46
  *
44
- * The annotate + shapes tools are NOT here — those are real tools the
45
- * annotation plugin registers (highlight, ink, square, circle, …) — and
46
- * neither are the form palette tools (the form plugin's draw-to-place).
47
+ * Everything else in the Insert band is REAL now: stamps come from the stamp
48
+ * plugin's libraries (armed by the stamps sidebar), and image/attachment are
49
+ * the annotation plugin's click-then-pick tools. The annotate + shapes tools
50
+ * were never here either — nor the form palette (the form plugin's
51
+ * draw-to-place).
47
52
  */
48
53
  const INERT_TOOLS = [{
49
54
  id: "signature",
50
55
  cursor: "copy"
51
- }, {
52
- id: "image",
53
- cursor: "copy"
54
56
  }];
55
57
  const demoToolsPlugin = () => definePlugin({
56
58
  id: "demo-tools",
@@ -145,6 +147,7 @@ const en = {
145
147
  delete: "Delete",
146
148
  group: "Group",
147
149
  ungroup: "Ungroup",
150
+ stampFromSelection: "Make stamp",
148
151
  strip: "Annotation actions"
149
152
  },
150
153
  selection: {
@@ -269,7 +272,23 @@ const en = {
269
272
  redactApplying: "Applying…",
270
273
  redactLabelTitle: "Redaction label",
271
274
  redactLabelPlaceholder: "e.g. REDACTED",
272
- redactLabelRepeat: "Repeat label across region"
275
+ redactLabelRepeat: "Repeat label across region",
276
+ stampsTitle: "Stamps",
277
+ stampsAll: "All stamps",
278
+ stampsLoading: "Loading stamps…",
279
+ stampsEmpty: "No stamps yet. Import a PDF to turn each of its pages into a stamp.",
280
+ stampsRemoveStamp: "Remove stamp",
281
+ stampsArmedHint: "Click a page to place the stamp.",
282
+ stampsImport: "Import PDF…",
283
+ stampsImporting: "Importing…",
284
+ stampsImportHint: "Import a PDF as a stamp library — each page becomes one stamp.",
285
+ stampsRemoveLibrary: "Remove library",
286
+ stampsExportLibrary: "Export library as PDF",
287
+ stampsCustomLibrary: "My stamps",
288
+ stampsCustomLabel: "Custom stamp",
289
+ stampsError: "The built-in stamps could not be loaded.",
290
+ stampsArmError: "That stamp could not be armed.",
291
+ stampsImportError: "That PDF could not be imported as a stamp library."
273
292
  }
274
293
  }
275
294
  };
@@ -286,7 +305,8 @@ const en = {
286
305
  */
287
306
  const ViewerConfigContext = createContext({
288
307
  chrome: defaultChrome,
289
- icons: {}
308
+ icons: {},
309
+ stamps: {}
290
310
  });
291
311
  const ViewerConfigProvider = ViewerConfigContext.Provider;
292
312
  function useChromeSchema() {
@@ -301,6 +321,9 @@ function useStripSchema(id) {
301
321
  function useCustomIcons() {
302
322
  return useContext(ViewerConfigContext).icons;
303
323
  }
324
+ function useStampsConfig() {
325
+ return useContext(ViewerConfigContext).stamps;
326
+ }
304
327
  //#endregion
305
328
  //#region src/handle.ts
306
329
  function createViewerHandle(kernel) {
@@ -3440,6 +3463,310 @@ function SearchPanel() {
3440
3463
  });
3441
3464
  }
3442
3465
  //#endregion
3466
+ //#region src/config/default-stamps.ts
3467
+ /**
3468
+ * The viewer's BUILT-IN stamp library: `@embedpdf/default-stamps`, the
3469
+ * standard rubber stamps as one Acrobat-compatible PDF per locale.
3470
+ *
3471
+ * Delivery: the library ships INSIDE whatever ships this viewer. The package
3472
+ * exposes each locale as a lazy ES module (`@embedpdf/default-stamps/library`),
3473
+ * so it travels through the module graph like any other code — every bundler
3474
+ * splits it into a chunk served from the app's own origin, the CDN snippet
3475
+ * carries it as a sibling chunk in its folder, and nothing is fetched from a
3476
+ * third party. `stamps.defaultLibrary` overrides that:
3477
+ * - `false` → no built-in library, no request (air-gapped);
3478
+ * - a URL template → exactly that (`{locale}` slot), for self-hosted copies.
3479
+ *
3480
+ * Loading is LAZY (first open of the stamps panel, never at boot) and
3481
+ * locale-aware: the chrome locale leads, the browser languages break ties
3482
+ * for the locales the chrome does not translate itself, and a locale change
3483
+ * while a default library is loaded swaps it for the new one. The file names
3484
+ * itself (`/Title`, `Id: embedpdf-standard`), so no overrides are passed.
3485
+ */
3486
+ /** The built-in library's PieceInfo id — excluded from persistence (it is
3487
+ * loaded again on every first open, in the locale of that moment). */
3488
+ const DEFAULT_LIBRARY_ID = "embedpdf-standard";
3489
+ /** Which shipped locale to show for the chrome's locale + the browser's. */
3490
+ function resolveStampsLocale(chromeLocale) {
3491
+ return negotiateLocale(LOCALES, [chromeLocale, ...typeof navigator !== "undefined" ? navigator.languages ?? [] : []]) ?? "en";
3492
+ }
3493
+ const fetchBytes = async (url) => {
3494
+ const response = await fetch(url);
3495
+ if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);
3496
+ return new Uint8Array(await response.arrayBuffer());
3497
+ };
3498
+ /** The library bytes for `locale`: your URL when you gave one, otherwise the
3499
+ * copy that shipped with the viewer. Never anything else. */
3500
+ const defaultLibraryBytes = (locale, source) => source ? fetchBytes(source.replace("{locale}", locale)) : loadDefaultLibrary(locale);
3501
+ /** Per workspace: the locale the default library was last brought to, and
3502
+ * the in-flight work — the panel mounts and unmounts with the sidebar, so
3503
+ * this cannot live in component state. */
3504
+ const loads = /* @__PURE__ */ new WeakMap();
3505
+ /**
3506
+ * Bring the built-in library to `locale`. Idempotent per workspace and
3507
+ * locale; a locale change swaps the loaded default (remove, then import);
3508
+ * a default the user removed in this session stays removed — the locale is
3509
+ * recorded, nothing is re-added. An embedder that seeded a library under
3510
+ * the same id is left alone.
3511
+ */
3512
+ function ensureDefaultLibrary(stamp, locale, source) {
3513
+ if (source === false) return Promise.resolve();
3514
+ const current = loads.get(stamp);
3515
+ if (current?.locale === locale) return current.promise;
3516
+ const run = async () => {
3517
+ await current?.promise.catch(() => void 0);
3518
+ const loaded = stamp.library(DEFAULT_LIBRARY_ID);
3519
+ if (loaded?.locale === locale) return;
3520
+ if (current && !loaded) return;
3521
+ if (loaded) await stamp.removeLibrary(DEFAULT_LIBRARY_ID);
3522
+ await stamp.importLibraryPdf(await defaultLibraryBytes(locale, source));
3523
+ };
3524
+ const promise = run();
3525
+ loads.set(stamp, {
3526
+ locale,
3527
+ promise
3528
+ });
3529
+ return promise;
3530
+ }
3531
+ //#endregion
3532
+ //#region src/ui/stamps-panel.tsx
3533
+ /**
3534
+ * The stamps sidebar (right panel) — v2's rubber-stamp sidebar on v3 parts.
3535
+ * The stamp plugin owns the libraries, the asset bytes and the cached
3536
+ * previews; this panel is the PICKER over them:
3537
+ *
3538
+ * a library <select> ("All stamps" + one per library, shown once there
3539
+ * are two) → a two-column grid of thumbnails → click a stamp → `armAsset`
3540
+ * arms the annotation plugin's stamp tool → hovering a page ghosts the
3541
+ * exact placement → each click on a page places one.
3542
+ *
3543
+ * Deliberately NOT a file dialog: picking arbitrary image bytes is the Image
3544
+ * tool's job (a click-then-pick tool, `insert:add-image`). The stamp button
3545
+ * opens a library; a library is a set of reusable, named assets — the two
3546
+ * gestures are different, so they are different controls.
3547
+ *
3548
+ * The panel stays open while you place, so a stamp can be dropped on several
3549
+ * pages and swapped without a round trip through the toolbar. A command can
3550
+ * open it ON a library (`annotation:stamp-from-selection` → "My stamps")
3551
+ * through the surface's open props.
3552
+ */
3553
+ const ALL = "all";
3554
+ /** The user's libraries live in the browser (IndexedDB): restored on first
3555
+ * open, written on every change. The built-in set is never stored — it is
3556
+ * fetched again next time, in the locale of that moment. */
3557
+ const store = typeof indexedDB === "undefined" ? null : indexedDbByteStore("embedpdf-stamps", { storeName: "libraries" });
3558
+ /** Restore runs ONCE per workspace — the panel mounts and unmounts with the
3559
+ * sidebar, so the guard cannot live in component state. */
3560
+ const restored = /* @__PURE__ */ new WeakMap();
3561
+ const restoreOnce = (stamp) => {
3562
+ let pending = restored.get(stamp);
3563
+ if (!pending) {
3564
+ pending = store ? restoreStampLibraries(stamp, store) : Promise.resolve();
3565
+ restored.set(stamp, pending);
3566
+ }
3567
+ return pending;
3568
+ };
3569
+ function StampsPanel() {
3570
+ const t = useT();
3571
+ const stamp = useStamp();
3572
+ const libraries = useStampLibraries();
3573
+ const assets = useStampAssets();
3574
+ const { armAsset } = useArmStampAsset();
3575
+ const { activeToolId } = useTool();
3576
+ const { locale } = useLocale();
3577
+ const { defaultLibrary } = useStampsConfig();
3578
+ const surface = useSurface("stamps");
3579
+ const fileRef = useRef(null);
3580
+ const [armedId, setArmedId] = useState(null);
3581
+ const [busy, setBusy] = useState(null);
3582
+ const [error, setError] = useState(null);
3583
+ const [picked, setPicked] = useState(ALL);
3584
+ const openedOn = surface.props?.libraryId;
3585
+ useEffect(() => {
3586
+ if (typeof openedOn === "string") setPicked(openedOn);
3587
+ }, [openedOn]);
3588
+ useEffect(() => {
3589
+ let live = true;
3590
+ setBusy("loading");
3591
+ restoreOnce(stamp).then(() => ensureDefaultLibrary(stamp, resolveStampsLocale(locale), defaultLibrary)).catch((err) => {
3592
+ console.error("[embedpdf] default stamps failed:", err);
3593
+ if (live) setError(t("demo.stampsError"));
3594
+ }).finally(() => live && setBusy(null));
3595
+ return () => {
3596
+ live = false;
3597
+ };
3598
+ }, [stamp, locale]);
3599
+ useEffect(() => store ? persistStampLibraries(stamp, store, { except: [DEFAULT_LIBRARY_ID] }) : void 0, [stamp]);
3600
+ useEffect(() => {
3601
+ if (activeToolId !== "stamp") setArmedId(null);
3602
+ }, [activeToolId]);
3603
+ const arm = (asset) => {
3604
+ setError(null);
3605
+ setArmedId(asset.id);
3606
+ armAsset(asset.id).catch((err) => {
3607
+ console.error("[embedpdf] arm stamp failed:", err);
3608
+ setArmedId(null);
3609
+ setError(t("demo.stampsArmError"));
3610
+ });
3611
+ };
3612
+ const importPdf = (file) => {
3613
+ setError(null);
3614
+ setBusy("importing");
3615
+ stamp.importLibraryPdf(file, { name: file.name.replace(/\.pdf$/i, "") }).then((id) => setPicked(id)).catch((err) => {
3616
+ console.error("[embedpdf] stamp library import failed:", err);
3617
+ setError(t("demo.stampsImportError"));
3618
+ }).finally(() => setBusy(null));
3619
+ };
3620
+ /** The library as the PDF it is — title, registry, artwork — for Acrobat
3621
+ * or another viewer. */
3622
+ const exportPdf = (libraryId, name) => {
3623
+ const bytes = stamp.exportLibrary(libraryId);
3624
+ if (!bytes) return;
3625
+ const blob = new Blob([bytes], { type: "application/pdf" });
3626
+ const url = URL.createObjectURL(blob);
3627
+ const a = document.createElement("a");
3628
+ a.href = url;
3629
+ a.download = `${name || "stamps"}.pdf`;
3630
+ a.click();
3631
+ URL.revokeObjectURL(url);
3632
+ };
3633
+ const selectedId = libraries.length === 1 ? libraries[0].id : libraries.some((library) => library.id === picked) ? picked : ALL;
3634
+ const selected = selectedId === ALL ? null : libraries.find((l) => l.id === selectedId) ?? null;
3635
+ const shown = selectedId === ALL ? assets : assets.filter((a) => a.libraryId === selectedId);
3636
+ const isDefault = (libraryId) => libraryId === DEFAULT_LIBRARY_ID;
3637
+ return /* @__PURE__ */ jsxs("div", {
3638
+ className: "flex min-h-0 flex-1 flex-col",
3639
+ children: [
3640
+ libraries.length > 0 && /* @__PURE__ */ jsxs("div", {
3641
+ className: "border-border-subtle flex items-center gap-1 border-b p-2",
3642
+ children: [libraries.length > 1 ? /* @__PURE__ */ jsxs("select", {
3643
+ value: selectedId,
3644
+ onChange: (event) => setPicked(event.target.value),
3645
+ "aria-label": t("demo.stampsTitle"),
3646
+ className: "border-border bg-surface text-fg min-w-0 flex-1 rounded border px-2 py-1.5 text-sm",
3647
+ children: [/* @__PURE__ */ jsx("option", {
3648
+ value: ALL,
3649
+ children: t("demo.stampsAll")
3650
+ }), libraries.map((library) => /* @__PURE__ */ jsx("option", {
3651
+ value: library.id,
3652
+ children: library.name
3653
+ }, library.id))]
3654
+ }) : /* @__PURE__ */ jsx("span", {
3655
+ className: "text-fg-muted min-w-0 flex-1 truncate px-1 text-xs font-semibold uppercase tracking-wide",
3656
+ children: libraries[0].name
3657
+ }), selected && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
3658
+ type: "button",
3659
+ onClick: () => exportPdf(selected.id, selected.name),
3660
+ title: t("demo.stampsExportLibrary"),
3661
+ className: "text-fg-muted hover:text-fg grid h-7 w-7 shrink-0 place-items-center rounded",
3662
+ children: /* @__PURE__ */ jsx(Icon, {
3663
+ name: "download",
3664
+ size: 14
3665
+ })
3666
+ }), !isDefault(selected.id) && /* @__PURE__ */ jsx("button", {
3667
+ type: "button",
3668
+ onClick: () => void stamp.removeLibrary(selected.id),
3669
+ title: t("demo.stampsRemoveLibrary"),
3670
+ className: "text-fg-muted hover:text-fg grid h-7 w-7 shrink-0 place-items-center rounded",
3671
+ children: /* @__PURE__ */ jsx(Icon, {
3672
+ name: "trash",
3673
+ size: 14
3674
+ })
3675
+ })] })]
3676
+ }),
3677
+ /* @__PURE__ */ jsx("div", {
3678
+ className: "min-h-0 flex-1 overflow-y-auto p-3",
3679
+ children: busy === "loading" && shown.length === 0 ? /* @__PURE__ */ jsxs("div", {
3680
+ className: "flex flex-col items-center gap-3 px-4 py-10 text-center",
3681
+ children: [/* @__PURE__ */ jsx("div", { className: "border-border-subtle border-t-accent h-6 w-6 animate-spin rounded-full border-2" }), /* @__PURE__ */ jsx("p", {
3682
+ className: "text-fg-muted text-sm",
3683
+ children: t("demo.stampsLoading")
3684
+ })]
3685
+ }) : shown.length === 0 ? /* @__PURE__ */ jsxs("div", {
3686
+ className: "flex flex-col items-center gap-3 px-4 py-10 text-center",
3687
+ children: [/* @__PURE__ */ jsx(Icon, {
3688
+ name: "rubberStamp",
3689
+ size: 32,
3690
+ className: "text-fg-muted"
3691
+ }), /* @__PURE__ */ jsx("p", {
3692
+ className: "text-fg-muted text-sm",
3693
+ children: t("demo.stampsEmpty")
3694
+ })]
3695
+ }) : /* @__PURE__ */ jsx("ul", {
3696
+ className: "grid grid-cols-2 gap-3",
3697
+ children: shown.map((asset) => /* @__PURE__ */ jsxs("li", {
3698
+ className: "group relative",
3699
+ children: [/* @__PURE__ */ jsx(StampAssetCell, {
3700
+ asset,
3701
+ armed: armedId === asset.id,
3702
+ onArm: () => arm(asset)
3703
+ }), !isDefault(asset.libraryId) && /* @__PURE__ */ jsx("button", {
3704
+ type: "button",
3705
+ onClick: () => void stamp.removeAsset(asset.id),
3706
+ title: t("demo.stampsRemoveStamp"),
3707
+ className: "bg-surface border-border text-fg-muted hover:text-fg absolute right-1 top-1 grid h-5 w-5 place-items-center rounded-full border opacity-0 shadow-sm focus:opacity-100 group-hover:opacity-100",
3708
+ children: /* @__PURE__ */ jsx(Icon, {
3709
+ name: "x",
3710
+ size: 12
3711
+ })
3712
+ })]
3713
+ }, asset.id))
3714
+ })
3715
+ }),
3716
+ (error || armedId) && /* @__PURE__ */ jsx("div", {
3717
+ className: `border-border-subtle shrink-0 border-t px-3 py-2 text-xs ${error ? "text-red-600" : "text-fg-muted"}`,
3718
+ children: error ?? t("demo.stampsArmedHint")
3719
+ }),
3720
+ /* @__PURE__ */ jsxs("div", {
3721
+ className: "border-border-subtle shrink-0 border-t p-3",
3722
+ children: [/* @__PURE__ */ jsxs("button", {
3723
+ type: "button",
3724
+ disabled: busy != null,
3725
+ onClick: () => fileRef.current?.click(),
3726
+ title: t("demo.stampsImportHint"),
3727
+ className: "border-border text-fg hover:bg-hover flex w-full items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm disabled:opacity-40",
3728
+ children: [/* @__PURE__ */ jsx(Icon, {
3729
+ name: "rubberStampPlus",
3730
+ size: 16
3731
+ }), busy === "importing" ? t("demo.stampsImporting") : t("demo.stampsImport")]
3732
+ }), /* @__PURE__ */ jsx("input", {
3733
+ ref: fileRef,
3734
+ type: "file",
3735
+ accept: "application/pdf",
3736
+ className: "hidden",
3737
+ onChange: (e) => {
3738
+ const file = e.currentTarget.files?.[0];
3739
+ e.currentTarget.value = "";
3740
+ if (file) importPdf(file);
3741
+ }
3742
+ })]
3743
+ })
3744
+ ]
3745
+ });
3746
+ }
3747
+ /** One grid cell: the plugin's cached preview through the object-URL hook
3748
+ * (the DOM resource's lifetime belongs to the framework layer, not the
3749
+ * store). Image only, as v2 — the artwork IS the label; the label is the
3750
+ * tooltip for the curious. */
3751
+ function StampAssetCell({ asset, armed, onArm }) {
3752
+ const url = useStampAssetPreviewUrl(asset.id);
3753
+ return /* @__PURE__ */ jsx("button", {
3754
+ type: "button",
3755
+ onClick: onArm,
3756
+ title: asset.label,
3757
+ "aria-pressed": armed,
3758
+ className: `flex aspect-square w-full items-center justify-center rounded-md border p-2 ${armed ? "border-accent bg-accent-light ring-accent ring-2" : "border-border-subtle hover:border-border hover:bg-hover"}`,
3759
+ children: url ? /* @__PURE__ */ jsx("img", {
3760
+ src: url,
3761
+ alt: asset.label,
3762
+ className: "max-h-full max-w-full object-contain"
3763
+ }) : /* @__PURE__ */ jsx("span", {
3764
+ className: "text-fg truncate text-sm",
3765
+ children: asset.label
3766
+ })
3767
+ });
3768
+ }
3769
+ //#endregion
3443
3770
  //#region src/ui/panels.tsx
3444
3771
  /**
3445
3772
  * The static chrome around the toolbars: the left/right sidebars (shell
@@ -3533,10 +3860,11 @@ function RightSidebar() {
3533
3860
  const comment = useSurface("comment");
3534
3861
  const style = useSurface("annotation-style");
3535
3862
  const redaction = useSurface("redaction");
3536
- const active = search.isOpen ? "search" : comment.isOpen ? "comment" : style.isOpen ? "style" : redaction.isOpen ? "redaction" : null;
3863
+ const stamps = useSurface("stamps");
3864
+ const active = search.isOpen ? "search" : comment.isOpen ? "comment" : style.isOpen ? "style" : redaction.isOpen ? "redaction" : stamps.isOpen ? "stamps" : null;
3537
3865
  if (!active) return null;
3538
- const titleKey = active === "search" ? "demo.searchTitle" : active === "comment" ? "demo.commentsTitle" : active === "redaction" ? "demo.redactionTitle" : "demo.styleTitle";
3539
- const close = active === "search" ? search.close : active === "comment" ? comment.close : active === "redaction" ? redaction.close : style.close;
3866
+ const titleKey = active === "search" ? "demo.searchTitle" : active === "comment" ? "demo.commentsTitle" : active === "redaction" ? "demo.redactionTitle" : active === "stamps" ? "demo.stampsTitle" : "demo.styleTitle";
3867
+ const close = active === "search" ? search.close : active === "comment" ? comment.close : active === "redaction" ? redaction.close : active === "stamps" ? stamps.close : style.close;
3540
3868
  return /* @__PURE__ */ jsxs("aside", {
3541
3869
  className: "border-border-subtle bg-surface flex w-72 shrink-0 flex-col border-l",
3542
3870
  children: [/* @__PURE__ */ jsxs("div", {
@@ -3553,7 +3881,7 @@ function RightSidebar() {
3553
3881
  size: 16
3554
3882
  })
3555
3883
  })]
3556
- }), active === "style" ? /* @__PURE__ */ jsx(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ jsx(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ jsx(RedactionPanel, {}) : /* @__PURE__ */ jsx(CommentsPanel, {}, documentId ?? "none")]
3884
+ }), active === "style" ? /* @__PURE__ */ jsx(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ jsx(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ jsx(RedactionPanel, {}) : active === "stamps" ? /* @__PURE__ */ jsx(StampsPanel, {}) : /* @__PURE__ */ jsx(CommentsPanel, {}, documentId ?? "none")]
3557
3885
  });
3558
3886
  }
3559
3887
  function PageControls() {
@@ -3873,7 +4201,7 @@ function DocumentArea({ children }) {
3873
4201
  function Shell() {
3874
4202
  useCommandShortcuts();
3875
4203
  useFilePickerProvider();
3876
- useFormScriptingProvider();
4204
+ useActionsUiAdapter();
3877
4205
  const schema = useChromeSchema();
3878
4206
  const frame = schema.frame ?? {};
3879
4207
  const toolbarEdge = frame.toolbar ?? "top";
@@ -3953,7 +4281,7 @@ function Shell() {
3953
4281
  * changing these props on a mounted viewer does not rebuild the workspace.
3954
4282
  */
3955
4283
  /** Lazy built-in packs — a strings override for one of these wraps its loader. */
3956
- const BUILTIN_LOADERS = { es: () => import("./es-aQlZ3hcL.js").then((m) => m.es) };
4284
+ const BUILTIN_LOADERS = { es: () => import("./es-DT5ZoB99.js").then((m) => m.es) };
3957
4285
  const isDict = (v) => typeof v === "object" && v !== null;
3958
4286
  /**
3959
4287
  * Deep-merge `over` into `base` (immutably), expanding dotted keys —
@@ -3997,7 +4325,7 @@ function Booting() {
3997
4325
  })
3998
4326
  });
3999
4327
  }
4000
- function FullViewer({ engine, initialDocuments, fallback, commands, icons, strings, locale = "auto", disabledCategories, chrome, theme, themeTarget, onViewer }) {
4328
+ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strings, locale = "auto", disabledCategories, chrome, stamps, theme, themeTarget, onViewer }) {
4001
4329
  const [resolved] = useState(() => {
4002
4330
  const byId = new Map(defaultCommands.map((c) => [c.id, c]));
4003
4331
  for (const c of commands ?? []) byId.set(c.id, c);
@@ -4028,6 +4356,7 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
4028
4356
  commands: resolvedCommands,
4029
4357
  chrome: resolvedChrome,
4030
4358
  icons: icons ?? {},
4359
+ stamps: stamps ?? {},
4031
4360
  i18n: {
4032
4361
  locales,
4033
4362
  loaders,
@@ -4076,8 +4405,23 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
4076
4405
  start: "none",
4077
4406
  end: "open-arrow"
4078
4407
  } }
4408
+ }, {
4409
+ id: "image",
4410
+ extends: "stamp",
4411
+ source: {
4412
+ kind: "prompt",
4413
+ accept: "image/png,image/jpeg"
4414
+ }
4079
4415
  }] }),
4080
- formPlugin({ scripting: { enabled: true } }),
4416
+ stampPlugin(),
4417
+ actionsPlugin({ javascript: {
4418
+ enabled: true,
4419
+ identity: {
4420
+ name: "John Doe",
4421
+ corporation: "Acme Inc"
4422
+ }
4423
+ } }),
4424
+ formPlugin(),
4081
4425
  linkPlugin(),
4082
4426
  redactionPlugin(),
4083
4427
  searchPlugin(),
@@ -4111,6 +4455,6 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
4111
4455
  });
4112
4456
  }
4113
4457
  //#endregion
4114
- export { AnnotationToken, CommandsToken, DocumentsToken, FormToken, FullViewer, I18nToken, InteractionToken, MetadataToken, RedactionToken, SearchToken, SelectionToken, ShellToken, StageToken, addItem, chromeHelpers, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, item, removeItems, replaceItem, themeConfigOf, validateChrome };
4458
+ export { AnnotationToken, CommandsToken, DocumentsToken, FormToken, FullViewer, I18nToken, InteractionToken, MetadataToken, RedactionToken, SearchToken, SelectionToken, ShellToken, StageToken, StampToken, addItem, chromeHelpers, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, item, removeItems, replaceItem, themeConfigOf, validateChrome };
4115
4459
 
4116
4460
  //# sourceMappingURL=index.js.map