kviewer 0.2.0 → 0.2.2
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 +47 -0
- package/dist/module.d.mts +8 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +25 -3
- package/dist/runtime/annotation/engine/canvas-resolution.d.ts +7 -0
- package/dist/runtime/annotation/engine/canvas-resolution.js +21 -0
- package/dist/runtime/annotation/engine/editor/editor.d.ts +5 -0
- package/dist/runtime/annotation/engine/editor/editor.js +14 -0
- package/dist/runtime/annotation/engine/editor/selector.d.ts +13 -1
- package/dist/runtime/annotation/engine/editor/selector.js +60 -8
- package/dist/runtime/annotation/engine/painter.d.ts +31 -0
- package/dist/runtime/annotation/engine/painter.js +88 -2
- package/dist/runtime/annotation/engine/store.js +3 -0
- package/dist/runtime/annotation/engine/tools/arrow.js +0 -1
- package/dist/runtime/annotation/engine/tools/circle.js +0 -2
- package/dist/runtime/annotation/engine/tools/free-highlight.js +0 -2
- package/dist/runtime/annotation/engine/tools/freehand.js +0 -2
- package/dist/runtime/annotation/engine/tools/rectangle.js +0 -2
- package/dist/runtime/annotation/engine/types.d.ts +5 -0
- package/dist/runtime/annotation/font-style.d.ts +15 -0
- package/dist/runtime/annotation/font-style.js +19 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +8 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +24 -1
- package/dist/runtime/annotation/pdf-export/annotation-flags.d.ts +8 -0
- package/dist/runtime/annotation/pdf-export/annotation-flags.js +5 -0
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +3 -1
- package/dist/runtime/annotation/pdf-export/export.js +13 -1
- package/dist/runtime/annotation/pdf-export/parse_circle.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_freetext.js +31 -19
- package/dist/runtime/annotation/pdf-export/parse_highlight.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_ink.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_line.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_polyline.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_square.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_stamp.js +4 -2
- package/dist/runtime/annotation/pdf-export/parse_strikeout.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_text.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_underline.js +3 -0
- package/dist/runtime/annotation/pdf-export/unicode-font.d.ts +5 -0
- package/dist/runtime/annotation/pdf-export/unicode-font.js +31 -0
- package/dist/runtime/annotation/pdf-export/winansi.d.ts +17 -0
- package/dist/runtime/annotation/pdf-export/winansi.js +77 -0
- package/dist/runtime/annotation/pdf-import/decode_circle.js +0 -1
- package/dist/runtime/annotation/pdf-import/decode_ink.js +0 -1
- package/dist/runtime/annotation/pdf-import/decode_line.js +0 -1
- package/dist/runtime/annotation/pdf-import/decode_square.js +0 -1
- package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.d.ts +12 -0
- package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +17 -4
- package/dist/runtime/annotation/pdf-import/types.d.ts +2 -0
- package/dist/runtime/assets/fonts/LICENSE-LiberationSans +102 -0
- package/dist/runtime/assets/fonts/LiberationSans-Regular.ttf +0 -0
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/AnnotationToolbar.vue +6 -4
- package/dist/runtime/components/PdfPage.d.vue.ts +16 -0
- package/dist/runtime/components/PdfPage.vue +170 -9
- package/dist/runtime/components/PdfPage.vue.d.ts +16 -0
- package/dist/runtime/components/PerfHud.d.vue.ts +3 -0
- package/dist/runtime/components/PerfHud.vue +97 -0
- package/dist/runtime/components/PerfHud.vue.d.ts +3 -0
- package/dist/runtime/components/ToolButton.vue +4 -1
- package/dist/runtime/components/Viewer.d.vue.ts +37 -2
- package/dist/runtime/components/Viewer.vue +208 -13
- package/dist/runtime/components/Viewer.vue.d.ts +37 -2
- package/dist/runtime/components/ViewerBar.d.vue.ts +21 -1
- package/dist/runtime/components/ViewerBar.vue +183 -101
- package/dist/runtime/components/ViewerBar.vue.d.ts +21 -1
- package/dist/runtime/components/ViewerTabs.d.vue.ts +10 -2
- package/dist/runtime/components/ViewerTabs.vue +13 -2
- package/dist/runtime/components/ViewerTabs.vue.d.ts +10 -2
- package/dist/runtime/components/form-fields/FormDropdown.vue +50 -19
- package/dist/runtime/components/form-fields/FormSignatureField.vue +4 -2
- package/dist/runtime/components/form-fields/FormTextField.vue +7 -18
- package/dist/runtime/components/modals/FreeTextModal.vue +6 -4
- package/dist/runtime/components/modals/SignatureDrawModal.vue +6 -4
- package/dist/runtime/components/panels/PlacedFieldSidebar.vue +67 -50
- package/dist/runtime/components/panels/SignaturePicker.vue +6 -4
- package/dist/runtime/components/panels/StampPicker.vue +3 -1
- package/dist/runtime/components/tools/AnnotationToolButton.d.vue.ts +10 -0
- package/dist/runtime/components/tools/AnnotationToolButton.vue +33 -0
- package/dist/runtime/components/tools/AnnotationToolButton.vue.d.ts +10 -0
- package/dist/runtime/components/tools/MarqueeTool.vue +3 -1
- package/dist/runtime/components/tools/PageSettings.vue +13 -11
- package/dist/runtime/components/tools/SearchTool.vue +12 -10
- package/dist/runtime/components/tools/ToolProperties.vue +7 -5
- package/dist/runtime/components/tools/ToolbarMenu.d.vue.ts +9 -0
- package/dist/runtime/components/tools/ToolbarMenu.vue +79 -0
- package/dist/runtime/components/tools/ToolbarMenu.vue.d.ts +9 -0
- package/dist/runtime/components/tools/ZoomControls.vue +17 -9
- package/dist/runtime/composables/canvas-limits.d.ts +22 -0
- package/dist/runtime/composables/canvas-limits.js +10 -0
- package/dist/runtime/composables/useFormFields.d.ts +6 -0
- package/dist/runtime/composables/useFormFields.js +28 -0
- package/dist/runtime/composables/useInertiaPanzoom.d.ts +10 -0
- package/dist/runtime/composables/useInertiaPanzoom.js +57 -10
- package/dist/runtime/composables/useKviewerI18n.d.ts +18 -0
- package/dist/runtime/composables/useKviewerI18n.js +29 -0
- package/dist/runtime/composables/usePageVirtualization.d.ts +11 -0
- package/dist/runtime/composables/usePageVirtualization.js +40 -0
- package/dist/runtime/composables/useScriptingManager.d.ts +8 -4
- package/dist/runtime/composables/useScriptingManager.js +2 -0
- package/dist/runtime/composables/useViewerState.d.ts +4 -0
- package/dist/runtime/composables/useViewerState.js +8 -0
- package/dist/runtime/embed/bridge-client.d.ts +9 -1
- package/dist/runtime/embed/bridge-client.js +13 -0
- package/dist/runtime/embed/bridge-host.d.ts +3 -0
- package/dist/runtime/embed/bridge-host.js +30 -8
- package/dist/runtime/embed/protocol.d.ts +21 -0
- package/dist/runtime/i18n/messages.d.ts +252 -0
- package/dist/runtime/i18n/messages.js +249 -0
- package/dist/runtime/public-types.d.ts +2 -0
- package/dist/runtime/toolbar-tools.d.ts +54 -0
- package/dist/runtime/toolbar-tools.js +79 -0
- package/dist/types.d.mts +1 -1
- package/package.json +14 -31
|
@@ -1,117 +1,128 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex flex-col shrink-0 border-b border-default">
|
|
3
|
-
<!--
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
<UButton
|
|
10
|
-
icon="i-lucide-download"
|
|
11
|
-
label="Download"
|
|
12
|
-
variant="ghost"
|
|
13
|
-
color="neutral"
|
|
14
|
-
size="xs"
|
|
15
|
-
class="w-full justify-start"
|
|
16
|
-
data-testid="viewer-download"
|
|
17
|
-
@click="onDownload"
|
|
18
|
-
/>
|
|
19
|
-
<UButton
|
|
20
|
-
:icon="state.shapeDetection.value ? 'i-lucide-square-check-big' : 'i-lucide-square-dashed'"
|
|
21
|
-
:label="state.shapeDetection.value ? 'Disable form field detection' : 'Detect form fields'"
|
|
22
|
-
:variant="state.shapeDetection.value ? 'soft' : 'ghost'"
|
|
23
|
-
:color="state.shapeDetection.value ? 'primary' : 'neutral'"
|
|
24
|
-
size="xs"
|
|
25
|
-
class="w-full justify-start"
|
|
26
|
-
data-testid="viewer-toggle-shape-detection"
|
|
27
|
-
@click="state.shapeDetection.value = !state.shapeDetection.value"
|
|
28
|
-
/>
|
|
29
|
-
<!-- Host-supplied menu items appended after the built-ins.
|
|
30
|
-
A small separator visually marks the boundary; we only
|
|
31
|
-
render it when there is at least one custom item. -->
|
|
32
|
-
<template v-if="props.menuItems && props.menuItems.length > 0">
|
|
33
|
-
<div class="my-1 border-t border-default" />
|
|
34
|
-
<template v-for="item in props.menuItems" :key="item.key">
|
|
35
|
-
<div v-if="item.type === 'separator'" class="my-1 border-t border-default" />
|
|
36
|
-
<UButton
|
|
37
|
-
v-else-if="item.type === 'button'"
|
|
38
|
-
:icon="item.icon"
|
|
39
|
-
:label="item.label"
|
|
40
|
-
:disabled="item.disabled"
|
|
41
|
-
variant="ghost"
|
|
42
|
-
color="neutral"
|
|
43
|
-
size="xs"
|
|
44
|
-
class="w-full justify-start"
|
|
45
|
-
:data-testid="`viewer-menu-${item.key}`"
|
|
46
|
-
@click="item.onSelect()"
|
|
47
|
-
/>
|
|
48
|
-
<UButton
|
|
49
|
-
v-else-if="item.type === 'checkbox'"
|
|
50
|
-
:icon="item.icon ?? (item.checked ? 'i-lucide-check-square' : 'i-lucide-square')"
|
|
51
|
-
:label="item.label"
|
|
52
|
-
:variant="item.checked ? 'soft' : 'ghost'"
|
|
53
|
-
:color="item.checked ? 'primary' : 'neutral'"
|
|
54
|
-
:disabled="item.disabled"
|
|
55
|
-
size="xs"
|
|
56
|
-
class="w-full justify-start"
|
|
57
|
-
:data-testid="`viewer-menu-${item.key}`"
|
|
58
|
-
@click="item.onUpdate(!item.checked)"
|
|
59
|
-
/>
|
|
60
|
-
</template>
|
|
61
|
-
</template>
|
|
62
|
-
</div>
|
|
63
|
-
</template>
|
|
64
|
-
</UPopover>
|
|
65
|
-
<PageSettings />
|
|
3
|
+
<!-- Default layout: omitting `tools` renders today's toolbar verbatim. -->
|
|
4
|
+
<template v-if="props.tools === void 0">
|
|
5
|
+
<!-- Top navigation bar -->
|
|
6
|
+
<div class="flex items-center h-[40px] px-3 gap-2 bg-elevated/50">
|
|
7
|
+
<ToolbarMenu :menu-items="props.menuItems" />
|
|
8
|
+
<PageSettings />
|
|
66
9
|
|
|
67
|
-
|
|
10
|
+
<USeparator orientation="vertical" class="h-5" :ui="{ border: 'border-accented' }" />
|
|
68
11
|
|
|
69
|
-
|
|
12
|
+
<ZoomControls />
|
|
70
13
|
|
|
71
|
-
|
|
72
|
-
|
|
14
|
+
<template v-if="!state.readonly.value">
|
|
15
|
+
<USeparator orientation="vertical" class="h-5" :ui="{ border: 'border-accented' }" />
|
|
73
16
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<UButton
|
|
78
|
-
icon="i-lucide-form-input"
|
|
79
|
-
:variant="state.formEditMode.value ? 'soft' : 'ghost'"
|
|
80
|
-
:color="state.formEditMode.value ? 'primary' : 'neutral'"
|
|
81
|
-
size="xs"
|
|
82
|
-
:title="state.formEditMode.value ? 'Exit form-edit mode' : 'Enter form-edit mode'"
|
|
83
|
-
data-testid="form-edit-toggle"
|
|
84
|
-
@click="state.setFormEditMode(!state.formEditMode.value)"
|
|
85
|
-
/>
|
|
86
|
-
</template>
|
|
17
|
+
<HandTool />
|
|
18
|
+
<MarqueeTool />
|
|
19
|
+
</template>
|
|
87
20
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
21
|
+
<div class="ml-auto flex items-center gap-2">
|
|
22
|
+
<PageInfo />
|
|
23
|
+
<SearchTool />
|
|
24
|
+
</div>
|
|
91
25
|
</div>
|
|
92
|
-
</div>
|
|
93
26
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
27
|
+
<!-- Tool bar -->
|
|
28
|
+
<div v-if="!state.readonly.value" class="grid grid-cols-3 items-center h-[44px] px-3 gap-2 bg-elevated">
|
|
29
|
+
<div class="justify-self-start">
|
|
30
|
+
<ToolProperties @style-updated="bumpStyleVersion" />
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="justify-self-center">
|
|
34
|
+
<FormFieldTools v-if="state.formEditMode.value" />
|
|
35
|
+
<DrawingTools v-else :style-version="styleVersion" />
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="justify-self-end flex items-center gap-1">
|
|
39
|
+
<ActionTools />
|
|
40
|
+
</div>
|
|
98
41
|
</div>
|
|
42
|
+
</template>
|
|
99
43
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
44
|
+
<!-- Config-driven layout: `tools` chooses which buttons render, in what
|
|
45
|
+
order, with custom buttons interleaved via #tool-<name> slots. -->
|
|
46
|
+
<template v-else>
|
|
47
|
+
<!-- Top navigation bar -->
|
|
48
|
+
<div class="flex items-center h-[40px] px-3 gap-2 bg-elevated/50">
|
|
49
|
+
<template v-for="(entry, i) in topEntries" :key="entryKey(entry, i)">
|
|
50
|
+
<USeparator v-if="entry === 'separator'" orientation="vertical" class="h-5" :ui="{ border: 'border-accented' }" />
|
|
51
|
+
<div v-else-if="entry === 'spacer'" class="ml-auto" />
|
|
52
|
+
<ToolbarMenu v-else-if="entry === 'menu'" :menu-items="props.menuItems" />
|
|
53
|
+
<PageSettings v-else-if="entry === 'pageSettings'" />
|
|
54
|
+
<ZoomControls v-else-if="entry === 'zoom'" />
|
|
55
|
+
<HandTool v-else-if="entry === 'hand' && isEnabled('hand')" />
|
|
56
|
+
<MarqueeTool v-else-if="entry === 'marquee' && isEnabled('marquee')" />
|
|
57
|
+
<PageInfo v-else-if="entry === 'pageInfo'" />
|
|
58
|
+
<SearchTool v-else-if="entry === 'search'" />
|
|
59
|
+
<slot v-else-if="isSlot(entry)" :name="slotName(entry)" :state="state" />
|
|
60
|
+
</template>
|
|
103
61
|
</div>
|
|
104
62
|
|
|
105
|
-
|
|
106
|
-
|
|
63
|
+
<!-- Tool bar — hidden entirely in readonly, like the default layout.
|
|
64
|
+
Columns are split on `spacer`; three columns lay out as a grid so
|
|
65
|
+
the middle (tools) is centered between properties and actions. -->
|
|
66
|
+
<div
|
|
67
|
+
v-if="!state.readonly.value && bottomEntries.length > 0"
|
|
68
|
+
:class="bottomRowClass"
|
|
69
|
+
>
|
|
70
|
+
<div
|
|
71
|
+
v-for="(segment, si) in bottomSegments"
|
|
72
|
+
:key="`segment-${si}`"
|
|
73
|
+
class="flex items-center gap-1"
|
|
74
|
+
:class="segmentClass(bottomSegments.length, si)"
|
|
75
|
+
>
|
|
76
|
+
<template v-for="(entry, i) in segment" :key="entryKey(entry, i)">
|
|
77
|
+
<USeparator v-if="entry === 'separator'" orientation="vertical" class="h-5" :ui="{ border: 'border-accented' }" />
|
|
78
|
+
<ToolProperties v-else-if="entry === 'properties'" @style-updated="bumpStyleVersion" />
|
|
79
|
+
<StampPicker v-else-if="entry === 'stamp' && modeOk('stamp')" />
|
|
80
|
+
<SignaturePicker v-else-if="entry === 'signature' && modeOk('signature') && state.signatureHandlers" />
|
|
81
|
+
<UButton
|
|
82
|
+
v-else-if="entry === 'undo'"
|
|
83
|
+
icon="i-lucide-undo-2"
|
|
84
|
+
variant="ghost"
|
|
85
|
+
color="neutral"
|
|
86
|
+
size="sm"
|
|
87
|
+
data-testid="action-undo"
|
|
88
|
+
:disabled="!state.history.canUndo.value"
|
|
89
|
+
@click="state.history.undo"
|
|
90
|
+
/>
|
|
91
|
+
<UButton
|
|
92
|
+
v-else-if="entry === 'redo'"
|
|
93
|
+
icon="i-lucide-redo-2"
|
|
94
|
+
variant="ghost"
|
|
95
|
+
color="neutral"
|
|
96
|
+
size="sm"
|
|
97
|
+
data-testid="action-redo"
|
|
98
|
+
:disabled="!state.history.canRedo.value"
|
|
99
|
+
@click="state.history.redo"
|
|
100
|
+
/>
|
|
101
|
+
<AnnotationToolButton
|
|
102
|
+
v-else-if="annotationButtonType(entry) !== void 0 && modeOk(entry)"
|
|
103
|
+
:type="annotationButtonType(entry)"
|
|
104
|
+
:data-testid="entry === 'eraser' ? 'action-eraser' : `tool-${entry}`"
|
|
105
|
+
/>
|
|
106
|
+
<slot v-else-if="isSlot(entry)" :name="slotName(entry)" :state="state" />
|
|
107
|
+
</template>
|
|
108
|
+
</div>
|
|
107
109
|
</div>
|
|
108
|
-
</
|
|
110
|
+
</template>
|
|
109
111
|
</div>
|
|
110
112
|
</template>
|
|
111
113
|
|
|
112
114
|
<script setup>
|
|
113
|
-
import { ref } from "vue";
|
|
115
|
+
import { computed, provide, ref } from "vue";
|
|
114
116
|
import { useViewerState } from "../composables/useViewerState";
|
|
117
|
+
import {
|
|
118
|
+
ANNOTATION_TOOL_TYPES,
|
|
119
|
+
BOTTOM_TOOL_IDS,
|
|
120
|
+
DRAW_TOOLS,
|
|
121
|
+
FORM_TOOLS,
|
|
122
|
+
READONLY_HIDDEN_TOP,
|
|
123
|
+
STYLE_VERSION_KEY,
|
|
124
|
+
TOP_TOOL_IDS
|
|
125
|
+
} from "../toolbar-tools";
|
|
115
126
|
import PageSettings from "./tools/PageSettings.vue";
|
|
116
127
|
import ZoomControls from "./tools/ZoomControls.vue";
|
|
117
128
|
import HandTool from "./tools/HandTool.vue";
|
|
@@ -122,19 +133,90 @@ import ActionTools from "./tools/ActionTools.vue";
|
|
|
122
133
|
import PageInfo from "./tools/PageInfo.vue";
|
|
123
134
|
import SearchTool from "./tools/SearchTool.vue";
|
|
124
135
|
import ToolProperties from "./tools/ToolProperties.vue";
|
|
136
|
+
import ToolbarMenu from "./tools/ToolbarMenu.vue";
|
|
137
|
+
import AnnotationToolButton from "./tools/AnnotationToolButton.vue";
|
|
138
|
+
import StampPicker from "./panels/StampPicker.vue";
|
|
139
|
+
import SignaturePicker from "./panels/SignaturePicker.vue";
|
|
125
140
|
const props = defineProps({
|
|
126
|
-
menuItems: { type: Array, required: false }
|
|
141
|
+
menuItems: { type: Array, required: false },
|
|
142
|
+
tools: { type: Array, required: false }
|
|
127
143
|
});
|
|
128
144
|
const state = useViewerState();
|
|
129
145
|
const styleVersion = ref(0);
|
|
146
|
+
provide(STYLE_VERSION_KEY, styleVersion);
|
|
130
147
|
function bumpStyleVersion() {
|
|
131
148
|
styleVersion.value += 1;
|
|
132
149
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
150
|
+
function isSlot(entry) {
|
|
151
|
+
return typeof entry === "object" && entry !== null && entry.type === "slot";
|
|
152
|
+
}
|
|
153
|
+
function slotName(entry) {
|
|
154
|
+
if (!isSlot(entry)) return "";
|
|
155
|
+
return `tool-${entry.name ?? entry.key}`;
|
|
156
|
+
}
|
|
157
|
+
function entryKey(entry, index) {
|
|
158
|
+
return `${isSlot(entry) ? `slot-${entry.key}` : entry}-${index}`;
|
|
159
|
+
}
|
|
160
|
+
function homeRow(entry) {
|
|
161
|
+
if (typeof entry !== "string") return null;
|
|
162
|
+
if (TOP_TOOL_IDS.has(entry)) return "top";
|
|
163
|
+
if (BOTTOM_TOOL_IDS.has(entry)) return "bottom";
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const rows = computed(() => {
|
|
167
|
+
const top = [];
|
|
168
|
+
const bottom = [];
|
|
169
|
+
let current = "top";
|
|
170
|
+
for (const entry of props.tools ?? []) {
|
|
171
|
+
const home = homeRow(entry);
|
|
172
|
+
let row;
|
|
173
|
+
if (home) {
|
|
174
|
+
row = home;
|
|
175
|
+
current = home;
|
|
176
|
+
} else if (isSlot(entry) && entry.row) {
|
|
177
|
+
row = entry.row;
|
|
178
|
+
current = entry.row;
|
|
179
|
+
} else {
|
|
180
|
+
row = current;
|
|
181
|
+
}
|
|
182
|
+
(row === "top" ? top : bottom).push(entry);
|
|
183
|
+
}
|
|
184
|
+
return { top, bottom };
|
|
185
|
+
});
|
|
186
|
+
const topEntries = computed(() => rows.value.top);
|
|
187
|
+
const bottomEntries = computed(() => rows.value.bottom);
|
|
188
|
+
const bottomSegments = computed(() => {
|
|
189
|
+
const segments = [[]];
|
|
190
|
+
for (const entry of bottomEntries.value) {
|
|
191
|
+
if (entry === "spacer") segments.push([]);
|
|
192
|
+
else segments[segments.length - 1].push(entry);
|
|
193
|
+
}
|
|
194
|
+
return segments;
|
|
195
|
+
});
|
|
196
|
+
const bottomRowClass = computed(
|
|
197
|
+
() => bottomSegments.value.length === 3 ? "grid grid-cols-3 items-center h-[44px] px-3 gap-2 bg-elevated" : "flex items-center h-[44px] px-3 gap-2 bg-elevated"
|
|
198
|
+
);
|
|
199
|
+
function segmentClass(count, index) {
|
|
200
|
+
if (count === 3) {
|
|
201
|
+
return ["justify-self-start", "justify-self-center", "justify-self-end"][index] ?? "";
|
|
202
|
+
}
|
|
203
|
+
if (count === 2 && index === 1) return "ml-auto";
|
|
204
|
+
return "";
|
|
205
|
+
}
|
|
206
|
+
function isEnabled(id) {
|
|
207
|
+
if (state.readonly.value && READONLY_HIDDEN_TOP.has(id)) return false;
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
function modeOk(id) {
|
|
211
|
+
if (FORM_TOOLS.has(id)) return state.formEditMode.value;
|
|
212
|
+
if (DRAW_TOOLS.has(id)) return !state.formEditMode.value;
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
function annotationButtonType(entry) {
|
|
216
|
+
if (entry === "eraser") return "eraser";
|
|
217
|
+
if (typeof entry === "string" && entry in ANNOTATION_TOOL_TYPES) {
|
|
218
|
+
return ANNOTATION_TOOL_TYPES[entry];
|
|
138
219
|
}
|
|
220
|
+
return void 0;
|
|
139
221
|
}
|
|
140
222
|
</script>
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import type { ViewerMenuItem } from '../menu-items.js';
|
|
2
|
+
import { type ViewerToolEntry } from '../toolbar-tools.js';
|
|
2
3
|
type __VLS_Props = {
|
|
3
4
|
/** Extra items appended to the burger menu after the built-ins.
|
|
4
5
|
* See {@link ViewerMenuItem}. Pure forwarding from `<KViewer>`. */
|
|
5
6
|
menuItems?: ViewerMenuItem[];
|
|
7
|
+
/** Configures which toolbar tools render and in what order. When
|
|
8
|
+
* omitted, the default toolbar is rendered. See {@link ViewerToolEntry}. */
|
|
9
|
+
tools?: ViewerToolEntry[];
|
|
6
10
|
};
|
|
7
|
-
declare
|
|
11
|
+
declare var __VLS_109: string, __VLS_110: {
|
|
12
|
+
state: import("../composables/useViewerState.js").ViewerState;
|
|
13
|
+
}, __VLS_154: string, __VLS_155: {
|
|
14
|
+
state: import("../composables/useViewerState.js").ViewerState;
|
|
15
|
+
};
|
|
16
|
+
type __VLS_Slots = {} & {
|
|
17
|
+
[K in NonNullable<typeof __VLS_109>]?: (props: typeof __VLS_110) => any;
|
|
18
|
+
} & {
|
|
19
|
+
[K in NonNullable<typeof __VLS_154>]?: (props: typeof __VLS_155) => any;
|
|
20
|
+
};
|
|
21
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
22
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
8
23
|
declare const _default: typeof __VLS_export;
|
|
9
24
|
export default _default;
|
|
25
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
26
|
+
new (): {
|
|
27
|
+
$slots: S;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -2,6 +2,7 @@ import { type ComponentPublicInstance } from 'vue';
|
|
|
2
2
|
import type { ViewMode } from '../composables/useViewerState.js';
|
|
3
3
|
import type { StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
4
4
|
import type { ViewerMenuItem } from '../menu-items.js';
|
|
5
|
+
import type { ViewerToolEntry } from '../toolbar-tools.js';
|
|
5
6
|
export interface ViewerTabItem {
|
|
6
7
|
/** Unique identifier for the tab. */
|
|
7
8
|
id: string;
|
|
@@ -62,6 +63,8 @@ type __VLS_Props = {
|
|
|
62
63
|
scripting?: boolean;
|
|
63
64
|
/** Forwarded to every Viewer instance — see KViewer's `menuItems`. */
|
|
64
65
|
menuItems?: ViewerMenuItem[];
|
|
66
|
+
/** Forwarded to every Viewer instance — see KViewer's `tools`. */
|
|
67
|
+
tools?: ViewerToolEntry[];
|
|
65
68
|
};
|
|
66
69
|
declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
67
70
|
id?: string;
|
|
@@ -269,8 +272,12 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
269
272
|
shapeDetection?: boolean | undefined;
|
|
270
273
|
scripting?: boolean | undefined;
|
|
271
274
|
};
|
|
272
|
-
},
|
|
275
|
+
}, __VLS_39: string, __VLS_40: {
|
|
276
|
+
state: import("../composables/useViewerState.js").ViewerState;
|
|
277
|
+
}, __VLS_42: {};
|
|
273
278
|
type __VLS_Slots = {} & {
|
|
279
|
+
[K in NonNullable<typeof __VLS_39>]?: (props: typeof __VLS_40) => any;
|
|
280
|
+
} & {
|
|
274
281
|
'tabs-leading'?: (props: typeof __VLS_7) => any;
|
|
275
282
|
} & {
|
|
276
283
|
'tabs-trailing'?: (props: typeof __VLS_21) => any;
|
|
@@ -279,7 +286,7 @@ type __VLS_Slots = {} & {
|
|
|
279
286
|
} & {
|
|
280
287
|
footer?: (props: typeof __VLS_35) => any;
|
|
281
288
|
} & {
|
|
282
|
-
empty?: (props: typeof
|
|
289
|
+
empty?: (props: typeof __VLS_42) => any;
|
|
283
290
|
};
|
|
284
291
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
285
292
|
addTab: typeof addTab;
|
|
@@ -402,6 +409,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
402
409
|
userName: string;
|
|
403
410
|
zoom: number;
|
|
404
411
|
menuItems: ViewerMenuItem[];
|
|
412
|
+
tools: ViewerToolEntry[];
|
|
405
413
|
scripting: boolean;
|
|
406
414
|
stamps: StampDefinition[];
|
|
407
415
|
signatureHandlers: SignatureHandlers;
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
:shape-detection="activeTab.shapeDetection ?? shapeDetection"
|
|
62
62
|
:scripting="activeTab.scripting ?? scripting"
|
|
63
63
|
:menu-items="menuItems"
|
|
64
|
+
:tools="tools"
|
|
64
65
|
:role-colors="roleColors"
|
|
65
66
|
:active-role-id="activeRoleId"
|
|
66
67
|
@update:active-role-id="(v) => emit('update:activeRoleId', v)"
|
|
@@ -71,6 +72,11 @@
|
|
|
71
72
|
<template v-if="$slots.footer" #footer>
|
|
72
73
|
<slot name="footer" :tab="activeTab" />
|
|
73
74
|
</template>
|
|
75
|
+
<!-- Forward custom #tool-* slots so tabs consumers can inject
|
|
76
|
+
custom toolbar buttons; tabs-*/header/footer/empty untouched. -->
|
|
77
|
+
<template v-for="name in toolSlotNames" :key="name" #[name]="scope">
|
|
78
|
+
<slot :name="name" v-bind="scope" />
|
|
79
|
+
</template>
|
|
74
80
|
</Viewer>
|
|
75
81
|
</div>
|
|
76
82
|
|
|
@@ -83,7 +89,7 @@
|
|
|
83
89
|
</template>
|
|
84
90
|
|
|
85
91
|
<script setup>
|
|
86
|
-
import { ref, computed, watch, nextTick } from "vue";
|
|
92
|
+
import { ref, computed, watch, nextTick, useSlots } from "vue";
|
|
87
93
|
import Viewer from "./Viewer.vue";
|
|
88
94
|
const props = defineProps({
|
|
89
95
|
items: { type: Array, required: true },
|
|
@@ -100,8 +106,13 @@ const props = defineProps({
|
|
|
100
106
|
roleColors: { type: Object, required: false, default: void 0 },
|
|
101
107
|
activeRoleId: { type: [String, null], required: false, default: void 0 },
|
|
102
108
|
scripting: { type: Boolean, required: false, default: false },
|
|
103
|
-
menuItems: { type: Array, required: false, default: void 0 }
|
|
109
|
+
menuItems: { type: Array, required: false, default: void 0 },
|
|
110
|
+
tools: { type: Array, required: false, default: void 0 }
|
|
104
111
|
});
|
|
112
|
+
const slots = useSlots();
|
|
113
|
+
const toolSlotNames = computed(
|
|
114
|
+
() => Object.keys(slots).filter((name) => name.startsWith("tool-"))
|
|
115
|
+
);
|
|
105
116
|
const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "update:activeRoleId"]);
|
|
106
117
|
let idCounter = 0;
|
|
107
118
|
function generateId() {
|
|
@@ -2,6 +2,7 @@ import { type ComponentPublicInstance } from 'vue';
|
|
|
2
2
|
import type { ViewMode } from '../composables/useViewerState.js';
|
|
3
3
|
import type { StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
4
4
|
import type { ViewerMenuItem } from '../menu-items.js';
|
|
5
|
+
import type { ViewerToolEntry } from '../toolbar-tools.js';
|
|
5
6
|
export interface ViewerTabItem {
|
|
6
7
|
/** Unique identifier for the tab. */
|
|
7
8
|
id: string;
|
|
@@ -62,6 +63,8 @@ type __VLS_Props = {
|
|
|
62
63
|
scripting?: boolean;
|
|
63
64
|
/** Forwarded to every Viewer instance — see KViewer's `menuItems`. */
|
|
64
65
|
menuItems?: ViewerMenuItem[];
|
|
66
|
+
/** Forwarded to every Viewer instance — see KViewer's `tools`. */
|
|
67
|
+
tools?: ViewerToolEntry[];
|
|
65
68
|
};
|
|
66
69
|
declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
67
70
|
id?: string;
|
|
@@ -269,8 +272,12 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
269
272
|
shapeDetection?: boolean | undefined;
|
|
270
273
|
scripting?: boolean | undefined;
|
|
271
274
|
};
|
|
272
|
-
},
|
|
275
|
+
}, __VLS_39: string, __VLS_40: {
|
|
276
|
+
state: import("../composables/useViewerState.js").ViewerState;
|
|
277
|
+
}, __VLS_42: {};
|
|
273
278
|
type __VLS_Slots = {} & {
|
|
279
|
+
[K in NonNullable<typeof __VLS_39>]?: (props: typeof __VLS_40) => any;
|
|
280
|
+
} & {
|
|
274
281
|
'tabs-leading'?: (props: typeof __VLS_7) => any;
|
|
275
282
|
} & {
|
|
276
283
|
'tabs-trailing'?: (props: typeof __VLS_21) => any;
|
|
@@ -279,7 +286,7 @@ type __VLS_Slots = {} & {
|
|
|
279
286
|
} & {
|
|
280
287
|
footer?: (props: typeof __VLS_35) => any;
|
|
281
288
|
} & {
|
|
282
|
-
empty?: (props: typeof
|
|
289
|
+
empty?: (props: typeof __VLS_42) => any;
|
|
283
290
|
};
|
|
284
291
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
285
292
|
addTab: typeof addTab;
|
|
@@ -402,6 +409,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
402
409
|
userName: string;
|
|
403
410
|
zoom: number;
|
|
404
411
|
menuItems: ViewerMenuItem[];
|
|
412
|
+
tools: ViewerToolEntry[];
|
|
405
413
|
scripting: boolean;
|
|
406
414
|
stamps: StampDefinition[];
|
|
407
415
|
signatureHandlers: SignatureHandlers;
|
|
@@ -42,31 +42,33 @@
|
|
|
42
42
|
</option>
|
|
43
43
|
</select>
|
|
44
44
|
|
|
45
|
-
<!-- Standard dropdown combo box
|
|
46
|
-
|
|
45
|
+
<!-- Standard dropdown combo box. Rendered with Nuxt UI's USelect so the
|
|
46
|
+
option list is portalled to <body> and shows at a readable size,
|
|
47
|
+
independent of the page-zoom transform — a native <select> popup is
|
|
48
|
+
drawn by the OS and can't be styled (issue #71). The closed trigger
|
|
49
|
+
keeps the field's look and height-fitted font via the
|
|
50
|
+
.kviewer-form-uselect class + inline fieldStyle. -->
|
|
51
|
+
<USelect
|
|
47
52
|
v-else
|
|
48
|
-
:
|
|
53
|
+
:items="selectItems"
|
|
54
|
+
:model-value="currentValue"
|
|
49
55
|
:disabled="isReadOnly"
|
|
50
56
|
:required="props.field.required"
|
|
57
|
+
placeholder="—"
|
|
58
|
+
variant="none"
|
|
59
|
+
color="neutral"
|
|
60
|
+
size="md"
|
|
61
|
+
trailing-icon="i-lucide-chevron-down"
|
|
51
62
|
:style="fieldStyle"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
—
|
|
57
|
-
</option>
|
|
58
|
-
<option
|
|
59
|
-
v-for="opt in props.field.options ?? []"
|
|
60
|
-
:key="opt.exportValue"
|
|
61
|
-
:value="opt.exportValue"
|
|
62
|
-
>
|
|
63
|
-
{{ opt.displayValue }}
|
|
64
|
-
</option>
|
|
65
|
-
</select>
|
|
63
|
+
:ui="selectUi"
|
|
64
|
+
class="kviewer-form-uselect"
|
|
65
|
+
@update:model-value="onSelectChange"
|
|
66
|
+
/>
|
|
66
67
|
</template>
|
|
67
68
|
|
|
68
69
|
<script setup>
|
|
69
70
|
import { computed } from "vue";
|
|
71
|
+
import { computeFieldFontSize, decodeFontName } from "../../annotation/font-style";
|
|
70
72
|
import { useFormFields } from "../../composables/useFormFields";
|
|
71
73
|
import { useViewerState } from "../../composables/useViewerState";
|
|
72
74
|
const props = defineProps({
|
|
@@ -78,10 +80,25 @@ const isReadOnly = computed(
|
|
|
78
80
|
() => state.readonly.value || props.field.readOnly || formFields.isFieldLocked(props.field.id)
|
|
79
81
|
);
|
|
80
82
|
const datalistId = computed(() => `kviewer-datalist-${props.field.id}`);
|
|
83
|
+
const isStandardCombo = computed(() => props.field.combo && !props.field.editable);
|
|
81
84
|
const listBoxSize = computed(() => {
|
|
82
85
|
const optCount = props.field.options?.length ?? 0;
|
|
83
86
|
return Math.max(2, optCount);
|
|
84
87
|
});
|
|
88
|
+
const CLEAR_VALUE = "__kviewer_clear__";
|
|
89
|
+
const selectItems = computed(() => {
|
|
90
|
+
const opts = (props.field.options ?? []).map((o) => ({
|
|
91
|
+
label: o.displayValue,
|
|
92
|
+
value: o.exportValue
|
|
93
|
+
}));
|
|
94
|
+
if (!props.field.required) opts.unshift({ label: "\u2014", value: CLEAR_VALUE });
|
|
95
|
+
return opts;
|
|
96
|
+
});
|
|
97
|
+
const selectUi = {
|
|
98
|
+
trailing: "pe-0.5",
|
|
99
|
+
trailingIcon: "size-2",
|
|
100
|
+
content: "min-w-(--reka-select-trigger-width)"
|
|
101
|
+
};
|
|
85
102
|
const currentValue = computed(() => {
|
|
86
103
|
const fv = formFields.getFieldValue(props.field.id);
|
|
87
104
|
if (!fv) return "";
|
|
@@ -90,9 +107,20 @@ const currentValue = computed(() => {
|
|
|
90
107
|
});
|
|
91
108
|
const fieldStyle = computed(() => {
|
|
92
109
|
const style = {};
|
|
93
|
-
|
|
94
|
-
|
|
110
|
+
const rect = props.field.rect;
|
|
111
|
+
const rectHeight = rect[3] - rect[1];
|
|
112
|
+
if (isStandardCombo.value) {
|
|
113
|
+
const declared = props.field.fontSize && props.field.fontSize > 0 ? props.field.fontSize : Infinity;
|
|
114
|
+
const fill = Math.max(6, Math.min(rectHeight - 3, 14));
|
|
115
|
+
style.fontSize = `${Math.round(10 * Math.min(declared, fill)) / 10}px`;
|
|
116
|
+
style.lineHeight = "1";
|
|
117
|
+
} else {
|
|
118
|
+
style.fontSize = `${computeFieldFontSize(props.field.fontSize, rectHeight)}px`;
|
|
95
119
|
}
|
|
120
|
+
const fontStyle = decodeFontName(props.field.fontName);
|
|
121
|
+
style.fontFamily = fontStyle.fontFamily;
|
|
122
|
+
if (fontStyle.fontWeight !== "normal") style.fontWeight = fontStyle.fontWeight;
|
|
123
|
+
if (fontStyle.fontStyle !== "normal") style.fontStyle = fontStyle.fontStyle;
|
|
96
124
|
if (props.field.color && props.field.color.length >= 3) {
|
|
97
125
|
style.color = `rgb(${props.field.color[0]}, ${props.field.color[1]}, ${props.field.color[2]})`;
|
|
98
126
|
}
|
|
@@ -114,4 +142,7 @@ function onInputChange(event) {
|
|
|
114
142
|
const target = event.target;
|
|
115
143
|
formFields.setFieldValue(props.field.id, target.value);
|
|
116
144
|
}
|
|
145
|
+
function onSelectChange(value) {
|
|
146
|
+
formFields.setFieldValue(props.field.id, value === CLEAR_VALUE ? "" : value);
|
|
147
|
+
}
|
|
117
148
|
</script>
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
v-if="signatureUrl"
|
|
9
9
|
:src="signatureUrl"
|
|
10
10
|
class="kviewer-form-signature__preview"
|
|
11
|
-
alt="
|
|
11
|
+
:alt="t('signature')"
|
|
12
12
|
>
|
|
13
13
|
<span v-else class="kviewer-form-signature__placeholder">
|
|
14
|
-
{{ props.field.promptText || "
|
|
14
|
+
{{ props.field.promptText || t("clickToSign") }}
|
|
15
15
|
</span>
|
|
16
16
|
</div>
|
|
17
17
|
|
|
@@ -28,12 +28,14 @@
|
|
|
28
28
|
import { ref, computed } from "vue";
|
|
29
29
|
import { useFormFields } from "../../composables/useFormFields";
|
|
30
30
|
import { useViewerState } from "../../composables/useViewerState";
|
|
31
|
+
import { useKviewerI18n } from "../../composables/useKviewerI18n";
|
|
31
32
|
import SignatureDrawModal from "../modals/SignatureDrawModal.vue";
|
|
32
33
|
const props = defineProps({
|
|
33
34
|
field: { type: Object, required: true }
|
|
34
35
|
});
|
|
35
36
|
const formFields = useFormFields();
|
|
36
37
|
const state = useViewerState();
|
|
38
|
+
const { t } = useKviewerI18n();
|
|
37
39
|
const drawModalOpen = ref(false);
|
|
38
40
|
const signatureUrl = computed(() => {
|
|
39
41
|
const fv = formFields.getFieldValue(props.field.id);
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<script setup>
|
|
27
27
|
import { computed } from "vue";
|
|
28
|
-
import { decodeFontName } from "../../annotation/font-style";
|
|
28
|
+
import { computeFieldFontSize, decodeFontName } from "../../annotation/font-style";
|
|
29
29
|
import { useFormFields } from "../../composables/useFormFields";
|
|
30
30
|
import { useViewerState } from "../../composables/useViewerState";
|
|
31
31
|
const props = defineProps({
|
|
@@ -40,26 +40,15 @@ const currentValue = computed(() => {
|
|
|
40
40
|
const fv = formFields.getFieldValue(props.field.id);
|
|
41
41
|
return typeof fv?.value === "string" ? fv.value : "";
|
|
42
42
|
});
|
|
43
|
-
const FONT_BORDER_SIZE = 2;
|
|
44
|
-
const LINE_HEIGHT_FACTOR = 1.35;
|
|
45
|
-
const DEFAULT_FIELD_FONT_SIZE = 9;
|
|
46
43
|
const fieldStyle = computed(() => {
|
|
47
44
|
const style = {};
|
|
48
|
-
const declared = props.field.fontSize && props.field.fontSize > 0 ? props.field.fontSize : DEFAULT_FIELD_FONT_SIZE;
|
|
49
45
|
const rect = props.field.rect;
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
57
|
-
const lineHeight = rectHeight / numberOfLines;
|
|
58
|
-
computed2 = Math.min(declared, lineHeight / LINE_HEIGHT_FACTOR);
|
|
59
|
-
} else {
|
|
60
|
-
computed2 = Math.min(declared, rectHeight / LINE_HEIGHT_FACTOR);
|
|
61
|
-
}
|
|
62
|
-
style.fontSize = `${Math.round(10 * computed2) / 10}px`;
|
|
46
|
+
const computed2 = computeFieldFontSize(
|
|
47
|
+
props.field.fontSize,
|
|
48
|
+
rect[3] - rect[1],
|
|
49
|
+
{ multiLine: props.field.multiLine }
|
|
50
|
+
);
|
|
51
|
+
style.fontSize = `${computed2}px`;
|
|
63
52
|
const fontStyle = decodeFontName(props.field.fontName);
|
|
64
53
|
style.fontFamily = fontStyle.fontFamily;
|
|
65
54
|
if (fontStyle.fontWeight !== "normal") style.fontWeight = fontStyle.fontWeight;
|