# Drag & Drop Attachment Uploader

## Purpose

This Yeeflow Custom Code control adds a drag-and-drop upload area while retaining click-to-select behavior. It supports configurable single-file and multiple-file modes and writes uploaded Attachment metadata to a bound Attachment form variable.

## Supported placement

- Approval Form Custom Code control (`codein`)
- Data List custom form is structurally compatible, but its writeback must be runtime-tested separately.
- Dashboard and public-form persistence are not claimed.

## Parameters

| Parameter | Type | Required | Purpose |
| --- | --- | --- | --- |
| `attachmentTarget` | Variable | Yes | Select the writable Attachment variable. The control captures its configured variable ID and reads/writes its current value. |
| `multiple` | Variable | No | `true` enables multiple files; default is single-file. |
| `acceptedFileTypes` | String | No | Extensions/MIME types such as `.pdf,.docx,image/*`. Empty means all. |
| `maxFileSizeMB` | String | No | Maximum size per file; default `20`. |
| `maxFileCount` | String | No | Maximum number of files; default `10`. |
| `title` | String | No | Drop-zone title. |
| `helperText` | String | No | Drop-zone supporting text. When provided, it replaces the automatically generated file-rule summary; when empty, the control shows that summary as the fallback. |
| `displaySize` | String | No | Upload-area density: `Large`, `Medium`, or `Small`. Default `Large`. Values are case-insensitive. |
| `hideUploadedFiles` | Variable | No | `true` hides the uploaded-file list rendered by this Custom Code control. Default `false`, so the list is shown. |

## Upload strategy and persistence boundary

1. The Custom Code control handles click/drop selection and client-side validation.
2. It uploads each selected file through `yeeSDKClient.files.upload({ fileName, file: arrayBuffer })`.
3. It normalizes the SDK response to Yeeflow Attachment metadata and writes that value directly to the configured Attachment variable through the form/variable setter exposed by the runtime.
4. Single-file mode writes one Attachment object. Multiple-file mode writes an array and appends later uploads until `maxFileCount` is reached.
5. Delete updates the same bound variable directly. It does not search for or click a native Attachment menu.
6. The control neither searches for nor changes any native Attachment control. Native-control visibility remains entirely under the form designer's configuration.

For the verified single-file case, the native control persists an object such as `{"id":"<uuid>","name":"VendorQuotation_001.jpg","fileSize":152580}`. The control deliberately does not manufacture this payload or wrap it in an array.

## Approval Form setup

1. Create or reuse an Attachment variable, for example `VendorQuotation`.
2. Add a Custom Code control and paste `drag-drop-attachment-uploader.tsx`.
3. Bind `attachmentTarget` to `Workflow Variables:VendorQuotation`.
4. Set `multiple` to `false` for one quotation or `true` for supporting-document batches.
5. Configure `acceptedFileTypes`, for example `.pdf,.png,.jpg,.jpeg`.
6. Configure `maxFileSizeMB` and `maxFileCount` according to the tenant policy.
7. Set `displaySize` to `Large`, `Medium`, or `Small`. Leave it empty for the default `Large` layout.
8. Set `hideUploadedFiles=false` (the default) to show the Custom Code file-card list, including Preview, Download and Delete.
9. Set `hideUploadedFiles=true` only when the Custom Code file-card list should be hidden. This setting does not change any native Attachment control.

## Behavior

- Dragging and dropping files and clicking **Choose file** use the same validation/upload flow.
- `Large` provides the full centered drop zone and all guidance text; `Medium` reduces height, padding, icon size, and spacing while retaining the same information; `Small` uses a compact horizontal layout and hides the secondary accepted-file rules line to fit field-dense forms.
- Display size changes presentation only. Validation, SDK upload, variable writeback, file cards, Preview, Download, Delete, and Download all behavior remain unchanged.
- In single-file mode, the upload/drop zone is shown only while the Attachment variable is empty. After one file is registered it is hidden; deleting that file and clearing the variable makes the zone reappear automatically.
- In multiple-file mode, the upload/drop zone remains visible until `maxFileCount` is reached and reappears when the count falls below the limit.
- Multiple-file mode appends uploaded files up to `maxFileCount` and removes duplicates by file identifier or name/size fallback.
- File cards use responsive CSS grid layout, allowing multiple files on one row when space permits.
- When `hideUploadedFiles=false` (default), the Custom Code file-card list is shown with Preview, Download and Delete actions.
- When `hideUploadedFiles=true`, the Custom Code file-card list is hidden. Upload and Attachment-variable persistence continue to work.
- In multiple-file mode, an Outlook-style **Download all** text action appears below the visible file list only when at least two files have been uploaded. It retrieves all listed files through `yeeSDKClient.files.getContent`, creates an uncompressed ZIP in the browser, and downloads it as `attachments-<timestamp>.zip`.
- **Download all** is not shown for zero or one file, in single-file mode, or when `hideUploadedFiles=true`.
- File badges distinguish PDF, image, Word, Excel/CSV, PowerPoint, archive, text and generic file types.
- Preview is shown only for browser-supported formats: PDF; PNG, JPG/JPEG, GIF, WebP, BMP and SVG images; TXT, Markdown, JSON and XML text; and CSV. For these formats, clicking the filename or Preview fetches content by attachment ID through `yeeSDKClient.files.getContent` and opens a blob preview. Office files, archives and unknown formats show Download only.
- Download fetches the same protected content and saves it under the attachment name.
- Delete removes the selected attachment from the bound variable directly. In single-file mode it writes `null`; in multiple-file mode it writes the remaining array.
- Read-only task forms completely hide the upload/drop zone, continue to display uploaded files, and keep preview/download functions available. Remove operations remain hidden.
- File names are rendered as React text, not raw HTML.

## Test checklist

- Open a new Approval Form and confirm the drop zone renders.
- Test `Large`, `Medium`, and `Small` on desktop and narrow widths; confirm only layout density changes.
- Bind `attachmentTarget` and verify the configured variable is included in `requiredFields()` even when its current value is empty.
- Test click upload with one small PDF.
- Test drag upload with one small PDF.
- Confirm the Attachment variable changes immediately and survives form save/reopen.
- Enable multiple mode and test two files in one drop.
- Confirm single mode keeps only one file.
- Test extension, size and maximum-count rejections.
- Test removing one attachment and saving the form.
- Test Preview and Download for at least one JPG and one PDF.
- In multiple-file mode, select **Download all**, open the resulting ZIP, and verify every file name and file content.
- Test with no native Attachment controls on the page and confirm both uploaders still persist independently.
- Test both `hideUploadedFiles=false` and `true`; confirm only the Custom Code file-card list changes.
- Open a read-only/task page and confirm the upload/drop zone and remove actions are hidden while preview/download remain available.
- Confirm the browser network log shows one SDK upload request per selected file and no duplicate upload.

## Troubleshooting

- **SDK upload unavailable:** confirm the Custom Code runtime exposes `context.modules.yeeSDKClient.files.upload`.
- **Upload succeeds but variable remains empty:** verify `attachmentTarget` is bound to the intended Attachment variable through the variable picker rather than supplied as a display label or current value.
- **Click works but drop does not:** confirm the browser supports `DataTransfer` and that no parent control intercepts `drop` events.
- **File appears but does not survive save:** confirm the runtime exposes a supported form/variable setter and that the status says `Attachment registered in the form.`.
- **Delete does not persist:** verify the configured variable ID is exact and that the form runtime exposes a writable setter for Approval Form variables.
- **Download all fails:** verify every attachment has a valid file ID and the runtime exposes `yeeSDKClient.files.getContent`. ZIP creation occurs in browser memory, so the total selected-file size must remain practical for the user's device.
