hazo_pdf 1.6.0 → 1.6.1

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,9 @@ 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"`. |
1032
1036
  | `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
1037
 
1034
1038
  **Example - Minimal toolbar:**
@@ -1838,6 +1842,39 @@ interface FileItem {
1838
1842
  - Files can be uploaded as PDFs or non-PDF formats (images, Excel, text files)
1839
1843
  - Non-PDF files are automatically converted to PDF before upload
1840
1844
 
1845
+ ### Direct Upload Mode
1846
+
1847
+ 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.
1848
+
1849
+ ```tsx
1850
+ // 1-click upload: click "Add file" → file picker opens immediately
1851
+ <PdfViewer
1852
+ files={files}
1853
+ on_files_change={setFiles}
1854
+ on_upload={handle_upload}
1855
+ direct_upload={true}
1856
+ />
1857
+
1858
+ // Also works with PdfViewerDialog
1859
+ <PdfViewerDialog
1860
+ open={is_open}
1861
+ on_open_change={set_is_open}
1862
+ files={files}
1863
+ on_files_change={setFiles}
1864
+ on_upload={handle_upload}
1865
+ direct_upload={true}
1866
+ />
1867
+ ```
1868
+
1869
+ 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.
1870
+
1871
+ Can also be set via INI config:
1872
+
1873
+ ```ini
1874
+ [file_upload]
1875
+ direct_upload = true
1876
+ ```
1877
+
1841
1878
  ---
1842
1879
 
1843
1880
  ## PDF Rotation
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  default_config
4
- } from "./chunk-CXHR3TT6.js";
4
+ } from "./chunk-LTUXHTQF.js";
5
5
 
6
6
  // src/utils/pdf_saver.ts
7
7
  async function save_annotations_to_pdf(pdf_source, annotations, _output_filename, config, page_rotations) {
@@ -224,4 +224,4 @@ export {
224
224
  download_pdf,
225
225
  save_and_download_pdf
226
226
  };
227
- //# sourceMappingURL=chunk-RQOQLZRS.js.map
227
+ //# sourceMappingURL=chunk-GGEFUU2N.js.map
@@ -93,6 +93,7 @@ var default_config = {
93
93
  toolbar_show_annotate_button: true,
94
94
  toolbar_show_file_info_button: true,
95
95
  toolbar_show_extract_button: true,
96
+ toolbar_show_download_button: false,
96
97
  toolbar_zoom_out_label: "\u2212",
97
98
  toolbar_zoom_in_label: "+",
98
99
  toolbar_zoom_reset_label: "Reset",
@@ -122,7 +123,8 @@ var default_config = {
122
123
  show_add_button: true,
123
124
  dropzone_border_color: "#d1d5db",
124
125
  dropzone_border_color_active: "#3b82f6",
125
- dropzone_background_color: "#f9fafb"
126
+ dropzone_background_color: "#f9fafb",
127
+ direct_upload: false
126
128
  },
127
129
  pdf_conversion: {
128
130
  conversion_enabled: true,
@@ -154,4 +156,4 @@ var default_config = {
154
156
  export {
155
157
  default_config
156
158
  };
157
- //# sourceMappingURL=chunk-CXHR3TT6.js.map
159
+ //# sourceMappingURL=chunk-LTUXHTQF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/default_config.ts"],"sourcesContent":["/**\n * Default configuration values for hazo_pdf\n * All styling defaults organized by category\n */\n\nimport type { PdfViewerConfig } from '../types/config';\n\n/**\n * Default PDF Viewer Configuration\n * These values are used when no config file is provided or when values are missing\n */\nexport const default_config: PdfViewerConfig = {\n fonts: {\n freetext_font_family: 'Arial, sans-serif',\n freetext_font_size_min: 12,\n freetext_font_size_max: 24,\n freetext_font_size_default: 14,\n font_foreground_color: '#000000', // Black by default\n },\n\n highlight_annotation: {\n highlight_fill_color: '#FFFF00',\n highlight_fill_opacity: 0.3,\n highlight_border_color: '#FFD700',\n highlight_border_color_hover: '#FFD700',\n highlight_fill_opacity_hover: 0.4,\n },\n\n square_annotation: {\n square_fill_color: '#FF0000',\n square_fill_opacity: 0.2,\n square_border_color: '#FF0000',\n square_border_color_hover: '#CC0000',\n square_fill_opacity_hover: 0.3,\n },\n\n freetext_annotation: {\n freetext_text_color: '#000000',\n freetext_text_color_hover: '#000000',\n freetext_border_color: '#003366',\n freetext_border_width: 1,\n freetext_background_color: '#E6F3FF',\n freetext_background_opacity: 0.1,\n freetext_font_weight: 'normal',\n freetext_font_style: 'normal',\n freetext_text_decoration: 'none',\n freetext_padding_horizontal: 4,\n freetext_padding_vertical: 2,\n },\n\n page_styling: {\n page_border_color: '#999999',\n page_box_shadow: '0 2px 8px rgba(0, 0, 0, 0.3)',\n page_background_color: '#ffffff',\n },\n\n viewer: {\n viewer_background_color: '#2d2d2d',\n append_timestamp_to_text_edits: false,\n annotation_text_suffix_fixed_text: '',\n add_enclosing_brackets_to_suffixes: true,\n suffix_enclosing_brackets: '[]',\n suffix_text_position: 'below_multi_line',\n },\n\n context_menu: {\n context_menu_background_color: '#ffffff',\n context_menu_border_color: '#d1d5db',\n context_menu_item_hover_background: '#f3f4f6',\n context_menu_item_disabled_opacity: 0.5,\n right_click_custom_stamps: '', // Empty string means no custom stamps\n },\n\n dialog: {\n dialog_backdrop_opacity: 0.2,\n dialog_background_color: '#ffffff',\n dialog_border_color: '#d1d5db',\n dialog_button_submit_color: '#16a34a',\n dialog_button_submit_color_hover: '#15803d',\n dialog_button_cancel_color: '#6b7280',\n dialog_button_cancel_color_hover: '#4b5563',\n dialog_button_disabled_opacity: 0.4,\n },\n\n toolbar: {\n toolbar_background_color: '#f9fafb',\n toolbar_border_color: '#e5e7eb',\n toolbar_font_family: 'system-ui, -apple-system, sans-serif',\n toolbar_font_size: 14,\n toolbar_font_color: '#111827',\n toolbar_button_background_color: '#ffffff',\n toolbar_button_background_color_hover: '#f3f4f6',\n toolbar_button_text_color: '#374151',\n toolbar_button_active_background_color: '#3b82f6',\n toolbar_button_active_text_color: '#ffffff',\n toolbar_button_save_background_color: '#10b981',\n toolbar_button_save_background_color_hover: '#059669',\n toolbar_button_save_text_color: '#ffffff',\n toolbar_button_disabled_opacity: 0.5,\n toolbar_show_zoom_controls: true,\n toolbar_show_rotation_controls: true,\n toolbar_show_square_button: true,\n toolbar_show_undo_button: true,\n toolbar_show_redo_button: true,\n toolbar_show_save_button: true,\n toolbar_show_metadata_button: true,\n toolbar_show_annotate_button: true,\n toolbar_show_file_info_button: true,\n toolbar_show_extract_button: true,\n toolbar_show_download_button: false,\n toolbar_zoom_out_label: '−',\n toolbar_zoom_in_label: '+',\n toolbar_zoom_reset_label: 'Reset',\n toolbar_square_label: 'Square',\n toolbar_undo_label: 'Undo',\n toolbar_redo_label: 'Redo',\n toolbar_save_label: 'Save',\n toolbar_saving_label: 'Saving...',\n toolbar_metadata_label: 'Metadata',\n },\n\n file_manager: {\n file_manager_enabled: false,\n show_file_list: true,\n allow_delete: true,\n show_popout_button: true,\n file_list_height: 60,\n selected_color: '#3b82f6',\n file_list_background_color: '#f9fafb',\n file_list_border_color: '#e5e7eb',\n },\n\n file_upload: {\n upload_enabled: true,\n allowed_types: 'application/pdf,image/jpeg,image/png,image/gif,image/webp,text/plain,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel',\n max_file_size: 10485760, // 10MB\n max_files: 10,\n show_add_button: true,\n dropzone_border_color: '#d1d5db',\n dropzone_border_color_active: '#3b82f6',\n dropzone_background_color: '#f9fafb',\n direct_upload: false,\n },\n\n pdf_conversion: {\n conversion_enabled: true,\n page_size: 'letter',\n image_quality: 0.85,\n image_fit: 'fit',\n margin: 36,\n },\n\n auto_highlight: {\n auto_highlight_border_color: '#FF6B00',\n auto_highlight_background_color: '#FFF3E0',\n auto_highlight_background_opacity: 0.3,\n auto_highlight_border_width: 1,\n auto_highlight_normalize_text: true,\n auto_highlight_padding_x: 2,\n auto_highlight_padding_y: 1,\n auto_highlight_y_offset: -3,\n },\n\n file_button: {\n icon_size: 24,\n icon_color: '#6b7280',\n icon_color_hover: '#374151',\n icon_color_with_files: '#3b82f6',\n badge_background: '#3b82f6',\n badge_text_color: '#ffffff',\n },\n};\n"],"mappings":"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","names":[]}
@@ -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,8 @@ 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
+ on_download,
4463
4547
  enable_popout = false,
4464
4548
  popout_route = "/pdf-viewer",
4465
4549
  on_popout,
@@ -4468,6 +4552,8 @@ var PdfViewer = forwardRef(({
4468
4552
  // hazo_files integration props
4469
4553
  file_manager,
4470
4554
  save_path,
4555
+ // Upload behavior
4556
+ direct_upload,
4471
4557
  // File info sidepanel data props
4472
4558
  doc_data,
4473
4559
  highlight_fields_info,
@@ -4484,6 +4570,7 @@ var PdfViewer = forwardRef(({
4484
4570
  const [annotations, setAnnotations] = useState13(initial_annotations);
4485
4571
  const [current_tool, setCurrentTool] = useState13(null);
4486
4572
  const [saving, setSaving] = useState13(false);
4573
+ const [downloading, setDownloading] = useState13(false);
4487
4574
  const [extracting, setExtracting] = useState13(false);
4488
4575
  const [extract_error, setExtractError] = useState13(null);
4489
4576
  const [sidepanel_open, setSidepanelOpen] = useState13(false);
@@ -4494,6 +4581,7 @@ var PdfViewer = forwardRef(({
4494
4581
  const [auto_highlight_ids, setAutoHighlightIds] = useState13(/* @__PURE__ */ new Set());
4495
4582
  const [page_rotations, setPageRotations] = useState13(/* @__PURE__ */ new Map());
4496
4583
  const [current_visible_page, setCurrentVisiblePage] = useState13(0);
4584
+ const [, setConfigVersion] = useState13(0);
4497
4585
  const [current_file, setCurrentFile] = useState13(
4498
4586
  files && files.length > 0 ? files[0] : null
4499
4587
  );
@@ -4531,11 +4619,11 @@ var PdfViewer = forwardRef(({
4531
4619
  setCurrentFile(null);
4532
4620
  }
4533
4621
  }, [files]);
4534
- const handle_file_select = useCallback4((file) => {
4622
+ const handle_file_select = useCallback5((file) => {
4535
4623
  setCurrentFile(file);
4536
4624
  on_file_select?.(file);
4537
4625
  }, [on_file_select]);
4538
- const config_ref = useRef11(null);
4626
+ const config_ref = useRef11(load_pdf_config());
4539
4627
  useEffect10(() => {
4540
4628
  if (!config_file) {
4541
4629
  config_ref.current = load_pdf_config();
@@ -4545,6 +4633,7 @@ var PdfViewer = forwardRef(({
4545
4633
  if (is_browser) {
4546
4634
  load_pdf_config_async(config_file).then((config) => {
4547
4635
  config_ref.current = config;
4636
+ setConfigVersion((v) => v + 1);
4548
4637
  console.log("[PdfViewer] Config loaded:", {
4549
4638
  append_timestamp_to_text_edits: config.viewer.append_timestamp_to_text_edits,
4550
4639
  config_object: config
@@ -4552,6 +4641,7 @@ var PdfViewer = forwardRef(({
4552
4641
  }).catch((error2) => {
4553
4642
  console.warn(`[PdfViewer] Could not load config file "${config_file}", using defaults:`, error2);
4554
4643
  config_ref.current = load_pdf_config();
4644
+ setConfigVersion((v) => v + 1);
4555
4645
  });
4556
4646
  } else {
4557
4647
  config_ref.current = load_pdf_config(config_file);
@@ -4560,6 +4650,15 @@ var PdfViewer = forwardRef(({
4560
4650
  });
4561
4651
  }
4562
4652
  }, [config_file]);
4653
+ if (direct_upload !== void 0 && config_ref.current) {
4654
+ config_ref.current = {
4655
+ ...config_ref.current,
4656
+ file_upload: {
4657
+ ...config_ref.current.file_upload,
4658
+ direct_upload
4659
+ }
4660
+ };
4661
+ }
4563
4662
  const effective_background_color = background_color || config_ref.current?.viewer.viewer_background_color || "#2d2d2d";
4564
4663
  const format_annotation_timestamp = () => {
4565
4664
  const now = /* @__PURE__ */ new Date();
@@ -5016,7 +5115,7 @@ ${suffix_line}`;
5016
5115
  }
5017
5116
  // eslint-disable-next-line react-hooks/exhaustive-deps
5018
5117
  }), [annotations, handle_annotation_create, handle_annotation_delete]);
5019
- const handle_undo = useCallback4(() => {
5118
+ const handle_undo = useCallback5(() => {
5020
5119
  if (history_index > 0) {
5021
5120
  history_ref.current.saving = true;
5022
5121
  const previous_index = history_index - 1;
@@ -5028,7 +5127,7 @@ ${suffix_line}`;
5028
5127
  }, 0);
5029
5128
  }
5030
5129
  }, [history_index, history]);
5031
- const handle_redo = useCallback4(() => {
5130
+ const handle_redo = useCallback5(() => {
5032
5131
  if (history_index < history.length - 1) {
5033
5132
  history_ref.current.saving = true;
5034
5133
  const next_index = history_index + 1;
@@ -5074,7 +5173,7 @@ ${suffix_line}`;
5074
5173
  if (normalized < 0) normalized += 360;
5075
5174
  return normalized;
5076
5175
  };
5077
- const handle_rotate_left = useCallback4(() => {
5176
+ const handle_rotate_left = useCallback5(() => {
5078
5177
  setPageRotations((prev) => {
5079
5178
  const new_map = new Map(prev);
5080
5179
  const current_rotation = new_map.get(current_visible_page) || 0;
@@ -5082,7 +5181,7 @@ ${suffix_line}`;
5082
5181
  return new_map;
5083
5182
  });
5084
5183
  }, [current_visible_page]);
5085
- const handle_rotate_right = useCallback4(() => {
5184
+ const handle_rotate_right = useCallback5(() => {
5086
5185
  setPageRotations((prev) => {
5087
5186
  const new_map = new Map(prev);
5088
5187
  const current_rotation = new_map.get(current_visible_page) || 0;
@@ -5090,7 +5189,7 @@ ${suffix_line}`;
5090
5189
  return new_map;
5091
5190
  });
5092
5191
  }, [current_visible_page]);
5093
- const handle_rotate_all_left = useCallback4(() => {
5192
+ const handle_rotate_all_left = useCallback5(() => {
5094
5193
  if (!pdf_document) return;
5095
5194
  setPageRotations((prev) => {
5096
5195
  const new_map = new Map(prev);
@@ -5101,7 +5200,7 @@ ${suffix_line}`;
5101
5200
  return new_map;
5102
5201
  });
5103
5202
  }, [pdf_document]);
5104
- const handle_rotate_all_right = useCallback4(() => {
5203
+ const handle_rotate_all_right = useCallback5(() => {
5105
5204
  if (!pdf_document) return;
5106
5205
  setPageRotations((prev) => {
5107
5206
  const new_map = new Map(prev);
@@ -5112,7 +5211,7 @@ ${suffix_line}`;
5112
5211
  return new_map;
5113
5212
  });
5114
5213
  }, [pdf_document]);
5115
- const handle_visible_page_change = useCallback4((page_index) => {
5214
+ const handle_visible_page_change = useCallback5((page_index) => {
5116
5215
  setCurrentVisiblePage(page_index);
5117
5216
  }, []);
5118
5217
  const handle_sidepanel_toggle = () => {
@@ -5133,7 +5232,7 @@ ${suffix_line}`;
5133
5232
  }
5134
5233
  return { updatedRow, allData };
5135
5234
  };
5136
- const handle_popout = useCallback4(() => {
5235
+ const handle_popout = useCallback5(() => {
5137
5236
  if (!files || files.length === 0) {
5138
5237
  console.warn("PdfViewer: No files to popout");
5139
5238
  return;
@@ -5272,7 +5371,7 @@ ${suffix_line}`;
5272
5371
  const original_filename = is_multi_file_mode && current_file ? current_file.name : effective_url.split("/").pop() || "document.pdf";
5273
5372
  const filename_without_ext = original_filename.replace(/\.pdf$/i, "");
5274
5373
  const output_filename = `${filename_without_ext}_annotated.pdf`;
5275
- const { save_annotations_to_pdf, download_pdf } = await import("./pdf_saver-JXAIRQVL.js");
5374
+ const { save_annotations_to_pdf, download_pdf } = await import("./pdf_saver-C2X4AZYR.js");
5276
5375
  const pdf_source = cached_pdf_data || effective_url;
5277
5376
  logger2.debug("[PdfViewer] Saving PDF", { source_type: cached_pdf_data ? "cached ArrayBuffer" : "URL" });
5278
5377
  const pdf_bytes = await save_annotations_to_pdf(pdf_source, annotations, output_filename, config_ref.current, page_rotations);
@@ -5299,6 +5398,32 @@ ${suffix_line}`;
5299
5398
  setSaving(false);
5300
5399
  }
5301
5400
  };
5401
+ const handle_download = async () => {
5402
+ if (!effective_url && !cached_pdf_data) {
5403
+ console.error("PdfViewer: No PDF available for download");
5404
+ return;
5405
+ }
5406
+ setDownloading(true);
5407
+ try {
5408
+ const logger2 = get_logger();
5409
+ const { save_annotations_to_pdf, download_pdf } = await import("./pdf_saver-C2X4AZYR.js");
5410
+ const original_filename = is_multi_file_mode && current_file ? current_file.name : effective_url?.split("/").pop() || "document.pdf";
5411
+ const output_filename = download_filename || original_filename;
5412
+ const pdf_source = cached_pdf_data || effective_url;
5413
+ logger2.debug("[PdfViewer] Downloading PDF", { filename: output_filename });
5414
+ const pdf_bytes = await save_annotations_to_pdf(pdf_source, annotations, output_filename, config_ref.current, page_rotations);
5415
+ download_pdf(pdf_bytes, output_filename);
5416
+ on_download?.(output_filename);
5417
+ } catch (error2) {
5418
+ console.error("PdfViewer: Error downloading PDF:", error2);
5419
+ const error_obj = error2 instanceof Error ? error2 : new Error(String(error2));
5420
+ if (on_error) {
5421
+ on_error(error_obj);
5422
+ }
5423
+ } finally {
5424
+ setDownloading(false);
5425
+ }
5426
+ };
5302
5427
  const handle_extract = async () => {
5303
5428
  if (!extract_api_endpoint) {
5304
5429
  console.warn("PdfViewer: No extract_api_endpoint configured");
@@ -5412,6 +5537,7 @@ ${suffix_line}`;
5412
5537
  toolbar_show_annotate_button: show_annotate_button ?? base_toolbar_config.toolbar_show_annotate_button,
5413
5538
  toolbar_show_file_info_button: show_file_info_button ?? base_toolbar_config.toolbar_show_file_info_button,
5414
5539
  toolbar_show_extract_button: show_extract_button ?? base_toolbar_config.toolbar_show_extract_button,
5540
+ toolbar_show_download_button: show_download_button ?? base_toolbar_config.toolbar_show_download_button,
5415
5541
  toolbar_show_rotation_controls: base_toolbar_config.toolbar_show_rotation_controls ?? true
5416
5542
  };
5417
5543
  const is_toolbar_enabled = toolbar_enabled ?? true;
@@ -5690,6 +5816,35 @@ ${suffix_line}`;
5690
5816
  children: /* @__PURE__ */ jsx15(Save, { className: "cls_pdf_viewer_toolbar_icon", size: 16 })
5691
5817
  }
5692
5818
  ) }),
5819
+ toolbar_config.toolbar_show_download_button && /* @__PURE__ */ jsx15("div", { className: "cls_pdf_viewer_toolbar_group", children: /* @__PURE__ */ jsx15(
5820
+ "button",
5821
+ {
5822
+ type: "button",
5823
+ onClick: handle_download,
5824
+ disabled: downloading || !pdf_document,
5825
+ className: cn(
5826
+ "cls_pdf_viewer_toolbar_button",
5827
+ "cls_pdf_viewer_toolbar_button_download",
5828
+ (downloading || !pdf_document) && "cls_pdf_viewer_toolbar_button_disabled"
5829
+ ),
5830
+ "aria-label": "Download PDF",
5831
+ title: downloading ? "Downloading..." : "Download PDF",
5832
+ style: {
5833
+ backgroundColor: toolbar_config.toolbar_button_background_color,
5834
+ color: toolbar_config.toolbar_button_text_color,
5835
+ opacity: downloading || !pdf_document ? toolbar_config.toolbar_button_disabled_opacity : 1
5836
+ },
5837
+ onMouseEnter: (e) => {
5838
+ if (!(downloading || !pdf_document)) {
5839
+ e.currentTarget.style.backgroundColor = toolbar_config.toolbar_button_background_color_hover;
5840
+ }
5841
+ },
5842
+ onMouseLeave: (e) => {
5843
+ e.currentTarget.style.backgroundColor = toolbar_config.toolbar_button_background_color;
5844
+ },
5845
+ children: /* @__PURE__ */ jsx15(Download, { className: "cls_pdf_viewer_toolbar_icon", size: 16 })
5846
+ }
5847
+ ) }),
5693
5848
  toolbar_config.toolbar_show_extract_button && extract_api_endpoint && /* @__PURE__ */ jsx15("div", { className: "cls_pdf_viewer_toolbar_group", children: /* @__PURE__ */ jsx15(
5694
5849
  "button",
5695
5850
  {
@@ -5945,7 +6100,7 @@ ${suffix_line}`;
5945
6100
  )
5946
6101
  ] });
5947
6102
  })(),
5948
- context_menu?.visible && /* @__PURE__ */ jsxs14(Fragment5, { children: [
6103
+ context_menu?.visible && /* @__PURE__ */ jsxs14(Fragment6, { children: [
5949
6104
  /* @__PURE__ */ jsx15(
5950
6105
  "div",
5951
6106
  {
@@ -6152,4 +6307,4 @@ export {
6152
6307
  PdfViewer,
6153
6308
  pdf_viewer_default
6154
6309
  };
6155
- //# sourceMappingURL=chunk-ROKBDX4O.js.map
6310
+ //# sourceMappingURL=chunk-YEHKU7NV.js.map