@embedpdf/viewer-chrome 3.0.0-next.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/LICENSE +215 -0
- package/README.md +317 -0
- package/dist/chrome.cjs +228 -0
- package/dist/chrome.cjs.map +1 -0
- package/dist/chrome.d.cts +36 -0
- package/dist/chrome.d.ts +36 -0
- package/dist/chrome.js +224 -0
- package/dist/chrome.js.map +1 -0
- package/dist/commands.cjs +497 -0
- package/dist/commands.cjs.map +1 -0
- package/dist/commands.d.cts +22 -0
- package/dist/commands.d.ts +22 -0
- package/dist/commands.js +494 -0
- package/dist/commands.js.map +1 -0
- package/dist/es-C5084JO6.cjs +166 -0
- package/dist/es-C5084JO6.cjs.map +1 -0
- package/dist/es-D0PzYE9Z.js +166 -0
- package/dist/es-D0PzYE9Z.js.map +1 -0
- package/dist/index.cjs +3700 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +121 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +3529 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
- package/src/styles.css +132 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3700 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
const require_chrome = require("./chrome.cjs");
|
|
25
|
+
const require_commands = require("./commands.cjs");
|
|
26
|
+
let react = require("react");
|
|
27
|
+
react = __toESM(react, 1);
|
|
28
|
+
let _embedpdf_react_runtime = require("@embedpdf/react/runtime");
|
|
29
|
+
let _embedpdf_react_stage = require("@embedpdf/react/stage");
|
|
30
|
+
let _embedpdf_react_render = require("@embedpdf/react/render");
|
|
31
|
+
let _embedpdf_react_page_edit = require("@embedpdf/react/page-edit");
|
|
32
|
+
let _embedpdf_react_interaction = require("@embedpdf/react/interaction");
|
|
33
|
+
let _embedpdf_react_selection = require("@embedpdf/react/selection");
|
|
34
|
+
let _embedpdf_react_annotation = require("@embedpdf/react/annotation");
|
|
35
|
+
let _embedpdf_react_redaction = require("@embedpdf/react/redaction");
|
|
36
|
+
let _embedpdf_react_form = require("@embedpdf/react/form");
|
|
37
|
+
let _embedpdf_react_link = require("@embedpdf/react/link");
|
|
38
|
+
let _embedpdf_react_search = require("@embedpdf/react/search");
|
|
39
|
+
let _embedpdf_react_i18n = require("@embedpdf/react/i18n");
|
|
40
|
+
let _embedpdf_react_commands = require("@embedpdf/react/commands");
|
|
41
|
+
let _embedpdf_react_shell = require("@embedpdf/react/shell");
|
|
42
|
+
let _embedpdf_react_toolbar = require("@embedpdf/react/toolbar");
|
|
43
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
44
|
+
let _embedpdf_react_scrollbar = require("@embedpdf/react/scrollbar");
|
|
45
|
+
let _embedpdf_react_annotation_menu = require("@embedpdf/react/annotation-menu");
|
|
46
|
+
let _embedpdf_react_metadata = require("@embedpdf/react/metadata");
|
|
47
|
+
//#region src/config/stage.ts
|
|
48
|
+
/**
|
|
49
|
+
* The Stage is a LENS: one document can be viewed through several at once. The
|
|
50
|
+
* thumbnail sidebar is a second lens over the SAME document — a single-column,
|
|
51
|
+
* fixed-zoom grid with its own camera, fully independent of the main view.
|
|
52
|
+
*
|
|
53
|
+
* The token lives here (not in App.tsx) because two places need it: App wires the
|
|
54
|
+
* `stagePlugin` lens with it, and the sidebar renders `<Stage token={...}>` off it.
|
|
55
|
+
*/
|
|
56
|
+
const ThumbsStageToken = (0, _embedpdf_react_runtime.createCapabilityToken)("stage-thumbs");
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/config/demo-tools.plugin.ts
|
|
59
|
+
/**
|
|
60
|
+
* Example-only: register the insert / redact authoring tools as INERT
|
|
61
|
+
* interaction tools. v3 doesn't yet ship signature or redaction plugins, but
|
|
62
|
+
* this is a CHROME demo — its job is selection state and layout, not the
|
|
63
|
+
* tool's effect. Registering them as real (behaviourless) tools lets every
|
|
64
|
+
* tool button in every mode band go through the SAME path
|
|
65
|
+
* (`InteractionToken.activateTool`), so `active` is uniform and honest: the
|
|
66
|
+
* button really is the active tool; it just has no handler behind it.
|
|
67
|
+
*
|
|
68
|
+
* The annotate + shapes tools are NOT here — those are real tools the
|
|
69
|
+
* annotation plugin registers (highlight, ink, square, circle, …) — and
|
|
70
|
+
* neither are the form palette tools (the form plugin's draw-to-place).
|
|
71
|
+
*/
|
|
72
|
+
const INERT_TOOLS = [{
|
|
73
|
+
id: "signature",
|
|
74
|
+
cursor: "copy"
|
|
75
|
+
}, {
|
|
76
|
+
id: "image",
|
|
77
|
+
cursor: "copy"
|
|
78
|
+
}];
|
|
79
|
+
const demoToolsPlugin = () => (0, _embedpdf_react_runtime.definePlugin)({
|
|
80
|
+
id: "demo-tools",
|
|
81
|
+
scope: "document",
|
|
82
|
+
requires: [_embedpdf_react_interaction.InteractionToken],
|
|
83
|
+
init: (ctx) => {
|
|
84
|
+
const interaction = ctx.get(_embedpdf_react_interaction.InteractionToken);
|
|
85
|
+
for (const tool of INERT_TOOLS) interaction.registerTool({
|
|
86
|
+
id: tool.id,
|
|
87
|
+
cursor: tool.cursor,
|
|
88
|
+
enables: /* @__PURE__ */ new Set()
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/locales/en.ts
|
|
94
|
+
/**
|
|
95
|
+
* English pack. Strings belong to the PRODUCT, not the plugins — the
|
|
96
|
+
* `commands.*` namespace mirrors the v2 snippet packs so translations carry
|
|
97
|
+
* over; `demo.*` is this example's own chrome.
|
|
98
|
+
*/
|
|
99
|
+
const en = {
|
|
100
|
+
code: "en",
|
|
101
|
+
name: "English",
|
|
102
|
+
translations: {
|
|
103
|
+
commands: {
|
|
104
|
+
zoom: {
|
|
105
|
+
in: "Zoom In",
|
|
106
|
+
out: "Zoom Out",
|
|
107
|
+
fitPage: "Fit to Page",
|
|
108
|
+
fitWidth: "Fit to Width",
|
|
109
|
+
automatic: "Automatic",
|
|
110
|
+
menu: "Zoom",
|
|
111
|
+
level: "Zoom Level",
|
|
112
|
+
p50: "50%",
|
|
113
|
+
p100: "100%",
|
|
114
|
+
p150: "150%",
|
|
115
|
+
p200: "200%",
|
|
116
|
+
p400: "400%"
|
|
117
|
+
},
|
|
118
|
+
pan: "Pan",
|
|
119
|
+
pointer: "Pointer",
|
|
120
|
+
sidebar: "Sidebar",
|
|
121
|
+
search: "Search",
|
|
122
|
+
comment: "Comment",
|
|
123
|
+
style: "Style",
|
|
124
|
+
menu: "Menu",
|
|
125
|
+
viewControls: "View Controls",
|
|
126
|
+
download: "Download",
|
|
127
|
+
print: "Print",
|
|
128
|
+
fullscreen: "Full Screen",
|
|
129
|
+
spread: {
|
|
130
|
+
group: "Page Layout",
|
|
131
|
+
none: "Single Page",
|
|
132
|
+
odd: "Two Page",
|
|
133
|
+
even: "Cover Page"
|
|
134
|
+
},
|
|
135
|
+
scroll: {
|
|
136
|
+
group: "Scrolling",
|
|
137
|
+
vertical: "Vertical",
|
|
138
|
+
horizontal: "Horizontal"
|
|
139
|
+
},
|
|
140
|
+
rotate: {
|
|
141
|
+
group: "Rotate",
|
|
142
|
+
clockwise: "Rotate Clockwise",
|
|
143
|
+
counterclockwise: "Rotate Counter-Clockwise"
|
|
144
|
+
},
|
|
145
|
+
mode: {
|
|
146
|
+
group: "Mode",
|
|
147
|
+
view: "View",
|
|
148
|
+
annotate: "Annotate",
|
|
149
|
+
shapes: "Shapes",
|
|
150
|
+
insert: "Insert",
|
|
151
|
+
form: "Form",
|
|
152
|
+
redact: "Redact"
|
|
153
|
+
},
|
|
154
|
+
annotate: {
|
|
155
|
+
highlight: "Highlight",
|
|
156
|
+
strikeout: "Strikeout",
|
|
157
|
+
underline: "Underline",
|
|
158
|
+
squiggly: "Squiggly",
|
|
159
|
+
ink: "Draw",
|
|
160
|
+
inkHighlight: "Ink Highlighter",
|
|
161
|
+
text: "Text",
|
|
162
|
+
insertText: "Insert Text",
|
|
163
|
+
replaceText: "Replace Text",
|
|
164
|
+
callout: "Callout",
|
|
165
|
+
note: "Comment",
|
|
166
|
+
link: "Link",
|
|
167
|
+
gotoLink: "Go to link",
|
|
168
|
+
removeLink: "Remove link",
|
|
169
|
+
delete: "Delete",
|
|
170
|
+
group: "Group",
|
|
171
|
+
ungroup: "Ungroup",
|
|
172
|
+
strip: "Annotation actions"
|
|
173
|
+
},
|
|
174
|
+
shapes: {
|
|
175
|
+
rectangle: "Rectangle",
|
|
176
|
+
circle: "Circle",
|
|
177
|
+
line: "Line",
|
|
178
|
+
arrow: "Arrow",
|
|
179
|
+
polygon: "Polygon",
|
|
180
|
+
polyline: "Polyline"
|
|
181
|
+
},
|
|
182
|
+
insert: {
|
|
183
|
+
stamp: "Rubber Stamp",
|
|
184
|
+
attachment: "Attach File",
|
|
185
|
+
signature: "Signature",
|
|
186
|
+
image: "Image"
|
|
187
|
+
},
|
|
188
|
+
form: {
|
|
189
|
+
textfield: "Text Field",
|
|
190
|
+
checkbox: "Checkbox",
|
|
191
|
+
radio: "Radio Button",
|
|
192
|
+
select: "Dropdown",
|
|
193
|
+
listbox: "List Box"
|
|
194
|
+
},
|
|
195
|
+
redact: {
|
|
196
|
+
mark: "Mark for Redaction",
|
|
197
|
+
applyAll: "Apply Redactions",
|
|
198
|
+
clearAll: "Clear Marks",
|
|
199
|
+
panel: "Redactions"
|
|
200
|
+
},
|
|
201
|
+
undo: "Undo",
|
|
202
|
+
redo: "Redo"
|
|
203
|
+
},
|
|
204
|
+
passwordPrompt: {
|
|
205
|
+
title: "Password required",
|
|
206
|
+
required: "This document is password protected. Enter the password to open it.",
|
|
207
|
+
incorrect: "The password was incorrect. Please try again.",
|
|
208
|
+
incorrectWarning: "Incorrect password",
|
|
209
|
+
label: "Password",
|
|
210
|
+
placeholder: "Enter password",
|
|
211
|
+
cancel: "Cancel",
|
|
212
|
+
open: "Open",
|
|
213
|
+
opening: "Opening…"
|
|
214
|
+
},
|
|
215
|
+
documentError: {
|
|
216
|
+
title: "Couldn’t open document",
|
|
217
|
+
unknown: "The document could not be opened.",
|
|
218
|
+
close: "Close document"
|
|
219
|
+
},
|
|
220
|
+
demo: {
|
|
221
|
+
starting: "Starting viewer…",
|
|
222
|
+
opening: "Opening document…",
|
|
223
|
+
more: "More",
|
|
224
|
+
thumbnails: "Thumbnails",
|
|
225
|
+
outline: "Outline",
|
|
226
|
+
searchPlaceholder: "Search document…",
|
|
227
|
+
searchTitle: "Search",
|
|
228
|
+
searchCaseSensitive: "Case sensitive",
|
|
229
|
+
searchWholeWord: "Whole word",
|
|
230
|
+
searchRegex: "Regular expression",
|
|
231
|
+
searchSearching: "Searching…",
|
|
232
|
+
searchResults: "{count} results found",
|
|
233
|
+
searchNoResults: "No results",
|
|
234
|
+
searchPage: "Page {page}",
|
|
235
|
+
commentsTitle: "Comments",
|
|
236
|
+
styleTitle: "Style",
|
|
237
|
+
empty: "Nothing here yet in this demo panel.",
|
|
238
|
+
page: "Page {current} of {total}",
|
|
239
|
+
cancel: "Cancel",
|
|
240
|
+
pageBadge: "p. {page}",
|
|
241
|
+
redactionTitle: "Redactions",
|
|
242
|
+
redactionEmpty: "No pending redactions. Use the Redact tool to mark text or areas.",
|
|
243
|
+
redactionArea: "Area mark",
|
|
244
|
+
redactionText: "Text mark",
|
|
245
|
+
redactionRemove: "Remove mark",
|
|
246
|
+
redactConfirmTitle: "Apply redactions?",
|
|
247
|
+
redactConfirmBody: "Permanently remove the content under {count} redaction marks. This cannot be undone.",
|
|
248
|
+
redactConfirmCollateral: "{count} other annotations will also be removed.",
|
|
249
|
+
redactApply: "Apply",
|
|
250
|
+
redactApplying: "Applying…",
|
|
251
|
+
redactLabelTitle: "Redaction label",
|
|
252
|
+
redactLabelPlaceholder: "e.g. REDACTED",
|
|
253
|
+
redactLabelRepeat: "Repeat label across region"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region src/config-context.tsx
|
|
259
|
+
/**
|
|
260
|
+
* The resolved customization, as React context. The chrome schema and the
|
|
261
|
+
* user's extra icons are RESOLVED ONCE in <FullViewer> and read here by the
|
|
262
|
+
* shell, menus, and strips — no component imports the default schema value
|
|
263
|
+
* directly, so "the host replaced the chrome" is invisible below this line.
|
|
264
|
+
*
|
|
265
|
+
* The default context value is the default config: components render correctly
|
|
266
|
+
* in tests and stories without a provider.
|
|
267
|
+
*/
|
|
268
|
+
const ViewerConfigContext = (0, react.createContext)({
|
|
269
|
+
chrome: require_chrome.defaultChrome,
|
|
270
|
+
icons: {}
|
|
271
|
+
});
|
|
272
|
+
const ViewerConfigProvider = ViewerConfigContext.Provider;
|
|
273
|
+
function useChromeSchema() {
|
|
274
|
+
return (0, react.useContext)(ViewerConfigContext).chrome;
|
|
275
|
+
}
|
|
276
|
+
function useMenuSchema(id) {
|
|
277
|
+
return require_chrome.getMenu((0, react.useContext)(ViewerConfigContext).chrome, id);
|
|
278
|
+
}
|
|
279
|
+
function useStripSchema(id) {
|
|
280
|
+
return require_chrome.getStrip((0, react.useContext)(ViewerConfigContext).chrome, id);
|
|
281
|
+
}
|
|
282
|
+
function useCustomIcons() {
|
|
283
|
+
return (0, react.useContext)(ViewerConfigContext).icons;
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/handle.ts
|
|
287
|
+
function createViewerHandle(kernel) {
|
|
288
|
+
const watch = (select, cb, isEqual = Object.is) => {
|
|
289
|
+
let previous = select();
|
|
290
|
+
return kernel.subscribe(() => {
|
|
291
|
+
const next = select();
|
|
292
|
+
if (isEqual(previous, next)) return;
|
|
293
|
+
const before = previous;
|
|
294
|
+
previous = next;
|
|
295
|
+
cb(next, before);
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
const commands = () => kernel.capability(_embedpdf_react_commands.CommandsToken);
|
|
299
|
+
return {
|
|
300
|
+
documents: kernel.documents,
|
|
301
|
+
get: (token) => kernel.capability(token),
|
|
302
|
+
tryGet: (token) => kernel.tryCapability(token),
|
|
303
|
+
forDocument: (documentId) => ({
|
|
304
|
+
get: (token) => kernel.capability(token, documentId),
|
|
305
|
+
tryGet: (token) => kernel.tryCapability(token, documentId)
|
|
306
|
+
}),
|
|
307
|
+
watch,
|
|
308
|
+
execute: (id, documentId) => commands().execute(id, documentId),
|
|
309
|
+
resolve: (id, documentId) => commands().resolve(id, documentId) ?? null,
|
|
310
|
+
watchCommand: (id, cb) => watch(() => commands().resolve(id) ?? null, cb, _embedpdf_react_commands.resolvedCommandsEqual)
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/ui/icons.tsx
|
|
315
|
+
/**
|
|
316
|
+
* Icon set — the snippet's Tabler-style stroke icons, extracted to path data
|
|
317
|
+
* (from viewers/snippet/src/components/icons) and rendered by one <Icon>
|
|
318
|
+
* component. Names match the snippet's icon registry so command `icon` ids
|
|
319
|
+
* carry over verbatim. Stroke-based (currentColor); a few glyphs carry filled
|
|
320
|
+
* sub-paths, marked `fill: true`.
|
|
321
|
+
*
|
|
322
|
+
* ACCENT SLOTS: a tool glyph tags the sub-paths that preview the tool's
|
|
323
|
+
* drawing defaults — `stroke`/`fill: 'primary' | 'secondary'` paint from the
|
|
324
|
+
* command's resolved `iconAccent` (slot placement ported 1:1 from the v2
|
|
325
|
+
* hand-authored components). With no accent, a slotted stroke inherits
|
|
326
|
+
* currentColor and a slotted fill renders nothing — pixel-identical to the
|
|
327
|
+
* untagged glyph, so non-tool surfaces never change.
|
|
328
|
+
*
|
|
329
|
+
* GENERATED by scratchpad/extract-icons.mjs — 93 icons (slot tags by hand).
|
|
330
|
+
*/
|
|
331
|
+
const ICON_PATHS = {
|
|
332
|
+
alertTriangle: ["M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"],
|
|
333
|
+
alignBottom: [
|
|
334
|
+
"M3 19v-14a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z",
|
|
335
|
+
"M11 17h2",
|
|
336
|
+
"M9 14h6",
|
|
337
|
+
"M10 11h4"
|
|
338
|
+
],
|
|
339
|
+
alignCenter: [
|
|
340
|
+
"M4 6l16 0",
|
|
341
|
+
"M8 12l8 0",
|
|
342
|
+
"M6 18l12 0"
|
|
343
|
+
],
|
|
344
|
+
alignLeft: [
|
|
345
|
+
"M4 6l16 0",
|
|
346
|
+
"M4 12l10 0",
|
|
347
|
+
"M4 18l14 0"
|
|
348
|
+
],
|
|
349
|
+
alignMiddle: [
|
|
350
|
+
"M3 19v-14a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z",
|
|
351
|
+
"M11 15h2",
|
|
352
|
+
"M9 12h6",
|
|
353
|
+
"M10 9h4"
|
|
354
|
+
],
|
|
355
|
+
alignRight: [
|
|
356
|
+
"M4 6l16 0",
|
|
357
|
+
"M10 12l10 0",
|
|
358
|
+
"M6 18l14 0"
|
|
359
|
+
],
|
|
360
|
+
alignTop: [
|
|
361
|
+
"M3 19v-14a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z",
|
|
362
|
+
"M11 13h2",
|
|
363
|
+
"M9 10h6",
|
|
364
|
+
"M10 7h4"
|
|
365
|
+
],
|
|
366
|
+
arrowBackUp: ["M9 14l-4 -4l4 -4", "M5 10h11a4 4 0 1 1 0 8h-1"],
|
|
367
|
+
arrowForwardUp: ["M15 14l4 -4l-4 -4", "M19 10h-11a4 4 0 1 0 0 8h1"],
|
|
368
|
+
bold: ["M7 5h6a3.5 3.5 0 0 1 0 7h-6z", "M13 12h1a3.5 3.5 0 0 1 0 7h-7v-7"],
|
|
369
|
+
book: [
|
|
370
|
+
"M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0",
|
|
371
|
+
"M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0",
|
|
372
|
+
"M3 6l0 13",
|
|
373
|
+
"M12 6l0 13",
|
|
374
|
+
"M21 6l0 13"
|
|
375
|
+
],
|
|
376
|
+
book2: [
|
|
377
|
+
"M19 4v16h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12z",
|
|
378
|
+
"M19 16h-12a2 2 0 0 0 -2 2",
|
|
379
|
+
"M9 8h6"
|
|
380
|
+
],
|
|
381
|
+
callout: [
|
|
382
|
+
{
|
|
383
|
+
d: "M20 6v4a1 1 0 0 1 -1 1h-14a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h14a1 1 0 0 1 1 1",
|
|
384
|
+
stroke: "primary",
|
|
385
|
+
fill: "secondary"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
d: "M17 11v6h-9",
|
|
389
|
+
stroke: "primary"
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
d: "M8 15l-2 2l2 2",
|
|
393
|
+
stroke: "primary"
|
|
394
|
+
}
|
|
395
|
+
],
|
|
396
|
+
check: ["M5 12l5 5l10 -10"],
|
|
397
|
+
chevronDown: ["M6 9l6 6l6 -6"],
|
|
398
|
+
chevronLeft: ["M15 6l-6 6l6 6"],
|
|
399
|
+
chevronRight: ["M9 6l6 6l-6 6"],
|
|
400
|
+
circle: [{
|
|
401
|
+
d: "M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0",
|
|
402
|
+
stroke: "primary",
|
|
403
|
+
fill: "secondary"
|
|
404
|
+
}],
|
|
405
|
+
comment: [
|
|
406
|
+
"M12 11v.01",
|
|
407
|
+
"M8 11v.01",
|
|
408
|
+
"M16 11v.01",
|
|
409
|
+
"M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3z"
|
|
410
|
+
],
|
|
411
|
+
copy: ["M7 7m0 2.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667z", "M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1"],
|
|
412
|
+
deviceFloppy: [
|
|
413
|
+
"M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2",
|
|
414
|
+
"M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0",
|
|
415
|
+
"M14 4l0 4l-6 0l0 -4"
|
|
416
|
+
],
|
|
417
|
+
dots: [
|
|
418
|
+
"M5 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0",
|
|
419
|
+
"M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0",
|
|
420
|
+
"M19 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"
|
|
421
|
+
],
|
|
422
|
+
doublePage: ["M3 3m0 1a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v16a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1zm9 -1v18"],
|
|
423
|
+
download: [
|
|
424
|
+
"M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2",
|
|
425
|
+
"M7 11l5 5l5 -5",
|
|
426
|
+
"M12 4l0 12"
|
|
427
|
+
],
|
|
428
|
+
externalLink: [
|
|
429
|
+
"M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6",
|
|
430
|
+
"M11 13l9 -9",
|
|
431
|
+
"M15 4h5v5"
|
|
432
|
+
],
|
|
433
|
+
eye: ["M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"],
|
|
434
|
+
file: ["M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"],
|
|
435
|
+
fileImport: ["M14 3v4a1 1 0 0 0 1 1h4", "M5 13v-8a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-5.5m-9.5 -2h7m-3 -3l3 3l-3 3"],
|
|
436
|
+
fitToPage: [
|
|
437
|
+
"M12 20h-6a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h6",
|
|
438
|
+
"M18 14v7",
|
|
439
|
+
"M18 3v7",
|
|
440
|
+
"M15 18l3 3l3 -3",
|
|
441
|
+
"M15 6l3 -3l3 3"
|
|
442
|
+
],
|
|
443
|
+
fitToWidth: [
|
|
444
|
+
"M4 12v-6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v6",
|
|
445
|
+
"M10 18h-7",
|
|
446
|
+
"M21 18h-7",
|
|
447
|
+
"M6 15l-3 3l3 3",
|
|
448
|
+
"M18 15l3 3l-3 3"
|
|
449
|
+
],
|
|
450
|
+
formCheckbox: ["M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14", "M9 12l2 2l4 -4"],
|
|
451
|
+
formListbox: [
|
|
452
|
+
"M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14",
|
|
453
|
+
"M11 8h6",
|
|
454
|
+
"M11 12h6",
|
|
455
|
+
"M11 16h6",
|
|
456
|
+
"M7 8l0 .01",
|
|
457
|
+
"M7 12l0 .01",
|
|
458
|
+
"M7 16l0 .01"
|
|
459
|
+
],
|
|
460
|
+
formRadio: [{
|
|
461
|
+
d: "M9 12a2 2 0 1 0 6 0a2 2 0 1 0 -6 0",
|
|
462
|
+
fill: true
|
|
463
|
+
}, "M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"],
|
|
464
|
+
formSelect: ["M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14", "M9 11l3 3l3 -3"],
|
|
465
|
+
formTextfield: [
|
|
466
|
+
"M12 3a3 3 0 0 0 -3 3v12a3 3 0 0 0 3 3",
|
|
467
|
+
"M6 3a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3",
|
|
468
|
+
"M13 7h7a1 1 0 0 1 1 1v8a1 1 0 0 1 -1 1h-7",
|
|
469
|
+
"M5 7h-1a1 1 0 0 0 -1 1v8a1 1 0 0 0 1 1h1",
|
|
470
|
+
"M17 12h.01",
|
|
471
|
+
"M13 12h.01"
|
|
472
|
+
],
|
|
473
|
+
freeText: [
|
|
474
|
+
{
|
|
475
|
+
d: "M4 7v-2h12v2",
|
|
476
|
+
stroke: "primary"
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
d: "M10 5v14",
|
|
480
|
+
stroke: "primary"
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
d: "M12 19h-4",
|
|
484
|
+
stroke: "primary"
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
d: "M15 13v-1h6v1",
|
|
488
|
+
stroke: "primary"
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
d: "M18 12v7",
|
|
492
|
+
stroke: "primary"
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
d: "M17 19h2",
|
|
496
|
+
stroke: "primary"
|
|
497
|
+
}
|
|
498
|
+
],
|
|
499
|
+
fullscreen: [
|
|
500
|
+
"M4 8v-2a2 2 0 0 1 2 -2h2",
|
|
501
|
+
"M4 16v2a2 2 0 0 0 2 2h2",
|
|
502
|
+
"M16 4h2a2 2 0 0 1 2 2v2",
|
|
503
|
+
"M16 20h2a2 2 0 0 0 2 -2v-2"
|
|
504
|
+
],
|
|
505
|
+
fullscreenExit: [
|
|
506
|
+
"M4 8v-2c0 -.551 .223 -1.05 .584 -1.412",
|
|
507
|
+
"M4 16v2a2 2 0 0 0 2 2h2",
|
|
508
|
+
"M16 4h2a2 2 0 0 1 2 2v2",
|
|
509
|
+
"M16 20h2c.545 0 1.04 -.218 1.4 -.572",
|
|
510
|
+
"M3 3l18 18"
|
|
511
|
+
],
|
|
512
|
+
group: [
|
|
513
|
+
"M3 5a2 2 0 1 0 4 0a2 2 0 1 0 -4 0",
|
|
514
|
+
"M17 5a2 2 0 1 0 4 0a2 2 0 1 0 -4 0",
|
|
515
|
+
"M3 19a2 2 0 1 0 4 0a2 2 0 1 0 -4 0",
|
|
516
|
+
"M17 19a2 2 0 1 0 4 0a2 2 0 1 0 -4 0",
|
|
517
|
+
"M5 7v10",
|
|
518
|
+
"M19 7v10",
|
|
519
|
+
"M7 5h10",
|
|
520
|
+
"M7 19h10",
|
|
521
|
+
"M8 8H12.8V11.2H16V16H11.2V12.8H8V8Z"
|
|
522
|
+
],
|
|
523
|
+
hand: [
|
|
524
|
+
"M8 13v-7.5a1.5 1.5 0 0 1 3 0v6.5",
|
|
525
|
+
"M11 5.5v-2a1.5 1.5 0 1 1 3 0v8.5",
|
|
526
|
+
"M14 5.5a1.5 1.5 0 0 1 3 0v6.5",
|
|
527
|
+
"M17 7.5a1.5 1.5 0 0 1 3 0v8.5a6 6 0 0 1 -6 6h-2h.208a6 6 0 0 1 -5.012 -2.7a69.74 69.74 0 0 1 -.196 -.3c-.312 -.479 -1.407 -2.388 -3.286 -5.728a1.5 1.5 0 0 1 .536 -2.022a1.867 1.867 0 0 1 2.28 .28l1.47 1.47"
|
|
528
|
+
],
|
|
529
|
+
highlight: [
|
|
530
|
+
{
|
|
531
|
+
d: "M4 6h16a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2H4a2 2 0 0 1 -2 -2V8a2 2 0 0 1 2 -2z",
|
|
532
|
+
fill: "primary"
|
|
533
|
+
},
|
|
534
|
+
"M8 16v-8a4 4 0 1 1 8 0v8",
|
|
535
|
+
"M8 10h8"
|
|
536
|
+
],
|
|
537
|
+
horizontal: [
|
|
538
|
+
"M7 8l-4 4l4 4",
|
|
539
|
+
"M17 8l4 4l-4 4",
|
|
540
|
+
"M3 12l18 0"
|
|
541
|
+
],
|
|
542
|
+
inkHighlighter: [
|
|
543
|
+
"M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4",
|
|
544
|
+
"M12.5 5.5l4 4",
|
|
545
|
+
"M4.5 13.5l4 4",
|
|
546
|
+
{
|
|
547
|
+
d: "M21 15v4h-8l4 -4l4 0",
|
|
548
|
+
stroke: "primary",
|
|
549
|
+
fill: "primary"
|
|
550
|
+
}
|
|
551
|
+
],
|
|
552
|
+
insertText: [
|
|
553
|
+
{
|
|
554
|
+
d: "M3 7v-2h12v2",
|
|
555
|
+
stroke: "primary"
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
d: "M9 5v14",
|
|
559
|
+
stroke: "primary"
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
d: "M7 19h4",
|
|
563
|
+
stroke: "primary"
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
d: "M18 15l2 2h-5",
|
|
567
|
+
stroke: "primary"
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
d: "M18 19l2 -2",
|
|
571
|
+
stroke: "primary"
|
|
572
|
+
}
|
|
573
|
+
],
|
|
574
|
+
italic: [
|
|
575
|
+
"M11 5l6 0",
|
|
576
|
+
"M7 19l6 0",
|
|
577
|
+
"M14 5l-4 14"
|
|
578
|
+
],
|
|
579
|
+
line: [{
|
|
580
|
+
d: "M20 4l-16 16",
|
|
581
|
+
stroke: "primary"
|
|
582
|
+
}],
|
|
583
|
+
lineArrow: [{
|
|
584
|
+
d: "M20 4l-16 16",
|
|
585
|
+
stroke: "primary"
|
|
586
|
+
}, {
|
|
587
|
+
d: "M16 3h5v5",
|
|
588
|
+
stroke: "primary"
|
|
589
|
+
}],
|
|
590
|
+
link: [
|
|
591
|
+
"M9 15l6 -6",
|
|
592
|
+
"M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464",
|
|
593
|
+
"M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463"
|
|
594
|
+
],
|
|
595
|
+
linkOff: [
|
|
596
|
+
"M9 15l3 -3m2 -2l1 -1",
|
|
597
|
+
"M11 6l.463 -.536a5 5 0 0 1 7.072 7.071l-.534 .464",
|
|
598
|
+
"M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463",
|
|
599
|
+
"M3 3l18 18"
|
|
600
|
+
],
|
|
601
|
+
listTree: [
|
|
602
|
+
"M9 6h11",
|
|
603
|
+
"M12 12h8",
|
|
604
|
+
"M15 18h5",
|
|
605
|
+
"M5 6v.01",
|
|
606
|
+
"M8 12v.01",
|
|
607
|
+
"M11 18v.01"
|
|
608
|
+
],
|
|
609
|
+
lock: ["M5 11h14a2 2 0 0 1 2 2v7a2 2 0 0 1 -2 2H5a2 2 0 0 1 -2 -2v-7a2 2 0 0 1 2 -2z", "M7 11V7a5 5 0 0 1 10 0v4"],
|
|
610
|
+
marqueeSelect: [
|
|
611
|
+
"M17 20h6",
|
|
612
|
+
"M20 17v6",
|
|
613
|
+
"M4 13v-2",
|
|
614
|
+
"M4 6v-1a1 1 0 0 1 1 -1h1",
|
|
615
|
+
"M6 20h-1a1 1 0 0 1 -1 -1v-1",
|
|
616
|
+
"M11 4h2",
|
|
617
|
+
"M11 20h2",
|
|
618
|
+
"M18 4h1a1 1 0 0 1 1 1v1",
|
|
619
|
+
"M20 11v2"
|
|
620
|
+
],
|
|
621
|
+
menu: ["M4 8l16 0", "M4 16l16 0"],
|
|
622
|
+
message: [
|
|
623
|
+
"M8 9h8",
|
|
624
|
+
"M8 13h6",
|
|
625
|
+
"M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"
|
|
626
|
+
],
|
|
627
|
+
palette: [
|
|
628
|
+
"M12 21a9 9 0 0 1 0 -18c4.97 0 9 3.582 9 8c0 1.06 -.474 2.078 -1.318 2.828c-.844 .75 -1.989 1.172 -3.182 1.172h-2.5a2 2 0 0 0 -1 3.75a1.3 1.3 0 0 1 -1 2.25",
|
|
629
|
+
"M8.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0",
|
|
630
|
+
"M12.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0",
|
|
631
|
+
"M16.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"
|
|
632
|
+
],
|
|
633
|
+
paperclip: ["M15 7l-6.5 6.5a1.5 1.5 0 0 0 3 3l6.5 -6.5a3 3 0 0 0 -6 -6l-6.5 6.5a4.5 4.5 0 0 0 9 9l6.5 -6.5"],
|
|
634
|
+
pencilMarker: ["m9.109 16.275 8.856-8.097c.812-.743.87-2.014.127-2.826s-2.014-.869-2.826-.127L6.41 13.322l-.127 2.826zM13.79 6.575l2.7 2.952", {
|
|
635
|
+
d: "M19.375 20.125c.569.063-4.05-.562-6.412-.437s-4.759 1.229-6.857 1.625c-1.764.687-3.404-.938-1.981-2.5",
|
|
636
|
+
stroke: "primary"
|
|
637
|
+
}],
|
|
638
|
+
photo: [
|
|
639
|
+
"M15 8h.01",
|
|
640
|
+
"M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12z",
|
|
641
|
+
"M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5",
|
|
642
|
+
"M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3"
|
|
643
|
+
],
|
|
644
|
+
plus: ["M12 5l0 14", "M5 12l14 0"],
|
|
645
|
+
pointer: ["M7.904 17.563a1.2 1.2 0 0 0 2.228 .308l2.09 -3.093l4.907 4.907a1.067 1.067 0 0 0 1.509 0l1.047 -1.047a1.067 1.067 0 0 0 0 -1.509l-4.907 -4.907l3.113 -2.09a1.2 1.2 0 0 0 -.309 -2.228l-13.582 -3.904l3.904 13.563z"],
|
|
646
|
+
polygon: [{
|
|
647
|
+
d: "M13.163 2.168l8.021 5.828c.694 .504 .984 1.397 .719 2.212l-3.064 9.43a1.978 1.978 0 0 1 -1.881 1.367h-9.916a1.978 1.978 0 0 1 -1.881 -1.367l-3.064 -9.43a1.978 1.978 0 0 1 .719 -2.212l8.021 -5.828a1.978 1.978 0 0 1 2.326 0z",
|
|
648
|
+
stroke: "primary",
|
|
649
|
+
fill: "secondary"
|
|
650
|
+
}],
|
|
651
|
+
print: [
|
|
652
|
+
"M17 17h2a2 2 0 0 0 2 -2v-4a2 2 0 0 0 -2 -2h-14a2 2 0 0 0 -2 2v4a2 2 0 0 0 2 2h2",
|
|
653
|
+
"M17 9v-4a2 2 0 0 0 -2 -2h-6a2 2 0 0 0 -2 2v4",
|
|
654
|
+
"M7 13m0 2a2 2 0 0 1 2 -2h6a2 2 0 0 1 2 2v4a2 2 0 0 1 -2 2h-6a2 2 0 0 1 -2 -2z"
|
|
655
|
+
],
|
|
656
|
+
redact: [
|
|
657
|
+
"M7 4h10",
|
|
658
|
+
"M12 4v8",
|
|
659
|
+
"M-7 24l12 -12",
|
|
660
|
+
"M-3 24l12 -12",
|
|
661
|
+
"M1 24l12 -12",
|
|
662
|
+
"M5 24l12 -12",
|
|
663
|
+
"M9 24l12 -12",
|
|
664
|
+
"M13 24l12 -12",
|
|
665
|
+
"M17 24l12 -12",
|
|
666
|
+
"M21 24l12 -12",
|
|
667
|
+
"M25 24l12 -12"
|
|
668
|
+
],
|
|
669
|
+
redactArea: [
|
|
670
|
+
"M6 20h-1a2 2 0 0 1 -2 -2v-1",
|
|
671
|
+
"M3 13v-3",
|
|
672
|
+
"M3 6v-1a2 2 0 0 1 2 -2h1",
|
|
673
|
+
"M10 3h3",
|
|
674
|
+
"M17 3h1a2 2 0 0 1 2 2v1",
|
|
675
|
+
"M12 10h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2v-8a2 2 0 0 1 2 -2z",
|
|
676
|
+
"M10 12l2 -2",
|
|
677
|
+
"M10 16l6 -6",
|
|
678
|
+
"M10 20l10 -10",
|
|
679
|
+
"M12 22l10 -10",
|
|
680
|
+
"M16 22l6 -6",
|
|
681
|
+
"M20 22l2 -2"
|
|
682
|
+
],
|
|
683
|
+
redactCombined: [
|
|
684
|
+
"M6 20h-1a2 2 0 0 1 -2 -2v-1",
|
|
685
|
+
"M3 13v-3",
|
|
686
|
+
"M20 13v-3",
|
|
687
|
+
"M3 6v-1a2 2 0 0 1 2 -2h1",
|
|
688
|
+
"M10 3h3",
|
|
689
|
+
"M17 3h1a2 2 0 0 1 2 2v1",
|
|
690
|
+
"M8 8h8",
|
|
691
|
+
"M12 8v7",
|
|
692
|
+
"M6 19l12 -12",
|
|
693
|
+
"M6 23l12 -12",
|
|
694
|
+
"M10 23l12 -12",
|
|
695
|
+
"M14 27l12 -12",
|
|
696
|
+
"M14 23l12 -12"
|
|
697
|
+
],
|
|
698
|
+
redactionSidebar: [
|
|
699
|
+
"M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z",
|
|
700
|
+
"M15 4v16",
|
|
701
|
+
"M7 8h5",
|
|
702
|
+
"M7 11h5",
|
|
703
|
+
"M7 14h4"
|
|
704
|
+
],
|
|
705
|
+
replaceText: [
|
|
706
|
+
{
|
|
707
|
+
d: "M3 7v-2h12v2",
|
|
708
|
+
stroke: "primary"
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
d: "M9 5v14",
|
|
712
|
+
stroke: "primary"
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
d: "M7 19h4",
|
|
716
|
+
stroke: "primary"
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
d: "M20 14l2 2h-3",
|
|
720
|
+
stroke: "primary"
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
d: "M20 18l2 -2",
|
|
724
|
+
stroke: "primary"
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
d: "M19 16a3 3 0 1 0 2 5.236",
|
|
728
|
+
stroke: "primary"
|
|
729
|
+
}
|
|
730
|
+
],
|
|
731
|
+
rotateClockwise: ["M4.05 11a8 8 0 1 1 .5 4m-.5 5v-5h5"],
|
|
732
|
+
rotateCounterClockwise: ["M19.95 11a8 8 0 1 0 -.5 4m.5 5v-5h-5"],
|
|
733
|
+
rubberStamp: ["M21 17.85h-18c0 -4.05 1.421 -4.05 3.79 -4.05c5.21 0 1.21 -4.59 1.21 -6.8a4 4 0 1 1 8 0c0 2.21 -4 6.8 1.21 6.8c2.369 0 3.79 0 3.79 4.05", "M5 21h14"],
|
|
734
|
+
rubberStampPlus: [
|
|
735
|
+
"M21 17.85h-18c0 -4.05 1.421 -4.05 3.79 -4.05c5.21 0 1.21 -4.59 1.21 -6.8a4 4 0 0 1 4.9 -4.1",
|
|
736
|
+
"M16 10c0 1.2 -4 3.8 1.21 3.8c2.369 0 3.79 0 3.79 4.05",
|
|
737
|
+
"M5 21h14",
|
|
738
|
+
"M16 5h6",
|
|
739
|
+
"M19 2v6"
|
|
740
|
+
],
|
|
741
|
+
save: [
|
|
742
|
+
"M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2",
|
|
743
|
+
"M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0",
|
|
744
|
+
"M14 4l0 4l-6 0l0 -4"
|
|
745
|
+
],
|
|
746
|
+
screenshot: [
|
|
747
|
+
"M7 19a2 2 0 0 1 -2 -2",
|
|
748
|
+
"M5 13v-2",
|
|
749
|
+
"M5 7a2 2 0 0 1 2 -2",
|
|
750
|
+
"M11 5h2",
|
|
751
|
+
"M17 5a2 2 0 0 1 2 2",
|
|
752
|
+
"M19 11v2",
|
|
753
|
+
"M19 17v4",
|
|
754
|
+
"M21 19h-4",
|
|
755
|
+
"M13 19h-2"
|
|
756
|
+
],
|
|
757
|
+
search: ["M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0", "M21 21l-6 -6"],
|
|
758
|
+
settings: ["M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z", "M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"],
|
|
759
|
+
sidebar: ["M4 18v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z", "M10 18v-12a2 2 0 0 1 2 -2h6a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-6a2 2 0 0 1 -2 -2z"],
|
|
760
|
+
signature: ["M3 17c3.333 -3.333 5 -6 5 -8c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 4.877 2.5 6c1.5 2 2.5 2.5 3.5 1l2 -3c.333 2.667 1.333 4 3 4c.53 0 2.639 -2 3 -2c.517 0 1.517 .667 3 2"],
|
|
761
|
+
singlePage: ["M5 3m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v16a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z"],
|
|
762
|
+
square: [{
|
|
763
|
+
d: "M3 3m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z",
|
|
764
|
+
stroke: "primary",
|
|
765
|
+
fill: "secondary"
|
|
766
|
+
}],
|
|
767
|
+
squares: ["M8 10a2 2 0 0 1 2 -2h9a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-9a2 2 0 0 1 -2 -2z", "M16 8v-3a2 2 0 0 0 -2 -2h-9a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h3"],
|
|
768
|
+
squiggly: [
|
|
769
|
+
"M8 16v-8a4 4 0 1 1 8 0v8",
|
|
770
|
+
"M8 10h8",
|
|
771
|
+
{
|
|
772
|
+
d: "M4 20c1.5 -1.5 3.5 -1.5 5 0s3.5 1.5 5 0 3.5 -1.5 5 0",
|
|
773
|
+
stroke: "primary"
|
|
774
|
+
}
|
|
775
|
+
],
|
|
776
|
+
strikethrough: ["M8 16v-8a4 4 0 1 1 8 0v8", {
|
|
777
|
+
d: "M4 10h16",
|
|
778
|
+
stroke: "primary"
|
|
779
|
+
}],
|
|
780
|
+
text: [
|
|
781
|
+
"M6.5 15.5m-3.5 0a3.5 3.5 0 1 0 7 0a3.5 3.5 0 1 0 -7 0",
|
|
782
|
+
"M14 19v-10.5a3.5 3.5 0 0 1 7 0v10.5",
|
|
783
|
+
"M14 13h7",
|
|
784
|
+
"M10 12v7"
|
|
785
|
+
],
|
|
786
|
+
trash: [
|
|
787
|
+
"M4 7l16 0",
|
|
788
|
+
"M10 11l0 6",
|
|
789
|
+
"M14 11l0 6",
|
|
790
|
+
"M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12",
|
|
791
|
+
"M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"
|
|
792
|
+
],
|
|
793
|
+
underline: [
|
|
794
|
+
{
|
|
795
|
+
d: "M4 20h16",
|
|
796
|
+
stroke: "primary"
|
|
797
|
+
},
|
|
798
|
+
"M8 16v-8a4 4 0 1 1 8 0v8",
|
|
799
|
+
"M8 10h8"
|
|
800
|
+
],
|
|
801
|
+
ungroup: ["M4 5a1 1 0 0 1 1 -1h5a1 1 0 0 1 1 1v5a1 1 0 0 1 -1 1h-5a1 1 0 0 1 -1 -1z", "M13 14a1 1 0 0 1 1 -1h5a1 1 0 0 1 1 1v5a1 1 0 0 1 -1 1h-5a1 1 0 0 1 -1 -1z"],
|
|
802
|
+
unlock: ["M7 11V7a5 5 0 0 1 9.9-1"],
|
|
803
|
+
vertical: [
|
|
804
|
+
"M8 7l4 -4l4 4",
|
|
805
|
+
"M8 17l4 4l4 -4",
|
|
806
|
+
"M12 3l0 18"
|
|
807
|
+
],
|
|
808
|
+
viewSettings: [
|
|
809
|
+
"M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0",
|
|
810
|
+
"M12 10.5v1.5",
|
|
811
|
+
"M12 16v1.5",
|
|
812
|
+
"M15.031 12.25l-1.299 .75",
|
|
813
|
+
"M10.268 15l-1.3 .75",
|
|
814
|
+
"M15 15.803l-1.285 -.773",
|
|
815
|
+
"M10.285 12.97l-1.285 -.773",
|
|
816
|
+
"M14 3v4a1 1 0 0 0 1 1h4",
|
|
817
|
+
"M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"
|
|
818
|
+
],
|
|
819
|
+
widgetEdit: [
|
|
820
|
+
"M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1",
|
|
821
|
+
"M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415",
|
|
822
|
+
"M16 5l3 3"
|
|
823
|
+
],
|
|
824
|
+
x: ["M18 6l-12 12", "M6 6l12 12"],
|
|
825
|
+
zigzag: [{
|
|
826
|
+
d: "M12 2.4L21.36 11.76L2.64 12.24L12 21.6",
|
|
827
|
+
stroke: "primary"
|
|
828
|
+
}],
|
|
829
|
+
zoomIn: [
|
|
830
|
+
"M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0",
|
|
831
|
+
"M9 12h6",
|
|
832
|
+
"M12 9v6"
|
|
833
|
+
],
|
|
834
|
+
zoomInArea: [
|
|
835
|
+
"M15 13v4",
|
|
836
|
+
"M13 15h4",
|
|
837
|
+
"M15 15m-5 0a5 5 0 1 0 10 0a5 5 0 1 0 -10 0",
|
|
838
|
+
"M22 22l-3 -3",
|
|
839
|
+
"M6 18h-1a2 2 0 0 1 -2 -2v-1",
|
|
840
|
+
"M3 11v-1",
|
|
841
|
+
"M3 6v-1a2 2 0 0 1 2 -2h1",
|
|
842
|
+
"M10 3h1",
|
|
843
|
+
"M15 3h1a2 2 0 0 1 2 2v1"
|
|
844
|
+
],
|
|
845
|
+
zoomOut: ["M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0", "M9 12l6 0"]
|
|
846
|
+
};
|
|
847
|
+
/** One SVG renderer for every icon. Unknown names render nothing (caller shows
|
|
848
|
+
* a text fallback), so a missing glyph never throws. */
|
|
849
|
+
function Icon({ name, size = 20, strokeWidth = 2, className, title, accent }) {
|
|
850
|
+
const paths = useCustomIcons()[name] ?? ICON_PATHS[name];
|
|
851
|
+
if (!paths) return null;
|
|
852
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
853
|
+
width: size,
|
|
854
|
+
height: size,
|
|
855
|
+
viewBox: "0 0 24 24",
|
|
856
|
+
fill: "none",
|
|
857
|
+
stroke: "currentColor",
|
|
858
|
+
strokeWidth,
|
|
859
|
+
strokeLinecap: "round",
|
|
860
|
+
strokeLinejoin: "round",
|
|
861
|
+
className,
|
|
862
|
+
role: "img",
|
|
863
|
+
"aria-hidden": title ? void 0 : true,
|
|
864
|
+
"aria-label": title,
|
|
865
|
+
children: paths.map((p, i) => {
|
|
866
|
+
if (typeof p === "string") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: p }, i);
|
|
867
|
+
const fill = p.fill === true ? "currentColor" : p.fill ? accent?.[p.fill] : void 0;
|
|
868
|
+
const stroke = p.stroke ? accent?.[p.stroke] : p.fill ? "none" : void 0;
|
|
869
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
870
|
+
d: p.d,
|
|
871
|
+
fill: fill ?? "none",
|
|
872
|
+
stroke
|
|
873
|
+
}, i);
|
|
874
|
+
})
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
//#endregion
|
|
878
|
+
//#region src/ui/theme.tsx
|
|
879
|
+
/**
|
|
880
|
+
* Light/dark theme — one class, and the CSS variables in styles.css do the
|
|
881
|
+
* rest (both toolbars, sidebars, popovers, and the canvas re-theme from it).
|
|
882
|
+
* The mode comes from the embedder's `theme` preference; the chrome ships no
|
|
883
|
+
* switch of its own, so a host that wants one drives this through `set`. The
|
|
884
|
+
* class lands on <html> by default; a delivery that must not touch the page
|
|
885
|
+
* (the <embedpdf-viewer> shadow root) passes `target` and the class stays
|
|
886
|
+
* inside its own subtree. An explicit preference outranks the OS setting.
|
|
887
|
+
*/
|
|
888
|
+
const ThemeCtx = (0, react.createContext)(null);
|
|
889
|
+
const systemMode = () => typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
890
|
+
function ThemeProvider({ children, preference = "system", target }) {
|
|
891
|
+
const [mode, setMode] = (0, react.useState)(() => preference === "system" ? systemMode() : preference);
|
|
892
|
+
(0, react.useEffect)(() => {
|
|
893
|
+
(target ?? document.documentElement).classList.toggle("dark", mode === "dark");
|
|
894
|
+
}, [mode, target]);
|
|
895
|
+
const value = (0, react.useMemo)(() => ({
|
|
896
|
+
mode,
|
|
897
|
+
toggle: () => setMode((m) => m === "dark" ? "light" : "dark"),
|
|
898
|
+
set: setMode
|
|
899
|
+
}), [mode]);
|
|
900
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeCtx.Provider, {
|
|
901
|
+
value,
|
|
902
|
+
children
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
//#endregion
|
|
906
|
+
//#region src/ui/menu.tsx
|
|
907
|
+
/**
|
|
908
|
+
* Menu rendering — a MenuSchema (a tree of command ids) rendered as rows.
|
|
909
|
+
* Reused by the toolbar dropdowns (document / zoom / page-settings) AND by the
|
|
910
|
+
* overflow menu's inline submenu expansion, so a command's menu form is
|
|
911
|
+
* identical wherever it surfaces. Rows resolve live through the command
|
|
912
|
+
* registry: label, icon, active tick, enabled state.
|
|
913
|
+
*/
|
|
914
|
+
function MenuRow({ commandId, onRun }) {
|
|
915
|
+
const cmd = (0, _embedpdf_react_commands.useCommand)(commandId);
|
|
916
|
+
const commands = (0, _embedpdf_react_commands.useCommands)();
|
|
917
|
+
if (!cmd || !cmd.visible) return null;
|
|
918
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
919
|
+
type: "button",
|
|
920
|
+
role: cmd.active ? "menuitemradio" : "menuitem",
|
|
921
|
+
part: cmd.active ? "menu-item menu-item-active" : "menu-item",
|
|
922
|
+
"aria-checked": cmd.active || void 0,
|
|
923
|
+
disabled: !cmd.enabled,
|
|
924
|
+
onClick: () => {
|
|
925
|
+
commands.execute(commandId);
|
|
926
|
+
onRun?.();
|
|
927
|
+
},
|
|
928
|
+
className: `flex w-full items-center gap-3 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors disabled:pointer-events-none disabled:opacity-40 ${cmd.active ? "bg-selected text-accent" : "text-fg-secondary hover:bg-hover"}`,
|
|
929
|
+
children: [cmd.icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
930
|
+
name: cmd.icon,
|
|
931
|
+
size: 16,
|
|
932
|
+
accent: cmd.iconAccent,
|
|
933
|
+
className: cmd.active ? "" : "text-fg-muted"
|
|
934
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
935
|
+
className: "flex-1 truncate",
|
|
936
|
+
children: cmd.label
|
|
937
|
+
})]
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
/** One section-separated menu, by schema id. */
|
|
941
|
+
function MenuBody({ menuId, onRun }) {
|
|
942
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
943
|
+
const menu = useMenuSchema(menuId);
|
|
944
|
+
if (!menu) return null;
|
|
945
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
946
|
+
className: "min-w-52 p-1",
|
|
947
|
+
children: menu.sections.map((section, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
948
|
+
i > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "bg-border-subtle my-1 h-px" }),
|
|
949
|
+
section.labelKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
950
|
+
className: "text-fg-muted px-2.5 pb-1 pt-1.5 text-[11px] font-semibold uppercase tracking-wide",
|
|
951
|
+
children: t(section.labelKey)
|
|
952
|
+
}),
|
|
953
|
+
section.items.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuRow, {
|
|
954
|
+
commandId: id,
|
|
955
|
+
onRun
|
|
956
|
+
}, id))
|
|
957
|
+
] }, i))
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
/** The popover shell: a floating surface + click-away scrim. */
|
|
961
|
+
function Popover({ children, onClose, align = "start" }) {
|
|
962
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
963
|
+
className: "fixed inset-0 z-40",
|
|
964
|
+
onClick: onClose
|
|
965
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
966
|
+
part: "menu",
|
|
967
|
+
className: `border-border-subtle bg-elevated absolute top-full z-50 mt-1 rounded-lg border shadow-xl ${align === "end" ? "right-0" : "left-0"}`,
|
|
968
|
+
children
|
|
969
|
+
})] });
|
|
970
|
+
}
|
|
971
|
+
/** A menu dropdown that expands inline (used inside the overflow menu, where a
|
|
972
|
+
* submenu can't anchor to an off-screen trigger). */
|
|
973
|
+
function InlineSubmenu({ menuId, label }) {
|
|
974
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
975
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
976
|
+
type: "button",
|
|
977
|
+
onClick: () => setOpen((o) => !o),
|
|
978
|
+
className: "text-fg-secondary hover:bg-hover flex w-full items-center gap-3 rounded-md px-2.5 py-1.5 text-left text-sm",
|
|
979
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
980
|
+
className: "flex-1 truncate",
|
|
981
|
+
children: label
|
|
982
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
983
|
+
name: open ? "chevronDown" : "chevronRight",
|
|
984
|
+
size: 16
|
|
985
|
+
})]
|
|
986
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
987
|
+
className: "border-border-subtle ml-3 border-l pl-1",
|
|
988
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuBody, { menuId })
|
|
989
|
+
})] });
|
|
990
|
+
}
|
|
991
|
+
//#endregion
|
|
992
|
+
//#region src/ui/toolbar.tsx
|
|
993
|
+
/**
|
|
994
|
+
* The app's toolbar look — Tailwind render props over the headless <Toolbar>.
|
|
995
|
+
* The app owns the pixels; ui-core owns the physics (measure → solve →
|
|
996
|
+
* overflow). Nothing here knows about breakpoints or widths.
|
|
997
|
+
*
|
|
998
|
+
* Styling is a 1:1 port of the v2 snippet's components:
|
|
999
|
+
* Button → viewers/snippet/src/components/ui/button.tsx
|
|
1000
|
+
* TabButton → viewers/snippet/src/components/ui/tab-button.tsx (underline)
|
|
1001
|
+
* zoom widget → viewers/snippet/src/components/custom-zoom-toolbar.tsx
|
|
1002
|
+
* mode select → viewers/snippet/src/components/mode-select-button.tsx
|
|
1003
|
+
*/
|
|
1004
|
+
const BTN_BASE = "flex h-8 w-auto min-w-8 cursor-pointer items-center justify-center rounded-md p-[5px] transition-colors";
|
|
1005
|
+
const BTN_HOVER = "hover:bg-hover hover:ring hover:ring-accent";
|
|
1006
|
+
const BTN_ACTIVE = "border-none bg-selected text-accent shadow ring ring-accent";
|
|
1007
|
+
const BTN_DISABLED = "cursor-not-allowed opacity-50 hover:bg-transparent hover:ring-0";
|
|
1008
|
+
const buttonClass = (active, enabled = true) => `${BTN_BASE} ${active ? BTN_ACTIVE : BTN_HOVER} ${enabled ? "" : BTN_DISABLED}`;
|
|
1009
|
+
const TAB_BASE = "flex h-8 w-auto min-w-8 cursor-pointer items-center justify-center rounded-none border-b-2 px-2 py-1 text-sm transition-colors hover:bg-transparent hover:border-b-fg-muted";
|
|
1010
|
+
const TAB_ACTIVE = "border-b-accent text-accent hover:border-b-accent";
|
|
1011
|
+
const TAB_INACTIVE = "border-b-transparent";
|
|
1012
|
+
const tabClass = (active, enabled = true) => `${TAB_BASE} ${active ? TAB_ACTIVE : TAB_INACTIVE} ${enabled ? "" : "cursor-not-allowed opacity-50"}`;
|
|
1013
|
+
function CommandButton({ cmd, variant, run }) {
|
|
1014
|
+
const isTab = variant === "label";
|
|
1015
|
+
const button = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1016
|
+
type: "button",
|
|
1017
|
+
onClick: run,
|
|
1018
|
+
disabled: !cmd.enabled,
|
|
1019
|
+
"aria-pressed": cmd.active || void 0,
|
|
1020
|
+
"aria-haspopup": cmd.menu ? "menu" : void 0,
|
|
1021
|
+
"aria-label": cmd.label,
|
|
1022
|
+
title: cmd.label,
|
|
1023
|
+
part: isTab ? `mode-tab${cmd.active ? " mode-tab-active" : ""}` : `toolbar-button${cmd.active ? " toolbar-button-active" : ""}`,
|
|
1024
|
+
className: isTab ? tabClass(cmd.active, cmd.enabled) : buttonClass(cmd.active, cmd.enabled),
|
|
1025
|
+
children: isTab ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1026
|
+
className: "whitespace-nowrap px-1",
|
|
1027
|
+
children: cmd.label
|
|
1028
|
+
}) : variant === "icon+label" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1029
|
+
className: "flex items-center whitespace-nowrap text-sm",
|
|
1030
|
+
children: [cmd.icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1031
|
+
name: cmd.icon,
|
|
1032
|
+
size: 20,
|
|
1033
|
+
accent: cmd.iconAccent,
|
|
1034
|
+
className: "mr-1.5 shrink-0"
|
|
1035
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: cmd.label })]
|
|
1036
|
+
}) : cmd.icon ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1037
|
+
name: cmd.icon,
|
|
1038
|
+
size: 20,
|
|
1039
|
+
accent: cmd.iconAccent
|
|
1040
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1041
|
+
className: "text-sm",
|
|
1042
|
+
children: cmd.label
|
|
1043
|
+
})
|
|
1044
|
+
});
|
|
1045
|
+
if (!cmd.menu) return button;
|
|
1046
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuAnchoredButton, {
|
|
1047
|
+
cmd,
|
|
1048
|
+
children: button
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
function MenuAnchoredButton({ cmd, children }) {
|
|
1052
|
+
const menus = (0, _embedpdf_react_shell.useMenus)();
|
|
1053
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1054
|
+
className: "relative inline-flex",
|
|
1055
|
+
children: [children, (cmd.menu ? menus.isOpen(cmd.menu) : false) && cmd.menu && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Popover, {
|
|
1056
|
+
onClose: () => menus.close(cmd.menu),
|
|
1057
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuBody, {
|
|
1058
|
+
menuId: cmd.menu,
|
|
1059
|
+
onRun: () => menus.close(cmd.menu)
|
|
1060
|
+
})
|
|
1061
|
+
})]
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
function ZoomControls() {
|
|
1065
|
+
const commands = (0, _embedpdf_react_commands.useCommands)();
|
|
1066
|
+
const menus = (0, _embedpdf_react_shell.useMenus)();
|
|
1067
|
+
const stage = (0, _embedpdf_react_runtime.useOptionalCapability)(_embedpdf_react_stage.StageToken);
|
|
1068
|
+
const level = (0, _embedpdf_react_runtime.useOptionalSelector)(_embedpdf_react_stage.StageToken, (c) => c.zoomLevel(), 1);
|
|
1069
|
+
const pct = Math.round((level ?? 1) * 100);
|
|
1070
|
+
const [inputValue, setInputValue] = (0, react.useState)(String(pct));
|
|
1071
|
+
(0, react.useEffect)(() => setInputValue(String(pct)), [pct]);
|
|
1072
|
+
const submit = (e) => {
|
|
1073
|
+
e.preventDefault();
|
|
1074
|
+
const value = parseFloat(inputValue);
|
|
1075
|
+
if (!isNaN(value) && value > 0) stage?.zoomTo({ level: value / 100 });
|
|
1076
|
+
};
|
|
1077
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1078
|
+
className: "relative",
|
|
1079
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1080
|
+
className: "bg-hover flex items-center rounded",
|
|
1081
|
+
children: [
|
|
1082
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1083
|
+
onSubmit: submit,
|
|
1084
|
+
className: "flex min-w-0 flex-nowrap items-center overflow-hidden whitespace-nowrap",
|
|
1085
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1086
|
+
name: "zoom",
|
|
1087
|
+
type: "text",
|
|
1088
|
+
inputMode: "numeric",
|
|
1089
|
+
pattern: "\\d*",
|
|
1090
|
+
"aria-label": "Set zoom",
|
|
1091
|
+
className: "h-6 w-8 min-w-0 shrink border-0 bg-transparent p-0 text-right text-sm outline-none focus:outline-none",
|
|
1092
|
+
value: inputValue,
|
|
1093
|
+
onChange: (e) => setInputValue(e.target.value.replace(/[^0-9]/g, "")),
|
|
1094
|
+
onBlur: () => {
|
|
1095
|
+
if (!inputValue || parseFloat(inputValue) <= 0) setInputValue(String(pct));
|
|
1096
|
+
}
|
|
1097
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1098
|
+
className: "shrink-0 text-sm",
|
|
1099
|
+
children: "%"
|
|
1100
|
+
})]
|
|
1101
|
+
}),
|
|
1102
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1103
|
+
type: "button",
|
|
1104
|
+
"aria-haspopup": "menu",
|
|
1105
|
+
"aria-expanded": menus.isOpen("zoom"),
|
|
1106
|
+
title: "Zoom options",
|
|
1107
|
+
className: buttonClass(false),
|
|
1108
|
+
onClick: () => menus.toggle("zoom"),
|
|
1109
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1110
|
+
name: "chevronDown",
|
|
1111
|
+
size: 20
|
|
1112
|
+
})
|
|
1113
|
+
}),
|
|
1114
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1115
|
+
type: "button",
|
|
1116
|
+
title: "Zoom out",
|
|
1117
|
+
className: buttonClass(false),
|
|
1118
|
+
onClick: () => commands.execute("zoom:out"),
|
|
1119
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1120
|
+
name: "zoomOut",
|
|
1121
|
+
size: 20
|
|
1122
|
+
})
|
|
1123
|
+
}),
|
|
1124
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1125
|
+
type: "button",
|
|
1126
|
+
title: "Zoom in",
|
|
1127
|
+
className: buttonClass(false),
|
|
1128
|
+
onClick: () => commands.execute("zoom:in"),
|
|
1129
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1130
|
+
name: "zoomIn",
|
|
1131
|
+
size: 20
|
|
1132
|
+
})
|
|
1133
|
+
})
|
|
1134
|
+
]
|
|
1135
|
+
}), menus.isOpen("zoom") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Popover, {
|
|
1136
|
+
onClose: () => menus.close("zoom"),
|
|
1137
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuBody, {
|
|
1138
|
+
menuId: "zoom",
|
|
1139
|
+
onRun: () => menus.close("zoom")
|
|
1140
|
+
})
|
|
1141
|
+
})]
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
/** The 'button' variant of the zoom slot — a single icon that opens the menu. */
|
|
1145
|
+
function ZoomButton() {
|
|
1146
|
+
const menus = (0, _embedpdf_react_shell.useMenus)();
|
|
1147
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1148
|
+
className: "relative inline-flex",
|
|
1149
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1150
|
+
type: "button",
|
|
1151
|
+
title: "Zoom",
|
|
1152
|
+
"aria-haspopup": "menu",
|
|
1153
|
+
"aria-expanded": menus.isOpen("zoom"),
|
|
1154
|
+
className: buttonClass(menus.isOpen("zoom")),
|
|
1155
|
+
onClick: () => menus.toggle("zoom"),
|
|
1156
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1157
|
+
name: "zoomIn",
|
|
1158
|
+
size: 20
|
|
1159
|
+
})
|
|
1160
|
+
}), menus.isOpen("zoom") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Popover, {
|
|
1161
|
+
onClose: () => menus.close("zoom"),
|
|
1162
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuBody, {
|
|
1163
|
+
menuId: "zoom",
|
|
1164
|
+
onRun: () => menus.close("zoom")
|
|
1165
|
+
})
|
|
1166
|
+
})]
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
function GroupTrigger({ view }) {
|
|
1170
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1171
|
+
const activeHidden = view.commands.some((c) => c.active);
|
|
1172
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1173
|
+
className: "relative inline-flex",
|
|
1174
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1175
|
+
type: "button",
|
|
1176
|
+
onClick: () => setOpen((o) => !o),
|
|
1177
|
+
"aria-haspopup": "menu",
|
|
1178
|
+
"aria-expanded": open,
|
|
1179
|
+
className: tabClass(activeHidden),
|
|
1180
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1181
|
+
name: "chevronDown",
|
|
1182
|
+
size: 18
|
|
1183
|
+
})
|
|
1184
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Popover, {
|
|
1185
|
+
onClose: () => setOpen(false),
|
|
1186
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1187
|
+
className: "min-w-44 p-1",
|
|
1188
|
+
children: view.commands.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1189
|
+
type: "button",
|
|
1190
|
+
role: view.role === "tabs" ? "menuitemradio" : "menuitem",
|
|
1191
|
+
"aria-checked": view.role === "tabs" ? c.active : void 0,
|
|
1192
|
+
disabled: !c.enabled,
|
|
1193
|
+
onClick: () => {
|
|
1194
|
+
view.execute(c.id);
|
|
1195
|
+
setOpen(false);
|
|
1196
|
+
},
|
|
1197
|
+
className: `flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm disabled:cursor-not-allowed disabled:opacity-50 ${c.active ? "bg-selected text-accent" : "text-fg-secondary hover:bg-hover"}`,
|
|
1198
|
+
children: c.label
|
|
1199
|
+
}, c.id))
|
|
1200
|
+
})
|
|
1201
|
+
})]
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
function CollapsedModes({ view }) {
|
|
1205
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1206
|
+
const active = view.commands.find((c) => c.active) ?? view.commands[0];
|
|
1207
|
+
const isActive = Boolean(active?.active && active.id !== "mode:view");
|
|
1208
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1209
|
+
style: { width: 100 },
|
|
1210
|
+
className: "relative",
|
|
1211
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1212
|
+
type: "button",
|
|
1213
|
+
onClick: () => setOpen((o) => !o),
|
|
1214
|
+
"aria-haspopup": "menu",
|
|
1215
|
+
"aria-expanded": open,
|
|
1216
|
+
className: "bg-surface hover:bg-hover flex w-full cursor-pointer appearance-none items-center rounded-md py-1.5 pl-3 pr-2 text-[13px] transition-colors",
|
|
1217
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1218
|
+
className: `min-w-0 flex-1 truncate text-left ${isActive ? "text-accent" : "text-fg"}`,
|
|
1219
|
+
children: active?.label
|
|
1220
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1221
|
+
name: "chevronDown",
|
|
1222
|
+
size: 16,
|
|
1223
|
+
className: isActive ? "text-accent" : "text-fg-secondary"
|
|
1224
|
+
})]
|
|
1225
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Popover, {
|
|
1226
|
+
onClose: () => setOpen(false),
|
|
1227
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1228
|
+
className: "min-w-44 p-1",
|
|
1229
|
+
children: view.commands.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1230
|
+
type: "button",
|
|
1231
|
+
role: "menuitemradio",
|
|
1232
|
+
"aria-checked": c.active,
|
|
1233
|
+
onClick: () => {
|
|
1234
|
+
view.execute(c.id);
|
|
1235
|
+
setOpen(false);
|
|
1236
|
+
},
|
|
1237
|
+
className: `flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm ${c.active ? "bg-selected text-accent" : "text-fg-secondary hover:bg-hover"}`,
|
|
1238
|
+
children: c.label
|
|
1239
|
+
}, c.id))
|
|
1240
|
+
})
|
|
1241
|
+
})]
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
function OverflowMenu({ view }) {
|
|
1245
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
1246
|
+
if (!view.isOpen) return null;
|
|
1247
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Popover, {
|
|
1248
|
+
onClose: view.close,
|
|
1249
|
+
align: "end",
|
|
1250
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1251
|
+
className: "min-w-56 p-1",
|
|
1252
|
+
children: view.sections.map((section, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1253
|
+
i > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "bg-border-subtle my-1 h-px" }),
|
|
1254
|
+
section.labelKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1255
|
+
className: "text-fg-muted px-2.5 pb-1 pt-1.5 text-[11px] font-semibold uppercase tracking-wide",
|
|
1256
|
+
children: t(section.labelKey)
|
|
1257
|
+
}),
|
|
1258
|
+
section.rows.map((row) => {
|
|
1259
|
+
const cmd = view.resolve(row.command);
|
|
1260
|
+
if (!cmd) return null;
|
|
1261
|
+
if (row.type === "submenu") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineSubmenu, {
|
|
1262
|
+
menuId: row.menu,
|
|
1263
|
+
label: cmd.label
|
|
1264
|
+
}, row.command);
|
|
1265
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1266
|
+
type: "button",
|
|
1267
|
+
role: section.role === "radio" ? "menuitemradio" : "menuitem",
|
|
1268
|
+
"aria-checked": section.role === "radio" ? cmd.active : void 0,
|
|
1269
|
+
disabled: !cmd.enabled,
|
|
1270
|
+
onClick: () => {
|
|
1271
|
+
view.execute(row.command);
|
|
1272
|
+
view.close();
|
|
1273
|
+
},
|
|
1274
|
+
className: `flex w-full items-center gap-3 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${cmd.active ? "bg-selected text-accent" : "text-fg-secondary hover:bg-hover"}`,
|
|
1275
|
+
children: [cmd.icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1276
|
+
name: cmd.icon,
|
|
1277
|
+
size: 16,
|
|
1278
|
+
className: cmd.active ? "" : "text-fg-muted"
|
|
1279
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1280
|
+
className: "flex-1 truncate",
|
|
1281
|
+
children: cmd.label
|
|
1282
|
+
})]
|
|
1283
|
+
}, row.command);
|
|
1284
|
+
})
|
|
1285
|
+
] }, i))
|
|
1286
|
+
})
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
function OverflowTrigger({ isOpen, toggle }) {
|
|
1290
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1291
|
+
type: "button",
|
|
1292
|
+
onClick: toggle,
|
|
1293
|
+
"aria-haspopup": "menu",
|
|
1294
|
+
"aria-expanded": isOpen,
|
|
1295
|
+
title: (0, _embedpdf_react_i18n.useT)()("demo.more"),
|
|
1296
|
+
className: buttonClass(isOpen),
|
|
1297
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1298
|
+
name: "dots",
|
|
1299
|
+
size: 20
|
|
1300
|
+
})
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
function AppToolbar({ bar, className }) {
|
|
1304
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_toolbar.Toolbar, {
|
|
1305
|
+
bar,
|
|
1306
|
+
gap: 8,
|
|
1307
|
+
separatorWidth: 1,
|
|
1308
|
+
className,
|
|
1309
|
+
renderCommand: (cmd, variant, run) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandButton, {
|
|
1310
|
+
cmd,
|
|
1311
|
+
variant,
|
|
1312
|
+
run
|
|
1313
|
+
}),
|
|
1314
|
+
renderCustom: { "zoom-controls": (variant) => variant === "inline" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomControls, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomButton, {}) },
|
|
1315
|
+
renderCollapsed: (view) => view.id === "modes" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsedModes, { view }) : void 0,
|
|
1316
|
+
renderGroupTrigger: (view) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupTrigger, { view }),
|
|
1317
|
+
renderSeparator: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "bg-border h-6 w-px self-center" }),
|
|
1318
|
+
renderOverflowTrigger: (isOpen, toggle) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverflowTrigger, {
|
|
1319
|
+
isOpen,
|
|
1320
|
+
toggle
|
|
1321
|
+
}),
|
|
1322
|
+
renderOverflowMenu: (view) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverflowMenu, { view })
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
//#endregion
|
|
1326
|
+
//#region src/ui/annotation-strip.tsx
|
|
1327
|
+
/**
|
|
1328
|
+
* The annotation selection strip — v2's floating "selection menu" rebuilt as
|
|
1329
|
+
* chrome. Three parties, none of them this file's business to compute:
|
|
1330
|
+
*
|
|
1331
|
+
* config/chrome.ts `strips.annotation` declares WHAT may appear
|
|
1332
|
+
* each command's visible/enabled decides WHICH items show
|
|
1333
|
+
* <AnnotationMenu> solves WHERE (camera transform,
|
|
1334
|
+
* rotate-knob dodging, pointer isolation)
|
|
1335
|
+
*
|
|
1336
|
+
* useStripView is the live intersection: hidden commands drop out, empty
|
|
1337
|
+
* groups vanish, null when nothing applies. This file only draws pixels.
|
|
1338
|
+
* Mounted in the Stage `overlay` slot.
|
|
1339
|
+
*/
|
|
1340
|
+
function StripButton({ cmd, run }) {
|
|
1341
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1342
|
+
type: "button",
|
|
1343
|
+
onClick: run,
|
|
1344
|
+
disabled: !cmd.enabled,
|
|
1345
|
+
"aria-pressed": cmd.active || void 0,
|
|
1346
|
+
"aria-label": cmd.label,
|
|
1347
|
+
title: cmd.label,
|
|
1348
|
+
className: buttonClass(cmd.active, cmd.enabled),
|
|
1349
|
+
children: cmd.icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1350
|
+
name: cmd.icon,
|
|
1351
|
+
size: 20,
|
|
1352
|
+
accent: cmd.iconAccent
|
|
1353
|
+
})
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
function AnnotationStrip() {
|
|
1357
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
1358
|
+
const view = (0, _embedpdf_react_toolbar.useStripView)(useStripSchema("annotation"));
|
|
1359
|
+
if (!view) return null;
|
|
1360
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_annotation_menu.AnnotationMenu, {
|
|
1361
|
+
placement: "bottom",
|
|
1362
|
+
gap: 15,
|
|
1363
|
+
children: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1364
|
+
role: "toolbar",
|
|
1365
|
+
"aria-label": t("commands.annotate.strip"),
|
|
1366
|
+
className: "border-border-subtle bg-elevated flex items-center gap-1 rounded-lg border p-1 shadow-xl",
|
|
1367
|
+
children: view.groups.map((g, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [i > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1368
|
+
"aria-hidden": true,
|
|
1369
|
+
className: "bg-border h-5 w-px"
|
|
1370
|
+
}), g.commands.map((cmd) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StripButton, {
|
|
1371
|
+
cmd,
|
|
1372
|
+
run: () => view.execute(cmd.id)
|
|
1373
|
+
}, cmd.id))] }, g.id))
|
|
1374
|
+
})
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
//#endregion
|
|
1378
|
+
//#region src/ui/tab-bar.tsx
|
|
1379
|
+
/**
|
|
1380
|
+
* The document tab bar — a 1:1 port of v2's tab-bar.tsx
|
|
1381
|
+
* (viewers/snippet/src/components/tab-bar.tsx): rounded-top tabs on the
|
|
1382
|
+
* surface-alt strip, the active tab merging into the toolbar surface below,
|
|
1383
|
+
* close-× on the active tab, and a + button opening a real file dialog.
|
|
1384
|
+
*
|
|
1385
|
+
* v2 needed a DocumentManager capability; here the kernel's document registry
|
|
1386
|
+
* (useDocuments) already is the tab model — open/close/setActive, reactive.
|
|
1387
|
+
*/
|
|
1388
|
+
function TabBar({ visibility = "always", allowOpenFile = true }) {
|
|
1389
|
+
const { docs, activeId, setActive, close, open } = (0, _embedpdf_react_runtime.useDocuments)();
|
|
1390
|
+
const fileInput = (0, react.useRef)(null);
|
|
1391
|
+
if (!(visibility === "always" || visibility === "multiple" && docs.length > 1)) return null;
|
|
1392
|
+
const openFile = async (file) => {
|
|
1393
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
1394
|
+
const source = {
|
|
1395
|
+
kind: "bytes",
|
|
1396
|
+
id: `${file.name}-${Date.now()}`,
|
|
1397
|
+
bytes
|
|
1398
|
+
};
|
|
1399
|
+
await open(source, { name: file.name });
|
|
1400
|
+
};
|
|
1401
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1402
|
+
className: "bg-surface-alt flex items-end pr-2 pt-2",
|
|
1403
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1404
|
+
className: "flex flex-1 items-end overflow-x-auto pl-4",
|
|
1405
|
+
children: [docs.map((doc) => {
|
|
1406
|
+
const isActive = activeId === doc.id;
|
|
1407
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1408
|
+
onClick: () => setActive(doc.id),
|
|
1409
|
+
role: "tab",
|
|
1410
|
+
part: isActive ? "tab tab-active" : "tab",
|
|
1411
|
+
tabIndex: 0,
|
|
1412
|
+
"aria-selected": isActive,
|
|
1413
|
+
onKeyDown: (e) => {
|
|
1414
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1415
|
+
e.preventDefault();
|
|
1416
|
+
setActive(doc.id);
|
|
1417
|
+
}
|
|
1418
|
+
},
|
|
1419
|
+
className: `group relative flex min-w-[120px] max-w-[240px] cursor-pointer items-center gap-2 rounded-t-lg px-4 py-2 text-sm font-medium transition-all ${isActive ? "bg-surface text-fg z-10" : "bg-surface-alt text-fg-secondary hover:bg-hover hover:text-fg"}`,
|
|
1420
|
+
children: [
|
|
1421
|
+
doc.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1422
|
+
className: "border-border-subtle border-t-accent h-3 w-3 flex-shrink-0 animate-spin rounded-full border",
|
|
1423
|
+
"aria-label": "Loading"
|
|
1424
|
+
}),
|
|
1425
|
+
doc.status === "locked" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1426
|
+
name: "lock",
|
|
1427
|
+
size: 13,
|
|
1428
|
+
className: "text-fg-muted flex-shrink-0"
|
|
1429
|
+
}),
|
|
1430
|
+
doc.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1431
|
+
name: "alertTriangle",
|
|
1432
|
+
size: 13,
|
|
1433
|
+
className: "flex-shrink-0 text-red-500"
|
|
1434
|
+
}),
|
|
1435
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1436
|
+
className: "min-w-0 flex-1 truncate",
|
|
1437
|
+
children: doc.name ?? `Document ${doc.id.slice(0, 8)}`
|
|
1438
|
+
}),
|
|
1439
|
+
isActive && docs.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1440
|
+
type: "button",
|
|
1441
|
+
onClick: (e) => {
|
|
1442
|
+
e.stopPropagation();
|
|
1443
|
+
close(doc.id);
|
|
1444
|
+
},
|
|
1445
|
+
"aria-label": `Close ${doc.name ?? "document"}`,
|
|
1446
|
+
className: "hover:bg-hover flex-shrink-0 cursor-pointer rounded-full p-1 opacity-100 transition-all",
|
|
1447
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1448
|
+
name: "x",
|
|
1449
|
+
size: 14
|
|
1450
|
+
})
|
|
1451
|
+
})
|
|
1452
|
+
]
|
|
1453
|
+
}, doc.id);
|
|
1454
|
+
}), allowOpenFile && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1455
|
+
type: "button",
|
|
1456
|
+
onClick: () => fileInput.current?.click(),
|
|
1457
|
+
"aria-label": "Open File",
|
|
1458
|
+
title: "Open File",
|
|
1459
|
+
className: "text-fg-secondary hover:bg-hover hover:text-fg mb-1.5 ml-1 flex-shrink-0 cursor-pointer rounded p-1.5 transition-colors",
|
|
1460
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1461
|
+
name: "plus",
|
|
1462
|
+
size: 14
|
|
1463
|
+
})
|
|
1464
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1465
|
+
ref: fileInput,
|
|
1466
|
+
type: "file",
|
|
1467
|
+
accept: ".pdf,application/pdf",
|
|
1468
|
+
className: "hidden",
|
|
1469
|
+
onChange: (e) => {
|
|
1470
|
+
const file = e.target.files?.[0];
|
|
1471
|
+
if (file) openFile(file);
|
|
1472
|
+
e.target.value = "";
|
|
1473
|
+
}
|
|
1474
|
+
})] })]
|
|
1475
|
+
})
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
//#endregion
|
|
1479
|
+
//#region src/ui/tool-cursor.tsx
|
|
1480
|
+
/**
|
|
1481
|
+
* THIS viewer's armed-tool cursor: the toolbar's own icon riding the pointer
|
|
1482
|
+
* as a real CSS cursor (OS-composited — zero lag, unlike any DOM follower).
|
|
1483
|
+
* The library owns arbitration (unmapped hover claims win, page gaps fall
|
|
1484
|
+
* back to the arrow — see `useToolCursor`); this module only decides what the
|
|
1485
|
+
* cursor LOOKS like. It reuses the SAME icon set and the SAME accent
|
|
1486
|
+
* derivation as the toolbar buttons (TOOL_ICONS is recorded by the command
|
|
1487
|
+
* definitions), so the cursor is pixel-identical to the button the user just
|
|
1488
|
+
* pressed — recolor the tool and both follow. A tool without a command icon
|
|
1489
|
+
* keeps its declared keyword cursor.
|
|
1490
|
+
*
|
|
1491
|
+
* ONE table decides the shape ({@link GLYPHS}): cursor KEYWORDS are the
|
|
1492
|
+
* affordances — a tool declares its base ('crosshair' to draw, 'copy' to
|
|
1493
|
+
* place), the selection handler claims 'text' over text — and each mapped
|
|
1494
|
+
* keyword is redrawn as its glyph + the tool's icon. Keywords the table omits
|
|
1495
|
+
* stay native: a markup tool's 'default' base is the bare arrow (identity
|
|
1496
|
+
* appears exactly where the action is possible — over text), a foreign 'move'
|
|
1497
|
+
* over an annotation drops the icon. A new tool gets the right cursor from
|
|
1498
|
+
* what it declares; nothing tool-specific lives here.
|
|
1499
|
+
*
|
|
1500
|
+
* The image: a white-haloed glyph at the hotspot (readable on any page color)
|
|
1501
|
+
* with the icon at its top right — 40px, under the 128px cursor ceiling;
|
|
1502
|
+
* Chromium may briefly show the default arrow when a >32px cursor would
|
|
1503
|
+
* overlap browser UI (anti-spoofing), which is fine mid-viewport.
|
|
1504
|
+
*/
|
|
1505
|
+
const SIZE = 40;
|
|
1506
|
+
const HOTSPOT = {
|
|
1507
|
+
x: 8,
|
|
1508
|
+
y: 28
|
|
1509
|
+
};
|
|
1510
|
+
const INK = "#1a1a1a";
|
|
1511
|
+
const HALO = "#ffffff";
|
|
1512
|
+
/** The drag-and-drop "will place a copy" green (Chrome's DnD badge). */
|
|
1513
|
+
const PLACE_GREEN = "#34a853";
|
|
1514
|
+
/** The icon's paths as SVG markup — the string twin of `<Icon>`'s render loop
|
|
1515
|
+
* (same fill/stroke slot rules), with a white halo pass underneath so the
|
|
1516
|
+
* glyph stays readable over page content. */
|
|
1517
|
+
function iconGlyph(name, accent) {
|
|
1518
|
+
const paths = ICON_PATHS[name];
|
|
1519
|
+
if (!paths) return null;
|
|
1520
|
+
const halo = [];
|
|
1521
|
+
const draw = [];
|
|
1522
|
+
for (const p of paths) {
|
|
1523
|
+
const spec = typeof p === "string" ? {
|
|
1524
|
+
d: p,
|
|
1525
|
+
fill: void 0,
|
|
1526
|
+
stroke: void 0
|
|
1527
|
+
} : p;
|
|
1528
|
+
const fill = spec.fill === true ? INK : spec.fill ? accent?.[spec.fill] : void 0;
|
|
1529
|
+
const stroke = spec.stroke ? accent?.[spec.stroke] ?? INK : spec.fill ? "none" : INK;
|
|
1530
|
+
halo.push(`<path d="${spec.d}" fill="none" stroke="${HALO}" stroke-width="3.5"/>`);
|
|
1531
|
+
draw.push(`<path d="${spec.d}" fill="${fill ?? "none"}" stroke="${stroke}"/>`);
|
|
1532
|
+
}
|
|
1533
|
+
return halo.join("") + draw.join("");
|
|
1534
|
+
}
|
|
1535
|
+
const haloed = (x1, y1, x2, y2) => `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${HALO}" stroke-width="3"/><line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${INK}" stroke-width="1.25"/>`;
|
|
1536
|
+
function actionGlyph(glyph) {
|
|
1537
|
+
const { x, y } = HOTSPOT;
|
|
1538
|
+
if (glyph === "crosshair") return haloed(x, y - 6, x, y + 6) + haloed(x - 6, y, x + 6, y);
|
|
1539
|
+
if (glyph === "ibeam") return haloed(x, y - 6.5, x, y + 6.5) + haloed(x - 2.5, y - 6.5, x + 2.5, y - 6.5) + haloed(x - 2.5, y + 6.5, x + 2.5, y + 6.5);
|
|
1540
|
+
return `<circle cx="${x}" cy="${y}" r="7.5" fill="${PLACE_GREEN}" stroke="${HALO}" stroke-width="1.5"/><line x1="${x}" y1="${y - 3.5}" x2="${x}" y2="${y + 3.5}" stroke="${HALO}" stroke-width="2"/><line x1="${x - 3.5}" y1="${y}" x2="${x + 3.5}" y2="${y}" stroke="${HALO}" stroke-width="2"/>`;
|
|
1541
|
+
}
|
|
1542
|
+
function cursorSvg(glyph, icon, accent) {
|
|
1543
|
+
const g = iconGlyph(icon, accent);
|
|
1544
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}" fill="none" stroke-linecap="round" stroke-linejoin="round">` + actionGlyph(glyph) + (g ? `<g transform="translate(17 3) scale(${(20 / 24).toFixed(4)})" stroke-width="2">${g}</g>` : "") + `</svg>`;
|
|
1545
|
+
}
|
|
1546
|
+
/** keyword → glyph: how each affordance is drawn when a tool icon rides
|
|
1547
|
+
* along. Draw tools declare 'crosshair', place tools (stamp/signature/image)
|
|
1548
|
+
* declare 'copy', and the selection handler claims 'text' over text — which
|
|
1549
|
+
* keywords ever SHOW is decided by those declarations and claims, never
|
|
1550
|
+
* here. 'default' is deliberately absent: the bare arrow means "no action
|
|
1551
|
+
* here", so it never carries an icon. */
|
|
1552
|
+
const GLYPHS = {
|
|
1553
|
+
crosshair: "crosshair",
|
|
1554
|
+
text: "ibeam",
|
|
1555
|
+
copy: "plus"
|
|
1556
|
+
};
|
|
1557
|
+
/** Mount once (renders nothing): keeps the armed tool's cursor skin in sync
|
|
1558
|
+
* with its toolbar icon + live defaults. */
|
|
1559
|
+
function ArmedToolCursor() {
|
|
1560
|
+
const { activeToolId } = (0, _embedpdf_react_interaction.useTool)();
|
|
1561
|
+
const d = (0, _embedpdf_react_annotation.useAnnotationDefaults)(activeToolId);
|
|
1562
|
+
const entry = require_commands.TOOL_ICONS[activeToolId];
|
|
1563
|
+
const accent = entry?.accent ? {
|
|
1564
|
+
primary: d[entry.accent.primary] ?? void 0,
|
|
1565
|
+
secondary: entry.accent.secondary ? d[entry.accent.secondary] ?? void 0 : void 0
|
|
1566
|
+
} : void 0;
|
|
1567
|
+
(0, _embedpdf_react_interaction.useToolCursor)(entry ? {
|
|
1568
|
+
toolId: activeToolId,
|
|
1569
|
+
cursors: Object.fromEntries(Object.entries(GLYPHS).map(([keyword, glyph]) => [keyword, {
|
|
1570
|
+
svg: cursorSvg(glyph, entry.icon, accent),
|
|
1571
|
+
hotspot: HOTSPOT
|
|
1572
|
+
}]))
|
|
1573
|
+
} : null);
|
|
1574
|
+
return null;
|
|
1575
|
+
}
|
|
1576
|
+
//#endregion
|
|
1577
|
+
//#region src/ui/annotation-flags.tsx
|
|
1578
|
+
/**
|
|
1579
|
+
* The annotation FLAGS section — a live test surface for `/F` (ISO 32000
|
|
1580
|
+
* Table 167). Select any annotation(s) and toggle flags; the writes go through
|
|
1581
|
+
* the plugin's `updateSelectionFlags` (optimistic, flags-only engine patch),
|
|
1582
|
+
* and the reads come from `useSelectionFlags` (per-flag value, `null` when the
|
|
1583
|
+
* selection disagrees → indeterminate checkbox).
|
|
1584
|
+
*
|
|
1585
|
+
* Things to try:
|
|
1586
|
+
* - `locked`: the selection keeps its outline but loses handles/knob; move,
|
|
1587
|
+
* restyle and delete are refused; unchecking it here still works.
|
|
1588
|
+
* - `hidden` / `noView`: the annotation disappears (it stays selected, so
|
|
1589
|
+
* this panel can bring it back).
|
|
1590
|
+
* - `readOnly`: visible but no longer clickable — deselect and try.
|
|
1591
|
+
* - `noZoom` / `noRotate`: zoom or rotate the page; the body keeps its
|
|
1592
|
+
* screen size / stays upright, anchored at its top-left corner.
|
|
1593
|
+
* - `print`: flip it off and print/flatten elsewhere — the annotation is
|
|
1594
|
+
* excluded (viewer rendering is unaffected).
|
|
1595
|
+
*/
|
|
1596
|
+
const FLAG_ROWS = [
|
|
1597
|
+
{
|
|
1598
|
+
key: "print",
|
|
1599
|
+
label: "Print",
|
|
1600
|
+
hint: "Include when the page is printed"
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
key: "hidden",
|
|
1604
|
+
label: "Hidden",
|
|
1605
|
+
hint: "Gone everywhere: screen, clicks, print"
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
key: "noView",
|
|
1609
|
+
label: "No view",
|
|
1610
|
+
hint: "Hidden on screen, may still print"
|
|
1611
|
+
},
|
|
1612
|
+
{
|
|
1613
|
+
key: "toggleNoView",
|
|
1614
|
+
label: "Toggle no view",
|
|
1615
|
+
hint: "Reveal a no-view annotation while selected"
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
key: "readOnly",
|
|
1619
|
+
label: "Read only",
|
|
1620
|
+
hint: "Visible but not clickable"
|
|
1621
|
+
},
|
|
1622
|
+
{
|
|
1623
|
+
key: "locked",
|
|
1624
|
+
label: "Locked",
|
|
1625
|
+
hint: "No move / resize / delete / restyle"
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
key: "lockedContents",
|
|
1629
|
+
label: "Locked contents",
|
|
1630
|
+
hint: "Text cannot change; geometry can"
|
|
1631
|
+
},
|
|
1632
|
+
{
|
|
1633
|
+
key: "noZoom",
|
|
1634
|
+
label: "No zoom",
|
|
1635
|
+
hint: "Screen-constant size (anchored top-left)"
|
|
1636
|
+
},
|
|
1637
|
+
{
|
|
1638
|
+
key: "noRotate",
|
|
1639
|
+
label: "No rotate",
|
|
1640
|
+
hint: "Stays upright when the page rotates"
|
|
1641
|
+
},
|
|
1642
|
+
{
|
|
1643
|
+
key: "invisible",
|
|
1644
|
+
label: "Invisible",
|
|
1645
|
+
hint: "Legacy: hide unknown subtypes"
|
|
1646
|
+
}
|
|
1647
|
+
];
|
|
1648
|
+
function FlagRow({ label, hint, value, onToggle }) {
|
|
1649
|
+
const ref = (0, react.useRef)(null);
|
|
1650
|
+
(0, react.useEffect)(() => {
|
|
1651
|
+
if (ref.current) ref.current.indeterminate = value === null;
|
|
1652
|
+
}, [value]);
|
|
1653
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1654
|
+
className: "hover:bg-hover flex cursor-pointer items-start gap-2.5 rounded px-1.5 py-1.5",
|
|
1655
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1656
|
+
ref,
|
|
1657
|
+
type: "checkbox",
|
|
1658
|
+
className: "accent-accent mt-0.5",
|
|
1659
|
+
checked: value === true,
|
|
1660
|
+
onChange: () => onToggle(value !== true)
|
|
1661
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1662
|
+
className: "min-w-0",
|
|
1663
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1664
|
+
className: "text-fg block text-sm leading-tight",
|
|
1665
|
+
children: label
|
|
1666
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1667
|
+
className: "text-fg-muted block text-xs leading-tight",
|
|
1668
|
+
children: hint
|
|
1669
|
+
})]
|
|
1670
|
+
})]
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
/** The `/F` flags of the current selection, live-editable. Renders nothing
|
|
1674
|
+
* when nothing is selected (the style panel shows its tool defaults then). */
|
|
1675
|
+
function AnnotationFlagsSection() {
|
|
1676
|
+
const annotation = (0, _embedpdf_react_annotation.useAnnotation)();
|
|
1677
|
+
const flags = (0, _embedpdf_react_annotation.useSelectionFlags)();
|
|
1678
|
+
if (!flags) return null;
|
|
1679
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1680
|
+
className: "border-border mt-2 border-t pt-4",
|
|
1681
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1682
|
+
className: "text-fg-muted mb-2 text-[11px] font-semibold uppercase tracking-wide",
|
|
1683
|
+
children: "Flags (/F)"
|
|
1684
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1685
|
+
className: "flex flex-col",
|
|
1686
|
+
children: FLAG_ROWS.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FlagRow, {
|
|
1687
|
+
label: row.label,
|
|
1688
|
+
hint: row.hint,
|
|
1689
|
+
value: flags[row.key],
|
|
1690
|
+
onToggle: (next) => annotation.updateSelectionFlags({ [row.key]: next })
|
|
1691
|
+
}, row.key))
|
|
1692
|
+
})]
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
//#endregion
|
|
1696
|
+
//#region src/ui/annotation-style.tsx
|
|
1697
|
+
/**
|
|
1698
|
+
* The annotation style panel — the v2 snippet's style sidebar, rebuilt the v3
|
|
1699
|
+
* way. The plugin owns the schema AND the state; this file owns only how each
|
|
1700
|
+
* PropSpec renders and the flat patch it writes back. There is NO per-subtype
|
|
1701
|
+
* branching here: a new annotation kind that declares its `PropSpec[]` in the
|
|
1702
|
+
* kind table gets a working style panel for free.
|
|
1703
|
+
*
|
|
1704
|
+
* Data flow (mirrors examples/react's AnnotationSidebar):
|
|
1705
|
+
* selection present → useSelectionProps() specs/values, write updateSelection()
|
|
1706
|
+
* nothing selected → propsForTool(tool) specs + useAnnotationDefaults(tool)
|
|
1707
|
+
* values, write setDefaults(tool, …)
|
|
1708
|
+
*
|
|
1709
|
+
* The look is ported 1:1 from viewers/snippet's annotation-sidebar (six-column
|
|
1710
|
+
* swatch grid, range slider, SVG stroke / line-ending dropdowns, font-size
|
|
1711
|
+
* combo, align toggles), retinted from v2's `bg-bg-input`/`text-fg-primary`
|
|
1712
|
+
* tokens to this app's semantic ones (bg-surface / text-fg / border-border …).
|
|
1713
|
+
*/
|
|
1714
|
+
const PRESET_COLORS = [
|
|
1715
|
+
"#000000",
|
|
1716
|
+
"#5f6368",
|
|
1717
|
+
"#e44234",
|
|
1718
|
+
"#ff8b00",
|
|
1719
|
+
"#ffd500",
|
|
1720
|
+
"#00cc66",
|
|
1721
|
+
"#00b8d9",
|
|
1722
|
+
"#4a90e2",
|
|
1723
|
+
"#9b51e0",
|
|
1724
|
+
"#f272c8",
|
|
1725
|
+
"#a0522d",
|
|
1726
|
+
"#ffffff"
|
|
1727
|
+
];
|
|
1728
|
+
const FONT_OPTIONS = [
|
|
1729
|
+
{
|
|
1730
|
+
v: "helvetica",
|
|
1731
|
+
label: "Helvetica"
|
|
1732
|
+
},
|
|
1733
|
+
{
|
|
1734
|
+
v: "helvetica-bold",
|
|
1735
|
+
label: "Helvetica Bold"
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
v: "times-roman",
|
|
1739
|
+
label: "Times Roman"
|
|
1740
|
+
},
|
|
1741
|
+
{
|
|
1742
|
+
v: "courier",
|
|
1743
|
+
label: "Courier"
|
|
1744
|
+
}
|
|
1745
|
+
];
|
|
1746
|
+
const FONT_SIZES = [
|
|
1747
|
+
8,
|
|
1748
|
+
9,
|
|
1749
|
+
10,
|
|
1750
|
+
11,
|
|
1751
|
+
12,
|
|
1752
|
+
14,
|
|
1753
|
+
16,
|
|
1754
|
+
18,
|
|
1755
|
+
24,
|
|
1756
|
+
36,
|
|
1757
|
+
48,
|
|
1758
|
+
72
|
|
1759
|
+
];
|
|
1760
|
+
const BORDER_OPTS = [
|
|
1761
|
+
{
|
|
1762
|
+
key: "solid",
|
|
1763
|
+
make: () => ({ kind: "solid" })
|
|
1764
|
+
},
|
|
1765
|
+
{
|
|
1766
|
+
key: "dashed-6-2",
|
|
1767
|
+
make: () => ({
|
|
1768
|
+
kind: "dashed",
|
|
1769
|
+
dash: [6, 2]
|
|
1770
|
+
})
|
|
1771
|
+
},
|
|
1772
|
+
{
|
|
1773
|
+
key: "dashed-3-3",
|
|
1774
|
+
make: () => ({
|
|
1775
|
+
kind: "dashed",
|
|
1776
|
+
dash: [3, 3]
|
|
1777
|
+
})
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
key: "dashed-1-2",
|
|
1781
|
+
make: () => ({
|
|
1782
|
+
kind: "dashed",
|
|
1783
|
+
dash: [1, 2]
|
|
1784
|
+
})
|
|
1785
|
+
},
|
|
1786
|
+
{
|
|
1787
|
+
key: "cloudy-1",
|
|
1788
|
+
make: () => ({
|
|
1789
|
+
kind: "cloudy",
|
|
1790
|
+
intensity: 1
|
|
1791
|
+
})
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
key: "cloudy-2",
|
|
1795
|
+
make: () => ({
|
|
1796
|
+
kind: "cloudy",
|
|
1797
|
+
intensity: 2
|
|
1798
|
+
})
|
|
1799
|
+
}
|
|
1800
|
+
];
|
|
1801
|
+
const borderKey = (b) => b.kind === "cloudy" ? `cloudy-${b.intensity >= 2 ? 2 : 1}` : b.kind === "dashed" ? `dashed-${b.dash.join("-")}` : "solid";
|
|
1802
|
+
const LINE_ENDINGS = [
|
|
1803
|
+
{
|
|
1804
|
+
v: "none",
|
|
1805
|
+
label: "None"
|
|
1806
|
+
},
|
|
1807
|
+
{
|
|
1808
|
+
v: "open-arrow",
|
|
1809
|
+
label: "Open arrow"
|
|
1810
|
+
},
|
|
1811
|
+
{
|
|
1812
|
+
v: "closed-arrow",
|
|
1813
|
+
label: "Closed arrow"
|
|
1814
|
+
},
|
|
1815
|
+
{
|
|
1816
|
+
v: "r-open-arrow",
|
|
1817
|
+
label: "Reverse open"
|
|
1818
|
+
},
|
|
1819
|
+
{
|
|
1820
|
+
v: "r-closed-arrow",
|
|
1821
|
+
label: "Reverse closed"
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
v: "circle",
|
|
1825
|
+
label: "Circle"
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
v: "square",
|
|
1829
|
+
label: "Square"
|
|
1830
|
+
},
|
|
1831
|
+
{
|
|
1832
|
+
v: "diamond",
|
|
1833
|
+
label: "Diamond"
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
v: "butt",
|
|
1837
|
+
label: "Butt"
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
v: "slash",
|
|
1841
|
+
label: "Slash"
|
|
1842
|
+
}
|
|
1843
|
+
];
|
|
1844
|
+
const BLEND_MODES = [
|
|
1845
|
+
{
|
|
1846
|
+
v: "normal",
|
|
1847
|
+
label: "Normal"
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
v: "multiply",
|
|
1851
|
+
label: "Multiply"
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
v: "screen",
|
|
1855
|
+
label: "Screen"
|
|
1856
|
+
},
|
|
1857
|
+
{
|
|
1858
|
+
v: "overlay",
|
|
1859
|
+
label: "Overlay"
|
|
1860
|
+
},
|
|
1861
|
+
{
|
|
1862
|
+
v: "darken",
|
|
1863
|
+
label: "Darken"
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
v: "lighten",
|
|
1867
|
+
label: "Lighten"
|
|
1868
|
+
},
|
|
1869
|
+
{
|
|
1870
|
+
v: "color-dodge",
|
|
1871
|
+
label: "Color Dodge"
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
v: "color-burn",
|
|
1875
|
+
label: "Color Burn"
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
v: "hard-light",
|
|
1879
|
+
label: "Hard Light"
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
v: "soft-light",
|
|
1883
|
+
label: "Soft Light"
|
|
1884
|
+
},
|
|
1885
|
+
{
|
|
1886
|
+
v: "difference",
|
|
1887
|
+
label: "Difference"
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
v: "exclusion",
|
|
1891
|
+
label: "Exclusion"
|
|
1892
|
+
},
|
|
1893
|
+
{
|
|
1894
|
+
v: "hue",
|
|
1895
|
+
label: "Hue"
|
|
1896
|
+
},
|
|
1897
|
+
{
|
|
1898
|
+
v: "saturation",
|
|
1899
|
+
label: "Saturation"
|
|
1900
|
+
},
|
|
1901
|
+
{
|
|
1902
|
+
v: "color",
|
|
1903
|
+
label: "Color"
|
|
1904
|
+
},
|
|
1905
|
+
{
|
|
1906
|
+
v: "luminosity",
|
|
1907
|
+
label: "Luminosity"
|
|
1908
|
+
}
|
|
1909
|
+
];
|
|
1910
|
+
function Field({ label, mixed, children }) {
|
|
1911
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1912
|
+
className: "mb-5",
|
|
1913
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1914
|
+
className: "text-fg mb-2 block text-sm font-medium",
|
|
1915
|
+
children: [label, mixed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1916
|
+
className: "text-fg-muted ml-1.5 text-xs font-normal",
|
|
1917
|
+
children: "(mixed)"
|
|
1918
|
+
})]
|
|
1919
|
+
}), children]
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
const isTransparent = (c) => c == null || c === "transparent" || /^#([0-9a-f]{8})$/i.test(c) && c.slice(-2).toLowerCase() === "00";
|
|
1923
|
+
function Swatch({ color, active, onSelect }) {
|
|
1924
|
+
const style = isTransparent(color) ? {
|
|
1925
|
+
backgroundColor: "#fff",
|
|
1926
|
+
backgroundImage: "linear-gradient(45deg, transparent 40%, #ef4444 40%, #ef4444 60%, transparent 60%)",
|
|
1927
|
+
backgroundSize: "100% 100%"
|
|
1928
|
+
} : { backgroundColor: color };
|
|
1929
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1930
|
+
type: "button",
|
|
1931
|
+
title: color,
|
|
1932
|
+
onClick: () => onSelect(color),
|
|
1933
|
+
className: `border-border-strong h-5 w-5 rounded-full border ${active ? "outline-accent outline outline-2 outline-offset-2" : ""}`,
|
|
1934
|
+
style
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
function SwatchGrid({ value, onSelect, allowTransparent }) {
|
|
1938
|
+
const active = (c) => c.toLowerCase() === (value ?? "").toLowerCase();
|
|
1939
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1940
|
+
className: "grid grid-cols-6 gap-x-1 gap-y-3",
|
|
1941
|
+
children: [PRESET_COLORS.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Swatch, {
|
|
1942
|
+
color: c,
|
|
1943
|
+
active: active(c),
|
|
1944
|
+
onSelect
|
|
1945
|
+
}, c)), allowTransparent && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Swatch, {
|
|
1946
|
+
color: "transparent",
|
|
1947
|
+
active: isTransparent(value ?? null),
|
|
1948
|
+
onSelect
|
|
1949
|
+
})]
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
function RangeSlider({ value, min, max, step, onChange }) {
|
|
1953
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1954
|
+
type: "range",
|
|
1955
|
+
className: "accent-accent mb-1.5 h-1 w-full cursor-pointer",
|
|
1956
|
+
value,
|
|
1957
|
+
min,
|
|
1958
|
+
max,
|
|
1959
|
+
step,
|
|
1960
|
+
onChange: (e) => onChange(parseFloat(e.target.value))
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
function useOutsideClose(open, close) {
|
|
1964
|
+
const rootRef = (0, react.useRef)(null);
|
|
1965
|
+
(0, react.useEffect)(() => {
|
|
1966
|
+
if (!open) return;
|
|
1967
|
+
const onDoc = (e) => {
|
|
1968
|
+
if (rootRef.current && !e.composedPath().includes(rootRef.current)) close();
|
|
1969
|
+
};
|
|
1970
|
+
document.addEventListener("mousedown", onDoc);
|
|
1971
|
+
return () => document.removeEventListener("mousedown", onDoc);
|
|
1972
|
+
}, [open, close]);
|
|
1973
|
+
return rootRef;
|
|
1974
|
+
}
|
|
1975
|
+
function DropdownShell({ trigger, children }) {
|
|
1976
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1977
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1978
|
+
ref: useOutsideClose(open, () => setOpen(false)),
|
|
1979
|
+
className: "relative w-full",
|
|
1980
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1981
|
+
type: "button",
|
|
1982
|
+
onClick: () => setOpen((o) => !o),
|
|
1983
|
+
className: "border-border bg-surface text-fg flex w-full items-center justify-between gap-2 rounded border px-3 py-1.5",
|
|
1984
|
+
children: [trigger, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1985
|
+
name: "chevronDown",
|
|
1986
|
+
size: 16,
|
|
1987
|
+
className: "text-fg-secondary shrink-0"
|
|
1988
|
+
})]
|
|
1989
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1990
|
+
className: "border-border bg-elevated absolute z-10 mt-1 max-h-60 w-full overflow-y-auto rounded border p-1 shadow-lg",
|
|
1991
|
+
children: children(() => setOpen(false))
|
|
1992
|
+
})]
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
function OptionRow({ selected, onClick, children }) {
|
|
1996
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1997
|
+
type: "button",
|
|
1998
|
+
onClick,
|
|
1999
|
+
className: `hover:bg-hover flex w-full items-center rounded px-2 py-1.5 text-left text-sm ${selected ? "bg-selected text-accent" : "text-fg"}`,
|
|
2000
|
+
children
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
function borderSvg(b) {
|
|
2004
|
+
if (b.kind === "cloudy") {
|
|
2005
|
+
const r = b.intensity >= 2 ? 5 : 3;
|
|
2006
|
+
const n = Math.ceil(80 / (r * 2));
|
|
2007
|
+
const step = 80 / n;
|
|
2008
|
+
const baseline = r + 1;
|
|
2009
|
+
const viewH = r * 2 + 2;
|
|
2010
|
+
const parts = [`M 0 ${baseline}`];
|
|
2011
|
+
for (let i = 0; i < n; i++) parts.push(`A ${r} ${r} 0 0 1 ${step * (i + 1)} ${baseline}`);
|
|
2012
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2013
|
+
width: "80",
|
|
2014
|
+
height: viewH,
|
|
2015
|
+
viewBox: `0 0 80 ${viewH}`,
|
|
2016
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2017
|
+
d: parts.join(" "),
|
|
2018
|
+
fill: "none",
|
|
2019
|
+
stroke: "currentColor",
|
|
2020
|
+
strokeWidth: "1.5",
|
|
2021
|
+
strokeLinejoin: "round",
|
|
2022
|
+
strokeLinecap: "round"
|
|
2023
|
+
})
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2027
|
+
width: "80",
|
|
2028
|
+
height: "8",
|
|
2029
|
+
viewBox: "0 0 80 8",
|
|
2030
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
2031
|
+
x1: "0",
|
|
2032
|
+
y1: "4",
|
|
2033
|
+
x2: "80",
|
|
2034
|
+
y2: "4",
|
|
2035
|
+
stroke: "currentColor",
|
|
2036
|
+
strokeWidth: "2",
|
|
2037
|
+
strokeDasharray: b.kind === "dashed" ? b.dash.join(" ") : void 0
|
|
2038
|
+
})
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
function BorderSelect({ value, cloudy, onChange }) {
|
|
2042
|
+
const opts = cloudy ? BORDER_OPTS : BORDER_OPTS.filter((o) => !o.key.startsWith("cloudy"));
|
|
2043
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownShell, {
|
|
2044
|
+
trigger: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2045
|
+
className: "text-fg-secondary",
|
|
2046
|
+
children: borderSvg(value)
|
|
2047
|
+
}),
|
|
2048
|
+
children: (close) => opts.map((o) => {
|
|
2049
|
+
const b = o.make();
|
|
2050
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionRow, {
|
|
2051
|
+
selected: borderKey(value) === o.key,
|
|
2052
|
+
onClick: () => {
|
|
2053
|
+
onChange(b);
|
|
2054
|
+
close();
|
|
2055
|
+
},
|
|
2056
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2057
|
+
className: "text-fg-secondary",
|
|
2058
|
+
children: borderSvg(b)
|
|
2059
|
+
})
|
|
2060
|
+
}, o.key);
|
|
2061
|
+
})
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
const LE_MARKERS = {
|
|
2065
|
+
square: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M68 -4 L76 -4 L76 4 L68 4 Z" }),
|
|
2066
|
+
circle: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2067
|
+
cx: "72",
|
|
2068
|
+
cy: "0",
|
|
2069
|
+
r: "4"
|
|
2070
|
+
}),
|
|
2071
|
+
diamond: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M72 -5 L77 0 L72 5 L67 0 Z" }),
|
|
2072
|
+
"open-arrow": /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2073
|
+
d: "M67 -5 L77 0 L67 5",
|
|
2074
|
+
fill: "none"
|
|
2075
|
+
}),
|
|
2076
|
+
"closed-arrow": /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M67 -5 L77 0 L67 5 Z" }),
|
|
2077
|
+
"r-open-arrow": /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2078
|
+
d: "M77 -5 L67 0 L77 5",
|
|
2079
|
+
fill: "none"
|
|
2080
|
+
}),
|
|
2081
|
+
"r-closed-arrow": /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M77 -5 L67 0 L77 5 Z" }),
|
|
2082
|
+
butt: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2083
|
+
d: "M72 -5 L72 5",
|
|
2084
|
+
fill: "none"
|
|
2085
|
+
}),
|
|
2086
|
+
slash: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2087
|
+
d: "M67 -5 L77 5",
|
|
2088
|
+
fill: "none"
|
|
2089
|
+
})
|
|
2090
|
+
};
|
|
2091
|
+
const LE_LINE_END = {
|
|
2092
|
+
square: 68,
|
|
2093
|
+
circle: 68,
|
|
2094
|
+
diamond: 67,
|
|
2095
|
+
"open-arrow": 76,
|
|
2096
|
+
"closed-arrow": 67,
|
|
2097
|
+
"r-open-arrow": 67,
|
|
2098
|
+
"r-closed-arrow": 67,
|
|
2099
|
+
butt: 72,
|
|
2100
|
+
slash: 72
|
|
2101
|
+
};
|
|
2102
|
+
function LineEndingPreview({ name, side }) {
|
|
2103
|
+
const marker = LE_MARKERS[name];
|
|
2104
|
+
const lineEndX = LE_LINE_END[name] ?? 77;
|
|
2105
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2106
|
+
width: "80",
|
|
2107
|
+
height: "20",
|
|
2108
|
+
viewBox: "0 0 80 20",
|
|
2109
|
+
className: "text-fg",
|
|
2110
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
|
|
2111
|
+
transform: side === "start" ? "rotate(180 40 10)" : void 0,
|
|
2112
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
2113
|
+
x1: "4",
|
|
2114
|
+
y1: "10",
|
|
2115
|
+
x2: lineEndX,
|
|
2116
|
+
y2: "10",
|
|
2117
|
+
stroke: "currentColor",
|
|
2118
|
+
strokeWidth: "1.5"
|
|
2119
|
+
}), marker && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
|
|
2120
|
+
transform: "translate(0, 10)",
|
|
2121
|
+
fill: "currentColor",
|
|
2122
|
+
stroke: "currentColor",
|
|
2123
|
+
strokeWidth: "1.5",
|
|
2124
|
+
children: marker
|
|
2125
|
+
})]
|
|
2126
|
+
})
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
function LineEndingSelect({ side, value, onChange }) {
|
|
2130
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownShell, {
|
|
2131
|
+
trigger: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineEndingPreview, {
|
|
2132
|
+
name: value,
|
|
2133
|
+
side
|
|
2134
|
+
}),
|
|
2135
|
+
children: (close) => LINE_ENDINGS.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionRow, {
|
|
2136
|
+
selected: value === o.v,
|
|
2137
|
+
onClick: () => {
|
|
2138
|
+
onChange(o.v);
|
|
2139
|
+
close();
|
|
2140
|
+
},
|
|
2141
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineEndingPreview, {
|
|
2142
|
+
name: o.v,
|
|
2143
|
+
side
|
|
2144
|
+
})
|
|
2145
|
+
}, o.v))
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
function FontFamilySelect({ value, onChange }) {
|
|
2149
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownShell, {
|
|
2150
|
+
trigger: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2151
|
+
className: "text-fg text-sm",
|
|
2152
|
+
children: FONT_OPTIONS.find((o) => o.v === value)?.label ?? value
|
|
2153
|
+
}),
|
|
2154
|
+
children: (close) => FONT_OPTIONS.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionRow, {
|
|
2155
|
+
selected: o.v === value,
|
|
2156
|
+
onClick: () => {
|
|
2157
|
+
onChange(o.v);
|
|
2158
|
+
close();
|
|
2159
|
+
},
|
|
2160
|
+
children: o.label
|
|
2161
|
+
}, o.v))
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
function FontSizeCombo({ value, onChange }) {
|
|
2165
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2166
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2167
|
+
ref: useOutsideClose(open, () => setOpen(false)),
|
|
2168
|
+
className: "relative w-full",
|
|
2169
|
+
children: [
|
|
2170
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2171
|
+
type: "number",
|
|
2172
|
+
min: 1,
|
|
2173
|
+
value,
|
|
2174
|
+
onChange: (e) => {
|
|
2175
|
+
const n = parseInt(e.target.value, 10);
|
|
2176
|
+
if (Number.isFinite(n) && n > 0) onChange(n);
|
|
2177
|
+
},
|
|
2178
|
+
onClick: () => setOpen(true),
|
|
2179
|
+
className: "border-border bg-surface text-fg w-full rounded border px-2 py-1.5 pr-7 text-sm"
|
|
2180
|
+
}),
|
|
2181
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2182
|
+
type: "button",
|
|
2183
|
+
tabIndex: -1,
|
|
2184
|
+
onClick: () => setOpen((o) => !o),
|
|
2185
|
+
className: "absolute inset-y-0 right-1 flex items-center",
|
|
2186
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2187
|
+
name: "chevronDown",
|
|
2188
|
+
size: 16,
|
|
2189
|
+
className: "text-fg-secondary"
|
|
2190
|
+
})
|
|
2191
|
+
}),
|
|
2192
|
+
open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2193
|
+
className: "border-border bg-elevated absolute z-10 mt-1 max-h-48 w-full overflow-y-auto rounded border p-1 shadow-lg",
|
|
2194
|
+
children: FONT_SIZES.map((sz) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionRow, {
|
|
2195
|
+
selected: sz === value,
|
|
2196
|
+
onClick: () => {
|
|
2197
|
+
onChange(sz);
|
|
2198
|
+
setOpen(false);
|
|
2199
|
+
},
|
|
2200
|
+
children: sz
|
|
2201
|
+
}, sz))
|
|
2202
|
+
})
|
|
2203
|
+
]
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
function BlendModeSelect({ value, onChange }) {
|
|
2207
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2208
|
+
value,
|
|
2209
|
+
onChange: (event) => onChange(event.target.value),
|
|
2210
|
+
className: "border-border bg-surface text-fg w-full rounded border px-3 py-1.5 text-sm",
|
|
2211
|
+
children: BLEND_MODES.map((mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2212
|
+
value: mode.v,
|
|
2213
|
+
children: mode.label
|
|
2214
|
+
}, mode.v))
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
function Toggle({ active, title, onClick, children }) {
|
|
2218
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2219
|
+
type: "button",
|
|
2220
|
+
title,
|
|
2221
|
+
onClick,
|
|
2222
|
+
className: `flex h-9 w-9 items-center justify-center rounded border transition-colors ${active ? "border-accent bg-accent text-on-accent" : "border-border bg-surface text-fg hover:bg-hover"}`,
|
|
2223
|
+
children
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* The link-target editor — v2's "Link / Go to link / Remove link" menu items,
|
|
2228
|
+
* rebuilt on the schema: any kind whose table declares the `link` spec gets
|
|
2229
|
+
* this control, and every write is the ONE `updateSelection({ link })` path
|
|
2230
|
+
* (the plugin materializes/retargets/deletes the attached children).
|
|
2231
|
+
*/
|
|
2232
|
+
function LinkTargetControl({ label, value, mixed, onChange }) {
|
|
2233
|
+
const kernel = (0, _embedpdf_react_runtime.useKernel)();
|
|
2234
|
+
const link = (0, _embedpdf_react_runtime.useOptionalCapability)(_embedpdf_react_link.LinkToken);
|
|
2235
|
+
const [mode, setMode] = (0, react.useState)(value?.kind === "goto" ? "page" : "uri");
|
|
2236
|
+
const [uri, setUri] = (0, react.useState)(value?.kind === "uri" ? value.uri : "");
|
|
2237
|
+
const [pageNo, setPageNo] = (0, react.useState)("1");
|
|
2238
|
+
(0, react.useEffect)(() => {
|
|
2239
|
+
setMode(value?.kind === "goto" ? "page" : "uri");
|
|
2240
|
+
if (value?.kind === "uri") setUri(value.uri);
|
|
2241
|
+
}, [value]);
|
|
2242
|
+
const apply = () => {
|
|
2243
|
+
if (mode === "uri") {
|
|
2244
|
+
const trimmed = uri.trim();
|
|
2245
|
+
if (trimmed) onChange({ link: {
|
|
2246
|
+
kind: "uri",
|
|
2247
|
+
uri: trimmed
|
|
2248
|
+
} });
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const activeId = kernel.documents.activeId();
|
|
2252
|
+
const layout = (activeId ? kernel.getState().core.documents[activeId] : null)?.pages[Math.max(0, Number(pageNo) - 1)];
|
|
2253
|
+
if (layout) onChange({ link: {
|
|
2254
|
+
kind: "goto",
|
|
2255
|
+
destination: {
|
|
2256
|
+
kind: "fit",
|
|
2257
|
+
pageObjectNumber: layout.pageObjectNumber
|
|
2258
|
+
}
|
|
2259
|
+
} });
|
|
2260
|
+
};
|
|
2261
|
+
const inputCls = "border-border bg-surface text-fg w-full rounded border px-3 py-1.5 text-sm";
|
|
2262
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
|
|
2263
|
+
label,
|
|
2264
|
+
mixed,
|
|
2265
|
+
children: [
|
|
2266
|
+
value != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2267
|
+
className: "mb-2 flex items-center gap-2",
|
|
2268
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2269
|
+
type: "button",
|
|
2270
|
+
className: "border-border bg-surface text-fg flex-1 rounded border px-2 py-1.5 text-sm",
|
|
2271
|
+
onClick: () => link?.activate(value),
|
|
2272
|
+
children: "Go to link"
|
|
2273
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2274
|
+
type: "button",
|
|
2275
|
+
className: "border-border bg-surface text-fg flex-1 rounded border px-2 py-1.5 text-sm",
|
|
2276
|
+
onClick: () => onChange({ link: null }),
|
|
2277
|
+
children: "Remove link"
|
|
2278
|
+
})]
|
|
2279
|
+
}),
|
|
2280
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2281
|
+
className: "mb-2 flex gap-2",
|
|
2282
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
|
|
2283
|
+
title: "Link to a URL",
|
|
2284
|
+
active: mode === "uri",
|
|
2285
|
+
onClick: () => setMode("uri"),
|
|
2286
|
+
children: "URL"
|
|
2287
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
|
|
2288
|
+
title: "Link to a page",
|
|
2289
|
+
active: mode === "page",
|
|
2290
|
+
onClick: () => setMode("page"),
|
|
2291
|
+
children: "Page"
|
|
2292
|
+
})]
|
|
2293
|
+
}),
|
|
2294
|
+
mode === "uri" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2295
|
+
type: "url",
|
|
2296
|
+
className: inputCls,
|
|
2297
|
+
placeholder: "https://…",
|
|
2298
|
+
value: uri,
|
|
2299
|
+
onChange: (e) => setUri(e.target.value),
|
|
2300
|
+
onKeyDown: (e) => e.key === "Enter" && apply()
|
|
2301
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2302
|
+
type: "number",
|
|
2303
|
+
min: 1,
|
|
2304
|
+
className: inputCls,
|
|
2305
|
+
placeholder: "Page number",
|
|
2306
|
+
value: pageNo,
|
|
2307
|
+
onChange: (e) => setPageNo(e.target.value),
|
|
2308
|
+
onKeyDown: (e) => e.key === "Enter" && apply()
|
|
2309
|
+
}),
|
|
2310
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2311
|
+
type: "button",
|
|
2312
|
+
className: "border-border bg-surface text-fg mt-2 w-full rounded border px-2 py-1.5 text-sm font-medium",
|
|
2313
|
+
onClick: apply,
|
|
2314
|
+
children: value == null ? "Add link" : "Update link"
|
|
2315
|
+
})
|
|
2316
|
+
]
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
function PropControl({ spec, value, mixed, onChange }) {
|
|
2320
|
+
switch (spec.key) {
|
|
2321
|
+
case "color":
|
|
2322
|
+
case "fontColor": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2323
|
+
label: spec.label,
|
|
2324
|
+
mixed,
|
|
2325
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwatchGrid, {
|
|
2326
|
+
value,
|
|
2327
|
+
onSelect: (c) => onChange({ [spec.key]: c })
|
|
2328
|
+
})
|
|
2329
|
+
});
|
|
2330
|
+
case "interiorColor": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2331
|
+
label: spec.label,
|
|
2332
|
+
mixed,
|
|
2333
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwatchGrid, {
|
|
2334
|
+
value,
|
|
2335
|
+
allowTransparent: true,
|
|
2336
|
+
onSelect: (c) => onChange({ interiorColor: c === "transparent" ? null : c })
|
|
2337
|
+
})
|
|
2338
|
+
});
|
|
2339
|
+
case "opacity": {
|
|
2340
|
+
const v = value ?? 1;
|
|
2341
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
|
|
2342
|
+
label: spec.label,
|
|
2343
|
+
mixed,
|
|
2344
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeSlider, {
|
|
2345
|
+
value: v,
|
|
2346
|
+
min: spec.min,
|
|
2347
|
+
max: spec.max,
|
|
2348
|
+
step: spec.step,
|
|
2349
|
+
onChange: (n) => onChange({ opacity: n })
|
|
2350
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2351
|
+
className: "text-fg-muted text-xs",
|
|
2352
|
+
children: [Math.round(v * 100), "%"]
|
|
2353
|
+
})]
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
case "strokeWidth": {
|
|
2357
|
+
const v = value ?? spec.min;
|
|
2358
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
|
|
2359
|
+
label: spec.label,
|
|
2360
|
+
mixed,
|
|
2361
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeSlider, {
|
|
2362
|
+
value: v,
|
|
2363
|
+
min: spec.min,
|
|
2364
|
+
max: spec.max,
|
|
2365
|
+
step: spec.step,
|
|
2366
|
+
onChange: (n) => onChange({ strokeWidth: n })
|
|
2367
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2368
|
+
className: "text-fg-muted text-xs",
|
|
2369
|
+
children: [v, "px"]
|
|
2370
|
+
})]
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
case "fontSize": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2374
|
+
label: spec.label,
|
|
2375
|
+
mixed,
|
|
2376
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FontSizeCombo, {
|
|
2377
|
+
value: value ?? 12,
|
|
2378
|
+
onChange: (n) => onChange({ fontSize: n })
|
|
2379
|
+
})
|
|
2380
|
+
});
|
|
2381
|
+
case "border": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2382
|
+
label: spec.label,
|
|
2383
|
+
mixed,
|
|
2384
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BorderSelect, {
|
|
2385
|
+
value: value ?? { kind: "solid" },
|
|
2386
|
+
cloudy: spec.cloudy,
|
|
2387
|
+
onChange: (b) => onChange({ border: b })
|
|
2388
|
+
})
|
|
2389
|
+
});
|
|
2390
|
+
case "lineEndings": {
|
|
2391
|
+
const le = value ?? {
|
|
2392
|
+
start: "none",
|
|
2393
|
+
end: "none"
|
|
2394
|
+
};
|
|
2395
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2396
|
+
label: spec.label,
|
|
2397
|
+
mixed,
|
|
2398
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2399
|
+
className: "grid grid-cols-2 gap-3",
|
|
2400
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2401
|
+
className: "text-fg-muted mb-1.5 text-xs",
|
|
2402
|
+
children: "Start"
|
|
2403
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineEndingSelect, {
|
|
2404
|
+
side: "start",
|
|
2405
|
+
value: le.start,
|
|
2406
|
+
onChange: (v) => onChange({ lineEndings: { start: v } })
|
|
2407
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2408
|
+
className: "text-fg-muted mb-1.5 text-xs",
|
|
2409
|
+
children: "End"
|
|
2410
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineEndingSelect, {
|
|
2411
|
+
side: "end",
|
|
2412
|
+
value: le.end,
|
|
2413
|
+
onChange: (v) => onChange({ lineEndings: { end: v } })
|
|
2414
|
+
})] })]
|
|
2415
|
+
})
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
case "fontFamily": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2419
|
+
label: spec.label,
|
|
2420
|
+
mixed,
|
|
2421
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FontFamilySelect, {
|
|
2422
|
+
value: value ?? "helvetica",
|
|
2423
|
+
onChange: (v) => onChange({ fontFamily: v })
|
|
2424
|
+
})
|
|
2425
|
+
});
|
|
2426
|
+
case "textAlign": {
|
|
2427
|
+
const alignIcon = {
|
|
2428
|
+
left: "alignLeft",
|
|
2429
|
+
center: "alignCenter",
|
|
2430
|
+
right: "alignRight"
|
|
2431
|
+
};
|
|
2432
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2433
|
+
label: spec.label,
|
|
2434
|
+
mixed,
|
|
2435
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2436
|
+
className: "flex gap-2",
|
|
2437
|
+
children: [
|
|
2438
|
+
"left",
|
|
2439
|
+
"center",
|
|
2440
|
+
"right"
|
|
2441
|
+
].map((al) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
|
|
2442
|
+
title: `Align ${al}`,
|
|
2443
|
+
active: value === al,
|
|
2444
|
+
onClick: () => onChange({ textAlign: al }),
|
|
2445
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2446
|
+
name: alignIcon[al],
|
|
2447
|
+
size: 18
|
|
2448
|
+
})
|
|
2449
|
+
}, al))
|
|
2450
|
+
})
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
case "blendMode": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2454
|
+
label: spec.label,
|
|
2455
|
+
mixed,
|
|
2456
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlendModeSelect, {
|
|
2457
|
+
value: value ?? "normal",
|
|
2458
|
+
onChange: (blendMode) => onChange({ blendMode })
|
|
2459
|
+
})
|
|
2460
|
+
});
|
|
2461
|
+
case "link": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkTargetControl, {
|
|
2462
|
+
label: spec.label,
|
|
2463
|
+
value: value ?? null,
|
|
2464
|
+
mixed,
|
|
2465
|
+
onChange
|
|
2466
|
+
});
|
|
2467
|
+
default: return null;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
function EmptyState() {
|
|
2471
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2472
|
+
className: "text-fg-muted flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center",
|
|
2473
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2474
|
+
name: "palette",
|
|
2475
|
+
size: 40,
|
|
2476
|
+
className: "text-fg-disabled"
|
|
2477
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2478
|
+
className: "max-w-[180px] text-sm",
|
|
2479
|
+
children: "Pick a draw tool or select an annotation to edit its style."
|
|
2480
|
+
})]
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* The schema-driven style panel. Rendered inside the right sidebar's
|
|
2485
|
+
* `annotation-style` surface (see ui/panels.tsx). Owns its own scroll.
|
|
2486
|
+
*/
|
|
2487
|
+
function AnnotationStylePanel() {
|
|
2488
|
+
const annotation = (0, _embedpdf_react_annotation.useAnnotation)();
|
|
2489
|
+
const { activeToolId } = (0, _embedpdf_react_interaction.useTool)();
|
|
2490
|
+
const sel = (0, _embedpdf_react_annotation.useSelectionProps)();
|
|
2491
|
+
const defaults = (0, _embedpdf_react_annotation.useAnnotationDefaults)(activeToolId);
|
|
2492
|
+
const selected = (0, _embedpdf_react_annotation.useAnnotationSelected)();
|
|
2493
|
+
const hasSel = sel.specs.length > 0;
|
|
2494
|
+
const specs = hasSel ? sel.specs : annotation.propsForTool(activeToolId);
|
|
2495
|
+
const values = hasSel ? sel.values : defaults;
|
|
2496
|
+
const write = (patch) => hasSel ? annotation.updateSelection(patch) : annotation.setDefaults(activeToolId, patch);
|
|
2497
|
+
if (specs.length === 0 && selected.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {});
|
|
2498
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2499
|
+
className: "flex-1 overflow-y-auto p-4",
|
|
2500
|
+
children: [
|
|
2501
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2502
|
+
className: "text-fg-muted mb-4 text-[11px] font-semibold uppercase tracking-wide",
|
|
2503
|
+
children: hasSel ? `${selected.length} selected` : `${activeToolId} defaults`
|
|
2504
|
+
}),
|
|
2505
|
+
specs.map((spec) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PropControl, {
|
|
2506
|
+
spec,
|
|
2507
|
+
value: values[spec.key],
|
|
2508
|
+
mixed: hasSel && sel.mixed.includes(spec.key),
|
|
2509
|
+
onChange: write
|
|
2510
|
+
}, spec.key)),
|
|
2511
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedactionLabelSection, {}),
|
|
2512
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationFlagsSection, {})
|
|
2513
|
+
]
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
/**
|
|
2517
|
+
* Label editor shown when exactly one redaction mark is selected. The text is
|
|
2518
|
+
* what the destructive apply paints over the region (white-on-black by the
|
|
2519
|
+
* tool defaults); `repeat` tiles it. Writes ride the annotation update verb
|
|
2520
|
+
* via the redaction plugin, which preserves the label's `/DA` styling.
|
|
2521
|
+
*/
|
|
2522
|
+
function RedactionLabelSection() {
|
|
2523
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
2524
|
+
const redaction = (0, _embedpdf_react_runtime.useOptionalCapability)(_embedpdf_react_redaction.RedactionToken);
|
|
2525
|
+
const selected = (0, _embedpdf_react_annotation.useAnnotationSelected)();
|
|
2526
|
+
const mark = selected.length === 1 && selected[0].subtype === "redact" ? selected[0] : null;
|
|
2527
|
+
const [draft, setDraft] = (0, react.useState)(null);
|
|
2528
|
+
(0, react.useEffect)(() => setDraft(null), [mark?.ref && refKeyOfRedact(mark.ref)]);
|
|
2529
|
+
if (!redaction || !mark || mark.subtype !== "redact") return null;
|
|
2530
|
+
const value = draft ?? mark.overlayText ?? "";
|
|
2531
|
+
const commit = () => {
|
|
2532
|
+
if (draft === null) return;
|
|
2533
|
+
redaction.setLabel(mark.ref, { overlayText: draft.length > 0 ? draft : null });
|
|
2534
|
+
setDraft(null);
|
|
2535
|
+
};
|
|
2536
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2537
|
+
className: "border-border-subtle mt-4 border-t pt-4",
|
|
2538
|
+
children: [
|
|
2539
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2540
|
+
className: "text-fg-muted mb-2 text-[11px] font-semibold uppercase tracking-wide",
|
|
2541
|
+
children: t("demo.redactLabelTitle")
|
|
2542
|
+
}),
|
|
2543
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2544
|
+
type: "text",
|
|
2545
|
+
value,
|
|
2546
|
+
placeholder: t("demo.redactLabelPlaceholder"),
|
|
2547
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2548
|
+
onBlur: commit,
|
|
2549
|
+
onKeyDown: (e) => e.key === "Enter" && commit(),
|
|
2550
|
+
className: "border-border bg-surface text-fg w-full rounded-md border px-2 py-1.5 text-sm"
|
|
2551
|
+
}),
|
|
2552
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2553
|
+
className: "text-fg mt-2 flex items-center gap-2 text-sm",
|
|
2554
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2555
|
+
type: "checkbox",
|
|
2556
|
+
checked: mark.repeat,
|
|
2557
|
+
onChange: (e) => void redaction.setLabel(mark.ref, { repeat: e.target.checked })
|
|
2558
|
+
}), t("demo.redactLabelRepeat")]
|
|
2559
|
+
})
|
|
2560
|
+
]
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
/** Stable key for the effect dep — mirrors the annotation model's ref keys. */
|
|
2564
|
+
function refKeyOfRedact(ref) {
|
|
2565
|
+
return JSON.stringify(ref);
|
|
2566
|
+
}
|
|
2567
|
+
//#endregion
|
|
2568
|
+
//#region src/ui/redaction-panel.tsx
|
|
2569
|
+
/**
|
|
2570
|
+
* The pending-redactions review panel (right sidebar). The list is a LIVE view
|
|
2571
|
+
* over the annotation plane — deleting a mark here is deleting the annotation.
|
|
2572
|
+
* "Apply all" routes through the confirm modal: the apply is irreversible.
|
|
2573
|
+
*/
|
|
2574
|
+
function RedactionPanel() {
|
|
2575
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
2576
|
+
const redaction = (0, _embedpdf_react_redaction.useRedaction)();
|
|
2577
|
+
const pending = (0, _embedpdf_react_redaction.usePendingRedactions)();
|
|
2578
|
+
const anno = (0, _embedpdf_react_runtime.useCapability)(_embedpdf_react_annotation.AnnotationToken);
|
|
2579
|
+
const confirm = (0, _embedpdf_react_shell.useSurface)("redact-confirm");
|
|
2580
|
+
(0, react.useEffect)(() => {
|
|
2581
|
+
redaction.preparePending();
|
|
2582
|
+
}, []);
|
|
2583
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2584
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
2585
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2586
|
+
className: "min-h-0 flex-1 overflow-y-auto p-2",
|
|
2587
|
+
children: pending.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2588
|
+
className: "flex flex-col items-center gap-3 px-4 py-10 text-center",
|
|
2589
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2590
|
+
name: "redactionSidebar",
|
|
2591
|
+
size: 32,
|
|
2592
|
+
className: "text-fg-muted"
|
|
2593
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2594
|
+
className: "text-fg-muted text-sm",
|
|
2595
|
+
children: t("demo.redactionEmpty")
|
|
2596
|
+
})]
|
|
2597
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2598
|
+
className: "flex flex-col gap-1",
|
|
2599
|
+
children: pending.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2600
|
+
role: "button",
|
|
2601
|
+
tabIndex: 0,
|
|
2602
|
+
onClick: () => anno.select(item.ref),
|
|
2603
|
+
onKeyDown: (e) => e.key === "Enter" && anno.select(item.ref),
|
|
2604
|
+
className: "hover:bg-hover group flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left",
|
|
2605
|
+
children: [
|
|
2606
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2607
|
+
name: item.kind === "area" ? "redactArea" : "redact",
|
|
2608
|
+
size: 16,
|
|
2609
|
+
className: "text-fg-muted shrink-0"
|
|
2610
|
+
}),
|
|
2611
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2612
|
+
className: "text-fg min-w-0 flex-1 truncate text-sm",
|
|
2613
|
+
children: item.overlayText ?? t(item.kind === "area" ? "demo.redactionArea" : "demo.redactionText")
|
|
2614
|
+
}),
|
|
2615
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2616
|
+
className: "text-fg-muted shrink-0 text-xs",
|
|
2617
|
+
children: t("demo.pageBadge", { params: { page: item.pageIndex >= 0 ? String(item.pageIndex + 1) : "?" } })
|
|
2618
|
+
}),
|
|
2619
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2620
|
+
type: "button",
|
|
2621
|
+
onClick: (e) => {
|
|
2622
|
+
e.stopPropagation();
|
|
2623
|
+
anno.delete(item.ref);
|
|
2624
|
+
},
|
|
2625
|
+
className: "text-fg-muted hover:text-fg grid h-6 w-6 shrink-0 place-items-center rounded opacity-0 group-hover:opacity-100",
|
|
2626
|
+
title: t("demo.redactionRemove"),
|
|
2627
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2628
|
+
name: "trash",
|
|
2629
|
+
size: 14
|
|
2630
|
+
})
|
|
2631
|
+
})
|
|
2632
|
+
]
|
|
2633
|
+
}) }, item.id))
|
|
2634
|
+
})
|
|
2635
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2636
|
+
className: "border-border-subtle flex shrink-0 gap-2 border-t p-3",
|
|
2637
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2638
|
+
type: "button",
|
|
2639
|
+
disabled: pending.length === 0,
|
|
2640
|
+
onClick: () => {
|
|
2641
|
+
for (const item of pending) anno.delete(item.ref);
|
|
2642
|
+
},
|
|
2643
|
+
className: "border-border text-fg hover:bg-hover flex-1 rounded-md border px-3 py-1.5 text-sm disabled:opacity-40",
|
|
2644
|
+
children: t("commands.redact.clearAll")
|
|
2645
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2646
|
+
type: "button",
|
|
2647
|
+
disabled: pending.length === 0 || !redaction.canApply() || redaction.applying,
|
|
2648
|
+
onClick: () => confirm.open(),
|
|
2649
|
+
className: "flex-1 rounded-md bg-red-600 px-3 py-1.5 text-sm text-white hover:bg-red-700 disabled:opacity-40",
|
|
2650
|
+
children: t("commands.redact.applyAll")
|
|
2651
|
+
})]
|
|
2652
|
+
})]
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
//#endregion
|
|
2656
|
+
//#region src/ui/search-panel.tsx
|
|
2657
|
+
/**
|
|
2658
|
+
* The search panel — the v2 snippet's search sidebar on the v3 plugin-search
|
|
2659
|
+
* capability. ONE shape end to end: the flat `SearchQuery` {text, regex,
|
|
2660
|
+
* matchCase, wholeWord} is what the engine matches, what the plugin stores
|
|
2661
|
+
* (document-scoped, survives the sidebar closing), and what this box renders.
|
|
2662
|
+
* The panel keeps only a DRAFT of it for keystroke/debounce echo.
|
|
2663
|
+
*
|
|
2664
|
+
* Data flow:
|
|
2665
|
+
* type / toggle → validateSearchQuery → (debounced) search(draftQuery)
|
|
2666
|
+
* useSearchState() → query / status / hitCount / activeIndex (reactive)
|
|
2667
|
+
* useSelector(SearchToken, c => c.hits()) → the streamed hit list
|
|
2668
|
+
* click a hit / prev / next → goTo/prev/next (capability reveals it on-page)
|
|
2669
|
+
*
|
|
2670
|
+
* The look is ported 1:1 from viewers/snippet's search-sidebar (magnifier
|
|
2671
|
+
* input with clear button, option checkboxes, results-found counter with
|
|
2672
|
+
* prev/next, per-page grouped snippet list with the match bolded and the
|
|
2673
|
+
* active hit accented), retinted to this app's semantic tokens. The regex
|
|
2674
|
+
* toggle is new in v3 (v2 had no pattern search); matchDiacritics exists in
|
|
2675
|
+
* the engine but is deliberately not exposed here — v2 parity.
|
|
2676
|
+
*/
|
|
2677
|
+
const DEBOUNCE_MS = 300;
|
|
2678
|
+
/** Field-wise equality on the flat query — the mount/echo no-op guard. */
|
|
2679
|
+
const sameQuery = (a, b) => a != null && a.text === b.text && !!a.regex === !!b.regex && !!a.matchCase === !!b.matchCase && !!a.wholeWord === !!b.wholeWord;
|
|
2680
|
+
function Checkbox({ label, checked, onChange }) {
|
|
2681
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2682
|
+
className: "text-fg-secondary flex cursor-pointer select-none items-center gap-2 text-xs font-medium",
|
|
2683
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2684
|
+
className: "relative flex h-4 w-4 items-center justify-center",
|
|
2685
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2686
|
+
type: "checkbox",
|
|
2687
|
+
checked,
|
|
2688
|
+
onChange: (e) => onChange(e.target.checked),
|
|
2689
|
+
className: "border-border bg-surface checked:border-accent checked:bg-accent peer h-4 w-4 shrink-0 appearance-none rounded-[3px] border transition-colors"
|
|
2690
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2691
|
+
viewBox: "0 0 24 24",
|
|
2692
|
+
className: "text-on-accent pointer-events-none absolute h-3 w-3 opacity-0 peer-checked:opacity-100",
|
|
2693
|
+
fill: "none",
|
|
2694
|
+
stroke: "currentColor",
|
|
2695
|
+
strokeWidth: 3,
|
|
2696
|
+
strokeLinecap: "round",
|
|
2697
|
+
strokeLinejoin: "round",
|
|
2698
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "20 6 9 17 4 12" })
|
|
2699
|
+
})]
|
|
2700
|
+
}), label]
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
function renderSnippet(s) {
|
|
2704
|
+
if (!s) return null;
|
|
2705
|
+
const end = s.matchStart + s.matchLength;
|
|
2706
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2707
|
+
s.text.slice(0, s.matchStart),
|
|
2708
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2709
|
+
className: "text-accent font-semibold",
|
|
2710
|
+
children: s.text.slice(s.matchStart, end)
|
|
2711
|
+
}),
|
|
2712
|
+
s.text.slice(end)
|
|
2713
|
+
] });
|
|
2714
|
+
}
|
|
2715
|
+
function HitLine({ hit, active, onClick }) {
|
|
2716
|
+
const ref = (0, react.useRef)(null);
|
|
2717
|
+
(0, react.useEffect)(() => {
|
|
2718
|
+
if (active) ref.current?.scrollIntoView({
|
|
2719
|
+
behavior: "smooth",
|
|
2720
|
+
block: "nearest"
|
|
2721
|
+
});
|
|
2722
|
+
}, [active]);
|
|
2723
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2724
|
+
ref,
|
|
2725
|
+
type: "button",
|
|
2726
|
+
onClick,
|
|
2727
|
+
className: `w-full rounded border p-2 text-left text-sm transition-colors ${active ? "border-accent bg-accent-light text-fg" : "border-border-subtle bg-surface text-fg-secondary hover:bg-hover"}`,
|
|
2728
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: renderSnippet(hit.snippet) })
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
function SearchPanel() {
|
|
2732
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
2733
|
+
const search = (0, _embedpdf_react_search.useSearch)();
|
|
2734
|
+
const { query, status, hitCount, activeIndex } = (0, _embedpdf_react_search.useSearchState)();
|
|
2735
|
+
const hits = (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_search.SearchToken, (c) => c.hits());
|
|
2736
|
+
const [draft, setDraft] = (0, react.useState)(() => query?.text ?? "");
|
|
2737
|
+
const [matchCase, setMatchCase] = (0, react.useState)(() => query?.matchCase ?? false);
|
|
2738
|
+
const [wholeWord, setWholeWord] = (0, react.useState)(() => query?.wholeWord ?? false);
|
|
2739
|
+
const [regex, setRegex] = (0, react.useState)(() => query?.regex ?? false);
|
|
2740
|
+
const inputRef = (0, react.useRef)(null);
|
|
2741
|
+
const draftQuery = {
|
|
2742
|
+
text: draft,
|
|
2743
|
+
regex,
|
|
2744
|
+
matchCase,
|
|
2745
|
+
wholeWord
|
|
2746
|
+
};
|
|
2747
|
+
const validation = draft && regex ? (0, _embedpdf_react_search.validateSearchQuery)(draftQuery) : { ok: true };
|
|
2748
|
+
(0, react.useEffect)(() => {
|
|
2749
|
+
if (!validation.ok || sameQuery(query, draftQuery)) return;
|
|
2750
|
+
if (draft === "" && query === null) return;
|
|
2751
|
+
const id = setTimeout(() => search.search(draftQuery), DEBOUNCE_MS);
|
|
2752
|
+
return () => clearTimeout(id);
|
|
2753
|
+
}, [
|
|
2754
|
+
draft,
|
|
2755
|
+
matchCase,
|
|
2756
|
+
wholeWord,
|
|
2757
|
+
regex,
|
|
2758
|
+
query,
|
|
2759
|
+
validation.ok,
|
|
2760
|
+
search
|
|
2761
|
+
]);
|
|
2762
|
+
const grouped = (0, react.useMemo)(() => {
|
|
2763
|
+
const map = /* @__PURE__ */ new Map();
|
|
2764
|
+
hits.forEach((hit, index) => {
|
|
2765
|
+
const arr = map.get(hit.pageIndex);
|
|
2766
|
+
if (arr) arr.push({
|
|
2767
|
+
hit,
|
|
2768
|
+
index
|
|
2769
|
+
});
|
|
2770
|
+
else map.set(hit.pageIndex, [{
|
|
2771
|
+
hit,
|
|
2772
|
+
index
|
|
2773
|
+
}]);
|
|
2774
|
+
});
|
|
2775
|
+
return [...map.entries()].sort((a, b) => a[0] - b[0]);
|
|
2776
|
+
}, [hits]);
|
|
2777
|
+
const searching = status === "searching";
|
|
2778
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2779
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
2780
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2781
|
+
className: "p-3",
|
|
2782
|
+
children: [
|
|
2783
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2784
|
+
className: "relative",
|
|
2785
|
+
children: [
|
|
2786
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2787
|
+
className: "text-fg-muted pointer-events-none absolute inset-y-0 left-0 flex items-center pl-2.5",
|
|
2788
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2789
|
+
name: "search",
|
|
2790
|
+
size: 16
|
|
2791
|
+
})
|
|
2792
|
+
}),
|
|
2793
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2794
|
+
ref: inputRef,
|
|
2795
|
+
type: "text",
|
|
2796
|
+
value: draft,
|
|
2797
|
+
placeholder: t("demo.searchPlaceholder"),
|
|
2798
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2799
|
+
className: `bg-surface text-fg w-full rounded-md border py-1.5 pl-8 pr-9 text-sm outline-none focus:ring-1 ${validation.ok ? "border-border focus:border-accent focus:ring-accent" : "border-red-400 focus:border-red-400 focus:ring-red-400"}`
|
|
2800
|
+
}),
|
|
2801
|
+
draft && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2802
|
+
type: "button",
|
|
2803
|
+
onClick: () => {
|
|
2804
|
+
setDraft("");
|
|
2805
|
+
inputRef.current?.focus();
|
|
2806
|
+
},
|
|
2807
|
+
className: "text-fg-muted hover:text-fg-secondary absolute inset-y-0 right-0 flex items-center pr-2.5",
|
|
2808
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2809
|
+
name: "x",
|
|
2810
|
+
size: 16
|
|
2811
|
+
})
|
|
2812
|
+
})
|
|
2813
|
+
]
|
|
2814
|
+
}),
|
|
2815
|
+
!validation.ok && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2816
|
+
className: "mt-1.5 text-xs text-red-500",
|
|
2817
|
+
children: validation.message
|
|
2818
|
+
}),
|
|
2819
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2820
|
+
className: "mt-3 flex flex-col gap-2",
|
|
2821
|
+
children: [
|
|
2822
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Checkbox, {
|
|
2823
|
+
label: t("demo.searchCaseSensitive"),
|
|
2824
|
+
checked: matchCase,
|
|
2825
|
+
onChange: setMatchCase
|
|
2826
|
+
}),
|
|
2827
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Checkbox, {
|
|
2828
|
+
label: t("demo.searchWholeWord"),
|
|
2829
|
+
checked: wholeWord,
|
|
2830
|
+
onChange: setWholeWord
|
|
2831
|
+
}),
|
|
2832
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Checkbox, {
|
|
2833
|
+
label: t("demo.searchRegex"),
|
|
2834
|
+
checked: regex,
|
|
2835
|
+
onChange: setRegex
|
|
2836
|
+
})
|
|
2837
|
+
]
|
|
2838
|
+
}),
|
|
2839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", { className: "border-border-subtle mb-2 mt-4" }),
|
|
2840
|
+
status !== "idle" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2841
|
+
className: "flex h-8 items-center justify-between",
|
|
2842
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2843
|
+
className: "text-fg-muted text-xs",
|
|
2844
|
+
children: hitCount === 0 ? t(searching ? "demo.searchSearching" : "demo.searchNoResults") : t("demo.searchResults", { params: { count: hitCount } })
|
|
2845
|
+
}), hitCount > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2846
|
+
className: "flex items-center",
|
|
2847
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2848
|
+
type: "button",
|
|
2849
|
+
title: "Previous",
|
|
2850
|
+
className: buttonClass(false),
|
|
2851
|
+
onClick: () => search.prev(),
|
|
2852
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2853
|
+
name: "chevronLeft",
|
|
2854
|
+
size: 20
|
|
2855
|
+
})
|
|
2856
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2857
|
+
type: "button",
|
|
2858
|
+
title: "Next",
|
|
2859
|
+
className: buttonClass(false),
|
|
2860
|
+
onClick: () => search.next(),
|
|
2861
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2862
|
+
name: "chevronRight",
|
|
2863
|
+
size: 20
|
|
2864
|
+
})
|
|
2865
|
+
})]
|
|
2866
|
+
})]
|
|
2867
|
+
})
|
|
2868
|
+
]
|
|
2869
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2870
|
+
className: "flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto px-3 pb-3",
|
|
2871
|
+
children: grouped.map(([pageIndex, items]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2872
|
+
className: "mt-2 first:mt-0",
|
|
2873
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2874
|
+
className: "bg-surface/80 text-fg-muted py-2 text-xs backdrop-blur",
|
|
2875
|
+
children: t("demo.searchPage", { params: { page: pageIndex + 1 } })
|
|
2876
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2877
|
+
className: "flex flex-col gap-2",
|
|
2878
|
+
children: items.map(({ hit, index }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HitLine, {
|
|
2879
|
+
hit,
|
|
2880
|
+
active: index === activeIndex,
|
|
2881
|
+
onClick: () => search.goTo(index)
|
|
2882
|
+
}, index))
|
|
2883
|
+
})]
|
|
2884
|
+
}, pageIndex))
|
|
2885
|
+
})]
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
//#endregion
|
|
2889
|
+
//#region src/ui/panels.tsx
|
|
2890
|
+
/**
|
|
2891
|
+
* The static chrome around the toolbars: the left/right sidebars (shell
|
|
2892
|
+
* surfaces) and the bottom page-controls overlay. Deliberately minimal —
|
|
2893
|
+
* chrome-parity scope. Panels read their open state from plugin-shell; the
|
|
2894
|
+
* app owns their DOM.
|
|
2895
|
+
*
|
|
2896
|
+
* There is no built-in header: branding, a locale picker and a theme switch
|
|
2897
|
+
* are the EMBEDDER's chrome, not the viewer's. The frame keeps a `header`
|
|
2898
|
+
* socket for a slotted one (see Shell).
|
|
2899
|
+
*/
|
|
2900
|
+
function LeftSidebar() {
|
|
2901
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
2902
|
+
const sidebar = (0, _embedpdf_react_shell.useSurface)("sidebar");
|
|
2903
|
+
if (!sidebar.isOpen) return null;
|
|
2904
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
2905
|
+
className: "border-border-subtle bg-surface flex w-60 shrink-0 flex-col border-r",
|
|
2906
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2907
|
+
className: "border-border-subtle flex items-center gap-1 border-b p-2",
|
|
2908
|
+
children: [
|
|
2909
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2910
|
+
className: "bg-accent-light text-accent rounded-md px-2 py-1 text-xs font-medium",
|
|
2911
|
+
children: t("demo.thumbnails")
|
|
2912
|
+
}),
|
|
2913
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2914
|
+
className: "text-fg-muted rounded-md px-2 py-1 text-xs font-medium",
|
|
2915
|
+
children: t("demo.outline")
|
|
2916
|
+
}),
|
|
2917
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "flex-1" }),
|
|
2918
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2919
|
+
type: "button",
|
|
2920
|
+
onClick: sidebar.close,
|
|
2921
|
+
className: "text-fg-muted hover:bg-hover grid h-7 w-7 place-items-center rounded-md",
|
|
2922
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2923
|
+
name: "x",
|
|
2924
|
+
size: 16
|
|
2925
|
+
})
|
|
2926
|
+
})
|
|
2927
|
+
]
|
|
2928
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThumbnailList, {})]
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
function ThumbnailList() {
|
|
2932
|
+
const { currentPage, goToPage } = (0, _embedpdf_react_stage.usePages)();
|
|
2933
|
+
const { reveal } = (0, _embedpdf_react_stage.usePages)(ThumbsStageToken);
|
|
2934
|
+
(0, react.useEffect)(() => reveal(currentPage), [currentPage, reveal]);
|
|
2935
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_stage.Stage, {
|
|
2936
|
+
token: ThumbsStageToken,
|
|
2937
|
+
zoomGestures: false,
|
|
2938
|
+
className: "flex-1",
|
|
2939
|
+
style: { position: "relative" },
|
|
2940
|
+
pageChrome: (page) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2941
|
+
type: "button",
|
|
2942
|
+
onClick: () => goToPage(page.pageIndex),
|
|
2943
|
+
title: `Page ${page.pageIndex + 1}`,
|
|
2944
|
+
style: {
|
|
2945
|
+
position: "absolute",
|
|
2946
|
+
top: page.frame.top,
|
|
2947
|
+
left: page.frame.left,
|
|
2948
|
+
right: page.frame.right,
|
|
2949
|
+
bottom: page.frame.bottom,
|
|
2950
|
+
cursor: "pointer",
|
|
2951
|
+
boxSizing: "border-box",
|
|
2952
|
+
borderRadius: 4,
|
|
2953
|
+
border: page.pageIndex === currentPage ? "2px solid var(--ep-accent)" : "1px solid var(--ep-border-subtle)",
|
|
2954
|
+
boxShadow: page.pageIndex === currentPage ? "0 0 0 2px var(--ep-accent-light)" : "none"
|
|
2955
|
+
}
|
|
2956
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2957
|
+
style: {
|
|
2958
|
+
position: "absolute",
|
|
2959
|
+
bottom: 0,
|
|
2960
|
+
left: 0,
|
|
2961
|
+
right: 0,
|
|
2962
|
+
height: page.frame.bottom,
|
|
2963
|
+
display: "flex",
|
|
2964
|
+
alignItems: "center",
|
|
2965
|
+
justifyContent: "center",
|
|
2966
|
+
fontSize: 11,
|
|
2967
|
+
color: "var(--ep-fg-muted)",
|
|
2968
|
+
pointerEvents: "none"
|
|
2969
|
+
},
|
|
2970
|
+
children: page.pageIndex + 1
|
|
2971
|
+
})] }),
|
|
2972
|
+
children: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_render.RenderLayer, {})
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
function RightSidebar() {
|
|
2976
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
2977
|
+
const documentId = (0, _embedpdf_react_runtime.useDocumentId)();
|
|
2978
|
+
const search = (0, _embedpdf_react_shell.useSurface)("search");
|
|
2979
|
+
const comment = (0, _embedpdf_react_shell.useSurface)("comment");
|
|
2980
|
+
const style = (0, _embedpdf_react_shell.useSurface)("annotation-style");
|
|
2981
|
+
const redaction = (0, _embedpdf_react_shell.useSurface)("redaction");
|
|
2982
|
+
const active = search.isOpen ? "search" : comment.isOpen ? "comment" : style.isOpen ? "style" : redaction.isOpen ? "redaction" : null;
|
|
2983
|
+
if (!active) return null;
|
|
2984
|
+
const titleKey = active === "search" ? "demo.searchTitle" : active === "comment" ? "demo.commentsTitle" : active === "redaction" ? "demo.redactionTitle" : "demo.styleTitle";
|
|
2985
|
+
const close = active === "search" ? search.close : active === "comment" ? comment.close : active === "redaction" ? redaction.close : style.close;
|
|
2986
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
2987
|
+
className: "border-border-subtle bg-surface flex w-72 shrink-0 flex-col border-l",
|
|
2988
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2989
|
+
className: "border-border-subtle flex items-center justify-between border-b p-3",
|
|
2990
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2991
|
+
className: "text-fg text-sm font-semibold",
|
|
2992
|
+
children: t(titleKey)
|
|
2993
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2994
|
+
type: "button",
|
|
2995
|
+
onClick: close,
|
|
2996
|
+
className: "text-fg-muted hover:bg-hover grid h-7 w-7 place-items-center rounded-md",
|
|
2997
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
2998
|
+
name: "x",
|
|
2999
|
+
size: 16
|
|
3000
|
+
})
|
|
3001
|
+
})]
|
|
3002
|
+
}), active === "style" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedactionPanel, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3003
|
+
className: "p-3",
|
|
3004
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3005
|
+
className: "text-fg-muted text-sm",
|
|
3006
|
+
children: t("demo.empty")
|
|
3007
|
+
})
|
|
3008
|
+
})]
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
3011
|
+
function PageControls() {
|
|
3012
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
3013
|
+
const current = (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_stage.StageToken, (c) => c.currentPage() + 1);
|
|
3014
|
+
const total = (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_stage.StageToken, (c) => c.pageCount());
|
|
3015
|
+
if (!total) return null;
|
|
3016
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3017
|
+
className: "pointer-events-none absolute inset-x-0 bottom-5 flex justify-center",
|
|
3018
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3019
|
+
className: "border-border-subtle bg-elevated/95 text-fg-secondary pointer-events-auto rounded-full border px-4 py-1.5 text-sm shadow-lg backdrop-blur",
|
|
3020
|
+
children: t("demo.page", { params: {
|
|
3021
|
+
current,
|
|
3022
|
+
total
|
|
3023
|
+
} })
|
|
3024
|
+
})
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
//#endregion
|
|
3028
|
+
//#region src/ui/redact-confirm.tsx
|
|
3029
|
+
/**
|
|
3030
|
+
* The apply-all confirm modal (`redact-confirm` shell surface, `exclusive:
|
|
3031
|
+
* 'modal'` via the command router). Shows the pending count and the
|
|
3032
|
+
* CLIENT-SIDE collateral estimate — other annotations the apply will destroy —
|
|
3033
|
+
* before anything irreversible happens. The authoritative collateral count
|
|
3034
|
+
* comes back on the result.
|
|
3035
|
+
*/
|
|
3036
|
+
function RedactConfirmModal() {
|
|
3037
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
3038
|
+
const surface = (0, _embedpdf_react_shell.useSurface)("redact-confirm");
|
|
3039
|
+
const redaction = (0, _embedpdf_react_redaction.useRedaction)();
|
|
3040
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
3041
|
+
const [error, setError] = (0, react.useState)(null);
|
|
3042
|
+
if (!surface.isOpen) return null;
|
|
3043
|
+
const count = redaction.pendingCount();
|
|
3044
|
+
const collateral = redaction.estimateCollateral();
|
|
3045
|
+
const apply = async () => {
|
|
3046
|
+
setBusy(true);
|
|
3047
|
+
setError(null);
|
|
3048
|
+
try {
|
|
3049
|
+
await redaction.applyAll();
|
|
3050
|
+
surface.close();
|
|
3051
|
+
} catch (err) {
|
|
3052
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3053
|
+
} finally {
|
|
3054
|
+
setBusy(false);
|
|
3055
|
+
}
|
|
3056
|
+
};
|
|
3057
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3058
|
+
className: "fixed inset-0 z-50 grid place-items-center bg-black/40",
|
|
3059
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3060
|
+
className: "border-border bg-surface w-96 rounded-lg border p-4 shadow-xl",
|
|
3061
|
+
children: [
|
|
3062
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
3063
|
+
className: "text-fg text-base font-semibold",
|
|
3064
|
+
children: t("demo.redactConfirmTitle")
|
|
3065
|
+
}),
|
|
3066
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3067
|
+
className: "text-fg-muted mt-2 text-sm",
|
|
3068
|
+
children: t("demo.redactConfirmBody", { params: { count } })
|
|
3069
|
+
}),
|
|
3070
|
+
collateral > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3071
|
+
className: "mt-2 text-sm font-medium text-red-600",
|
|
3072
|
+
children: t("demo.redactConfirmCollateral", { params: { count: collateral } })
|
|
3073
|
+
}) : null,
|
|
3074
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3075
|
+
className: "mt-2 text-sm text-red-600",
|
|
3076
|
+
children: error
|
|
3077
|
+
}) : null,
|
|
3078
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3079
|
+
className: "mt-4 flex justify-end gap-2",
|
|
3080
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3081
|
+
type: "button",
|
|
3082
|
+
disabled: busy,
|
|
3083
|
+
onClick: surface.close,
|
|
3084
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm",
|
|
3085
|
+
children: t("demo.cancel")
|
|
3086
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3087
|
+
type: "button",
|
|
3088
|
+
disabled: busy,
|
|
3089
|
+
onClick: () => void apply(),
|
|
3090
|
+
className: "rounded-md bg-red-600 px-3 py-1.5 text-sm text-white hover:bg-red-700 disabled:opacity-60",
|
|
3091
|
+
children: busy ? t("demo.redactApplying") : t("demo.redactApply")
|
|
3092
|
+
})]
|
|
3093
|
+
})
|
|
3094
|
+
]
|
|
3095
|
+
})
|
|
3096
|
+
});
|
|
3097
|
+
}
|
|
3098
|
+
//#endregion
|
|
3099
|
+
//#region src/ui/document-boot.tsx
|
|
3100
|
+
/**
|
|
3101
|
+
* Boot-state panes for the document area — what renders INSTEAD of the Stage
|
|
3102
|
+
* while the selected tab's document isn't ready:
|
|
3103
|
+
*
|
|
3104
|
+
* `locked` → <PasswordPrompt/> (per-tab, v2's document-password-prompt port)
|
|
3105
|
+
* `error` → <DocumentError/>
|
|
3106
|
+
*
|
|
3107
|
+
* Both are per-TAB, not modal: several tabs can be locked/broken at once and
|
|
3108
|
+
* switching tabs stays free. The prompt drives ONE call —
|
|
3109
|
+
* `documents.unlock(id, { password })` — identical on the local (worker loads
|
|
3110
|
+
* the parked bytes) and cloud (/access grant) engines. The "incorrect" copy
|
|
3111
|
+
* keys on a real rejection: either this session's failed attempt, or
|
|
3112
|
+
* `passwordProvided` (a config-supplied password the engine already rejected).
|
|
3113
|
+
*/
|
|
3114
|
+
function PasswordPrompt() {
|
|
3115
|
+
const docId = (0, _embedpdf_react_runtime.useDocumentId)();
|
|
3116
|
+
const { docs, unlock, close } = (0, _embedpdf_react_runtime.useDocuments)();
|
|
3117
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
3118
|
+
const [password, setPassword] = (0, react.useState)("");
|
|
3119
|
+
const [rejected, setRejected] = (0, react.useState)(false);
|
|
3120
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
3121
|
+
const doc = docs.find((d) => d.id === docId);
|
|
3122
|
+
if (!docId || !doc) return null;
|
|
3123
|
+
const incorrect = rejected || doc.passwordProvided === true;
|
|
3124
|
+
const submit = async () => {
|
|
3125
|
+
if (busy || !password.trim()) return;
|
|
3126
|
+
setBusy(true);
|
|
3127
|
+
try {
|
|
3128
|
+
await unlock(docId, { password });
|
|
3129
|
+
} catch {
|
|
3130
|
+
setRejected(true);
|
|
3131
|
+
setPassword("");
|
|
3132
|
+
} finally {
|
|
3133
|
+
setBusy(false);
|
|
3134
|
+
}
|
|
3135
|
+
};
|
|
3136
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3137
|
+
className: "bg-app flex h-full w-full items-center justify-center p-8",
|
|
3138
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3139
|
+
className: "bg-surface border-border w-full max-w-sm rounded-xl border p-6 shadow-lg",
|
|
3140
|
+
children: [
|
|
3141
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3142
|
+
className: "flex flex-col items-center text-center",
|
|
3143
|
+
children: [
|
|
3144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3145
|
+
className: "bg-accent-light mb-4 rounded-full p-4",
|
|
3146
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3147
|
+
name: "lock",
|
|
3148
|
+
size: 28,
|
|
3149
|
+
className: "text-accent"
|
|
3150
|
+
})
|
|
3151
|
+
}),
|
|
3152
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
3153
|
+
className: "text-fg text-lg font-semibold",
|
|
3154
|
+
children: t("passwordPrompt.title")
|
|
3155
|
+
}),
|
|
3156
|
+
doc.name && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3157
|
+
className: "text-fg-muted mt-1 text-sm",
|
|
3158
|
+
children: doc.name
|
|
3159
|
+
})
|
|
3160
|
+
]
|
|
3161
|
+
}),
|
|
3162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3163
|
+
className: "text-fg-secondary mt-4 text-center text-sm",
|
|
3164
|
+
children: incorrect ? t("passwordPrompt.incorrect") : t("passwordPrompt.required")
|
|
3165
|
+
}),
|
|
3166
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3167
|
+
className: "mt-5",
|
|
3168
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
3169
|
+
className: "text-fg mb-1.5 block text-sm font-medium",
|
|
3170
|
+
htmlFor: "doc-password",
|
|
3171
|
+
children: t("passwordPrompt.label")
|
|
3172
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3173
|
+
id: "doc-password",
|
|
3174
|
+
type: "password",
|
|
3175
|
+
value: password,
|
|
3176
|
+
onChange: (e) => setPassword(e.target.value),
|
|
3177
|
+
onKeyDown: (e) => e.key === "Enter" && void submit(),
|
|
3178
|
+
disabled: busy,
|
|
3179
|
+
placeholder: t("passwordPrompt.placeholder"),
|
|
3180
|
+
className: "bg-surface border-border focus:border-accent focus:ring-accent text-fg placeholder:text-fg-muted block w-full rounded-md border px-3 py-2.5 text-sm focus:outline-none focus:ring-1 disabled:opacity-50",
|
|
3181
|
+
autoFocus: true
|
|
3182
|
+
})]
|
|
3183
|
+
}),
|
|
3184
|
+
incorrect && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3185
|
+
className: "mt-3 flex items-center gap-2 rounded-md border border-red-500/40 bg-red-500/10 p-3",
|
|
3186
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3187
|
+
name: "alertTriangle",
|
|
3188
|
+
size: 16,
|
|
3189
|
+
className: "flex-shrink-0 text-red-500"
|
|
3190
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3191
|
+
className: "text-sm text-red-500",
|
|
3192
|
+
children: t("passwordPrompt.incorrectWarning")
|
|
3193
|
+
})]
|
|
3194
|
+
}),
|
|
3195
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3196
|
+
className: "mt-6 flex gap-3",
|
|
3197
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3198
|
+
type: "button",
|
|
3199
|
+
onClick: () => void close(docId),
|
|
3200
|
+
disabled: busy,
|
|
3201
|
+
className: "border-border text-fg-secondary hover:bg-hover flex-1 cursor-pointer rounded-md border px-4 py-2.5 text-sm font-medium transition-colors disabled:opacity-50",
|
|
3202
|
+
children: t("passwordPrompt.cancel")
|
|
3203
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3204
|
+
type: "button",
|
|
3205
|
+
onClick: () => void submit(),
|
|
3206
|
+
disabled: busy || !password.trim(),
|
|
3207
|
+
className: "bg-accent text-on-accent flex-1 cursor-pointer rounded-md px-4 py-2.5 text-sm font-medium transition-colors hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",
|
|
3208
|
+
children: busy ? t("passwordPrompt.opening") : t("passwordPrompt.open")
|
|
3209
|
+
})]
|
|
3210
|
+
})
|
|
3211
|
+
]
|
|
3212
|
+
})
|
|
3213
|
+
});
|
|
3214
|
+
}
|
|
3215
|
+
function DocumentError() {
|
|
3216
|
+
const docId = (0, _embedpdf_react_runtime.useDocumentId)();
|
|
3217
|
+
const { docs, close } = (0, _embedpdf_react_runtime.useDocuments)();
|
|
3218
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
3219
|
+
const doc = docs.find((d) => d.id === docId);
|
|
3220
|
+
if (!docId || !doc) return null;
|
|
3221
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3222
|
+
className: "bg-app flex h-full w-full items-center justify-center p-8",
|
|
3223
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3224
|
+
className: "bg-surface border-border flex max-w-sm flex-col items-center rounded-xl border p-6 text-center shadow-lg",
|
|
3225
|
+
children: [
|
|
3226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3227
|
+
className: "mb-4 rounded-full bg-red-500/10 p-4",
|
|
3228
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3229
|
+
name: "alertTriangle",
|
|
3230
|
+
size: 28,
|
|
3231
|
+
className: "text-red-500"
|
|
3232
|
+
})
|
|
3233
|
+
}),
|
|
3234
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
3235
|
+
className: "text-fg text-lg font-semibold",
|
|
3236
|
+
children: t("documentError.title")
|
|
3237
|
+
}),
|
|
3238
|
+
doc.name && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3239
|
+
className: "text-fg-muted mt-1 text-sm",
|
|
3240
|
+
children: doc.name
|
|
3241
|
+
}),
|
|
3242
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3243
|
+
className: "text-fg-secondary mt-2 text-sm",
|
|
3244
|
+
children: t("documentError.unknown")
|
|
3245
|
+
}),
|
|
3246
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3247
|
+
type: "button",
|
|
3248
|
+
onClick: () => void close(docId),
|
|
3249
|
+
className: "bg-accent text-on-accent mt-5 w-full cursor-pointer rounded-md px-4 py-2.5 text-sm font-medium transition-colors hover:opacity-90",
|
|
3250
|
+
children: t("documentError.close")
|
|
3251
|
+
})
|
|
3252
|
+
]
|
|
3253
|
+
})
|
|
3254
|
+
});
|
|
3255
|
+
}
|
|
3256
|
+
//#endregion
|
|
3257
|
+
//#region src/Shell.tsx
|
|
3258
|
+
/**
|
|
3259
|
+
* The viewer shell — the snippet's layout, driven entirely by the v3 commands
|
|
3260
|
+
* + measured-toolbar system.
|
|
3261
|
+
*
|
|
3262
|
+
* ── header socket (empty unless a child fills it) ─────────
|
|
3263
|
+
* ── main toolbar (measured; auto-overflows) ───────────────
|
|
3264
|
+
* ── mode band (DERIVED from the shell's open mode surface) ─
|
|
3265
|
+
* left sidebar │ Stage (pages) │ right sidebar
|
|
3266
|
+
* ──────────── page-controls overlay ──────────────────────
|
|
3267
|
+
*
|
|
3268
|
+
* The workspace/document split is structural: header + toolbars are
|
|
3269
|
+
* workspace-scoped and render at t≈0 (translated, measured) while the wasm
|
|
3270
|
+
* engine still boots; everything document-scoped sits inside <DocumentGate>,
|
|
3271
|
+
* whose fallback is the empty-workspace state (v2's loader, but UNDER a live
|
|
3272
|
+
* toolbar instead of replacing the whole app).
|
|
3273
|
+
*
|
|
3274
|
+
* Which mode band shows is not stored anywhere: it's a projection of
|
|
3275
|
+
* plugin-shell's exclusive 'mode' surface, read null-safely so the band simply
|
|
3276
|
+
* doesn't exist without a document.
|
|
3277
|
+
*/
|
|
3278
|
+
const ANNOTATION_RENDERERS = [_embedpdf_react_form.formWidgetRenderer];
|
|
3279
|
+
function ModeBand({ edge }) {
|
|
3280
|
+
const schema = useChromeSchema();
|
|
3281
|
+
const modeSurfaces = (0, react.useMemo)(() => Object.keys(schema.modeBars ?? {}), [schema]);
|
|
3282
|
+
const activeMode = (0, _embedpdf_react_runtime.useOptionalSelector)(_embedpdf_react_shell.ShellToken, (s) => modeSurfaces.find((m) => s.isOpen(m)) ?? null, null);
|
|
3283
|
+
if (!activeMode) return null;
|
|
3284
|
+
const bar = require_chrome.getModeBar(schema, activeMode);
|
|
3285
|
+
if (!bar) return null;
|
|
3286
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3287
|
+
className: `border-border bg-surface-alt flex shrink-0 items-center px-4 py-2 ${edge === "top" ? "border-b" : "border-t"}`,
|
|
3288
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppToolbar, {
|
|
3289
|
+
bar,
|
|
3290
|
+
className: "w-full"
|
|
3291
|
+
})
|
|
3292
|
+
});
|
|
3293
|
+
}
|
|
3294
|
+
/** The empty-workspace state: shown while the initial documents (and the
|
|
3295
|
+
* engine behind them) are still loading — or if the user closes every tab. */
|
|
3296
|
+
function OpeningDocuments() {
|
|
3297
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
3298
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3299
|
+
className: "grid h-full w-full place-items-center",
|
|
3300
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3301
|
+
className: "text-fg-muted flex flex-col items-center gap-3",
|
|
3302
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-border-subtle border-t-accent h-8 w-8 animate-spin rounded-full border-2" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3303
|
+
className: "text-sm",
|
|
3304
|
+
children: t("demo.opening")
|
|
3305
|
+
})]
|
|
3306
|
+
})
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
/**
|
|
3310
|
+
* The document area's lifecycle switch. A `locked` tab shows its password
|
|
3311
|
+
* prompt, an `error` tab its error pane — per TAB (keyed by document), so
|
|
3312
|
+
* several can coexist and tab switching stays free. Everything else is the
|
|
3313
|
+
* ready-gated Stage; the gate's fallback covers `loading`.
|
|
3314
|
+
*/
|
|
3315
|
+
function DocumentArea({ children }) {
|
|
3316
|
+
const docId = (0, _embedpdf_react_runtime.useDocumentId)();
|
|
3317
|
+
const status = (0, _embedpdf_react_runtime.useDocumentStatus)();
|
|
3318
|
+
if (status === "locked") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PasswordPrompt, {}, docId);
|
|
3319
|
+
if (status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DocumentError, {}, docId);
|
|
3320
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_runtime.DocumentGate, {
|
|
3321
|
+
fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpeningDocuments, {}),
|
|
3322
|
+
children
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
function Shell() {
|
|
3326
|
+
(0, _embedpdf_react_commands.useCommandShortcuts)();
|
|
3327
|
+
(0, _embedpdf_react_annotation.useFilePickerProvider)();
|
|
3328
|
+
(0, _embedpdf_react_form.useFormScriptingProvider)();
|
|
3329
|
+
const schema = useChromeSchema();
|
|
3330
|
+
const frame = schema.frame ?? {};
|
|
3331
|
+
const toolbarEdge = frame.toolbar ?? "top";
|
|
3332
|
+
const toolbarBand = schema.bars.main && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3333
|
+
part: "toolbar",
|
|
3334
|
+
className: `border-border bg-surface flex shrink-0 items-center px-4 py-2 ${toolbarEdge === "top" ? "border-b" : "border-t"}`,
|
|
3335
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppToolbar, {
|
|
3336
|
+
bar: schema.bars.main,
|
|
3337
|
+
className: "w-full"
|
|
3338
|
+
})
|
|
3339
|
+
});
|
|
3340
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3341
|
+
className: "bg-app text-fg flex h-full flex-col",
|
|
3342
|
+
children: [
|
|
3343
|
+
(frame.header ?? true) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("slot", { name: "header" }),
|
|
3344
|
+
(frame.tabs ?? "always") !== "never" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("slot", {
|
|
3345
|
+
name: "tabs",
|
|
3346
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabBar, { visibility: frame.tabs ?? "always" })
|
|
3347
|
+
}),
|
|
3348
|
+
toolbarEdge === "top" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [toolbarBand, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeBand, { edge: "top" })] }),
|
|
3349
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3350
|
+
className: "relative flex min-h-0 flex-1",
|
|
3351
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DocumentArea, { children: [
|
|
3352
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LeftSidebar, {}),
|
|
3353
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3354
|
+
className: "relative min-w-0 flex-1",
|
|
3355
|
+
children: [
|
|
3356
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArmedToolCursor, {}),
|
|
3357
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_stage.Stage, {
|
|
3358
|
+
interaction: true,
|
|
3359
|
+
overlay: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3360
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationStrip, {}),
|
|
3361
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_scrollbar.Scrollbar, { axis: "y" }),
|
|
3362
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_scrollbar.Scrollbar, { axis: "x" })
|
|
3363
|
+
] }),
|
|
3364
|
+
className: "h-full w-full",
|
|
3365
|
+
style: { background: "var(--ep-canvas)" },
|
|
3366
|
+
children: () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_render.RenderLayer, { annotations: false }),
|
|
3368
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_render.TileLayer, { annotations: false }),
|
|
3369
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_selection.SelectionLayer, {}),
|
|
3370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_search.SearchLayer, {}),
|
|
3371
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_link.LinkLayer, {}),
|
|
3372
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_annotation.AnnotationLayer, { renderers: ANNOTATION_RENDERERS })
|
|
3373
|
+
] })
|
|
3374
|
+
}),
|
|
3375
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageControls, {})
|
|
3376
|
+
]
|
|
3377
|
+
}),
|
|
3378
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RightSidebar, {}),
|
|
3379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedactConfirmModal, {})
|
|
3380
|
+
] })
|
|
3381
|
+
}),
|
|
3382
|
+
toolbarEdge === "bottom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeBand, { edge: "bottom" }), toolbarBand] })
|
|
3383
|
+
]
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
//#endregion
|
|
3387
|
+
//#region src/viewer.tsx
|
|
3388
|
+
/**
|
|
3389
|
+
* The full viewer, as a component — and the customization CONTRACT. The
|
|
3390
|
+
* `ViewerCustomization` surface here is the exact shape that later powers
|
|
3391
|
+
* `EmbedPDF.init({...})` and every framework wrapper, because all of it is
|
|
3392
|
+
* plain data (see README.md).
|
|
3393
|
+
*
|
|
3394
|
+
* Two semantics, on purpose:
|
|
3395
|
+
* - REGISTRIES (commands / icons / strings) merge ADDITIVELY over the
|
|
3396
|
+
* defaults by id; a colliding id overrides.
|
|
3397
|
+
* - STRUCTURE (chrome) is an owned VALUE: the default, a transform of it,
|
|
3398
|
+
* or the host's own schema — never merged.
|
|
3399
|
+
*
|
|
3400
|
+
* The engine stays a prop — choosing local-wasm vs cloud is the host's
|
|
3401
|
+
* decision, and each delivery wires its own. The chrome —
|
|
3402
|
+
* toolbars, translations, theme — renders at t≈0; only the pages wait on
|
|
3403
|
+
* the engine. Customization is mount-time (exactly like v2's init config);
|
|
3404
|
+
* changing these props on a mounted viewer does not rebuild the workspace.
|
|
3405
|
+
*/
|
|
3406
|
+
/** Lazy built-in packs — a strings override for one of these wraps its loader. */
|
|
3407
|
+
const BUILTIN_LOADERS = { es: () => Promise.resolve().then(() => require("./es-C5084JO6.cjs")).then((m) => m.es) };
|
|
3408
|
+
const isDict = (v) => typeof v === "object" && v !== null;
|
|
3409
|
+
/**
|
|
3410
|
+
* Deep-merge `over` into `base` (immutably), expanding dotted keys —
|
|
3411
|
+
* `{ 'acme.send': 'X' }` and `{ acme: { send: 'X' } }` are the same override.
|
|
3412
|
+
*/
|
|
3413
|
+
function mergeTranslations(base, over) {
|
|
3414
|
+
const out = { ...base };
|
|
3415
|
+
for (const [key, value] of Object.entries(over)) {
|
|
3416
|
+
const path = key.split(".");
|
|
3417
|
+
let node = out;
|
|
3418
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
3419
|
+
const prev = node[path[i]];
|
|
3420
|
+
node = node[path[i]] = isDict(prev) ? { ...prev } : {};
|
|
3421
|
+
}
|
|
3422
|
+
const leaf = path[path.length - 1];
|
|
3423
|
+
const prev = node[leaf];
|
|
3424
|
+
node[leaf] = typeof value === "string" ? value : mergeTranslations(isDict(prev) ? prev : {}, value);
|
|
3425
|
+
}
|
|
3426
|
+
return out;
|
|
3427
|
+
}
|
|
3428
|
+
const mergeLocale = (pack, over) => over ? {
|
|
3429
|
+
...pack,
|
|
3430
|
+
translations: mergeTranslations(pack.translations, over)
|
|
3431
|
+
} : pack;
|
|
3432
|
+
/** Normalize the shorthand — deliveries use this to read one shape. */
|
|
3433
|
+
const themeConfigOf = (theme) => typeof theme === "string" ? { preference: theme } : theme ?? {};
|
|
3434
|
+
/** Inside <Viewer>: mints the handle from the live kernel, reports it up. */
|
|
3435
|
+
function HandleBridge({ onViewer }) {
|
|
3436
|
+
const kernel = (0, _embedpdf_react_runtime.useKernel)();
|
|
3437
|
+
(0, react.useEffect)(() => {
|
|
3438
|
+
onViewer(createViewerHandle(kernel));
|
|
3439
|
+
}, [kernel]);
|
|
3440
|
+
return null;
|
|
3441
|
+
}
|
|
3442
|
+
function Booting() {
|
|
3443
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3444
|
+
className: "bg-app text-fg-muted grid h-full place-items-center",
|
|
3445
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3446
|
+
className: "animate-pulse text-sm",
|
|
3447
|
+
children: (0, _embedpdf_react_i18n.useT)()("demo.starting")
|
|
3448
|
+
})
|
|
3449
|
+
});
|
|
3450
|
+
}
|
|
3451
|
+
function FullViewer({ engine, initialDocuments, fallback, commands, icons, strings, locale = "auto", disabledCategories, chrome, theme, themeTarget, onViewer }) {
|
|
3452
|
+
const [resolved] = (0, react.useState)(() => {
|
|
3453
|
+
const byId = new Map(require_commands.defaultCommands.map((c) => [c.id, c]));
|
|
3454
|
+
for (const c of commands ?? []) byId.set(c.id, c);
|
|
3455
|
+
const resolvedCommands = [...byId.values()];
|
|
3456
|
+
const resolvedChrome = typeof chrome === "function" ? chrome(require_chrome.defaultChrome, _embedpdf_react_toolbar.chromeHelpers) : chrome ?? require_chrome.defaultChrome;
|
|
3457
|
+
const overrides = strings ?? {};
|
|
3458
|
+
const locales = [mergeLocale(en, overrides.en)];
|
|
3459
|
+
const loaders = {};
|
|
3460
|
+
for (const [code, load] of Object.entries(BUILTIN_LOADERS)) {
|
|
3461
|
+
const over = overrides[code];
|
|
3462
|
+
loaders[code] = over ? () => load().then((pack) => mergeLocale(pack, over)) : load;
|
|
3463
|
+
}
|
|
3464
|
+
for (const [code, over] of Object.entries(overrides)) {
|
|
3465
|
+
if (code === "en" || code in BUILTIN_LOADERS) continue;
|
|
3466
|
+
locales.push({
|
|
3467
|
+
code,
|
|
3468
|
+
name: code,
|
|
3469
|
+
translations: mergeTranslations({}, over)
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3472
|
+
const codes = [.../* @__PURE__ */ new Set([
|
|
3473
|
+
"en",
|
|
3474
|
+
...Object.keys(BUILTIN_LOADERS),
|
|
3475
|
+
...Object.keys(overrides)
|
|
3476
|
+
])];
|
|
3477
|
+
const initial = locale === "auto" ? (0, _embedpdf_react_i18n.negotiateLocale)(codes, typeof navigator !== "undefined" ? navigator.languages : []) ?? "en" : locale;
|
|
3478
|
+
return {
|
|
3479
|
+
commands: resolvedCommands,
|
|
3480
|
+
chrome: resolvedChrome,
|
|
3481
|
+
icons: icons ?? {},
|
|
3482
|
+
i18n: {
|
|
3483
|
+
locales,
|
|
3484
|
+
loaders,
|
|
3485
|
+
initial
|
|
3486
|
+
}
|
|
3487
|
+
};
|
|
3488
|
+
});
|
|
3489
|
+
(0, react.useEffect)(() => {
|
|
3490
|
+
if (process.env.NODE_ENV === "production") return;
|
|
3491
|
+
const ids = new Set(resolved.commands.map((c) => c.id));
|
|
3492
|
+
for (const problem of (0, _embedpdf_react_toolbar.validateChrome)(resolved.chrome, ids)) console.warn(`[embedpdf] chrome: ${problem}`);
|
|
3493
|
+
for (const c of resolved.commands) if (c.icon && !(c.icon in ICON_PATHS) && !(c.icon in resolved.icons)) console.warn(`[embedpdf] command "${c.id}": unknown icon "${c.icon}"`);
|
|
3494
|
+
}, [resolved]);
|
|
3495
|
+
const [plugins] = (0, react.useState)(() => [
|
|
3496
|
+
(0, _embedpdf_react_stage.stagePlugin)({
|
|
3497
|
+
layout: "vertical",
|
|
3498
|
+
interaction: true
|
|
3499
|
+
}),
|
|
3500
|
+
(0, _embedpdf_react_stage.stagePlugin)({
|
|
3501
|
+
id: "stage-thumbs",
|
|
3502
|
+
token: ThumbsStageToken,
|
|
3503
|
+
layout: "grid",
|
|
3504
|
+
columns: 1,
|
|
3505
|
+
sizing: "uniform",
|
|
3506
|
+
zoom: { pageWidth: 150 },
|
|
3507
|
+
padding: 12,
|
|
3508
|
+
gap: { px: 16 },
|
|
3509
|
+
pageFrame: {
|
|
3510
|
+
top: 0,
|
|
3511
|
+
right: 0,
|
|
3512
|
+
bottom: 20,
|
|
3513
|
+
left: 0
|
|
3514
|
+
},
|
|
3515
|
+
fitAlign: {
|
|
3516
|
+
x: "center",
|
|
3517
|
+
y: "start"
|
|
3518
|
+
},
|
|
3519
|
+
scrollBehavior: "instant"
|
|
3520
|
+
}),
|
|
3521
|
+
(0, _embedpdf_react_render.renderPlugin)(),
|
|
3522
|
+
(0, _embedpdf_react_page_edit.pageEditPlugin)(),
|
|
3523
|
+
(0, _embedpdf_react_interaction.interactionPlugin)({ defaultTool: "pointer" }),
|
|
3524
|
+
(0, _embedpdf_react_selection.selectionPlugin)(),
|
|
3525
|
+
(0, _embedpdf_react_annotation.annotationPlugin)({ tools: [{
|
|
3526
|
+
id: "arrow",
|
|
3527
|
+
extends: "line",
|
|
3528
|
+
defaults: { lineEndings: {
|
|
3529
|
+
start: "none",
|
|
3530
|
+
end: "open-arrow"
|
|
3531
|
+
} }
|
|
3532
|
+
}] }),
|
|
3533
|
+
(0, _embedpdf_react_form.formPlugin)({ scripting: { enabled: true } }),
|
|
3534
|
+
(0, _embedpdf_react_link.linkPlugin)(),
|
|
3535
|
+
(0, _embedpdf_react_redaction.redactionPlugin)(),
|
|
3536
|
+
(0, _embedpdf_react_search.searchPlugin)(),
|
|
3537
|
+
demoToolsPlugin(),
|
|
3538
|
+
(0, _embedpdf_react_i18n.i18nPlugin)({
|
|
3539
|
+
locale: resolved.i18n.initial,
|
|
3540
|
+
fallbackLocale: "en",
|
|
3541
|
+
locales: resolved.i18n.locales,
|
|
3542
|
+
loaders: resolved.i18n.loaders
|
|
3543
|
+
}),
|
|
3544
|
+
(0, _embedpdf_react_commands.commandsPlugin)({
|
|
3545
|
+
commands: resolved.commands,
|
|
3546
|
+
disabledCategories: disabledCategories ? [...disabledCategories] : void 0
|
|
3547
|
+
}),
|
|
3548
|
+
(0, _embedpdf_react_shell.shellPlugin)()
|
|
3549
|
+
]);
|
|
3550
|
+
const config = resolved;
|
|
3551
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeProvider, {
|
|
3552
|
+
preference: themeConfigOf(theme).preference,
|
|
3553
|
+
target: themeTarget,
|
|
3554
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_runtime.Viewer, {
|
|
3555
|
+
engine,
|
|
3556
|
+
plugins,
|
|
3557
|
+
initialDocuments,
|
|
3558
|
+
fallback: fallback ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Booting, {}),
|
|
3559
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ViewerConfigProvider, {
|
|
3560
|
+
value: config,
|
|
3561
|
+
children: [onViewer && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HandleBridge, { onViewer }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Shell, {})]
|
|
3562
|
+
})
|
|
3563
|
+
})
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
//#endregion
|
|
3567
|
+
Object.defineProperty(exports, "AnnotationToken", {
|
|
3568
|
+
enumerable: true,
|
|
3569
|
+
get: function() {
|
|
3570
|
+
return _embedpdf_react_annotation.AnnotationToken;
|
|
3571
|
+
}
|
|
3572
|
+
});
|
|
3573
|
+
Object.defineProperty(exports, "CommandsToken", {
|
|
3574
|
+
enumerable: true,
|
|
3575
|
+
get: function() {
|
|
3576
|
+
return _embedpdf_react_commands.CommandsToken;
|
|
3577
|
+
}
|
|
3578
|
+
});
|
|
3579
|
+
Object.defineProperty(exports, "DocumentsToken", {
|
|
3580
|
+
enumerable: true,
|
|
3581
|
+
get: function() {
|
|
3582
|
+
return _embedpdf_react_runtime.DocumentsToken;
|
|
3583
|
+
}
|
|
3584
|
+
});
|
|
3585
|
+
Object.defineProperty(exports, "FormToken", {
|
|
3586
|
+
enumerable: true,
|
|
3587
|
+
get: function() {
|
|
3588
|
+
return _embedpdf_react_form.FormToken;
|
|
3589
|
+
}
|
|
3590
|
+
});
|
|
3591
|
+
exports.FullViewer = FullViewer;
|
|
3592
|
+
Object.defineProperty(exports, "I18nToken", {
|
|
3593
|
+
enumerable: true,
|
|
3594
|
+
get: function() {
|
|
3595
|
+
return _embedpdf_react_i18n.I18nToken;
|
|
3596
|
+
}
|
|
3597
|
+
});
|
|
3598
|
+
Object.defineProperty(exports, "InteractionToken", {
|
|
3599
|
+
enumerable: true,
|
|
3600
|
+
get: function() {
|
|
3601
|
+
return _embedpdf_react_interaction.InteractionToken;
|
|
3602
|
+
}
|
|
3603
|
+
});
|
|
3604
|
+
Object.defineProperty(exports, "MetadataToken", {
|
|
3605
|
+
enumerable: true,
|
|
3606
|
+
get: function() {
|
|
3607
|
+
return _embedpdf_react_metadata.MetadataToken;
|
|
3608
|
+
}
|
|
3609
|
+
});
|
|
3610
|
+
Object.defineProperty(exports, "RedactionToken", {
|
|
3611
|
+
enumerable: true,
|
|
3612
|
+
get: function() {
|
|
3613
|
+
return _embedpdf_react_redaction.RedactionToken;
|
|
3614
|
+
}
|
|
3615
|
+
});
|
|
3616
|
+
Object.defineProperty(exports, "SearchToken", {
|
|
3617
|
+
enumerable: true,
|
|
3618
|
+
get: function() {
|
|
3619
|
+
return _embedpdf_react_search.SearchToken;
|
|
3620
|
+
}
|
|
3621
|
+
});
|
|
3622
|
+
Object.defineProperty(exports, "SelectionToken", {
|
|
3623
|
+
enumerable: true,
|
|
3624
|
+
get: function() {
|
|
3625
|
+
return _embedpdf_react_selection.SelectionToken;
|
|
3626
|
+
}
|
|
3627
|
+
});
|
|
3628
|
+
Object.defineProperty(exports, "ShellToken", {
|
|
3629
|
+
enumerable: true,
|
|
3630
|
+
get: function() {
|
|
3631
|
+
return _embedpdf_react_shell.ShellToken;
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
Object.defineProperty(exports, "StageToken", {
|
|
3635
|
+
enumerable: true,
|
|
3636
|
+
get: function() {
|
|
3637
|
+
return _embedpdf_react_stage.StageToken;
|
|
3638
|
+
}
|
|
3639
|
+
});
|
|
3640
|
+
Object.defineProperty(exports, "addItem", {
|
|
3641
|
+
enumerable: true,
|
|
3642
|
+
get: function() {
|
|
3643
|
+
return _embedpdf_react_toolbar.addItem;
|
|
3644
|
+
}
|
|
3645
|
+
});
|
|
3646
|
+
Object.defineProperty(exports, "chromeHelpers", {
|
|
3647
|
+
enumerable: true,
|
|
3648
|
+
get: function() {
|
|
3649
|
+
return _embedpdf_react_toolbar.chromeHelpers;
|
|
3650
|
+
}
|
|
3651
|
+
});
|
|
3652
|
+
exports.createViewerHandle = createViewerHandle;
|
|
3653
|
+
Object.defineProperty(exports, "custom", {
|
|
3654
|
+
enumerable: true,
|
|
3655
|
+
get: function() {
|
|
3656
|
+
return _embedpdf_react_toolbar.custom;
|
|
3657
|
+
}
|
|
3658
|
+
});
|
|
3659
|
+
exports.defaultChrome = require_chrome.defaultChrome;
|
|
3660
|
+
exports.defaultCommands = require_commands.defaultCommands;
|
|
3661
|
+
exports.defaultIcons = ICON_PATHS;
|
|
3662
|
+
Object.defineProperty(exports, "defineChrome", {
|
|
3663
|
+
enumerable: true,
|
|
3664
|
+
get: function() {
|
|
3665
|
+
return _embedpdf_react_toolbar.defineChrome;
|
|
3666
|
+
}
|
|
3667
|
+
});
|
|
3668
|
+
Object.defineProperty(exports, "group", {
|
|
3669
|
+
enumerable: true,
|
|
3670
|
+
get: function() {
|
|
3671
|
+
return _embedpdf_react_toolbar.group;
|
|
3672
|
+
}
|
|
3673
|
+
});
|
|
3674
|
+
Object.defineProperty(exports, "item", {
|
|
3675
|
+
enumerable: true,
|
|
3676
|
+
get: function() {
|
|
3677
|
+
return _embedpdf_react_toolbar.item;
|
|
3678
|
+
}
|
|
3679
|
+
});
|
|
3680
|
+
Object.defineProperty(exports, "removeItems", {
|
|
3681
|
+
enumerable: true,
|
|
3682
|
+
get: function() {
|
|
3683
|
+
return _embedpdf_react_toolbar.removeItems;
|
|
3684
|
+
}
|
|
3685
|
+
});
|
|
3686
|
+
Object.defineProperty(exports, "replaceItem", {
|
|
3687
|
+
enumerable: true,
|
|
3688
|
+
get: function() {
|
|
3689
|
+
return _embedpdf_react_toolbar.replaceItem;
|
|
3690
|
+
}
|
|
3691
|
+
});
|
|
3692
|
+
exports.themeConfigOf = themeConfigOf;
|
|
3693
|
+
Object.defineProperty(exports, "validateChrome", {
|
|
3694
|
+
enumerable: true,
|
|
3695
|
+
get: function() {
|
|
3696
|
+
return _embedpdf_react_toolbar.validateChrome;
|
|
3697
|
+
}
|
|
3698
|
+
});
|
|
3699
|
+
|
|
3700
|
+
//# sourceMappingURL=index.cjs.map
|