@embedpdf/viewer-chrome 3.0.0-next.13 → 3.0.0-next.15
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/README.md +175 -132
- package/dist/annotation-fonts-BqoLZ4Mp.js +80 -0
- package/dist/annotation-fonts-BqoLZ4Mp.js.map +1 -0
- package/dist/annotation-fonts-Dt0IQXhz.cjs +91 -0
- package/dist/annotation-fonts-Dt0IQXhz.cjs.map +1 -0
- package/dist/annotation-style--rPIZsuP.js +879 -0
- package/dist/annotation-style--rPIZsuP.js.map +1 -0
- package/dist/annotation-style-Bzvvp8-N.cjs +879 -0
- package/dist/annotation-style-Bzvvp8-N.cjs.map +1 -0
- package/dist/ar-D9XKAmDx.js +567 -0
- package/dist/ar-D9XKAmDx.js.map +1 -0
- package/dist/ar-DnEj5vbP.cjs +567 -0
- package/dist/ar-DnEj5vbP.cjs.map +1 -0
- package/dist/chrome.cjs +378 -221
- package/dist/chrome.cjs.map +1 -1
- package/dist/chrome.d.cts +5 -34
- package/dist/chrome.d.ts +5 -34
- package/dist/chrome.js +377 -218
- package/dist/chrome.js.map +1 -1
- package/dist/commands-BmgxhJ8B.js +953 -0
- package/dist/commands-BmgxhJ8B.js.map +1 -0
- package/dist/commands-CYt4SFUX.cjs +1060 -0
- package/dist/commands-CYt4SFUX.cjs.map +1 -0
- package/dist/commands.cjs +12 -595
- package/dist/commands.d.cts +29 -4
- package/dist/commands.d.ts +29 -4
- package/dist/commands.js +2 -593
- package/dist/comments-panel-MUVgLCBI.js +514 -0
- package/dist/comments-panel-MUVgLCBI.js.map +1 -0
- package/dist/comments-panel-pEcQg4Vs.cjs +514 -0
- package/dist/comments-panel-pEcQg4Vs.cjs.map +1 -0
- package/dist/de-Bdr4aWzm.js +566 -0
- package/dist/de-Bdr4aWzm.js.map +1 -0
- package/dist/de-Dv6zvbnd.cjs +566 -0
- package/dist/de-Dv6zvbnd.cjs.map +1 -0
- package/dist/dialog-B5GBisKv.js +55 -0
- package/dist/dialog-B5GBisKv.js.map +1 -0
- package/dist/dialog-t8F13BL_.cjs +60 -0
- package/dist/dialog-t8F13BL_.cjs.map +1 -0
- package/dist/es-BP09DR9w.cjs +566 -0
- package/dist/es-BP09DR9w.cjs.map +1 -0
- package/dist/es-BSXz5tBv.js +566 -0
- package/dist/es-BSXz5tBv.js.map +1 -0
- package/dist/form-design-panel-B5MXTZ96.cjs +418 -0
- package/dist/form-design-panel-B5MXTZ96.cjs.map +1 -0
- package/dist/form-design-panel-vWWU9uB-.js +418 -0
- package/dist/form-design-panel-vWWU9uB-.js.map +1 -0
- package/dist/fr-BBQQn7tJ.cjs +566 -0
- package/dist/fr-BBQQn7tJ.cjs.map +1 -0
- package/dist/fr-Ckn3K9v1.js +566 -0
- package/dist/fr-Ckn3K9v1.js.map +1 -0
- package/dist/he-DRsG7bH7.cjs +567 -0
- package/dist/he-DRsG7bH7.cjs.map +1 -0
- package/dist/he-wdGU5PkU.js +567 -0
- package/dist/he-wdGU5PkU.js.map +1 -0
- package/dist/icons-BrK5iVL8.js +856 -0
- package/dist/icons-BrK5iVL8.js.map +1 -0
- package/dist/icons-x0Tc05bs.cjs +1010 -0
- package/dist/icons-x0Tc05bs.cjs.map +1 -0
- package/dist/index.cjs +3162 -5106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +636 -143
- package/dist/index.d.ts +636 -143
- package/dist/index.js +3083 -4975
- package/dist/index.js.map +1 -1
- package/dist/it-BuP4o0xQ.cjs +566 -0
- package/dist/it-BuP4o0xQ.cjs.map +1 -0
- package/dist/it-Us-VmvIu.js +566 -0
- package/dist/it-Us-VmvIu.js.map +1 -0
- package/dist/ja-Cizj0-5O.js +566 -0
- package/dist/ja-Cizj0-5O.js.map +1 -0
- package/dist/ja-DovEd6ts.cjs +566 -0
- package/dist/ja-DovEd6ts.cjs.map +1 -0
- package/dist/ko-B-qJEol2.cjs +566 -0
- package/dist/ko-B-qJEol2.cjs.map +1 -0
- package/dist/ko-DlrXliE1.js +566 -0
- package/dist/ko-DlrXliE1.js.map +1 -0
- package/dist/labels-BiM9wp9K.cjs +21 -0
- package/dist/labels-BiM9wp9K.cjs.map +1 -0
- package/dist/labels-C6_i87Oj.js +16 -0
- package/dist/labels-C6_i87Oj.js.map +1 -0
- package/dist/link-editor-CSjqPnj1.cjs +125 -0
- package/dist/link-editor-CSjqPnj1.cjs.map +1 -0
- package/dist/link-editor-SxM1nnJU.js +125 -0
- package/dist/link-editor-SxM1nnJU.js.map +1 -0
- package/dist/measurement-Bs61XWbJ.js +325 -0
- package/dist/measurement-Bs61XWbJ.js.map +1 -0
- package/dist/measurement-CcCsHPOX.cjs +326 -0
- package/dist/measurement-CcCsHPOX.cjs.map +1 -0
- package/dist/nl-0aF7M_0h.js +566 -0
- package/dist/nl-0aF7M_0h.js.map +1 -0
- package/dist/nl-D3KZDDiJ.cjs +566 -0
- package/dist/nl-D3KZDDiJ.cjs.map +1 -0
- package/dist/pl-BW--AaCI.js +566 -0
- package/dist/pl-BW--AaCI.js.map +1 -0
- package/dist/pl-DSFhAUqx.cjs +566 -0
- package/dist/pl-DSFhAUqx.cjs.map +1 -0
- package/dist/pt-C5S-8MC5.cjs +566 -0
- package/dist/pt-C5S-8MC5.cjs.map +1 -0
- package/dist/pt-DIhpU5pT.js +566 -0
- package/dist/pt-DIhpU5pT.js.map +1 -0
- package/dist/redact-confirm-B6j36TPw.js +83 -0
- package/dist/redact-confirm-B6j36TPw.js.map +1 -0
- package/dist/redact-confirm-CX_SY2ZN.cjs +83 -0
- package/dist/redact-confirm-CX_SY2ZN.cjs.map +1 -0
- package/dist/redaction-panel-BmoIl-Ll.cjs +96 -0
- package/dist/redaction-panel-BmoIl-Ll.cjs.map +1 -0
- package/dist/redaction-panel-IJDshSJI.js +96 -0
- package/dist/redaction-panel-IJDshSJI.js.map +1 -0
- package/dist/search-panel-HfIhJm1M.js +249 -0
- package/dist/search-panel-HfIhJm1M.js.map +1 -0
- package/dist/search-panel-s9IoiOtv.cjs +249 -0
- package/dist/search-panel-s9IoiOtv.cjs.map +1 -0
- package/dist/sheet-BDsa_Sup.cjs +442 -0
- package/dist/sheet-BDsa_Sup.cjs.map +1 -0
- package/dist/sheet-R6XDgyEf.js +365 -0
- package/dist/sheet-R6XDgyEf.js.map +1 -0
- package/dist/sign-dialog-CgnR7akj.cjs +155 -0
- package/dist/sign-dialog-CgnR7akj.cjs.map +1 -0
- package/dist/sign-dialog-GDqCoQhA.js +155 -0
- package/dist/sign-dialog-GDqCoQhA.js.map +1 -0
- package/dist/signature-inspector-DyO313Qf.cjs +166 -0
- package/dist/signature-inspector-DyO313Qf.cjs.map +1 -0
- package/dist/signature-inspector-okFz8do6.js +166 -0
- package/dist/signature-inspector-okFz8do6.js.map +1 -0
- package/dist/signature-maker-BhCmYyHD.js +393 -0
- package/dist/signature-maker-BhCmYyHD.js.map +1 -0
- package/dist/signature-maker-BoYa8vgA.cjs +393 -0
- package/dist/signature-maker-BoYa8vgA.cjs.map +1 -0
- package/dist/signatures-panel-B56PC6d9.cjs +300 -0
- package/dist/signatures-panel-B56PC6d9.cjs.map +1 -0
- package/dist/signatures-panel-CgnFfhFm.js +300 -0
- package/dist/signatures-panel-CgnFfhFm.js.map +1 -0
- package/dist/stamp-store-BHvRs-Do.cjs +147 -0
- package/dist/stamp-store-BHvRs-Do.cjs.map +1 -0
- package/dist/stamp-store-WuF-L3Jj.js +112 -0
- package/dist/stamp-store-WuF-L3Jj.js.map +1 -0
- package/dist/stamps-panel-B9NYGayn.cjs +231 -0
- package/dist/stamps-panel-B9NYGayn.cjs.map +1 -0
- package/dist/stamps-panel-CDO0FPyK.js +231 -0
- package/dist/stamps-panel-CDO0FPyK.js.map +1 -0
- package/dist/toolbar-CJu2wKTU.cjs +1659 -0
- package/dist/toolbar-CJu2wKTU.cjs.map +1 -0
- package/dist/toolbar-z9od3tKl.js +1564 -0
- package/dist/toolbar-z9od3tKl.js.map +1 -0
- package/dist/zh-cn-3tmMeHqv.cjs +566 -0
- package/dist/zh-cn-3tmMeHqv.cjs.map +1 -0
- package/dist/zh-cn-CxkUUBct.js +566 -0
- package/dist/zh-cn-CxkUUBct.js.map +1 -0
- package/package.json +6 -4
- package/src/styles.css +153 -89
- package/dist/commands.cjs.map +0 -1
- package/dist/commands.js.map +0 -1
- package/dist/es-DYwzfxyt.cjs +0 -293
- package/dist/es-DYwzfxyt.cjs.map +0 -1
- package/dist/es-sN6uqBgD.js +0 -293
- package/dist/es-sN6uqBgD.js.map +0 -1
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
import { n as Icon } from "./icons-BrK5iVL8.js";
|
|
2
|
+
import { n as useAnnotationFonts } from "./annotation-fonts-BqoLZ4Mp.js";
|
|
3
|
+
import { t as useLabel } from "./labels-C6_i87Oj.js";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { useDocumentId, useKernelValue, useOptionalCapability } from "@embedpdf/react/runtime";
|
|
6
|
+
import { useInteractionState } from "@embedpdf/react/interaction";
|
|
7
|
+
import { annotationKey, useAnnotation, useAnnotationDefaults, useAnnotationProperties, useAnnotationState } from "@embedpdf/react/annotation";
|
|
8
|
+
import { RedactionToken } from "@embedpdf/react/redaction";
|
|
9
|
+
import { useT } from "@embedpdf/react/i18n";
|
|
10
|
+
import { useCommands } from "@embedpdf/react/commands";
|
|
11
|
+
import { observeOutsidePress } from "@embedpdf/web";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
//#region src/ui/annotation-flags.tsx
|
|
14
|
+
/**
|
|
15
|
+
* The annotation flags section: a live test surface for `/F` (ISO 32000
|
|
16
|
+
* Table 167). Select any annotation(s) and toggle flags. Flags are
|
|
17
|
+
* properties like any other: the reads come from `useAnnotationProperties()`
|
|
18
|
+
* (a flag the selection disagrees on is `mixed`, an indeterminate checkbox),
|
|
19
|
+
* each named as the schema names it, and the writes go through
|
|
20
|
+
* `selection.update({ locked: true })`. The hint under each is the viewer's.
|
|
21
|
+
*
|
|
22
|
+
* Things to try:
|
|
23
|
+
* - `locked`: the selection keeps its outline but loses handles/knob; move,
|
|
24
|
+
* restyle and delete are refused; unchecking it here still works.
|
|
25
|
+
* - `hidden` / `noView`: the annotation disappears (it stays selected, so
|
|
26
|
+
* this panel can bring it back).
|
|
27
|
+
* - `readOnly`: visible but no longer clickable — deselect and try.
|
|
28
|
+
* - `noZoom` / `noRotate`: zoom or rotate the page; the body keeps its
|
|
29
|
+
* screen size / stays upright, anchored at its top-left corner.
|
|
30
|
+
* - `print`: flip it off and print/flatten elsewhere — the annotation is
|
|
31
|
+
* excluded (viewer rendering is unaffected).
|
|
32
|
+
*/
|
|
33
|
+
/** What each flag does, in a few words under its name: the hint's key. */
|
|
34
|
+
const FLAG_HINTS = {
|
|
35
|
+
print: "annotationFlags.print",
|
|
36
|
+
hidden: "annotationFlags.hidden",
|
|
37
|
+
noView: "annotationFlags.noView",
|
|
38
|
+
toggleNoView: "annotationFlags.toggleNoView",
|
|
39
|
+
readOnly: "annotationFlags.readOnly",
|
|
40
|
+
locked: "annotationFlags.locked",
|
|
41
|
+
lockedContents: "annotationFlags.lockedContents",
|
|
42
|
+
noZoom: "annotationFlags.noZoom",
|
|
43
|
+
noRotate: "annotationFlags.noRotate",
|
|
44
|
+
invisible: "annotationFlags.invisible"
|
|
45
|
+
};
|
|
46
|
+
function FlagRow({ label, hint, value, onToggle }) {
|
|
47
|
+
const ref = useRef(null);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (ref.current) ref.current.indeterminate = value === null;
|
|
50
|
+
}, [value]);
|
|
51
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
52
|
+
className: "hover:bg-hover flex cursor-pointer items-start gap-2.5 rounded px-1.5 py-1.5",
|
|
53
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
54
|
+
ref,
|
|
55
|
+
type: "checkbox",
|
|
56
|
+
className: "accent-accent mt-0.5",
|
|
57
|
+
checked: value === true,
|
|
58
|
+
onChange: () => onToggle(value !== true)
|
|
59
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
60
|
+
className: "min-w-0",
|
|
61
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
62
|
+
className: "text-fg block text-sm leading-tight",
|
|
63
|
+
children: label
|
|
64
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
65
|
+
className: "text-fg-muted block text-xs leading-tight",
|
|
66
|
+
children: hint
|
|
67
|
+
})]
|
|
68
|
+
})]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** The `/F` flags of the current selection, live-editable. Renders nothing
|
|
72
|
+
* when nothing is selected (the style panel shows its tool defaults then). */
|
|
73
|
+
function AnnotationFlagsSection() {
|
|
74
|
+
const t = useT();
|
|
75
|
+
const label = useLabel();
|
|
76
|
+
const annotation = useAnnotation();
|
|
77
|
+
const { properties, values, mixed } = useAnnotationProperties();
|
|
78
|
+
const flags = properties.filter((property) => property.control === "flag");
|
|
79
|
+
if (!flags.length) return null;
|
|
80
|
+
const valueOf = (key) => mixed.includes(key) ? null : values[key] === true;
|
|
81
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
82
|
+
className: "border-border mt-2 border-t pt-4",
|
|
83
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
84
|
+
className: "text-fg-muted mb-2 text-[11px] font-semibold uppercase tracking-wide",
|
|
85
|
+
children: t("annotationFlags.title")
|
|
86
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
87
|
+
className: "flex flex-col",
|
|
88
|
+
children: flags.map((flag) => /* @__PURE__ */ jsx(FlagRow, {
|
|
89
|
+
label: label(flag),
|
|
90
|
+
hint: t(FLAG_HINTS[flag.key]),
|
|
91
|
+
value: valueOf(flag.key),
|
|
92
|
+
onToggle: (next) => void annotation.selection.update({ [flag.key]: next })
|
|
93
|
+
}, flag.key))
|
|
94
|
+
})]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/ui/annotation-style.tsx
|
|
99
|
+
/**
|
|
100
|
+
* The annotation style panel. The plugin owns the schema and the state; this
|
|
101
|
+
* file owns only how each property renders and the engine fields it writes
|
|
102
|
+
* back. There is no per-subtype branching here: a new annotation kind that
|
|
103
|
+
* declares its properties in the kind table gets a working style panel for free.
|
|
104
|
+
*
|
|
105
|
+
* Data flow (mirrors examples/react's AnnotationSidebar):
|
|
106
|
+
* selection present → useAnnotationProperties(), write selection.update()
|
|
107
|
+
* nothing selected → useAnnotationProperties(tool), write tools.updateDefaults(tool, …)
|
|
108
|
+
*
|
|
109
|
+
* The look: a six-column swatch grid, range slider, SVG stroke / line-ending
|
|
110
|
+
* dropdowns, font-size combo and align toggles, styled with this app's
|
|
111
|
+
* semantic tokens (bg-surface / text-fg / border-border …).
|
|
112
|
+
*/
|
|
113
|
+
const PRESET_COLORS = [
|
|
114
|
+
"#000000",
|
|
115
|
+
"#5f6368",
|
|
116
|
+
"#e44234",
|
|
117
|
+
"#ff8b00",
|
|
118
|
+
"#ffd500",
|
|
119
|
+
"#00cc66",
|
|
120
|
+
"#00b8d9",
|
|
121
|
+
"#4a90e2",
|
|
122
|
+
"#9b51e0",
|
|
123
|
+
"#f272c8",
|
|
124
|
+
"#a0522d",
|
|
125
|
+
"#ffffff"
|
|
126
|
+
];
|
|
127
|
+
/** The standard fonts the picker offers, of the schema's; the registered fonts follow them. */
|
|
128
|
+
const OFFERED_FONTS = [
|
|
129
|
+
"helvetica",
|
|
130
|
+
"helvetica-bold",
|
|
131
|
+
"times-roman",
|
|
132
|
+
"courier"
|
|
133
|
+
];
|
|
134
|
+
const FONT_SIZES = [
|
|
135
|
+
8,
|
|
136
|
+
9,
|
|
137
|
+
10,
|
|
138
|
+
11,
|
|
139
|
+
12,
|
|
140
|
+
14,
|
|
141
|
+
16,
|
|
142
|
+
18,
|
|
143
|
+
24,
|
|
144
|
+
36,
|
|
145
|
+
48,
|
|
146
|
+
72
|
|
147
|
+
];
|
|
148
|
+
/** The borders the picker offers: the schema's styles, a dash or a cloud in a few sizes. */
|
|
149
|
+
const BORDER_OPTS = [
|
|
150
|
+
{
|
|
151
|
+
key: "solid",
|
|
152
|
+
make: () => ({ kind: "solid" })
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
key: "dashed-6-2",
|
|
156
|
+
make: () => ({
|
|
157
|
+
kind: "dashed",
|
|
158
|
+
dash: [6, 2]
|
|
159
|
+
})
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: "dashed-3-3",
|
|
163
|
+
make: () => ({
|
|
164
|
+
kind: "dashed",
|
|
165
|
+
dash: [3, 3]
|
|
166
|
+
})
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: "dashed-1-2",
|
|
170
|
+
make: () => ({
|
|
171
|
+
kind: "dashed",
|
|
172
|
+
dash: [1, 2]
|
|
173
|
+
})
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
key: "cloudy-1",
|
|
177
|
+
make: () => ({
|
|
178
|
+
kind: "cloudy",
|
|
179
|
+
intensity: 1
|
|
180
|
+
})
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: "cloudy-2",
|
|
184
|
+
make: () => ({
|
|
185
|
+
kind: "cloudy",
|
|
186
|
+
intensity: 2
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
const borderKey = (border) => border.kind === "cloudy" ? `cloudy-${border.intensity >= 2 ? 2 : 1}` : border.kind === "dashed" ? `dashed-${border.dash.join("-")}` : "solid";
|
|
191
|
+
/** The border the three engine fields describe: a cloud wins over the style under it. */
|
|
192
|
+
const borderOf = (values) => {
|
|
193
|
+
const intensity = values.cloudyIntensity;
|
|
194
|
+
if (intensity) return {
|
|
195
|
+
kind: "cloudy",
|
|
196
|
+
intensity
|
|
197
|
+
};
|
|
198
|
+
const dash = values.dashArray;
|
|
199
|
+
return values.borderStyle === "dashed" ? {
|
|
200
|
+
kind: "dashed",
|
|
201
|
+
dash: dash?.length ? dash : [3, 3]
|
|
202
|
+
} : { kind: "solid" };
|
|
203
|
+
};
|
|
204
|
+
/** The engine fields that state a border, every one of them (a patch replaces each whole). */
|
|
205
|
+
const borderFields = (border) => ({
|
|
206
|
+
borderStyle: border.kind === "dashed" ? "dashed" : "solid",
|
|
207
|
+
dashArray: border.kind === "dashed" ? border.dash : null,
|
|
208
|
+
cloudyIntensity: border.kind === "cloudy" ? border.intensity : null
|
|
209
|
+
});
|
|
210
|
+
/** The option of a choice with this value, for its label. */
|
|
211
|
+
const optionOf = (options, value) => options.find((option) => option.value === value);
|
|
212
|
+
function Field({ label, mixed, children }) {
|
|
213
|
+
const t = useT();
|
|
214
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
215
|
+
className: "mb-5",
|
|
216
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
217
|
+
className: "text-fg mb-2 block text-sm font-medium",
|
|
218
|
+
children: [label, mixed && /* @__PURE__ */ jsx("span", {
|
|
219
|
+
className: "text-fg-muted ms-1.5 text-xs font-normal",
|
|
220
|
+
children: t("stylePanel.mixed")
|
|
221
|
+
})]
|
|
222
|
+
}), children]
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const isTransparent = (color) => color == null || color === "transparent" || /^#([0-9a-f]{8})$/i.test(color) && color.slice(-2).toLowerCase() === "00";
|
|
226
|
+
function Swatch({ color, active, onSelect }) {
|
|
227
|
+
const t = useT();
|
|
228
|
+
const style = isTransparent(color) ? {
|
|
229
|
+
backgroundColor: "#fff",
|
|
230
|
+
backgroundImage: "linear-gradient(45deg, transparent 40%, #ef4444 40%, #ef4444 60%, transparent 60%)",
|
|
231
|
+
backgroundSize: "100% 100%"
|
|
232
|
+
} : { backgroundColor: color };
|
|
233
|
+
return /* @__PURE__ */ jsx("button", {
|
|
234
|
+
type: "button",
|
|
235
|
+
title: isTransparent(color) ? t("stylePanel.transparent") : color,
|
|
236
|
+
onClick: () => onSelect(color),
|
|
237
|
+
className: `border-border-strong h-5 w-5 rounded-full border ${active ? "outline-accent outline outline-2 outline-offset-2" : ""}`,
|
|
238
|
+
style
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function SwatchGrid({ value, onSelect, allowTransparent }) {
|
|
242
|
+
const active = (color) => color.toLowerCase() === (value ?? "").toLowerCase();
|
|
243
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
244
|
+
className: "grid grid-cols-6 gap-x-1 gap-y-3",
|
|
245
|
+
children: [PRESET_COLORS.map((color) => /* @__PURE__ */ jsx(Swatch, {
|
|
246
|
+
color,
|
|
247
|
+
active: active(color),
|
|
248
|
+
onSelect
|
|
249
|
+
}, color)), allowTransparent && /* @__PURE__ */ jsx(Swatch, {
|
|
250
|
+
color: "transparent",
|
|
251
|
+
active: isTransparent(value ?? null),
|
|
252
|
+
onSelect
|
|
253
|
+
})]
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function RangeSlider({ value, min, max, step, onChange }) {
|
|
257
|
+
return /* @__PURE__ */ jsx("input", {
|
|
258
|
+
type: "range",
|
|
259
|
+
className: "accent-accent mb-1.5 h-1 w-full cursor-pointer",
|
|
260
|
+
value,
|
|
261
|
+
min,
|
|
262
|
+
max,
|
|
263
|
+
step,
|
|
264
|
+
onChange: (event) => onChange(parseFloat(event.target.value))
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/** Close an open dropdown on a press outside it; the root holds its trigger too. */
|
|
268
|
+
function useOutsideClose(open, close) {
|
|
269
|
+
const rootRef = useRef(null);
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
const root = rootRef.current;
|
|
272
|
+
return open && root ? observeOutsidePress(root, close) : void 0;
|
|
273
|
+
}, [open, close]);
|
|
274
|
+
return rootRef;
|
|
275
|
+
}
|
|
276
|
+
function DropdownShell({ trigger, children }) {
|
|
277
|
+
const [open, setOpen] = useState(false);
|
|
278
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
279
|
+
ref: useOutsideClose(open, () => setOpen(false)),
|
|
280
|
+
className: "relative w-full",
|
|
281
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
282
|
+
type: "button",
|
|
283
|
+
onClick: () => setOpen((previous) => !previous),
|
|
284
|
+
className: "border-border bg-surface text-fg flex w-full items-center justify-between gap-2 rounded border px-3 py-1.5",
|
|
285
|
+
children: [trigger, /* @__PURE__ */ jsx(Icon, {
|
|
286
|
+
name: "chevronDown",
|
|
287
|
+
size: 16,
|
|
288
|
+
className: "text-fg-secondary shrink-0"
|
|
289
|
+
})]
|
|
290
|
+
}), open && /* @__PURE__ */ jsx("div", {
|
|
291
|
+
className: "border-border bg-elevated absolute z-10 mt-1 max-h-60 w-full overflow-y-auto rounded border p-1 shadow-lg",
|
|
292
|
+
children: children(() => setOpen(false))
|
|
293
|
+
})]
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function OptionRow({ selected, title, onClick, children }) {
|
|
297
|
+
return /* @__PURE__ */ jsx("button", {
|
|
298
|
+
type: "button",
|
|
299
|
+
title,
|
|
300
|
+
"aria-label": title,
|
|
301
|
+
onClick,
|
|
302
|
+
className: `hover:bg-hover flex w-full items-center rounded px-2 py-1.5 text-start text-sm ${selected ? "bg-selected text-accent" : "text-fg"}`,
|
|
303
|
+
children
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function borderSvg(border) {
|
|
307
|
+
if (border.kind === "cloudy") {
|
|
308
|
+
const radius = border.intensity >= 2 ? 5 : 3;
|
|
309
|
+
const bumps = Math.ceil(80 / (radius * 2));
|
|
310
|
+
const step = 80 / bumps;
|
|
311
|
+
const baseline = radius + 1;
|
|
312
|
+
const viewH = radius * 2 + 2;
|
|
313
|
+
const parts = [`M 0 ${baseline}`];
|
|
314
|
+
for (let i = 0; i < bumps; i++) parts.push(`A ${radius} ${radius} 0 0 1 ${step * (i + 1)} ${baseline}`);
|
|
315
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
316
|
+
width: "80",
|
|
317
|
+
height: viewH,
|
|
318
|
+
viewBox: `0 0 80 ${viewH}`,
|
|
319
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
320
|
+
d: parts.join(" "),
|
|
321
|
+
fill: "none",
|
|
322
|
+
stroke: "currentColor",
|
|
323
|
+
strokeWidth: "1.5",
|
|
324
|
+
strokeLinejoin: "round",
|
|
325
|
+
strokeLinecap: "round"
|
|
326
|
+
})
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
330
|
+
width: "80",
|
|
331
|
+
height: "8",
|
|
332
|
+
viewBox: "0 0 80 8",
|
|
333
|
+
children: /* @__PURE__ */ jsx("line", {
|
|
334
|
+
x1: "0",
|
|
335
|
+
y1: "4",
|
|
336
|
+
x2: "80",
|
|
337
|
+
y2: "4",
|
|
338
|
+
stroke: "currentColor",
|
|
339
|
+
strokeWidth: "2",
|
|
340
|
+
strokeDasharray: border.kind === "dashed" ? border.dash.join(" ") : void 0
|
|
341
|
+
})
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function BorderSelect({ value, styles, onChange }) {
|
|
345
|
+
const label = useLabel();
|
|
346
|
+
const options = BORDER_OPTS.filter((option) => optionOf(styles, option.make().kind));
|
|
347
|
+
return /* @__PURE__ */ jsx(DropdownShell, {
|
|
348
|
+
trigger: /* @__PURE__ */ jsx("span", {
|
|
349
|
+
className: "text-fg-secondary",
|
|
350
|
+
children: borderSvg(value)
|
|
351
|
+
}),
|
|
352
|
+
children: (close) => options.map((option) => {
|
|
353
|
+
const border = option.make();
|
|
354
|
+
return /* @__PURE__ */ jsx(OptionRow, {
|
|
355
|
+
selected: borderKey(value) === option.key,
|
|
356
|
+
title: label(optionOf(styles, border.kind)),
|
|
357
|
+
onClick: () => {
|
|
358
|
+
onChange(border);
|
|
359
|
+
close();
|
|
360
|
+
},
|
|
361
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
362
|
+
className: "text-fg-secondary",
|
|
363
|
+
children: borderSvg(border)
|
|
364
|
+
})
|
|
365
|
+
}, option.key);
|
|
366
|
+
})
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
const LE_MARKERS = {
|
|
370
|
+
square: /* @__PURE__ */ jsx("path", { d: "M68 -4 L76 -4 L76 4 L68 4 Z" }),
|
|
371
|
+
circle: /* @__PURE__ */ jsx("circle", {
|
|
372
|
+
cx: "72",
|
|
373
|
+
cy: "0",
|
|
374
|
+
r: "4"
|
|
375
|
+
}),
|
|
376
|
+
diamond: /* @__PURE__ */ jsx("path", { d: "M72 -5 L77 0 L72 5 L67 0 Z" }),
|
|
377
|
+
"open-arrow": /* @__PURE__ */ jsx("path", {
|
|
378
|
+
d: "M67 -5 L77 0 L67 5",
|
|
379
|
+
fill: "none"
|
|
380
|
+
}),
|
|
381
|
+
"closed-arrow": /* @__PURE__ */ jsx("path", { d: "M67 -5 L77 0 L67 5 Z" }),
|
|
382
|
+
"r-open-arrow": /* @__PURE__ */ jsx("path", {
|
|
383
|
+
d: "M77 -5 L67 0 L77 5",
|
|
384
|
+
fill: "none"
|
|
385
|
+
}),
|
|
386
|
+
"r-closed-arrow": /* @__PURE__ */ jsx("path", { d: "M77 -5 L67 0 L77 5 Z" }),
|
|
387
|
+
butt: /* @__PURE__ */ jsx("path", {
|
|
388
|
+
d: "M72 -5 L72 5",
|
|
389
|
+
fill: "none"
|
|
390
|
+
}),
|
|
391
|
+
slash: /* @__PURE__ */ jsx("path", {
|
|
392
|
+
d: "M67 -5 L77 5",
|
|
393
|
+
fill: "none"
|
|
394
|
+
})
|
|
395
|
+
};
|
|
396
|
+
const LE_LINE_END = {
|
|
397
|
+
square: 68,
|
|
398
|
+
circle: 68,
|
|
399
|
+
diamond: 67,
|
|
400
|
+
"open-arrow": 76,
|
|
401
|
+
"closed-arrow": 67,
|
|
402
|
+
"r-open-arrow": 67,
|
|
403
|
+
"r-closed-arrow": 67,
|
|
404
|
+
butt: 72,
|
|
405
|
+
slash: 72
|
|
406
|
+
};
|
|
407
|
+
function LineEndingPreview({ name, side }) {
|
|
408
|
+
const marker = LE_MARKERS[name];
|
|
409
|
+
const lineEndX = LE_LINE_END[name] ?? 77;
|
|
410
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
411
|
+
width: "80",
|
|
412
|
+
height: "20",
|
|
413
|
+
viewBox: "0 0 80 20",
|
|
414
|
+
className: "text-fg",
|
|
415
|
+
children: /* @__PURE__ */ jsxs("g", {
|
|
416
|
+
transform: side === "start" ? "rotate(180 40 10)" : void 0,
|
|
417
|
+
children: [/* @__PURE__ */ jsx("line", {
|
|
418
|
+
x1: "4",
|
|
419
|
+
y1: "10",
|
|
420
|
+
x2: lineEndX,
|
|
421
|
+
y2: "10",
|
|
422
|
+
stroke: "currentColor",
|
|
423
|
+
strokeWidth: "1.5"
|
|
424
|
+
}), marker && /* @__PURE__ */ jsx("g", {
|
|
425
|
+
transform: "translate(0, 10)",
|
|
426
|
+
fill: "currentColor",
|
|
427
|
+
stroke: "currentColor",
|
|
428
|
+
strokeWidth: "1.5",
|
|
429
|
+
children: marker
|
|
430
|
+
})]
|
|
431
|
+
})
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
function LineEndingSelect({ side, value, options, onChange }) {
|
|
435
|
+
const label = useLabel();
|
|
436
|
+
return /* @__PURE__ */ jsx(DropdownShell, {
|
|
437
|
+
trigger: /* @__PURE__ */ jsx(LineEndingPreview, {
|
|
438
|
+
name: value,
|
|
439
|
+
side
|
|
440
|
+
}),
|
|
441
|
+
children: (close) => options.map((option) => /* @__PURE__ */ jsx(OptionRow, {
|
|
442
|
+
selected: value === option.value,
|
|
443
|
+
title: label(option),
|
|
444
|
+
onClick: () => {
|
|
445
|
+
onChange(option.value);
|
|
446
|
+
close();
|
|
447
|
+
},
|
|
448
|
+
children: /* @__PURE__ */ jsx(LineEndingPreview, {
|
|
449
|
+
name: option.value,
|
|
450
|
+
side
|
|
451
|
+
})
|
|
452
|
+
}, option.value))
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function FontFamilySelect({ value, standard, onChange }) {
|
|
456
|
+
const label = useLabel();
|
|
457
|
+
const configured = useAnnotationFonts();
|
|
458
|
+
const options = [...standard.filter((option) => OFFERED_FONTS.includes(option.value)).map((option) => ({
|
|
459
|
+
value: option.value,
|
|
460
|
+
label: label(option)
|
|
461
|
+
})), ...configured.map((font) => ({
|
|
462
|
+
value: font.key,
|
|
463
|
+
label: font.label
|
|
464
|
+
}))];
|
|
465
|
+
const named = optionOf(standard, value);
|
|
466
|
+
return /* @__PURE__ */ jsx(DropdownShell, {
|
|
467
|
+
trigger: /* @__PURE__ */ jsx("span", {
|
|
468
|
+
className: "text-fg text-sm",
|
|
469
|
+
children: options.find((option) => option.value === value)?.label ?? (named ? label(named) : value)
|
|
470
|
+
}),
|
|
471
|
+
children: (close) => options.map((option) => /* @__PURE__ */ jsx(OptionRow, {
|
|
472
|
+
selected: option.value === value,
|
|
473
|
+
onClick: () => {
|
|
474
|
+
onChange(option.value);
|
|
475
|
+
close();
|
|
476
|
+
},
|
|
477
|
+
children: option.label
|
|
478
|
+
}, option.value))
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
function FontSizeCombo({ value, onChange }) {
|
|
482
|
+
const [open, setOpen] = useState(false);
|
|
483
|
+
const rootRef = useOutsideClose(open, () => setOpen(false));
|
|
484
|
+
const [draft, setDraft] = useState(null);
|
|
485
|
+
const commit = () => {
|
|
486
|
+
if (draft === null) return;
|
|
487
|
+
const size = parseInt(draft, 10);
|
|
488
|
+
setDraft(null);
|
|
489
|
+
if (Number.isFinite(size) && size > 0 && size !== value) onChange(size);
|
|
490
|
+
};
|
|
491
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
492
|
+
ref: rootRef,
|
|
493
|
+
className: "relative w-full",
|
|
494
|
+
children: [
|
|
495
|
+
/* @__PURE__ */ jsx("input", {
|
|
496
|
+
type: "number",
|
|
497
|
+
min: 1,
|
|
498
|
+
value: draft ?? value,
|
|
499
|
+
onChange: (event) => setDraft(event.target.value),
|
|
500
|
+
onBlur: commit,
|
|
501
|
+
onKeyDown: (event) => {
|
|
502
|
+
if (event.key === "Enter") {
|
|
503
|
+
event.preventDefault();
|
|
504
|
+
commit();
|
|
505
|
+
setOpen(false);
|
|
506
|
+
} else if (event.key === "Escape") {
|
|
507
|
+
setDraft(null);
|
|
508
|
+
setOpen(false);
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
onClick: () => setOpen(true),
|
|
512
|
+
className: "border-border bg-surface text-fg w-full rounded border px-2 py-1.5 pe-7 text-sm"
|
|
513
|
+
}),
|
|
514
|
+
/* @__PURE__ */ jsx("button", {
|
|
515
|
+
type: "button",
|
|
516
|
+
tabIndex: -1,
|
|
517
|
+
onClick: () => setOpen((previous) => !previous),
|
|
518
|
+
className: "absolute inset-y-0 end-1 flex items-center",
|
|
519
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
520
|
+
name: "chevronDown",
|
|
521
|
+
size: 16,
|
|
522
|
+
className: "text-fg-secondary"
|
|
523
|
+
})
|
|
524
|
+
}),
|
|
525
|
+
open && /* @__PURE__ */ jsx("div", {
|
|
526
|
+
className: "border-border bg-elevated absolute z-10 mt-1 max-h-48 w-full overflow-y-auto rounded border p-1 shadow-lg",
|
|
527
|
+
children: FONT_SIZES.map((sz) => /* @__PURE__ */ jsx(OptionRow, {
|
|
528
|
+
selected: sz === value,
|
|
529
|
+
onClick: () => {
|
|
530
|
+
setDraft(null);
|
|
531
|
+
onChange(sz);
|
|
532
|
+
setOpen(false);
|
|
533
|
+
},
|
|
534
|
+
children: sz
|
|
535
|
+
}, sz))
|
|
536
|
+
})
|
|
537
|
+
]
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
function BlendModeSelect({ value, options, name, onChange }) {
|
|
541
|
+
const label = useLabel();
|
|
542
|
+
return /* @__PURE__ */ jsx("select", {
|
|
543
|
+
value,
|
|
544
|
+
"aria-label": name,
|
|
545
|
+
onChange: (event) => onChange(event.target.value),
|
|
546
|
+
className: "border-border bg-surface text-fg w-full rounded border px-3 py-1.5 text-sm",
|
|
547
|
+
children: options.map((mode) => /* @__PURE__ */ jsx("option", {
|
|
548
|
+
value: mode.value,
|
|
549
|
+
children: label(mode)
|
|
550
|
+
}, mode.value))
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
function Toggle({ active, title, onClick, keepFocus, children }) {
|
|
554
|
+
return /* @__PURE__ */ jsx("button", {
|
|
555
|
+
type: "button",
|
|
556
|
+
title,
|
|
557
|
+
onClick,
|
|
558
|
+
onMouseDown: keepFocus ? (event) => event.preventDefault() : void 0,
|
|
559
|
+
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"}`,
|
|
560
|
+
children
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
const isFormatSpec = (spec) => spec.key === "bold" || spec.key === "italic" || spec.key === "underline";
|
|
564
|
+
/**
|
|
565
|
+
* The format toggles a free-text kind declares, in one row. While the text
|
|
566
|
+
* editor holds a range they read and write that range's runs (the plugin
|
|
567
|
+
* routes `toggleTextFormat`); otherwise the annotation's body. The buttons
|
|
568
|
+
* keep focus in the editor so the range survives the click.
|
|
569
|
+
*/
|
|
570
|
+
function FormatToggles({ specs, values, mixed, onToggle }) {
|
|
571
|
+
const t = useT();
|
|
572
|
+
const label = useLabel();
|
|
573
|
+
return /* @__PURE__ */ jsx(Field, {
|
|
574
|
+
label: t("demo.formatLabel"),
|
|
575
|
+
mixed: specs.some((spec) => mixed.includes(spec.key)),
|
|
576
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
577
|
+
className: "flex gap-2",
|
|
578
|
+
children: specs.map((spec) => {
|
|
579
|
+
const active = values[spec.key] === true && !mixed.includes(spec.key);
|
|
580
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
581
|
+
title: label(spec),
|
|
582
|
+
active,
|
|
583
|
+
keepFocus: true,
|
|
584
|
+
onClick: () => onToggle(spec.key, !active),
|
|
585
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
586
|
+
name: spec.key,
|
|
587
|
+
size: 18
|
|
588
|
+
})
|
|
589
|
+
}, spec.key);
|
|
590
|
+
})
|
|
591
|
+
})
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
function FieldControl({ spec, values, mixed, onChange, onChangeEach }) {
|
|
595
|
+
const t = useT();
|
|
596
|
+
const label = useLabel();
|
|
597
|
+
const value = values[spec.key];
|
|
598
|
+
switch (spec.key) {
|
|
599
|
+
case "color":
|
|
600
|
+
case "fontColor": return /* @__PURE__ */ jsx(Field, {
|
|
601
|
+
label: label(spec),
|
|
602
|
+
mixed,
|
|
603
|
+
children: /* @__PURE__ */ jsx(SwatchGrid, {
|
|
604
|
+
value,
|
|
605
|
+
onSelect: (color) => onChange({ [spec.key]: color })
|
|
606
|
+
})
|
|
607
|
+
});
|
|
608
|
+
case "interiorColor": return /* @__PURE__ */ jsx(Field, {
|
|
609
|
+
label: label(spec),
|
|
610
|
+
mixed,
|
|
611
|
+
children: /* @__PURE__ */ jsx(SwatchGrid, {
|
|
612
|
+
value,
|
|
613
|
+
allowTransparent: true,
|
|
614
|
+
onSelect: (color) => onChange({ interiorColor: color === "transparent" ? null : color })
|
|
615
|
+
})
|
|
616
|
+
});
|
|
617
|
+
case "opacity": {
|
|
618
|
+
const opacity = value ?? 1;
|
|
619
|
+
return /* @__PURE__ */ jsxs(Field, {
|
|
620
|
+
label: label(spec),
|
|
621
|
+
mixed,
|
|
622
|
+
children: [/* @__PURE__ */ jsx(RangeSlider, {
|
|
623
|
+
value: opacity,
|
|
624
|
+
min: spec.min,
|
|
625
|
+
max: spec.max,
|
|
626
|
+
step: spec.step,
|
|
627
|
+
onChange: (next) => onChange({ opacity: next })
|
|
628
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
629
|
+
className: "text-fg-muted text-xs",
|
|
630
|
+
children: [Math.round(opacity * 100), "%"]
|
|
631
|
+
})]
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
case "strokeWidth": {
|
|
635
|
+
const strokeWidth = value ?? spec.min;
|
|
636
|
+
return /* @__PURE__ */ jsxs(Field, {
|
|
637
|
+
label: label(spec),
|
|
638
|
+
mixed,
|
|
639
|
+
children: [/* @__PURE__ */ jsx(RangeSlider, {
|
|
640
|
+
value: strokeWidth,
|
|
641
|
+
min: spec.min,
|
|
642
|
+
max: spec.max,
|
|
643
|
+
step: spec.step,
|
|
644
|
+
onChange: (next) => onChange({ strokeWidth: next })
|
|
645
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
646
|
+
className: "text-fg-muted text-xs",
|
|
647
|
+
children: `${strokeWidth}px`
|
|
648
|
+
})]
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
case "fontSize": return /* @__PURE__ */ jsx(Field, {
|
|
652
|
+
label: label(spec),
|
|
653
|
+
mixed,
|
|
654
|
+
children: /* @__PURE__ */ jsx(FontSizeCombo, {
|
|
655
|
+
value: value ?? 12,
|
|
656
|
+
onChange: (next) => onChange({ fontSize: next })
|
|
657
|
+
})
|
|
658
|
+
});
|
|
659
|
+
case "borderStyle": return /* @__PURE__ */ jsx(Field, {
|
|
660
|
+
label: label(spec),
|
|
661
|
+
mixed,
|
|
662
|
+
children: /* @__PURE__ */ jsx(BorderSelect, {
|
|
663
|
+
value: borderOf(values),
|
|
664
|
+
styles: spec.options,
|
|
665
|
+
onChange: (border) => onChange(borderFields(border))
|
|
666
|
+
})
|
|
667
|
+
});
|
|
668
|
+
case "lineEndings": {
|
|
669
|
+
const le = value ?? {
|
|
670
|
+
start: "none",
|
|
671
|
+
end: "none"
|
|
672
|
+
};
|
|
673
|
+
return /* @__PURE__ */ jsx(Field, {
|
|
674
|
+
label: label(spec),
|
|
675
|
+
mixed,
|
|
676
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
677
|
+
className: "grid grid-cols-2 gap-3",
|
|
678
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
679
|
+
className: "text-fg-muted mb-1.5 text-xs",
|
|
680
|
+
children: t("stylePanel.lineStart")
|
|
681
|
+
}), /* @__PURE__ */ jsx(LineEndingSelect, {
|
|
682
|
+
side: "start",
|
|
683
|
+
value: le.start,
|
|
684
|
+
options: spec.options,
|
|
685
|
+
onChange: (ending) => onChangeEach((current) => ({ lineEndings: {
|
|
686
|
+
...current.lineEndings,
|
|
687
|
+
start: ending
|
|
688
|
+
} }))
|
|
689
|
+
})] }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
690
|
+
className: "text-fg-muted mb-1.5 text-xs",
|
|
691
|
+
children: t("stylePanel.lineEnd")
|
|
692
|
+
}), /* @__PURE__ */ jsx(LineEndingSelect, {
|
|
693
|
+
side: "end",
|
|
694
|
+
value: le.end,
|
|
695
|
+
options: spec.options,
|
|
696
|
+
onChange: (ending) => onChangeEach((current) => ({ lineEndings: {
|
|
697
|
+
...current.lineEndings,
|
|
698
|
+
end: ending
|
|
699
|
+
} }))
|
|
700
|
+
})] })]
|
|
701
|
+
})
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
case "fontFamily": return /* @__PURE__ */ jsx(Field, {
|
|
705
|
+
label: label(spec),
|
|
706
|
+
mixed,
|
|
707
|
+
children: /* @__PURE__ */ jsx(FontFamilySelect, {
|
|
708
|
+
value: value ?? "helvetica",
|
|
709
|
+
standard: spec.options,
|
|
710
|
+
onChange: (family) => onChange({ fontFamily: family })
|
|
711
|
+
})
|
|
712
|
+
});
|
|
713
|
+
case "textAlign": {
|
|
714
|
+
const alignIcon = {
|
|
715
|
+
left: "alignLeft",
|
|
716
|
+
center: "alignCenter",
|
|
717
|
+
right: "alignRight"
|
|
718
|
+
};
|
|
719
|
+
return /* @__PURE__ */ jsx(Field, {
|
|
720
|
+
label: label(spec),
|
|
721
|
+
mixed,
|
|
722
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
723
|
+
className: "flex gap-2",
|
|
724
|
+
children: spec.options.map((option) => {
|
|
725
|
+
const al = option.value;
|
|
726
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
727
|
+
title: label(option),
|
|
728
|
+
active: value === al,
|
|
729
|
+
onClick: () => onChange({ textAlign: al }),
|
|
730
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
731
|
+
name: alignIcon[al],
|
|
732
|
+
size: 18
|
|
733
|
+
})
|
|
734
|
+
}, al);
|
|
735
|
+
})
|
|
736
|
+
})
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
case "bold":
|
|
740
|
+
case "italic":
|
|
741
|
+
case "underline": return null;
|
|
742
|
+
case "blendMode": return /* @__PURE__ */ jsx(Field, {
|
|
743
|
+
label: label(spec),
|
|
744
|
+
mixed,
|
|
745
|
+
children: /* @__PURE__ */ jsx(BlendModeSelect, {
|
|
746
|
+
value: value ?? "normal",
|
|
747
|
+
options: spec.options,
|
|
748
|
+
name: label(spec),
|
|
749
|
+
onChange: (blendMode) => onChange({ blendMode })
|
|
750
|
+
})
|
|
751
|
+
});
|
|
752
|
+
case "link": return null;
|
|
753
|
+
default: return null;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function EmptyState() {
|
|
757
|
+
const t = useT();
|
|
758
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
759
|
+
className: "text-fg-muted flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center",
|
|
760
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
761
|
+
name: "palette",
|
|
762
|
+
size: 40,
|
|
763
|
+
className: "text-fg-disabled"
|
|
764
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
765
|
+
className: "max-w-[180px] text-sm",
|
|
766
|
+
children: t("stylePanel.empty")
|
|
767
|
+
})]
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
/** The rich body a format on or off is, merged over a tool's current one. */
|
|
771
|
+
const FORMAT_BODY = {
|
|
772
|
+
bold: (on) => ({ weight: on ? 700 : 400 }),
|
|
773
|
+
italic: (on) => ({ italic: on }),
|
|
774
|
+
underline: (on) => ({ decoration: on ? ["underline"] : [] })
|
|
775
|
+
};
|
|
776
|
+
/**
|
|
777
|
+
* The schema-driven style panel. Rendered inside the right sidebar's
|
|
778
|
+
* `annotation-style` surface (see ui/panels.tsx). Owns its own scroll.
|
|
779
|
+
*/
|
|
780
|
+
function AnnotationStylePanel() {
|
|
781
|
+
const t = useT();
|
|
782
|
+
const annotation = useAnnotation();
|
|
783
|
+
const commands = useCommands();
|
|
784
|
+
const documentId = useDocumentId() ?? void 0;
|
|
785
|
+
const activeToolId = useInteractionState((state) => state.activeToolId) ?? "pointer";
|
|
786
|
+
const selection = useAnnotationProperties();
|
|
787
|
+
const tool = useAnnotationProperties(activeToolId);
|
|
788
|
+
const toolDefaults = useAnnotationDefaults(activeToolId);
|
|
789
|
+
const selected = useAnnotationState((state) => state.selected);
|
|
790
|
+
const toolLabel = useKernelValue(() => commands.resolveCommand(`tool:${activeToolId}`, documentId)?.label ?? null, Object.is);
|
|
791
|
+
const hasSel = selection.properties.length > 0;
|
|
792
|
+
const { properties, values, mixed } = hasSel ? selection : tool;
|
|
793
|
+
const fields = properties.filter((property) => property.control !== "flag" && property.control !== "text");
|
|
794
|
+
const write = (patch) => hasSel ? annotation.selection.update(patch) : annotation.tools.updateDefaults(activeToolId, patch);
|
|
795
|
+
const writeEach = (patchOf) => hasSel ? annotation.selection.update((member) => patchOf(member)) : annotation.tools.updateDefaults(activeToolId, patchOf(toolDefaults));
|
|
796
|
+
const toggleFormat = (format, on) => {
|
|
797
|
+
if (hasSel) return void annotation.text.toggleFormat(format);
|
|
798
|
+
const body = toolDefaults.richText?.body;
|
|
799
|
+
annotation.tools.updateDefaults(activeToolId, { richText: { body: {
|
|
800
|
+
...body,
|
|
801
|
+
...FORMAT_BODY[format](on)
|
|
802
|
+
} } });
|
|
803
|
+
};
|
|
804
|
+
if (fields.length === 0 && selected.length === 0) return /* @__PURE__ */ jsx(EmptyState, {});
|
|
805
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
806
|
+
className: "flex-1 overflow-y-auto p-4",
|
|
807
|
+
children: [
|
|
808
|
+
/* @__PURE__ */ jsx("p", {
|
|
809
|
+
className: "text-fg-muted mb-4 text-[11px] font-semibold uppercase tracking-wide",
|
|
810
|
+
children: hasSel ? t("stylePanel.selected", { params: { count: selected.length } }) : toolLabel === null ? t("stylePanel.defaults") : t("stylePanel.toolDefaults", { params: { tool: toolLabel } })
|
|
811
|
+
}),
|
|
812
|
+
fields.map((spec) => spec.key === "bold" ? /* @__PURE__ */ jsx(FormatToggles, {
|
|
813
|
+
specs: fields.filter((spec) => isFormatSpec(spec)),
|
|
814
|
+
values,
|
|
815
|
+
mixed,
|
|
816
|
+
onToggle: toggleFormat
|
|
817
|
+
}, "format") : /* @__PURE__ */ jsx(FieldControl, {
|
|
818
|
+
spec,
|
|
819
|
+
values,
|
|
820
|
+
mixed: mixed.includes(spec.key),
|
|
821
|
+
onChange: write,
|
|
822
|
+
onChangeEach: writeEach
|
|
823
|
+
}, spec.key)),
|
|
824
|
+
/* @__PURE__ */ jsx(RedactionLabelSection, {}),
|
|
825
|
+
/* @__PURE__ */ jsx(AnnotationFlagsSection, {})
|
|
826
|
+
]
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Label editor shown when exactly one redaction mark is selected. The text is
|
|
831
|
+
* what the destructive apply paints over the region (white-on-black by the
|
|
832
|
+
* tool defaults); `repeat` tiles it. Writes ride the annotation update verb
|
|
833
|
+
* via the redaction plugin, which preserves the label's `/DA` styling.
|
|
834
|
+
*/
|
|
835
|
+
function RedactionLabelSection() {
|
|
836
|
+
const t = useT();
|
|
837
|
+
const redaction = useOptionalCapability(RedactionToken);
|
|
838
|
+
const selected = useAnnotationState((state) => state.selected);
|
|
839
|
+
const mark = selected.length === 1 && selected[0].subtype === "redact" ? selected[0] : null;
|
|
840
|
+
const [draft, setDraft] = useState(null);
|
|
841
|
+
useEffect(() => setDraft(null), [mark?.ref && annotationKey(mark.ref)]);
|
|
842
|
+
if (!redaction || !mark || mark.subtype !== "redact") return null;
|
|
843
|
+
const value = draft ?? mark.overlayText ?? "";
|
|
844
|
+
const commit = () => {
|
|
845
|
+
if (draft === null) return;
|
|
846
|
+
redaction.updateLabel(mark.ref, { overlayText: draft.length > 0 ? draft : null });
|
|
847
|
+
setDraft(null);
|
|
848
|
+
};
|
|
849
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
850
|
+
className: "border-border-subtle mt-4 border-t pt-4",
|
|
851
|
+
children: [
|
|
852
|
+
/* @__PURE__ */ jsx("p", {
|
|
853
|
+
className: "text-fg-muted mb-2 text-[11px] font-semibold uppercase tracking-wide",
|
|
854
|
+
children: t("demo.redactLabelTitle")
|
|
855
|
+
}),
|
|
856
|
+
/* @__PURE__ */ jsx("input", {
|
|
857
|
+
type: "text",
|
|
858
|
+
value,
|
|
859
|
+
placeholder: t("demo.redactLabelPlaceholder"),
|
|
860
|
+
onChange: (event) => setDraft(event.target.value),
|
|
861
|
+
onBlur: commit,
|
|
862
|
+
onKeyDown: (event) => event.key === "Enter" && commit(),
|
|
863
|
+
className: "border-border bg-surface text-fg w-full rounded-md border px-2 py-1.5 text-sm"
|
|
864
|
+
}),
|
|
865
|
+
/* @__PURE__ */ jsxs("label", {
|
|
866
|
+
className: "text-fg mt-2 flex items-center gap-2 text-sm",
|
|
867
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
868
|
+
type: "checkbox",
|
|
869
|
+
checked: mark.repeat,
|
|
870
|
+
onChange: (event) => void redaction.updateLabel(mark.ref, { repeat: event.target.checked })
|
|
871
|
+
}), t("demo.redactLabelRepeat")]
|
|
872
|
+
})
|
|
873
|
+
]
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
//#endregion
|
|
877
|
+
export { AnnotationStylePanel };
|
|
878
|
+
|
|
879
|
+
//# sourceMappingURL=annotation-style--rPIZsuP.js.map
|