@devm7mdali/pdf-maker
Version:
PDF maker web component
198 lines (167 loc) • 6.19 kB
JavaScript
/**
* Custom element for converting HTML to PDF
* @element pdf-maker
* @attr {string} api-key - API key for authentication
* @attr {string} endpoint - PDF generation endpoint URL
* @attr {string} filename - Default filename for downloaded PDF
* @attr {'portrait'|'landscape'} orientation - PDF orientation
* @attr {string} placeholder - Textarea placeholder text
* @fires pdf-maker:start - Fired when PDF generation starts
* @fires pdf-maker:success - Fired when PDF is successfully generated
* @fires pdf-maker:error - Fired when PDF generation fails
*/
class PDFMaker extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._defaultFilename = 'document.pdf';
this._textarea = null;
this._button = null;
this._status = null;
}
static get observedAttributes() {
//! explained at the top
return ['api-key', 'endpoint', 'filename', 'orientation', 'placeholder'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) return;
switch (name) {
case 'placeholder':
if (this._textarea) {
this._textarea.placeholder = newValue || 'Enter / paste HTML to convert to PDF';
}
break;
case 'filename':
this._defaultFilename = newValue || 'document.pdf';
break;
}
}
// Public API Properties (matching TypeScript declarations)
get html() {
return this._textarea?.value || '';
}
set html(value) {
if (this._textarea) {
this._textarea.value = value || '';
}
}
get loading() {
return this._button?.disabled || false;
}
get status() {
return this._status?.textContent || '';
}
// Public API Methods (matching TypeScript declarations)
async generatePDF() {
if (!this._button) {
throw new Error('Component not ready');
}
// Return a promise that resolves with the blob
return new Promise((resolve, reject) => {
const handleSuccess = (e) => {
this.removeEventListener('pdf-maker:success', handleSuccess);
this.removeEventListener('pdf-maker:error', handleError);
resolve(e.detail.blob);
};
const handleError = (e) => {
this.removeEventListener('pdf-maker:success', handleSuccess);
this.removeEventListener('pdf-maker:error', handleError);
reject(e.detail.error);
};
this.addEventListener('pdf-maker:success', handleSuccess, { once: true });
this.addEventListener('pdf-maker:error', handleError, { once: true });
// Trigger the button click
this._button.click();
});
}
// Private method to dispatch custom events
_dispatch(eventName, detail = {}) {
this.dispatchEvent(new CustomEvent(`pdf-maker:${eventName}`, {
detail,
bubbles: true,
cancelable: false
}));
}
connectedCallback() {
this.getAttribute('api-key');
const endpoint = this.getAttribute('endpoint') || 'https://your-api.com/upload';
const defaultFilename = this.getAttribute('filename') || 'document.pdf';
this.getAttribute('orientation') || 'portrait';
const style = document.createElement('style');
style.textContent = `
.uploader { display: flex; flex-direction: column; gap: 8px; font-family: system-ui,sans-serif; }
textarea { border: 1px solid #ccc; border-radius: 6px; padding: 6px; font-family: monospace; }
button { background:#4f46e5; color:#fff; border:none; padding:8px 12px; border-radius:6px; cursor:pointer; }
button:disabled { opacity:.6; cursor:progress; }
.status { font-size:12px; color:#555; min-height:16px; }
`;
const wrapper = document.createElement('div');
wrapper.className = 'uploader';
const input = document.createElement('textarea');
input.rows = 6;
input.placeholder = 'Enter / paste HTML to convert to PDF';
const button = document.createElement('button');
button.textContent = 'Generate PDF';
const status = document.createElement('div');
status.className = 'status';
// Store references for public API access
this._textarea = input;
this._button = button;
this._status = status;
button.addEventListener('click', async () => {
const html = input.value.trim();
if (!html) return alert('Please provide HTML content.');
// Dispatch start event
this._dispatch('start', { html });
status.textContent = 'Requesting PDF...';
status.className = 'status';
button.disabled = true;
try {
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ html, orientation: 'portrait' })
});
const contentType = res.headers.get('content-type') || '';
if (!res.ok) {
let detail = '';
try { detail = contentType.includes('json') ? JSON.stringify(await res.json()) : (await res.text()).slice(0, 200); } catch { }
throw new Error(`HTTP ${res.status} ${detail}`);
}
if (!contentType.includes('application/pdf')) {
throw new Error(`Unexpected content-type ${contentType}`);
}
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = defaultFilename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
status.textContent = 'PDF downloaded.';
} catch (err) {
const errorMessage = 'Error: ' + (err.message || err);
status.textContent = errorMessage;
status.className = 'status error';
// Dispatch error event
this._dispatch('error', {
error: err,
html
});
} finally {
button.disabled = false;
}
});
wrapper.appendChild(input);
wrapper.appendChild(button);
wrapper.appendChild(status);
this.shadowRoot.append(style, wrapper);
}
}
customElements.define('pdf-maker', PDFMaker);
export { PDFMaker as default };
//# sourceMappingURL=pdf-maker.esm.js.map