kviewer 0.0.1 → 0.0.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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kviewer",
3
3
  "configKey": "kviewer",
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -31,10 +31,12 @@ export declare class Painter {
31
31
  private tempDataTransfer;
32
32
  private callbacks;
33
33
  private getStylusModeEnabled?;
34
- constructor({ userName, callbacks, getStylusModeEnabled, }: {
34
+ private getReadonly?;
35
+ constructor({ userName, callbacks, getStylusModeEnabled, getReadonly, }: {
35
36
  userName: string;
36
37
  callbacks: PainterCallbacks;
37
38
  getStylusModeEnabled?: () => boolean;
39
+ getReadonly?: () => boolean;
38
40
  });
39
41
  private editFreeText;
40
42
  private bindGlobalEvents;
@@ -33,14 +33,17 @@ export class Painter {
33
33
  tempDataTransfer = null;
34
34
  callbacks;
35
35
  getStylusModeEnabled;
36
+ getReadonly;
36
37
  constructor({
37
38
  userName,
38
39
  callbacks,
39
- getStylusModeEnabled
40
+ getStylusModeEnabled,
41
+ getReadonly
40
42
  }) {
41
43
  this.userName = userName;
42
44
  this.callbacks = callbacks;
43
45
  this.getStylusModeEnabled = getStylusModeEnabled;
46
+ this.getReadonly = getReadonly;
44
47
  this.store = new Store();
45
48
  this.selector = new Selector({
46
49
  konvaCanvasStore: this.konvaCanvasStore,
@@ -110,6 +113,7 @@ export class Painter {
110
113
  window.addEventListener("keyup", this.globalKeyUpHandler);
111
114
  }
112
115
  globalKeyUpHandler = (e) => {
116
+ if (this.getReadonly?.()) return;
113
117
  if (e.code === "Escape" && this.currentAnnotation && (this.currentAnnotation.type === AnnotationType.SIGNATURE || this.currentAnnotation.type === AnnotationType.STAMP)) {
114
118
  hideCursorPreview();
115
119
  this.callbacks.forceReset();
@@ -65,7 +65,7 @@ const state = useViewerState();
65
65
  const colors = defaultOptions.colors;
66
66
  const isMultiSelect = computed(() => state.selectedAnnotations.value.length > 1);
67
67
  const hasSelection = computed(
68
- () => state.selectedAnnotation.value !== null || state.selectedAnnotations.value.length > 0
68
+ () => !state.readonly.value && (state.selectedAnnotation.value !== null || state.selectedAnnotations.value.length > 0)
69
69
  );
70
70
  const selectedDef = computed(() => {
71
71
  const ann = state.selectedAnnotation.value;
@@ -82,12 +82,18 @@ const isAnnotating = computed(() => {
82
82
  return tool === "hand" || tool === "marquee" || tool === "eraser" || typeof tool === "number" && tool !== AnnotationType.NONE;
83
83
  });
84
84
  const isSearchTextInteractive = computed(() => search.isOpen.value && state.activeTool.value === "hand");
85
- const annotationPointerEvents = computed(() => isSearchTextInteractive.value ? "none" : isAnnotating.value ? "auto" : "none");
85
+ const annotationPointerEvents = computed(() => {
86
+ if (state.readonly.value) return "none";
87
+ return isSearchTextInteractive.value ? "none" : isAnnotating.value ? "auto" : "none";
88
+ });
86
89
  const isUsingAnnotationTool = computed(() => {
87
90
  const tool = state.activeTool.value;
88
91
  return tool === "marquee" || tool === "eraser" || typeof tool === "number" && tool !== AnnotationType.NONE;
89
92
  });
90
- const formFieldPointerEvents = computed(() => isUsingAnnotationTool.value ? "none" : "auto");
93
+ const formFieldPointerEvents = computed(() => {
94
+ if (state.readonly.value) return "none";
95
+ return isUsingAnnotationTool.value ? "none" : "auto";
96
+ });
91
97
  const formFieldZIndex = computed(() => isUsingAnnotationTool.value ? 2 : 4);
92
98
  onMounted(async () => {
93
99
  pageProxy.value = await proxyCache.getPage(props.pageNumber);
@@ -9,6 +9,8 @@ type __VLS_Props = {
9
9
  signatureHandlers?: SignatureHandlers;
10
10
  viewMode?: ViewMode;
11
11
  zoom?: number;
12
+ /** When true, the viewer is in view-only mode: annotations, drawing tools, and form editing are disabled. */
13
+ readonly?: boolean;
12
14
  /** When false, global keyboard shortcuts (e.g. Cmd+F) are suppressed. Used by ViewerTabs to prevent hidden viewers from capturing input. */
13
15
  active?: boolean;
14
16
  };
@@ -96,11 +96,11 @@
96
96
  <!-- Footer slot: empty by default -->
97
97
  <slot name="footer" />
98
98
 
99
- <ClientOnly>
99
+ <ClientOnly v-if="!viewerState.readonly.value">
100
100
  <AnnotationToolbar />
101
101
  </ClientOnly>
102
102
 
103
- <ClientOnly>
103
+ <ClientOnly v-if="!viewerState.readonly.value">
104
104
  <FreeTextModal
105
105
  v-model:open="freeTextModalOpen"
106
106
  :default-color="freeTextDefaults.color"
@@ -119,6 +119,7 @@ import {
119
119
  shallowRef,
120
120
  computed,
121
121
  watch,
122
+ watchEffect,
122
123
  nextTick,
123
124
  triggerRef,
124
125
  onMounted,
@@ -150,11 +151,15 @@ const props = defineProps({
150
151
  signatureHandlers: { type: Object, required: false, default: void 0 },
151
152
  viewMode: { type: String, required: false, default: "fit-width" },
152
153
  zoom: { type: Number, required: false, default: 1 },
154
+ readonly: { type: Boolean, required: false, default: false },
153
155
  active: { type: Boolean, required: false, default: true }
154
156
  });
155
157
  const viewerRoot = ref(null);
156
158
  const scrollContainer = ref(null);
157
159
  const { state: viewerState, setScrollToPageFn } = provideViewerState();
160
+ watchEffect(() => {
161
+ viewerState.readonly.value = props.readonly ?? false;
162
+ });
158
163
  const viewerSearch = provideViewerSearch();
159
164
  const formFieldsState = provideFormFields();
160
165
  const pageSettings = providePageSettings(viewerRoot);
@@ -9,6 +9,8 @@ type __VLS_Props = {
9
9
  signatureHandlers?: SignatureHandlers;
10
10
  viewMode?: ViewMode;
11
11
  zoom?: number;
12
+ /** When true, the viewer is in view-only mode: annotations, drawing tools, and form editing are disabled. */
13
+ readonly?: boolean;
12
14
  /** When false, global keyboard shortcuts (e.g. Cmd+F) are suppressed. Used by ViewerTabs to prevent hidden viewers from capturing input. */
13
15
  active?: boolean;
14
16
  };
@@ -24,10 +24,12 @@
24
24
 
25
25
  <ZoomControls />
26
26
 
27
- <USeparator orientation="vertical" class="h-5" color="neutral" />
27
+ <template v-if="!state.readonly.value">
28
+ <USeparator orientation="vertical" class="h-5" color="neutral" />
28
29
 
29
- <HandTool />
30
- <MarqueeTool />
30
+ <HandTool />
31
+ <MarqueeTool />
32
+ </template>
31
33
 
32
34
  <div class="ml-auto flex items-center gap-2">
33
35
  <PageInfo />
@@ -36,7 +38,7 @@
36
38
  </div>
37
39
 
38
40
  <!-- Tool bar -->
39
- <div class="grid grid-cols-3 items-center h-11 px-3 gap-2 bg-elevated">
41
+ <div v-if="!state.readonly.value" class="grid grid-cols-3 items-center h-11 px-3 gap-2 bg-elevated">
40
42
  <div class="justify-self-start">
41
43
  <ToolProperties @style-updated="bumpStyleVersion" />
42
44
  </div>
@@ -36,6 +36,8 @@ type __VLS_Props = {
36
36
  signatureHandlers?: SignatureHandlers;
37
37
  /** Enable text layer on all Viewer instances. */
38
38
  textLayer?: boolean;
39
+ /** When true, all viewers are in view-only mode. */
40
+ readonly?: boolean;
39
41
  /** Default view mode for tabs without a per-tab override. */
40
42
  viewMode?: ViewMode;
41
43
  /** Default zoom for tabs without a per-tab override. */
@@ -56,6 +56,7 @@
56
56
  :signature-handlers="signatureHandlers"
57
57
  :view-mode="tab.viewMode ?? viewMode"
58
58
  :zoom="tab.zoom ?? zoom"
59
+ :readonly="readonly"
59
60
  >
60
61
  <template v-if="$slots.header" #header>
61
62
  <slot name="header" :tab="tab" />
@@ -84,6 +85,7 @@ const props = defineProps({
84
85
  userName: { type: String, required: false, default: void 0 },
85
86
  signatureHandlers: { type: Object, required: false, default: void 0 },
86
87
  textLayer: { type: Boolean, required: false },
88
+ readonly: { type: Boolean, required: false },
87
89
  viewMode: { type: String, required: false, default: "fit-width" },
88
90
  zoom: { type: Number, required: false, default: 1 },
89
91
  minTabs: { type: Number, required: false, default: 0 }
@@ -36,6 +36,8 @@ type __VLS_Props = {
36
36
  signatureHandlers?: SignatureHandlers;
37
37
  /** Enable text layer on all Viewer instances. */
38
38
  textLayer?: boolean;
39
+ /** When true, all viewers are in view-only mode. */
40
+ readonly?: boolean;
39
41
  /** Default view mode for tabs without a per-tab override. */
40
42
  viewMode?: ViewMode;
41
43
  /** Default zoom for tabs without a per-tab override. */
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <button
3
3
  type="button"
4
- :disabled="props.field.readOnly"
4
+ :disabled="isReadOnly"
5
5
  :style="buttonStyle"
6
6
  class="kviewer-form-button"
7
7
  @click="onClick"
@@ -13,10 +13,13 @@
13
13
  <script setup>
14
14
  import { computed } from "vue";
15
15
  import { useFormFields } from "../../composables/useFormFields";
16
+ import { useViewerState } from "../../composables/useViewerState";
16
17
  const props = defineProps({
17
18
  field: { type: Object, required: true }
18
19
  });
19
20
  const formFields = useFormFields();
21
+ const state = useViewerState();
22
+ const isReadOnly = computed(() => state.readonly.value || props.field.readOnly);
20
23
  const buttonStyle = computed(() => {
21
24
  const style = {};
22
25
  if (props.field.fontSize && props.field.fontSize > 0) {
@@ -3,7 +3,7 @@
3
3
  <input
4
4
  type="checkbox"
5
5
  :checked="isChecked"
6
- :disabled="props.field.readOnly"
6
+ :disabled="isReadOnly"
7
7
  :required="props.field.required"
8
8
  @change="onChange"
9
9
  >
@@ -13,10 +13,13 @@
13
13
  <script setup>
14
14
  import { computed } from "vue";
15
15
  import { useFormFields } from "../../composables/useFormFields";
16
+ import { useViewerState } from "../../composables/useViewerState";
16
17
  const props = defineProps({
17
18
  field: { type: Object, required: true }
18
19
  });
19
20
  const formFields = useFormFields();
21
+ const state = useViewerState();
22
+ const isReadOnly = computed(() => state.readonly.value || props.field.readOnly);
20
23
  const isChecked = computed(() => {
21
24
  const fv = formFields.getFieldValue(props.field.id);
22
25
  return fv?.value === true;
@@ -4,7 +4,7 @@
4
4
  <input
5
5
  :value="currentValue"
6
6
  :list="datalistId"
7
- :disabled="props.field.readOnly"
7
+ :disabled="isReadOnly"
8
8
  :required="props.field.required"
9
9
  :style="fieldStyle"
10
10
  class="kviewer-form-input"
@@ -25,7 +25,7 @@
25
25
  <select
26
26
  v-else-if="!props.field.combo"
27
27
  :value="currentValue"
28
- :disabled="props.field.readOnly"
28
+ :disabled="isReadOnly"
29
29
  :required="props.field.required"
30
30
  :multiple="props.field.multiSelect"
31
31
  :size="listBoxSize"
@@ -46,7 +46,7 @@
46
46
  <select
47
47
  v-else
48
48
  :value="currentValue"
49
- :disabled="props.field.readOnly"
49
+ :disabled="isReadOnly"
50
50
  :required="props.field.required"
51
51
  :style="fieldStyle"
52
52
  class="kviewer-form-input kviewer-form-select"
@@ -68,10 +68,13 @@
68
68
  <script setup>
69
69
  import { computed } from "vue";
70
70
  import { useFormFields } from "../../composables/useFormFields";
71
+ import { useViewerState } from "../../composables/useViewerState";
71
72
  const props = defineProps({
72
73
  field: { type: Object, required: true }
73
74
  });
74
75
  const formFields = useFormFields();
76
+ const state = useViewerState();
77
+ const isReadOnly = computed(() => state.readonly.value || props.field.readOnly);
75
78
  const datalistId = computed(() => `kviewer-datalist-${props.field.id}`);
76
79
  const listBoxSize = computed(() => {
77
80
  const optCount = props.field.options?.length ?? 0;
@@ -5,7 +5,7 @@
5
5
  :name="props.field.fieldName"
6
6
  :value="props.field.buttonValue"
7
7
  :checked="isSelected"
8
- :disabled="props.field.readOnly"
8
+ :disabled="isReadOnly"
9
9
  :required="props.field.required"
10
10
  @change="onChange"
11
11
  >
@@ -15,10 +15,13 @@
15
15
  <script setup>
16
16
  import { computed } from "vue";
17
17
  import { useFormFields } from "../../composables/useFormFields";
18
+ import { useViewerState } from "../../composables/useViewerState";
18
19
  const props = defineProps({
19
20
  field: { type: Object, required: true }
20
21
  });
21
22
  const formFields = useFormFields();
23
+ const state = useViewerState();
24
+ const isReadOnly = computed(() => state.readonly.value || props.field.readOnly);
22
25
  const isSelected = computed(() => {
23
26
  const fv = formFields.getFieldValue(props.field.id);
24
27
  if (!fv) return false;
@@ -32,7 +32,7 @@ const signatureUrl = computed(() => {
32
32
  });
33
33
  const hasSigned = computed(() => !!signatureUrl.value);
34
34
  async function onClickSign() {
35
- if (props.field.readOnly) return;
35
+ if (props.field.readOnly || state.readonly.value) return;
36
36
  if (state.signatures.value.length > 0) {
37
37
  const sig = state.signatures.value[0];
38
38
  if (sig) {
@@ -3,7 +3,7 @@
3
3
  v-if="props.field.multiLine"
4
4
  :value="currentValue"
5
5
  :maxlength="props.field.maxLen || void 0"
6
- :readonly="props.field.readOnly"
6
+ :readonly="isReadOnly"
7
7
  :required="props.field.required"
8
8
  :style="fieldStyle"
9
9
  class="kviewer-form-input"
@@ -14,7 +14,7 @@
14
14
  :type="props.field.password ? 'password' : 'text'"
15
15
  :value="currentValue"
16
16
  :maxlength="props.field.maxLen || void 0"
17
- :readonly="props.field.readOnly"
17
+ :readonly="isReadOnly"
18
18
  :required="props.field.required"
19
19
  :style="fieldStyle"
20
20
  class="kviewer-form-input"
@@ -25,10 +25,13 @@
25
25
  <script setup>
26
26
  import { computed } from "vue";
27
27
  import { useFormFields } from "../../composables/useFormFields";
28
+ import { useViewerState } from "../../composables/useViewerState";
28
29
  const props = defineProps({
29
30
  field: { type: Object, required: true }
30
31
  });
31
32
  const formFields = useFormFields();
33
+ const state = useViewerState();
34
+ const isReadOnly = computed(() => state.readonly.value || props.field.readOnly);
32
35
  const currentValue = computed(() => {
33
36
  const fv = formFields.getFieldValue(props.field.id);
34
37
  return typeof fv?.value === "string" ? fv.value : "";
@@ -63,7 +63,8 @@ export function createAnnotationEngine(viewerState, options) {
63
63
  const painter = new Painter({
64
64
  userName: options.userName ?? "User",
65
65
  callbacks,
66
- getStylusModeEnabled: () => viewerState.stylusMode.value
66
+ getStylusModeEnabled: () => viewerState.stylusMode.value,
67
+ getReadonly: () => viewerState.readonly.value
67
68
  });
68
69
  viewerState.painter.value = painter;
69
70
  return painter;
@@ -53,6 +53,7 @@ export interface ViewerState {
53
53
  setStylusMode: (enabled: boolean) => void;
54
54
  scrollToPage: (pageNumber: number) => void;
55
55
  history: AnnotationHistory;
56
+ readonly: Ref<boolean>;
56
57
  }
57
58
  export interface ViewerStateProvider {
58
59
  state: ViewerState;
@@ -62,6 +62,7 @@ export function provideViewerState() {
62
62
  if (prev !== void 0) setScale(prev);
63
63
  }
64
64
  }
65
+ const readonly = ref(false);
65
66
  const stylusMode = ref(false);
66
67
  function setStylusMode(enabled) {
67
68
  stylusMode.value = enabled;
@@ -91,6 +92,7 @@ export function provideViewerState() {
91
92
  if (activeSignature.value?.id === id) activeSignature.value = null;
92
93
  }
93
94
  function selectTool(tool, dataTransfer) {
95
+ if (readonly.value && tool !== "hand") return;
94
96
  activeTool.value = tool;
95
97
  selectedAnnotation.value = null;
96
98
  selectedAnnotations.value = [];
@@ -113,6 +115,7 @@ export function provideViewerState() {
113
115
  }
114
116
  }
115
117
  function deleteAnnotation(id) {
118
+ if (readonly.value) return;
116
119
  if (painter.value) {
117
120
  painter.value.delete(id, true);
118
121
  annotations.value.delete(id);
@@ -168,7 +171,8 @@ export function provideViewerState() {
168
171
  stylusMode,
169
172
  setStylusMode,
170
173
  scrollToPage,
171
- history
174
+ history,
175
+ readonly
172
176
  };
173
177
  provide(VIEWER_STATE_KEY, state);
174
178
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kviewer",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Kabema PDF Editor",
5
5
  "repository": "kabema/kviewer",
6
6
  "license": "MIT",