mgcode-docx-editor 0.1.0

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/index.js ADDED
@@ -0,0 +1,1483 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DocxEditor: () => DocxEditor_default
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/DocxEditor.tsx
38
+ var import_react3 = require("react");
39
+ var import_material4 = require("@mui/material");
40
+
41
+ // src/docx/generate.ts
42
+ var import_html_to_docx = __toESM(require("@turbodocx/html-to-docx"));
43
+ var import_jszip = __toESM(require("jszip"));
44
+ var import_buffer = require("buffer");
45
+
46
+ // src/docx/pageFields.ts
47
+ var PAGE_NUMBER_TOKEN = "[[PAGINA]]";
48
+ var TOTAL_PAGES_TOKEN = "[[TOTAL_PAGINAS]]";
49
+ var FIELD_BY_TOKEN = [
50
+ { token: PAGE_NUMBER_TOKEN, instr: "PAGE" },
51
+ { token: TOTAL_PAGES_TOKEN, instr: "NUMPAGES" }
52
+ ];
53
+ function hasPageFieldTokens(html) {
54
+ if (!html) return false;
55
+ return FIELD_BY_TOKEN.some(({ token }) => html.includes(token));
56
+ }
57
+ var W_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
58
+ var RUN_PATTERN = /<(w:)?r(?:\s[^>]*)?>[\s\S]*?<\/(?:w:)?r>/g;
59
+ function splitTextByTokens(text) {
60
+ const parts = [];
61
+ let rest = text;
62
+ while (rest.length > 0) {
63
+ let earliest = -1;
64
+ let found = null;
65
+ for (const candidate of FIELD_BY_TOKEN) {
66
+ const i = rest.indexOf(candidate.token);
67
+ if (i !== -1 && (earliest === -1 || i < earliest)) {
68
+ earliest = i;
69
+ found = candidate;
70
+ }
71
+ }
72
+ if (!found) {
73
+ parts.push({ kind: "text", value: rest });
74
+ break;
75
+ }
76
+ if (earliest > 0) parts.push({ kind: "text", value: rest.slice(0, earliest) });
77
+ parts.push({ kind: "field", instr: found.instr });
78
+ rest = rest.slice(earliest + found.token.length);
79
+ }
80
+ return parts;
81
+ }
82
+ function rebuildRun(runXml, prefix) {
83
+ const tMatch = runXml.match(/<(?:w:)?t([^>]*)>([\s\S]*?)<\/(?:w:)?t>/);
84
+ if (!tMatch) return runXml;
85
+ const [, tAttrs, text] = tMatch;
86
+ if (!FIELD_BY_TOKEN.some(({ token }) => text.includes(token))) return runXml;
87
+ const rPrMatch = runXml.match(/<(?:w:)?rPr(?:\/>|>[\s\S]*?<\/(?:w:)?rPr>)/);
88
+ const props = rPrMatch ? rPrMatch[0] : "";
89
+ const p = prefix;
90
+ const tOpen = `<${p}t${tAttrs}>`;
91
+ const tClose = `</${p}t>`;
92
+ const nsDecl = p ? "" : ` xmlns:w="${W_NAMESPACE}"`;
93
+ return splitTextByTokens(text).map(
94
+ (part) => part.kind === "text" ? `<${p}r>${props}${tOpen}${part.value}${tClose}</${p}r>` : (
95
+ // "1" é o valor em cache exibido até o Word/LibreOffice atualizar o campo.
96
+ `<${p}fldSimple${nsDecl} w:instr=" ${part.instr} "><${p}r>${props}${tOpen}1${tClose}</${p}r></${p}fldSimple>`
97
+ )
98
+ ).join("");
99
+ }
100
+ function injectPageFields(xml) {
101
+ return xml.replace(
102
+ RUN_PATTERN,
103
+ (runXml, prefix) => rebuildRun(runXml, prefix ?? "")
104
+ );
105
+ }
106
+
107
+ // src/docx/generate.ts
108
+ var DEFAULT_MARGINS = {
109
+ top: 1440,
110
+ right: 1440,
111
+ bottom: 1440,
112
+ left: 1440
113
+ };
114
+ var PAGE_SIZE_A4_TWIPS = { width: 11906, height: 16838 };
115
+ function ensureBufferGlobal() {
116
+ const g = globalThis;
117
+ if (typeof g.Buffer === "undefined") g.Buffer = import_buffer.Buffer;
118
+ }
119
+ async function toArrayBuffer(out) {
120
+ if (out instanceof ArrayBuffer) return out;
121
+ if (typeof Blob !== "undefined" && out instanceof Blob) {
122
+ return out.arrayBuffer();
123
+ }
124
+ const u8 = out;
125
+ return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);
126
+ }
127
+ function wrapBody(bodyHtml) {
128
+ return `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>${bodyHtml}</body></html>`;
129
+ }
130
+ async function modelToDocx(model, options = {}) {
131
+ ensureBufferGlobal();
132
+ const margins = options.margins ?? DEFAULT_MARGINS;
133
+ const hasHeader = !!model.header && model.header.trim().length > 0;
134
+ const hasFooter = !!model.footer && model.footer.trim().length > 0;
135
+ const out = await (0, import_html_to_docx.default)(
136
+ wrapBody(model.body),
137
+ hasHeader ? model.header : null,
138
+ {
139
+ header: hasHeader,
140
+ footer: hasFooter,
141
+ margins,
142
+ pageSize: PAGE_SIZE_A4_TWIPS,
143
+ font: options.font ?? "Arial"
144
+ },
145
+ hasFooter ? model.footer : null
146
+ );
147
+ const buffer = await toArrayBuffer(out);
148
+ if (!hasPageFieldTokens(model.header) && !hasPageFieldTokens(model.footer)) {
149
+ return buffer;
150
+ }
151
+ const zip = await import_jszip.default.loadAsync(buffer);
152
+ const partNames = Object.keys(zip.files).filter(
153
+ (name) => /^word\/(header|footer)\d*\.xml$/.test(name)
154
+ );
155
+ for (const name of partNames) {
156
+ const xml = await zip.file(name).async("string");
157
+ zip.file(name, injectPageFields(xml));
158
+ }
159
+ return zip.generateAsync({ type: "arraybuffer" });
160
+ }
161
+
162
+ // src/docx/importDocx.ts
163
+ var import_mammoth = __toESM(require("mammoth"));
164
+ async function bufferToModel(buffer) {
165
+ const input = typeof Buffer !== "undefined" ? { buffer: Buffer.from(buffer) } : { arrayBuffer: buffer };
166
+ const { value: bodyHtml } = await import_mammoth.default.convertToHtml(input);
167
+ return {
168
+ body: bodyHtml
169
+ };
170
+ }
171
+
172
+ // src/docx/pagination.ts
173
+ function computePageLayout(blocks, metrics) {
174
+ const { usableHeightPx, interPageGapPx } = metrics;
175
+ const stride = usableHeightPx + interPageGapPx;
176
+ const spacers = [];
177
+ let added = 0;
178
+ let maxEnd = 0;
179
+ blocks.forEach((block, index) => {
180
+ let start = block.top + added;
181
+ const page = Math.floor(start / stride);
182
+ const regionStart = page * stride;
183
+ const regionEnd = regionStart + usableHeightPx;
184
+ const fitsInAPage = block.height <= usableHeightPx;
185
+ const crossesBottom = start + block.height > regionEnd;
186
+ if (start >= regionEnd || crossesBottom && fitsInAPage && start > regionStart) {
187
+ const nextStart = (page + 1) * stride;
188
+ const height = nextStart - start;
189
+ spacers.push({ beforeBlockIndex: index, heightPx: height });
190
+ added += height;
191
+ start = nextStart;
192
+ }
193
+ maxEnd = Math.max(maxEnd, start + block.height);
194
+ });
195
+ const pageCount = Math.max(1, Math.floor(Math.max(0, maxEnd - 1) / stride) + 1);
196
+ return { spacers, pageCount };
197
+ }
198
+ var PAGE_SPACER_ATTR = "data-page-spacer";
199
+ function createSpacerElement(heightPx) {
200
+ const el = document.createElement("div");
201
+ el.setAttribute(PAGE_SPACER_ATTR, "true");
202
+ el.setAttribute("contenteditable", "false");
203
+ el.setAttribute("aria-hidden", "true");
204
+ el.style.height = `${heightPx}px`;
205
+ el.style.userSelect = "none";
206
+ el.style.pointerEvents = "none";
207
+ return el;
208
+ }
209
+ function stripPageSpacers(html) {
210
+ const tmp = document.createElement("div");
211
+ tmp.innerHTML = html;
212
+ tmp.querySelectorAll(`[${PAGE_SPACER_ATTR}]`).forEach((n) => n.remove());
213
+ return tmp.innerHTML;
214
+ }
215
+
216
+ // src/editorCommands.ts
217
+ function execFormat(command, value) {
218
+ document.execCommand("styleWithCSS", false, "true");
219
+ document.execCommand(command, false, value);
220
+ }
221
+ function setFontSize(region, pt) {
222
+ document.execCommand("styleWithCSS", false, "false");
223
+ document.execCommand("fontSize", false, "7");
224
+ const marked = region.querySelectorAll('font[size="7"]');
225
+ marked.forEach((el) => {
226
+ el.removeAttribute("size");
227
+ el.style.fontSize = `${pt}pt`;
228
+ });
229
+ }
230
+ function setFontFamily(family) {
231
+ document.execCommand("styleWithCSS", false, "true");
232
+ document.execCommand("fontName", false, family);
233
+ }
234
+ function insertTextIntoRegion(region, text) {
235
+ region.focus();
236
+ const selection = window.getSelection();
237
+ const node = document.createTextNode(text);
238
+ if (selection && selection.rangeCount > 0 && selection.anchorNode && region.contains(selection.anchorNode)) {
239
+ const range = selection.getRangeAt(0);
240
+ range.deleteContents();
241
+ range.insertNode(node);
242
+ range.setStartAfter(node);
243
+ range.setEndAfter(node);
244
+ selection.removeAllRanges();
245
+ selection.addRange(range);
246
+ } else {
247
+ region.appendChild(node);
248
+ }
249
+ }
250
+
251
+ // src/docx/units.ts
252
+ var TWIPS_PER_INCH = 1440;
253
+ var PX_PER_INCH = 96;
254
+ function pxToTwips(px) {
255
+ return Math.round(px / PX_PER_INCH * TWIPS_PER_INCH);
256
+ }
257
+ function twipsToPx(twips) {
258
+ return twips / TWIPS_PER_INCH * PX_PER_INCH;
259
+ }
260
+
261
+ // src/Toolbar.tsx
262
+ var import_react = require("react");
263
+ var import_material = require("@mui/material");
264
+ var import_Undo = __toESM(require("@mui/icons-material/Undo"));
265
+ var import_Redo = __toESM(require("@mui/icons-material/Redo"));
266
+ var import_FormatBold = __toESM(require("@mui/icons-material/FormatBold"));
267
+ var import_FormatItalic = __toESM(require("@mui/icons-material/FormatItalic"));
268
+ var import_FormatUnderlined = __toESM(require("@mui/icons-material/FormatUnderlined"));
269
+ var import_StrikethroughS = __toESM(require("@mui/icons-material/StrikethroughS"));
270
+ var import_FormatColorText = __toESM(require("@mui/icons-material/FormatColorText"));
271
+ var import_BorderColor = __toESM(require("@mui/icons-material/BorderColor"));
272
+ var import_Link = __toESM(require("@mui/icons-material/Link"));
273
+ var import_Superscript = __toESM(require("@mui/icons-material/Superscript"));
274
+ var import_Subscript = __toESM(require("@mui/icons-material/Subscript"));
275
+ var import_FormatAlignLeft = __toESM(require("@mui/icons-material/FormatAlignLeft"));
276
+ var import_FormatAlignCenter = __toESM(require("@mui/icons-material/FormatAlignCenter"));
277
+ var import_FormatAlignRight = __toESM(require("@mui/icons-material/FormatAlignRight"));
278
+ var import_FormatAlignJustify = __toESM(require("@mui/icons-material/FormatAlignJustify"));
279
+ var import_FormatListBulleted = __toESM(require("@mui/icons-material/FormatListBulleted"));
280
+ var import_FormatListNumbered = __toESM(require("@mui/icons-material/FormatListNumbered"));
281
+ var import_FormatIndentDecrease = __toESM(require("@mui/icons-material/FormatIndentDecrease"));
282
+ var import_FormatIndentIncrease = __toESM(require("@mui/icons-material/FormatIndentIncrease"));
283
+ var import_FormatClear = __toESM(require("@mui/icons-material/FormatClear"));
284
+ var import_Image = __toESM(require("@mui/icons-material/Image"));
285
+ var import_DataObject = __toESM(require("@mui/icons-material/DataObject"));
286
+ var import_Add = __toESM(require("@mui/icons-material/Add"));
287
+ var import_Remove = __toESM(require("@mui/icons-material/Remove"));
288
+ var import_jsx_runtime = require("react/jsx-runtime");
289
+ var FONT_FAMILIES = [
290
+ "Arial",
291
+ "Times New Roman",
292
+ "Calibri",
293
+ "Courier New",
294
+ "Georgia",
295
+ "Verdana"
296
+ ];
297
+ var BLOCK_FORMATS = [
298
+ { label: "Texto normal", tag: "P" },
299
+ { label: "T\xEDtulo 1", tag: "H1" },
300
+ { label: "T\xEDtulo 2", tag: "H2" },
301
+ { label: "T\xEDtulo 3", tag: "H3" }
302
+ ];
303
+ function Toolbar(props) {
304
+ const [fontFamily, setFontFamilyState] = (0, import_react.useState)("Arial");
305
+ const [fontSize, setFontSizeState] = (0, import_react.useState)(11);
306
+ const [blockTag, setBlockTag] = (0, import_react.useState)("P");
307
+ const [varAnchor, setVarAnchor] = (0, import_react.useState)(null);
308
+ const colorInputRef = (0, import_react.useRef)(null);
309
+ const highlightInputRef = (0, import_react.useRef)(null);
310
+ const changeFontSize = (next) => {
311
+ const clamped = Math.min(96, Math.max(1, next));
312
+ setFontSizeState(clamped);
313
+ props.onFontSize(clamped);
314
+ };
315
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
316
+ import_material.Box,
317
+ {
318
+ sx: {
319
+ display: "flex",
320
+ alignItems: "center",
321
+ flexWrap: "wrap",
322
+ gap: 0.5,
323
+ px: 1,
324
+ py: 0.5,
325
+ borderBottom: 1,
326
+ borderColor: "divider",
327
+ bgcolor: "background.paper"
328
+ },
329
+ onMouseDown: (e) => e.preventDefault(),
330
+ children: [
331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Desfazer", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("undo"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Undo.default, { fontSize: "small" }) }) }),
332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Refazer", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("redo"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Redo.default, { fontSize: "small" }) }) }),
333
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Divider, { orientation: "vertical", flexItem: true, sx: { mx: 0.5 } }),
334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
335
+ import_material.Select,
336
+ {
337
+ size: "small",
338
+ value: blockTag,
339
+ onChange: (e) => {
340
+ setBlockTag(e.target.value);
341
+ props.onFormatBlock(e.target.value);
342
+ },
343
+ sx: { minWidth: 130, height: 32 },
344
+ children: BLOCK_FORMATS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.MenuItem, { value: b.tag, children: b.label }, b.tag))
345
+ }
346
+ ),
347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
348
+ import_material.Select,
349
+ {
350
+ size: "small",
351
+ value: fontFamily,
352
+ onChange: (e) => {
353
+ setFontFamilyState(e.target.value);
354
+ props.onFontFamily(e.target.value);
355
+ },
356
+ sx: { minWidth: 130, height: 32 },
357
+ children: FONT_FAMILIES.map((f) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.MenuItem, { value: f, style: { fontFamily: f }, children: f }, f))
358
+ }
359
+ ),
360
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_material.Box, { sx: { display: "flex", alignItems: "center" }, children: [
361
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Diminuir fonte", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => changeFontSize(fontSize - 1), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Remove.default, { fontSize: "small" }) }) }),
362
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
363
+ import_material.Typography,
364
+ {
365
+ component: "span",
366
+ sx: {
367
+ minWidth: 24,
368
+ textAlign: "center",
369
+ fontVariantNumeric: "tabular-nums"
370
+ },
371
+ children: fontSize
372
+ }
373
+ ),
374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Aumentar fonte", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => changeFontSize(fontSize + 1), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Add.default, { fontSize: "small" }) }) })
375
+ ] }),
376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Divider, { orientation: "vertical", flexItem: true, sx: { mx: 0.5 } }),
377
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Negrito", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("bold"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatBold.default, { fontSize: "small" }) }) }),
378
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "It\xE1lico", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("italic"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatItalic.default, { fontSize: "small" }) }) }),
379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Sublinhado", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("underline"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatUnderlined.default, { fontSize: "small" }) }) }),
380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Tachado", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
381
+ import_material.IconButton,
382
+ {
383
+ size: "small",
384
+ onClick: () => props.onCommand("strikeThrough"),
385
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_StrikethroughS.default, { fontSize: "small" })
386
+ }
387
+ ) }),
388
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Cor do texto", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => colorInputRef.current?.click(), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatColorText.default, { fontSize: "small" }) }) }),
389
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
390
+ "input",
391
+ {
392
+ ref: colorInputRef,
393
+ type: "color",
394
+ "aria-label": "Cor do texto",
395
+ style: { display: "none" },
396
+ onChange: (e) => props.onColor(e.target.value)
397
+ }
398
+ ),
399
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Realce", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
400
+ import_material.IconButton,
401
+ {
402
+ size: "small",
403
+ onClick: () => highlightInputRef.current?.click(),
404
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_BorderColor.default, { fontSize: "small" })
405
+ }
406
+ ) }),
407
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
408
+ "input",
409
+ {
410
+ ref: highlightInputRef,
411
+ type: "color",
412
+ "aria-label": "Cor de realce",
413
+ style: { display: "none" },
414
+ onChange: (e) => props.onHighlight(e.target.value)
415
+ }
416
+ ),
417
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Inserir link", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: props.onLink, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Link.default, { fontSize: "small" }) }) }),
418
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Sobrescrito", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("superscript"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Superscript.default, { fontSize: "small" }) }) }),
419
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Subscrito", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("subscript"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Subscript.default, { fontSize: "small" }) }) }),
420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Divider, { orientation: "vertical", flexItem: true, sx: { mx: 0.5 } }),
421
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Alinhar \xE0 esquerda", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("justifyLeft"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatAlignLeft.default, { fontSize: "small" }) }) }),
422
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Centralizar", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
423
+ import_material.IconButton,
424
+ {
425
+ size: "small",
426
+ onClick: () => props.onCommand("justifyCenter"),
427
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatAlignCenter.default, { fontSize: "small" })
428
+ }
429
+ ) }),
430
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Alinhar \xE0 direita", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
431
+ import_material.IconButton,
432
+ {
433
+ size: "small",
434
+ onClick: () => props.onCommand("justifyRight"),
435
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatAlignRight.default, { fontSize: "small" })
436
+ }
437
+ ) }),
438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Justificar", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("justifyFull"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatAlignJustify.default, { fontSize: "small" }) }) }),
439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Lista com marcadores", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
440
+ import_material.IconButton,
441
+ {
442
+ size: "small",
443
+ onClick: () => props.onCommand("insertUnorderedList"),
444
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatListBulleted.default, { fontSize: "small" })
445
+ }
446
+ ) }),
447
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Lista numerada", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
448
+ import_material.IconButton,
449
+ {
450
+ size: "small",
451
+ onClick: () => props.onCommand("insertOrderedList"),
452
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatListNumbered.default, { fontSize: "small" })
453
+ }
454
+ ) }),
455
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Diminuir recuo", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("outdent"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatIndentDecrease.default, { fontSize: "small" }) }) }),
456
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Aumentar recuo", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: () => props.onCommand("indent"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatIndentIncrease.default, { fontSize: "small" }) }) }),
457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Divider, { orientation: "vertical", flexItem: true, sx: { mx: 0.5 } }),
458
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Inserir imagem", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.IconButton, { size: "small", onClick: props.onInsertImage, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Image.default, { fontSize: "small" }) }) }),
459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Tooltip, { title: "Limpar formata\xE7\xE3o", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
460
+ import_material.IconButton,
461
+ {
462
+ size: "small",
463
+ onClick: () => props.onCommand("removeFormat"),
464
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_FormatClear.default, { fontSize: "small" })
465
+ }
466
+ ) }),
467
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
468
+ import_material.Button,
469
+ {
470
+ size: "small",
471
+ variant: "outlined",
472
+ startIcon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_DataObject.default, {}),
473
+ onClick: (e) => setVarAnchor(e.currentTarget),
474
+ sx: { ml: 0.5, textTransform: "none" },
475
+ children: "Vari\xE1vel"
476
+ }
477
+ ),
478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
479
+ import_material.Menu,
480
+ {
481
+ anchorEl: varAnchor,
482
+ open: Boolean(varAnchor),
483
+ onClose: () => setVarAnchor(null),
484
+ children: [
485
+ props.variables.map((name) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
486
+ import_material.MenuItem,
487
+ {
488
+ onClick: () => {
489
+ props.onInsertVariable(name);
490
+ setVarAnchor(null);
491
+ },
492
+ children: name
493
+ },
494
+ name
495
+ )),
496
+ props.variables.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_material.Divider, {}),
497
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
498
+ import_material.MenuItem,
499
+ {
500
+ onClick: () => {
501
+ props.onInsertPageField("pageNumber");
502
+ setVarAnchor(null);
503
+ },
504
+ children: "N\xBA da p\xE1gina"
505
+ }
506
+ ),
507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
508
+ import_material.MenuItem,
509
+ {
510
+ onClick: () => {
511
+ props.onInsertPageField("totalPages");
512
+ setVarAnchor(null);
513
+ },
514
+ children: "Total de p\xE1ginas"
515
+ }
516
+ )
517
+ ]
518
+ }
519
+ )
520
+ ]
521
+ }
522
+ );
523
+ }
524
+ var Toolbar_default = Toolbar;
525
+
526
+ // src/ZoomControl.tsx
527
+ var import_material2 = require("@mui/material");
528
+ var import_jsx_runtime2 = require("react/jsx-runtime");
529
+ var ZOOM_LEVELS = [0.5, 0.75, 0.9, 1, 1.25, 1.5, 2];
530
+ function ZoomControl(props) {
531
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
532
+ import_material2.Select,
533
+ {
534
+ size: "small",
535
+ value: props.zoom,
536
+ onChange: (e) => props.onChange(Number(e.target.value)),
537
+ "aria-label": "Zoom",
538
+ sx: { height: 32, minWidth: 88 },
539
+ children: ZOOM_LEVELS.map((z) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_material2.MenuItem, { value: z, children: [
540
+ Math.round(z * 100),
541
+ "%"
542
+ ] }, z))
543
+ }
544
+ );
545
+ }
546
+ var ZoomControl_default = ZoomControl;
547
+
548
+ // src/Ruler.tsx
549
+ var import_react2 = require("react");
550
+ var import_material3 = require("@mui/material");
551
+ var import_jsx_runtime3 = require("react/jsx-runtime");
552
+ var RULER_HEIGHT = 24;
553
+ function Ruler(props) {
554
+ const { pageWidthPx, zoom, margins, indent } = props;
555
+ const barRef = (0, import_react2.useRef)(null);
556
+ const scaledWidth = pageWidthPx * zoom;
557
+ const screenPxToTwips = (0, import_react2.useCallback)(
558
+ (px) => pxToTwips(px / zoom),
559
+ [zoom]
560
+ );
561
+ const leftMarginPx = twipsToPx(margins.left) * zoom;
562
+ const rightMarginFromLeftPx = (pageWidthPx - twipsToPx(margins.right)) * zoom;
563
+ const firstLinePx = (twipsToPx(margins.left) + twipsToPx(indent.firstLine)) * zoom;
564
+ const leftIndentPx = (twipsToPx(margins.left) + twipsToPx(indent.left)) * zoom;
565
+ const rightIndentPx = (pageWidthPx - twipsToPx(margins.right) - twipsToPx(indent.right)) * zoom;
566
+ const startDrag = (kind) => (e) => {
567
+ e.preventDefault();
568
+ const bar = barRef.current;
569
+ if (!bar) return;
570
+ const onMove = (ev) => {
571
+ const rect = bar.getBoundingClientRect();
572
+ const x = Math.min(Math.max(ev.clientX - rect.left, 0), scaledWidth);
573
+ const twipsFromLeft = screenPxToTwips(x);
574
+ const pageWidthTwips = pxToTwips(pageWidthPx);
575
+ switch (kind) {
576
+ case "leftMargin": {
577
+ const left = Math.min(twipsFromLeft, pageWidthTwips - margins.right - 720);
578
+ props.onMarginsChange({ ...margins, left: Math.max(0, left) });
579
+ break;
580
+ }
581
+ case "rightMargin": {
582
+ const right = Math.min(
583
+ pageWidthTwips - twipsFromLeft,
584
+ pageWidthTwips - margins.left - 720
585
+ );
586
+ props.onMarginsChange({ ...margins, right: Math.max(0, right) });
587
+ break;
588
+ }
589
+ case "firstLineIndent": {
590
+ props.onIndentChange({
591
+ ...indent,
592
+ firstLine: Math.max(0, twipsFromLeft - margins.left)
593
+ });
594
+ break;
595
+ }
596
+ case "leftIndent": {
597
+ props.onIndentChange({
598
+ ...indent,
599
+ left: Math.max(0, twipsFromLeft - margins.left)
600
+ });
601
+ break;
602
+ }
603
+ case "rightIndent": {
604
+ props.onIndentChange({
605
+ ...indent,
606
+ right: Math.max(0, pageWidthTwips - margins.right - twipsFromLeft)
607
+ });
608
+ break;
609
+ }
610
+ }
611
+ };
612
+ const onUp = () => {
613
+ window.removeEventListener("mousemove", onMove);
614
+ window.removeEventListener("mouseup", onUp);
615
+ };
616
+ window.addEventListener("mousemove", onMove);
617
+ window.addEventListener("mouseup", onUp);
618
+ };
619
+ const ticks = [];
620
+ const inches = Math.floor(pageWidthPx / PX_PER_INCH);
621
+ for (let i = 0; i <= inches; i++) {
622
+ ticks.push(
623
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
624
+ import_material3.Box,
625
+ {
626
+ sx: {
627
+ position: "absolute",
628
+ left: i * PX_PER_INCH * zoom,
629
+ top: 0,
630
+ bottom: 0,
631
+ borderLeft: "1px solid",
632
+ borderColor: "text.disabled",
633
+ fontSize: 9,
634
+ color: "text.secondary",
635
+ pl: "2px"
636
+ },
637
+ children: i
638
+ },
639
+ i
640
+ )
641
+ );
642
+ }
643
+ const marginHandleSx = {
644
+ position: "absolute",
645
+ top: 0,
646
+ width: 8,
647
+ height: RULER_HEIGHT,
648
+ cursor: "ew-resize",
649
+ bgcolor: "primary.main",
650
+ opacity: 0.6,
651
+ transform: "translateX(-50%)",
652
+ "&:hover": { opacity: 1 }
653
+ };
654
+ const indentHandleSx = {
655
+ position: "absolute",
656
+ width: 0,
657
+ height: 0,
658
+ cursor: "ew-resize",
659
+ borderLeft: "6px solid transparent",
660
+ borderRight: "6px solid transparent",
661
+ transform: "translateX(-50%)"
662
+ };
663
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
664
+ import_material3.Box,
665
+ {
666
+ ref: barRef,
667
+ sx: {
668
+ position: "relative",
669
+ height: RULER_HEIGHT,
670
+ width: scaledWidth,
671
+ bgcolor: "grey.100",
672
+ borderBottom: 1,
673
+ borderColor: "divider",
674
+ userSelect: "none",
675
+ mx: "auto"
676
+ },
677
+ role: "presentation",
678
+ "aria-label": "R\xE9gua",
679
+ children: [
680
+ ticks,
681
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
682
+ import_material3.Box,
683
+ {
684
+ sx: {
685
+ position: "absolute",
686
+ left: 0,
687
+ top: 0,
688
+ bottom: 0,
689
+ width: leftMarginPx,
690
+ bgcolor: "grey.300",
691
+ opacity: 0.5
692
+ }
693
+ }
694
+ ),
695
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
696
+ import_material3.Box,
697
+ {
698
+ sx: {
699
+ position: "absolute",
700
+ left: rightMarginFromLeftPx,
701
+ right: 0,
702
+ top: 0,
703
+ bottom: 0,
704
+ bgcolor: "grey.300",
705
+ opacity: 0.5
706
+ }
707
+ }
708
+ ),
709
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
710
+ import_material3.Box,
711
+ {
712
+ sx: { ...marginHandleSx, left: leftMarginPx },
713
+ onMouseDown: startDrag("leftMargin"),
714
+ "aria-label": "Margem esquerda"
715
+ }
716
+ ),
717
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
718
+ import_material3.Box,
719
+ {
720
+ sx: { ...marginHandleSx, left: rightMarginFromLeftPx },
721
+ onMouseDown: startDrag("rightMargin"),
722
+ "aria-label": "Margem direita"
723
+ }
724
+ ),
725
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
726
+ import_material3.Box,
727
+ {
728
+ sx: {
729
+ ...indentHandleSx,
730
+ left: firstLinePx,
731
+ top: 0,
732
+ borderTop: "7px solid",
733
+ borderTopColor: "primary.dark"
734
+ },
735
+ onMouseDown: startDrag("firstLineIndent"),
736
+ "aria-label": "Recuo da primeira linha"
737
+ }
738
+ ),
739
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
740
+ import_material3.Box,
741
+ {
742
+ sx: {
743
+ ...indentHandleSx,
744
+ left: leftIndentPx,
745
+ bottom: 0,
746
+ borderBottom: "7px solid",
747
+ borderBottomColor: "primary.dark"
748
+ },
749
+ onMouseDown: startDrag("leftIndent"),
750
+ "aria-label": "Recuo \xE0 esquerda"
751
+ }
752
+ ),
753
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
754
+ import_material3.Box,
755
+ {
756
+ sx: {
757
+ ...indentHandleSx,
758
+ left: rightIndentPx,
759
+ bottom: 0,
760
+ borderBottom: "7px solid",
761
+ borderBottomColor: "primary.dark"
762
+ },
763
+ onMouseDown: startDrag("rightIndent"),
764
+ "aria-label": "Recuo \xE0 direita"
765
+ }
766
+ )
767
+ ]
768
+ }
769
+ );
770
+ }
771
+ var Ruler_default = Ruler;
772
+
773
+ // src/DocxEditor.tsx
774
+ var import_jsx_runtime4 = require("react/jsx-runtime");
775
+ var PAGE_WIDTH_PX = 794;
776
+ var PAGE_HEIGHT_PX = 1123;
777
+ var PAGE_GAP_PX = 24;
778
+ var HEADER_FOOTER_DISTANCE_PX = 48;
779
+ var EMPTY_INDENT = { firstLine: 0, left: 0, right: 0 };
780
+ function EditableRegion(props) {
781
+ const setRef = (el) => {
782
+ if (el && el.dataset.initialized !== "1") {
783
+ el.innerHTML = props.html;
784
+ el.dataset.initialized = "1";
785
+ }
786
+ props.onRegister(el);
787
+ };
788
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
789
+ import_material4.Box,
790
+ {
791
+ component: "div",
792
+ ref: setRef,
793
+ contentEditable: props.editable,
794
+ suppressContentEditableWarning: true,
795
+ onFocus: (e) => props.onFocus(e.currentTarget),
796
+ onBlur: props.onBlur,
797
+ onInput: props.onInput,
798
+ "aria-label": props.ariaLabel,
799
+ "data-placeholder": props.placeholder,
800
+ sx: [
801
+ {
802
+ outline: "none",
803
+ position: "relative",
804
+ minHeight: 24,
805
+ "& p": { m: 0, mb: 1 },
806
+ "&:empty::before": {
807
+ content: "attr(data-placeholder)",
808
+ color: "text.disabled"
809
+ }
810
+ },
811
+ ...Array.isArray(props.sx) ? props.sx : props.sx ? [props.sx] : []
812
+ ]
813
+ }
814
+ );
815
+ }
816
+ function currentBlock(region) {
817
+ if (!region) return null;
818
+ const selection = window.getSelection();
819
+ let node = selection && selection.anchorNode ? selection.anchorNode : null;
820
+ if (!node || !region.contains(node)) {
821
+ return region.firstElementChild ?? region;
822
+ }
823
+ let el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
824
+ while (el && el !== region) {
825
+ if (/^(P|H1|H2|H3|H4|LI|DIV)$/.test(el.tagName) && el.parentElement === region) {
826
+ return el;
827
+ }
828
+ if (el.parentElement === region) return el;
829
+ el = el.parentElement;
830
+ }
831
+ return region.firstElementChild ?? region;
832
+ }
833
+ var mirrorSx = {
834
+ pointerEvents: "none",
835
+ "& p": { m: 0, mb: 1 }
836
+ };
837
+ function withPagePreview(html, pageNumber, totalPages) {
838
+ return html.split(PAGE_NUMBER_TOKEN).join(String(pageNumber)).split(TOTAL_PAGES_TOKEN).join(String(totalPages));
839
+ }
840
+ var RESIZE_CORNERS = ["nw", "ne", "sw", "se"];
841
+ var DocxEditor = (0, import_react3.forwardRef)(
842
+ function DocxEditor2(props, ref) {
843
+ const {
844
+ documentBuffer,
845
+ document: documentModel,
846
+ mode,
847
+ showToolbar = false,
848
+ showRuler = false,
849
+ showZoomControl = false
850
+ } = props;
851
+ const editable = mode === "editing";
852
+ const [model, setModel] = (0, import_react3.useState)(
853
+ () => documentModel ?? { body: "" }
854
+ );
855
+ const [docVersion, setDocVersion] = (0, import_react3.useState)(0);
856
+ const [loading, setLoading] = (0, import_react3.useState)(false);
857
+ const [zoom, setZoom] = (0, import_react3.useState)(1);
858
+ const [margins, setMargins] = (0, import_react3.useState)(DEFAULT_MARGINS);
859
+ const [indent, setIndent] = (0, import_react3.useState)(EMPTY_INDENT);
860
+ const [pageCount, setPageCount] = (0, import_react3.useState)(1);
861
+ const [mirror, setMirror] = (0, import_react3.useState)(() => ({
862
+ header: documentModel?.header ?? "",
863
+ footer: documentModel?.footer ?? ""
864
+ }));
865
+ const [chromeHeights, setChromeHeights] = (0, import_react3.useState)({ header: 0, footer: 0 });
866
+ const [focusedRegion, setFocusedRegion] = (0, import_react3.useState)(null);
867
+ const [selectedImg, setSelectedImg] = (0, import_react3.useState)(null);
868
+ const [frame, setFrame] = (0, import_react3.useState)(null);
869
+ const regionEls = (0, import_react3.useRef)({});
870
+ const activeRegion = (0, import_react3.useRef)(null);
871
+ const imageInputRef = (0, import_react3.useRef)(null);
872
+ const rafId = (0, import_react3.useRef)(null);
873
+ const pagesColumnRef = (0, import_react3.useRef)(null);
874
+ const padTop = twipsToPx(margins.top);
875
+ const padRight = twipsToPx(margins.right);
876
+ const padBottom = twipsToPx(margins.bottom);
877
+ const padLeft = twipsToPx(margins.left);
878
+ const bodyTopPx = Math.max(
879
+ padTop,
880
+ HEADER_FOOTER_DISTANCE_PX + chromeHeights.header
881
+ );
882
+ const bodyBottomPx = Math.max(
883
+ padBottom,
884
+ HEADER_FOOTER_DISTANCE_PX + chromeHeights.footer
885
+ );
886
+ const recomputePagination = (0, import_react3.useCallback)(() => {
887
+ const body = regionEls.current.body;
888
+ if (!body) return;
889
+ const headerH = regionEls.current.header?.offsetHeight ?? 0;
890
+ const footerH = regionEls.current.footer?.offsetHeight ?? 0;
891
+ setChromeHeights(
892
+ (prev) => prev.header === headerH && prev.footer === footerH ? prev : { header: headerH, footer: footerH }
893
+ );
894
+ const top = Math.max(
895
+ twipsToPx(margins.top),
896
+ HEADER_FOOTER_DISTANCE_PX + headerH
897
+ );
898
+ const bottom = Math.max(
899
+ twipsToPx(margins.bottom),
900
+ HEADER_FOOTER_DISTANCE_PX + footerH
901
+ );
902
+ const usableHeightPx = Math.max(PAGE_HEIGHT_PX - top - bottom, 1);
903
+ const interPageGapPx = bottom + PAGE_GAP_PX + top;
904
+ let spacerAccum = 0;
905
+ const blocks = [];
906
+ const blockEls = [];
907
+ Array.from(body.children).forEach((child) => {
908
+ const el = child;
909
+ if (el.hasAttribute(PAGE_SPACER_ATTR)) {
910
+ spacerAccum += el.offsetHeight;
911
+ return;
912
+ }
913
+ blocks.push({ top: el.offsetTop - spacerAccum, height: el.offsetHeight });
914
+ blockEls.push(el);
915
+ });
916
+ const layout = computePageLayout(blocks, { usableHeightPx, interPageGapPx });
917
+ body.querySelectorAll(`[${PAGE_SPACER_ATTR}]`).forEach((node) => node.remove());
918
+ for (const spacer of layout.spacers) {
919
+ const target = blockEls[spacer.beforeBlockIndex];
920
+ if (target) body.insertBefore(createSpacerElement(spacer.heightPx), target);
921
+ }
922
+ setPageCount(layout.pageCount);
923
+ }, [margins]);
924
+ const schedulePagination = (0, import_react3.useCallback)(() => {
925
+ if (rafId.current !== null) cancelAnimationFrame(rafId.current);
926
+ rafId.current = requestAnimationFrame(() => {
927
+ rafId.current = null;
928
+ recomputePagination();
929
+ });
930
+ }, [recomputePagination]);
931
+ const handleContentChanged = (0, import_react3.useCallback)(() => {
932
+ const header = regionEls.current.header?.innerHTML ?? "";
933
+ const footer = regionEls.current.footer?.innerHTML ?? "";
934
+ setMirror(
935
+ (prev) => prev.header === header && prev.footer === footer ? prev : { header, footer }
936
+ );
937
+ schedulePagination();
938
+ }, [schedulePagination]);
939
+ (0, import_react3.useEffect)(() => {
940
+ if (!documentBuffer) return;
941
+ let cancelled = false;
942
+ setLoading(true);
943
+ bufferToModel(documentBuffer).then((imported) => {
944
+ if (cancelled) return;
945
+ setModel((prev) => ({ ...imported, variables: prev.variables }));
946
+ setMirror({
947
+ header: imported.header ?? "",
948
+ footer: imported.footer ?? ""
949
+ });
950
+ setDocVersion((v) => v + 1);
951
+ }).finally(() => {
952
+ if (!cancelled) setLoading(false);
953
+ });
954
+ return () => {
955
+ cancelled = true;
956
+ };
957
+ }, [documentBuffer]);
958
+ (0, import_react3.useEffect)(() => {
959
+ recomputePagination();
960
+ }, [recomputePagination, docVersion, mirror]);
961
+ (0, import_react3.useEffect)(() => {
962
+ const body = regionEls.current.body;
963
+ if (!body) return;
964
+ const onLoad = () => schedulePagination();
965
+ body.addEventListener("load", onLoad, true);
966
+ return () => body.removeEventListener("load", onLoad, true);
967
+ }, [schedulePagination, docVersion]);
968
+ (0, import_react3.useEffect)(
969
+ () => () => {
970
+ if (rafId.current !== null) cancelAnimationFrame(rafId.current);
971
+ },
972
+ []
973
+ );
974
+ const buildModel = (0, import_react3.useCallback)(() => {
975
+ const els = regionEls.current;
976
+ return {
977
+ header: els.header ? els.header.innerHTML : model.header,
978
+ body: els.body ? stripPageSpacers(els.body.innerHTML) : model.body,
979
+ footer: els.footer ? els.footer.innerHTML : model.footer,
980
+ variables: model.variables
981
+ };
982
+ }, [model]);
983
+ (0, import_react3.useImperativeHandle)(
984
+ ref,
985
+ () => ({
986
+ save: () => modelToDocx(buildModel(), { margins }),
987
+ getModel: () => buildModel()
988
+ }),
989
+ [buildModel, margins]
990
+ );
991
+ const focusActive = () => {
992
+ const el = activeRegion.current ?? regionEls.current.body ?? null;
993
+ el?.focus();
994
+ return el;
995
+ };
996
+ const handleCommand = (command, value) => {
997
+ focusActive();
998
+ execFormat(command, value);
999
+ handleContentChanged();
1000
+ };
1001
+ const handleLink = () => {
1002
+ const url = window.prompt("URL do link:");
1003
+ if (!url) return;
1004
+ focusActive();
1005
+ execFormat("createLink", url);
1006
+ handleContentChanged();
1007
+ };
1008
+ const handleInsertImage = () => imageInputRef.current?.click();
1009
+ const onImagePicked = (e) => {
1010
+ const file = e.target.files?.[0];
1011
+ if (!file) return;
1012
+ const reader = new FileReader();
1013
+ reader.onload = () => {
1014
+ focusActive();
1015
+ execFormat("insertImage", String(reader.result));
1016
+ handleContentChanged();
1017
+ };
1018
+ reader.readAsDataURL(file);
1019
+ e.target.value = "";
1020
+ };
1021
+ const handleInsertVariable = (name) => {
1022
+ const el = activeRegion.current ?? regionEls.current.body ?? null;
1023
+ if (el) {
1024
+ insertTextIntoRegion(el, `{{${name}}}`);
1025
+ handleContentChanged();
1026
+ }
1027
+ };
1028
+ const handleInsertPageField = (field) => {
1029
+ const el = activeRegion.current ?? regionEls.current.footer ?? null;
1030
+ if (el) {
1031
+ insertTextIntoRegion(
1032
+ el,
1033
+ field === "pageNumber" ? PAGE_NUMBER_TOKEN : TOTAL_PAGES_TOKEN
1034
+ );
1035
+ handleContentChanged();
1036
+ }
1037
+ };
1038
+ const applyIndent = (next) => {
1039
+ setIndent(next);
1040
+ const block = currentBlock(activeRegion.current ?? regionEls.current.body ?? null);
1041
+ if (block) {
1042
+ block.style.textIndent = `${twipsToPx(next.firstLine)}px`;
1043
+ block.style.marginLeft = `${twipsToPx(next.left)}px`;
1044
+ block.style.marginRight = `${twipsToPx(next.right)}px`;
1045
+ }
1046
+ schedulePagination();
1047
+ };
1048
+ const registerRegion = (key) => (el) => {
1049
+ regionEls.current[key] = el;
1050
+ };
1051
+ const onRegionFocus = (el) => {
1052
+ activeRegion.current = el;
1053
+ const els = regionEls.current;
1054
+ setFocusedRegion(
1055
+ el === els.header ? "header" : el === els.footer ? "footer" : "body"
1056
+ );
1057
+ };
1058
+ const onRegionBlur = () => setFocusedRegion(null);
1059
+ const editingChrome = focusedRegion === "header" || focusedRegion === "footer";
1060
+ const updateFrame = (0, import_react3.useCallback)(() => {
1061
+ const img = selectedImg;
1062
+ const column = pagesColumnRef.current;
1063
+ if (!img || !img.isConnected || !column) {
1064
+ setFrame(null);
1065
+ return;
1066
+ }
1067
+ const imgRect = img.getBoundingClientRect();
1068
+ const colRect = column.getBoundingClientRect();
1069
+ setFrame({
1070
+ top: (imgRect.top - colRect.top) / zoom,
1071
+ left: (imgRect.left - colRect.left) / zoom,
1072
+ width: imgRect.width / zoom,
1073
+ height: imgRect.height / zoom
1074
+ });
1075
+ }, [selectedImg, zoom]);
1076
+ const updateFrameRef = (0, import_react3.useRef)(updateFrame);
1077
+ (0, import_react3.useEffect)(() => {
1078
+ updateFrameRef.current = updateFrame;
1079
+ });
1080
+ (0, import_react3.useEffect)(() => {
1081
+ updateFrame();
1082
+ }, [updateFrame, pageCount, margins, mirror]);
1083
+ (0, import_react3.useEffect)(() => {
1084
+ setSelectedImg(null);
1085
+ }, [docVersion]);
1086
+ const handleColumnClick = (e) => {
1087
+ if (!editable) return;
1088
+ const target = e.target;
1089
+ if (target instanceof HTMLImageElement && target.closest("[contenteditable]")) {
1090
+ setSelectedImg(target);
1091
+ } else {
1092
+ setSelectedImg(null);
1093
+ }
1094
+ };
1095
+ const startResize = (corner) => (e) => {
1096
+ const img = selectedImg;
1097
+ if (!img) return;
1098
+ e.preventDefault();
1099
+ e.stopPropagation();
1100
+ const startX = e.clientX;
1101
+ const startWidth = img.offsetWidth;
1102
+ const region = img.closest("[contenteditable]");
1103
+ const maxWidth = region ? region.clientWidth : PAGE_WIDTH_PX;
1104
+ const sign = corner === "ne" || corner === "se" ? 1 : -1;
1105
+ const onMove = (ev) => {
1106
+ const dx = (ev.clientX - startX) / zoom * sign;
1107
+ const next = Math.round(Math.min(maxWidth, Math.max(20, startWidth + dx)));
1108
+ img.style.width = `${next}px`;
1109
+ img.style.height = "auto";
1110
+ updateFrameRef.current();
1111
+ };
1112
+ const onUp = () => {
1113
+ document.removeEventListener("mousemove", onMove);
1114
+ document.removeEventListener("mouseup", onUp);
1115
+ img.style.height = `${img.offsetHeight}px`;
1116
+ handleContentChanged();
1117
+ updateFrameRef.current();
1118
+ };
1119
+ document.addEventListener("mousemove", onMove);
1120
+ document.addEventListener("mouseup", onUp);
1121
+ };
1122
+ const columnHeight = pageCount * PAGE_HEIGHT_PX + (pageCount - 1) * PAGE_GAP_PX;
1123
+ const usableFirstPagePx = Math.max(
1124
+ PAGE_HEIGHT_PX - bodyTopPx - bodyBottomPx,
1125
+ 24
1126
+ );
1127
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1128
+ import_material4.Box,
1129
+ {
1130
+ sx: {
1131
+ display: "flex",
1132
+ flexDirection: "column",
1133
+ height: "100%",
1134
+ bgcolor: "grey.200"
1135
+ },
1136
+ children: [
1137
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1138
+ import_material4.Box,
1139
+ {
1140
+ sx: {
1141
+ display: "flex",
1142
+ alignItems: "center",
1143
+ gap: 1,
1144
+ flexWrap: "wrap",
1145
+ position: "sticky",
1146
+ top: 0,
1147
+ zIndex: 4
1148
+ },
1149
+ children: [
1150
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_material4.Box, { sx: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1151
+ Toolbar_default,
1152
+ {
1153
+ variables: model.variables ?? [],
1154
+ onCommand: handleCommand,
1155
+ onFontFamily: (f) => {
1156
+ focusActive();
1157
+ setFontFamily(f);
1158
+ handleContentChanged();
1159
+ },
1160
+ onFontSize: (pt) => {
1161
+ const el = focusActive();
1162
+ if (el) setFontSize(el, pt);
1163
+ handleContentChanged();
1164
+ },
1165
+ onFormatBlock: (tag) => handleCommand("formatBlock", tag),
1166
+ onColor: (c) => handleCommand("foreColor", c),
1167
+ onHighlight: (c) => handleCommand("hiliteColor", c),
1168
+ onLink: handleLink,
1169
+ onInsertImage: handleInsertImage,
1170
+ onInsertVariable: handleInsertVariable,
1171
+ onInsertPageField: handleInsertPageField
1172
+ }
1173
+ ) }),
1174
+ showZoomControl && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_material4.Box, { sx: { px: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ZoomControl_default, { zoom, onChange: setZoom }) })
1175
+ ]
1176
+ }
1177
+ ),
1178
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1179
+ "input",
1180
+ {
1181
+ ref: imageInputRef,
1182
+ type: "file",
1183
+ accept: "image/*",
1184
+ style: { display: "none" },
1185
+ onChange: onImagePicked
1186
+ }
1187
+ ),
1188
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_material4.Box, { sx: { flex: 1, overflow: "auto", py: 3 }, children: [
1189
+ showRuler && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_material4.Box, { sx: { display: "flex", justifyContent: "center", mb: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1190
+ Ruler_default,
1191
+ {
1192
+ pageWidthPx: PAGE_WIDTH_PX,
1193
+ zoom,
1194
+ margins,
1195
+ indent,
1196
+ onMarginsChange: setMargins,
1197
+ onIndentChange: applyIndent
1198
+ }
1199
+ ) }),
1200
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1201
+ import_material4.Box,
1202
+ {
1203
+ ref: pagesColumnRef,
1204
+ onClick: handleColumnClick,
1205
+ sx: {
1206
+ width: PAGE_WIDTH_PX,
1207
+ height: columnHeight,
1208
+ transform: `scale(${zoom})`,
1209
+ transformOrigin: "top center",
1210
+ mx: "auto",
1211
+ position: "relative"
1212
+ },
1213
+ children: [
1214
+ Array.from({ length: pageCount }, (_, page) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1215
+ import_material4.Box,
1216
+ {
1217
+ "data-page-index": page,
1218
+ sx: {
1219
+ position: "absolute",
1220
+ top: page * (PAGE_HEIGHT_PX + PAGE_GAP_PX),
1221
+ left: 0,
1222
+ width: "100%",
1223
+ height: PAGE_HEIGHT_PX,
1224
+ bgcolor: "background.paper",
1225
+ boxShadow: 3,
1226
+ zIndex: 0
1227
+ },
1228
+ children: page > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1229
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1230
+ import_material4.Box,
1231
+ {
1232
+ "aria-hidden": true,
1233
+ sx: {
1234
+ ...mirrorSx,
1235
+ position: "absolute",
1236
+ top: HEADER_FOOTER_DISTANCE_PX,
1237
+ left: padLeft,
1238
+ right: padRight,
1239
+ zIndex: 2,
1240
+ opacity: focusedRegion === "body" ? 0.55 : 1,
1241
+ transition: "opacity 120ms"
1242
+ },
1243
+ dangerouslySetInnerHTML: {
1244
+ __html: withPagePreview(mirror.header, page + 1, pageCount)
1245
+ }
1246
+ }
1247
+ ),
1248
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1249
+ import_material4.Box,
1250
+ {
1251
+ "aria-hidden": true,
1252
+ sx: {
1253
+ ...mirrorSx,
1254
+ position: "absolute",
1255
+ bottom: HEADER_FOOTER_DISTANCE_PX,
1256
+ left: padLeft,
1257
+ right: padRight,
1258
+ zIndex: 2,
1259
+ opacity: focusedRegion === "body" ? 0.55 : 1,
1260
+ transition: "opacity 120ms"
1261
+ },
1262
+ dangerouslySetInnerHTML: {
1263
+ __html: withPagePreview(mirror.footer, page + 1, pageCount)
1264
+ }
1265
+ }
1266
+ )
1267
+ ] })
1268
+ },
1269
+ page
1270
+ )),
1271
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1272
+ import_material4.Box,
1273
+ {
1274
+ sx: {
1275
+ position: "absolute",
1276
+ top: 0,
1277
+ left: 0,
1278
+ width: "100%",
1279
+ height: PAGE_HEIGHT_PX,
1280
+ zIndex: 2,
1281
+ pointerEvents: "none"
1282
+ },
1283
+ children: [
1284
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1285
+ import_material4.Box,
1286
+ {
1287
+ sx: {
1288
+ position: "absolute",
1289
+ top: HEADER_FOOTER_DISTANCE_PX,
1290
+ left: padLeft,
1291
+ right: padRight,
1292
+ pointerEvents: "auto",
1293
+ opacity: focusedRegion === "body" ? 0.55 : 1,
1294
+ transition: "opacity 120ms",
1295
+ ...focusedRegion === "header" && {
1296
+ outline: "1px dashed",
1297
+ outlineColor: "primary.main",
1298
+ outlineOffset: "4px"
1299
+ }
1300
+ },
1301
+ children: [
1302
+ focusedRegion === "header" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1303
+ import_material4.Chip,
1304
+ {
1305
+ label: "Cabe\xE7alho",
1306
+ size: "small",
1307
+ color: "primary",
1308
+ sx: {
1309
+ position: "absolute",
1310
+ top: -32,
1311
+ left: -4,
1312
+ borderRadius: 0.5,
1313
+ pointerEvents: "none"
1314
+ }
1315
+ }
1316
+ ),
1317
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1318
+ EditableRegion,
1319
+ {
1320
+ html: model.header ?? "",
1321
+ editable,
1322
+ ariaLabel: "Cabe\xE7alho",
1323
+ placeholder: "Cabe\xE7alho (opcional)",
1324
+ onRegister: registerRegion("header"),
1325
+ onFocus: onRegionFocus,
1326
+ onBlur: onRegionBlur,
1327
+ onInput: handleContentChanged
1328
+ },
1329
+ `header-${docVersion}`
1330
+ )
1331
+ ]
1332
+ }
1333
+ ),
1334
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1335
+ import_material4.Box,
1336
+ {
1337
+ sx: {
1338
+ position: "absolute",
1339
+ bottom: HEADER_FOOTER_DISTANCE_PX,
1340
+ left: padLeft,
1341
+ right: padRight,
1342
+ pointerEvents: "auto",
1343
+ opacity: focusedRegion === "body" ? 0.55 : 1,
1344
+ transition: "opacity 120ms",
1345
+ ...focusedRegion === "footer" && {
1346
+ outline: "1px dashed",
1347
+ outlineColor: "primary.main",
1348
+ outlineOffset: "4px"
1349
+ }
1350
+ },
1351
+ children: [
1352
+ focusedRegion === "footer" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1353
+ import_material4.Chip,
1354
+ {
1355
+ label: "Rodap\xE9",
1356
+ size: "small",
1357
+ color: "primary",
1358
+ sx: {
1359
+ position: "absolute",
1360
+ bottom: -32,
1361
+ left: -4,
1362
+ borderRadius: 0.5,
1363
+ pointerEvents: "none"
1364
+ }
1365
+ }
1366
+ ),
1367
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1368
+ EditableRegion,
1369
+ {
1370
+ html: model.footer ?? "",
1371
+ editable,
1372
+ ariaLabel: "Rodap\xE9",
1373
+ placeholder: "Rodap\xE9 (opcional)",
1374
+ onRegister: registerRegion("footer"),
1375
+ onFocus: onRegionFocus,
1376
+ onBlur: onRegionBlur,
1377
+ onInput: handleContentChanged
1378
+ },
1379
+ `footer-${docVersion}`
1380
+ )
1381
+ ]
1382
+ }
1383
+ )
1384
+ ]
1385
+ }
1386
+ ),
1387
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1388
+ import_material4.Box,
1389
+ {
1390
+ sx: {
1391
+ position: "absolute",
1392
+ top: bodyTopPx,
1393
+ left: padLeft,
1394
+ width: PAGE_WIDTH_PX - padLeft - padRight,
1395
+ zIndex: 1,
1396
+ opacity: editingChrome ? 0.45 : 1,
1397
+ transition: "opacity 120ms"
1398
+ },
1399
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1400
+ EditableRegion,
1401
+ {
1402
+ html: model.body,
1403
+ editable,
1404
+ ariaLabel: "Corpo do documento",
1405
+ placeholder: "Digite o conte\xFAdo do template\u2026",
1406
+ sx: { minHeight: usableFirstPagePx },
1407
+ onRegister: registerRegion("body"),
1408
+ onFocus: onRegionFocus,
1409
+ onBlur: onRegionBlur,
1410
+ onInput: handleContentChanged
1411
+ },
1412
+ `body-${docVersion}`
1413
+ )
1414
+ }
1415
+ ),
1416
+ editable && selectedImg && frame && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1417
+ import_material4.Box,
1418
+ {
1419
+ "data-testid": "image-resize-frame",
1420
+ sx: {
1421
+ position: "absolute",
1422
+ top: frame.top,
1423
+ left: frame.left,
1424
+ width: frame.width,
1425
+ height: frame.height,
1426
+ border: "1px solid",
1427
+ borderColor: "primary.main",
1428
+ zIndex: 3,
1429
+ pointerEvents: "none"
1430
+ },
1431
+ children: RESIZE_CORNERS.map((corner) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1432
+ import_material4.Box,
1433
+ {
1434
+ "data-testid": "image-resize-handle",
1435
+ onMouseDown: startResize(corner),
1436
+ sx: {
1437
+ position: "absolute",
1438
+ width: 10,
1439
+ height: 10,
1440
+ bgcolor: "primary.main",
1441
+ border: "1px solid #fff",
1442
+ pointerEvents: "auto",
1443
+ cursor: corner === "nw" || corner === "se" ? "nwse-resize" : "nesw-resize",
1444
+ top: corner.startsWith("n") ? -5 : "auto",
1445
+ bottom: corner.startsWith("s") ? -5 : "auto",
1446
+ left: corner.endsWith("w") ? -5 : "auto",
1447
+ right: corner.endsWith("e") ? -5 : "auto"
1448
+ }
1449
+ },
1450
+ corner
1451
+ ))
1452
+ }
1453
+ ),
1454
+ loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1455
+ import_material4.Box,
1456
+ {
1457
+ sx: {
1458
+ position: "absolute",
1459
+ inset: 0,
1460
+ display: "flex",
1461
+ alignItems: "center",
1462
+ justifyContent: "center",
1463
+ bgcolor: "rgba(255,255,255,0.6)",
1464
+ zIndex: 3
1465
+ },
1466
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_material4.CircularProgress, {})
1467
+ }
1468
+ )
1469
+ ]
1470
+ }
1471
+ )
1472
+ ] })
1473
+ ]
1474
+ }
1475
+ );
1476
+ }
1477
+ );
1478
+ var DocxEditor_default = DocxEditor;
1479
+ // Annotate the CommonJS export names for ESM import in node:
1480
+ 0 && (module.exports = {
1481
+ DocxEditor
1482
+ });
1483
+ //# sourceMappingURL=index.js.map