@sofereditor/react 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.cjs ADDED
@@ -0,0 +1,3759 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ A4_PAGE: () => A4_PAGE,
24
+ Editor: () => Editor,
25
+ EditorProvider: () => EditorProvider,
26
+ ImageCaptionDialog: () => ImageCaptionDialog,
27
+ LinkDialog: () => LinkDialog,
28
+ NodeView: () => NodeView,
29
+ PageConfigDialog: () => PageConfigDialog,
30
+ Toolbar: () => Toolbar,
31
+ applyDomSelection: () => applyDomSelection,
32
+ contentHeight: () => contentHeight,
33
+ contentWidth: () => contentWidth,
34
+ defaultPageLayout: () => defaultPageLayout,
35
+ deltaLength: () => deltaLength,
36
+ findBlockIndex: () => findBlockIndex,
37
+ getBlockElement: () => getBlockElement,
38
+ getCellElement: () => getCellElement,
39
+ getFragmentForOffset: () => getFragmentForOffset,
40
+ isTableRectSelection: () => isTableRectSelection,
41
+ readDomSelection: () => readDomSelection,
42
+ renderInline: () => renderInline,
43
+ selectionsEqual: () => selectionsEqual,
44
+ sliceDelta: () => sliceDelta,
45
+ textOffsetWithin: () => textOffsetWithin,
46
+ useEditor: () => useEditor,
47
+ useEditorContext: () => useEditorContext,
48
+ usePageLayout: () => usePageLayout,
49
+ usePageSettings: () => usePageSettings
50
+ });
51
+ module.exports = __toCommonJS(index_exports);
52
+
53
+ // src/Editor.tsx
54
+ var import_react12 = require("react");
55
+ var import_core5 = require("@sofereditor/core");
56
+
57
+ // src/dom-bridge.ts
58
+ function findContainingBlockElement(node, root) {
59
+ let cur = node;
60
+ while (cur && cur !== root) {
61
+ if (cur.nodeType === Node.ELEMENT_NODE) {
62
+ const el = cur;
63
+ if (el.dataset.blockIndex != null) return el;
64
+ }
65
+ cur = cur.parentNode;
66
+ }
67
+ return null;
68
+ }
69
+ function findContainingCellElement(node, blockEl) {
70
+ let cur = node;
71
+ while (cur && cur !== blockEl) {
72
+ if (cur.nodeType === Node.ELEMENT_NODE) {
73
+ const el = cur;
74
+ if (el.dataset.cellIndex != null) return el;
75
+ }
76
+ cur = cur.parentNode;
77
+ }
78
+ return null;
79
+ }
80
+ function findBlockIndex(node, root) {
81
+ const el = findContainingBlockElement(node, root);
82
+ if (!el) return null;
83
+ return Number.parseInt(el.dataset.blockIndex, 10);
84
+ }
85
+ function getBlockElement(root, blockIndex) {
86
+ return root.querySelector(`[data-block-index="${blockIndex}"]`);
87
+ }
88
+ function readFragmentStart(el) {
89
+ const s = el.dataset.fragmentStart;
90
+ if (s == null) return 0;
91
+ const n = Number.parseInt(s, 10);
92
+ return Number.isFinite(n) ? n : 0;
93
+ }
94
+ function readFragmentEnd(el) {
95
+ const s = el.dataset.fragmentEnd;
96
+ if (s == null) return void 0;
97
+ const n = Number.parseInt(s, 10);
98
+ return Number.isFinite(n) ? n : void 0;
99
+ }
100
+ function getFragmentForOffset(root, blockIndex, offset) {
101
+ const candidates = root.querySelectorAll(`[data-block-index="${blockIndex}"]`);
102
+ if (candidates.length === 0) return null;
103
+ if (candidates.length === 1) return candidates[0];
104
+ let last = null;
105
+ for (const el of candidates) {
106
+ last = el;
107
+ const start = readFragmentStart(el);
108
+ const end = readFragmentEnd(el);
109
+ if (end == null) return el;
110
+ if (offset >= start && offset < end) return el;
111
+ }
112
+ return last;
113
+ }
114
+ function getCellElement(root, blockIndex, cellIndex) {
115
+ const blockEl = getBlockElement(root, blockIndex);
116
+ if (!blockEl) return null;
117
+ return blockEl.querySelector(`[data-cell-index="${cellIndex}"]`);
118
+ }
119
+ function isImgEmbed(n) {
120
+ return n.nodeType === Node.ELEMENT_NODE && n.dataset.embed === "image";
121
+ }
122
+ function isWrapAnchorImg(n) {
123
+ return n.nodeType === Node.ELEMENT_NODE && n.dataset.wrapAnchor === "true";
124
+ }
125
+ function isPhantomEmbed(n) {
126
+ return n.nodeType === Node.ELEMENT_NODE && n.dataset.embedPhantom === "true";
127
+ }
128
+ function indexInParent(n) {
129
+ const parent = n.parentNode;
130
+ if (!parent) return 0;
131
+ let i = 0;
132
+ for (const c of Array.from(parent.childNodes)) {
133
+ if (c === n) return i;
134
+ i++;
135
+ }
136
+ return 0;
137
+ }
138
+ function isRejectedSubtree(el, container) {
139
+ if (el === container) return false;
140
+ if (el.dataset.blockIndex != null) return true;
141
+ if (container.dataset.cellIndex != null && el.dataset.cellIndex != null) return true;
142
+ return false;
143
+ }
144
+ function textOffsetWithin(container, node, offsetInNode) {
145
+ let target = node;
146
+ let targetOff = offsetInNode;
147
+ if (isImgEmbed(target) && target.parentNode) {
148
+ targetOff = indexInParent(target);
149
+ target = target.parentNode;
150
+ }
151
+ let offset = 0;
152
+ let done = false;
153
+ function visit(n) {
154
+ if (done) return;
155
+ if (n === target) {
156
+ if (n.nodeType === Node.TEXT_NODE) {
157
+ offset += targetOff;
158
+ done = true;
159
+ return;
160
+ }
161
+ const children = n.childNodes;
162
+ for (let i = 0; i < targetOff && i < children.length; i++) {
163
+ visit(children[i]);
164
+ if (done) return;
165
+ }
166
+ done = true;
167
+ return;
168
+ }
169
+ if (n.nodeType === Node.TEXT_NODE) {
170
+ offset += (n.textContent ?? "").length;
171
+ return;
172
+ }
173
+ if (n.nodeType !== Node.ELEMENT_NODE) return;
174
+ const el = n;
175
+ if (isRejectedSubtree(el, container)) return;
176
+ if (isImgEmbed(el) && isWrapAnchorImg(el)) return;
177
+ if (isImgEmbed(el) || isPhantomEmbed(el)) {
178
+ offset += 1;
179
+ return;
180
+ }
181
+ for (const child of Array.from(el.childNodes)) {
182
+ visit(child);
183
+ if (done) return;
184
+ }
185
+ }
186
+ visit(container);
187
+ return offset;
188
+ }
189
+ function resolveAnchorElement(node, root) {
190
+ const blockEl = findContainingBlockElement(node, root);
191
+ if (!blockEl) return null;
192
+ const blockIdx = Number.parseInt(blockEl.dataset.blockIndex, 10);
193
+ const blockType = blockEl.dataset.blockType;
194
+ if (blockType === "table") {
195
+ const cellEl = findContainingCellElement(node, blockEl);
196
+ if (!cellEl) {
197
+ const firstCell = blockEl.querySelector("[data-cell-index]");
198
+ if (!firstCell) return null;
199
+ return {
200
+ blockIdx,
201
+ cellIdx: 0,
202
+ container: firstCell,
203
+ containerStart: 0
204
+ };
205
+ }
206
+ const cellIdx = Number.parseInt(cellEl.dataset.cellIndex, 10);
207
+ return {
208
+ blockIdx,
209
+ cellIdx,
210
+ container: cellEl,
211
+ containerStart: 0
212
+ };
213
+ }
214
+ return {
215
+ blockIdx,
216
+ cellIdx: void 0,
217
+ container: blockEl,
218
+ containerStart: readFragmentStart(blockEl)
219
+ };
220
+ }
221
+ function readDomSelection(root) {
222
+ const sel = window.getSelection();
223
+ if (!sel || sel.rangeCount === 0) return null;
224
+ if (!sel.anchorNode || !sel.focusNode) return null;
225
+ if (!root.contains(sel.anchorNode) || !root.contains(sel.focusNode)) return null;
226
+ const a = resolveAnchorElement(sel.anchorNode, root);
227
+ const f = resolveAnchorElement(sel.focusNode, root);
228
+ if (!a || !f) return null;
229
+ const anchor = {
230
+ blockIndex: a.blockIdx,
231
+ cellIndex: a.cellIdx,
232
+ offset: a.containerStart + textOffsetWithin(a.container, sel.anchorNode, sel.anchorOffset)
233
+ };
234
+ const focus = {
235
+ blockIndex: f.blockIdx,
236
+ cellIndex: f.cellIdx,
237
+ offset: f.containerStart + textOffsetWithin(f.container, sel.focusNode, sel.focusOffset)
238
+ };
239
+ return { anchor, focus };
240
+ }
241
+ function locatePoint(root, pos) {
242
+ let container;
243
+ let containerStart = 0;
244
+ if (pos.cellIndex != null) {
245
+ container = getCellElement(root, pos.blockIndex, pos.cellIndex);
246
+ if (!container) return null;
247
+ } else {
248
+ container = getFragmentForOffset(root, pos.blockIndex, pos.offset);
249
+ if (!container) return null;
250
+ containerStart = readFragmentStart(container);
251
+ }
252
+ let remaining = Math.max(0, pos.offset - containerStart);
253
+ let result = null;
254
+ let lastImg = null;
255
+ function visit(n) {
256
+ if (result) return;
257
+ if (n.nodeType === Node.TEXT_NODE) {
258
+ const len = (n.textContent ?? "").length;
259
+ if (remaining <= len) {
260
+ result = { node: n, offset: remaining };
261
+ return;
262
+ }
263
+ remaining -= len;
264
+ lastImg = null;
265
+ return;
266
+ }
267
+ if (n.nodeType !== Node.ELEMENT_NODE) return;
268
+ const el = n;
269
+ if (isRejectedSubtree(el, container)) return;
270
+ if (isImgEmbed(el) && isWrapAnchorImg(el)) return;
271
+ if (isImgEmbed(el) || isPhantomEmbed(el)) {
272
+ if (remaining === 0) {
273
+ const parent = el.parentNode;
274
+ if (parent) {
275
+ result = { node: parent, offset: indexInParent(el) };
276
+ return;
277
+ }
278
+ }
279
+ remaining -= 1;
280
+ lastImg = el;
281
+ return;
282
+ }
283
+ for (const child of Array.from(el.childNodes)) {
284
+ visit(child);
285
+ if (result) return;
286
+ }
287
+ }
288
+ visit(container);
289
+ if (result) return result;
290
+ if (lastImg && remaining === 0) {
291
+ const parent = lastImg.parentNode;
292
+ if (parent) return { node: parent, offset: indexInParent(lastImg) + 1 };
293
+ }
294
+ return { node: container, offset: 0 };
295
+ }
296
+ function selectionsEqual(a, b) {
297
+ return a.anchor.blockIndex === b.anchor.blockIndex && (a.anchor.cellIndex ?? -1) === (b.anchor.cellIndex ?? -1) && a.anchor.offset === b.anchor.offset && a.focus.blockIndex === b.focus.blockIndex && (a.focus.cellIndex ?? -1) === (b.focus.cellIndex ?? -1) && a.focus.offset === b.focus.offset;
298
+ }
299
+ function isTableRectSelection(sel) {
300
+ if (sel.anchor.blockIndex !== sel.focus.blockIndex) return false;
301
+ if (sel.anchor.cellIndex == null || sel.focus.cellIndex == null) return false;
302
+ return sel.anchor.cellIndex !== sel.focus.cellIndex;
303
+ }
304
+ function applyDomSelection(root, modelSel) {
305
+ const domSel = window.getSelection();
306
+ if (!domSel) return;
307
+ if (isTableRectSelection(modelSel)) {
308
+ domSel.removeAllRanges();
309
+ return;
310
+ }
311
+ const anchorPt = locatePoint(root, modelSel.anchor);
312
+ const focusPt = locatePoint(root, modelSel.focus);
313
+ if (!anchorPt || !focusPt) return;
314
+ try {
315
+ domSel.removeAllRanges();
316
+ domSel.setBaseAndExtent(anchorPt.node, anchorPt.offset, focusPt.node, focusPt.offset);
317
+ } catch {
318
+ }
319
+ }
320
+
321
+ // src/EditorContext.tsx
322
+ var import_react4 = require("react");
323
+
324
+ // src/LinkDialog.tsx
325
+ var import_react = require("react");
326
+ var import_jsx_runtime = require("react/jsx-runtime");
327
+ function LinkDialog() {
328
+ const { linkRequest, resolveLinkRequest } = useEditorContext();
329
+ const dialogRef = (0, import_react.useRef)(null);
330
+ const inputRef = (0, import_react.useRef)(null);
331
+ const [href, setHref] = (0, import_react.useState)("");
332
+ (0, import_react.useEffect)(() => {
333
+ const dialog = dialogRef.current;
334
+ if (!dialog) return;
335
+ if (linkRequest) {
336
+ setHref(linkRequest.initialHref);
337
+ if (!dialog.open) dialog.showModal();
338
+ queueMicrotask(() => {
339
+ const input = inputRef.current;
340
+ if (input) {
341
+ input.focus();
342
+ input.select();
343
+ }
344
+ });
345
+ } else if (dialog.open) {
346
+ dialog.close();
347
+ }
348
+ }, [linkRequest]);
349
+ if (!linkRequest) return null;
350
+ const onSubmit = (e) => {
351
+ e.preventDefault();
352
+ resolveLinkRequest(href.trim());
353
+ };
354
+ const onRemove = () => {
355
+ resolveLinkRequest("");
356
+ };
357
+ const onCancel = () => {
358
+ resolveLinkRequest(null);
359
+ };
360
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
361
+ "dialog",
362
+ {
363
+ ref: dialogRef,
364
+ className: "ed-link-dialog",
365
+ onClose: onCancel,
366
+ onCancel: (e) => {
367
+ e.preventDefault();
368
+ onCancel();
369
+ },
370
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit, className: "ed-link-dialog-form", children: [
371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ed-link-dialog-row", children: [
372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "ed-link-href", children: "URL" }),
373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
374
+ "input",
375
+ {
376
+ id: "ed-link-href",
377
+ ref: inputRef,
378
+ type: "url",
379
+ inputMode: "url",
380
+ value: href,
381
+ onChange: (e) => setHref(e.target.value),
382
+ placeholder: "https://exemplo.com",
383
+ autoComplete: "off"
384
+ }
385
+ )
386
+ ] }),
387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ed-link-dialog-actions", children: [
388
+ linkRequest.initialHref ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: onRemove, className: "ed-link-dialog-remove", children: "Remover link" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
389
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ed-link-dialog-confirm", children: [
390
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: onCancel, children: "Cancelar" }),
391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", className: "ed-link-dialog-apply", children: "Aplicar" })
392
+ ] })
393
+ ] })
394
+ ] })
395
+ }
396
+ );
397
+ }
398
+
399
+ // src/PageConfigDialog.tsx
400
+ var import_react2 = require("react");
401
+ var import_core = require("@sofereditor/core");
402
+ var import_jsx_runtime2 = require("react/jsx-runtime");
403
+ function PageConfigDialog() {
404
+ const { pageConfigRequest, resolvePageConfigRequest, setPageSettings } = useEditorContext();
405
+ const dialogRef = (0, import_react2.useRef)(null);
406
+ const [sizePreset, setSizePreset] = (0, import_react2.useState)("a4");
407
+ const [widthMm, setWidthMm] = (0, import_react2.useState)(210);
408
+ const [heightMm, setHeightMm] = (0, import_react2.useState)(297);
409
+ const [marginPreset, setMarginPreset] = (0, import_react2.useState)("normal");
410
+ const [marginTopMm, setMarginTopMm] = (0, import_react2.useState)(25.4);
411
+ const [marginBottomMm, setMarginBottomMm] = (0, import_react2.useState)(25.4);
412
+ const [marginLeftMm, setMarginLeftMm] = (0, import_react2.useState)(25.4);
413
+ const [marginRightMm, setMarginRightMm] = (0, import_react2.useState)(25.4);
414
+ (0, import_react2.useEffect)(() => {
415
+ const dialog = dialogRef.current;
416
+ if (!dialog) return;
417
+ if (pageConfigRequest) {
418
+ hydrateFromSettings(pageConfigRequest.initial, {
419
+ setSizePreset,
420
+ setWidthMm,
421
+ setHeightMm,
422
+ setMarginPreset,
423
+ setMarginTopMm,
424
+ setMarginBottomMm,
425
+ setMarginLeftMm,
426
+ setMarginRightMm
427
+ });
428
+ if (!dialog.open) dialog.showModal();
429
+ } else if (dialog.open) {
430
+ dialog.close();
431
+ }
432
+ }, [pageConfigRequest]);
433
+ (0, import_react2.useEffect)(() => {
434
+ if (sizePreset === "custom") return;
435
+ const p = import_core.PAGE_PRESETS[sizePreset];
436
+ setWidthMm(roundMm((0, import_core.pxToMm)(p.width)));
437
+ setHeightMm(roundMm((0, import_core.pxToMm)(p.height)));
438
+ }, [sizePreset]);
439
+ (0, import_react2.useEffect)(() => {
440
+ if (marginPreset === "custom") return;
441
+ const px = import_core.MARGIN_PRESETS[marginPreset];
442
+ const mm = roundMm((0, import_core.pxToMm)(px));
443
+ setMarginTopMm(mm);
444
+ setMarginBottomMm(mm);
445
+ setMarginLeftMm(mm);
446
+ setMarginRightMm(mm);
447
+ }, [marginPreset]);
448
+ const customWidth = sizePreset === "custom";
449
+ const previewPx = (0, import_react2.useMemo)(
450
+ () => ({
451
+ width: Math.round((0, import_core.mmToPx)(widthMm)),
452
+ height: Math.round((0, import_core.mmToPx)(heightMm))
453
+ }),
454
+ [widthMm, heightMm]
455
+ );
456
+ if (!pageConfigRequest) return null;
457
+ const onApply = (e) => {
458
+ e.preventDefault();
459
+ const next = {
460
+ width: (0, import_core.mmToPx)(widthMm),
461
+ height: (0, import_core.mmToPx)(heightMm),
462
+ marginTop: (0, import_core.mmToPx)(marginTopMm),
463
+ marginBottom: (0, import_core.mmToPx)(marginBottomMm),
464
+ marginLeft: (0, import_core.mmToPx)(marginLeftMm),
465
+ marginRight: (0, import_core.mmToPx)(marginRightMm)
466
+ };
467
+ setPageSettings(next);
468
+ resolvePageConfigRequest(true);
469
+ };
470
+ const onCancel = () => resolvePageConfigRequest(false);
471
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
472
+ "dialog",
473
+ {
474
+ ref: dialogRef,
475
+ className: "ed-page-config-dialog",
476
+ onClose: onCancel,
477
+ onCancel: (e) => {
478
+ e.preventDefault();
479
+ onCancel();
480
+ },
481
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: onApply, className: "ed-page-config-form", children: [
482
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { children: "Configurar p\xE1gina" }),
483
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("fieldset", { children: [
484
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("legend", { children: "Tamanho" }),
485
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ed-page-config-row", children: [
486
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
487
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
488
+ "input",
489
+ {
490
+ type: "radio",
491
+ name: "size",
492
+ checked: sizePreset === "a4",
493
+ onChange: () => setSizePreset("a4")
494
+ }
495
+ ),
496
+ " ",
497
+ "A4 (210 \xD7 297 mm)"
498
+ ] }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
501
+ "input",
502
+ {
503
+ type: "radio",
504
+ name: "size",
505
+ checked: sizePreset === "letter",
506
+ onChange: () => setSizePreset("letter")
507
+ }
508
+ ),
509
+ " ",
510
+ "Carta (216 \xD7 279 mm)"
511
+ ] }),
512
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
513
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
514
+ "input",
515
+ {
516
+ type: "radio",
517
+ name: "size",
518
+ checked: sizePreset === "oficio",
519
+ onChange: () => setSizePreset("oficio")
520
+ }
521
+ ),
522
+ " ",
523
+ "Of\xEDcio (216 \xD7 330 mm)"
524
+ ] }),
525
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
526
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
527
+ "input",
528
+ {
529
+ type: "radio",
530
+ name: "size",
531
+ checked: sizePreset === "custom",
532
+ onChange: () => setSizePreset("custom")
533
+ }
534
+ ),
535
+ " ",
536
+ "Personalizado"
537
+ ] })
538
+ ] }),
539
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ed-page-config-row", children: [
540
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
541
+ "Largura (mm)",
542
+ " ",
543
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
544
+ "input",
545
+ {
546
+ type: "number",
547
+ step: 0.1,
548
+ min: 50,
549
+ max: 1e3,
550
+ value: widthMm,
551
+ onChange: (e) => setWidthMm(Number(e.target.value)),
552
+ disabled: !customWidth
553
+ }
554
+ )
555
+ ] }),
556
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
557
+ "Altura (mm)",
558
+ " ",
559
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
560
+ "input",
561
+ {
562
+ type: "number",
563
+ step: 0.1,
564
+ min: 50,
565
+ max: 1e3,
566
+ value: heightMm,
567
+ onChange: (e) => setHeightMm(Number(e.target.value)),
568
+ disabled: !customWidth
569
+ }
570
+ )
571
+ ] }),
572
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "ed-page-config-preview", children: [
573
+ previewPx.width,
574
+ " \xD7 ",
575
+ previewPx.height,
576
+ " px"
577
+ ] })
578
+ ] })
579
+ ] }),
580
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("fieldset", { children: [
581
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("legend", { children: "Margens" }),
582
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ed-page-config-row", children: [
583
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
584
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
585
+ "input",
586
+ {
587
+ type: "radio",
588
+ name: "margin",
589
+ checked: marginPreset === "normal",
590
+ onChange: () => setMarginPreset("normal")
591
+ }
592
+ ),
593
+ " ",
594
+ "Normal (25,4 mm)"
595
+ ] }),
596
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
597
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
598
+ "input",
599
+ {
600
+ type: "radio",
601
+ name: "margin",
602
+ checked: marginPreset === "narrow",
603
+ onChange: () => setMarginPreset("narrow")
604
+ }
605
+ ),
606
+ " ",
607
+ "Estreita (12,7 mm)"
608
+ ] }),
609
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
610
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
611
+ "input",
612
+ {
613
+ type: "radio",
614
+ name: "margin",
615
+ checked: marginPreset === "wide",
616
+ onChange: () => setMarginPreset("wide")
617
+ }
618
+ ),
619
+ " ",
620
+ "Larga (50,8 mm)"
621
+ ] }),
622
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
623
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
624
+ "input",
625
+ {
626
+ type: "radio",
627
+ name: "margin",
628
+ checked: marginPreset === "custom",
629
+ onChange: () => setMarginPreset("custom")
630
+ }
631
+ ),
632
+ " ",
633
+ "Personalizada"
634
+ ] })
635
+ ] }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ed-page-config-row", children: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
638
+ "Topo",
639
+ " ",
640
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
641
+ "input",
642
+ {
643
+ type: "number",
644
+ step: 0.1,
645
+ min: 0,
646
+ max: 200,
647
+ value: marginTopMm,
648
+ onChange: (e) => {
649
+ setMarginTopMm(Number(e.target.value));
650
+ setMarginPreset("custom");
651
+ }
652
+ }
653
+ )
654
+ ] }),
655
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
656
+ "Inferior",
657
+ " ",
658
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
659
+ "input",
660
+ {
661
+ type: "number",
662
+ step: 0.1,
663
+ min: 0,
664
+ max: 200,
665
+ value: marginBottomMm,
666
+ onChange: (e) => {
667
+ setMarginBottomMm(Number(e.target.value));
668
+ setMarginPreset("custom");
669
+ }
670
+ }
671
+ )
672
+ ] }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
674
+ "Esquerda",
675
+ " ",
676
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
677
+ "input",
678
+ {
679
+ type: "number",
680
+ step: 0.1,
681
+ min: 0,
682
+ max: 200,
683
+ value: marginLeftMm,
684
+ onChange: (e) => {
685
+ setMarginLeftMm(Number(e.target.value));
686
+ setMarginPreset("custom");
687
+ }
688
+ }
689
+ )
690
+ ] }),
691
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
692
+ "Direita",
693
+ " ",
694
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
695
+ "input",
696
+ {
697
+ type: "number",
698
+ step: 0.1,
699
+ min: 0,
700
+ max: 200,
701
+ value: marginRightMm,
702
+ onChange: (e) => {
703
+ setMarginRightMm(Number(e.target.value));
704
+ setMarginPreset("custom");
705
+ }
706
+ }
707
+ )
708
+ ] })
709
+ ] })
710
+ ] }),
711
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ed-page-config-actions", children: [
712
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: onCancel, children: "Cancelar" }),
713
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", className: "ed-page-config-apply", children: "Aplicar" })
714
+ ] })
715
+ ] })
716
+ }
717
+ );
718
+ }
719
+ function roundMm(mm) {
720
+ return Math.round(mm * 10) / 10;
721
+ }
722
+ function hydrateFromSettings(s, setters) {
723
+ const preset = (0, import_core.detectPreset)(s.width, s.height);
724
+ setters.setSizePreset(preset);
725
+ setters.setWidthMm(roundMm((0, import_core.pxToMm)(s.width)));
726
+ setters.setHeightMm(roundMm((0, import_core.pxToMm)(s.height)));
727
+ const top = roundMm((0, import_core.pxToMm)(s.marginTop));
728
+ const bottom = roundMm((0, import_core.pxToMm)(s.marginBottom));
729
+ const left = roundMm((0, import_core.pxToMm)(s.marginLeft));
730
+ const right = roundMm((0, import_core.pxToMm)(s.marginRight));
731
+ setters.setMarginTopMm(top);
732
+ setters.setMarginBottomMm(bottom);
733
+ setters.setMarginLeftMm(left);
734
+ setters.setMarginRightMm(right);
735
+ setters.setMarginPreset(detectMarginPreset(s));
736
+ }
737
+ function detectMarginPreset(s) {
738
+ const TOL = 1;
739
+ const allEqual = Math.abs(s.marginTop - s.marginBottom) <= TOL && Math.abs(s.marginTop - s.marginLeft) <= TOL && Math.abs(s.marginTop - s.marginRight) <= TOL;
740
+ if (!allEqual) return "custom";
741
+ for (const [name, px] of Object.entries(import_core.MARGIN_PRESETS)) {
742
+ if (Math.abs(s.marginTop - px) <= TOL) return name;
743
+ }
744
+ return "custom";
745
+ }
746
+
747
+ // src/ImageCaptionDialog.tsx
748
+ var import_react3 = require("react");
749
+ var import_jsx_runtime3 = require("react/jsx-runtime");
750
+ function ImageCaptionDialog() {
751
+ const { imageCaptionRequest, resolveImageCaptionRequest } = useEditorContext();
752
+ const dialogRef = (0, import_react3.useRef)(null);
753
+ const inputRef = (0, import_react3.useRef)(null);
754
+ const [caption, setCaption] = (0, import_react3.useState)("");
755
+ const [align, setAlign] = (0, import_react3.useState)("center");
756
+ (0, import_react3.useEffect)(() => {
757
+ const dialog = dialogRef.current;
758
+ if (!dialog) return;
759
+ if (imageCaptionRequest) {
760
+ setCaption(imageCaptionRequest.initialCaption);
761
+ setAlign(imageCaptionRequest.initialAlign);
762
+ if (!dialog.open) dialog.showModal();
763
+ queueMicrotask(() => {
764
+ const input = inputRef.current;
765
+ if (input) {
766
+ input.focus();
767
+ input.select();
768
+ }
769
+ });
770
+ } else if (dialog.open) {
771
+ dialog.close();
772
+ }
773
+ }, [imageCaptionRequest]);
774
+ if (!imageCaptionRequest) return null;
775
+ const onSubmit = (e) => {
776
+ e.preventDefault();
777
+ resolveImageCaptionRequest({ caption, align });
778
+ };
779
+ const onRemove = () => resolveImageCaptionRequest({ caption: "", align });
780
+ const onCancel = () => resolveImageCaptionRequest(null);
781
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
782
+ "dialog",
783
+ {
784
+ ref: dialogRef,
785
+ className: "ed-caption-dialog ed-link-dialog",
786
+ onClose: onCancel,
787
+ onCancel: (e) => {
788
+ e.preventDefault();
789
+ onCancel();
790
+ },
791
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit, className: "ed-link-dialog-form", children: [
792
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ed-link-dialog-row", children: [
793
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { htmlFor: "ed-caption-input", children: "Legenda" }),
794
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
795
+ "input",
796
+ {
797
+ id: "ed-caption-input",
798
+ ref: inputRef,
799
+ type: "text",
800
+ value: caption,
801
+ onChange: (e) => setCaption(e.target.value),
802
+ placeholder: "Ex.: Figura 1 \u2014 Mapa do Brasil",
803
+ autoComplete: "off"
804
+ }
805
+ )
806
+ ] }),
807
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ed-link-dialog-row", children: [
808
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Alinhamento" }),
809
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ed-caption-align-group", role: "radiogroup", "aria-label": "Alinhamento da legenda", children: ["left", "center", "right"].map((value) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "ed-caption-align-option", children: [
810
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
811
+ "input",
812
+ {
813
+ type: "radio",
814
+ name: "caption-align",
815
+ value,
816
+ checked: align === value,
817
+ onChange: () => setAlign(value)
818
+ }
819
+ ),
820
+ value === "left" ? "Esquerda" : value === "center" ? "Centro" : "Direita"
821
+ ] }, value)) })
822
+ ] }),
823
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ed-link-dialog-actions", children: [
824
+ imageCaptionRequest.initialCaption ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: onRemove, className: "ed-link-dialog-remove", children: "Remover legenda" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", {}),
825
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ed-link-dialog-confirm", children: [
826
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: onCancel, children: "Cancelar" }),
827
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "ed-link-dialog-apply", children: "Aplicar" })
828
+ ] })
829
+ ] })
830
+ ] })
831
+ }
832
+ );
833
+ }
834
+
835
+ // src/EditorContext.tsx
836
+ var import_jsx_runtime4 = require("react/jsx-runtime");
837
+ var EditorContext = (0, import_react4.createContext)(null);
838
+ function EditorProvider({ editor, children }) {
839
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(EditorContext.Provider, { value: editor, children: [
840
+ children,
841
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LinkDialog, {}),
842
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PageConfigDialog, {}),
843
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ImageCaptionDialog, {})
844
+ ] });
845
+ }
846
+ function useEditorContext() {
847
+ const ctx = (0, import_react4.useContext)(EditorContext);
848
+ if (!ctx) {
849
+ throw new Error("useEditorContext must be used inside an <EditorProvider>");
850
+ }
851
+ return ctx;
852
+ }
853
+
854
+ // src/ImageResizeOverlay.tsx
855
+ var import_react5 = require("react");
856
+ var import_jsx_runtime5 = require("react/jsx-runtime");
857
+ function ImageResizeOverlay({
858
+ rootRef,
859
+ blockIndex,
860
+ offset,
861
+ cellIndex,
862
+ embed,
863
+ onCommit,
864
+ onCommitMove
865
+ }) {
866
+ const [rect, setRect] = (0, import_react5.useState)(null);
867
+ const imgRef = (0, import_react5.useRef)(null);
868
+ const locateImage = (0, import_react5.useCallback)(() => {
869
+ const root = rootRef.current;
870
+ if (!root) return null;
871
+ let container = null;
872
+ if (cellIndex != null) {
873
+ container = root.querySelector(
874
+ `[data-block-index="${blockIndex}"] [data-cell-index="${cellIndex}"]`
875
+ );
876
+ } else {
877
+ container = getFragmentForOffset(root, blockIndex, offset);
878
+ }
879
+ if (!container) return null;
880
+ const fragmentStart = container.dataset.fragmentStart ? Number.parseInt(container.dataset.fragmentStart, 10) || 0 : 0;
881
+ const localOffset = offset - fragmentStart;
882
+ return container.querySelector(
883
+ `img[data-embed="image"][data-embed-offset="${localOffset}"]`
884
+ );
885
+ }, [rootRef, blockIndex, offset, cellIndex]);
886
+ const measure = (0, import_react5.useCallback)(() => {
887
+ const img = locateImage();
888
+ if (!img) {
889
+ setRect((prev) => prev === null ? prev : null);
890
+ return;
891
+ }
892
+ imgRef.current = img;
893
+ const root = rootRef.current;
894
+ if (!root) return;
895
+ const r = img.getBoundingClientRect();
896
+ const rr = root.getBoundingClientRect();
897
+ const next = {
898
+ top: r.top - rr.top + root.scrollTop,
899
+ left: r.left - rr.left + root.scrollLeft,
900
+ width: r.width,
901
+ height: r.height
902
+ };
903
+ setRect(
904
+ (prev) => prev && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
905
+ );
906
+ }, [locateImage, rootRef]);
907
+ (0, import_react5.useLayoutEffect)(() => {
908
+ measure();
909
+ let cancelled = false;
910
+ const remeasure = () => {
911
+ if (cancelled) return;
912
+ measure();
913
+ };
914
+ const id = typeof requestAnimationFrame === "function" ? requestAnimationFrame(remeasure) : (queueMicrotask(remeasure), 0);
915
+ return () => {
916
+ cancelled = true;
917
+ if (typeof cancelAnimationFrame === "function" && typeof id === "number" && id > 0) {
918
+ cancelAnimationFrame(id);
919
+ }
920
+ };
921
+ }, [
922
+ measure,
923
+ embed.width,
924
+ embed.height,
925
+ embed.align,
926
+ embed.src,
927
+ embed.layout,
928
+ embed.offsetX,
929
+ embed.offsetY
930
+ ]);
931
+ (0, import_react5.useEffect)(() => {
932
+ const root = rootRef.current;
933
+ if (!root) return;
934
+ const onScroll = () => measure();
935
+ window.addEventListener("resize", onScroll);
936
+ root.addEventListener("scroll", onScroll);
937
+ return () => {
938
+ window.removeEventListener("resize", onScroll);
939
+ root.removeEventListener("scroll", onScroll);
940
+ };
941
+ }, [measure, rootRef]);
942
+ (0, import_react5.useEffect)(() => {
943
+ if (typeof ResizeObserver === "undefined") return;
944
+ const img = locateImage();
945
+ const root = rootRef.current;
946
+ if (!img || !root) return;
947
+ const ro = new ResizeObserver(() => measure());
948
+ ro.observe(img);
949
+ ro.observe(root);
950
+ return () => ro.disconnect();
951
+ }, [locateImage, measure, rootRef, embed.layout, embed.align, embed.src]);
952
+ if (!rect) return null;
953
+ const layout = embed.layout ?? "inline";
954
+ const movable = layout !== "inline" && onCommitMove !== void 0;
955
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
956
+ "div",
957
+ {
958
+ className: "ed-image-overlay",
959
+ style: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },
960
+ contentEditable: false,
961
+ children: [
962
+ movable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
963
+ MoveHandle,
964
+ {
965
+ layout,
966
+ startOffsetX: embed.offsetX ?? 0,
967
+ startOffsetY: embed.offsetY ?? 0,
968
+ imgRef,
969
+ onCommit: onCommitMove,
970
+ onLiveChange: measure
971
+ }
972
+ ),
973
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
974
+ Handle,
975
+ {
976
+ corner: "nw",
977
+ startW: embed.width,
978
+ startH: embed.height,
979
+ imgRef,
980
+ onCommit,
981
+ onLiveChange: measure
982
+ }
983
+ ),
984
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
985
+ Handle,
986
+ {
987
+ corner: "ne",
988
+ startW: embed.width,
989
+ startH: embed.height,
990
+ imgRef,
991
+ onCommit,
992
+ onLiveChange: measure
993
+ }
994
+ ),
995
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
996
+ Handle,
997
+ {
998
+ corner: "sw",
999
+ startW: embed.width,
1000
+ startH: embed.height,
1001
+ imgRef,
1002
+ onCommit,
1003
+ onLiveChange: measure
1004
+ }
1005
+ ),
1006
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1007
+ Handle,
1008
+ {
1009
+ corner: "se",
1010
+ startW: embed.width,
1011
+ startH: embed.height,
1012
+ imgRef,
1013
+ onCommit,
1014
+ onLiveChange: measure
1015
+ }
1016
+ )
1017
+ ]
1018
+ }
1019
+ );
1020
+ }
1021
+ function MoveHandle({
1022
+ layout,
1023
+ startOffsetX,
1024
+ startOffsetY,
1025
+ imgRef,
1026
+ onCommit,
1027
+ onLiveChange
1028
+ }) {
1029
+ const dragRef = (0, import_react5.useRef)(null);
1030
+ const onPointerDown = (e) => {
1031
+ if (e.button !== 0) return;
1032
+ if (!imgRef.current) return;
1033
+ e.preventDefault();
1034
+ e.stopPropagation();
1035
+ e.target.setPointerCapture(e.pointerId);
1036
+ dragRef.current = {
1037
+ startX: e.clientX,
1038
+ startY: e.clientY,
1039
+ liveOX: startOffsetX,
1040
+ liveOY: startOffsetY
1041
+ };
1042
+ };
1043
+ const onPointerMove = (e) => {
1044
+ const d = dragRef.current;
1045
+ if (!d || !imgRef.current) return;
1046
+ const dx = e.clientX - d.startX;
1047
+ const dy = e.clientY - d.startY;
1048
+ const img = imgRef.current;
1049
+ let nox = startOffsetX;
1050
+ let noy = startOffsetY;
1051
+ if (layout === "wrap-left") {
1052
+ nox = Math.max(8, startOffsetX + dx);
1053
+ noy = Math.max(0, startOffsetY + dy);
1054
+ img.style.marginRight = `${nox}px`;
1055
+ img.style.marginTop = `${noy}px`;
1056
+ } else if (layout === "wrap-right") {
1057
+ nox = Math.max(8, startOffsetX - dx);
1058
+ noy = Math.max(0, startOffsetY + dy);
1059
+ img.style.marginLeft = `${nox}px`;
1060
+ img.style.marginTop = `${noy}px`;
1061
+ } else if (layout === "behind" || layout === "front") {
1062
+ nox = startOffsetX + dx;
1063
+ noy = startOffsetY + dy;
1064
+ img.style.left = `${nox}px`;
1065
+ img.style.top = `${noy}px`;
1066
+ }
1067
+ d.liveOX = nox;
1068
+ d.liveOY = noy;
1069
+ onLiveChange();
1070
+ };
1071
+ const finish = (e) => {
1072
+ const d = dragRef.current;
1073
+ if (!d) return;
1074
+ dragRef.current = null;
1075
+ try {
1076
+ e.target.releasePointerCapture(e.pointerId);
1077
+ } catch {
1078
+ }
1079
+ onCommit(Math.round(d.liveOX), Math.round(d.liveOY));
1080
+ };
1081
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1082
+ "div",
1083
+ {
1084
+ className: "ed-image-move",
1085
+ onPointerDown,
1086
+ onPointerMove,
1087
+ onPointerUp: finish,
1088
+ onPointerCancel: finish
1089
+ }
1090
+ );
1091
+ }
1092
+ var MIN_W = 20;
1093
+ function Handle({
1094
+ corner,
1095
+ startW,
1096
+ startH,
1097
+ imgRef,
1098
+ onCommit,
1099
+ onLiveChange
1100
+ }) {
1101
+ const dragRef = (0, import_react5.useRef)(null);
1102
+ const onPointerDown = (e) => {
1103
+ if (e.button !== 0) return;
1104
+ if (!imgRef.current) return;
1105
+ e.preventDefault();
1106
+ e.stopPropagation();
1107
+ e.target.setPointerCapture(e.pointerId);
1108
+ const aspect = startW > 0 && startH > 0 ? startW / startH : 1;
1109
+ dragRef.current = {
1110
+ startX: e.clientX,
1111
+ startY: e.clientY,
1112
+ startW,
1113
+ startH,
1114
+ aspect,
1115
+ liveW: startW,
1116
+ liveH: startH
1117
+ };
1118
+ };
1119
+ const onPointerMove = (e) => {
1120
+ const d = dragRef.current;
1121
+ if (!d || !imgRef.current) return;
1122
+ const dxRaw = e.clientX - d.startX;
1123
+ const dx = corner === "se" || corner === "ne" ? dxRaw : -dxRaw;
1124
+ let w = Math.max(MIN_W, d.startW + dx);
1125
+ let h = w / d.aspect;
1126
+ imgRef.current.style.width = `${w}px`;
1127
+ imgRef.current.style.height = `${h}px`;
1128
+ imgRef.current.width = Math.round(w);
1129
+ imgRef.current.height = Math.round(h);
1130
+ d.liveW = w;
1131
+ d.liveH = h;
1132
+ onLiveChange();
1133
+ };
1134
+ const finish = (e) => {
1135
+ const d = dragRef.current;
1136
+ if (!d) return;
1137
+ dragRef.current = null;
1138
+ try {
1139
+ e.target.releasePointerCapture(e.pointerId);
1140
+ } catch {
1141
+ }
1142
+ onCommit(Math.round(d.liveW), Math.round(d.liveH));
1143
+ };
1144
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1145
+ "div",
1146
+ {
1147
+ className: `ed-image-handle ${corner}`,
1148
+ onPointerDown,
1149
+ onPointerMove,
1150
+ onPointerUp: finish,
1151
+ onPointerCancel: finish
1152
+ }
1153
+ );
1154
+ }
1155
+
1156
+ // src/NodeView.tsx
1157
+ var import_react8 = require("react");
1158
+ var import_core3 = require("@sofereditor/core");
1159
+
1160
+ // src/renderInline.tsx
1161
+ var import_react6 = require("react");
1162
+ var import_core2 = require("@sofereditor/core");
1163
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1164
+ function renderInline(delta, keyPrefix) {
1165
+ if (delta.length === 0 || delta.length === 1 && typeof delta[0].insert === "string" && delta[0].insert === "") {
1166
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("br", { "data-empty": "true" });
1167
+ }
1168
+ let offset = 0;
1169
+ const wrapAnchors = [];
1170
+ const body = [];
1171
+ for (const op of delta) {
1172
+ if (typeof op.insert === "string") {
1173
+ const text = op.insert;
1174
+ if (text.length === 0) continue;
1175
+ const key = `${keyPrefix}-${offset}`;
1176
+ body.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react6.Fragment, { children: wrap(text, op.attributes) }, key));
1177
+ offset += text.length;
1178
+ } else if ((0, import_core2.isImageEmbed)(op.insert)) {
1179
+ const key = `${keyPrefix}-${offset}`;
1180
+ const layout = op.insert.layout ?? "inline";
1181
+ if (layout === "wrap-left" || layout === "wrap-right") {
1182
+ wrapAnchors.push(
1183
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ImageEmbedView, { embed: op.insert, offset, wrapAnchor: true }, `${key}-anchor`)
1184
+ );
1185
+ body.push(
1186
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1187
+ "span",
1188
+ {
1189
+ "data-embed-phantom": "true",
1190
+ "data-embed-offset": offset,
1191
+ contentEditable: false,
1192
+ style: PHANTOM_STYLE
1193
+ },
1194
+ `${key}-phantom`
1195
+ )
1196
+ );
1197
+ } else {
1198
+ body.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ImageEmbedView, { embed: op.insert, offset }, key));
1199
+ }
1200
+ offset += 1;
1201
+ } else if (op.insert != null) {
1202
+ offset += 1;
1203
+ }
1204
+ }
1205
+ if (wrapAnchors.length === 0 && body.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("br", { "data-empty": "true" });
1206
+ return [...wrapAnchors, ...body];
1207
+ }
1208
+ var PHANTOM_STYLE = {
1209
+ display: "inline-block",
1210
+ width: 0,
1211
+ height: 0,
1212
+ overflow: "hidden",
1213
+ verticalAlign: "baseline"
1214
+ };
1215
+ function imageEmbedPropsEqual(a, b) {
1216
+ if (a.offset !== b.offset || a.wrapAnchor !== b.wrapAnchor) return false;
1217
+ const x = a.embed;
1218
+ const y = b.embed;
1219
+ if (x === y) return true;
1220
+ return x.width === y.width && x.height === y.height && x.layout === y.layout && x.align === y.align && x.offsetX === y.offsetX && x.offsetY === y.offsetY && x.caption === y.caption && x.captionAlign === y.captionAlign && // src is the expensive compare — only fall back to ref equality. Fresh
1221
+ // toDelta() objects share the same string reference for embed payloads
1222
+ // because Y.js stores the embed in its content map and re-exposes it.
1223
+ x.src === y.src;
1224
+ }
1225
+ var ImageEmbedView = (0, import_react6.memo)(ImageEmbedViewImpl, imageEmbedPropsEqual);
1226
+ function ImageEmbedViewImpl({
1227
+ embed,
1228
+ offset,
1229
+ wrapAnchor
1230
+ }) {
1231
+ const layout = embed.layout ?? "inline";
1232
+ const align = embed.align;
1233
+ const ox = embed.offsetX ?? 0;
1234
+ const oy = embed.offsetY ?? 0;
1235
+ const hasCaption = !!embed.caption && embed.caption.length > 0;
1236
+ let outerStyle;
1237
+ switch (layout) {
1238
+ case "wrap-left":
1239
+ outerStyle = {
1240
+ float: "left",
1241
+ width: embed.width,
1242
+ marginRight: Math.max(8, ox),
1243
+ marginTop: Math.max(0, oy),
1244
+ marginBottom: 4
1245
+ };
1246
+ break;
1247
+ case "wrap-right":
1248
+ outerStyle = {
1249
+ float: "right",
1250
+ width: embed.width,
1251
+ marginLeft: Math.max(8, ox),
1252
+ marginTop: Math.max(0, oy),
1253
+ marginBottom: 4
1254
+ };
1255
+ break;
1256
+ case "behind":
1257
+ case "front":
1258
+ outerStyle = {
1259
+ position: "absolute",
1260
+ left: ox,
1261
+ top: oy,
1262
+ width: embed.width,
1263
+ zIndex: layout === "behind" ? -1 : 2
1264
+ };
1265
+ break;
1266
+ case "inline":
1267
+ default:
1268
+ outerStyle = align ? {
1269
+ display: "block",
1270
+ width: embed.width,
1271
+ marginLeft: align === "left" ? 0 : "auto",
1272
+ marginRight: align === "right" ? 0 : "auto"
1273
+ } : {
1274
+ display: "inline-block",
1275
+ verticalAlign: "text-bottom",
1276
+ width: embed.width
1277
+ };
1278
+ break;
1279
+ }
1280
+ const imgStyle = hasCaption ? { width: embed.width, height: embed.height, display: "block" } : outerStyle;
1281
+ const img = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1282
+ "img",
1283
+ {
1284
+ src: embed.src,
1285
+ width: embed.width,
1286
+ height: embed.height,
1287
+ contentEditable: false,
1288
+ draggable: false,
1289
+ "data-embed": "image",
1290
+ "data-embed-offset": offset,
1291
+ "data-embed-align": align ?? "",
1292
+ "data-embed-layout": layout,
1293
+ "data-wrap-anchor": wrapAnchor ? "true" : void 0,
1294
+ style: imgStyle,
1295
+ alt: ""
1296
+ }
1297
+ );
1298
+ if (!hasCaption) return img;
1299
+ const captionAlign = embed.captionAlign ?? "center";
1300
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1301
+ "figure",
1302
+ {
1303
+ className: "ed-figure",
1304
+ contentEditable: false,
1305
+ "data-embed-figure": "true",
1306
+ "data-embed-layout": layout,
1307
+ style: outerStyle,
1308
+ children: [
1309
+ img,
1310
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1311
+ "figcaption",
1312
+ {
1313
+ className: "ed-figcaption",
1314
+ "data-caption-align": captionAlign,
1315
+ style: { textAlign: captionAlign },
1316
+ children: embed.caption
1317
+ }
1318
+ )
1319
+ ]
1320
+ }
1321
+ );
1322
+ }
1323
+ function wrap(text, attrs) {
1324
+ let node = text;
1325
+ if (!attrs) return node;
1326
+ if (attrs.bold) node = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: node });
1327
+ if (attrs.italic) node = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("em", { children: node });
1328
+ if (attrs.underline) node = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("u", { children: node });
1329
+ if (attrs.strike) node = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("s", { children: node });
1330
+ const styleParts = {};
1331
+ if (attrs.color) styleParts.color = attrs.color;
1332
+ if (attrs.fontFamily) styleParts.fontFamily = attrs.fontFamily;
1333
+ if (attrs.fontSize) styleParts.fontSize = attrs.fontSize;
1334
+ if (Object.keys(styleParts).length > 0) {
1335
+ node = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: styleParts, children: node });
1336
+ }
1337
+ if (attrs.link?.href) {
1338
+ node = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1339
+ "a",
1340
+ {
1341
+ href: attrs.link.href,
1342
+ title: attrs.link.title,
1343
+ rel: "noopener noreferrer",
1344
+ target: "_blank",
1345
+ onClick: (e) => {
1346
+ if (!(e.metaKey || e.ctrlKey)) e.preventDefault();
1347
+ },
1348
+ children: node
1349
+ }
1350
+ );
1351
+ }
1352
+ if (attrs.comment?.markId) {
1353
+ node = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "data-comment-id": attrs.comment.markId, className: "ed-comment", children: node });
1354
+ }
1355
+ return node;
1356
+ }
1357
+
1358
+ // src/sliceDelta.ts
1359
+ function deltaLength(delta) {
1360
+ let sum = 0;
1361
+ for (const op of delta) {
1362
+ if (typeof op.insert === "string") sum += op.insert.length;
1363
+ else if (op.insert != null) sum += 1;
1364
+ }
1365
+ return sum;
1366
+ }
1367
+ function sliceDelta(delta, start, end) {
1368
+ if (end <= start) return [];
1369
+ const out = [];
1370
+ let cursor = 0;
1371
+ for (const op of delta) {
1372
+ if (typeof op.insert === "string") {
1373
+ const opStart = cursor;
1374
+ const opEnd = cursor + op.insert.length;
1375
+ cursor = opEnd;
1376
+ if (opEnd <= start) continue;
1377
+ if (opStart >= end) break;
1378
+ const sliceFrom = Math.max(opStart, start) - opStart;
1379
+ const sliceTo = Math.min(opEnd, end) - opStart;
1380
+ out.push({
1381
+ insert: op.insert.slice(sliceFrom, sliceTo),
1382
+ attributes: op.attributes
1383
+ });
1384
+ } else if (op.insert != null) {
1385
+ const opStart = cursor;
1386
+ const opEnd = cursor + 1;
1387
+ cursor = opEnd;
1388
+ if (opEnd <= start) continue;
1389
+ if (opStart >= end) break;
1390
+ out.push({ insert: op.insert, attributes: op.attributes });
1391
+ }
1392
+ }
1393
+ return out;
1394
+ }
1395
+
1396
+ // src/TableResizeOverlay.tsx
1397
+ var import_react7 = require("react");
1398
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1399
+ function TableResizeOverlay({ tableRef, blockIndex, cols }) {
1400
+ const editor = useEditorContext();
1401
+ const [positions, setPositions] = (0, import_react7.useState)(null);
1402
+ const [layout, setLayout] = (0, import_react7.useState)(null);
1403
+ const draggingRef = (0, import_react7.useRef)(null);
1404
+ const measure = (0, import_react7.useCallback)(() => {
1405
+ const table = tableRef.current;
1406
+ if (!table) return;
1407
+ const tableRect = table.getBoundingClientRect();
1408
+ const cgCols = table.querySelectorAll("colgroup > col");
1409
+ let rights = [];
1410
+ if (cgCols.length === cols) {
1411
+ let acc = 0;
1412
+ cgCols.forEach((c) => {
1413
+ acc += c.getBoundingClientRect().width;
1414
+ rights.push(acc);
1415
+ });
1416
+ } else {
1417
+ const firstRowCells = table.querySelectorAll("tbody > tr:first-child > td");
1418
+ let acc = 0;
1419
+ firstRowCells.forEach((td) => {
1420
+ const cs = Math.max(1, td.colSpan || 1);
1421
+ const w = td.getBoundingClientRect().width / cs;
1422
+ for (let i = 0; i < cs; i++) {
1423
+ acc += w;
1424
+ rights.push(acc);
1425
+ }
1426
+ });
1427
+ }
1428
+ if (rights.length !== cols) {
1429
+ setLayout(null);
1430
+ return;
1431
+ }
1432
+ setLayout({ rights, height: tableRect.height });
1433
+ }, [cols, tableRef]);
1434
+ (0, import_react7.useLayoutEffect)(() => {
1435
+ measure();
1436
+ }, [measure]);
1437
+ (0, import_react7.useEffect)(() => {
1438
+ const table = tableRef.current;
1439
+ if (!table) return;
1440
+ const ro = new ResizeObserver(() => measure());
1441
+ ro.observe(table);
1442
+ return () => ro.disconnect();
1443
+ }, [measure, tableRef]);
1444
+ const onPointerDown = (0, import_react7.useCallback)(
1445
+ (col) => (e) => {
1446
+ e.preventDefault();
1447
+ e.stopPropagation();
1448
+ const table = tableRef.current;
1449
+ if (!table) return;
1450
+ const cgCols = table.querySelectorAll("colgroup > col");
1451
+ const startWidth = cgCols[col]?.getBoundingClientRect().width ?? 100;
1452
+ draggingRef.current = { col, startX: e.clientX, startWidth };
1453
+ setPositions({ x: e.clientX, height: layout?.height ?? 0 });
1454
+ e.currentTarget.setPointerCapture(e.pointerId);
1455
+ },
1456
+ [layout, tableRef]
1457
+ );
1458
+ const onPointerMove = (0, import_react7.useCallback)(
1459
+ (e) => {
1460
+ const d = draggingRef.current;
1461
+ if (!d) return;
1462
+ const dx = e.clientX - d.startX;
1463
+ const next = Math.max(40, d.startWidth + dx);
1464
+ editor.setColumnWidth(blockIndex, d.col, next);
1465
+ },
1466
+ [blockIndex, editor]
1467
+ );
1468
+ const onPointerUp = (0, import_react7.useCallback)((e) => {
1469
+ draggingRef.current = null;
1470
+ setPositions(null);
1471
+ try {
1472
+ e.currentTarget.releasePointerCapture(e.pointerId);
1473
+ } catch {
1474
+ }
1475
+ }, []);
1476
+ if (!layout) return null;
1477
+ const handleStyle = (x) => ({
1478
+ position: "absolute",
1479
+ top: 0,
1480
+ left: `${x - 3}px`,
1481
+ width: "6px",
1482
+ height: `${layout.height}px`,
1483
+ cursor: "col-resize",
1484
+ zIndex: 5
1485
+ });
1486
+ void positions;
1487
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: layout.rights.map((x, c) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1488
+ "div",
1489
+ {
1490
+ contentEditable: false,
1491
+ className: "ed-col-resize-handle",
1492
+ style: handleStyle(x),
1493
+ onPointerDown: onPointerDown(c),
1494
+ onPointerMove,
1495
+ onPointerUp,
1496
+ "aria-label": `Redimensionar coluna ${c + 1}`,
1497
+ role: "separator"
1498
+ },
1499
+ c
1500
+ )) });
1501
+ }
1502
+
1503
+ // src/NodeView.tsx
1504
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1505
+ function NodeView({ block, index, children, fragment, tableFragment }) {
1506
+ if (block.type === "table") {
1507
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableView, { block, index, tableFragment });
1508
+ }
1509
+ const inline = fragment ? renderInline(sliceDelta(block.delta, fragment.start, fragment.end), index) : renderInline(block.delta, index);
1510
+ const common = commonBlockProps(block.attrs, index, block.type, fragment);
1511
+ switch (block.type) {
1512
+ case "heading": {
1513
+ const level = clampLevel(block.attrs.level);
1514
+ const Tag = `h${level}`;
1515
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1516
+ Tag,
1517
+ {
1518
+ ...common,
1519
+ className: `ed-block ed-heading ed-heading-${level}${alignClass(block.attrs.align)}${fragmentClass(fragment)}`,
1520
+ children: inline
1521
+ }
1522
+ );
1523
+ }
1524
+ case "blockquote":
1525
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1526
+ "blockquote",
1527
+ {
1528
+ ...common,
1529
+ className: `ed-block ed-blockquote${alignClass(block.attrs.align)}${fragmentClass(fragment)}`,
1530
+ children: inline
1531
+ }
1532
+ );
1533
+ case "codeBlock":
1534
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1535
+ "pre",
1536
+ {
1537
+ ...common,
1538
+ "data-language": block.attrs.language ?? void 0,
1539
+ className: `ed-block ed-codeblock${alignClass(block.attrs.align)}`,
1540
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: inline })
1541
+ }
1542
+ );
1543
+ case "listItem": {
1544
+ const level = clampListLevel(block.attrs.listLevel);
1545
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1546
+ "li",
1547
+ {
1548
+ ...common,
1549
+ "data-list-kind": block.attrs.listKind ?? "bullet",
1550
+ "data-list-level": level,
1551
+ className: `ed-block ed-listitem${alignClass(block.attrs.align)}`,
1552
+ children: [
1553
+ inline,
1554
+ children
1555
+ ]
1556
+ }
1557
+ );
1558
+ }
1559
+ case "paragraph":
1560
+ default:
1561
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1562
+ "div",
1563
+ {
1564
+ ...common,
1565
+ className: `ed-block ed-paragraph${alignClass(block.attrs.align)}${fragmentClass(fragment)}`,
1566
+ children: inline
1567
+ }
1568
+ );
1569
+ }
1570
+ }
1571
+ function commonBlockProps(attrs, index, type, fragment) {
1572
+ const style = {};
1573
+ if (attrs.align) style.textAlign = attrs.align;
1574
+ const base = {
1575
+ "data-block-index": index,
1576
+ "data-block-type": type,
1577
+ dir: attrs.dir,
1578
+ style: Object.keys(style).length > 0 ? style : void 0
1579
+ };
1580
+ if (!fragment) return base;
1581
+ base["data-fragment-index"] = fragment.index;
1582
+ base["data-fragment-start"] = fragment.start;
1583
+ base["data-fragment-end"] = fragment.end;
1584
+ return base;
1585
+ }
1586
+ function fragmentClass(f) {
1587
+ if (!f) return "";
1588
+ return ` ed-fragment ed-fragment-${f.index}`;
1589
+ }
1590
+ function alignClass(a) {
1591
+ return a ? ` ed-align-${a}` : "";
1592
+ }
1593
+ function clampLevel(l) {
1594
+ if (l === 2 || l === 3 || l === 4 || l === 5 || l === 6) return l;
1595
+ return 1;
1596
+ }
1597
+ function clampListLevel(l) {
1598
+ if (typeof l !== "number" || !Number.isFinite(l)) return 0;
1599
+ return Math.max(0, Math.min(5, Math.trunc(l)));
1600
+ }
1601
+ function TableView({ block, index, tableFragment }) {
1602
+ const rows = clampPositive(block.attrs.rows);
1603
+ const cols = clampPositive(block.attrs.cols);
1604
+ const cells = block.cells ?? [];
1605
+ const widths = readColWidths(block.attrs.colWidths, cols);
1606
+ const tableRef = (0, import_react8.useRef)(null);
1607
+ const rowStart = tableFragment?.rowStart ?? 0;
1608
+ const rowEnd = tableFragment?.rowEnd ?? rows;
1609
+ const isFirstFragment = !tableFragment || tableFragment.index === 0;
1610
+ const editor = useEditorContext();
1611
+ const rect = (0, import_react8.useMemo)(() => {
1612
+ if (editor.selection.anchor.blockIndex !== index) return null;
1613
+ if (editor.selection.focus.blockIndex !== index) return null;
1614
+ return (0, import_core3.tableRectSelection)(editor.doc, editor.selection);
1615
+ }, [editor.doc, editor.selection, index]);
1616
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "ed-table-wrap", children: [
1617
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1618
+ "table",
1619
+ {
1620
+ ref: tableRef,
1621
+ "data-block-index": index,
1622
+ "data-block-type": "table",
1623
+ className: "ed-block ed-table",
1624
+ children: [
1625
+ widths && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("colgroup", { children: widths.map((w, c) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("col", { style: w != null ? { width: `${w}px` } : void 0 }, c)) }),
1626
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tbody", { children: Array.from({ length: rowEnd - rowStart }, (_, i) => {
1627
+ const r = rowStart + i;
1628
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tr", { "data-cell-row": r, children: Array.from({ length: cols }, (_2, c) => {
1629
+ const flat = r * cols + c;
1630
+ const cell = cells[flat];
1631
+ if (cell?.attrs.covered === true) return null;
1632
+ const rowspan = spanAttr(cell?.attrs.rowspan);
1633
+ const colspan = spanAttr(cell?.attrs.colspan);
1634
+ const delta = cell?.delta ?? [];
1635
+ const inRect = rect !== null && r >= rect.top && r <= rect.bottom && c >= rect.left && c <= rect.right;
1636
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1637
+ "td",
1638
+ {
1639
+ "data-cell-index": flat,
1640
+ "data-cell-row": r,
1641
+ "data-cell-col": c,
1642
+ "data-cell-rowspan": rowspan,
1643
+ "data-cell-colspan": colspan,
1644
+ rowSpan: rowspan > 1 ? rowspan : void 0,
1645
+ colSpan: colspan > 1 ? colspan : void 0,
1646
+ className: inRect ? "ed-cell ed-cell--selected" : "ed-cell",
1647
+ children: renderInline(delta, `t${index}-c${flat}`)
1648
+ },
1649
+ c
1650
+ );
1651
+ }) }, r);
1652
+ }) })
1653
+ ]
1654
+ }
1655
+ ),
1656
+ isFirstFragment && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableResizeOverlay, { tableRef, blockIndex: index, cols })
1657
+ ] });
1658
+ }
1659
+ function readColWidths(raw, cols) {
1660
+ if (!Array.isArray(raw)) return null;
1661
+ const out = [];
1662
+ for (let i = 0; i < cols; i++) {
1663
+ const v = raw[i];
1664
+ out.push(typeof v === "number" && Number.isFinite(v) && v > 0 ? v : null);
1665
+ }
1666
+ return out;
1667
+ }
1668
+ function spanAttr(v) {
1669
+ if (typeof v !== "number" || !Number.isFinite(v) || v < 1) return 1;
1670
+ return Math.trunc(v);
1671
+ }
1672
+ function clampPositive(n) {
1673
+ if (typeof n !== "number" || !Number.isFinite(n) || n <= 0) return 1;
1674
+ return Math.trunc(n);
1675
+ }
1676
+
1677
+ // src/usePagination.ts
1678
+ var import_react9 = require("react");
1679
+ var A4_PAGE = {
1680
+ width: 794,
1681
+ height: 1123,
1682
+ marginTop: 96,
1683
+ marginBottom: 96,
1684
+ marginLeft: 96,
1685
+ marginRight: 96
1686
+ };
1687
+ function contentHeight(g) {
1688
+ return g.height - g.marginTop - g.marginBottom;
1689
+ }
1690
+ function contentWidth(g) {
1691
+ return g.width - g.marginLeft - g.marginRight;
1692
+ }
1693
+ function defaultPageLayout(topLevelCount) {
1694
+ return {
1695
+ pages: [
1696
+ {
1697
+ slots: Array.from({ length: topLevelCount }, (_, i) => ({ topLevelIndex: i }))
1698
+ }
1699
+ ]
1700
+ };
1701
+ }
1702
+ function usePageLayout(rootRef, geometry, topLevelCount, deps) {
1703
+ const [phase, setPhase] = (0, import_react9.useState)("measure");
1704
+ const [layout, setLayout] = (0, import_react9.useState)(() => defaultPageLayout(topLevelCount));
1705
+ const lastDepsRef = (0, import_react9.useRef)([]);
1706
+ const currentDeps = [...deps, topLevelCount, geometry];
1707
+ if (!shallowArrayEqual(lastDepsRef.current, currentDeps)) {
1708
+ lastDepsRef.current = currentDeps;
1709
+ if (phase !== "measure") setPhase("measure");
1710
+ const next = defaultPageLayout(topLevelCount);
1711
+ if (!layoutsDeepEqual(layout, next)) setLayout(next);
1712
+ }
1713
+ (0, import_react9.useLayoutEffect)(() => {
1714
+ if (phase !== "measure") return;
1715
+ const root = rootRef.current;
1716
+ if (!root) return;
1717
+ if (!isUnfragmented(layout)) return;
1718
+ const next = computeLayout(root, geometry, topLevelCount);
1719
+ if (!layoutsDeepEqual(layout, next)) setLayout(next);
1720
+ setPhase("stable");
1721
+ }, [phase, layout, geometry, topLevelCount, rootRef]);
1722
+ return layout;
1723
+ }
1724
+ function isUnfragmented(layout) {
1725
+ return layout.pages.length === 1 && layout.pages[0].slots.every(
1726
+ (s) => s.fragment === void 0 && s.tableFragment === void 0
1727
+ );
1728
+ }
1729
+ function computeLayout(root, geometry, topLevelCount) {
1730
+ const tops = collectTopLevelElements(root);
1731
+ if (tops.length === 0) return defaultPageLayout(topLevelCount);
1732
+ const limit = contentHeight(geometry);
1733
+ const pages = [{ slots: [] }];
1734
+ let currentPage = pages[0];
1735
+ let used = 0;
1736
+ const cap = Math.min(tops.length, topLevelCount);
1737
+ for (let i = 0; i < cap; i++) {
1738
+ const el = tops[i];
1739
+ const h = elTotalHeight(el);
1740
+ if (used + h <= limit) {
1741
+ currentPage.slots.push({ topLevelIndex: i });
1742
+ used += h;
1743
+ continue;
1744
+ }
1745
+ if (isFragmentable(el)) {
1746
+ const result = placeFragmentedBlock(el, i, pages, used, limit);
1747
+ currentPage = pages[pages.length - 1];
1748
+ used = result.lastUsed;
1749
+ continue;
1750
+ }
1751
+ if (isTableTopLevel(el)) {
1752
+ const result = placeFragmentedTable(el, i, pages, used, limit);
1753
+ currentPage = pages[pages.length - 1];
1754
+ used = result.lastUsed;
1755
+ continue;
1756
+ }
1757
+ if (currentPage.slots.length === 0) {
1758
+ currentPage.slots.push({ topLevelIndex: i });
1759
+ pages.push({ slots: [] });
1760
+ currentPage = pages[pages.length - 1];
1761
+ used = 0;
1762
+ } else {
1763
+ pages.push({ slots: [] });
1764
+ currentPage = pages[pages.length - 1];
1765
+ currentPage.slots.push({ topLevelIndex: i });
1766
+ used = h;
1767
+ }
1768
+ }
1769
+ if (pages.length > 1 && pages[pages.length - 1].slots.length === 0) pages.pop();
1770
+ return { pages };
1771
+ }
1772
+ function placeFragmentedBlock(el, topLevelIndex, pages, startUsed, limit) {
1773
+ const totalChars = getBlockTextLength(el);
1774
+ if (totalChars === 0) {
1775
+ const h = elTotalHeight(el);
1776
+ let currentPage2 = pages[pages.length - 1];
1777
+ if (startUsed + h > limit && currentPage2.slots.length > 0) {
1778
+ pages.push({ slots: [] });
1779
+ currentPage2 = pages[pages.length - 1];
1780
+ startUsed = 0;
1781
+ }
1782
+ currentPage2.slots.push({ topLevelIndex });
1783
+ return { lastUsed: startUsed + h };
1784
+ }
1785
+ const lines = measureLines(el);
1786
+ if (lines.length === 0) {
1787
+ const h = elTotalHeight(el);
1788
+ let currentPage2 = pages[pages.length - 1];
1789
+ if (startUsed + h > limit && currentPage2.slots.length > 0) {
1790
+ pages.push({ slots: [] });
1791
+ currentPage2 = pages[pages.length - 1];
1792
+ startUsed = 0;
1793
+ }
1794
+ currentPage2.slots.push({ topLevelIndex });
1795
+ return { lastUsed: startUsed + h };
1796
+ }
1797
+ let lineIdx = 0;
1798
+ let currentPage = pages[pages.length - 1];
1799
+ let used = startUsed;
1800
+ let fragIndex = 0;
1801
+ while (lineIdx < lines.length) {
1802
+ const available = Math.max(0, limit - used);
1803
+ if (available <= 0) {
1804
+ pages.push({ slots: [] });
1805
+ currentPage = pages[pages.length - 1];
1806
+ used = 0;
1807
+ continue;
1808
+ }
1809
+ const startTop = lines[lineIdx].top;
1810
+ let bestK = -1;
1811
+ for (let k = lineIdx; k < lines.length; k++) {
1812
+ const h = lines[k].bottom - startTop;
1813
+ if (h <= available) bestK = k;
1814
+ else break;
1815
+ }
1816
+ if (bestK < 0) {
1817
+ if (currentPage.slots.length === 0) {
1818
+ bestK = lineIdx;
1819
+ } else {
1820
+ pages.push({ slots: [] });
1821
+ currentPage = pages[pages.length - 1];
1822
+ used = 0;
1823
+ continue;
1824
+ }
1825
+ }
1826
+ const fragStart = lines[lineIdx].start;
1827
+ const fragEnd = bestK + 1 < lines.length ? lines[bestK + 1].start : totalChars;
1828
+ const fragHeight = lines[bestK].bottom - startTop;
1829
+ currentPage.slots.push({
1830
+ topLevelIndex,
1831
+ fragment: { index: fragIndex, start: fragStart, end: fragEnd }
1832
+ });
1833
+ used += fragHeight;
1834
+ fragIndex++;
1835
+ lineIdx = bestK + 1;
1836
+ if (lineIdx < lines.length) {
1837
+ pages.push({ slots: [] });
1838
+ currentPage = pages[pages.length - 1];
1839
+ used = 0;
1840
+ }
1841
+ }
1842
+ return { lastUsed: used };
1843
+ }
1844
+ var lineMetricsCache = /* @__PURE__ */ new Map();
1845
+ var LINE_METRICS_CACHE_LIMIT = 200;
1846
+ function measureLines(el) {
1847
+ const text = el.textContent ?? "";
1848
+ const cacheKey = `${el.offsetWidth}|${text}`;
1849
+ const cached = lineMetricsCache.get(cacheKey);
1850
+ if (cached) {
1851
+ lineMetricsCache.delete(cacheKey);
1852
+ lineMetricsCache.set(cacheKey, cached);
1853
+ return cached;
1854
+ }
1855
+ const fresh = measureLinesUncached(el);
1856
+ lineMetricsCache.set(cacheKey, fresh);
1857
+ if (lineMetricsCache.size > LINE_METRICS_CACHE_LIMIT) {
1858
+ const oldest = lineMetricsCache.keys().next().value;
1859
+ if (oldest !== void 0) lineMetricsCache.delete(oldest);
1860
+ }
1861
+ return fresh;
1862
+ }
1863
+ function measureLinesUncached(el) {
1864
+ const range = document.createRange();
1865
+ const lines = [];
1866
+ const walker = document.createTreeWalker(
1867
+ el,
1868
+ NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
1869
+ blockBoundaryFilter(el)
1870
+ );
1871
+ let n;
1872
+ let charOffset = 0;
1873
+ while (n = walker.nextNode()) {
1874
+ if (n.nodeType === Node.TEXT_NODE) {
1875
+ const text = n.textContent ?? "";
1876
+ for (let i = 0; i < text.length; i++) {
1877
+ try {
1878
+ range.setStart(n, i);
1879
+ range.setEnd(n, i + 1);
1880
+ } catch {
1881
+ continue;
1882
+ }
1883
+ const rect = range.getBoundingClientRect();
1884
+ if (rect.width === 0 && rect.height === 0) continue;
1885
+ const last = lines[lines.length - 1];
1886
+ if (!last || rect.top >= last.bottom) {
1887
+ lines.push({ start: charOffset + i, top: rect.top, bottom: rect.bottom });
1888
+ } else {
1889
+ last.top = Math.min(last.top, rect.top);
1890
+ last.bottom = Math.max(last.bottom, rect.bottom);
1891
+ }
1892
+ }
1893
+ charOffset += text.length;
1894
+ continue;
1895
+ }
1896
+ if (n.nodeType === Node.ELEMENT_NODE && n.dataset.embed === "image") {
1897
+ const rect = n.getBoundingClientRect();
1898
+ if (rect.height > 0) {
1899
+ const last = lines[lines.length - 1];
1900
+ if (!last || rect.top >= last.bottom) {
1901
+ lines.push({ start: charOffset, top: rect.top, bottom: rect.bottom });
1902
+ } else {
1903
+ last.top = Math.min(last.top, rect.top);
1904
+ last.bottom = Math.max(last.bottom, rect.bottom);
1905
+ }
1906
+ }
1907
+ charOffset += 1;
1908
+ }
1909
+ }
1910
+ return lines;
1911
+ }
1912
+ function elTotalHeight(el) {
1913
+ const cs = getComputedStyle(el);
1914
+ const m = parseFloat(cs.marginTop || "0") + parseFloat(cs.marginBottom || "0");
1915
+ return el.offsetHeight + m;
1916
+ }
1917
+ function isFragmentable(el) {
1918
+ const type = el.getAttribute("data-block-type");
1919
+ if (type === "paragraph") return true;
1920
+ if (type === "heading") return true;
1921
+ if (type === "blockquote") return true;
1922
+ return false;
1923
+ }
1924
+ function isTableTopLevel(el) {
1925
+ return el.querySelector(':scope > table[data-block-type="table"]') !== null;
1926
+ }
1927
+ function placeFragmentedTable(el, topLevelIndex, pages, startUsed, limit) {
1928
+ const table = el.querySelector(':scope > table[data-block-type="table"]');
1929
+ const tbody = table?.querySelector(":scope > tbody");
1930
+ const trs = tbody ? Array.from(tbody.children) : [];
1931
+ if (trs.length === 0) {
1932
+ const h = elTotalHeight(el);
1933
+ let currentPage2 = pages[pages.length - 1];
1934
+ if (startUsed + h > limit && currentPage2.slots.length > 0) {
1935
+ pages.push({ slots: [] });
1936
+ currentPage2 = pages[pages.length - 1];
1937
+ startUsed = 0;
1938
+ }
1939
+ currentPage2.slots.push({ topLevelIndex });
1940
+ return { lastUsed: startUsed + h };
1941
+ }
1942
+ const rowHeights = trs.map((tr) => tr.getBoundingClientRect().height);
1943
+ const invalid = /* @__PURE__ */ new Set();
1944
+ for (let r = 0; r < trs.length; r++) {
1945
+ const tds = Array.from(trs[r].children);
1946
+ for (const td of tds) {
1947
+ const rs = td.rowSpan || 1;
1948
+ if (rs > 1) {
1949
+ for (let k = r; k <= r + rs - 2 && k < trs.length - 1; k++) {
1950
+ invalid.add(k);
1951
+ }
1952
+ }
1953
+ }
1954
+ }
1955
+ const wrapHeight = elTotalHeight(el);
1956
+ const tableHeight = table ? table.getBoundingClientRect().height : 0;
1957
+ const chrome = Math.max(0, wrapHeight - tableHeight);
1958
+ let rowIdx = 0;
1959
+ let used = startUsed;
1960
+ let fragIndex = 0;
1961
+ let currentPage = pages[pages.length - 1];
1962
+ while (rowIdx < trs.length) {
1963
+ const overhead = fragIndex === 0 ? chrome : 0;
1964
+ const available = Math.max(0, limit - used - overhead);
1965
+ if (available <= 0) {
1966
+ pages.push({ slots: [] });
1967
+ currentPage = pages[pages.length - 1];
1968
+ used = 0;
1969
+ continue;
1970
+ }
1971
+ let bestEnd = -1;
1972
+ let accH = 0;
1973
+ for (let k = rowIdx; k < trs.length; k++) {
1974
+ accH += rowHeights[k];
1975
+ if (accH > available) break;
1976
+ if (k === trs.length - 1 || !invalid.has(k)) bestEnd = k + 1;
1977
+ }
1978
+ if (bestEnd < 0) {
1979
+ if (currentPage.slots.length === 0) {
1980
+ let k = rowIdx;
1981
+ while (k < trs.length - 1 && invalid.has(k)) k++;
1982
+ bestEnd = k + 1;
1983
+ } else {
1984
+ pages.push({ slots: [] });
1985
+ currentPage = pages[pages.length - 1];
1986
+ used = 0;
1987
+ continue;
1988
+ }
1989
+ }
1990
+ let fragHeight = 0;
1991
+ for (let k = rowIdx; k < bestEnd; k++) fragHeight += rowHeights[k];
1992
+ if (fragIndex === 0) fragHeight += chrome;
1993
+ currentPage.slots.push({
1994
+ topLevelIndex,
1995
+ tableFragment: { index: fragIndex, rowStart: rowIdx, rowEnd: bestEnd }
1996
+ });
1997
+ used += fragHeight;
1998
+ fragIndex++;
1999
+ rowIdx = bestEnd;
2000
+ if (rowIdx < trs.length) {
2001
+ pages.push({ slots: [] });
2002
+ currentPage = pages[pages.length - 1];
2003
+ used = 0;
2004
+ }
2005
+ }
2006
+ return { lastUsed: used };
2007
+ }
2008
+ function collectTopLevelElements(root) {
2009
+ const pageContents = root.querySelectorAll(":scope > .ed-page > .ed-page-content");
2010
+ if (pageContents.length > 0) {
2011
+ const out = [];
2012
+ pageContents.forEach((pc) => {
2013
+ Array.from(pc.children).forEach((c) => out.push(c));
2014
+ });
2015
+ return out;
2016
+ }
2017
+ return Array.from(root.children);
2018
+ }
2019
+ function getBlockTextLength(el) {
2020
+ const walker = document.createTreeWalker(
2021
+ el,
2022
+ NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
2023
+ blockBoundaryFilter(el)
2024
+ );
2025
+ let n;
2026
+ let sum = 0;
2027
+ while (n = walker.nextNode()) {
2028
+ if (n.nodeType === Node.TEXT_NODE) {
2029
+ sum += (n.textContent ?? "").length;
2030
+ } else if (n.nodeType === Node.ELEMENT_NODE && n.dataset.embed === "image") {
2031
+ sum += 1;
2032
+ }
2033
+ }
2034
+ return sum;
2035
+ }
2036
+ function blockBoundaryFilter(blockEl) {
2037
+ return {
2038
+ acceptNode(n) {
2039
+ let p = n.parentNode;
2040
+ while (p && p !== blockEl) {
2041
+ if (p.nodeType === Node.ELEMENT_NODE) {
2042
+ const e = p;
2043
+ if (e.dataset.blockIndex != null) return NodeFilter.FILTER_REJECT;
2044
+ }
2045
+ p = p.parentNode;
2046
+ }
2047
+ return NodeFilter.FILTER_ACCEPT;
2048
+ }
2049
+ };
2050
+ }
2051
+ function shallowArrayEqual(a, b) {
2052
+ if (a.length !== b.length) return false;
2053
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
2054
+ return true;
2055
+ }
2056
+ function layoutsDeepEqual(a, b) {
2057
+ if (a.pages.length !== b.pages.length) return false;
2058
+ for (let i = 0; i < a.pages.length; i++) {
2059
+ const sA = a.pages[i].slots;
2060
+ const sB = b.pages[i].slots;
2061
+ if (sA.length !== sB.length) return false;
2062
+ for (let j = 0; j < sA.length; j++) {
2063
+ if (sA[j].topLevelIndex !== sB[j].topLevelIndex) return false;
2064
+ const fA = sA[j].fragment;
2065
+ const fB = sB[j].fragment;
2066
+ if (fA || fB) {
2067
+ if (!fA || !fB) return false;
2068
+ if (fA.index !== fB.index || fA.start !== fB.start || fA.end !== fB.end) return false;
2069
+ }
2070
+ const tA = sA[j].tableFragment;
2071
+ const tB = sB[j].tableFragment;
2072
+ if (tA || tB) {
2073
+ if (!tA || !tB) return false;
2074
+ if (tA.index !== tB.index || tA.rowStart !== tB.rowStart || tA.rowEnd !== tB.rowEnd) {
2075
+ return false;
2076
+ }
2077
+ }
2078
+ }
2079
+ }
2080
+ return true;
2081
+ }
2082
+
2083
+ // src/useEditor.ts
2084
+ var import_react11 = require("react");
2085
+
2086
+ // src/usePageSettings.ts
2087
+ var import_react10 = require("react");
2088
+ function usePageSettings(doc) {
2089
+ const cacheRef = (0, import_react10.useRef)(null);
2090
+ const subscribe = (0, import_react10.useCallback)(
2091
+ (notify) => doc.observePageSettings(notify),
2092
+ [doc]
2093
+ );
2094
+ const getSnapshot = (0, import_react10.useCallback)(() => {
2095
+ const next = doc.getPageSettings();
2096
+ const prev = cacheRef.current;
2097
+ if (prev && shallowEqual(prev, next)) return prev;
2098
+ cacheRef.current = next;
2099
+ return next;
2100
+ }, [doc]);
2101
+ return (0, import_react10.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
2102
+ }
2103
+ function shallowEqual(a, b) {
2104
+ return a.width === b.width && a.height === b.height && a.marginTop === b.marginTop && a.marginBottom === b.marginBottom && a.marginLeft === b.marginLeft && a.marginRight === b.marginRight && a.preset === b.preset;
2105
+ }
2106
+
2107
+ // src/useEditor.ts
2108
+ var import_core4 = require("@sofereditor/core");
2109
+ var MAX_INSERT_WIDTH = 600;
2110
+ function useEditor(opts = {}) {
2111
+ const doc = (0, import_react11.useMemo)(() => opts.document ?? new import_core4.EditorDocument(), [opts.document]);
2112
+ const history = (0, import_react11.useMemo)(() => new import_core4.EditorHistory(doc), [doc]);
2113
+ const [snapshot, setSnapshot] = (0, import_react11.useState)(() => doc.toJSON());
2114
+ const [selection, setSelectionState] = (0, import_react11.useState)(
2115
+ () => (0, import_core4.collapsedSelection)({ blockIndex: 0, offset: 0 })
2116
+ );
2117
+ const selectionRef = (0, import_react11.useRef)(selection);
2118
+ selectionRef.current = selection;
2119
+ const pendingMarksRef = (0, import_react11.useRef)({});
2120
+ const [pendingMarks, setPendingMarksState] = (0, import_react11.useState)({});
2121
+ const setPendingMarks = (0, import_react11.useCallback)((next) => {
2122
+ pendingMarksRef.current = next;
2123
+ setPendingMarksState(next);
2124
+ }, []);
2125
+ (0, import_react11.useEffect)(() => {
2126
+ let scheduled = false;
2127
+ let cancelled = false;
2128
+ let frame = 0;
2129
+ const flush = () => {
2130
+ scheduled = false;
2131
+ if (cancelled) return;
2132
+ setSnapshot(doc.toJSON());
2133
+ };
2134
+ const schedule = () => {
2135
+ if (scheduled) return;
2136
+ scheduled = true;
2137
+ if (typeof requestAnimationFrame === "function") {
2138
+ frame = requestAnimationFrame(flush);
2139
+ } else {
2140
+ queueMicrotask(flush);
2141
+ }
2142
+ };
2143
+ doc.ydoc.on("afterTransaction", schedule);
2144
+ return () => {
2145
+ cancelled = true;
2146
+ if (scheduled && typeof requestAnimationFrame === "function" && typeof cancelAnimationFrame === "function") {
2147
+ cancelAnimationFrame(frame);
2148
+ }
2149
+ doc.ydoc.off("afterTransaction", schedule);
2150
+ };
2151
+ }, [doc]);
2152
+ const getSelection = (0, import_react11.useCallback)(() => selectionRef.current, []);
2153
+ const setSelection = (0, import_react11.useCallback)(
2154
+ (sel) => {
2155
+ const prev = selectionRef.current;
2156
+ selectionRef.current = sel;
2157
+ setSelectionState(sel);
2158
+ if (Object.keys(pendingMarksRef.current).length > 0 && (prev.focus.blockIndex !== sel.focus.blockIndex || prev.focus.offset !== sel.focus.offset)) {
2159
+ const advancedByOne = prev.focus.blockIndex === sel.focus.blockIndex && sel.focus.offset === prev.focus.offset + 1;
2160
+ if (!advancedByOne) {
2161
+ pendingMarksRef.current = {};
2162
+ setPendingMarksState({});
2163
+ }
2164
+ }
2165
+ },
2166
+ []
2167
+ );
2168
+ const ctxRef = (0, import_react11.useRef)({ doc, getSelection, setSelection });
2169
+ ctxRef.current = { doc, getSelection, setSelection };
2170
+ const consumePendingMarks = (0, import_react11.useCallback)(() => {
2171
+ const current = pendingMarksRef.current;
2172
+ if (Object.keys(current).length === 0) return void 0;
2173
+ pendingMarksRef.current = {};
2174
+ setPendingMarksState({});
2175
+ return current;
2176
+ }, []);
2177
+ const isMarkActive = (0, import_react11.useCallback)(
2178
+ (name, value) => {
2179
+ const sel = selectionRef.current;
2180
+ const { start, end } = (0, import_core4.orderedRange)(sel);
2181
+ if (start.blockIndex === end.blockIndex && start.offset === end.offset) {
2182
+ const pending = pendingMarksRef.current[name];
2183
+ if (pending !== void 0) {
2184
+ if (pending === null) return false;
2185
+ return value === void 0 ? true : sameValue(pending, value);
2186
+ }
2187
+ const yText2 = doc.getBlockText(start.blockIndex);
2188
+ if (!yText2 || start.offset === 0) return false;
2189
+ return (0, import_core4.isMarkUniformInRange)(yText2, start.offset - 1, start.offset, name, value);
2190
+ }
2191
+ if (start.blockIndex !== end.blockIndex) {
2192
+ for (let i = start.blockIndex; i <= end.blockIndex; i++) {
2193
+ const yText2 = doc.getBlockText(i);
2194
+ if (!yText2) return false;
2195
+ const s = i === start.blockIndex ? start.offset : 0;
2196
+ const e = i === end.blockIndex ? end.offset : yText2.length;
2197
+ if (s >= e) continue;
2198
+ if (!(0, import_core4.isMarkUniformInRange)(yText2, s, e, name, value)) return false;
2199
+ }
2200
+ return true;
2201
+ }
2202
+ const yText = doc.getBlockText(start.blockIndex);
2203
+ if (!yText) return false;
2204
+ return (0, import_core4.isMarkUniformInRange)(yText, start.offset, end.offset, name, value);
2205
+ },
2206
+ [doc]
2207
+ );
2208
+ const getActiveMarks = (0, import_react11.useCallback)(() => {
2209
+ const sel = selectionRef.current;
2210
+ const { start, end } = (0, import_core4.orderedRange)(sel);
2211
+ if (start.blockIndex !== end.blockIndex) {
2212
+ return {};
2213
+ }
2214
+ const yText = doc.getBlockText(start.blockIndex);
2215
+ if (!yText) return {};
2216
+ if (start.offset === end.offset) {
2217
+ if (start.offset === 0) return {};
2218
+ return (0, import_core4.getMarksInRange)(yText, start.offset - 1, start.offset);
2219
+ }
2220
+ return (0, import_core4.getMarksInRange)(yText, start.offset, end.offset);
2221
+ }, [doc]);
2222
+ const toggleMark = (0, import_react11.useCallback)(
2223
+ (name, value) => {
2224
+ const sel = selectionRef.current;
2225
+ const { start, end } = (0, import_core4.orderedRange)(sel);
2226
+ const isCollapsed = start.blockIndex === end.blockIndex && start.offset === end.offset;
2227
+ if (isCollapsed) {
2228
+ const next = { ...pendingMarksRef.current };
2229
+ const currentlyActive = isMarkActive(name, value);
2230
+ if (currentlyActive) {
2231
+ next[name] = null;
2232
+ } else {
2233
+ next[name] = value ?? (booleanMarks.has(name) ? true : null);
2234
+ }
2235
+ setPendingMarks(next);
2236
+ return;
2237
+ }
2238
+ (0, import_core4.toggleMark)(ctxRef.current, name, value);
2239
+ },
2240
+ [isMarkActive, setPendingMarks]
2241
+ );
2242
+ const setMark = (0, import_react11.useCallback)(
2243
+ (name, value) => {
2244
+ const sel = selectionRef.current;
2245
+ const { start, end } = (0, import_core4.orderedRange)(sel);
2246
+ const isCollapsed = start.blockIndex === end.blockIndex && start.offset === end.offset;
2247
+ if (isCollapsed) {
2248
+ setPendingMarks({ ...pendingMarksRef.current, [name]: value });
2249
+ return;
2250
+ }
2251
+ (0, import_core4.setMark)(ctxRef.current, name, value);
2252
+ },
2253
+ [setPendingMarks]
2254
+ );
2255
+ const removeMark = (0, import_react11.useCallback)(
2256
+ (name) => {
2257
+ const sel = selectionRef.current;
2258
+ const { start, end } = (0, import_core4.orderedRange)(sel);
2259
+ const isCollapsed = start.blockIndex === end.blockIndex && start.offset === end.offset;
2260
+ if (isCollapsed) {
2261
+ setPendingMarks({ ...pendingMarksRef.current, [name]: null });
2262
+ return;
2263
+ }
2264
+ (0, import_core4.removeMark)(ctxRef.current, name);
2265
+ },
2266
+ [setPendingMarks]
2267
+ );
2268
+ const getBlockType = (0, import_react11.useCallback)(() => {
2269
+ const sel = selectionRef.current;
2270
+ const { start, end } = (0, import_core4.orderedRange)(sel);
2271
+ const first = doc.getBlockType(start.blockIndex);
2272
+ if (first === void 0) return "paragraph";
2273
+ for (let i = start.blockIndex + 1; i <= end.blockIndex; i++) {
2274
+ if (doc.getBlockType(i) !== first) return "mixed";
2275
+ }
2276
+ return first;
2277
+ }, [doc]);
2278
+ const getBlockAttr = (0, import_react11.useCallback)(
2279
+ (key) => {
2280
+ const sel = selectionRef.current;
2281
+ const { start, end } = (0, import_core4.orderedRange)(sel);
2282
+ const first = doc.getBlockAttrs(start.blockIndex)[key];
2283
+ for (let i = start.blockIndex + 1; i <= end.blockIndex; i++) {
2284
+ const next = doc.getBlockAttrs(i)[key];
2285
+ if (next !== first) return "mixed";
2286
+ }
2287
+ return first;
2288
+ },
2289
+ [doc]
2290
+ );
2291
+ const setBlockType = (0, import_react11.useCallback)((type, attrs) => {
2292
+ (0, import_core4.setBlockType)(ctxRef.current, type, attrs);
2293
+ }, []);
2294
+ const setBlockAttr = (0, import_react11.useCallback)(
2295
+ (key, value) => {
2296
+ (0, import_core4.setBlockAttr)(ctxRef.current, key, value);
2297
+ },
2298
+ []
2299
+ );
2300
+ const isListActive = (0, import_react11.useCallback)(
2301
+ (kind) => {
2302
+ const sel = selectionRef.current;
2303
+ const { start, end } = (0, import_core4.orderedRange)(sel);
2304
+ for (let i = start.blockIndex; i <= end.blockIndex; i++) {
2305
+ if (doc.getBlockType(i) !== "listItem") return false;
2306
+ if (doc.getBlockAttrs(i).listKind !== kind) return false;
2307
+ }
2308
+ return true;
2309
+ },
2310
+ [doc]
2311
+ );
2312
+ const toggleList = (0, import_react11.useCallback)((kind) => {
2313
+ (0, import_core4.toggleList)(ctxRef.current, kind);
2314
+ }, []);
2315
+ const indentList = (0, import_react11.useCallback)(() => {
2316
+ (0, import_core4.indentList)(ctxRef.current);
2317
+ }, []);
2318
+ const dedentList = (0, import_react11.useCallback)(() => {
2319
+ (0, import_core4.dedentList)(ctxRef.current);
2320
+ }, []);
2321
+ const splitListItem = (0, import_react11.useCallback)(() => {
2322
+ return (0, import_core4.splitListItem)(ctxRef.current);
2323
+ }, []);
2324
+ const isInTable = (0, import_react11.useCallback)(() => {
2325
+ return selectionRef.current.focus.cellIndex != null;
2326
+ }, []);
2327
+ const getTableLocation = (0, import_react11.useCallback)(() => {
2328
+ return (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2329
+ }, [doc]);
2330
+ const insertTable = (0, import_react11.useCallback)((rows, cols) => {
2331
+ (0, import_core4.insertTable)(ctxRef.current, rows, cols);
2332
+ }, []);
2333
+ const deleteTable = (0, import_react11.useCallback)((blockIndex) => {
2334
+ const idx = blockIndex ?? selectionRef.current.focus.blockIndex;
2335
+ (0, import_core4.deleteTable)(ctxRef.current, idx);
2336
+ }, []);
2337
+ const insertRowAbove = (0, import_react11.useCallback)(() => {
2338
+ const loc = (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2339
+ if (!loc) return;
2340
+ (0, import_core4.insertTableRow)(ctxRef.current, loc.blockIndex, loc.row, "before");
2341
+ }, [doc]);
2342
+ const insertRowBelow = (0, import_react11.useCallback)(() => {
2343
+ const loc = (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2344
+ if (!loc) return;
2345
+ (0, import_core4.insertTableRow)(ctxRef.current, loc.blockIndex, loc.row, "after");
2346
+ }, [doc]);
2347
+ const deleteCurrentRow = (0, import_react11.useCallback)(() => {
2348
+ const loc = (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2349
+ if (!loc) return;
2350
+ (0, import_core4.deleteTableRow)(ctxRef.current, loc.blockIndex, loc.row);
2351
+ }, [doc]);
2352
+ const insertColumnLeft = (0, import_react11.useCallback)(() => {
2353
+ const loc = (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2354
+ if (!loc) return;
2355
+ (0, import_core4.insertTableColumn)(ctxRef.current, loc.blockIndex, loc.col, "before");
2356
+ }, [doc]);
2357
+ const insertColumnRight = (0, import_react11.useCallback)(() => {
2358
+ const loc = (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2359
+ if (!loc) return;
2360
+ (0, import_core4.insertTableColumn)(ctxRef.current, loc.blockIndex, loc.col, "after");
2361
+ }, [doc]);
2362
+ const deleteCurrentColumn = (0, import_react11.useCallback)(() => {
2363
+ const loc = (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2364
+ if (!loc) return;
2365
+ (0, import_core4.deleteTableColumn)(ctxRef.current, loc.blockIndex, loc.col);
2366
+ }, [doc]);
2367
+ const moveToNextCell = (0, import_react11.useCallback)(() => {
2368
+ (0, import_core4.moveToNextCell)(ctxRef.current);
2369
+ }, []);
2370
+ const moveToPrevCell = (0, import_react11.useCallback)(() => {
2371
+ (0, import_core4.moveToPrevCell)(ctxRef.current);
2372
+ }, []);
2373
+ const mergeRight = (0, import_react11.useCallback)(() => {
2374
+ const loc = (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2375
+ if (!loc) return false;
2376
+ return (0, import_core4.mergeRight)(ctxRef.current, loc.blockIndex, loc.row, loc.col);
2377
+ }, [doc]);
2378
+ const mergeDown = (0, import_react11.useCallback)(() => {
2379
+ const loc = (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2380
+ if (!loc) return false;
2381
+ return (0, import_core4.mergeDown)(ctxRef.current, loc.blockIndex, loc.row, loc.col);
2382
+ }, [doc]);
2383
+ const splitCell = (0, import_react11.useCallback)(() => {
2384
+ const loc = (0, import_core4.tableLocationOf)(doc, selectionRef.current.focus);
2385
+ if (!loc) return false;
2386
+ return (0, import_core4.splitCell)(ctxRef.current, loc.blockIndex, loc.row, loc.col);
2387
+ }, [doc]);
2388
+ const setColumnWidth = (0, import_react11.useCallback)(
2389
+ (blockIndex, col, widthPx) => {
2390
+ (0, import_core4.setColumnWidth)(ctxRef.current, blockIndex, col, widthPx);
2391
+ },
2392
+ []
2393
+ );
2394
+ const getTableRect = (0, import_react11.useCallback)(
2395
+ () => (0, import_core4.tableRectSelection)(doc, selectionRef.current),
2396
+ [doc]
2397
+ );
2398
+ const hasTableRectSelection = (0, import_react11.useCallback)(() => {
2399
+ return (0, import_core4.tableRectSelection)(doc, selectionRef.current) !== null;
2400
+ }, [doc]);
2401
+ const mergeSelection = (0, import_react11.useCallback)(() => {
2402
+ return (0, import_core4.mergeSelection)(ctxRef.current);
2403
+ }, []);
2404
+ const insertImage = (0, import_react11.useCallback)((embed) => {
2405
+ (0, import_core4.insertImage)(ctxRef.current, embed);
2406
+ }, []);
2407
+ const setImageAttrs = (0, import_react11.useCallback)(
2408
+ (blockIndex, offset, partial, cellIndex, newOffset) => {
2409
+ (0, import_core4.setImageAttrs)(ctxRef.current, blockIndex, offset, partial, cellIndex, newOffset);
2410
+ },
2411
+ []
2412
+ );
2413
+ const uploadImageRef = (0, import_react11.useRef)(opts.uploadImage);
2414
+ uploadImageRef.current = opts.uploadImage;
2415
+ const insertImageFromFile = (0, import_react11.useCallback)(async (file) => {
2416
+ if (!file.type.startsWith("image/")) return;
2417
+ const dataUrl = await readAsDataURL(file);
2418
+ const dims = await readImageDimensions(dataUrl);
2419
+ let w = dims.width;
2420
+ let h = dims.height;
2421
+ if (w > MAX_INSERT_WIDTH) {
2422
+ h = Math.round(h * MAX_INSERT_WIDTH / w);
2423
+ w = MAX_INSERT_WIDTH;
2424
+ }
2425
+ const upload = uploadImageRef.current;
2426
+ const src = upload ? await upload(file) : dataUrl;
2427
+ (0, import_core4.insertImage)(ctxRef.current, { type: "image", src, width: w, height: h });
2428
+ }, []);
2429
+ const getSelectedEmbed = (0, import_react11.useCallback)(() => {
2430
+ const sel = selectionRef.current;
2431
+ if (sel.anchor.blockIndex !== sel.focus.blockIndex) return null;
2432
+ if ((sel.anchor.cellIndex ?? -1) !== (sel.focus.cellIndex ?? -1)) return null;
2433
+ const a = sel.anchor.offset;
2434
+ const b = sel.focus.offset;
2435
+ if (Math.abs(a - b) !== 1) return null;
2436
+ const start = Math.min(a, b);
2437
+ const yText = doc.textAt(sel.anchor.blockIndex, sel.anchor.cellIndex);
2438
+ if (!yText) return null;
2439
+ const delta = yText.toDelta();
2440
+ let cursor = 0;
2441
+ for (const op of delta) {
2442
+ if (typeof op.insert === "string") {
2443
+ cursor += op.insert.length;
2444
+ continue;
2445
+ }
2446
+ if (cursor === start && (0, import_core4.isImageEmbed)(op.insert)) {
2447
+ return {
2448
+ blockIndex: sel.anchor.blockIndex,
2449
+ cellIndex: sel.anchor.cellIndex,
2450
+ offset: start,
2451
+ embed: op.insert
2452
+ };
2453
+ }
2454
+ cursor += 1;
2455
+ }
2456
+ return null;
2457
+ }, [doc]);
2458
+ const [linkRequest, setLinkRequest] = (0, import_react11.useState)(null);
2459
+ const requestLink = (0, import_react11.useCallback)((initialHref = "") => {
2460
+ return new Promise((resolve) => {
2461
+ setLinkRequest({ initialHref, resolve });
2462
+ });
2463
+ }, []);
2464
+ const resolveLinkRequest = (0, import_react11.useCallback)((href) => {
2465
+ setLinkRequest((prev) => {
2466
+ if (prev) prev.resolve(href);
2467
+ return null;
2468
+ });
2469
+ }, []);
2470
+ const pageSettings = usePageSettings(doc);
2471
+ const setPageSettings = (0, import_react11.useCallback)(
2472
+ (partial) => {
2473
+ doc.setPageSettings(partial);
2474
+ },
2475
+ [doc]
2476
+ );
2477
+ const [pageConfigRequest, setPageConfigRequest] = (0, import_react11.useState)(null);
2478
+ const requestPageConfig = (0, import_react11.useCallback)(() => {
2479
+ return new Promise((resolve) => {
2480
+ setPageConfigRequest({ initial: doc.getPageSettings(), resolve });
2481
+ });
2482
+ }, [doc]);
2483
+ const resolvePageConfigRequest = (0, import_react11.useCallback)((applied) => {
2484
+ setPageConfigRequest((prev) => {
2485
+ if (prev) prev.resolve(applied);
2486
+ return null;
2487
+ });
2488
+ }, []);
2489
+ const [imageCaptionRequest, setImageCaptionRequest] = (0, import_react11.useState)(null);
2490
+ const requestImageCaption = (0, import_react11.useCallback)(
2491
+ (initialCaption = "", initialAlign = "center") => {
2492
+ return new Promise((resolve) => {
2493
+ setImageCaptionRequest({ initialCaption, initialAlign, resolve });
2494
+ });
2495
+ },
2496
+ []
2497
+ );
2498
+ const resolveImageCaptionRequest = (0, import_react11.useCallback)((result) => {
2499
+ setImageCaptionRequest((prev) => {
2500
+ if (prev) prev.resolve(result);
2501
+ return null;
2502
+ });
2503
+ }, []);
2504
+ return {
2505
+ doc,
2506
+ history,
2507
+ snapshot,
2508
+ selection,
2509
+ setSelection,
2510
+ getSelection,
2511
+ isMarkActive,
2512
+ getActiveMarks,
2513
+ toggleMark,
2514
+ setMark,
2515
+ removeMark,
2516
+ pendingMarks,
2517
+ consumePendingMarks,
2518
+ getBlockType,
2519
+ getBlockAttr,
2520
+ setBlockType,
2521
+ setBlockAttr,
2522
+ isListActive,
2523
+ toggleList,
2524
+ indentList,
2525
+ dedentList,
2526
+ splitListItem,
2527
+ linkRequest,
2528
+ requestLink,
2529
+ resolveLinkRequest,
2530
+ isInTable,
2531
+ getTableLocation,
2532
+ insertTable,
2533
+ deleteTable,
2534
+ insertRowAbove,
2535
+ insertRowBelow,
2536
+ deleteCurrentRow,
2537
+ insertColumnLeft,
2538
+ insertColumnRight,
2539
+ deleteCurrentColumn,
2540
+ moveToNextCell,
2541
+ moveToPrevCell,
2542
+ mergeRight,
2543
+ mergeDown,
2544
+ splitCell,
2545
+ setColumnWidth,
2546
+ getTableRect,
2547
+ hasTableRectSelection,
2548
+ mergeSelection,
2549
+ insertImage,
2550
+ setImageAttrs,
2551
+ insertImageFromFile,
2552
+ getSelectedEmbed,
2553
+ pageSettings,
2554
+ setPageSettings,
2555
+ pageConfigRequest,
2556
+ requestPageConfig,
2557
+ resolvePageConfigRequest,
2558
+ imageCaptionRequest,
2559
+ requestImageCaption,
2560
+ resolveImageCaptionRequest
2561
+ };
2562
+ }
2563
+ function readAsDataURL(file) {
2564
+ return new Promise((resolve, reject) => {
2565
+ const reader = new FileReader();
2566
+ reader.onerror = () => reject(reader.error ?? new Error("FileReader error"));
2567
+ reader.onload = () => resolve(reader.result);
2568
+ reader.readAsDataURL(file);
2569
+ });
2570
+ }
2571
+ function readImageDimensions(src) {
2572
+ return new Promise((resolve, reject) => {
2573
+ const img = new Image();
2574
+ img.onerror = () => reject(new Error("image load failed"));
2575
+ img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
2576
+ img.src = src;
2577
+ });
2578
+ }
2579
+ var booleanMarks = /* @__PURE__ */ new Set(["bold", "italic", "underline", "strike"]);
2580
+ function sameValue(a, b) {
2581
+ if (a === b) return true;
2582
+ if (typeof a === "object" && a && b && typeof b === "object") {
2583
+ const ak = Object.keys(a);
2584
+ const bk = Object.keys(b);
2585
+ if (ak.length !== bk.length) return false;
2586
+ return ak.every((k) => a[k] === b[k]);
2587
+ }
2588
+ return false;
2589
+ }
2590
+
2591
+ // src/Editor.tsx
2592
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2593
+ var defaultFooter = ({ pageNumber, pageCount }) => `${pageNumber} / ${pageCount}`;
2594
+ function Editor({
2595
+ editor: providedEditor,
2596
+ className,
2597
+ pageGeometry,
2598
+ renderPageHeader,
2599
+ renderPageFooter = defaultFooter
2600
+ }) {
2601
+ const internal = useEditor();
2602
+ const editor = providedEditor ?? internal;
2603
+ const { doc, history, snapshot, getSelection, setSelection } = editor;
2604
+ const rootRef = (0, import_react12.useRef)(null);
2605
+ const isComposingRef = (0, import_react12.useRef)(false);
2606
+ const suppressSelectionSyncRef = (0, import_react12.useRef)(false);
2607
+ const ctxRef = (0, import_react12.useRef)({ doc, getSelection, setSelection });
2608
+ ctxRef.current = { doc, getSelection, setSelection };
2609
+ const historyRef = (0, import_react12.useRef)(history);
2610
+ historyRef.current = history;
2611
+ const editorRef = (0, import_react12.useRef)(editor);
2612
+ editorRef.current = editor;
2613
+ (0, import_react12.useLayoutEffect)(() => {
2614
+ const root = rootRef.current;
2615
+ if (!root) return;
2616
+ if (isComposingRef.current) return;
2617
+ if (document.activeElement !== root) return;
2618
+ const current = readDomSelection(root);
2619
+ if (current && selectionsEqual(current, editor.selection)) return;
2620
+ suppressSelectionSyncRef.current = true;
2621
+ applyDomSelection(root, editor.selection);
2622
+ queueMicrotask(() => {
2623
+ suppressSelectionSyncRef.current = false;
2624
+ });
2625
+ });
2626
+ (0, import_react12.useEffect)(() => {
2627
+ const root = rootRef.current;
2628
+ if (!root) return;
2629
+ const handler = () => {
2630
+ if (suppressSelectionSyncRef.current) return;
2631
+ if (isComposingRef.current) return;
2632
+ if (document.activeElement !== root) return;
2633
+ const modelSel = readDomSelection(root);
2634
+ if (modelSel) setSelection(modelSel);
2635
+ };
2636
+ document.addEventListener("selectionchange", handler);
2637
+ return () => document.removeEventListener("selectionchange", handler);
2638
+ }, [setSelection]);
2639
+ (0, import_react12.useEffect)(() => {
2640
+ const root = rootRef.current;
2641
+ if (!root) return;
2642
+ let drag = null;
2643
+ function cellFromTarget(target) {
2644
+ if (!(target instanceof Node)) return null;
2645
+ let n = target;
2646
+ let cellEl = null;
2647
+ let tableEl = null;
2648
+ while (n && n !== root) {
2649
+ if (n.nodeType === Node.ELEMENT_NODE) {
2650
+ const e = n;
2651
+ if (!cellEl && e.dataset.cellIndex != null) cellEl = e;
2652
+ if (e.tagName === "TABLE" && e.dataset.blockIndex != null) {
2653
+ tableEl = e;
2654
+ break;
2655
+ }
2656
+ }
2657
+ n = n.parentNode;
2658
+ }
2659
+ if (!cellEl || !tableEl) return null;
2660
+ const cellIndex = Number.parseInt(cellEl.dataset.cellIndex, 10);
2661
+ const blockIndex = Number.parseInt(tableEl.dataset.blockIndex, 10);
2662
+ return { blockIndex, cellIndex, tableEl };
2663
+ }
2664
+ const onPointerDown = (e) => {
2665
+ if (e.button !== 0) return;
2666
+ const hit = cellFromTarget(e.target);
2667
+ if (!hit) {
2668
+ drag = null;
2669
+ return;
2670
+ }
2671
+ drag = {
2672
+ blockIndex: hit.blockIndex,
2673
+ anchorCellIndex: hit.cellIndex,
2674
+ anchorTableEl: hit.tableEl,
2675
+ rectActive: false
2676
+ };
2677
+ };
2678
+ const onPointerMove = (e) => {
2679
+ if (!drag) return;
2680
+ if ((e.buttons & 1) === 0) {
2681
+ drag = null;
2682
+ return;
2683
+ }
2684
+ const hit = cellFromTarget(e.target);
2685
+ if (!hit) return;
2686
+ if (hit.blockIndex !== drag.blockIndex) return;
2687
+ if (hit.cellIndex === drag.anchorCellIndex && !drag.rectActive) return;
2688
+ if (!drag.rectActive) {
2689
+ drag.rectActive = true;
2690
+ window.getSelection()?.removeAllRanges();
2691
+ }
2692
+ e.preventDefault();
2693
+ ctxRef.current.setSelection({
2694
+ anchor: { blockIndex: drag.blockIndex, cellIndex: drag.anchorCellIndex, offset: 0 },
2695
+ focus: { blockIndex: drag.blockIndex, cellIndex: hit.cellIndex, offset: 0 }
2696
+ });
2697
+ };
2698
+ const onPointerUp = () => {
2699
+ drag = null;
2700
+ };
2701
+ root.addEventListener("pointerdown", onPointerDown);
2702
+ document.addEventListener("pointermove", onPointerMove);
2703
+ document.addEventListener("pointerup", onPointerUp);
2704
+ return () => {
2705
+ root.removeEventListener("pointerdown", onPointerDown);
2706
+ document.removeEventListener("pointermove", onPointerMove);
2707
+ document.removeEventListener("pointerup", onPointerUp);
2708
+ };
2709
+ }, []);
2710
+ (0, import_react12.useEffect)(() => {
2711
+ const root = rootRef.current;
2712
+ if (!root) return;
2713
+ const onDragOver = (e) => {
2714
+ if (!e.dataTransfer) return;
2715
+ const hasFiles = Array.from(e.dataTransfer.items ?? []).some(
2716
+ (i) => i.kind === "file"
2717
+ );
2718
+ if (!hasFiles) return;
2719
+ e.preventDefault();
2720
+ e.dataTransfer.dropEffect = "copy";
2721
+ };
2722
+ const onDrop = (e) => {
2723
+ if (!e.dataTransfer) return;
2724
+ const images = Array.from(e.dataTransfer.files).filter(
2725
+ (f) => f.type.startsWith("image/")
2726
+ );
2727
+ if (images.length === 0) return;
2728
+ e.preventDefault();
2729
+ const range = caretRangeFromPoint(e.clientX, e.clientY);
2730
+ if (range) {
2731
+ const dom = window.getSelection();
2732
+ dom?.removeAllRanges();
2733
+ dom?.addRange(range);
2734
+ const sel = readDomSelection(root);
2735
+ if (sel) ctxRef.current.setSelection(sel);
2736
+ }
2737
+ void (async () => {
2738
+ for (const f of images) await editorRef.current.insertImageFromFile(f);
2739
+ })();
2740
+ };
2741
+ root.addEventListener("dragover", onDragOver);
2742
+ root.addEventListener("drop", onDrop);
2743
+ return () => {
2744
+ root.removeEventListener("dragover", onDragOver);
2745
+ root.removeEventListener("drop", onDrop);
2746
+ };
2747
+ }, []);
2748
+ (0, import_react12.useEffect)(() => {
2749
+ const root = rootRef.current;
2750
+ if (!root) return;
2751
+ const onPointerDown = (e) => {
2752
+ if (e.button !== 0) return;
2753
+ const target = e.target;
2754
+ if (!(target instanceof Element)) return;
2755
+ const img = target.closest('img[data-embed="image"]');
2756
+ if (!img) return;
2757
+ const blockEl = img.closest("[data-block-index]");
2758
+ if (!blockEl) return;
2759
+ const blockIndex = Number.parseInt(blockEl.dataset.blockIndex, 10);
2760
+ const offsetAttr = img.dataset.embedOffset;
2761
+ if (offsetAttr == null) return;
2762
+ const localOffset = Number.parseInt(offsetAttr, 10);
2763
+ const fragmentStart = Number.parseInt(blockEl.dataset.fragmentStart ?? "0", 10) || 0;
2764
+ const cellEl = img.closest("[data-cell-index]");
2765
+ const cellIndex = cellEl ? Number.parseInt(cellEl.dataset.cellIndex, 10) : void 0;
2766
+ const offset = fragmentStart + localOffset;
2767
+ e.preventDefault();
2768
+ ctxRef.current.setSelection({
2769
+ anchor: { blockIndex, cellIndex, offset },
2770
+ focus: { blockIndex, cellIndex, offset: offset + 1 }
2771
+ });
2772
+ };
2773
+ root.addEventListener("pointerdown", onPointerDown);
2774
+ return () => root.removeEventListener("pointerdown", onPointerDown);
2775
+ }, []);
2776
+ (0, import_react12.useEffect)(() => {
2777
+ const root = rootRef.current;
2778
+ if (!root) return;
2779
+ const handler = (e) => {
2780
+ const ctx = ctxRef.current;
2781
+ const hist = historyRef.current;
2782
+ const ed = editorRef.current;
2783
+ const type = e.inputType;
2784
+ if (isComposingRef.current && type === "insertCompositionText") return;
2785
+ switch (type) {
2786
+ case "insertText": {
2787
+ const data = e.data ?? "";
2788
+ if (data.length > 0) {
2789
+ e.preventDefault();
2790
+ (0, import_core5.insertText)(ctx, data, ed.consumePendingMarks());
2791
+ }
2792
+ return;
2793
+ }
2794
+ case "insertParagraph":
2795
+ case "insertLineBreak": {
2796
+ e.preventDefault();
2797
+ const selectedEmbed2 = ed.getSelectedEmbed();
2798
+ if (selectedEmbed2) {
2799
+ const off = selectedEmbed2.offset + 1;
2800
+ ctx.setSelection({
2801
+ anchor: {
2802
+ blockIndex: selectedEmbed2.blockIndex,
2803
+ cellIndex: selectedEmbed2.cellIndex,
2804
+ offset: off
2805
+ },
2806
+ focus: {
2807
+ blockIndex: selectedEmbed2.blockIndex,
2808
+ cellIndex: selectedEmbed2.cellIndex,
2809
+ offset: off
2810
+ }
2811
+ });
2812
+ }
2813
+ if (ed.splitListItem()) return;
2814
+ (0, import_core5.insertParagraph)(ctx);
2815
+ return;
2816
+ }
2817
+ case "insertReplacementText": {
2818
+ const text = e.data ?? e.dataTransfer?.getData("text/plain") ?? "";
2819
+ if (text.length > 0) {
2820
+ e.preventDefault();
2821
+ (0, import_core5.insertText)(ctx, text, ed.consumePendingMarks());
2822
+ }
2823
+ return;
2824
+ }
2825
+ case "insertFromPaste": {
2826
+ e.preventDefault();
2827
+ const images = Array.from(e.dataTransfer?.files ?? []).filter(
2828
+ (f) => f.type.startsWith("image/")
2829
+ );
2830
+ if (images.length > 0) {
2831
+ void (async () => {
2832
+ for (const f of images) await editorRef.current.insertImageFromFile(f);
2833
+ })();
2834
+ return;
2835
+ }
2836
+ const text = e.dataTransfer?.getData("text/plain") ?? "";
2837
+ if (text.length === 0) return;
2838
+ const lines = text.split(/\r\n|\r|\n/);
2839
+ lines.forEach((line, i) => {
2840
+ if (i > 0) (0, import_core5.insertParagraph)(ctx);
2841
+ if (line.length > 0) (0, import_core5.insertText)(ctx, line);
2842
+ });
2843
+ return;
2844
+ }
2845
+ case "deleteContentBackward":
2846
+ case "deleteWordBackward":
2847
+ case "deleteSoftLineBackward":
2848
+ case "deleteHardLineBackward": {
2849
+ e.preventDefault();
2850
+ (0, import_core5.deleteBackward)(ctx);
2851
+ return;
2852
+ }
2853
+ case "deleteContentForward":
2854
+ case "deleteWordForward":
2855
+ case "deleteSoftLineForward":
2856
+ case "deleteHardLineForward": {
2857
+ e.preventDefault();
2858
+ (0, import_core5.deleteForward)(ctx);
2859
+ return;
2860
+ }
2861
+ case "historyUndo": {
2862
+ e.preventDefault();
2863
+ hist.undo();
2864
+ return;
2865
+ }
2866
+ case "historyRedo": {
2867
+ e.preventDefault();
2868
+ hist.redo();
2869
+ return;
2870
+ }
2871
+ default: {
2872
+ e.preventDefault();
2873
+ return;
2874
+ }
2875
+ }
2876
+ };
2877
+ root.addEventListener("beforeinput", handler);
2878
+ return () => root.removeEventListener("beforeinput", handler);
2879
+ }, []);
2880
+ const onKeyDown = (0, import_react12.useCallback)((e) => {
2881
+ const ed = editorRef.current;
2882
+ const mod = e.metaKey || e.ctrlKey;
2883
+ if (e.key === "Tab") {
2884
+ e.preventDefault();
2885
+ if (ed.isInTable()) {
2886
+ if (e.shiftKey) ed.moveToPrevCell();
2887
+ else ed.moveToNextCell();
2888
+ return;
2889
+ }
2890
+ if (e.shiftKey) ed.dedentList();
2891
+ else ed.indentList();
2892
+ return;
2893
+ }
2894
+ if (!mod) return;
2895
+ const key = e.key.toLowerCase();
2896
+ if (!e.shiftKey && !e.altKey && key === "z") {
2897
+ e.preventDefault();
2898
+ historyRef.current.undo();
2899
+ return;
2900
+ }
2901
+ if (!e.altKey && (key === "y" || e.shiftKey && key === "z")) {
2902
+ e.preventDefault();
2903
+ historyRef.current.redo();
2904
+ return;
2905
+ }
2906
+ if (e.altKey && !e.shiftKey && /^[0-6]$/.test(key)) {
2907
+ e.preventDefault();
2908
+ const n = Number(key);
2909
+ if (n === 0) ed.setBlockType("paragraph");
2910
+ else ed.setBlockType("heading", { level: n });
2911
+ return;
2912
+ }
2913
+ if (e.shiftKey && !e.altKey && (key === "7" || key === "8")) {
2914
+ e.preventDefault();
2915
+ ed.toggleList(key === "7" ? "ordered" : "bullet");
2916
+ return;
2917
+ }
2918
+ const markToggle = !e.shiftKey && !e.altKey ? BOOLEAN_SHORTCUTS[key] : void 0;
2919
+ if (markToggle) {
2920
+ e.preventDefault();
2921
+ ed.toggleMark(markToggle);
2922
+ return;
2923
+ }
2924
+ if (e.shiftKey && !e.altKey && key === "x") {
2925
+ e.preventDefault();
2926
+ ed.toggleMark("strike");
2927
+ return;
2928
+ }
2929
+ if (!e.altKey && !e.shiftKey && key === "k") {
2930
+ e.preventDefault();
2931
+ const active = ed.getActiveMarks();
2932
+ const currentHref = active.link && typeof active.link === "object" && "href" in active.link ? active.link.href : "";
2933
+ void ed.requestLink(currentHref).then((href) => {
2934
+ if (href === null) return;
2935
+ if (href === "") ed.removeMark("link");
2936
+ else ed.setMark("link", { href });
2937
+ });
2938
+ }
2939
+ }, []);
2940
+ const onCompositionStart = (0, import_react12.useCallback)(() => {
2941
+ isComposingRef.current = true;
2942
+ }, []);
2943
+ const onCompositionEnd = (0, import_react12.useCallback)((e) => {
2944
+ isComposingRef.current = false;
2945
+ if (e.data && e.data.length > 0) {
2946
+ (0, import_core5.insertText)(ctxRef.current, e.data);
2947
+ }
2948
+ }, []);
2949
+ const topLevels = (0, import_react12.useMemo)(() => groupTopLevels(snapshot.blocks), [snapshot]);
2950
+ const docSettings = usePageSettings(editor.doc);
2951
+ const propIsSet = pageGeometry !== void 0;
2952
+ const geom = propIsSet ? pageGeometry ?? A4_PAGE : docSettings;
2953
+ const paginated = pageGeometry !== null;
2954
+ const selectedEmbed = editor.getSelectedEmbed();
2955
+ const layout = usePageLayout(
2956
+ rootRef,
2957
+ geom,
2958
+ topLevels.length,
2959
+ [topLevels, pageGeometry]
2960
+ );
2961
+ const effectiveLayout = paginated ? layout : defaultPageLayout(topLevels.length);
2962
+ const pageStyle = paginated ? {
2963
+ "--ed-page-width": `${geom.width}px`,
2964
+ "--ed-page-height": `${geom.height}px`,
2965
+ "--ed-page-margin-top": `${geom.marginTop}px`,
2966
+ "--ed-page-margin-bottom": `${geom.marginBottom}px`,
2967
+ "--ed-page-margin-left": `${geom.marginLeft}px`,
2968
+ "--ed-page-margin-right": `${geom.marginRight}px`
2969
+ } : void 0;
2970
+ const body = /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2971
+ "div",
2972
+ {
2973
+ ref: rootRef,
2974
+ contentEditable: true,
2975
+ suppressContentEditableWarning: true,
2976
+ spellCheck: true,
2977
+ autoCorrect: "off",
2978
+ autoCapitalize: "off",
2979
+ role: "textbox",
2980
+ "aria-multiline": "true",
2981
+ className: (className ?? "ed-root") + (paginated ? " ed-root--paged" : " ed-root--flat"),
2982
+ style: pageStyle,
2983
+ onKeyDown,
2984
+ onCompositionStart,
2985
+ onCompositionEnd,
2986
+ children: [
2987
+ paginated ? effectiveLayout.pages.map((page, pi) => {
2988
+ const ctx = {
2989
+ pageNumber: pi + 1,
2990
+ pageCount: effectiveLayout.pages.length
2991
+ };
2992
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2993
+ "div",
2994
+ {
2995
+ className: "ed-page",
2996
+ "data-page-number": ctx.pageNumber,
2997
+ "data-page-total": ctx.pageCount,
2998
+ children: [
2999
+ renderPageHeader && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ed-page-header", contentEditable: false, children: renderPageHeader(ctx) }),
3000
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ed-page-content", children: page.slots.map((slot) => renderSlot(slot, topLevels)) }),
3001
+ renderPageFooter && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ed-page-footer", contentEditable: false, children: renderPageFooter(ctx) })
3002
+ ]
3003
+ },
3004
+ `page-${pi}`
3005
+ );
3006
+ }) : topLevels.map((t) => renderTopLevel(t)),
3007
+ selectedEmbed && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3008
+ ImageResizeOverlay,
3009
+ {
3010
+ rootRef,
3011
+ blockIndex: selectedEmbed.blockIndex,
3012
+ offset: selectedEmbed.offset,
3013
+ cellIndex: selectedEmbed.cellIndex,
3014
+ embed: selectedEmbed.embed,
3015
+ onCommit: (w, h) => editor.setImageAttrs(
3016
+ selectedEmbed.blockIndex,
3017
+ selectedEmbed.offset,
3018
+ { width: w, height: h },
3019
+ selectedEmbed.cellIndex
3020
+ ),
3021
+ onCommitMove: (ox, oy) => editor.setImageAttrs(
3022
+ selectedEmbed.blockIndex,
3023
+ selectedEmbed.offset,
3024
+ { offsetX: ox, offsetY: oy },
3025
+ selectedEmbed.cellIndex
3026
+ )
3027
+ }
3028
+ )
3029
+ ]
3030
+ }
3031
+ );
3032
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EditorProvider, { editor, children: body });
3033
+ }
3034
+ function groupTopLevels(snapshot) {
3035
+ const out = [];
3036
+ let i = 0;
3037
+ while (i < snapshot.length) {
3038
+ const block = snapshot[i];
3039
+ if (block.type === "listItem") {
3040
+ const groupStart = i;
3041
+ const items = [];
3042
+ const kind = block.attrs.listKind === "ordered" ? "ordered" : "bullet";
3043
+ while (i < snapshot.length && snapshot[i].type === "listItem" && (snapshot[i].attrs.listKind ?? "bullet") === kind) {
3044
+ items.push({ block: snapshot[i], index: i });
3045
+ i++;
3046
+ }
3047
+ out.push({ kind: "list", items, listKind: kind, key: groupStart });
3048
+ continue;
3049
+ }
3050
+ out.push({ kind: "block", block, blockIndex: i, key: i });
3051
+ i++;
3052
+ }
3053
+ return out;
3054
+ }
3055
+ function renderSlot(slot, topLevels) {
3056
+ const top = topLevels[slot.topLevelIndex];
3057
+ if (!top) return null;
3058
+ if (slot.fragment && top.kind === "block") {
3059
+ const f = {
3060
+ index: slot.fragment.index,
3061
+ start: slot.fragment.start,
3062
+ end: slot.fragment.end
3063
+ };
3064
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3065
+ NodeView,
3066
+ {
3067
+ block: top.block,
3068
+ index: top.blockIndex,
3069
+ fragment: f
3070
+ },
3071
+ `${top.key}-f${f.index}`
3072
+ );
3073
+ }
3074
+ if (slot.tableFragment && top.kind === "block" && top.block.type === "table") {
3075
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3076
+ NodeView,
3077
+ {
3078
+ block: top.block,
3079
+ index: top.blockIndex,
3080
+ tableFragment: slot.tableFragment
3081
+ },
3082
+ `${top.key}-tf${slot.tableFragment.index}`
3083
+ );
3084
+ }
3085
+ return renderTopLevel(top);
3086
+ }
3087
+ function renderTopLevel(top) {
3088
+ if (top.kind === "list") {
3089
+ const tree = buildListTree(top.items);
3090
+ return renderListTree(tree, top.listKind, `list-${top.key}`);
3091
+ }
3092
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NodeView, { block: top.block, index: top.blockIndex }, top.key);
3093
+ }
3094
+ function buildListTree(items) {
3095
+ const roots = [];
3096
+ const stack = [{ children: roots, level: -1 }];
3097
+ for (const it of items) {
3098
+ const level = clampListLevel2(it.block.attrs.listLevel);
3099
+ while (stack.length > 1 && stack[stack.length - 1].level >= level) stack.pop();
3100
+ const node = { entry: it, children: [] };
3101
+ stack[stack.length - 1].children.push(node);
3102
+ stack.push({ children: node.children, level });
3103
+ }
3104
+ return roots;
3105
+ }
3106
+ function renderListTree(tree, kind, key) {
3107
+ const Tag = kind === "ordered" ? "ol" : "ul";
3108
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Tag, { className: `ed-list ed-list-${kind}`, "data-list-kind": kind, children: tree.map((node) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NodeView, { block: node.entry.block, index: node.entry.index, children: node.children.length > 0 ? renderListTree(node.children, kind, `nested-${node.entry.index}`) : null }, node.entry.index)) }, key);
3109
+ }
3110
+ function clampListLevel2(l) {
3111
+ if (typeof l !== "number" || !Number.isFinite(l)) return 0;
3112
+ return Math.max(0, Math.min(5, Math.trunc(l)));
3113
+ }
3114
+ var BOOLEAN_SHORTCUTS = {
3115
+ b: "bold",
3116
+ i: "italic",
3117
+ u: "underline"
3118
+ };
3119
+ function caretRangeFromPoint(x, y) {
3120
+ const doc = document;
3121
+ if (typeof doc.caretRangeFromPoint === "function") {
3122
+ return doc.caretRangeFromPoint(x, y);
3123
+ }
3124
+ if (typeof doc.caretPositionFromPoint === "function") {
3125
+ const pos = doc.caretPositionFromPoint(x, y);
3126
+ if (!pos) return null;
3127
+ const range = document.createRange();
3128
+ range.setStart(pos.offsetNode, pos.offset);
3129
+ range.collapse(true);
3130
+ return range;
3131
+ }
3132
+ return null;
3133
+ }
3134
+
3135
+ // src/Toolbar.tsx
3136
+ var import_react13 = require("react");
3137
+ var import_jsx_runtime10 = require("react/jsx-runtime");
3138
+ var FONT_FAMILIES = ["Arial"];
3139
+ var FONT_SIZES = ["8pt", "10pt", "11pt", "12pt", "14pt", "16pt", "18pt", "24pt", "32pt", "48pt"];
3140
+ var BLOCK_OPTIONS = [
3141
+ { value: "paragraph", label: "Par\xE1grafo", type: "paragraph" },
3142
+ { value: "h1", label: "T\xEDtulo 1", type: "heading", level: 1 },
3143
+ { value: "h2", label: "T\xEDtulo 2", type: "heading", level: 2 },
3144
+ { value: "h3", label: "T\xEDtulo 3", type: "heading", level: 3 },
3145
+ { value: "h4", label: "T\xEDtulo 4", type: "heading", level: 4 },
3146
+ { value: "h5", label: "T\xEDtulo 5", type: "heading", level: 5 },
3147
+ { value: "h6", label: "T\xEDtulo 6", type: "heading", level: 6 },
3148
+ { value: "blockquote", label: "Cita\xE7\xE3o", type: "blockquote" },
3149
+ { value: "codeBlock", label: "C\xF3digo", type: "codeBlock" }
3150
+ ];
3151
+ var ALIGN_OPTIONS = [
3152
+ { value: "left", label: "\u21E4", title: "Alinhar \xE0 esquerda" },
3153
+ { value: "center", label: "\u2261", title: "Centralizar" },
3154
+ { value: "right", label: "\u21E5", title: "Alinhar \xE0 direita" },
3155
+ { value: "justify", label: "\u2630", title: "Justificar" }
3156
+ ];
3157
+ var IMAGE_ALIGN_VALUES = /* @__PURE__ */ new Set(["left", "center", "right"]);
3158
+ var IMAGE_LAYOUT_OPTIONS = [
3159
+ { value: "inline", label: "\u2194", title: "Inline (no fluxo do texto)" },
3160
+ { value: "wrap-left", label: "\u25E7", title: "Texto contorna \xE0 direita" },
3161
+ { value: "wrap-right", label: "\u25E8", title: "Texto contorna \xE0 esquerda" },
3162
+ { value: "behind", label: "\u25A2\u2193", title: "Atr\xE1s do texto" },
3163
+ { value: "front", label: "\u25A2\u2191", title: "Na frente do texto" }
3164
+ ];
3165
+ function Toolbar({ className }) {
3166
+ const editor = useEditorContext();
3167
+ const {
3168
+ isMarkActive,
3169
+ getActiveMarks,
3170
+ toggleMark,
3171
+ setMark,
3172
+ removeMark,
3173
+ getBlockType,
3174
+ getBlockAttr,
3175
+ setBlockType,
3176
+ setBlockAttr,
3177
+ isListActive,
3178
+ toggleList,
3179
+ indentList,
3180
+ dedentList
3181
+ } = editor;
3182
+ const active = getActiveMarks();
3183
+ const blockType = getBlockType();
3184
+ const blockLevel = getBlockAttr("level");
3185
+ const blockAlign = getBlockAttr("align");
3186
+ const blockDir = getBlockAttr("dir");
3187
+ const stop = (e) => {
3188
+ const t = e.target;
3189
+ if (t && (t.tagName === "SELECT" || t.tagName === "INPUT" || t.tagName === "OPTION")) {
3190
+ return;
3191
+ }
3192
+ e.preventDefault();
3193
+ };
3194
+ const onToggle = (name) => (e) => {
3195
+ e.preventDefault();
3196
+ toggleMark(name);
3197
+ };
3198
+ const onColorChange = (e) => {
3199
+ setMark("color", e.target.value);
3200
+ };
3201
+ const onFontFamilyChange = (e) => {
3202
+ const v = e.target.value;
3203
+ if (v === "") removeMark("fontFamily");
3204
+ else setMark("fontFamily", v);
3205
+ };
3206
+ const onFontSizeChange = (e) => {
3207
+ const v = e.target.value;
3208
+ if (v === "") removeMark("fontSize");
3209
+ else setMark("fontSize", v);
3210
+ };
3211
+ const onBlockChange = (e) => {
3212
+ const opt = BLOCK_OPTIONS.find((o) => o.value === e.target.value);
3213
+ if (!opt) return;
3214
+ setBlockType(opt.type, opt.level !== void 0 ? { level: opt.level } : void 0);
3215
+ };
3216
+ const onAlign = (value) => (e) => {
3217
+ e.preventDefault();
3218
+ const selectedEmbed2 = editor.getSelectedEmbed();
3219
+ if (selectedEmbed2 && IMAGE_ALIGN_VALUES.has(value)) {
3220
+ editor.setImageAttrs(
3221
+ selectedEmbed2.blockIndex,
3222
+ selectedEmbed2.offset,
3223
+ { align: value },
3224
+ selectedEmbed2.cellIndex
3225
+ );
3226
+ return;
3227
+ }
3228
+ setBlockAttr("align", value);
3229
+ };
3230
+ const onInsertImageClick = (0, import_react13.useCallback)(
3231
+ (e) => {
3232
+ e.preventDefault();
3233
+ imageInputRef.current?.click();
3234
+ },
3235
+ []
3236
+ );
3237
+ const onImageInputChange = (0, import_react13.useCallback)(
3238
+ (e) => {
3239
+ const file = e.target.files?.[0];
3240
+ if (file) void editor.insertImageFromFile(file);
3241
+ e.target.value = "";
3242
+ },
3243
+ [editor]
3244
+ );
3245
+ const imageInputRef = (0, import_react13.useRef)(null);
3246
+ const onToggleDir = (e) => {
3247
+ e.preventDefault();
3248
+ const next = blockDir === "rtl" ? "ltr" : "rtl";
3249
+ setBlockAttr("dir", next);
3250
+ };
3251
+ const linkActive = isMarkActive("link");
3252
+ const onLinkClick = (0, import_react13.useCallback)(
3253
+ (e) => {
3254
+ e.preventDefault();
3255
+ const currentHref = active.link && typeof active.link === "object" && "href" in active.link ? active.link.href : "";
3256
+ void editor.requestLink(currentHref).then((href) => {
3257
+ if (href === null) return;
3258
+ if (href === "") removeMark("link");
3259
+ else setMark("link", { href });
3260
+ });
3261
+ },
3262
+ [active.link, editor, removeMark, setMark]
3263
+ );
3264
+ const colorValue = typeof active.color === "string" ? active.color : "#000000";
3265
+ const fontFamilyValue = typeof active.fontFamily === "string" ? active.fontFamily : "";
3266
+ const fontSizeValue = typeof active.fontSize === "string" ? active.fontSize : "";
3267
+ const blockValue = blockSelectValue(blockType, blockLevel);
3268
+ const selectedEmbed = editor.getSelectedEmbed();
3269
+ const currentImageLayout = selectedEmbed ? selectedEmbed.embed.layout ?? "inline" : "inline";
3270
+ const onImageLayoutClick = (layout) => (e) => {
3271
+ e.preventDefault();
3272
+ const sel = editor.getSelectedEmbed();
3273
+ if (!sel) return;
3274
+ const reset = layout !== (sel.embed.layout ?? "inline") ? { offsetX: 0, offsetY: 0 } : {};
3275
+ editor.setImageAttrs(
3276
+ sel.blockIndex,
3277
+ sel.offset,
3278
+ { layout, ...reset },
3279
+ sel.cellIndex
3280
+ );
3281
+ };
3282
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3283
+ "div",
3284
+ {
3285
+ className: className ?? "ed-toolbar",
3286
+ role: "toolbar",
3287
+ "aria-label": "Editor toolbar",
3288
+ onMouseDown: stop,
3289
+ children: [
3290
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Group, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3291
+ "select",
3292
+ {
3293
+ className: "ed-toolbar-select",
3294
+ value: blockValue,
3295
+ onChange: onBlockChange,
3296
+ onMouseDown: stop,
3297
+ "aria-label": "Tipo de bloco",
3298
+ children: [
3299
+ blockValue === "" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "", children: "\u2014 (misto)" }),
3300
+ BLOCK_OPTIONS.map((o) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: o.value, children: o.label }, o.value))
3301
+ ]
3302
+ }
3303
+ ) }),
3304
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Group, { children: [
3305
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkButton, { title: "Negrito (\u2318B)", pressed: isMarkActive("bold"), onClick: onToggle("bold"), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: "B" }) }),
3306
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkButton, { title: "It\xE1lico (\u2318I)", pressed: isMarkActive("italic"), onClick: onToggle("italic"), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("em", { children: "I" }) }),
3307
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkButton, { title: "Sublinhado (\u2318U)", pressed: isMarkActive("underline"), onClick: onToggle("underline"), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("u", { children: "U" }) }),
3308
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkButton, { title: "Tachado (\u2318\u21E7X)", pressed: isMarkActive("strike"), onClick: onToggle("strike"), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("s", { children: "S" }) })
3309
+ ] }),
3310
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Group, { children: [
3311
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "ed-toolbar-label", children: [
3312
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ed-toolbar-swatch", "aria-hidden": true, style: { background: colorValue } }),
3313
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "color", value: colorValue, onChange: onColorChange, onMouseDown: stop, "aria-label": "Cor do texto" })
3314
+ ] }),
3315
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3316
+ "button",
3317
+ {
3318
+ type: "button",
3319
+ className: "ed-toolbar-btn",
3320
+ title: "Remover cor",
3321
+ onMouseDown: stop,
3322
+ onClick: (e) => {
3323
+ e.preventDefault();
3324
+ removeMark("color");
3325
+ },
3326
+ children: "\u232B"
3327
+ }
3328
+ )
3329
+ ] }),
3330
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Group, { children: [
3331
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3332
+ "select",
3333
+ {
3334
+ className: "ed-toolbar-select",
3335
+ value: fontFamilyValue,
3336
+ onChange: onFontFamilyChange,
3337
+ onMouseDown: stop,
3338
+ "aria-label": "Fam\xEDlia da fonte",
3339
+ children: [
3340
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "", children: "Fonte\u2026" }),
3341
+ FONT_FAMILIES.map((f) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: f, children: f }, f))
3342
+ ]
3343
+ }
3344
+ ),
3345
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3346
+ "select",
3347
+ {
3348
+ className: "ed-toolbar-select",
3349
+ value: fontSizeValue,
3350
+ onChange: onFontSizeChange,
3351
+ onMouseDown: stop,
3352
+ "aria-label": "Tamanho da fonte",
3353
+ children: [
3354
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "", children: "Tamanho\u2026" }),
3355
+ FONT_SIZES.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: s, children: s }, s))
3356
+ ]
3357
+ }
3358
+ )
3359
+ ] }),
3360
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Group, { children: ALIGN_OPTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3361
+ "button",
3362
+ {
3363
+ type: "button",
3364
+ className: "ed-toolbar-btn",
3365
+ title: a.title,
3366
+ "aria-pressed": blockAlign === a.value,
3367
+ onMouseDown: stop,
3368
+ onClick: onAlign(a.value),
3369
+ children: a.label
3370
+ },
3371
+ a.value
3372
+ )) }),
3373
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Group, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3374
+ "button",
3375
+ {
3376
+ type: "button",
3377
+ className: "ed-toolbar-btn",
3378
+ "aria-pressed": blockDir === "rtl",
3379
+ title: blockDir === "rtl" ? "Mudar para LTR" : "Mudar para RTL (hebraico)",
3380
+ onMouseDown: stop,
3381
+ onClick: onToggleDir,
3382
+ children: blockDir === "rtl" ? "RTL" : "LTR"
3383
+ }
3384
+ ) }),
3385
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Group, { children: [
3386
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3387
+ "button",
3388
+ {
3389
+ type: "button",
3390
+ className: "ed-toolbar-btn",
3391
+ "aria-pressed": isListActive("bullet"),
3392
+ title: "Lista com marcadores (\u2318\u21E78)",
3393
+ onMouseDown: stop,
3394
+ onClick: (e) => {
3395
+ e.preventDefault();
3396
+ toggleList("bullet");
3397
+ },
3398
+ children: "\u2022"
3399
+ }
3400
+ ),
3401
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3402
+ "button",
3403
+ {
3404
+ type: "button",
3405
+ className: "ed-toolbar-btn",
3406
+ "aria-pressed": isListActive("ordered"),
3407
+ title: "Lista numerada (\u2318\u21E77)",
3408
+ onMouseDown: stop,
3409
+ onClick: (e) => {
3410
+ e.preventDefault();
3411
+ toggleList("ordered");
3412
+ },
3413
+ children: "1."
3414
+ }
3415
+ ),
3416
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3417
+ "button",
3418
+ {
3419
+ type: "button",
3420
+ className: "ed-toolbar-btn",
3421
+ title: "Diminuir recuo (\u21E7Tab)",
3422
+ onMouseDown: stop,
3423
+ onClick: (e) => {
3424
+ e.preventDefault();
3425
+ dedentList();
3426
+ },
3427
+ children: "\u21E4"
3428
+ }
3429
+ ),
3430
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3431
+ "button",
3432
+ {
3433
+ type: "button",
3434
+ className: "ed-toolbar-btn",
3435
+ title: "Aumentar recuo (Tab)",
3436
+ onMouseDown: stop,
3437
+ onClick: (e) => {
3438
+ e.preventDefault();
3439
+ indentList();
3440
+ },
3441
+ children: "\u21E5"
3442
+ }
3443
+ )
3444
+ ] }),
3445
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Group, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3446
+ "button",
3447
+ {
3448
+ type: "button",
3449
+ className: "ed-toolbar-btn",
3450
+ "aria-pressed": linkActive,
3451
+ title: linkActive ? "Remover link (\u2318K)" : "Adicionar link (\u2318K)",
3452
+ onMouseDown: stop,
3453
+ onClick: onLinkClick,
3454
+ children: "\u{1F517}"
3455
+ }
3456
+ ) }),
3457
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Group, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TableMenu, {}) }),
3458
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Group, { children: [
3459
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3460
+ "button",
3461
+ {
3462
+ type: "button",
3463
+ className: "ed-toolbar-btn",
3464
+ title: "Inserir imagem",
3465
+ onMouseDown: stop,
3466
+ onClick: onInsertImageClick,
3467
+ children: "\u{1F5BC}"
3468
+ }
3469
+ ),
3470
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3471
+ "input",
3472
+ {
3473
+ ref: imageInputRef,
3474
+ type: "file",
3475
+ accept: "image/*",
3476
+ hidden: true,
3477
+ onChange: onImageInputChange
3478
+ }
3479
+ )
3480
+ ] }),
3481
+ selectedEmbed && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Group, { children: [
3482
+ IMAGE_LAYOUT_OPTIONS.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3483
+ "button",
3484
+ {
3485
+ type: "button",
3486
+ className: "ed-toolbar-btn",
3487
+ title: opt.title,
3488
+ "aria-pressed": currentImageLayout === opt.value,
3489
+ onMouseDown: stop,
3490
+ onClick: onImageLayoutClick(opt.value),
3491
+ children: opt.label
3492
+ },
3493
+ opt.value
3494
+ )),
3495
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3496
+ "button",
3497
+ {
3498
+ type: "button",
3499
+ className: "ed-toolbar-btn",
3500
+ title: "Legenda da imagem",
3501
+ "aria-pressed": !!selectedEmbed.embed.caption,
3502
+ onMouseDown: stop,
3503
+ onClick: (e) => {
3504
+ e.preventDefault();
3505
+ const initialCaption = selectedEmbed.embed.caption ?? "";
3506
+ const initialAlign = selectedEmbed.embed.captionAlign ?? "center";
3507
+ void editor.requestImageCaption(initialCaption, initialAlign).then((result) => {
3508
+ if (result === null) return;
3509
+ const empty = result.caption.length === 0;
3510
+ editor.setImageAttrs(
3511
+ selectedEmbed.blockIndex,
3512
+ selectedEmbed.offset,
3513
+ {
3514
+ caption: empty ? void 0 : result.caption,
3515
+ captionAlign: empty ? void 0 : result.align
3516
+ },
3517
+ selectedEmbed.cellIndex
3518
+ );
3519
+ });
3520
+ },
3521
+ children: "Legenda"
3522
+ }
3523
+ )
3524
+ ] }),
3525
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Group, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3526
+ "button",
3527
+ {
3528
+ type: "button",
3529
+ className: "ed-toolbar-btn",
3530
+ title: "Configurar p\xE1gina (tamanho, margens)",
3531
+ onMouseDown: stop,
3532
+ onClick: (e) => {
3533
+ e.preventDefault();
3534
+ void editor.requestPageConfig();
3535
+ },
3536
+ children: "\u2699"
3537
+ }
3538
+ ) })
3539
+ ]
3540
+ }
3541
+ );
3542
+ }
3543
+ function TableMenu() {
3544
+ const editor = useEditorContext();
3545
+ const inTable = editor.isInTable();
3546
+ const [open, setOpen] = (0, import_react13.useState)(false);
3547
+ const rootRef = (0, import_react13.useRef)(null);
3548
+ (0, import_react13.useEffect)(() => {
3549
+ if (!open) return;
3550
+ const onDown = (e) => {
3551
+ if (!rootRef.current) return;
3552
+ if (rootRef.current.contains(e.target)) return;
3553
+ setOpen(false);
3554
+ };
3555
+ document.addEventListener("mousedown", onDown);
3556
+ return () => document.removeEventListener("mousedown", onDown);
3557
+ }, [open]);
3558
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref: rootRef, className: "ed-toolbar-tablemenu", children: [
3559
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3560
+ "button",
3561
+ {
3562
+ type: "button",
3563
+ className: "ed-toolbar-btn",
3564
+ title: "Tabela",
3565
+ "aria-pressed": open,
3566
+ onMouseDown: (e) => e.preventDefault(),
3567
+ onClick: (e) => {
3568
+ e.preventDefault();
3569
+ setOpen((v) => !v);
3570
+ },
3571
+ children: "\u25A6"
3572
+ }
3573
+ ),
3574
+ open && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3575
+ "div",
3576
+ {
3577
+ className: "ed-table-popover",
3578
+ role: "menu",
3579
+ onMouseDown: (e) => {
3580
+ const t = e.target;
3581
+ if (!t || t.tagName !== "INPUT" && t.tagName !== "BUTTON") {
3582
+ e.preventDefault();
3583
+ }
3584
+ },
3585
+ children: [
3586
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3587
+ TableSizePicker,
3588
+ {
3589
+ onPick: (rows, cols) => {
3590
+ setOpen(false);
3591
+ editor.insertTable(rows, cols);
3592
+ }
3593
+ }
3594
+ ),
3595
+ inTable && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ed-table-actions", children: [
3596
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("hr", {}),
3597
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => {
3598
+ setOpen(false);
3599
+ editor.insertRowAbove();
3600
+ }, children: "Inserir linha acima" }),
3601
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => {
3602
+ setOpen(false);
3603
+ editor.insertRowBelow();
3604
+ }, children: "Inserir linha abaixo" }),
3605
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => {
3606
+ setOpen(false);
3607
+ editor.insertColumnLeft();
3608
+ }, children: "Inserir coluna \xE0 esquerda" }),
3609
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => {
3610
+ setOpen(false);
3611
+ editor.insertColumnRight();
3612
+ }, children: "Inserir coluna \xE0 direita" }),
3613
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => {
3614
+ setOpen(false);
3615
+ editor.deleteCurrentRow();
3616
+ }, children: "Excluir linha" }),
3617
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => {
3618
+ setOpen(false);
3619
+ editor.deleteCurrentColumn();
3620
+ }, children: "Excluir coluna" }),
3621
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("hr", {}),
3622
+ editor.hasTableRectSelection() && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3623
+ "button",
3624
+ {
3625
+ type: "button",
3626
+ onClick: () => {
3627
+ setOpen(false);
3628
+ editor.mergeSelection();
3629
+ },
3630
+ children: "Mesclar c\xE9lulas selecionadas"
3631
+ }
3632
+ ),
3633
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => {
3634
+ setOpen(false);
3635
+ editor.mergeRight();
3636
+ }, children: "Mesclar com c\xE9lula \xE0 direita" }),
3637
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => {
3638
+ setOpen(false);
3639
+ editor.mergeDown();
3640
+ }, children: "Mesclar com c\xE9lula abaixo" }),
3641
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => {
3642
+ setOpen(false);
3643
+ editor.splitCell();
3644
+ }, children: "Dividir c\xE9lulas mescladas" }),
3645
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3646
+ "button",
3647
+ {
3648
+ type: "button",
3649
+ className: "ed-table-delete",
3650
+ onClick: () => {
3651
+ setOpen(false);
3652
+ editor.deleteTable();
3653
+ },
3654
+ children: "Excluir tabela"
3655
+ }
3656
+ )
3657
+ ] })
3658
+ ]
3659
+ }
3660
+ )
3661
+ ] });
3662
+ }
3663
+ var PICKER_MAX_ROWS = 8;
3664
+ var PICKER_MAX_COLS = 10;
3665
+ function TableSizePicker({ onPick }) {
3666
+ const [hover, setHover] = (0, import_react13.useState)(null);
3667
+ const label = hover ? `${hover.r + 1} \xD7 ${hover.c + 1}` : "Inserir tabela";
3668
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ed-table-picker", children: [
3669
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ed-table-picker-label", children: label }),
3670
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3671
+ "div",
3672
+ {
3673
+ className: "ed-table-picker-grid",
3674
+ style: {
3675
+ gridTemplateColumns: `repeat(${PICKER_MAX_COLS}, 18px)`,
3676
+ gridTemplateRows: `repeat(${PICKER_MAX_ROWS}, 18px)`
3677
+ },
3678
+ onMouseLeave: () => setHover(null),
3679
+ children: Array.from(
3680
+ { length: PICKER_MAX_ROWS },
3681
+ (_, r) => Array.from({ length: PICKER_MAX_COLS }, (_2, c) => {
3682
+ const filled = hover != null && r <= hover.r && c <= hover.c;
3683
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3684
+ "button",
3685
+ {
3686
+ type: "button",
3687
+ className: `ed-table-picker-cell${filled ? " filled" : ""}`,
3688
+ onMouseEnter: () => setHover({ r, c }),
3689
+ onMouseDown: (e) => e.preventDefault(),
3690
+ onClick: (e) => {
3691
+ e.preventDefault();
3692
+ onPick(r + 1, c + 1);
3693
+ },
3694
+ "aria-label": `${r + 1} \xD7 ${c + 1}`
3695
+ },
3696
+ `${r}-${c}`
3697
+ );
3698
+ })
3699
+ )
3700
+ }
3701
+ )
3702
+ ] });
3703
+ }
3704
+ function blockSelectValue(type, level) {
3705
+ if (type === "mixed") return "";
3706
+ if (type === "heading") {
3707
+ if (typeof level === "number" && level >= 1 && level <= 6) return `h${level}`;
3708
+ return "h1";
3709
+ }
3710
+ return type;
3711
+ }
3712
+ function Group({ children }) {
3713
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ed-toolbar-group", children });
3714
+ }
3715
+ function MarkButton({ title, pressed, onClick, children }) {
3716
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3717
+ "button",
3718
+ {
3719
+ type: "button",
3720
+ className: "ed-toolbar-btn",
3721
+ "aria-pressed": pressed,
3722
+ title,
3723
+ onMouseDown: (e) => e.preventDefault(),
3724
+ onClick,
3725
+ children
3726
+ }
3727
+ );
3728
+ }
3729
+ // Annotate the CommonJS export names for ESM import in node:
3730
+ 0 && (module.exports = {
3731
+ A4_PAGE,
3732
+ Editor,
3733
+ EditorProvider,
3734
+ ImageCaptionDialog,
3735
+ LinkDialog,
3736
+ NodeView,
3737
+ PageConfigDialog,
3738
+ Toolbar,
3739
+ applyDomSelection,
3740
+ contentHeight,
3741
+ contentWidth,
3742
+ defaultPageLayout,
3743
+ deltaLength,
3744
+ findBlockIndex,
3745
+ getBlockElement,
3746
+ getCellElement,
3747
+ getFragmentForOffset,
3748
+ isTableRectSelection,
3749
+ readDomSelection,
3750
+ renderInline,
3751
+ selectionsEqual,
3752
+ sliceDelta,
3753
+ textOffsetWithin,
3754
+ useEditor,
3755
+ useEditorContext,
3756
+ usePageLayout,
3757
+ usePageSettings
3758
+ });
3759
+ //# sourceMappingURL=index.cjs.map