hazo_pdf 1.6.0 → 1.6.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 CHANGED
@@ -890,6 +890,7 @@ You can override any or all of these on a per-highlight basis.
890
890
  | `on_load` | `(pdf: PDFDocumentProxy) => void` | Called when PDF is successfully loaded. Receives the PDF document proxy with metadata (page count, etc.). |
891
891
  | `on_error` | `(error: Error) => void` | Called when an error occurs (PDF load failure, rendering error, etc.). Receives the error object. |
892
892
  | `on_save` | `(pdf_bytes: Uint8Array, filename: string) => void` | Called when user clicks the Save button. Receives the PDF bytes with annotations embedded and a suggested filename. You can create a Blob and trigger download, or upload to a server. |
893
+ | `on_download` | `(filename: string) => void` | Called after the Download button triggers a browser download. Receives the filename used. |
893
894
 
894
895
  ##### Annotation Management
895
896
 
@@ -999,6 +1000,7 @@ Props for managing multiple PDF files. Mutually exclusive with `url` prop.
999
1000
  | `on_file_delete` | `(file_id: string) => void` | `undefined` | Callback when a file is deleted in multi-file mode. Receives file ID. |
1000
1001
  | `on_upload` | `(file: File, converted_pdf?: Uint8Array) => Promise<UploadResult>` | `undefined` | Callback for file upload. Receives original file and optional converted PDF (for non-PDF files). Must return `{ success: boolean, file_id?: string, url?: string, error?: string }`. |
1001
1002
  | `on_files_change` | `(files: FileItem[]) => void` | `undefined` | Callback when files array changes (file added, deleted, or reordered). |
1003
+ | `direct_upload` | `boolean` | `false` | When `true`, clicking "+ Add file" opens the native file picker directly (1-click). The button also accepts drag & drop. When `false` (default), clicking shows a dropzone modal first (2-click). Can also be set via `file_upload.direct_upload` in INI config. |
1002
1004
  | `enable_popout` | `boolean` | `false` | Enable popout to new tab feature. Shows popout button in toolbar. |
1003
1005
  | `popout_route` | `string` | `'/pdf-viewer'` | Route path for popout viewer. Used to construct new tab URL. |
1004
1006
  | `on_popout` | `(context: PopoutContext) => void` | `undefined` | Custom popout handler. Overrides default sessionStorage behavior. Receives context with file info and annotations. |
@@ -1028,7 +1030,10 @@ Props to control toolbar button visibility. These override config file values.
1028
1030
  | `show_metadata_button` | `boolean` | `true` | Show metadata panel button (only visible when `sidepanel_metadata_enabled` is true). |
1029
1031
  | `show_annotate_button` | `boolean` | `true` | Show annotate (FreeText) button. |
1030
1032
  | `show_file_info_button` | `boolean` | `true` | Show file info sidepanel button (only visible when `file_metadata` is provided). |
1033
+ | `show_download_button` | `boolean` | `false` | Show Download button. Downloads the PDF with annotations baked in. |
1031
1034
  | `show_extract_button` | `boolean` | `false` | Show Extract button for data extraction. Requires extraction props to be configured. |
1035
+ | `download_filename` | `string` | `undefined` | Custom filename for the downloaded PDF. Defaults to the original filename or `"document.pdf"`. |
1036
+ | `display_filename` | `string` | `undefined` | Override the displayed filename in the file info sidepanel. Useful when `url` is an API endpoint that produces a non-human-readable path. |
1032
1037
  | `on_close` | `() => void` | `undefined` | Callback when close button is clicked. When provided, shows a close button (X) in the toolbar. Useful for modal/dialog usage. |
1033
1038
 
1034
1039
  **Example - Minimal toolbar:**
@@ -1838,6 +1843,39 @@ interface FileItem {
1838
1843
  - Files can be uploaded as PDFs or non-PDF formats (images, Excel, text files)
1839
1844
  - Non-PDF files are automatically converted to PDF before upload
1840
1845
 
1846
+ ### Direct Upload Mode
1847
+
1848
+ By default, clicking "+ Add file" opens a dropzone modal where the user must click again to browse files (2-click flow). Set `direct_upload={true}` for a faster 1-click flow where clicking "+ Add file" immediately opens the native file picker. The button also doubles as a drag & drop target.
1849
+
1850
+ ```tsx
1851
+ // 1-click upload: click "Add file" → file picker opens immediately
1852
+ <PdfViewer
1853
+ files={files}
1854
+ on_files_change={setFiles}
1855
+ on_upload={handle_upload}
1856
+ direct_upload={true}
1857
+ />
1858
+
1859
+ // Also works with PdfViewerDialog
1860
+ <PdfViewerDialog
1861
+ open={is_open}
1862
+ on_open_change={set_is_open}
1863
+ files={files}
1864
+ on_files_change={setFiles}
1865
+ on_upload={handle_upload}
1866
+ direct_upload={true}
1867
+ />
1868
+ ```
1869
+
1870
+ This is recommended when embedding the viewer in workflows where users already know which file to upload (e.g., row-level file icons in data tables). The drag & drop capability is preserved — dragging files onto the "+ Add file" button shows a "Drop here" indicator.
1871
+
1872
+ Can also be set via INI config:
1873
+
1874
+ ```ini
1875
+ [file_upload]
1876
+ direct_upload = true
1877
+ ```
1878
+
1841
1879
  ---
1842
1880
 
1843
1881
  ## PDF Rotation
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
  import {
3
3
  default_config
4
- } from "./chunk-CXHR3TT6.js";
4
+ } from "./chunk-LTUXHTQF.js";
5
5
  import {
6
6
  __require
7
7
  } from "./chunk-AOSHQP7D.js";
8
8
 
9
9
  // src/components/pdf_viewer/pdf_viewer.tsx
10
- import { useState as useState13, useEffect as useEffect10, useRef as useRef11, useCallback as useCallback4, forwardRef, useImperativeHandle } from "react";
11
- import { Save, Undo2 as Undo22, Redo2, PanelRight, PanelRightOpen, ZoomIn, ZoomOut, RotateCcw, RotateCw, RefreshCw, Square, Type, ExternalLink, Info, Sparkles, X as X5 } from "lucide-react";
10
+ import { useState as useState13, useEffect as useEffect10, useRef as useRef11, useCallback as useCallback5, forwardRef, useImperativeHandle } from "react";
11
+ import { Save, Download, Undo2 as Undo22, Redo2, PanelRight, PanelRightOpen, ZoomIn, ZoomOut, RotateCcw, RotateCw, RefreshCw, Square, Type, ExternalLink, Info, Sparkles, X as X5 } from "lucide-react";
12
12
 
13
13
  // src/components/pdf_viewer/toolbar_dropdown_button.tsx
14
14
  import { useState, useRef, useEffect } from "react";
@@ -2651,6 +2651,10 @@ function build_config_from_ini(get_value) {
2651
2651
  get_value("toolbar", "toolbar_show_extract_button"),
2652
2652
  default_config.toolbar.toolbar_show_extract_button
2653
2653
  ),
2654
+ toolbar_show_download_button: parse_boolean(
2655
+ get_value("toolbar", "toolbar_show_download_button"),
2656
+ default_config.toolbar.toolbar_show_download_button
2657
+ ),
2654
2658
  toolbar_zoom_out_label: parse_string(
2655
2659
  get_value("toolbar", "toolbar_zoom_out_label"),
2656
2660
  default_config.toolbar.toolbar_zoom_out_label
@@ -2754,6 +2758,10 @@ function build_config_from_ini(get_value) {
2754
2758
  dropzone_background_color: parse_color(
2755
2759
  get_value("file_upload", "dropzone_background_color"),
2756
2760
  default_config.file_upload.dropzone_background_color
2761
+ ),
2762
+ direct_upload: parse_boolean(
2763
+ get_value("file_upload", "direct_upload"),
2764
+ default_config.file_upload.direct_upload
2757
2765
  )
2758
2766
  },
2759
2767
  pdf_conversion: {
@@ -2966,10 +2974,10 @@ function get_logger() {
2966
2974
  }
2967
2975
 
2968
2976
  // src/components/file_manager/index.tsx
2969
- import { useState as useState12, useCallback as useCallback3, useEffect as useEffect9 } from "react";
2977
+ import { useState as useState12, useCallback as useCallback4, useEffect as useEffect9 } from "react";
2970
2978
 
2971
2979
  // src/components/file_manager/file_list.tsx
2972
- import { useRef as useRef9, useState as useState9, useEffect as useEffect8 } from "react";
2980
+ import { useRef as useRef9, useState as useState9, useEffect as useEffect8, useCallback as useCallback2 } from "react";
2973
2981
  import { ChevronLeft as ChevronLeft3, ChevronRight as ChevronRight3, Plus } from "lucide-react";
2974
2982
 
2975
2983
  // src/components/file_manager/file_list_item.tsx
@@ -3056,7 +3064,7 @@ var FileListItem = ({
3056
3064
  };
3057
3065
 
3058
3066
  // src/components/file_manager/file_list.tsx
3059
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3067
+ import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3060
3068
  var FileList = ({
3061
3069
  files,
3062
3070
  selected_file_id,
@@ -3064,13 +3072,19 @@ var FileList = ({
3064
3072
  on_select,
3065
3073
  on_delete,
3066
3074
  on_add_click,
3075
+ on_files_selected,
3067
3076
  className
3068
3077
  }) => {
3069
3078
  const scroll_ref = useRef9(null);
3079
+ const file_input_ref = useRef9(null);
3080
+ const drag_counter = useRef9(0);
3070
3081
  const [can_scroll_left, setCanScrollLeft] = useState9(false);
3071
3082
  const [can_scroll_right, setCanScrollRight] = useState9(false);
3083
+ const [is_dragging, setIsDragging] = useState9(false);
3072
3084
  const file_manager_config = config?.file_manager;
3073
3085
  const upload_config = config?.file_upload;
3086
+ const is_direct_upload = !!on_files_selected;
3087
+ const accept_string = upload_config?.allowed_types || "application/pdf";
3074
3088
  const update_scroll_state = () => {
3075
3089
  if (!scroll_ref.current) return;
3076
3090
  const { scrollLeft, scrollWidth, clientWidth } = scroll_ref.current;
@@ -3105,11 +3119,54 @@ var FileList = ({
3105
3119
  });
3106
3120
  }
3107
3121
  }, [selected_file_id]);
3122
+ const handle_add_click = useCallback2(() => {
3123
+ if (is_direct_upload) {
3124
+ file_input_ref.current?.click();
3125
+ } else {
3126
+ on_add_click?.();
3127
+ }
3128
+ }, [is_direct_upload, on_add_click]);
3129
+ const handle_input_change = useCallback2((e) => {
3130
+ if (e.target.files && e.target.files.length > 0) {
3131
+ on_files_selected?.(Array.from(e.target.files));
3132
+ e.target.value = "";
3133
+ }
3134
+ }, [on_files_selected]);
3135
+ const handle_drag_enter = useCallback2((e) => {
3136
+ e.preventDefault();
3137
+ e.stopPropagation();
3138
+ drag_counter.current++;
3139
+ if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
3140
+ setIsDragging(true);
3141
+ }
3142
+ }, []);
3143
+ const handle_drag_leave = useCallback2((e) => {
3144
+ e.preventDefault();
3145
+ e.stopPropagation();
3146
+ drag_counter.current--;
3147
+ if (drag_counter.current === 0) {
3148
+ setIsDragging(false);
3149
+ }
3150
+ }, []);
3151
+ const handle_drag_over = useCallback2((e) => {
3152
+ e.preventDefault();
3153
+ e.stopPropagation();
3154
+ }, []);
3155
+ const handle_drop = useCallback2((e) => {
3156
+ e.preventDefault();
3157
+ e.stopPropagation();
3158
+ setIsDragging(false);
3159
+ drag_counter.current = 0;
3160
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
3161
+ on_files_selected?.(Array.from(e.dataTransfer.files));
3162
+ }
3163
+ }, [on_files_selected]);
3108
3164
  const height = file_manager_config?.file_list_height || 60;
3109
3165
  const background_color = file_manager_config?.file_list_background_color || "#f3f4f6";
3110
3166
  const border_color = file_manager_config?.file_list_border_color || "#e5e7eb";
3111
3167
  const allow_delete = file_manager_config?.allow_delete ?? true;
3112
3168
  const show_add_button = upload_config?.show_add_button ?? true;
3169
+ const has_add_handler = is_direct_upload || !!on_add_click;
3113
3170
  return /* @__PURE__ */ jsxs9(
3114
3171
  "div",
3115
3172
  {
@@ -3153,19 +3210,41 @@ var FileList = ({
3153
3210
  },
3154
3211
  file.id
3155
3212
  )),
3156
- show_add_button && on_add_click && /* @__PURE__ */ jsxs9(
3157
- "button",
3158
- {
3159
- className: "cls_file_list_add_btn",
3160
- onClick: on_add_click,
3161
- "aria-label": "Add file",
3162
- title: "Add file",
3163
- children: [
3164
- /* @__PURE__ */ jsx10(Plus, { size: 16 }),
3165
- /* @__PURE__ */ jsx10("span", { className: "cls_file_list_add_btn_text", children: "Add file" })
3166
- ]
3167
- }
3168
- )
3213
+ show_add_button && has_add_handler && /* @__PURE__ */ jsxs9(Fragment5, { children: [
3214
+ /* @__PURE__ */ jsxs9(
3215
+ "button",
3216
+ {
3217
+ className: cn(
3218
+ "cls_file_list_add_btn",
3219
+ is_dragging && "cls_file_list_add_btn_dragging"
3220
+ ),
3221
+ onClick: handle_add_click,
3222
+ onDragEnter: is_direct_upload ? handle_drag_enter : void 0,
3223
+ onDragLeave: is_direct_upload ? handle_drag_leave : void 0,
3224
+ onDragOver: is_direct_upload ? handle_drag_over : void 0,
3225
+ onDrop: is_direct_upload ? handle_drop : void 0,
3226
+ "aria-label": "Add file",
3227
+ title: is_direct_upload ? "Click to browse or drag files here" : "Add file",
3228
+ children: [
3229
+ /* @__PURE__ */ jsx10(Plus, { size: 16 }),
3230
+ /* @__PURE__ */ jsx10("span", { className: "cls_file_list_add_btn_text", children: is_dragging ? "Drop here" : "Add file" })
3231
+ ]
3232
+ }
3233
+ ),
3234
+ is_direct_upload && /* @__PURE__ */ jsx10(
3235
+ "input",
3236
+ {
3237
+ ref: file_input_ref,
3238
+ type: "file",
3239
+ accept: accept_string,
3240
+ multiple: true,
3241
+ onChange: handle_input_change,
3242
+ style: { display: "none" },
3243
+ "aria-hidden": "true",
3244
+ tabIndex: -1
3245
+ }
3246
+ )
3247
+ ] })
3169
3248
  ]
3170
3249
  }
3171
3250
  ),
@@ -3189,7 +3268,7 @@ var FileList = ({
3189
3268
  };
3190
3269
 
3191
3270
  // src/components/file_manager/upload_dropzone.tsx
3192
- import { useState as useState10, useRef as useRef10, useCallback as useCallback2 } from "react";
3271
+ import { useState as useState10, useRef as useRef10, useCallback as useCallback3 } from "react";
3193
3272
  import { Upload, X as X4 } from "lucide-react";
3194
3273
 
3195
3274
  // src/components/file_manager/upload_progress.tsx
@@ -3279,7 +3358,7 @@ var UploadDropzone = ({
3279
3358
  const allowed_types = (upload_config?.allowed_types || "application/pdf").split(",").map((t) => t.trim());
3280
3359
  const max_file_size = upload_config?.max_file_size || 10485760;
3281
3360
  const max_files = upload_config?.max_files || 10;
3282
- const validate_file = useCallback2((file) => {
3361
+ const validate_file = useCallback3((file) => {
3283
3362
  const is_type_allowed = allowed_types.some((type) => {
3284
3363
  const trimmed = type.trim().toLowerCase();
3285
3364
  const file_type = file.type.toLowerCase();
@@ -3299,7 +3378,7 @@ var UploadDropzone = ({
3299
3378
  }
3300
3379
  return null;
3301
3380
  }, [allowed_types, max_file_size]);
3302
- const handle_files = useCallback2((file_list) => {
3381
+ const handle_files = useCallback3((file_list) => {
3303
3382
  const files = Array.from(file_list);
3304
3383
  setValidationError(null);
3305
3384
  if (files.length === 0) {
@@ -4169,11 +4248,11 @@ var FileManager = ({
4169
4248
  setInternalSelectedId(external_selected_id);
4170
4249
  }
4171
4250
  }, [external_selected_id]);
4172
- const handle_file_select = useCallback3((file) => {
4251
+ const handle_file_select = useCallback4((file) => {
4173
4252
  setInternalSelectedId(file.id);
4174
4253
  on_file_select?.(file);
4175
4254
  }, [on_file_select]);
4176
- const handle_file_delete = useCallback3((file_id) => {
4255
+ const handle_file_delete = useCallback4((file_id) => {
4177
4256
  const new_files = files.filter((f) => f.id !== file_id);
4178
4257
  setInternalFiles(new_files);
4179
4258
  setTimeout(() => on_files_change?.(new_files), 0);
@@ -4184,7 +4263,7 @@ var FileManager = ({
4184
4263
  setInternalSelectedId(null);
4185
4264
  }
4186
4265
  }, [files, selected_file_id, on_files_change, on_file_delete, handle_file_select]);
4187
- const handle_files_selected = useCallback3(async (selected_files) => {
4266
+ const handle_files_selected = useCallback4(async (selected_files) => {
4188
4267
  const conversion_config = config?.pdf_conversion;
4189
4268
  for (const file of selected_files) {
4190
4269
  const file_id = generate_file_id();
@@ -4316,6 +4395,7 @@ var FileManager = ({
4316
4395
  }, [config, on_upload, on_files_change, handle_file_select]);
4317
4396
  const file_manager_config = config?.file_manager;
4318
4397
  const upload_config = config?.file_upload;
4398
+ const is_direct_upload = upload_config?.direct_upload ?? false;
4319
4399
  if (show_button_only) {
4320
4400
  return /* @__PURE__ */ jsx14(
4321
4401
  FileManagerButton,
@@ -4336,10 +4416,11 @@ var FileManager = ({
4336
4416
  config,
4337
4417
  on_select: handle_file_select,
4338
4418
  on_delete: file_manager_config?.allow_delete !== false ? handle_file_delete : void 0,
4339
- on_add_click: upload_config?.upload_enabled !== false ? () => setShowDropzone(true) : void 0
4419
+ on_add_click: upload_config?.upload_enabled !== false && !is_direct_upload ? () => setShowDropzone(true) : void 0,
4420
+ on_files_selected: upload_config?.upload_enabled !== false && is_direct_upload ? handle_files_selected : void 0
4340
4421
  }
4341
4422
  ),
4342
- show_dropzone && /* @__PURE__ */ jsx14("div", { className: "cls_file_manager_dropzone_overlay", children: /* @__PURE__ */ jsx14("div", { className: "cls_file_manager_dropzone_dialog", children: /* @__PURE__ */ jsx14(
4423
+ show_dropzone && !is_direct_upload && /* @__PURE__ */ jsx14("div", { className: "cls_file_manager_dropzone_overlay", children: /* @__PURE__ */ jsx14("div", { className: "cls_file_manager_dropzone_dialog", children: /* @__PURE__ */ jsx14(
4343
4424
  UploadDropzone,
4344
4425
  {
4345
4426
  config,
@@ -4411,7 +4492,7 @@ async function save_pdf_data(pdf_bytes, remote_path, file_manager, overwrite = t
4411
4492
  }
4412
4493
 
4413
4494
  // src/components/pdf_viewer/pdf_viewer.tsx
4414
- import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
4495
+ import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
4415
4496
  var POPOUT_STORAGE_KEY = "hazo_pdf_popout";
4416
4497
  var PdfViewer = forwardRef(({
4417
4498
  url,
@@ -4441,6 +4522,7 @@ var PdfViewer = forwardRef(({
4441
4522
  show_undo_button,
4442
4523
  show_redo_button,
4443
4524
  show_save_button,
4525
+ show_download_button,
4444
4526
  show_metadata_button,
4445
4527
  show_annotate_button,
4446
4528
  show_file_info_button,
@@ -4460,6 +4542,9 @@ var PdfViewer = forwardRef(({
4460
4542
  on_upload,
4461
4543
  on_files_change,
4462
4544
  // file_manager_display_mode is reserved for future use (dialog/standalone modes)
4545
+ download_filename,
4546
+ display_filename,
4547
+ on_download,
4463
4548
  enable_popout = false,
4464
4549
  popout_route = "/pdf-viewer",
4465
4550
  on_popout,
@@ -4468,6 +4553,8 @@ var PdfViewer = forwardRef(({
4468
4553
  // hazo_files integration props
4469
4554
  file_manager,
4470
4555
  save_path,
4556
+ // Upload behavior
4557
+ direct_upload,
4471
4558
  // File info sidepanel data props
4472
4559
  doc_data,
4473
4560
  highlight_fields_info,
@@ -4484,6 +4571,7 @@ var PdfViewer = forwardRef(({
4484
4571
  const [annotations, setAnnotations] = useState13(initial_annotations);
4485
4572
  const [current_tool, setCurrentTool] = useState13(null);
4486
4573
  const [saving, setSaving] = useState13(false);
4574
+ const [downloading, setDownloading] = useState13(false);
4487
4575
  const [extracting, setExtracting] = useState13(false);
4488
4576
  const [extract_error, setExtractError] = useState13(null);
4489
4577
  const [sidepanel_open, setSidepanelOpen] = useState13(false);
@@ -4494,6 +4582,7 @@ var PdfViewer = forwardRef(({
4494
4582
  const [auto_highlight_ids, setAutoHighlightIds] = useState13(/* @__PURE__ */ new Set());
4495
4583
  const [page_rotations, setPageRotations] = useState13(/* @__PURE__ */ new Map());
4496
4584
  const [current_visible_page, setCurrentVisiblePage] = useState13(0);
4585
+ const [, setConfigVersion] = useState13(0);
4497
4586
  const [current_file, setCurrentFile] = useState13(
4498
4587
  files && files.length > 0 ? files[0] : null
4499
4588
  );
@@ -4531,11 +4620,11 @@ var PdfViewer = forwardRef(({
4531
4620
  setCurrentFile(null);
4532
4621
  }
4533
4622
  }, [files]);
4534
- const handle_file_select = useCallback4((file) => {
4623
+ const handle_file_select = useCallback5((file) => {
4535
4624
  setCurrentFile(file);
4536
4625
  on_file_select?.(file);
4537
4626
  }, [on_file_select]);
4538
- const config_ref = useRef11(null);
4627
+ const config_ref = useRef11(load_pdf_config());
4539
4628
  useEffect10(() => {
4540
4629
  if (!config_file) {
4541
4630
  config_ref.current = load_pdf_config();
@@ -4545,6 +4634,7 @@ var PdfViewer = forwardRef(({
4545
4634
  if (is_browser) {
4546
4635
  load_pdf_config_async(config_file).then((config) => {
4547
4636
  config_ref.current = config;
4637
+ setConfigVersion((v) => v + 1);
4548
4638
  console.log("[PdfViewer] Config loaded:", {
4549
4639
  append_timestamp_to_text_edits: config.viewer.append_timestamp_to_text_edits,
4550
4640
  config_object: config
@@ -4552,6 +4642,7 @@ var PdfViewer = forwardRef(({
4552
4642
  }).catch((error2) => {
4553
4643
  console.warn(`[PdfViewer] Could not load config file "${config_file}", using defaults:`, error2);
4554
4644
  config_ref.current = load_pdf_config();
4645
+ setConfigVersion((v) => v + 1);
4555
4646
  });
4556
4647
  } else {
4557
4648
  config_ref.current = load_pdf_config(config_file);
@@ -4560,6 +4651,15 @@ var PdfViewer = forwardRef(({
4560
4651
  });
4561
4652
  }
4562
4653
  }, [config_file]);
4654
+ if (direct_upload !== void 0 && config_ref.current) {
4655
+ config_ref.current = {
4656
+ ...config_ref.current,
4657
+ file_upload: {
4658
+ ...config_ref.current.file_upload,
4659
+ direct_upload
4660
+ }
4661
+ };
4662
+ }
4563
4663
  const effective_background_color = background_color || config_ref.current?.viewer.viewer_background_color || "#2d2d2d";
4564
4664
  const format_annotation_timestamp = () => {
4565
4665
  const now = /* @__PURE__ */ new Date();
@@ -5016,7 +5116,7 @@ ${suffix_line}`;
5016
5116
  }
5017
5117
  // eslint-disable-next-line react-hooks/exhaustive-deps
5018
5118
  }), [annotations, handle_annotation_create, handle_annotation_delete]);
5019
- const handle_undo = useCallback4(() => {
5119
+ const handle_undo = useCallback5(() => {
5020
5120
  if (history_index > 0) {
5021
5121
  history_ref.current.saving = true;
5022
5122
  const previous_index = history_index - 1;
@@ -5028,7 +5128,7 @@ ${suffix_line}`;
5028
5128
  }, 0);
5029
5129
  }
5030
5130
  }, [history_index, history]);
5031
- const handle_redo = useCallback4(() => {
5131
+ const handle_redo = useCallback5(() => {
5032
5132
  if (history_index < history.length - 1) {
5033
5133
  history_ref.current.saving = true;
5034
5134
  const next_index = history_index + 1;
@@ -5074,7 +5174,7 @@ ${suffix_line}`;
5074
5174
  if (normalized < 0) normalized += 360;
5075
5175
  return normalized;
5076
5176
  };
5077
- const handle_rotate_left = useCallback4(() => {
5177
+ const handle_rotate_left = useCallback5(() => {
5078
5178
  setPageRotations((prev) => {
5079
5179
  const new_map = new Map(prev);
5080
5180
  const current_rotation = new_map.get(current_visible_page) || 0;
@@ -5082,7 +5182,7 @@ ${suffix_line}`;
5082
5182
  return new_map;
5083
5183
  });
5084
5184
  }, [current_visible_page]);
5085
- const handle_rotate_right = useCallback4(() => {
5185
+ const handle_rotate_right = useCallback5(() => {
5086
5186
  setPageRotations((prev) => {
5087
5187
  const new_map = new Map(prev);
5088
5188
  const current_rotation = new_map.get(current_visible_page) || 0;
@@ -5090,7 +5190,7 @@ ${suffix_line}`;
5090
5190
  return new_map;
5091
5191
  });
5092
5192
  }, [current_visible_page]);
5093
- const handle_rotate_all_left = useCallback4(() => {
5193
+ const handle_rotate_all_left = useCallback5(() => {
5094
5194
  if (!pdf_document) return;
5095
5195
  setPageRotations((prev) => {
5096
5196
  const new_map = new Map(prev);
@@ -5101,7 +5201,7 @@ ${suffix_line}`;
5101
5201
  return new_map;
5102
5202
  });
5103
5203
  }, [pdf_document]);
5104
- const handle_rotate_all_right = useCallback4(() => {
5204
+ const handle_rotate_all_right = useCallback5(() => {
5105
5205
  if (!pdf_document) return;
5106
5206
  setPageRotations((prev) => {
5107
5207
  const new_map = new Map(prev);
@@ -5112,7 +5212,7 @@ ${suffix_line}`;
5112
5212
  return new_map;
5113
5213
  });
5114
5214
  }, [pdf_document]);
5115
- const handle_visible_page_change = useCallback4((page_index) => {
5215
+ const handle_visible_page_change = useCallback5((page_index) => {
5116
5216
  setCurrentVisiblePage(page_index);
5117
5217
  }, []);
5118
5218
  const handle_sidepanel_toggle = () => {
@@ -5133,7 +5233,7 @@ ${suffix_line}`;
5133
5233
  }
5134
5234
  return { updatedRow, allData };
5135
5235
  };
5136
- const handle_popout = useCallback4(() => {
5236
+ const handle_popout = useCallback5(() => {
5137
5237
  if (!files || files.length === 0) {
5138
5238
  console.warn("PdfViewer: No files to popout");
5139
5239
  return;
@@ -5272,7 +5372,7 @@ ${suffix_line}`;
5272
5372
  const original_filename = is_multi_file_mode && current_file ? current_file.name : effective_url.split("/").pop() || "document.pdf";
5273
5373
  const filename_without_ext = original_filename.replace(/\.pdf$/i, "");
5274
5374
  const output_filename = `${filename_without_ext}_annotated.pdf`;
5275
- const { save_annotations_to_pdf, download_pdf } = await import("./pdf_saver-JXAIRQVL.js");
5375
+ const { save_annotations_to_pdf, download_pdf } = await import("./pdf_saver-C2X4AZYR.js");
5276
5376
  const pdf_source = cached_pdf_data || effective_url;
5277
5377
  logger2.debug("[PdfViewer] Saving PDF", { source_type: cached_pdf_data ? "cached ArrayBuffer" : "URL" });
5278
5378
  const pdf_bytes = await save_annotations_to_pdf(pdf_source, annotations, output_filename, config_ref.current, page_rotations);
@@ -5299,6 +5399,32 @@ ${suffix_line}`;
5299
5399
  setSaving(false);
5300
5400
  }
5301
5401
  };
5402
+ const handle_download = async () => {
5403
+ if (!effective_url && !cached_pdf_data) {
5404
+ console.error("PdfViewer: No PDF available for download");
5405
+ return;
5406
+ }
5407
+ setDownloading(true);
5408
+ try {
5409
+ const logger2 = get_logger();
5410
+ const { save_annotations_to_pdf, download_pdf } = await import("./pdf_saver-C2X4AZYR.js");
5411
+ const original_filename = is_multi_file_mode && current_file ? current_file.name : effective_url?.split("/").pop() || "document.pdf";
5412
+ const output_filename = download_filename || original_filename;
5413
+ const pdf_source = cached_pdf_data || effective_url;
5414
+ logger2.debug("[PdfViewer] Downloading PDF", { filename: output_filename });
5415
+ const pdf_bytes = await save_annotations_to_pdf(pdf_source, annotations, output_filename, config_ref.current, page_rotations);
5416
+ download_pdf(pdf_bytes, output_filename);
5417
+ on_download?.(output_filename);
5418
+ } catch (error2) {
5419
+ console.error("PdfViewer: Error downloading PDF:", error2);
5420
+ const error_obj = error2 instanceof Error ? error2 : new Error(String(error2));
5421
+ if (on_error) {
5422
+ on_error(error_obj);
5423
+ }
5424
+ } finally {
5425
+ setDownloading(false);
5426
+ }
5427
+ };
5302
5428
  const handle_extract = async () => {
5303
5429
  if (!extract_api_endpoint) {
5304
5430
  console.warn("PdfViewer: No extract_api_endpoint configured");
@@ -5322,7 +5448,7 @@ ${suffix_line}`;
5322
5448
  setExtractError(null);
5323
5449
  try {
5324
5450
  const logger2 = get_logger();
5325
- const current_filename = is_multi_file_mode ? current_file?.name : url ? url.split("/").pop() || "unknown" : "unknown";
5451
+ const current_filename = is_multi_file_mode ? current_file?.name : display_filename || (url ? url.split("/").pop() || "unknown" : "unknown");
5326
5452
  logger2.info("[PdfViewer] Starting data extraction", { filename: current_filename });
5327
5453
  const bytes = new Uint8Array(cached_pdf_data);
5328
5454
  let binary = "";
@@ -5412,6 +5538,7 @@ ${suffix_line}`;
5412
5538
  toolbar_show_annotate_button: show_annotate_button ?? base_toolbar_config.toolbar_show_annotate_button,
5413
5539
  toolbar_show_file_info_button: show_file_info_button ?? base_toolbar_config.toolbar_show_file_info_button,
5414
5540
  toolbar_show_extract_button: show_extract_button ?? base_toolbar_config.toolbar_show_extract_button,
5541
+ toolbar_show_download_button: show_download_button ?? base_toolbar_config.toolbar_show_download_button,
5415
5542
  toolbar_show_rotation_controls: base_toolbar_config.toolbar_show_rotation_controls ?? true
5416
5543
  };
5417
5544
  const is_toolbar_enabled = toolbar_enabled ?? true;
@@ -5690,6 +5817,35 @@ ${suffix_line}`;
5690
5817
  children: /* @__PURE__ */ jsx15(Save, { className: "cls_pdf_viewer_toolbar_icon", size: 16 })
5691
5818
  }
5692
5819
  ) }),
5820
+ toolbar_config.toolbar_show_download_button && /* @__PURE__ */ jsx15("div", { className: "cls_pdf_viewer_toolbar_group", children: /* @__PURE__ */ jsx15(
5821
+ "button",
5822
+ {
5823
+ type: "button",
5824
+ onClick: handle_download,
5825
+ disabled: downloading || !pdf_document,
5826
+ className: cn(
5827
+ "cls_pdf_viewer_toolbar_button",
5828
+ "cls_pdf_viewer_toolbar_button_download",
5829
+ (downloading || !pdf_document) && "cls_pdf_viewer_toolbar_button_disabled"
5830
+ ),
5831
+ "aria-label": "Download PDF",
5832
+ title: downloading ? "Downloading..." : "Download PDF",
5833
+ style: {
5834
+ backgroundColor: toolbar_config.toolbar_button_background_color,
5835
+ color: toolbar_config.toolbar_button_text_color,
5836
+ opacity: downloading || !pdf_document ? toolbar_config.toolbar_button_disabled_opacity : 1
5837
+ },
5838
+ onMouseEnter: (e) => {
5839
+ if (!(downloading || !pdf_document)) {
5840
+ e.currentTarget.style.backgroundColor = toolbar_config.toolbar_button_background_color_hover;
5841
+ }
5842
+ },
5843
+ onMouseLeave: (e) => {
5844
+ e.currentTarget.style.backgroundColor = toolbar_config.toolbar_button_background_color;
5845
+ },
5846
+ children: /* @__PURE__ */ jsx15(Download, { className: "cls_pdf_viewer_toolbar_icon", size: 16 })
5847
+ }
5848
+ ) }),
5693
5849
  toolbar_config.toolbar_show_extract_button && extract_api_endpoint && /* @__PURE__ */ jsx15("div", { className: "cls_pdf_viewer_toolbar_group", children: /* @__PURE__ */ jsx15(
5694
5850
  "button",
5695
5851
  {
@@ -5924,7 +6080,7 @@ ${suffix_line}`;
5924
6080
  is_open: file_info_sidepanel_open,
5925
6081
  on_toggle: handle_file_info_sidepanel_toggle,
5926
6082
  item: (() => {
5927
- const filename = current_file?.name || (url ? url.split("/").pop() || "" : "");
6083
+ const filename = current_file?.name || display_filename || (url ? url.split("/").pop() || "" : "");
5928
6084
  const filepath = effective_url || "";
5929
6085
  if (!filename) return null;
5930
6086
  return {
@@ -5938,14 +6094,14 @@ ${suffix_line}`;
5938
6094
  width: file_info_sidepanel_width,
5939
6095
  on_width_change: handle_file_info_sidepanel_width_change,
5940
6096
  file_metadata,
5941
- current_filename: current_file?.name || (url ? url.split("/").pop() || "" : ""),
6097
+ current_filename: current_file?.name || display_filename || (url ? url.split("/").pop() || "" : ""),
5942
6098
  doc_data,
5943
6099
  highlight_fields_info
5944
6100
  }
5945
6101
  )
5946
6102
  ] });
5947
6103
  })(),
5948
- context_menu?.visible && /* @__PURE__ */ jsxs14(Fragment5, { children: [
6104
+ context_menu?.visible && /* @__PURE__ */ jsxs14(Fragment6, { children: [
5949
6105
  /* @__PURE__ */ jsx15(
5950
6106
  "div",
5951
6107
  {
@@ -6152,4 +6308,4 @@ export {
6152
6308
  PdfViewer,
6153
6309
  pdf_viewer_default
6154
6310
  };
6155
- //# sourceMappingURL=chunk-ROKBDX4O.js.map
6311
+ //# sourceMappingURL=chunk-ETZ57VO7.js.map