@wrrdev/fenix-ui 1.1.14 → 1.1.16

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.
Files changed (81) hide show
  1. package/README.md +5 -4
  2. package/dist/components/editor/commands.d.ts +27 -0
  3. package/dist/components/editor/commands.d.ts.map +1 -0
  4. package/dist/components/editor/commands.js +235 -0
  5. package/dist/components/editor/commands.js.map +1 -0
  6. package/dist/components/editor/editor.d.ts +83 -0
  7. package/dist/components/editor/editor.d.ts.map +1 -0
  8. package/dist/components/editor/editor.js +1951 -0
  9. package/dist/components/editor/editor.js.map +1 -0
  10. package/dist/components/editor/index.d.ts +12 -0
  11. package/dist/components/editor/index.d.ts.map +1 -0
  12. package/dist/components/editor/index.js +7 -0
  13. package/dist/components/editor/index.js.map +1 -0
  14. package/dist/components/editor/normalize-html.d.ts +33 -0
  15. package/dist/components/editor/normalize-html.d.ts.map +1 -0
  16. package/dist/components/editor/normalize-html.js +316 -0
  17. package/dist/components/editor/normalize-html.js.map +1 -0
  18. package/dist/components/editor/sanitize-html.d.ts +37 -0
  19. package/dist/components/editor/sanitize-html.d.ts.map +1 -0
  20. package/dist/components/editor/sanitize-html.js +247 -0
  21. package/dist/components/editor/sanitize-html.js.map +1 -0
  22. package/dist/components/editor/schema.d.ts +36 -0
  23. package/dist/components/editor/schema.d.ts.map +1 -0
  24. package/dist/components/editor/schema.js +232 -0
  25. package/dist/components/editor/schema.js.map +1 -0
  26. package/dist/components/editor/selection.d.ts +22 -0
  27. package/dist/components/editor/selection.d.ts.map +1 -0
  28. package/dist/components/editor/selection.js +97 -0
  29. package/dist/components/editor/selection.js.map +1 -0
  30. package/dist/components/editor/styles.d.ts +10 -0
  31. package/dist/components/editor/styles.d.ts.map +1 -0
  32. package/dist/components/editor/styles.js +446 -0
  33. package/dist/components/editor/styles.js.map +1 -0
  34. package/dist/components/editor/table-utils.d.ts +33 -0
  35. package/dist/components/editor/table-utils.d.ts.map +1 -0
  36. package/dist/components/editor/table-utils.js +150 -0
  37. package/dist/components/editor/table-utils.js.map +1 -0
  38. package/dist/components/input/input.d.ts +5 -1
  39. package/dist/components/input/input.d.ts.map +1 -1
  40. package/dist/components/input/input.js +15 -1
  41. package/dist/components/input/input.js.map +1 -1
  42. package/dist/components/pagination/pagination.d.ts +3 -0
  43. package/dist/components/pagination/pagination.d.ts.map +1 -1
  44. package/dist/components/pagination/pagination.js +19 -9
  45. package/dist/components/pagination/pagination.js.map +1 -1
  46. package/dist/components/table/table.d.ts.map +1 -1
  47. package/dist/components/table/table.js +20 -3
  48. package/dist/components/table/table.js.map +1 -1
  49. package/dist/components/toast/toast.d.ts +10 -10
  50. package/dist/components/toast/toast.d.ts.map +1 -1
  51. package/dist/components/toast/toast.js +46 -13
  52. package/dist/components/toast/toast.js.map +1 -1
  53. package/dist/core/jsx.d.ts +25 -1
  54. package/dist/core/jsx.d.ts.map +1 -1
  55. package/dist/core/jsx.js.map +1 -1
  56. package/dist/core/page-range.d.ts +31 -0
  57. package/dist/core/page-range.d.ts.map +1 -0
  58. package/dist/core/page-range.js +41 -0
  59. package/dist/core/page-range.js.map +1 -0
  60. package/dist/core/vue.d.ts +3 -1
  61. package/dist/core/vue.d.ts.map +1 -1
  62. package/dist/core/vue.js.map +1 -1
  63. package/dist/docs/componentes/editor.doc.d.ts +6 -0
  64. package/dist/docs/componentes/editor.doc.d.ts.map +1 -0
  65. package/dist/docs/componentes/index.d.ts.map +1 -1
  66. package/dist/docs/componentes/input.doc.d.ts.map +1 -1
  67. package/dist/docs/componentes/table.doc.d.ts.map +1 -1
  68. package/dist/fenix-ui.umd.min.js +774 -256
  69. package/dist/fenix-ui2.esm.js.map +1 -1
  70. package/dist/fenix-ui4.esm.js +3877 -181
  71. package/dist/fenix-ui4.esm.js.map +1 -1
  72. package/dist/fenix-ui5.esm.js +1 -0
  73. package/dist/fenix-ui5.esm.js.map +1 -1
  74. package/dist/index.d.ts +1 -0
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +14 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/plugins/auto-import.d.ts.map +1 -1
  79. package/dist/plugins/auto-import.js +1 -0
  80. package/dist/plugins/auto-import.js.map +1 -1
  81. package/package.json +5 -1
@@ -2,9 +2,19 @@ var __typeError = (msg) => {
2
2
  throw TypeError(msg);
3
3
  };
4
4
  var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
5
6
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
6
8
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
7
- var _FxToast_instances, wirePause_fn, startTimer_fn, pauseTimer_fn, resumeTimer_fn, syncProgress_fn, render_fn, syncMore_fn;
9
+ var __privateWrapper = (obj, member, setter, getter) => ({
10
+ set _(value) {
11
+ __privateSet(obj, member, value, setter);
12
+ },
13
+ get _() {
14
+ return __privateGet(obj, member, getter);
15
+ }
16
+ });
17
+ var _built, _eventsBound, _initialized, _defaultValue, _value, _dirty, _sourceMode, _lastRange, _rafId, _emitTimer, _lastLimitError, _snapshotTimer, _history, _historyIndex, _imagePicker, _mediaFigure, _linkAnchor, _resizeDispose, _dialogReturnFocus, _dialogResolve, _disposers, _selectionRaf, _bodyOverflow, _FxEditor_instances, on_fn, bindEvents_fn, syncChrome_fn, applyCssVars_fn, updateEmpty_fn, updateCount_fn, fits_fn, limitError_fn, validate_fn, cleanHtml_fn, serialize_fn, applyValue_fn, scheduleEmit_fn, emitNow_fn, emitChange_fn, pushHistory_fn, scheduleSnapshot_fn, historyUndo_fn, historyRedo_fn, bindGlobalSelection_fn, restoreSelection_fn, syncBodyOverflow_fn, updateSourceUi_fn, releaseDialog_fn, renderToolbar_fn, updateToolbar_fn, handleToolbarKeydown_fn, moveToolbarTabStop_fn, trapDialogFocus_fn, execCommand_fn, afterCommand_fn, action_fn, handleKeydown_fn, tabInTable_fn, handleBeforeInput_fn, handlePaste_fn, handleDrop_fn, insertPlainText_fn, toggleSource_fn, applySource_fn, cancelSource_fn, openDialog_fn, fieldHtml_fn, submitDialog_fn, openLinkDialog_fn, openImageDialog_fn, toEmbedUrl_fn, openVideoDialog_fn, decorateMediaFigures_fn, syncQuickActions_fn, selectAnchor_fn, handleLinkOverlayClick_fn, openLink_fn, removeLink_fn, showLinkOverlay_fn, hideLinkOverlay_fn, positionLinkOverlay_fn, handleEditorClick_fn, showMediaOverlay_fn, hideMediaOverlay_fn, deleteMediaFigure_fn, positionMediaOverlay_fn, startResize_fn, endResize_fn, handleDoubleClick_fn, insertImage_fn, insertVideo_fn, insertEmbed_fn, insertTable_fn, insertColumns_fn, showTablePanel_fn, showColumnsPanel_fn, hidePanel_fn, currentTable_fn, tableAction_fn, handleImageFile_fn, fileToDataUrl_fn, error_fn, _FxToast_instances, wirePause_fn, startTimer_fn, pauseTimer_fn, resumeTimer_fn, syncProgress_fn, render_fn, syncMore_fn;
8
18
  import { a as FENIX_ICON_BASE_CSS, f as fenixIconHtml, g as esc, l as loadFenixIconsFont, i as isFenixIconName } from "./assets/font-DPeWTz-n.js";
9
19
  import { h, j } from "./assets/font-DPeWTz-n.js";
10
20
  const lightTokens = {
@@ -1177,6 +1187,11 @@ function defineFxSelect() {
1177
1187
  return defineElement("fx-select", FxSelect);
1178
1188
  }
1179
1189
  defineFxSelect();
1190
+ const INPUT_TYPES = /* @__PURE__ */ new Set(["text", "number", "password"]);
1191
+ function normalizeInputType(value) {
1192
+ const type = (value || "text").toLowerCase();
1193
+ return INPUT_TYPES.has(type) ? type : "text";
1194
+ }
1180
1195
  const _FxInput = class _FxInput extends FxFormElement {
1181
1196
  // `value` fica FORA da observação: refleti-lo a cada tecla não pode
1182
1197
  // re-renderizar o template, senão o campo perde o foco ao digitar.
@@ -1200,6 +1215,13 @@ const _FxInput = class _FxInput extends FxFormElement {
1200
1215
  "valid"
1201
1216
  ];
1202
1217
  }
1218
+ /** Tipo do input interno. Padrão: `'text'`. */
1219
+ get type() {
1220
+ return normalizeInputType(this.getAttr("type", "text"));
1221
+ }
1222
+ set type(value) {
1223
+ this.setAttribute("type", normalizeInputType(value));
1224
+ }
1203
1225
  /** Tamanho do campo. Padrão: `'md'`. */
1204
1226
  get size() {
1205
1227
  const s = this.getAttr("size", "md");
@@ -1223,7 +1245,7 @@ const _FxInput = class _FxInput extends FxFormElement {
1223
1245
  this.toggleAttr("disabled", Boolean(value));
1224
1246
  }
1225
1247
  render() {
1226
- const type = this.getAttr("type", "text");
1248
+ const type = this.type;
1227
1249
  const placeholder = this.getAttr("placeholder");
1228
1250
  const readonly = this.hasAttr("readonly");
1229
1251
  const disabled = this.hasAttr("disabled");
@@ -1292,6 +1314,8 @@ const _FxInput = class _FxInput extends FxFormElement {
1292
1314
  syncInternalControl() {
1293
1315
  const field = this.root.querySelector(".field");
1294
1316
  const value = this.getAttr("value");
1317
+ const type = this.type;
1318
+ if (field && field.type !== type) field.type = type;
1295
1319
  if (field && field.value !== value) field.value = value;
1296
1320
  }
1297
1321
  };
@@ -3313,6 +3337,43 @@ function defineFxRadio() {
3313
3337
  return defineElement("fx-radio", FxRadio);
3314
3338
  }
3315
3339
  defineFxRadio();
3340
+ function toInt(value, fallback) {
3341
+ return Number.isFinite(value) ? Math.trunc(value) : fallback;
3342
+ }
3343
+ function range(start, end) {
3344
+ const out = [];
3345
+ for (let p = start; p <= end; p++) out.push(p);
3346
+ return out;
3347
+ }
3348
+ function pageRange(current, count, options = {}) {
3349
+ const pages = Math.max(0, toInt(count, 0));
3350
+ if (pages === 0) return [];
3351
+ const boundaries = Math.max(0, toInt(options.boundaries ?? 1, 1));
3352
+ const siblings = Math.max(0, toInt(options.siblings ?? 1, 1));
3353
+ const page = Math.min(Math.max(1, toInt(current, 1)), pages);
3354
+ if (pages <= boundaries * 2 + siblings * 2 + 3) return range(1, pages);
3355
+ const startPages = range(1, Math.min(boundaries, pages));
3356
+ const endPages = range(
3357
+ Math.max(pages - boundaries + 1, boundaries + 1),
3358
+ pages
3359
+ );
3360
+ const siblingsStart = Math.max(
3361
+ Math.min(page - siblings, pages - boundaries - siblings * 2 - 1),
3362
+ boundaries + 2
3363
+ );
3364
+ const siblingsEnd = Math.min(
3365
+ Math.max(page + siblings, boundaries + siblings * 2 + 2),
3366
+ endPages.length > 0 ? endPages[0] - 2 : pages - 1
3367
+ );
3368
+ const items = [...startPages];
3369
+ if (siblingsStart > boundaries + 2) items.push("ellipsis");
3370
+ else if (boundaries + 1 < pages - boundaries) items.push(boundaries + 1);
3371
+ items.push(...range(siblingsStart, siblingsEnd));
3372
+ if (siblingsEnd < pages - boundaries - 1) items.push("ellipsis");
3373
+ else if (pages - boundaries > boundaries) items.push(pages - boundaries);
3374
+ items.push(...endPages);
3375
+ return items;
3376
+ }
3316
3377
  const LOCALE = "pt-BR";
3317
3378
  const PIPES = {
3318
3379
  /** Formata como moeda brasileira (R$). */
@@ -3795,13 +3856,13 @@ const _FxTable = class _FxTable extends FxElement {
3795
3856
  return [...this.querySelectorAll("fx-column")].map((c) => {
3796
3857
  const tpl = this._resolveCellTemplate(c);
3797
3858
  const direct = tpl ? void 0 : c.innerHTML.trim();
3798
- const align = c.getAttribute("align") ?? "left";
3859
+ const align2 = c.getAttribute("align") ?? "left";
3799
3860
  return {
3800
3861
  field: c.getAttribute("field") ?? "",
3801
3862
  header: c.getAttribute("header") ?? c.getAttribute("field") ?? "",
3802
3863
  sortable: c.hasAttribute("sortable"),
3803
3864
  filterable: c.hasAttribute("filterable"),
3804
- align: ["left", "center", "right", "justify", "start", "end"].includes(align) ? align : "left",
3865
+ align: ["left", "center", "right", "justify", "start", "end"].includes(align2) ? align2 : "left",
3805
3866
  template: tpl ?? void 0,
3806
3867
  directTemplate: direct || void 0
3807
3868
  };
@@ -3967,12 +4028,15 @@ const _FxTable = class _FxTable extends FxElement {
3967
4028
  const bodyHtml = rows.length ? rows.map(
3968
4029
  (row, i) => `<tr part="row" data-index="${i}">${cols.map((c) => `<td part="cell" style="text-align:${c.align}">${this.cellHtml(c, row)}</td>`).join("")}</tr>`
3969
4030
  ).join("") : `<tr><td class="empty" colspan="${cols.length || 1}">${esc(this.getAttr("empty-message", "Nenhum registro encontrado"))}</td></tr>`;
4031
+ const pageButtons = pageRange(this.page + 1, pages).map(
4032
+ (item) => item === "ellipsis" ? `<span class="ellipsis" part="ellipsis" aria-hidden="true">…</span>` : `<button type="button" class="pg-btn" part="page" data-pg="${item - 1}"${item === this.page + 1 ? ' aria-current="page"' : ""}>${item}</button>`
4033
+ ).join("");
3970
4034
  const pagerHtml = !paginated ? "" : `
3971
4035
  <div class="pager" part="pager">
3972
4036
  <span class="info">Página ${this.page + 1} de ${pages} · ${total} registros</span>
3973
4037
  <button type="button" class="pg-btn" part="first" aria-label="Primeira página" data-pg="first" ${this.page === 0 ? "disabled" : ""}>${fenixIconHtml("first_page")}</button>
3974
4038
  <button type="button" class="pg-btn" part="prev" aria-label="Página anterior" data-pg="prev" ${this.page === 0 ? "disabled" : ""}>${fenixIconHtml("navigate_before")}</button>
3975
- ${Array.from({ length: pages }, (_, p) => `<button type="button" class="pg-btn" data-pg="${p}" aria-current="${p === this.page}">${p + 1}</button>`).join("")}
4039
+ ${pageButtons}
3976
4040
  <button type="button" class="pg-btn" part="next" aria-label="Próxima página" data-pg="next" ${this.page >= pages - 1 ? "disabled" : ""}>${fenixIconHtml("navigate_next")}</button>
3977
4041
  <button type="button" class="pg-btn" part="last" aria-label="Última página" data-pg="last" ${this.page >= pages - 1 ? "disabled" : ""}>${fenixIconHtml("last_page")}</button>
3978
4042
  <label class="info">${esc("Por página:")}
@@ -4328,7 +4392,7 @@ _FxTable.styles = css`
4328
4392
  .pg-btn .fx-icon {
4329
4393
  font-size: calc(var(--fx-font-size) - 1px);
4330
4394
  }
4331
- .pg-btn:hover:not(:disabled):not([aria-current="true"]) {
4395
+ .pg-btn:hover:not(:disabled):not([aria-current="page"]) {
4332
4396
  border-color: var(--fx-color-primary);
4333
4397
  color: var(--fx-color-primary);
4334
4398
  }
@@ -4336,12 +4400,25 @@ _FxTable.styles = css`
4336
4400
  opacity: 0.5;
4337
4401
  cursor: not-allowed;
4338
4402
  }
4339
- .pg-btn[aria-current="true"] {
4403
+ .pg-btn[aria-current="page"] {
4340
4404
  background: var(--fx-color-primary);
4341
4405
  border-color: var(--fx-color-primary);
4342
4406
  color: #fff;
4343
4407
  font-weight: var(--fx-font-weight);
4344
4408
  }
4409
+ /* Elipse da janela de páginas (1 … 499 [500] 501 … 1000): ocupa o
4410
+ mesmo espaço de um botão para o rodapé não "pular" ao navegar,
4411
+ mas não é clicável. */
4412
+ .pager .ellipsis {
4413
+ display: inline-flex;
4414
+ align-items: center;
4415
+ justify-content: center;
4416
+ min-width: var(--fx-size-sm);
4417
+ height: var(--fx-size-sm);
4418
+ color: var(--fx-text-muted);
4419
+ font-size: calc(var(--fx-font-size) - 2px);
4420
+ user-select: none;
4421
+ }
4345
4422
  /* O seletor "Por página" só mostra números, então não pode herdar a
4346
4423
  largura padrão do <fx-select> (200px / 180px em size="sm"), que
4347
4424
  deixava o rodapé da paginação desproporcional. O limite tem que ser
@@ -4885,174 +4962,3742 @@ function defineFxTextarea() {
4885
4962
  return defineElement("fx-textarea", FxTextarea);
4886
4963
  }
4887
4964
  defineFxTextarea();
4888
- const _FxDialog = class _FxDialog extends FxElement {
4889
- constructor() {
4890
- super(...arguments);
4891
- this._previouslyFocused = null;
4965
+ const ALLOWED_TAGS = /* @__PURE__ */ new Set([
4966
+ "p",
4967
+ "br",
4968
+ "strong",
4969
+ "b",
4970
+ "em",
4971
+ "i",
4972
+ "u",
4973
+ "s",
4974
+ "del",
4975
+ "ins",
4976
+ "sub",
4977
+ "sup",
4978
+ "code",
4979
+ "pre",
4980
+ "blockquote",
4981
+ "h1",
4982
+ "h2",
4983
+ "h3",
4984
+ "h4",
4985
+ "h5",
4986
+ "h6",
4987
+ "ul",
4988
+ "ol",
4989
+ "li",
4990
+ "a",
4991
+ "img",
4992
+ "figure",
4993
+ "figcaption",
4994
+ "video",
4995
+ "source",
4996
+ "iframe",
4997
+ "table",
4998
+ "thead",
4999
+ "tbody",
5000
+ "tfoot",
5001
+ "tr",
5002
+ "th",
5003
+ "td",
5004
+ "colgroup",
5005
+ "col",
5006
+ "hr",
5007
+ "span",
5008
+ "div",
5009
+ "small",
5010
+ "mark"
5011
+ ]);
5012
+ const DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
5013
+ "script",
5014
+ "style",
5015
+ "template",
5016
+ "noscript",
5017
+ "svg",
5018
+ "math",
5019
+ "object",
5020
+ "embed",
5021
+ "form",
5022
+ "input",
5023
+ "button",
5024
+ "textarea",
5025
+ "select",
5026
+ "option",
5027
+ "meta",
5028
+ "link",
5029
+ "base",
5030
+ "title",
5031
+ "head",
5032
+ "audio",
5033
+ "canvas",
5034
+ "map",
5035
+ "area"
5036
+ ]);
5037
+ const GLOBAL_ATTRS = /* @__PURE__ */ new Set([
5038
+ "title",
5039
+ "dir",
5040
+ "lang",
5041
+ "role"
5042
+ ]);
5043
+ const TAG_ATTRS = {
5044
+ a: /* @__PURE__ */ new Set(["href", "target", "rel", "download"]),
5045
+ img: /* @__PURE__ */ new Set(["src", "alt", "width", "height", "loading", "decoding"]),
5046
+ video: /* @__PURE__ */ new Set(["src", "poster", "controls", "preload", "width", "height", "muted", "loop", "playsinline"]),
5047
+ source: /* @__PURE__ */ new Set(["src", "type"]),
5048
+ iframe: /* @__PURE__ */ new Set([
5049
+ "src",
5050
+ "title",
5051
+ "width",
5052
+ "height",
5053
+ "allow",
5054
+ "allowfullscreen",
5055
+ "sandbox",
5056
+ "referrerpolicy",
5057
+ "loading"
5058
+ ]),
5059
+ td: /* @__PURE__ */ new Set(["colspan", "rowspan", "scope", "style", "class", "data-align"]),
5060
+ th: /* @__PURE__ */ new Set(["colspan", "rowspan", "scope", "style", "class", "data-align"]),
5061
+ table: /* @__PURE__ */ new Set(["style", "class", "role", "data-cols", "data-stack"]),
5062
+ col: /* @__PURE__ */ new Set(["span", "style", "width"]),
5063
+ colgroup: /* @__PURE__ */ new Set(["span", "style"]),
5064
+ figure: /* @__PURE__ */ new Set(["class", "style", "data-align", "data-cols"]),
5065
+ figcaption: /* @__PURE__ */ new Set(["class", "style"]),
5066
+ span: /* @__PURE__ */ new Set(["class", "style"]),
5067
+ div: /* @__PURE__ */ new Set(["class", "style"]),
5068
+ p: /* @__PURE__ */ new Set(["class", "style"]),
5069
+ h1: /* @__PURE__ */ new Set(["class", "style"]),
5070
+ h2: /* @__PURE__ */ new Set(["class", "style"]),
5071
+ h3: /* @__PURE__ */ new Set(["class", "style"]),
5072
+ h4: /* @__PURE__ */ new Set(["class", "style"]),
5073
+ h5: /* @__PURE__ */ new Set(["class", "style"]),
5074
+ h6: /* @__PURE__ */ new Set(["class", "style"]),
5075
+ blockquote: /* @__PURE__ */ new Set(["class", "style"]),
5076
+ pre: /* @__PURE__ */ new Set(["class", "style"]),
5077
+ code: /* @__PURE__ */ new Set(["class"]),
5078
+ li: /* @__PURE__ */ new Set(["class", "style", "value"]),
5079
+ ol: /* @__PURE__ */ new Set(["class", "style", "start", "type"]),
5080
+ ul: /* @__PURE__ */ new Set(["class", "style"]),
5081
+ hr: /* @__PURE__ */ new Set(["class", "style"]),
5082
+ small: /* @__PURE__ */ new Set(["class"]),
5083
+ mark: /* @__PURE__ */ new Set(["class", "style"])
5084
+ };
5085
+ const ALLOWED_STYLE_PROPS = /* @__PURE__ */ new Set([
5086
+ "color",
5087
+ "background-color",
5088
+ "text-align",
5089
+ "font-size",
5090
+ "font-family",
5091
+ "font-weight",
5092
+ "font-style",
5093
+ "text-decoration",
5094
+ "text-decoration-line",
5095
+ "vertical-align",
5096
+ "width",
5097
+ "height",
5098
+ "max-width",
5099
+ "min-width",
5100
+ "margin",
5101
+ "margin-top",
5102
+ "margin-right",
5103
+ "margin-bottom",
5104
+ "margin-left",
5105
+ "padding",
5106
+ "padding-top",
5107
+ "padding-right",
5108
+ "padding-bottom",
5109
+ "padding-left",
5110
+ "border",
5111
+ "border-top",
5112
+ "border-right",
5113
+ "border-bottom",
5114
+ "border-left",
5115
+ "border-collapse",
5116
+ "border-spacing",
5117
+ "border-radius",
5118
+ "float",
5119
+ "display",
5120
+ "line-height",
5121
+ "white-space",
5122
+ "word-break",
5123
+ "overflow-wrap",
5124
+ "list-style-type"
5125
+ ]);
5126
+ const ALLOWED_CLASS_RE = /^fx-[a-z0-9-]+$/i;
5127
+ const ALLOWED_EMBED_HOSTS = /* @__PURE__ */ new Set([
5128
+ "www.youtube.com",
5129
+ "youtube.com",
5130
+ "www.youtube-nocookie.com",
5131
+ "youtube-nocookie.com",
5132
+ "player.vimeo.com"
5133
+ ]);
5134
+ const SAFE_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
5135
+ const SAFE_MEDIA_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
5136
+ const SAFE_IMAGE_DATA_RE = /^data:image\/(?:png|jpe?g|gif|webp|avif);base64,[a-z0-9+/=\s]+$/i;
5137
+ const DEFAULT_MAX_DATA_IMAGE_BYTES = 2 * 1024 * 1024;
5138
+ const DEFAULT_MAX_DATA_VIDEO_BYTES = 4 * 1024 * 1024;
5139
+ const FORBIDDEN_ATTRS = /* @__PURE__ */ new Set([
5140
+ "contenteditable",
5141
+ "spellcheck",
5142
+ "tabindex",
5143
+ "autofocus",
5144
+ "id",
5145
+ "onclick",
5146
+ "ondblclick",
5147
+ "onmousedown",
5148
+ "onmouseup",
5149
+ "onmouseover",
5150
+ "onmouseout",
5151
+ "onmousemove",
5152
+ "onkeydown",
5153
+ "onkeyup",
5154
+ "onkeypress",
5155
+ "onload",
5156
+ "onerror",
5157
+ "onfocus",
5158
+ "onblur",
5159
+ "oninput",
5160
+ "onchange",
5161
+ "onsubmit",
5162
+ "onreset",
5163
+ "onselect",
5164
+ "ondrag",
5165
+ "ondrop",
5166
+ "onpaste",
5167
+ "oncopy",
5168
+ "oncut",
5169
+ "onscroll",
5170
+ "onwheel",
5171
+ "onanimationstart",
5172
+ "onanimationend",
5173
+ "ontransitionend",
5174
+ "formaction",
5175
+ "srcdoc",
5176
+ "xlink:href",
5177
+ "xmlns",
5178
+ "http-equiv",
5179
+ "ping"
5180
+ ]);
5181
+ const DEFAULTS = {
5182
+ allowBase64Images: true,
5183
+ allowBase64Videos: false,
5184
+ allowEmbeds: true,
5185
+ maxDataImageBytes: DEFAULT_MAX_DATA_IMAGE_BYTES,
5186
+ maxDataVideoBytes: DEFAULT_MAX_DATA_VIDEO_BYTES
5187
+ };
5188
+ const SAFE_DATA_VIDEO_RE = /^data:video\/(?:mp4|webm);base64,[a-z0-9+/=\s]+$/i;
5189
+ function cleanRawUrl(value) {
5190
+ return value.replace(/[\u0000-\u001F\u007F\s]+/g, "").trim();
5191
+ }
5192
+ function isRelativeUrl(value) {
5193
+ return !value.startsWith("//") && /^(?:#|\/|\.{1,2}\/)/.test(value);
5194
+ }
5195
+ function sanitizeLinkUrl(value) {
5196
+ const raw = cleanRawUrl(value);
5197
+ if (!raw) return "";
5198
+ if (isRelativeUrl(raw)) return raw;
5199
+ const lower = raw.toLowerCase();
5200
+ if (lower.startsWith("javascript:") || lower.startsWith("vbscript:") || lower.startsWith("data:") || lower.startsWith("file:") || lower.startsWith("blob:")) {
5201
+ return "";
4892
5202
  }
4893
- static get observedAttributes() {
4894
- return ["open", "size", "heading"];
5203
+ try {
5204
+ const url = new URL(raw);
5205
+ return SAFE_LINK_PROTOCOLS.has(url.protocol) ? raw : "";
5206
+ } catch {
5207
+ return /^[a-z0-9][a-z0-9._~!$&'()*+,;=:@%/?#-]*$/i.test(raw) ? raw : "";
4895
5208
  }
4896
- get open() {
4897
- return this.hasAttr("open");
5209
+ }
5210
+ function sanitizeMediaUrl(value, tag, options = {}) {
5211
+ const opts = { ...DEFAULTS, ...options };
5212
+ const raw = cleanRawUrl(value);
5213
+ if (!raw) return "";
5214
+ if (raw.toLowerCase().startsWith("data:")) {
5215
+ if (tag === "img" && opts.allowBase64Images) {
5216
+ return SAFE_IMAGE_DATA_RE.test(raw) && raw.length <= opts.maxDataImageBytes ? raw : "";
5217
+ }
5218
+ if ((tag === "video" || tag === "source") && opts.allowBase64Videos) {
5219
+ return SAFE_DATA_VIDEO_RE.test(raw) && raw.length <= opts.maxDataVideoBytes ? raw : "";
5220
+ }
5221
+ return "";
4898
5222
  }
4899
- set open(value) {
4900
- this.toggleAttr("open", value);
5223
+ if (isRelativeUrl(raw)) return raw;
5224
+ const lower = raw.toLowerCase();
5225
+ if (lower.startsWith("javascript:") || lower.startsWith("vbscript:") || lower.startsWith("file:") || lower.startsWith("blob:")) {
5226
+ return "";
4901
5227
  }
4902
- render() {
4903
- const isOpen = this.open;
4904
- const heading = this.getAttr("heading");
4905
- this.setTemplate(`
4906
- <div class="overlay" part="overlay" ${isOpen ? "" : "hidden"}>
4907
- <div class="dialog" role="dialog" aria-modal="true" aria-label="${esc(heading)}" tabindex="-1">
4908
- <header>
4909
- <h2>${esc(heading)}</h2>
4910
- <button type="button" class="close" part="close" aria-label="Fechar">×</button>
4911
- </header>
4912
- <div class="body"><slot></slot></div>
4913
- <footer part="footer"><slot name="footer"></slot></footer>
4914
- </div>
4915
- </div>
4916
- `);
4917
- if (!isOpen) return;
4918
- this._cleanup?.();
4919
- this._cleanup = void 0;
4920
- const overlay = this.root.querySelector(".overlay");
4921
- const closeBtn = this.root.querySelector(".close");
4922
- const close = () => {
4923
- this.open = false;
4924
- this._cleanup?.();
4925
- this._cleanup = void 0;
4926
- this._restoreFocus();
4927
- this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
4928
- };
4929
- overlay?.addEventListener("click", (e) => {
4930
- if (e.target === overlay) close();
4931
- });
4932
- closeBtn?.addEventListener("click", close);
4933
- const onKey = (e) => {
4934
- if (e.key === "Escape") {
4935
- e.stopPropagation();
4936
- close();
4937
- return;
4938
- }
4939
- if (e.key === "Tab") this._trapFocus(e);
4940
- };
4941
- document.addEventListener("keydown", onKey);
4942
- this._cleanup = () => document.removeEventListener("keydown", onKey);
4943
- requestAnimationFrame(() => {
4944
- this._previouslyFocused = document.activeElement;
4945
- this.root.querySelector(".dialog")?.focus();
4946
- });
4947
- this.dispatchEvent(new CustomEvent("open", { bubbles: true, composed: true }));
5228
+ try {
5229
+ const url = new URL(raw);
5230
+ return SAFE_MEDIA_PROTOCOLS.has(url.protocol) ? raw : "";
5231
+ } catch {
5232
+ return "";
4948
5233
  }
4949
- /** Mantém o foco dentro do modal (WCAG 2.4.3 / 2.1.2). */
4950
- _trapFocus(e) {
4951
- const dialog = this.root.querySelector(".dialog");
4952
- if (!dialog) return;
4953
- const focusables = dialog.querySelectorAll(
4954
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
4955
- );
4956
- if (!focusables.length) return;
4957
- const first = focusables[0];
4958
- const last = focusables[focusables.length - 1];
4959
- const active = this.root.activeElement;
4960
- if (e.shiftKey && (active === first || active === dialog)) {
4961
- e.preventDefault();
4962
- last.focus();
4963
- } else if (!e.shiftKey && active === last) {
4964
- e.preventDefault();
4965
- first.focus();
5234
+ }
5235
+ function sanitizeEmbedUrl(value) {
5236
+ let raw = cleanRawUrl(value);
5237
+ if (!raw) return "";
5238
+ if (raw.startsWith("//")) raw = `https:${raw}`;
5239
+ let url;
5240
+ try {
5241
+ url = new URL(raw);
5242
+ } catch {
5243
+ return "";
5244
+ }
5245
+ if (url.protocol !== "https:") return "";
5246
+ if (!ALLOWED_EMBED_HOSTS.has(url.hostname.toLowerCase())) return "";
5247
+ const path = url.pathname.toLowerCase();
5248
+ const isValidPath = url.hostname.includes("youtube") && path.startsWith("/embed/") || url.hostname.includes("vimeo") && path.startsWith("/video/");
5249
+ if (!isValidPath) return "";
5250
+ return url.toString();
5251
+ }
5252
+ function sanitizeStyle(value) {
5253
+ if (!value) return "";
5254
+ const probe = document.createElement("span");
5255
+ probe.setAttribute("style", value);
5256
+ const out = [];
5257
+ for (let i = 0; i < probe.style.length; i++) {
5258
+ const prop = probe.style.item(i);
5259
+ if (!ALLOWED_STYLE_PROPS.has(prop)) continue;
5260
+ const val = probe.style.getPropertyValue(prop).trim();
5261
+ if (!val) continue;
5262
+ if (/\\/.test(val)) continue;
5263
+ if (/url\s*\(|expression\s*\(|javascript\s*:|@import|behavior\s*:|-moz-binding/i.test(val)) continue;
5264
+ out.push(`${prop}: ${val}`);
5265
+ }
5266
+ return out.join("; ");
5267
+ }
5268
+ function sanitizeAttributes(el, tag, opts) {
5269
+ for (const attr of Array.from(el.attributes)) {
5270
+ const name = attr.name.toLowerCase();
5271
+ const value = attr.value;
5272
+ if (FORBIDDEN_ATTRS.has(name) || name.startsWith("on")) {
5273
+ el.removeAttribute(attr.name);
5274
+ continue;
5275
+ }
5276
+ const allowed = GLOBAL_ATTRS.has(name) || TAG_ATTRS[tag]?.has(name);
5277
+ if (!allowed) {
5278
+ el.removeAttribute(attr.name);
5279
+ continue;
5280
+ }
5281
+ if (name === "style") {
5282
+ const safe = sanitizeStyle(value);
5283
+ if (safe) el.setAttribute("style", safe);
5284
+ else el.removeAttribute("style");
5285
+ continue;
5286
+ }
5287
+ if (name === "class") {
5288
+ const classes = value.split(/\s+/).filter((c) => ALLOWED_CLASS_RE.test(c));
5289
+ if (classes.length) el.setAttribute("class", classes.join(" "));
5290
+ else el.removeAttribute("class");
5291
+ continue;
5292
+ }
5293
+ if (name === "href") {
5294
+ const safe = sanitizeLinkUrl(value);
5295
+ if (safe) el.setAttribute("href", safe);
5296
+ else el.removeAttribute("href");
5297
+ continue;
5298
+ }
5299
+ if (name === "src") {
5300
+ const safe = sanitizeMediaUrl(value, tag, opts);
5301
+ if (safe) el.setAttribute("src", safe);
5302
+ else el.removeAttribute("src");
5303
+ continue;
5304
+ }
5305
+ if (name === "poster") {
5306
+ const safe = sanitizeMediaUrl(value, "img", opts);
5307
+ if (safe) el.setAttribute("poster", safe);
5308
+ else el.removeAttribute("poster");
5309
+ continue;
5310
+ }
5311
+ if (name === "target") {
5312
+ const safe = value === "_self" || value === "_blank" ? value : "_blank";
5313
+ el.setAttribute("target", safe);
5314
+ continue;
5315
+ }
5316
+ if (name === "colspan" || name === "rowspan" || name === "span" || name === "start") {
5317
+ const n = Number(value);
5318
+ if (Number.isInteger(n) && n > 0 && n <= 1e3) el.setAttribute(name, String(n));
5319
+ else el.removeAttribute(name);
5320
+ continue;
5321
+ }
5322
+ if (name === "width" || name === "height") {
5323
+ if (/^\d{1,4}(?:px|%)?$/i.test(value.trim())) el.setAttribute(name, value.trim());
5324
+ else el.removeAttribute(name);
5325
+ continue;
4966
5326
  }
4967
5327
  }
4968
- _restoreFocus() {
4969
- if (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();
4970
- this._previouslyFocused = null;
5328
+ if (tag === "a") {
5329
+ const href = el.getAttribute("href");
5330
+ if (!href) {
5331
+ el.removeAttribute("target");
5332
+ el.removeAttribute("rel");
5333
+ } else if (el.getAttribute("target") === "_blank") {
5334
+ el.setAttribute("rel", "noopener noreferrer");
5335
+ }
4971
5336
  }
4972
- disconnectedCallback() {
4973
- super.disconnectedCallback();
4974
- this._cleanup?.();
4975
- this._cleanup = void 0;
4976
- this._restoreFocus();
5337
+ if (tag === "iframe") {
5338
+ el.setAttribute("sandbox", "allow-scripts allow-same-origin allow-presentation");
5339
+ el.setAttribute("allowfullscreen", "");
5340
+ el.setAttribute("loading", "lazy");
5341
+ el.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
4977
5342
  }
4978
- };
4979
- _FxDialog.styles = css`
4980
- :host { display: contents; }
4981
- .overlay {
4982
- position: fixed;
4983
- inset: 0;
4984
- background: color-mix(in srgb, var(--fx-text-default, #000) 45%, transparent);
4985
- backdrop-filter: blur(3px);
4986
- -webkit-backdrop-filter: blur(3px);
4987
- display: flex;
4988
- align-items: center;
4989
- justify-content: center;
4990
- z-index: var(--fx-z-modal, 1100);
4991
- animation: fade var(--fx-motion-duration-fast) var(--fx-motion-easing);
5343
+ if (tag === "img") {
5344
+ if (!el.hasAttribute("alt")) el.setAttribute("alt", "");
5345
+ el.setAttribute("loading", "lazy");
5346
+ el.setAttribute("decoding", "async");
5347
+ }
5348
+ if (tag === "video") {
5349
+ el.setAttribute("controls", "");
5350
+ el.setAttribute("preload", "metadata");
5351
+ }
5352
+ }
5353
+ function sanitizeIframe(el) {
5354
+ const src = el.getAttribute("src") ?? "";
5355
+ const safe = sanitizeEmbedUrl(src);
5356
+ if (!safe) return false;
5357
+ el.setAttribute("src", safe);
5358
+ return true;
5359
+ }
5360
+ function unwrap$2(el, opts) {
5361
+ const parent = el.parentNode;
5362
+ if (!parent) {
5363
+ el.remove();
5364
+ return;
5365
+ }
5366
+ for (const child of Array.from(el.childNodes)) {
5367
+ sanitizeNode(child, opts);
5368
+ parent.insertBefore(child, el);
5369
+ }
5370
+ el.remove();
5371
+ }
5372
+ function sanitizeNode(node, opts) {
5373
+ if (node.nodeType === Node.TEXT_NODE) return;
5374
+ if (node.nodeType === Node.COMMENT_NODE) {
5375
+ node.parentNode?.removeChild(node);
5376
+ return;
5377
+ }
5378
+ if (node.nodeType !== Node.ELEMENT_NODE) {
5379
+ node.parentNode?.removeChild(node);
5380
+ return;
5381
+ }
5382
+ const el = node;
5383
+ const tag = el.tagName.toLowerCase();
5384
+ if (DROP_CONTENT_TAGS.has(tag)) {
5385
+ el.remove();
5386
+ return;
5387
+ }
5388
+ if (tag === "iframe") {
5389
+ if (!opts.allowEmbeds || !sanitizeIframe(el)) el.remove();
5390
+ else sanitizeAttributes(el, tag, opts);
5391
+ return;
5392
+ }
5393
+ if (!ALLOWED_TAGS.has(tag)) {
5394
+ unwrap$2(el, opts);
5395
+ return;
5396
+ }
5397
+ sanitizeAttributes(el, tag, opts);
5398
+ if ((tag === "img" || tag === "video" || tag === "source") && !el.hasAttribute("src")) {
5399
+ el.remove();
5400
+ return;
5401
+ }
5402
+ for (const child of Array.from(el.childNodes)) sanitizeNode(child, opts);
5403
+ }
5404
+ function sanitizeBody(html, options = {}) {
5405
+ const opts = { ...DEFAULTS, ...options };
5406
+ if (!html) return null;
5407
+ if (typeof DOMParser === "undefined" || typeof document === "undefined") return null;
5408
+ const doc = new DOMParser().parseFromString(
5409
+ `<!doctype html><html><body>${html}</body></html>`,
5410
+ "text/html"
5411
+ );
5412
+ for (const child of Array.from(doc.body.childNodes)) sanitizeNode(child, opts);
5413
+ return doc.body;
5414
+ }
5415
+ function sanitizeHtml(html, options = {}) {
5416
+ const body = sanitizeBody(html, options);
5417
+ return body ? body.innerHTML : "";
5418
+ }
5419
+ const BLOCK_TAGS = /* @__PURE__ */ new Set([
5420
+ "p",
5421
+ "h1",
5422
+ "h2",
5423
+ "h3",
5424
+ "h4",
5425
+ "h5",
5426
+ "h6",
5427
+ "blockquote",
5428
+ "pre",
5429
+ "ul",
5430
+ "ol",
5431
+ "li",
5432
+ "table",
5433
+ "figure",
5434
+ "hr",
5435
+ "div"
5436
+ ]);
5437
+ const INLINE_TAGS = /* @__PURE__ */ new Set([
5438
+ "strong",
5439
+ "b",
5440
+ "em",
5441
+ "i",
5442
+ "u",
5443
+ "s",
5444
+ "del",
5445
+ "ins",
5446
+ "sub",
5447
+ "sup",
5448
+ "code",
5449
+ "a",
5450
+ "span",
5451
+ "small",
5452
+ "mark",
5453
+ "br"
5454
+ ]);
5455
+ function isElement(node) {
5456
+ return node.nodeType === Node.ELEMENT_NODE;
5457
+ }
5458
+ function tagOf(node) {
5459
+ return isElement(node) ? node.tagName.toLowerCase() : "";
5460
+ }
5461
+ function hasBlockChild(el) {
5462
+ return Array.from(el.children).some((child) => BLOCK_TAGS.has(tagOf(child)) || tagOf(child) === "figure");
5463
+ }
5464
+ function unwrap$1(el) {
5465
+ const parent = el.parentNode;
5466
+ if (!parent) {
5467
+ el.remove();
5468
+ return;
5469
+ }
5470
+ while (el.firstChild) parent.insertBefore(el.firstChild, el);
5471
+ el.remove();
5472
+ }
5473
+ function rename(el, tag) {
5474
+ const doc = el.ownerDocument;
5475
+ const next = doc.createElement(tag);
5476
+ for (const attr of Array.from(el.attributes)) next.setAttribute(attr.name, attr.value);
5477
+ while (el.firstChild) next.appendChild(el.firstChild);
5478
+ el.replaceWith(next);
5479
+ return next;
5480
+ }
5481
+ function normalizeChildren(parent, options) {
5482
+ for (const node of Array.from(parent.childNodes)) {
5483
+ if (node.nodeType === Node.COMMENT_NODE) {
5484
+ node.remove();
5485
+ continue;
4992
5486
  }
4993
- .overlay[hidden] { display: none; }
4994
- @keyframes fade { from { opacity: 0; } to { opacity: 1; } }
4995
- .dialog {
4996
- background: var(--fx-surface-background);
4997
- border: 1px solid var(--fx-border-default);
4998
- border-radius: var(--fx-radius-lg);
4999
- box-shadow: var(--fx-shadow-xl);
5000
- /* Largura customizável por instância: defina --fx-dialog-width no elemento. */
5001
- width: min(var(--fx-dialog-width, 560px), calc(100vw - 32px));
5002
- max-height: calc(100vh - 64px);
5003
- display: flex;
5004
- flex-direction: column;
5005
- font-family: var(--fx-font-family);
5006
- animation: pop var(--fx-motion-duration-normal) var(--fx-motion-easing);
5487
+ if (node.nodeType === Node.TEXT_NODE) {
5488
+ node.nodeValue = (node.nodeValue ?? "").replace(/\u00a0/g, " ");
5489
+ continue;
5007
5490
  }
5008
- @keyframes pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
5009
- :host([size='sm']) .dialog { width: min(var(--fx-dialog-width, 400px), calc(100vw - 32px)); }
5010
- :host([size='lg']) .dialog { width: min(var(--fx-dialog-width, 800px), calc(100vw - 32px)); }
5011
- /* Mobile: dialog ocupa praticamente toda a largura da tela. */
5012
- @media (max-width: 640px) {
5013
- .dialog, :host([size='sm']) .dialog, :host([size='lg']) .dialog {
5014
- width: calc(100vw - 16px);
5015
- max-width: calc(100vw - 16px);
5491
+ if (!isElement(node)) continue;
5492
+ let el = node;
5493
+ let tag = tagOf(el);
5494
+ if (tag === "b") el = rename(el, "strong");
5495
+ else if (tag === "i") el = rename(el, "em");
5496
+ else if (tag === "strike" || tag === "del") el = rename(el, "s");
5497
+ tag = tagOf(el);
5498
+ if (tag === "font") {
5499
+ const span = rename(el, "span");
5500
+ const styles = [];
5501
+ const color = el.getAttribute("color");
5502
+ const face = el.getAttribute("face");
5503
+ if (color) styles.push(`color: ${color}`);
5504
+ if (face) styles.push(`font-family: ${face}`);
5505
+ if (styles.length) span.setAttribute("style", styles.join("; "));
5506
+ el = span;
5507
+ tag = "span";
5508
+ }
5509
+ if (tag === "div" || tag === "section" || tag === "article" || tag === "header" || tag === "footer" || tag === "main" || tag === "aside") {
5510
+ if (hasBlockChild(el)) {
5511
+ unwrap$1(el);
5512
+ continue;
5016
5513
  }
5514
+ el = rename(el, "p");
5515
+ tag = "p";
5017
5516
  }
5018
- header {
5019
- display: flex;
5020
- align-items: center;
5021
- justify-content: space-between;
5022
- padding: var(--fx-space-lg) var(--fx-space-xl);
5023
- border-bottom: 1px solid var(--fx-border-default);
5517
+ if (tag === "p" && hasBlockChild(el)) {
5518
+ unwrap$1(el);
5519
+ continue;
5024
5520
  }
5025
- h2 {
5026
- margin: 0;
5027
- font-size: calc(var(--fx-font-size) + 2px);
5028
- font-weight: 600;
5029
- color: var(--fx-text-default);
5521
+ if (tag === "span" && !el.hasAttribute("style") && !el.hasAttribute("class")) {
5522
+ unwrap$1(el);
5523
+ continue;
5030
5524
  }
5031
- .close {
5032
- border: none;
5033
- background: transparent;
5034
- color: var(--fx-text-muted);
5035
- font-size: calc(var(--fx-font-size) + 6px);
5036
- cursor: pointer;
5037
- line-height: 1;
5038
- padding: 0 var(--fx-space-xs);
5039
- border-radius: var(--fx-radius-full);
5525
+ if (!options.forEditor) {
5526
+ el.removeAttribute("contenteditable");
5527
+ el.removeAttribute("spellcheck");
5528
+ el.removeAttribute("tabindex");
5529
+ }
5530
+ if (tag === "a") {
5531
+ if (!el.getAttribute("href")) {
5532
+ el.removeAttribute("target");
5533
+ el.removeAttribute("rel");
5534
+ } else if (el.getAttribute("target") === "_blank") {
5535
+ el.setAttribute("rel", "noopener noreferrer");
5536
+ }
5040
5537
  }
5041
- .close:hover { color: var(--fx-color-danger); }
5042
- .body {
5043
- padding: var(--fx-space-lg) var(--fx-space-xl);
5044
- overflow-y: auto;
5045
- color: var(--fx-text-default);
5046
- font-size: var(--fx-font-size);
5538
+ normalizeChildren(el, options);
5539
+ }
5540
+ }
5541
+ function wrapInlineChildren(cell, doc) {
5542
+ if (Array.from(cell.children).some((child) => BLOCK_TAGS.has(tagOf(child)))) return;
5543
+ const p = doc.createElement("p");
5544
+ while (cell.firstChild) p.appendChild(cell.firstChild);
5545
+ if (!p.textContent?.trim() && !p.querySelector("img,video,iframe,br")) {
5546
+ p.innerHTML = "<br>";
5547
+ }
5548
+ cell.appendChild(p);
5549
+ }
5550
+ function normalizeFigures(body, options) {
5551
+ const doc = body.ownerDocument;
5552
+ for (const media of Array.from(body.querySelectorAll("img, video, iframe"))) {
5553
+ if (media.closest("figure")) continue;
5554
+ const anchor = media.closest("a");
5555
+ const wrapper = anchor && anchor.querySelectorAll("*").length === 1 ? anchor : media;
5556
+ const figure = doc.createElement("figure");
5557
+ const kind = tagOf(media);
5558
+ figure.className = kind === "img" ? "fx-image" : kind === "video" ? "fx-video" : "fx-embed";
5559
+ if (kind === "img") figure.setAttribute("data-align", "center");
5560
+ const p = wrapper.closest("p");
5561
+ if (p) p.after(figure);
5562
+ else wrapper.parentNode?.insertBefore(figure, wrapper);
5563
+ figure.appendChild(wrapper);
5564
+ if (options.forEditor) figure.setAttribute("contenteditable", "false");
5565
+ }
5566
+ for (const figure of Array.from(body.querySelectorAll("figure"))) {
5567
+ if (figure.classList.contains("fx-table") || figure.querySelector("table")) {
5568
+ figure.classList.add("fx-table");
5569
+ figure.removeAttribute("contenteditable");
5570
+ continue;
5047
5571
  }
5048
- footer {
5049
- padding: var(--fx-space-md) var(--fx-space-xl);
5050
- border-top: 1px solid var(--fx-border-default);
5051
- display: flex;
5052
- justify-content: flex-end;
5053
- gap: var(--fx-space-sm);
5572
+ const hasImg = Boolean(figure.querySelector("img"));
5573
+ const hasVideo = Boolean(figure.querySelector("video"));
5574
+ const hasIframe = Boolean(figure.querySelector("iframe"));
5575
+ if (!hasImg && !hasVideo && !hasIframe) {
5576
+ figure.remove();
5577
+ continue;
5054
5578
  }
5055
- footer[hidden] { display: none; }
5579
+ const classes = new Set((figure.getAttribute("class") ?? "").split(/\s+/).filter(Boolean));
5580
+ if (hasImg) classes.add("fx-image");
5581
+ if (hasVideo) classes.add("fx-video");
5582
+ if (hasIframe) classes.add("fx-embed");
5583
+ figure.setAttribute("class", Array.from(classes).join(" "));
5584
+ if (!figure.hasAttribute("data-align")) figure.setAttribute("data-align", "center");
5585
+ if (options.forEditor) figure.setAttribute("contenteditable", "false");
5586
+ else figure.removeAttribute("contenteditable");
5587
+ }
5588
+ }
5589
+ function normalizeTables(body) {
5590
+ const doc = body.ownerDocument;
5591
+ for (const table of Array.from(body.querySelectorAll("table"))) {
5592
+ const isGrid = table.classList.contains("fx-grid") || table.getAttribute("role") === "presentation";
5593
+ if (isGrid) {
5594
+ table.classList.add("fx-grid");
5595
+ table.setAttribute("role", "presentation");
5596
+ const firstRow = table.querySelector("tr");
5597
+ const cols = firstRow ? firstRow.children.length : 0;
5598
+ if (cols) table.setAttribute("data-cols", String(cols));
5599
+ } else {
5600
+ table.removeAttribute("role");
5601
+ const parentFigure = table.closest("figure");
5602
+ if (!parentFigure || !parentFigure.classList.contains("fx-table")) {
5603
+ const figure = doc.createElement("figure");
5604
+ figure.className = "fx-table";
5605
+ table.parentNode?.insertBefore(figure, table);
5606
+ figure.appendChild(table);
5607
+ }
5608
+ }
5609
+ let tbody = Array.from(table.children).find((child) => tagOf(child) === "tbody");
5610
+ const looseRows = Array.from(table.children).filter((child) => tagOf(child) === "tr");
5611
+ if (looseRows.length) {
5612
+ if (!tbody) {
5613
+ tbody = doc.createElement("tbody");
5614
+ table.appendChild(tbody);
5615
+ }
5616
+ for (const row of looseRows) tbody.appendChild(row);
5617
+ }
5618
+ if (!Array.from(table.children).some((child) => tagOf(child) === "colgroup")) {
5619
+ const firstRow = table.querySelector("tr");
5620
+ const count = firstRow ? Array.from(firstRow.children).filter((c) => tagOf(c) === "td" || tagOf(c) === "th").length : 0;
5621
+ if (count > 0) {
5622
+ const colgroup = doc.createElement("colgroup");
5623
+ for (let i = 0; i < count; i++) {
5624
+ const col = doc.createElement("col");
5625
+ col.style.width = `${(100 / count).toFixed(4).replace(/\.?0+$/, "")}%`;
5626
+ colgroup.appendChild(col);
5627
+ }
5628
+ table.insertBefore(colgroup, table.firstChild);
5629
+ }
5630
+ }
5631
+ for (const th of Array.from(table.querySelectorAll("th"))) {
5632
+ if (!th.getAttribute("scope")) th.setAttribute("scope", "col");
5633
+ }
5634
+ for (const cell of Array.from(table.querySelectorAll("th, td"))) {
5635
+ wrapInlineChildren(cell, doc);
5636
+ }
5637
+ }
5638
+ }
5639
+ function normalizeRoot(body, options) {
5640
+ const doc = body.ownerDocument;
5641
+ let buffer = [];
5642
+ const flush = (before) => {
5643
+ if (!buffer.length) return;
5644
+ const p = doc.createElement("p");
5645
+ body.insertBefore(p, before);
5646
+ for (const node of buffer) p.appendChild(node);
5647
+ buffer = [];
5648
+ if (!p.textContent?.trim() && !p.querySelector("img,video,iframe,br")) p.innerHTML = "<br>";
5649
+ };
5650
+ for (const node of Array.from(body.childNodes)) {
5651
+ if (node.nodeType === Node.TEXT_NODE) {
5652
+ if ((node.nodeValue ?? "").trim()) buffer.push(node);
5653
+ else node.remove();
5654
+ continue;
5655
+ }
5656
+ if (!isElement(node)) {
5657
+ node.remove();
5658
+ continue;
5659
+ }
5660
+ const tag = tagOf(node);
5661
+ if (BLOCK_TAGS.has(tag) || tag === "figure") {
5662
+ flush(node);
5663
+ continue;
5664
+ }
5665
+ if (INLINE_TAGS.has(tag) || tag === "img" || tag === "video" || tag === "iframe") {
5666
+ buffer.push(node);
5667
+ continue;
5668
+ }
5669
+ while (node.firstChild) buffer.push(node.firstChild), body.insertBefore(node.firstChild, node);
5670
+ node.remove();
5671
+ }
5672
+ flush(null);
5673
+ if (options.forEditor && !body.querySelector("p,h1,h2,h3,h4,h5,h6,ul,ol,blockquote,pre,table,figure,hr")) {
5674
+ body.innerHTML = "<p><br></p>";
5675
+ }
5676
+ }
5677
+ function stripEmpty(body, forEditor) {
5678
+ for (const span of Array.from(body.querySelectorAll("span"))) {
5679
+ if (!span.textContent?.trim() && !span.querySelector("img,video,iframe,br") && !span.hasAttribute("style")) {
5680
+ span.remove();
5681
+ }
5682
+ }
5683
+ for (const p of Array.from(body.querySelectorAll("p"))) {
5684
+ if (p.querySelector("img,video,iframe,br,table,figure")) continue;
5685
+ if (p.textContent?.trim()) continue;
5686
+ if (forEditor && p === body.lastElementChild) continue;
5687
+ p.remove();
5688
+ }
5689
+ for (const heading of Array.from(body.querySelectorAll("h1,h2,h3,h4,h5,h6,blockquote"))) {
5690
+ if (!heading.textContent?.trim() && !heading.querySelector("img,video,iframe,br")) heading.remove();
5691
+ }
5692
+ for (const figure of Array.from(body.querySelectorAll("figure"))) {
5693
+ if (!figure.querySelector("img,video,iframe,table")) figure.remove();
5694
+ }
5695
+ for (const table of Array.from(body.querySelectorAll("table"))) {
5696
+ if (!table.querySelector("td,th")) table.remove();
5697
+ }
5698
+ if (forEditor && !body.querySelector("p,h1,h2,h3,h4,h5,h6,ul,ol,blockquote,pre,table,figure,hr")) {
5699
+ body.innerHTML = "<p><br></p>";
5700
+ }
5701
+ }
5702
+ function normalizeElement(body, options = {}) {
5703
+ normalizeChildren(body, options);
5704
+ normalizeFigures(body, options);
5705
+ normalizeTables(body);
5706
+ normalizeChildren(body, options);
5707
+ normalizeRoot(body, options);
5708
+ stripEmpty(body, Boolean(options.forEditor));
5709
+ const result = body.innerHTML.trim();
5710
+ if (!result) return options.forEditor ? "<p><br></p>" : "";
5711
+ return result;
5712
+ }
5713
+ function normalizeHtml(html, options = {}) {
5714
+ if (!html) return options.forEditor ? "<p><br></p>" : "";
5715
+ if (typeof DOMParser === "undefined") return html;
5716
+ const doc = new DOMParser().parseFromString(
5717
+ `<!doctype html><html><body>${html}</body></html>`,
5718
+ "text/html"
5719
+ );
5720
+ return normalizeElement(doc.body, options);
5721
+ }
5722
+ function normalizeForStorage(html) {
5723
+ return normalizeHtml(html, { forEditor: false });
5724
+ }
5725
+ function normalizeForEditor(html) {
5726
+ return normalizeHtml(html, { forEditor: true });
5727
+ }
5728
+ const FX_EDITOR_CONTENT_CSS = `
5729
+ .content { line-height: 1.55; color: var(--fx-text-default); }
5730
+ .content p { margin: 0 0 0.65em; }
5731
+ .content h1, .content h2, .content h3, .content h4, .content h5, .content h6 {
5732
+ margin: 0.8em 0 0.4em; line-height: 1.25; font-weight: 600; color: var(--fx-text-default);
5733
+ }
5734
+ .content h1 { font-size: 1.8em; }
5735
+ .content h2 { font-size: 1.5em; }
5736
+ .content h3 { font-size: 1.25em; }
5737
+ .content h4 { font-size: 1.1em; }
5738
+ .content h5, .content h6 { font-size: 1em; }
5739
+ .content ul, .content ol { margin: 0 0 0.65em; padding-left: 1.6em; }
5740
+ .content li { margin: 0.15em 0; }
5741
+ .content blockquote {
5742
+ margin: 0.5em 0; padding: 0.25em 0 0.25em 1em;
5743
+ border-left: 3px solid var(--fx-color-primary); color: var(--fx-text-muted);
5744
+ }
5745
+ .content pre {
5746
+ margin: 0.6em 0; padding: var(--fx-space-md); overflow: auto;
5747
+ background: var(--fx-surface-surface); border-radius: var(--fx-radius-sm);
5748
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.9em;
5749
+ }
5750
+ .content code {
5751
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
5752
+ font-size: 0.92em; background: var(--fx-surface-surface);
5753
+ padding: 0.1em 0.3em; border-radius: 3px;
5754
+ }
5755
+ .content pre code { background: none; padding: 0; }
5756
+ .content hr { border: none; border-top: 1px solid var(--fx-border-default); margin: 1em 0; }
5757
+ .content a { color: var(--fx-color-primary); text-decoration: underline; cursor: pointer; }
5758
+ .content img { max-width: 100%; height: auto; display: block; }
5759
+ .content figure { margin: 1em 0; }
5760
+ .content figure.fx-image img { margin: 0 auto; }
5761
+ .content figure.fx-image[data-align="left"] { float: left; max-width: 50%; margin: 0 1em 0.5em 0; }
5762
+ .content figure.fx-image[data-align="right"] { float: right; max-width: 50%; margin: 0 0 0.5em 1em; }
5763
+ .content figure.fx-image[data-align="center"] { margin-left: auto; margin-right: auto; }
5764
+ .content figure.fx-video[data-align="left"],
5765
+ .content figure.fx-embed[data-align="left"] { float: left; max-width: 50%; margin: 0 1em 0.5em 0; }
5766
+ .content figure.fx-video[data-align="right"],
5767
+ .content figure.fx-embed[data-align="right"] { float: right; max-width: 50%; margin: 0 0 0.5em 1em; }
5768
+ .content figure.fx-video[data-align="center"],
5769
+ .content figure.fx-embed[data-align="center"] { margin-left: auto; margin-right: auto; }
5770
+ .content figcaption {
5771
+ margin-top: 0.4em; color: var(--fx-text-muted);
5772
+ font-size: 0.85em; text-align: center;
5773
+ }
5774
+ .content video { display: block; width: 100%; background: #000; border-radius: var(--fx-radius-sm); }
5775
+ .content iframe { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; }
5776
+ .content table { width: 100%; border-collapse: collapse; margin: 1em 0; }
5777
+ .content th, .content td { border: 1px solid var(--fx-border-default); padding: var(--fx-space-xs) var(--fx-space-sm); vertical-align: top; }
5778
+ .content th { background: var(--fx-surface-surface); text-align: left; font-weight: 600; }
5779
+ .content table.fx-grid { border: none; margin: 0.6em 0; }
5780
+ .content table.fx-grid > tbody > tr > td {
5781
+ border: 1px dashed var(--fx-border-default); padding: var(--fx-space-sm);
5782
+ vertical-align: top;
5783
+ }
5784
+ @media (max-width: 640px) {
5785
+ .content table.fx-grid[data-stack] > tbody > tr > td {
5786
+ display: block; width: 100% !important; border: none; padding: var(--fx-space-xs) 0;
5787
+ }
5788
+ }
5789
+ `;
5790
+ const EDITOR_STYLES = `
5791
+ :host {
5792
+ display: block;
5793
+ width: 100%;
5794
+ font-family: var(--fx-font-family);
5795
+ font-size: var(--fx-font-size);
5796
+ color: var(--fx-text-default);
5797
+ --fx-editor-min-height: 240px;
5798
+ --fx-editor-max-height: none;
5799
+ --fx-editor-radius: var(--fx-radius-md);
5800
+ --fx-editor-toolbar-bg: var(--fx-surface-surface);
5801
+ --fx-editor-content-padding: var(--fx-space-md) var(--fx-space-lg);
5802
+ }
5803
+ :host([full]) { width: 100%; }
5804
+ .editor {
5805
+ display: flex;
5806
+ flex-direction: column;
5807
+ overflow: hidden;
5808
+ background: var(--fx-surface-background);
5809
+ border: 1px solid var(--fx-border-default);
5810
+ border-radius: var(--fx-editor-radius);
5811
+ transition: border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
5812
+ box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
5813
+ }
5814
+ .editor:focus-within {
5815
+ border-color: var(--fx-color-primary);
5816
+ box-shadow: var(--fx-effect-focus-ring, none);
5817
+ }
5818
+ :host([error]) .editor, :host([invalid]) .editor {
5819
+ border-color: var(--fx-color-danger, #dc2626);
5820
+ box-shadow: var(--fx-effect-error-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent));
5821
+ }
5822
+ :host([success]) .editor, :host([valid]) .editor {
5823
+ border-color: var(--fx-color-success, #16a34a);
5824
+ box-shadow: var(--fx-effect-success-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent));
5825
+ }
5826
+ :host([disabled]) { opacity: 0.55; pointer-events: none; }
5827
+ :host([readonly]) .toolbar { display: none; }
5828
+
5829
+ .toolbar {
5830
+ position: relative;
5831
+ display: flex;
5832
+ flex-wrap: wrap;
5833
+ align-items: center;
5834
+ gap: 1px;
5835
+ padding: var(--fx-space-xs);
5836
+ background: var(--fx-editor-toolbar-bg);
5837
+ border-bottom: 1px solid var(--fx-border-default);
5838
+ user-select: none;
5839
+ }
5840
+ .toolbar[hidden] { display: none; }
5841
+ .tb-group {
5842
+ display: flex;
5843
+ align-items: center;
5844
+ gap: 1px;
5845
+ padding: 0 var(--fx-space-xs);
5846
+ border-right: 1px solid var(--fx-border-default);
5847
+ }
5848
+ .tb-group:last-child { border-right: none; }
5849
+ .tb-btn {
5850
+ display: inline-flex;
5851
+ align-items: center;
5852
+ justify-content: center;
5853
+ width: 30px;
5854
+ height: 30px;
5855
+ padding: 0;
5856
+ border: none;
5857
+ border-radius: var(--fx-radius-sm);
5858
+ background: transparent;
5859
+ color: var(--fx-text-default);
5860
+ font-size: 19px;
5861
+ cursor: pointer;
5862
+ transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);
5863
+ }
5864
+ .tb-btn:hover { background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.06)); }
5865
+ .tb-btn:focus-visible { outline: 2px solid var(--fx-color-primary); outline-offset: -2px; }
5866
+ .tb-btn.active {
5867
+ background: color-mix(in srgb, var(--fx-color-primary) 14%, transparent);
5868
+ color: var(--fx-color-primary);
5869
+ }
5870
+ .tb-btn:disabled { opacity: 0.4; cursor: not-allowed; }
5871
+ .tb-select {
5872
+ height: 30px;
5873
+ padding: 0 var(--fx-space-xs);
5874
+ border: 1px solid var(--fx-border-default);
5875
+ border-radius: var(--fx-radius-sm);
5876
+ background: var(--fx-surface-background);
5877
+ color: var(--fx-text-default);
5878
+ font-family: inherit;
5879
+ font-size: calc(var(--fx-font-size) - 2px);
5880
+ cursor: pointer;
5881
+ }
5882
+ .tb-color {
5883
+ width: 30px;
5884
+ height: 30px;
5885
+ padding: 2px;
5886
+ border: none;
5887
+ background: transparent;
5888
+ cursor: pointer;
5889
+ }
5890
+ .tb-color input { width: 26px; height: 26px; padding: 0; border: none; background: none; cursor: pointer; }
5891
+ .body { position: relative; display: flex; flex-direction: column; flex: 1 1 auto; }
5892
+ .content {
5893
+ position: relative;
5894
+ min-height: var(--fx-editor-min-height);
5895
+ max-height: var(--fx-editor-max-height);
5896
+ overflow: auto;
5897
+ padding: var(--fx-editor-content-padding);
5898
+ outline: none;
5899
+ box-sizing: border-box;
5900
+ }
5901
+ .content[hidden] { display: none; }
5902
+ .content.is-empty::before {
5903
+ content: attr(data-placeholder);
5904
+ color: var(--fx-text-muted);
5905
+ pointer-events: none;
5906
+ position: absolute;
5907
+ }
5908
+ .content[contenteditable="false"] { background: var(--fx-surface-surface-hover, rgba(0,0,0,.02)); }
5909
+ .content figure.fx-image { touch-action: none; }
5910
+
5911
+ .media-overlay {
5912
+ position: absolute;
5913
+ z-index: 5;
5914
+ pointer-events: none;
5915
+ border: 1px solid var(--fx-color-primary);
5916
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--fx-color-primary) 30%, transparent);
5917
+ }
5918
+ .media-overlay[hidden] { display: none; }
5919
+ .media-handle {
5920
+ position: absolute;
5921
+ width: 11px;
5922
+ height: 11px;
5923
+ border: 1px solid #fff;
5924
+ border-radius: 50%;
5925
+ background: var(--fx-color-primary);
5926
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
5927
+ pointer-events: auto;
5928
+ touch-action: none;
5929
+ }
5930
+ .media-handle.nw { left: -6px; top: -6px; cursor: nwse-resize; }
5931
+ .media-handle.ne { right: -6px; top: -6px; cursor: nesw-resize; }
5932
+ .media-handle.sw { left: -6px; bottom: -6px; cursor: nesw-resize; }
5933
+ .media-handle.se { right: -6px; bottom: -6px; cursor: nwse-resize; }
5934
+ .media-size {
5935
+ position: absolute;
5936
+ top: -24px;
5937
+ left: 0;
5938
+ padding: 2px 6px;
5939
+ border-radius: var(--fx-radius-sm);
5940
+ background: var(--fx-color-primary);
5941
+ color: #fff;
5942
+ font-size: 11px;
5943
+ line-height: 1.4;
5944
+ white-space: nowrap;
5945
+ }
5946
+ .media-delete {
5947
+ position: absolute;
5948
+ top: -32px;
5949
+ right: -1px;
5950
+ display: inline-flex;
5951
+ align-items: center;
5952
+ justify-content: center;
5953
+ width: 24px;
5954
+ height: 24px;
5955
+ padding: 0;
5956
+ border: none;
5957
+ border-radius: 50%;
5958
+ background: var(--fx-color-danger, #dc2626);
5959
+ color: #fff;
5960
+ font-size: 15px;
5961
+ line-height: 1;
5962
+ cursor: pointer;
5963
+ pointer-events: auto;
5964
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
5965
+ }
5966
+ .media-delete:hover { filter: brightness(0.92); }
5967
+ .media-delete:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
5968
+
5969
+ .content figure.fx-image,
5970
+ .content figure.fx-video,
5971
+ .content figure.fx-embed { position: relative; }
5972
+ .fx-media-quick {
5973
+ position: absolute;
5974
+ top: 6px;
5975
+ z-index: 4;
5976
+ display: inline-flex;
5977
+ align-items: center;
5978
+ justify-content: center;
5979
+ width: 24px;
5980
+ height: 24px;
5981
+ padding: 0;
5982
+ border: 1px solid color-mix(in srgb, var(--fx-color-primary) 35%, transparent);
5983
+ border-radius: var(--fx-radius-sm);
5984
+ background: var(--fx-surface-background);
5985
+ color: var(--fx-text-default);
5986
+ cursor: pointer;
5987
+ opacity: 0;
5988
+ transition: opacity var(--fx-motion-duration-fast) var(--fx-motion-easing), background var(--fx-motion-duration-fast) var(--fx-motion-easing);
5989
+ }
5990
+ .content figure:hover > .fx-media-quick,
5991
+ .content figure:focus-within > .fx-media-quick { opacity: 1; }
5992
+ .content figure.fx-video > .fx-media-quick,
5993
+ .content figure.fx-embed > .fx-media-quick { opacity: 0.92; }
5994
+ .fx-media-quick:hover { background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.06)); }
5995
+ .fx-media-quick.active { background: var(--fx-color-primary); color: #fff; }
5996
+ .fx-media-quick.quick-left { right: 82px; }
5997
+ .fx-media-quick.quick-center { right: 56px; }
5998
+ .fx-media-quick.quick-right { right: 30px; }
5999
+ .fx-media-quick.quick-delete { right: 4px; background: var(--fx-color-danger, #dc2626); color: #fff; border-color: transparent; }
6000
+ .fx-media-quick:focus-visible { outline: 2px solid var(--fx-color-primary); outline-offset: 1px; }
6001
+
6002
+ .link-overlay {
6003
+ position: absolute;
6004
+ z-index: 6;
6005
+ display: flex;
6006
+ gap: 2px;
6007
+ padding: 3px;
6008
+ background: var(--fx-surface-background);
6009
+ border: 1px solid var(--fx-border-default);
6010
+ border-radius: var(--fx-radius-sm);
6011
+ box-shadow: var(--fx-shadow-md, 0 4px 12px rgba(0, 0, 0, 0.16));
6012
+ }
6013
+ .link-overlay[hidden] { display: none; }
6014
+ .link-overlay button {
6015
+ border: none;
6016
+ background: none;
6017
+ color: var(--fx-text-default);
6018
+ font: inherit;
6019
+ font-size: 12px;
6020
+ line-height: 1.4;
6021
+ padding: 4px 8px;
6022
+ border-radius: var(--fx-radius-sm);
6023
+ cursor: pointer;
6024
+ }
6025
+ .link-overlay button:hover { background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.06)); }
6026
+ .link-overlay button[data-link-action="remove"] { color: var(--fx-color-danger, #dc2626); }
6027
+ .link-overlay button:focus-visible { outline: 2px solid var(--fx-color-primary); outline-offset: 1px; }
6028
+
6029
+ .source {
6030
+ display: block;
6031
+ width: 100%;
6032
+ min-height: var(--fx-editor-min-height);
6033
+ max-height: var(--fx-editor-max-height);
6034
+ padding: var(--fx-editor-content-padding);
6035
+ border: none;
6036
+ outline: none;
6037
+ resize: vertical;
6038
+ background: var(--fx-surface-background);
6039
+ color: var(--fx-text-default);
6040
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
6041
+ font-size: 13px;
6042
+ line-height: 1.55;
6043
+ box-sizing: border-box;
6044
+ }
6045
+ .source[hidden] { display: none; }
6046
+
6047
+ .statusbar {
6048
+ display: flex;
6049
+ align-items: center;
6050
+ justify-content: space-between;
6051
+ gap: var(--fx-space-sm);
6052
+ min-height: 28px;
6053
+ padding: 2px var(--fx-space-md);
6054
+ border-top: 1px solid var(--fx-border-default);
6055
+ color: var(--fx-text-muted);
6056
+ font-size: calc(var(--fx-font-size) - 3px);
6057
+ }
6058
+ .statusbar:empty { display: none; }
6059
+ .statusbar[hidden] { display: none; }
6060
+ .source-actions { display: inline-flex; gap: var(--fx-space-xs); }
6061
+ .source-actions[hidden] { display: none; }
6062
+
6063
+ .backdrop {
6064
+ position: fixed;
6065
+ inset: 0;
6066
+ z-index: var(--fx-z-modal, 1100);
6067
+ display: flex;
6068
+ align-items: center;
6069
+ justify-content: center;
6070
+ background: color-mix(in srgb, var(--fx-text-default, #000) 45%, transparent);
6071
+ }
6072
+ .backdrop[hidden] { display: none; }
6073
+ .dialog {
6074
+ width: min(460px, calc(100vw - 32px));
6075
+ max-height: calc(100vh - 48px);
6076
+ overflow: auto;
6077
+ background: var(--fx-surface-background);
6078
+ border: 1px solid var(--fx-border-default);
6079
+ border-radius: var(--fx-radius-lg);
6080
+ box-shadow: var(--fx-shadow-xl);
6081
+ font-family: var(--fx-font-family);
6082
+ font-size: var(--fx-font-size);
6083
+ }
6084
+ .dialog header {
6085
+ display: flex;
6086
+ align-items: center;
6087
+ justify-content: space-between;
6088
+ padding: var(--fx-space-md) var(--fx-space-lg);
6089
+ border-bottom: 1px solid var(--fx-border-default);
6090
+ }
6091
+ .dialog h3 { margin: 0; font-size: 1rem; font-weight: 600; }
6092
+ .dialog-close {
6093
+ border: none; background: transparent; color: var(--fx-text-muted);
6094
+ font-size: 1.4rem; line-height: 1; cursor: pointer;
6095
+ }
6096
+ .dialog-body { display: flex; flex-direction: column; gap: var(--fx-space-sm); padding: var(--fx-space-lg); }
6097
+ .field { display: flex; flex-direction: column; gap: 3px; font-size: calc(var(--fx-font-size) - 2px); color: var(--fx-text-muted); }
6098
+ .field input, .field select {
6099
+ height: var(--fx-size-md);
6100
+ padding: 0 var(--fx-space-sm);
6101
+ border: 1px solid var(--fx-border-default);
6102
+ border-radius: var(--fx-radius-sm);
6103
+ background: var(--fx-surface-background);
6104
+ color: var(--fx-text-default);
6105
+ font: inherit;
6106
+ font-size: var(--fx-font-size);
6107
+ }
6108
+ .field input:focus, .field select:focus { outline: none; border-color: var(--fx-color-primary); box-shadow: var(--fx-effect-focus-ring, none); }
6109
+ .dialog footer {
6110
+ display: flex; justify-content: flex-end; gap: var(--fx-space-sm);
6111
+ padding: var(--fx-space-md) var(--fx-space-lg);
6112
+ border-top: 1px solid var(--fx-border-default);
6113
+ }
6114
+ .btn {
6115
+ min-height: var(--fx-size-sm);
6116
+ padding: 0 var(--fx-space-md);
6117
+ border: 1px solid var(--fx-border-default);
6118
+ border-radius: var(--fx-radius-sm);
6119
+ background: var(--fx-surface-background);
6120
+ color: var(--fx-text-default);
6121
+ font: inherit; font-size: calc(var(--fx-font-size) - 2px);
6122
+ cursor: pointer;
6123
+ }
6124
+ .btn:hover { background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
6125
+ .btn.primary { background: var(--fx-color-primary); border-color: transparent; color: #fff; }
6126
+ .btn.primary:hover { filter: brightness(0.92); }
6127
+
6128
+ .panel {
6129
+ position: absolute;
6130
+ top: calc(100% + 4px);
6131
+ left: var(--fx-space-md);
6132
+ z-index: 6;
6133
+ padding: var(--fx-space-sm);
6134
+ background: var(--fx-surface-background);
6135
+ border: 1px solid var(--fx-border-default);
6136
+ border-radius: var(--fx-radius-md);
6137
+ box-shadow: var(--fx-shadow-lg);
6138
+ }
6139
+ .panel[hidden] { display: none; }
6140
+ .grid-picker { display: grid; grid-template-columns: repeat(8, 18px); gap: 3px; }
6141
+ .grid-picker button {
6142
+ width: 18px; height: 18px; padding: 0;
6143
+ border: 1px solid var(--fx-border-default);
6144
+ border-radius: 2px; background: var(--fx-surface-background); cursor: pointer;
6145
+ }
6146
+ .grid-picker button.on { background: var(--fx-color-primary); border-color: var(--fx-color-primary); }
6147
+ .panel-title { margin: 0 0 6px; font-size: 11px; color: var(--fx-text-muted); }
6148
+ .panel-actions { display: flex; flex-wrap: wrap; gap: 4px; max-width: 210px; }
6149
+ .panel-actions .btn { min-height: 26px; padding: 0 6px; font-size: 11px; }
6150
+
6151
+ :host([fullscreen]) {
6152
+ position: fixed;
6153
+ inset: 0;
6154
+ z-index: var(--fx-z-modal, 1100);
6155
+ background: var(--fx-surface-background);
6156
+ }
6157
+ :host([fullscreen]) .editor { height: 100%; border: none; border-radius: 0; }
6158
+ :host([fullscreen]) .content, :host([fullscreen]) .source { flex: 1 1 auto; max-height: none; }
6159
+
6160
+ @media (max-width: 640px) {
6161
+ .toolbar { gap: 0; }
6162
+ .tb-group { padding: 0 1px; }
6163
+ .tb-btn { width: 28px; height: 28px; }
6164
+ }
6165
+ ${FX_EDITOR_CONTENT_CSS}
6166
+
6167
+
6168
+ `;
6169
+ const BLOCK_SELECTOR = "p,h1,h2,h3,h4,h5,h6,li,blockquote,pre,td,th,figcaption,div";
6170
+ function rootOf(editor) {
6171
+ const root = editor.getRootNode();
6172
+ return typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot ? root : document;
6173
+ }
6174
+ function isRangeInside(editor, range2) {
6175
+ if (!range2) return false;
6176
+ const start = range2.startContainer;
6177
+ const end = range2.endContainer;
6178
+ return editor.contains(start) && editor.contains(end);
6179
+ }
6180
+ function readSelectionRange(root) {
6181
+ const selection = document.getSelection();
6182
+ if (!selection) return null;
6183
+ if (root instanceof ShadowRoot && selection.getComposedRanges) {
6184
+ try {
6185
+ const ranges = selection.getComposedRanges({ shadowRoots: [root] });
6186
+ const first = ranges[0];
6187
+ if (first) {
6188
+ const range2 = document.createRange();
6189
+ range2.setStart(first.startContainer, first.startOffset);
6190
+ range2.setEnd(first.endContainer, first.endOffset);
6191
+ return range2;
6192
+ }
6193
+ } catch {
6194
+ }
6195
+ }
6196
+ return selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
6197
+ }
6198
+ function getRange(editor, fallback) {
6199
+ const range2 = readSelectionRange(rootOf(editor));
6200
+ if (isRangeInside(editor, range2)) return range2;
6201
+ if (isRangeInside(editor, fallback ?? null)) return fallback ?? null;
6202
+ return null;
6203
+ }
6204
+ function saveRange(editor) {
6205
+ const range2 = getRange(editor);
6206
+ return range2 ? range2.cloneRange() : null;
6207
+ }
6208
+ function restoreRange(editor, range2) {
6209
+ if (!range2 || !isRangeInside(editor, range2)) return;
6210
+ editor.focus({ preventScroll: true });
6211
+ const selection = document.getSelection();
6212
+ if (!selection) return;
6213
+ selection.removeAllRanges();
6214
+ selection.addRange(range2);
6215
+ }
6216
+ function placeCaretAtEnd(editor) {
6217
+ const range2 = document.createRange();
6218
+ range2.selectNodeContents(editor);
6219
+ range2.collapse(false);
6220
+ const selection = document.getSelection();
6221
+ if (!selection) return;
6222
+ selection.removeAllRanges();
6223
+ selection.addRange(range2);
6224
+ }
6225
+ function getBlock(editor, range2) {
6226
+ const active = range2 ?? getRange(editor);
6227
+ if (!active) return null;
6228
+ const node = active.startContainer;
6229
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
6230
+ const block = el?.closest(BLOCK_SELECTOR) ?? null;
6231
+ return block && editor.contains(block) ? block : null;
6232
+ }
6233
+ function getSelectedBlocks(editor, range2) {
6234
+ const active = getRange(editor);
6235
+ if (!active) return [];
6236
+ if (active.collapsed) {
6237
+ const block = getBlock(editor, active);
6238
+ return block ? [block] : [];
6239
+ }
6240
+ return Array.from(editor.querySelectorAll(BLOCK_SELECTOR)).filter((block) => {
6241
+ try {
6242
+ return active.intersectsNode(block);
6243
+ } catch {
6244
+ return false;
6245
+ }
6246
+ });
6247
+ }
6248
+ function closestInEditor(editor, node, selector) {
6249
+ const el = node && (node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement);
6250
+ const match = el?.closest(selector) ?? null;
6251
+ return match && editor.contains(match) ? match : null;
6252
+ }
6253
+ function setSelection(range2) {
6254
+ const selection = document.getSelection();
6255
+ if (!selection) return;
6256
+ selection.removeAllRanges();
6257
+ selection.addRange(range2);
6258
+ }
6259
+ function nativeExec(command, value) {
6260
+ const exec = document.execCommand;
6261
+ if (typeof exec !== "function") return false;
6262
+ try {
6263
+ return exec.call(document, command, false, value);
6264
+ } catch {
6265
+ return false;
6266
+ }
6267
+ }
6268
+ function insertHtml(editor, html) {
6269
+ if (nativeExec("insertHTML", html)) return true;
6270
+ const range2 = getRange(editor);
6271
+ if (!range2) {
6272
+ editor.insertAdjacentHTML("beforeend", html);
6273
+ placeCaretAtEnd(editor);
6274
+ return true;
6275
+ }
6276
+ range2.deleteContents();
6277
+ const fragment = range2.createContextualFragment(html);
6278
+ const last = fragment.lastChild;
6279
+ range2.insertNode(fragment);
6280
+ if (last) {
6281
+ const next = document.createRange();
6282
+ next.setStartAfter(last);
6283
+ next.collapse(true);
6284
+ setSelection(next);
6285
+ }
6286
+ return true;
6287
+ }
6288
+ function unwrap(el) {
6289
+ const parent = el.parentNode;
6290
+ if (!parent) return;
6291
+ while (el.firstChild) parent.insertBefore(el.firstChild, el);
6292
+ el.remove();
6293
+ }
6294
+ function wrapSelection(editor, tag) {
6295
+ const range2 = getRange(editor);
6296
+ if (!range2) return false;
6297
+ const el = document.createElement(tag);
6298
+ if (range2.collapsed) {
6299
+ el.appendChild(document.createTextNode("​"));
6300
+ range2.insertNode(el);
6301
+ const caret = document.createRange();
6302
+ caret.setStart(el.firstChild ?? el, 1);
6303
+ caret.collapse(true);
6304
+ setSelection(caret);
6305
+ return true;
6306
+ }
6307
+ try {
6308
+ range2.surroundContents(el);
6309
+ } catch {
6310
+ el.appendChild(range2.extractContents());
6311
+ range2.insertNode(el);
6312
+ }
6313
+ return true;
6314
+ }
6315
+ function toggleInline(editor, tag) {
6316
+ const nativeMap = {
6317
+ strong: "bold",
6318
+ b: "bold",
6319
+ em: "italic",
6320
+ i: "italic",
6321
+ u: "underline",
6322
+ s: "strikeThrough",
6323
+ strike: "strikeThrough"
6324
+ };
6325
+ const nativeCommand = nativeMap[tag];
6326
+ if (nativeCommand && nativeExec(nativeCommand)) return true;
6327
+ const range2 = getRange(editor);
6328
+ if (!range2) return false;
6329
+ const current = (range2.startContainer.nodeType === Node.ELEMENT_NODE ? range2.startContainer : range2.startContainer.parentElement)?.closest(tag);
6330
+ if (current && editor.contains(current)) {
6331
+ unwrap(current);
6332
+ return true;
6333
+ }
6334
+ return wrapSelection(editor, tag);
6335
+ }
6336
+ function formatBlock(editor, tag) {
6337
+ const normalized = tag.toLowerCase().replace(/[<>]/g, "");
6338
+ if (nativeExec("formatBlock", normalized)) return true;
6339
+ const blocks = getSelectedBlocks(editor);
6340
+ if (!blocks.length) {
6341
+ const block = getBlock(editor);
6342
+ if (block) blocks.push(block);
6343
+ }
6344
+ for (const block of blocks) {
6345
+ if (!editor.contains(block)) continue;
6346
+ const next = document.createElement(normalized);
6347
+ for (const attr of Array.from(block.attributes)) next.setAttribute(attr.name, attr.value);
6348
+ while (block.firstChild) next.appendChild(block.firstChild);
6349
+ block.replaceWith(next);
6350
+ }
6351
+ return blocks.length > 0;
6352
+ }
6353
+ function align(editor, value) {
6354
+ const nativeMap = {
6355
+ left: "justifyLeft",
6356
+ center: "justifyCenter",
6357
+ right: "justifyRight",
6358
+ justify: "justifyFull"
6359
+ };
6360
+ if (nativeExec(nativeMap[value])) return true;
6361
+ const blocks = getSelectedBlocks(editor);
6362
+ for (const block of blocks) block.style.textAlign = value;
6363
+ return blocks.length > 0;
6364
+ }
6365
+ function toggleList(editor, ordered) {
6366
+ if (nativeExec(ordered ? "insertOrderedList" : "insertUnorderedList")) return true;
6367
+ const blocks = getSelectedBlocks(editor);
6368
+ if (!blocks.length) return false;
6369
+ const list = document.createElement(ordered ? "ol" : "ul");
6370
+ const first = blocks[0];
6371
+ first.parentNode?.insertBefore(list, first);
6372
+ for (const block of blocks) {
6373
+ const li = document.createElement("li");
6374
+ while (block.firstChild) li.appendChild(block.firstChild);
6375
+ list.appendChild(li);
6376
+ block.remove();
6377
+ }
6378
+ const range2 = document.createRange();
6379
+ range2.selectNodeContents(list.lastElementChild ?? list);
6380
+ range2.collapse(false);
6381
+ setSelection(range2);
6382
+ return true;
6383
+ }
6384
+ function removeFormat(editor) {
6385
+ if (nativeExec("removeFormat")) return true;
6386
+ const range2 = getRange(editor);
6387
+ if (!range2) return false;
6388
+ const root = range2.commonAncestorContainer;
6389
+ const container = root.nodeType === Node.ELEMENT_NODE ? root : root.parentElement;
6390
+ if (!container) return false;
6391
+ const tags = ["strong", "b", "em", "i", "u", "s", "strike", "span", "mark", "code", "sub", "sup"];
6392
+ for (const el of Array.from(container.querySelectorAll(tags.join(",")))) {
6393
+ if (range2.intersectsNode(el)) unwrap(el);
6394
+ }
6395
+ return true;
6396
+ }
6397
+ const linkHtml = (href, label, target) => `<a href="${escapeAttr(href)}" target="${target}" rel="noopener noreferrer">${escapeText(label)}</a>`;
6398
+ function applyLink(editor, href, text, target = "_blank") {
6399
+ const label = text?.trim() || href;
6400
+ const range2 = getRange(editor);
6401
+ if (!range2) return insertHtml(editor, linkHtml(href, label, target));
6402
+ const anchor = (range2.startContainer.nodeType === Node.ELEMENT_NODE ? range2.startContainer : range2.startContainer.parentElement)?.closest("a");
6403
+ if (anchor && editor.contains(anchor)) {
6404
+ anchor.setAttribute("href", href);
6405
+ anchor.setAttribute("target", target);
6406
+ anchor.setAttribute("rel", "noopener noreferrer");
6407
+ if (text && text !== anchor.textContent) anchor.textContent = text;
6408
+ return true;
6409
+ }
6410
+ if (!range2.collapsed) {
6411
+ if (nativeExec("createLink", href)) {
6412
+ const created = (range2.startContainer.nodeType === Node.ELEMENT_NODE ? range2.startContainer : range2.startContainer.parentElement)?.closest("a");
6413
+ created?.setAttribute("target", target);
6414
+ created?.setAttribute("rel", "noopener noreferrer");
6415
+ return true;
6416
+ }
6417
+ const el = document.createElement("a");
6418
+ el.setAttribute("href", href);
6419
+ el.setAttribute("target", target);
6420
+ el.setAttribute("rel", "noopener noreferrer");
6421
+ try {
6422
+ range2.surroundContents(el);
6423
+ } catch {
6424
+ el.appendChild(range2.extractContents());
6425
+ range2.insertNode(el);
6426
+ }
6427
+ return true;
6428
+ }
6429
+ return insertHtml(editor, linkHtml(href, label, target));
6430
+ }
6431
+ function unlink(editor) {
6432
+ if (nativeExec("unlink")) return true;
6433
+ const range2 = getRange(editor);
6434
+ if (!range2) return false;
6435
+ const anchor = (range2.startContainer.nodeType === Node.ELEMENT_NODE ? range2.startContainer : range2.startContainer.parentElement)?.closest("a");
6436
+ if (anchor && editor.contains(anchor)) {
6437
+ unwrap(anchor);
6438
+ return true;
6439
+ }
6440
+ return false;
6441
+ }
6442
+ function indent(editor, delta) {
6443
+ if (delta > 0 && nativeExec("indent")) return true;
6444
+ if (delta < 0 && nativeExec("outdent")) return true;
6445
+ const blocks = getSelectedBlocks(editor);
6446
+ for (const block of blocks) {
6447
+ const current = Number.parseInt(block.style.marginLeft || "0", 10) || 0;
6448
+ const next = Math.max(0, current + delta);
6449
+ block.style.marginLeft = next ? `${next}px` : "";
6450
+ }
6451
+ return blocks.length > 0;
6452
+ }
6453
+ function escapeAttr(value) {
6454
+ return value.replace(/[&<>"']/g, (ch) => {
6455
+ switch (ch) {
6456
+ case "&":
6457
+ return "&amp;";
6458
+ case "<":
6459
+ return "&lt;";
6460
+ case ">":
6461
+ return "&gt;";
6462
+ case '"':
6463
+ return "&quot;";
6464
+ default:
6465
+ return "&#39;";
6466
+ }
6467
+ });
6468
+ }
6469
+ function escapeText(value) {
6470
+ return value.replace(/[&<>]/g, (ch) => ch === "&" ? "&amp;" : ch === "<" ? "&lt;" : "&gt;");
6471
+ }
6472
+ function tableMatrix(table) {
6473
+ const rows = Array.from(table.rows);
6474
+ const matrix = rows.map(() => []);
6475
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
6476
+ const row = rows[rowIndex];
6477
+ for (const cell of Array.from(row.cells)) {
6478
+ let col = 0;
6479
+ while (matrix[rowIndex][col]) col++;
6480
+ const rowspan = Math.max(1, Math.min(cell.rowSpan || 1, rows.length - rowIndex));
6481
+ const colspan = Math.max(1, cell.colSpan || 1);
6482
+ for (let r = rowIndex; r < rowIndex + rowspan; r++) {
6483
+ if (!matrix[r]) matrix[r] = [];
6484
+ for (let c = col; c < col + colspan; c++) matrix[r][c] = cell;
6485
+ }
6486
+ }
6487
+ }
6488
+ const width = matrix.reduce((max, row) => Math.max(max, row.length), 0);
6489
+ for (const row of matrix) {
6490
+ while (row.length < width) row.push(null);
6491
+ }
6492
+ return { rows, matrix };
6493
+ }
6494
+ function cellPosition(table, cell) {
6495
+ const { matrix } = tableMatrix(table);
6496
+ for (let row = 0; row < matrix.length; row++) {
6497
+ for (let col = 0; col < matrix[row].length; col++) {
6498
+ if (matrix[row][col] === cell) return { row, col };
6499
+ }
6500
+ }
6501
+ return null;
6502
+ }
6503
+ function selectedCells(table, range2) {
6504
+ return Array.from(table.querySelectorAll("td,th")).filter((cell) => {
6505
+ try {
6506
+ return range2.intersectsNode(cell);
6507
+ } catch {
6508
+ return false;
6509
+ }
6510
+ });
6511
+ }
6512
+ function copyCellAttributes(from, to) {
6513
+ for (const attr of Array.from(from.attributes)) {
6514
+ if (attr.name === "colspan" || attr.name === "rowspan") continue;
6515
+ to.setAttribute(attr.name, attr.value);
6516
+ }
6517
+ }
6518
+ function mergeSelectedCells(table, range2) {
6519
+ const cells = selectedCells(table, range2);
6520
+ if (cells.length < 2) return null;
6521
+ const { matrix } = tableMatrix(table);
6522
+ const positions = cells.map((cell) => ({ cell, position: cellPosition(table, cell) })).filter((item) => Boolean(item.position));
6523
+ if (positions.length !== cells.length) return null;
6524
+ const minRow = Math.min(...positions.map((item) => item.position.row));
6525
+ const maxRow = Math.max(...positions.map((item) => item.position.row));
6526
+ const minCol = Math.min(...positions.map((item) => item.position.col));
6527
+ const maxCol = Math.max(...positions.map((item) => item.position.col));
6528
+ for (const { cell, position } of positions) {
6529
+ if (position.row < minRow || position.col < minCol) return null;
6530
+ if (position.row + Math.max(1, cell.rowSpan || 1) - 1 > maxRow) return null;
6531
+ if (position.col + Math.max(1, cell.colSpan || 1) - 1 > maxCol) return null;
6532
+ }
6533
+ const selected = new Set(cells);
6534
+ for (let row = minRow; row <= maxRow; row++) {
6535
+ for (let col = minCol; col <= maxCol; col++) {
6536
+ const cell = matrix[row]?.[col];
6537
+ if (!cell || !selected.has(cell)) return null;
6538
+ }
6539
+ }
6540
+ const anchor = matrix[minRow]?.[minCol];
6541
+ if (!anchor) return null;
6542
+ const tag = cells.every((cell) => cell.tagName.toLowerCase() === "th") ? "th" : "td";
6543
+ const merged = document.createElement(tag);
6544
+ copyCellAttributes(anchor, merged);
6545
+ if (tag === "td") merged.removeAttribute("scope");
6546
+ merged.colSpan = maxCol - minCol + 1;
6547
+ merged.rowSpan = maxRow - minRow + 1;
6548
+ if (merged.colSpan === 1) merged.removeAttribute("colspan");
6549
+ if (merged.rowSpan === 1) merged.removeAttribute("rowspan");
6550
+ for (const cell of cells) {
6551
+ while (cell.firstChild) merged.appendChild(cell.firstChild);
6552
+ }
6553
+ anchor.replaceWith(merged);
6554
+ for (const cell of cells) {
6555
+ if (cell !== anchor && cell.parentNode) cell.remove();
6556
+ }
6557
+ return merged;
6558
+ }
6559
+ function insertCellAtColumn(table, row, col, cell) {
6560
+ const rowIndex = Array.from(table.rows).indexOf(row);
6561
+ const { matrix } = tableMatrix(table);
6562
+ let before = null;
6563
+ for (const existing of Array.from(row.cells)) {
6564
+ let start = -1;
6565
+ for (let c = 0; c < (matrix[rowIndex]?.length ?? 0); c++) {
6566
+ if (matrix[rowIndex][c] === existing) {
6567
+ start = c;
6568
+ break;
6569
+ }
6570
+ }
6571
+ if (start >= col) {
6572
+ before = existing;
6573
+ break;
6574
+ }
6575
+ }
6576
+ row.insertBefore(cell, before);
6577
+ }
6578
+ function splitCell(table, cell) {
6579
+ const colspan = Math.max(1, cell.colSpan || 1);
6580
+ const rowspan = Math.max(1, cell.rowSpan || 1);
6581
+ if (colspan === 1 && rowspan === 1) return [];
6582
+ const position = cellPosition(table, cell);
6583
+ if (!position) return [];
6584
+ const rows = Array.from(table.rows);
6585
+ const makeCell = () => {
6586
+ const next = document.createElement(cell.tagName.toLowerCase());
6587
+ if (next.tagName.toLowerCase() === "th") {
6588
+ next.setAttribute("scope", cell.getAttribute("scope") ?? "col");
6589
+ }
6590
+ next.innerHTML = "<p><br></p>";
6591
+ return next;
6592
+ };
6593
+ const created = [];
6594
+ let last = cell;
6595
+ for (let index = 1; index < colspan; index++) {
6596
+ const next = makeCell();
6597
+ last.after(next);
6598
+ last = next;
6599
+ created.push(next);
6600
+ }
6601
+ cell.removeAttribute("colspan");
6602
+ cell.removeAttribute("rowspan");
6603
+ for (let index = 1; index < rowspan; index++) {
6604
+ const row = rows[position.row + index];
6605
+ if (!row) break;
6606
+ for (let offset = 0; offset < colspan; offset++) {
6607
+ const next = makeCell();
6608
+ insertCellAtColumn(table, row, position.col + offset, next);
6609
+ created.push(next);
6610
+ }
6611
+ }
6612
+ return created;
6613
+ }
6614
+ const MAX_PASTE_BYTES = 1024 * 1024;
6615
+ const MAX_IMAGE_BYTES = 8 * 1024 * 1024;
6616
+ const LARGE_DOC_CHARS = 2e4;
6617
+ const EMIT_DEBOUNCE_MS = 140;
6618
+ const HISTORY_LIMIT = 50;
6619
+ const QUICK_SVG = {
6620
+ left: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M4 10h10M4 14h16M4 18h10"/></svg>',
6621
+ center: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M7 10h10M4 14h16M7 18h10"/></svg>',
6622
+ right: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M10 10h10M4 14h16M10 18h10"/></svg>',
6623
+ delete: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4h8v2M6 6l1 15h10l1-15M10 10v7M14 10v7"/></svg>'
6624
+ };
6625
+ const _FxEditor = class _FxEditor extends FxFormElement {
6626
+ constructor() {
6627
+ super(...arguments);
6628
+ __privateAdd(this, _FxEditor_instances);
6629
+ __privateAdd(this, _built, false);
6630
+ __privateAdd(this, _eventsBound, false);
6631
+ __privateAdd(this, _initialized, false);
6632
+ __privateAdd(this, _defaultValue, "");
6633
+ __privateAdd(this, _value, "");
6634
+ __privateAdd(this, _dirty, false);
6635
+ __privateAdd(this, _sourceMode, false);
6636
+ __privateAdd(this, _lastRange, null);
6637
+ __privateAdd(this, _rafId, 0);
6638
+ __privateAdd(this, _emitTimer, null);
6639
+ __privateAdd(this, _lastLimitError, 0);
6640
+ __privateAdd(this, _snapshotTimer, null);
6641
+ __privateAdd(this, _history, []);
6642
+ __privateAdd(this, _historyIndex, -1);
6643
+ __privateAdd(this, _imagePicker, null);
6644
+ __privateAdd(this, _mediaFigure, null);
6645
+ __privateAdd(this, _linkAnchor, null);
6646
+ __privateAdd(this, _resizeDispose, null);
6647
+ __privateAdd(this, _dialogReturnFocus, null);
6648
+ __privateAdd(this, _dialogResolve, null);
6649
+ __privateAdd(this, _disposers, []);
6650
+ __privateAdd(this, _selectionRaf, 0);
6651
+ __privateAdd(this, _bodyOverflow, "");
6652
+ }
6653
+ static get observedAttributes() {
6654
+ return [
6655
+ "toolbar",
6656
+ "features",
6657
+ "placeholder",
6658
+ "readonly",
6659
+ "disabled",
6660
+ "required",
6661
+ "full",
6662
+ "error",
6663
+ "invalid",
6664
+ "success",
6665
+ "valid",
6666
+ "show-count",
6667
+ "paste-mode",
6668
+ "min-height",
6669
+ "max-height",
6670
+ "fullscreen",
6671
+ "maxlength"
6672
+ ];
6673
+ }
6674
+ get editor() {
6675
+ return this.root.querySelector(".content");
6676
+ }
6677
+ get source() {
6678
+ return this.root.querySelector(".source");
6679
+ }
6680
+ get toolbarEl() {
6681
+ return this.root.querySelector(".toolbar");
6682
+ }
6683
+ get value() {
6684
+ if (__privateGet(this, _sourceMode) && this.source) return __privateMethod(this, _FxEditor_instances, serialize_fn).call(this);
6685
+ if (__privateGet(this, _dirty)) __privateSet(this, _value, __privateMethod(this, _FxEditor_instances, serialize_fn).call(this));
6686
+ return __privateGet(this, _value);
6687
+ }
6688
+ set value(next) {
6689
+ const clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, next ?? "");
6690
+ __privateSet(this, _value, clean);
6691
+ __privateSet(this, _dirty, false);
6692
+ if (!__privateGet(this, _defaultValue)) __privateSet(this, _defaultValue, clean);
6693
+ __privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, clean);
6694
+ this.updateFormValue();
6695
+ }
6696
+ get html() {
6697
+ return this.value;
6698
+ }
6699
+ set html(next) {
6700
+ this.value = next;
6701
+ }
6702
+ get text() {
6703
+ return (this.editor?.textContent ?? "").replace(/\u200b/g, "").trim();
6704
+ }
6705
+ get defaultValue() {
6706
+ return __privateGet(this, _defaultValue);
6707
+ }
6708
+ set defaultValue(next) {
6709
+ __privateSet(this, _defaultValue, __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, next ?? ""));
6710
+ if (!__privateGet(this, _dirty)) this.value = __privateGet(this, _defaultValue);
6711
+ }
6712
+ get disabled() {
6713
+ return this.hasAttr("disabled");
6714
+ }
6715
+ set disabled(value) {
6716
+ this.toggleAttr("disabled", Boolean(value));
6717
+ }
6718
+ get readonly() {
6719
+ return this.hasAttr("readonly");
6720
+ }
6721
+ set readonly(value) {
6722
+ this.toggleAttr("readonly", Boolean(value));
6723
+ }
6724
+ get required() {
6725
+ return this.hasAttr("required");
6726
+ }
6727
+ set required(value) {
6728
+ this.toggleAttr("required", Boolean(value));
6729
+ }
6730
+ get placeholder() {
6731
+ return this.getAttr("placeholder");
6732
+ }
6733
+ set placeholder(value) {
6734
+ this.setAttribute("placeholder", value);
6735
+ }
6736
+ get toolbar() {
6737
+ const value = this.getAttr("toolbar", "standard");
6738
+ return ["minimal", "basic", "standard", "full", "none"].includes(value) ? value : "standard";
6739
+ }
6740
+ set toolbar(value) {
6741
+ this.setAttribute("toolbar", value);
6742
+ }
6743
+ get pasteMode() {
6744
+ return this.getAttr("paste-mode", "clean") === "plain" ? "plain" : "clean";
6745
+ }
6746
+ set pasteMode(value) {
6747
+ this.setAttribute("paste-mode", value);
6748
+ }
6749
+ get fullscreen() {
6750
+ return this.hasAttr("fullscreen");
6751
+ }
6752
+ set fullscreen(value) {
6753
+ this.toggleAttr("fullscreen", Boolean(value));
6754
+ }
6755
+ /** Limite de caracteres de texto (0 desativa). Padrão: 30.000. */
6756
+ get maxlength() {
6757
+ const value = Number.parseInt(this.getAttr("maxlength", "30000"), 10);
6758
+ return Number.isFinite(value) && value > 0 ? value : 0;
6759
+ }
6760
+ set maxlength(value) {
6761
+ if (Number.isFinite(value) && value >= 0) this.setAttribute("maxlength", String(Math.floor(value)));
6762
+ else this.removeAttribute("maxlength");
6763
+ }
6764
+ connectedCallback() {
6765
+ loadFenixIconsFont();
6766
+ super.connectedCallback();
6767
+ if (this.fullscreen) __privateMethod(this, _FxEditor_instances, syncBodyOverflow_fn).call(this);
6768
+ if (!__privateGet(this, _initialized)) {
6769
+ __privateSet(this, _initialized, true);
6770
+ const initial = __privateGet(this, _value) || this.getAttr("value") || this.innerHTML;
6771
+ this.replaceChildren();
6772
+ __privateSet(this, _defaultValue, __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, initial));
6773
+ __privateSet(this, _value, __privateGet(this, _defaultValue));
6774
+ __privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, __privateGet(this, _defaultValue));
6775
+ this.updateFormValue();
6776
+ __privateMethod(this, _FxEditor_instances, pushHistory_fn).call(this, __privateGet(this, _value));
6777
+ this.dispatchEvent(new CustomEvent("ready", { bubbles: true, composed: true }));
6778
+ } else if (__privateGet(this, _built) && !__privateGet(this, _eventsBound)) {
6779
+ __privateMethod(this, _FxEditor_instances, bindEvents_fn).call(this);
6780
+ __privateSet(this, _eventsBound, true);
6781
+ }
6782
+ __privateMethod(this, _FxEditor_instances, bindGlobalSelection_fn).call(this);
6783
+ }
6784
+ disconnectedCallback() {
6785
+ for (const dispose of __privateGet(this, _disposers)) dispose();
6786
+ __privateSet(this, _disposers, []);
6787
+ __privateSet(this, _eventsBound, false);
6788
+ cancelAnimationFrame(__privateGet(this, _rafId));
6789
+ cancelAnimationFrame(__privateGet(this, _selectionRaf));
6790
+ if (__privateGet(this, _emitTimer)) clearTimeout(__privateGet(this, _emitTimer));
6791
+ if (__privateGet(this, _snapshotTimer)) clearTimeout(__privateGet(this, _snapshotTimer));
6792
+ __privateMethod(this, _FxEditor_instances, endResize_fn).call(this);
6793
+ __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
6794
+ __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
6795
+ if (this.hasAttr("fullscreen")) document.body.style.overflow = __privateGet(this, _bodyOverflow);
6796
+ __privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null);
6797
+ super.disconnectedCallback();
6798
+ }
6799
+ attributeChangedCallback(name, oldValue, newValue) {
6800
+ if (oldValue === newValue || !__privateGet(this, _built)) return;
6801
+ if (name === "toolbar" || name === "features") __privateMethod(this, _FxEditor_instances, renderToolbar_fn).call(this);
6802
+ if (name === "fullscreen") __privateMethod(this, _FxEditor_instances, syncBodyOverflow_fn).call(this);
6803
+ __privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
6804
+ }
6805
+ formResetCallback() {
6806
+ __privateSet(this, _dirty, false);
6807
+ this.value = __privateGet(this, _defaultValue);
6808
+ }
6809
+ getFormValue() {
6810
+ return this.hasAttr("disabled") ? null : this.value;
6811
+ }
6812
+ render() {
6813
+ if (__privateGet(this, _built)) return;
6814
+ this.setTemplate(`
6815
+ <div class="editor" part="editor">
6816
+ <div class="toolbar" part="toolbar" role="toolbar" aria-label="Formatação" aria-orientation="horizontal"></div>
6817
+ <div class="body">
6818
+ <div class="content" part="content" contenteditable="true" role="textbox"
6819
+ aria-multiline="true" spellcheck="true"></div>
6820
+ <textarea class="source" part="source" spellcheck="false" hidden aria-label="HTML do conteúdo"></textarea>
6821
+ <div class="media-overlay" part="media-overlay" hidden>
6822
+ <span class="media-handle nw" data-handle="nw"></span>
6823
+ <span class="media-handle ne" data-handle="ne"></span>
6824
+ <span class="media-handle sw" data-handle="sw"></span>
6825
+ <span class="media-handle se" data-handle="se"></span>
6826
+ <span class="media-size"></span>
6827
+ <button type="button" class="media-delete" data-media-action="delete" aria-label="Excluir mídia" title="Excluir mídia">${fenixIconHtml("delete")}</button>
6828
+ </div>
6829
+ <div class="link-overlay" part="link-overlay" hidden>
6830
+ <button type="button" data-link-action="open">Abrir</button>
6831
+ <button type="button" data-link-action="edit">Editar</button>
6832
+ <button type="button" data-link-action="remove">Remover</button>
6833
+ </div>
6834
+ </div>
6835
+ <div class="statusbar" part="statusbar">
6836
+ <span class="count"></span>
6837
+ <span class="source-actions" hidden>
6838
+ <button type="button" class="btn source-apply">Aplicar</button>
6839
+ <button type="button" class="btn source-cancel">Cancelar</button>
6840
+ </span>
6841
+ <slot name="footer"></slot>
6842
+ </div>
6843
+ <input type="file" class="image-picker" accept="image/*" hidden>
6844
+ <div class="backdrop" part="dialog" hidden>
6845
+ <div class="dialog" role="dialog" aria-modal="true" aria-labelledby="fx-editor-dialog-title">
6846
+ <header>
6847
+ <h3 id="fx-editor-dialog-title"></h3>
6848
+ <button type="button" class="dialog-close" aria-label="Fechar">×</button>
6849
+ </header>
6850
+ <div class="dialog-body"></div>
6851
+ <footer>
6852
+ <button type="button" class="btn dialog-cancel">Cancelar</button>
6853
+ <button type="button" class="btn dialog-ok">Aplicar</button>
6854
+ </footer>
6855
+ </div>
6856
+ </div>
6857
+ </div>
6858
+ `);
6859
+ __privateSet(this, _built, true);
6860
+ __privateSet(this, _imagePicker, this.root.querySelector(".image-picker"));
6861
+ __privateMethod(this, _FxEditor_instances, bindEvents_fn).call(this);
6862
+ __privateSet(this, _eventsBound, true);
6863
+ __privateMethod(this, _FxEditor_instances, renderToolbar_fn).call(this);
6864
+ __privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
6865
+ }
6866
+ /* ---------------------------------------------------------------------
6867
+ * API pública
6868
+ * ------------------------------------------------------------------ */
6869
+ focus(options) {
6870
+ this.editor?.focus(options ?? { preventScroll: true });
6871
+ }
6872
+ blur() {
6873
+ this.editor?.blur();
6874
+ }
6875
+ execute(command, value) {
6876
+ if (value !== void 0) {
6877
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
6878
+ nativeExec("styleWithCSS", "true");
6879
+ nativeExec(command, value);
6880
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
6881
+ return;
6882
+ }
6883
+ __privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, command);
6884
+ }
6885
+ insertHTML(html) {
6886
+ const clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, html);
6887
+ const probe = document.createElement("div");
6888
+ probe.innerHTML = clean;
6889
+ const extra = probe.textContent?.length ?? 0;
6890
+ if (!__privateMethod(this, _FxEditor_instances, fits_fn).call(this, extra)) {
6891
+ __privateMethod(this, _FxEditor_instances, limitError_fn).call(this);
6892
+ return;
6893
+ }
6894
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
6895
+ insertHtml(this.editor, clean);
6896
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
6897
+ }
6898
+ /** Mescla as células tocadas pela seleção. */
6899
+ mergeCells() {
6900
+ const table = __privateMethod(this, _FxEditor_instances, currentTable_fn).call(this);
6901
+ const editor = this.editor;
6902
+ const range2 = editor ? getRange(editor, __privateGet(this, _lastRange)) : null;
6903
+ if (!table || !range2) return false;
6904
+ const merged = mergeSelectedCells(table, range2);
6905
+ if (!merged) return false;
6906
+ placeCaretAtEnd(merged);
6907
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
6908
+ return true;
6909
+ }
6910
+ /** Divide a célula mesclada atual. */
6911
+ splitCell() {
6912
+ const table = __privateMethod(this, _FxEditor_instances, currentTable_fn).call(this);
6913
+ const editor = this.editor;
6914
+ const range2 = editor ? getRange(editor, __privateGet(this, _lastRange)) : null;
6915
+ const cell = editor ? closestInEditor(editor, range2?.startContainer ?? null, "td,th") : null;
6916
+ if (!table || !cell) return false;
6917
+ const created = splitCell(table, cell);
6918
+ if (!created.length) return false;
6919
+ placeCaretAtEnd(cell);
6920
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
6921
+ return true;
6922
+ }
6923
+ getHtml() {
6924
+ return this.value;
6925
+ }
6926
+ setHtml(html) {
6927
+ this.value = html;
6928
+ }
6929
+ getText() {
6930
+ return this.text;
6931
+ }
6932
+ clear() {
6933
+ this.value = "";
6934
+ __privateSet(this, _dirty, false);
6935
+ __privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, "");
6936
+ this.updateFormValue();
6937
+ __privateMethod(this, _FxEditor_instances, emitChange_fn).call(this);
6938
+ this.focus();
6939
+ }
6940
+ undo() {
6941
+ this.execute("undo");
6942
+ }
6943
+ redo() {
6944
+ this.execute("redo");
6945
+ }
6946
+ saveSelection() {
6947
+ if (this.editor) __privateSet(this, _lastRange, saveRange(this.editor) ?? __privateGet(this, _lastRange));
6948
+ }
6949
+ restoreSelection() {
6950
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
6951
+ }
6952
+ openDialog(kind) {
6953
+ if (kind === "link") void __privateMethod(this, _FxEditor_instances, openLinkDialog_fn).call(this);
6954
+ else if (kind === "image") void __privateMethod(this, _FxEditor_instances, openImageDialog_fn).call(this);
6955
+ else void __privateMethod(this, _FxEditor_instances, openVideoDialog_fn).call(this);
6956
+ }
6957
+ toggleSource() {
6958
+ __privateMethod(this, _FxEditor_instances, toggleSource_fn).call(this);
6959
+ }
6960
+ toggleFullscreen() {
6961
+ this.fullscreen = !this.fullscreen;
6962
+ }
6963
+ };
6964
+ _built = new WeakMap();
6965
+ _eventsBound = new WeakMap();
6966
+ _initialized = new WeakMap();
6967
+ _defaultValue = new WeakMap();
6968
+ _value = new WeakMap();
6969
+ _dirty = new WeakMap();
6970
+ _sourceMode = new WeakMap();
6971
+ _lastRange = new WeakMap();
6972
+ _rafId = new WeakMap();
6973
+ _emitTimer = new WeakMap();
6974
+ _lastLimitError = new WeakMap();
6975
+ _snapshotTimer = new WeakMap();
6976
+ _history = new WeakMap();
6977
+ _historyIndex = new WeakMap();
6978
+ _imagePicker = new WeakMap();
6979
+ _mediaFigure = new WeakMap();
6980
+ _linkAnchor = new WeakMap();
6981
+ _resizeDispose = new WeakMap();
6982
+ _dialogReturnFocus = new WeakMap();
6983
+ _dialogResolve = new WeakMap();
6984
+ _disposers = new WeakMap();
6985
+ _selectionRaf = new WeakMap();
6986
+ _bodyOverflow = new WeakMap();
6987
+ _FxEditor_instances = new WeakSet();
6988
+ on_fn = function(target, type, handler, options) {
6989
+ target.addEventListener(type, handler, options);
6990
+ __privateGet(this, _disposers).push(() => target.removeEventListener(type, handler, options));
6991
+ };
6992
+ bindEvents_fn = function() {
6993
+ const editor = this.editor;
6994
+ const toolbar = this.toolbarEl;
6995
+ if (!editor || !toolbar) return;
6996
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "mousedown", (event) => {
6997
+ const target = event.target;
6998
+ __privateSet(this, _lastRange, saveRange(editor) ?? __privateGet(this, _lastRange));
6999
+ __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
7000
+ if (target.closest("button")) event.preventDefault();
7001
+ });
7002
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "click", (event) => {
7003
+ const target = event.target.closest("[data-cmd],[data-action]");
7004
+ if (!target || target.disabled) return;
7005
+ const cmd = target.dataset.cmd;
7006
+ const action = target.dataset.action;
7007
+ if (cmd) __privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, cmd);
7008
+ else if (action) __privateMethod(this, _FxEditor_instances, action_fn).call(this, action);
7009
+ });
7010
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "change", (event) => {
7011
+ const select = event.target.closest("[data-block]");
7012
+ if (!select) return;
7013
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7014
+ formatBlock(editor, select.value || "p");
7015
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7016
+ });
7017
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "input", (event) => {
7018
+ const input = event.target.closest("[data-color]");
7019
+ if (!input) return;
7020
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7021
+ nativeExec("styleWithCSS", "true");
7022
+ nativeExec(input.dataset.color ?? "foreColor", input.value);
7023
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7024
+ });
7025
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "keydown", (event) => __privateMethod(this, _FxEditor_instances, handleToolbarKeydown_fn).call(this, event));
7026
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "focusin", (event) => {
7027
+ const button = event.target.closest(".tb-btn");
7028
+ if (button) __privateMethod(this, _FxEditor_instances, moveToolbarTabStop_fn).call(this, button);
7029
+ });
7030
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "input", (event) => {
7031
+ event.stopPropagation();
7032
+ __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
7033
+ __privateSet(this, _dirty, true);
7034
+ __privateMethod(this, _FxEditor_instances, updateEmpty_fn).call(this);
7035
+ __privateMethod(this, _FxEditor_instances, scheduleEmit_fn).call(this);
7036
+ __privateMethod(this, _FxEditor_instances, scheduleSnapshot_fn).call(this);
7037
+ });
7038
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "focus", () => {
7039
+ this.dispatchEvent(new CustomEvent("focus", { bubbles: true, composed: true }));
7040
+ });
7041
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "blur", () => {
7042
+ __privateSet(this, _lastRange, saveRange(editor) ?? __privateGet(this, _lastRange));
7043
+ __privateMethod(this, _FxEditor_instances, emitNow_fn).call(this);
7044
+ __privateMethod(this, _FxEditor_instances, emitChange_fn).call(this);
7045
+ this.dispatchEvent(new CustomEvent("blur", { bubbles: true, composed: true }));
7046
+ });
7047
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "keydown", (event) => __privateMethod(this, _FxEditor_instances, handleKeydown_fn).call(this, event));
7048
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "beforeinput", (event) => __privateMethod(this, _FxEditor_instances, handleBeforeInput_fn).call(this, event));
7049
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "paste", (event) => __privateMethod(this, _FxEditor_instances, handlePaste_fn).call(this, event));
7050
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "dragover", (event) => event.preventDefault());
7051
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "drop", (event) => __privateMethod(this, _FxEditor_instances, handleDrop_fn).call(this, event));
7052
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "dblclick", (event) => __privateMethod(this, _FxEditor_instances, handleDoubleClick_fn).call(this, event));
7053
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "click", (event) => __privateMethod(this, _FxEditor_instances, handleEditorClick_fn).call(this, event));
7054
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "scroll", () => {
7055
+ __privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
7056
+ __privateMethod(this, _FxEditor_instances, positionLinkOverlay_fn).call(this);
7057
+ }, { passive: true });
7058
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, window, "resize", () => {
7059
+ __privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
7060
+ __privateMethod(this, _FxEditor_instances, positionLinkOverlay_fn).call(this);
7061
+ });
7062
+ const overlay = this.root.querySelector(".media-overlay");
7063
+ if (overlay) {
7064
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, overlay, "pointerdown", (event) => __privateMethod(this, _FxEditor_instances, startResize_fn).call(this, event));
7065
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, overlay, "click", (event) => {
7066
+ const action = event.target.closest("[data-media-action]");
7067
+ if (action?.dataset.mediaAction === "delete") __privateMethod(this, _FxEditor_instances, deleteMediaFigure_fn).call(this);
7068
+ });
7069
+ }
7070
+ const linkOverlay = this.root.querySelector(".link-overlay");
7071
+ if (linkOverlay) {
7072
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, linkOverlay, "click", (event) => __privateMethod(this, _FxEditor_instances, handleLinkOverlayClick_fn).call(this, event));
7073
+ }
7074
+ if (this.source) {
7075
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, this.source, "input", () => {
7076
+ __privateSet(this, _dirty, true);
7077
+ __privateMethod(this, _FxEditor_instances, scheduleEmit_fn).call(this);
7078
+ });
7079
+ }
7080
+ if (__privateGet(this, _imagePicker)) {
7081
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, __privateGet(this, _imagePicker), "change", () => {
7082
+ const file = __privateGet(this, _imagePicker)?.files?.[0];
7083
+ if (file) void __privateMethod(this, _FxEditor_instances, handleImageFile_fn).call(this, file);
7084
+ if (__privateGet(this, _imagePicker)) __privateGet(this, _imagePicker).value = "";
7085
+ });
7086
+ }
7087
+ const backdrop = this.root.querySelector(".backdrop");
7088
+ if (backdrop) {
7089
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, backdrop, "click", (event) => {
7090
+ if (event.target === backdrop) __privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null);
7091
+ });
7092
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, backdrop, "keydown", (event) => __privateMethod(this, _FxEditor_instances, trapDialogFocus_fn).call(this, event));
7093
+ }
7094
+ const dialogBody = this.root.querySelector(".dialog-body");
7095
+ if (dialogBody) {
7096
+ __privateMethod(this, _FxEditor_instances, on_fn).call(this, dialogBody, "keydown", (event) => {
7097
+ const target = event.target;
7098
+ if (event.key === "Enter" && target.tagName !== "BUTTON") {
7099
+ event.preventDefault();
7100
+ __privateMethod(this, _FxEditor_instances, submitDialog_fn).call(this);
7101
+ }
7102
+ });
7103
+ }
7104
+ for (const selector of [".dialog-close", ".dialog-cancel"]) {
7105
+ const btn = this.root.querySelector(selector);
7106
+ if (btn) __privateMethod(this, _FxEditor_instances, on_fn).call(this, btn, "click", () => __privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null));
7107
+ }
7108
+ const ok = this.root.querySelector(".dialog-ok");
7109
+ if (ok) __privateMethod(this, _FxEditor_instances, on_fn).call(this, ok, "click", () => __privateMethod(this, _FxEditor_instances, submitDialog_fn).call(this));
7110
+ const applySource = this.root.querySelector(".source-apply");
7111
+ if (applySource) __privateMethod(this, _FxEditor_instances, on_fn).call(this, applySource, "click", () => __privateMethod(this, _FxEditor_instances, applySource_fn).call(this));
7112
+ const cancelSource = this.root.querySelector(".source-cancel");
7113
+ if (cancelSource) __privateMethod(this, _FxEditor_instances, on_fn).call(this, cancelSource, "click", () => __privateMethod(this, _FxEditor_instances, cancelSource_fn).call(this));
7114
+ };
7115
+ syncChrome_fn = function() {
7116
+ const editor = this.editor;
7117
+ if (!editor) return;
7118
+ const locked = this.disabled || this.readonly;
7119
+ editor.setAttribute("contenteditable", locked ? "false" : "true");
7120
+ editor.setAttribute("aria-readonly", String(this.readonly));
7121
+ editor.setAttribute("aria-disabled", String(this.disabled));
7122
+ if (this.placeholder) editor.setAttribute("data-placeholder", this.placeholder);
7123
+ else editor.removeAttribute("data-placeholder");
7124
+ const toolbar = this.toolbarEl;
7125
+ toolbar?.toggleAttribute("hidden", this.toolbar === "none" || this.readonly);
7126
+ for (const control of Array.from(this.root.querySelectorAll(
7127
+ ".tb-btn, .tb-select, .tb-color input"
7128
+ ))) {
7129
+ const isSourceToggle = control.closest('[data-action="source"]') !== null;
7130
+ const isFullscreenToggle = control.closest('[data-action="fullscreen"]') !== null;
7131
+ control.disabled = this.readonly || this.disabled || __privateGet(this, _sourceMode) && !isSourceToggle && !isFullscreenToggle;
7132
+ }
7133
+ __privateMethod(this, _FxEditor_instances, applyCssVars_fn).call(this);
7134
+ __privateMethod(this, _FxEditor_instances, updateEmpty_fn).call(this);
7135
+ __privateMethod(this, _FxEditor_instances, updateCount_fn).call(this);
7136
+ __privateMethod(this, _FxEditor_instances, validate_fn).call(this);
7137
+ __privateMethod(this, _FxEditor_instances, updateSourceUi_fn).call(this);
7138
+ const fullscreenButton = this.root.querySelector('[data-action="fullscreen"]');
7139
+ fullscreenButton?.classList.toggle("active", this.fullscreen);
7140
+ fullscreenButton?.setAttribute("aria-pressed", String(this.fullscreen));
7141
+ if (locked || __privateGet(this, _sourceMode)) __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
7142
+ };
7143
+ applyCssVars_fn = function() {
7144
+ const min = this.getAttr("min-height");
7145
+ const max = this.getAttr("max-height");
7146
+ if (min) this.style.setProperty("--fx-editor-min-height", min);
7147
+ else this.style.removeProperty("--fx-editor-min-height");
7148
+ if (max) this.style.setProperty("--fx-editor-max-height", max);
7149
+ else this.style.removeProperty("--fx-editor-max-height");
7150
+ };
7151
+ updateEmpty_fn = function() {
7152
+ const editor = this.editor;
7153
+ if (!editor) return;
7154
+ const empty = !editor.textContent?.trim() && !editor.querySelector("img,video,iframe,table,hr");
7155
+ editor.classList.toggle("is-empty", empty);
7156
+ };
7157
+ updateCount_fn = function() {
7158
+ const count = this.root.querySelector(".count");
7159
+ if (!count) return;
7160
+ const statusbar = this.root.querySelector(".statusbar");
7161
+ const hasFooter = this.querySelector('[slot="footer"]') !== null;
7162
+ if (statusbar) statusbar.hidden = !__privateGet(this, _sourceMode) && !this.hasAttr("show-count") && !hasFooter;
7163
+ if (!this.hasAttr("show-count")) {
7164
+ count.textContent = "";
7165
+ return;
7166
+ }
7167
+ const text = this.text;
7168
+ const words = text ? text.split(/\s+/).filter(Boolean).length : 0;
7169
+ count.textContent = `${words} palavra${words === 1 ? "" : "s"} · ${text.length} caractere${text.length === 1 ? "" : "s"}`;
7170
+ };
7171
+ fits_fn = function(extra) {
7172
+ const max = this.maxlength;
7173
+ if (!max) return true;
7174
+ const editor = this.editor;
7175
+ const range2 = editor ? getRange(editor, __privateGet(this, _lastRange)) : null;
7176
+ const selected = range2 && !range2.collapsed ? range2.toString().length : 0;
7177
+ return this.text.length - selected + extra <= max;
7178
+ };
7179
+ limitError_fn = function() {
7180
+ const now = Date.now();
7181
+ if (now - __privateGet(this, _lastLimitError) < 1200) return;
7182
+ __privateSet(this, _lastLimitError, now);
7183
+ __privateMethod(this, _FxEditor_instances, error_fn).call(this, `Limite de ${this.maxlength.toLocaleString("pt-BR")} caracteres atingido.`);
7184
+ };
7185
+ validate_fn = function() {
7186
+ if (!this.internals || typeof this.internals.setValidity !== "function") return;
7187
+ try {
7188
+ if (this.required && !this.text) {
7189
+ this.internals.setValidity(
7190
+ { valueMissing: true },
7191
+ "Preencha este campo.",
7192
+ this.editor ?? void 0
7193
+ );
7194
+ } else {
7195
+ this.internals.setValidity({});
7196
+ }
7197
+ } catch {
7198
+ }
7199
+ };
7200
+ /** Sanitiza + normaliza em uma única parseada do HTML. */
7201
+ cleanHtml_fn = function(html, forEditor = false) {
7202
+ const body = sanitizeBody(html);
7203
+ if (!body) return forEditor ? "<p><br></p>" : "";
7204
+ return normalizeElement(body, { forEditor });
7205
+ };
7206
+ serialize_fn = function() {
7207
+ if (__privateGet(this, _sourceMode) && this.source) {
7208
+ return __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, this.source.value);
7209
+ }
7210
+ const editor = this.editor;
7211
+ if (!editor) return "";
7212
+ if (!editor.textContent?.trim() && !editor.querySelector("img,video,iframe,table,hr")) return "";
7213
+ let clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, editor.innerHTML);
7214
+ if (this.sanitizeHtml) {
7215
+ clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, this.sanitizeHtml(clean));
7216
+ }
7217
+ return clean;
7218
+ };
7219
+ applyValue_fn = function(html) {
7220
+ const editor = this.editor;
7221
+ if (!editor) return;
7222
+ __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
7223
+ const normalized = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, html, true);
7224
+ if (editor.innerHTML !== normalized) editor.innerHTML = normalized;
7225
+ __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
7226
+ __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
7227
+ __privateMethod(this, _FxEditor_instances, decorateMediaFigures_fn).call(this);
7228
+ __privateMethod(this, _FxEditor_instances, updateEmpty_fn).call(this);
7229
+ __privateMethod(this, _FxEditor_instances, updateCount_fn).call(this);
7230
+ };
7231
+ scheduleEmit_fn = function() {
7232
+ cancelAnimationFrame(__privateGet(this, _rafId));
7233
+ if (__privateGet(this, _emitTimer)) {
7234
+ clearTimeout(__privateGet(this, _emitTimer));
7235
+ __privateSet(this, _emitTimer, null);
7236
+ }
7237
+ const size = this.editor?.textContent?.length ?? 0;
7238
+ if (size < LARGE_DOC_CHARS) {
7239
+ __privateSet(this, _rafId, requestAnimationFrame(() => __privateMethod(this, _FxEditor_instances, emitNow_fn).call(this)));
7240
+ return;
7241
+ }
7242
+ __privateSet(this, _emitTimer, setTimeout(() => {
7243
+ __privateSet(this, _emitTimer, null);
7244
+ __privateMethod(this, _FxEditor_instances, emitNow_fn).call(this);
7245
+ }, EMIT_DEBOUNCE_MS));
7246
+ };
7247
+ emitNow_fn = function() {
7248
+ cancelAnimationFrame(__privateGet(this, _rafId));
7249
+ if (__privateGet(this, _emitTimer)) {
7250
+ clearTimeout(__privateGet(this, _emitTimer));
7251
+ __privateSet(this, _emitTimer, null);
7252
+ }
7253
+ const value = __privateMethod(this, _FxEditor_instances, serialize_fn).call(this);
7254
+ __privateSet(this, _value, value);
7255
+ __privateSet(this, _dirty, false);
7256
+ const detail = { value, html: value, text: this.text };
7257
+ for (const type of ["input", "valueChange"]) {
7258
+ this.dispatchEvent(new CustomEvent(type, { bubbles: true, composed: true, detail }));
7259
+ }
7260
+ __privateMethod(this, _FxEditor_instances, updateCount_fn).call(this);
7261
+ __privateMethod(this, _FxEditor_instances, validate_fn).call(this);
7262
+ };
7263
+ emitChange_fn = function() {
7264
+ const value = this.value;
7265
+ const detail = { value, html: value, text: this.text };
7266
+ this.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail }));
7267
+ };
7268
+ pushHistory_fn = function(html) {
7269
+ if (__privateGet(this, _history)[__privateGet(this, _historyIndex)] === html) return;
7270
+ __privateSet(this, _history, __privateGet(this, _history).slice(0, __privateGet(this, _historyIndex) + 1));
7271
+ __privateGet(this, _history).push(html);
7272
+ if (__privateGet(this, _history).length > HISTORY_LIMIT) __privateGet(this, _history).shift();
7273
+ __privateSet(this, _historyIndex, __privateGet(this, _history).length - 1);
7274
+ };
7275
+ scheduleSnapshot_fn = function() {
7276
+ if (__privateGet(this, _snapshotTimer)) clearTimeout(__privateGet(this, _snapshotTimer));
7277
+ __privateSet(this, _snapshotTimer, setTimeout(() => {
7278
+ __privateMethod(this, _FxEditor_instances, pushHistory_fn).call(this, __privateGet(this, _dirty) ? __privateMethod(this, _FxEditor_instances, serialize_fn).call(this) : __privateGet(this, _value));
7279
+ }, 600));
7280
+ };
7281
+ historyUndo_fn = function() {
7282
+ if (__privateGet(this, _historyIndex) <= 0) return;
7283
+ __privateWrapper(this, _historyIndex)._--;
7284
+ __privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, __privateGet(this, _history)[__privateGet(this, _historyIndex)]);
7285
+ if (this.editor) placeCaretAtEnd(this.editor);
7286
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7287
+ };
7288
+ historyRedo_fn = function() {
7289
+ if (__privateGet(this, _historyIndex) >= __privateGet(this, _history).length - 1) return;
7290
+ __privateWrapper(this, _historyIndex)._++;
7291
+ __privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, __privateGet(this, _history)[__privateGet(this, _historyIndex)]);
7292
+ if (this.editor) placeCaretAtEnd(this.editor);
7293
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7294
+ };
7295
+ bindGlobalSelection_fn = function() {
7296
+ const handler = () => {
7297
+ const editor = this.editor;
7298
+ if (!editor) return;
7299
+ const range2 = getRange(editor, __privateGet(this, _lastRange));
7300
+ if (!range2) return;
7301
+ __privateSet(this, _lastRange, range2.cloneRange());
7302
+ cancelAnimationFrame(__privateGet(this, _selectionRaf));
7303
+ __privateSet(this, _selectionRaf, requestAnimationFrame(() => __privateMethod(this, _FxEditor_instances, updateToolbar_fn).call(this)));
7304
+ };
7305
+ document.addEventListener("selectionchange", handler);
7306
+ __privateGet(this, _disposers).push(() => document.removeEventListener("selectionchange", handler));
7307
+ };
7308
+ restoreSelection_fn = function() {
7309
+ const editor = this.editor;
7310
+ if (!editor) return;
7311
+ if (__privateGet(this, _lastRange)) restoreRange(editor, __privateGet(this, _lastRange));
7312
+ else editor.focus({ preventScroll: true });
7313
+ };
7314
+ syncBodyOverflow_fn = function() {
7315
+ if (this.fullscreen) {
7316
+ __privateSet(this, _bodyOverflow, document.body.style.overflow);
7317
+ document.body.style.overflow = "hidden";
7318
+ } else {
7319
+ document.body.style.overflow = __privateGet(this, _bodyOverflow);
7320
+ }
7321
+ };
7322
+ updateSourceUi_fn = function() {
7323
+ const source = this.source;
7324
+ const content = this.editor;
7325
+ const actions = this.root.querySelector(".source-actions");
7326
+ if (actions) actions.toggleAttribute("hidden", !__privateGet(this, _sourceMode));
7327
+ if (source) source.hidden = !__privateGet(this, _sourceMode);
7328
+ if (content) content.hidden = __privateGet(this, _sourceMode);
7329
+ const toggle = this.root.querySelector('[data-action="source"]');
7330
+ toggle?.classList.toggle("active", __privateGet(this, _sourceMode));
7331
+ toggle?.setAttribute("aria-pressed", String(__privateGet(this, _sourceMode)));
7332
+ };
7333
+ releaseDialog_fn = function(result) {
7334
+ const backdrop = this.root.querySelector(".backdrop");
7335
+ backdrop?.setAttribute("hidden", "");
7336
+ const resolve = __privateGet(this, _dialogResolve);
7337
+ __privateSet(this, _dialogResolve, null);
7338
+ const returnFocus = __privateGet(this, _dialogReturnFocus);
7339
+ __privateSet(this, _dialogReturnFocus, null);
7340
+ resolve?.(result);
7341
+ if (returnFocus && returnFocus.isConnected) returnFocus.focus({ preventScroll: true });
7342
+ else __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7343
+ };
7344
+ renderToolbar_fn = function() {
7345
+ const toolbar = this.toolbarEl;
7346
+ if (!toolbar) return;
7347
+ const preset = this.toolbar;
7348
+ if (preset === "none") {
7349
+ toolbar.innerHTML = "";
7350
+ toolbar.hidden = true;
7351
+ return;
7352
+ }
7353
+ toolbar.hidden = false;
7354
+ const features = new Set(
7355
+ this.getAttr("features").split(",").map((item) => item.trim()).filter(Boolean)
7356
+ );
7357
+ const has = (feature) => features.size === 0 || features.has(feature);
7358
+ const button = (cmd, icon, label, action = "") => `<button type="button" class="tb-btn" tabindex="-1" ${action ? `data-action="${action}"` : `data-cmd="${cmd}"`} title="${label}" aria-label="${label}" aria-pressed="false">${fenixIconHtml(icon)}</button>`;
7359
+ const select = `<select class="tb-select" data-block aria-label="Formato do bloco">
7360
+ <option value="p">Parágrafo</option>
7361
+ <option value="h1">Título 1</option>
7362
+ <option value="h2">Título 2</option>
7363
+ <option value="h3">Título 3</option>
7364
+ <option value="h4">Título 4</option>
7365
+ <option value="blockquote">Citação</option>
7366
+ <option value="pre">Código</option>
7367
+ </select>`;
7368
+ const history = button("undo", "undo", "Desfazer") + button("redo", "redo", "Refazer");
7369
+ const inline = [
7370
+ button("bold", "format_bold", "Negrito"),
7371
+ button("italic", "format_italic", "Itálico"),
7372
+ button("underline", "format_underlined", "Sublinhado"),
7373
+ button("strikeThrough", "format_strikethrough", "Riscado"),
7374
+ button("code", "code", "Código inline"),
7375
+ button("removeFormat", "format_clear", "Limpar formatação")
7376
+ ].join("");
7377
+ const color = [
7378
+ `<label class="tb-color" title="Cor do texto"><input type="color" data-color="foreColor" value="#111827" aria-label="Cor do texto"></label>`,
7379
+ `<label class="tb-color" title="Realçar texto"><input type="color" data-color="hiliteColor" value="#fef08a" aria-label="Realçar texto"></label>`
7380
+ ].join("");
7381
+ const alignGroup = [
7382
+ button("justifyLeft", "format_align_left", "Alinhar à esquerda"),
7383
+ button("justifyCenter", "format_align_center", "Centralizar"),
7384
+ button("justifyRight", "format_align_right", "Alinhar à direita"),
7385
+ button("justifyFull", "format_align_justify", "Justificar")
7386
+ ].join("");
7387
+ const indentation = [
7388
+ button("outdent", "format_indent_decrease", "Diminuir recuo", "outdent"),
7389
+ button("indent", "format_indent_increase", "Aumentar recuo", "indent")
7390
+ ].join("");
7391
+ const lists = [
7392
+ button("insertUnorderedList", "format_list_bulleted", "Lista com marcadores"),
7393
+ button("insertOrderedList", "format_list_numbered", "Lista numerada")
7394
+ ].join("");
7395
+ const blocks = button("", "format_quote", "Citação", "blockquote") + button("", "code_blocks", "Bloco de código", "codeblock");
7396
+ const insert = [];
7397
+ if (has("link")) {
7398
+ insert.push(button("", "link", "Link (Ctrl+K)", "link"));
7399
+ insert.push(button("", "link_off", "Remover link", "unlink"));
7400
+ }
7401
+ if (preset !== "minimal") {
7402
+ if (has("image")) insert.push(button("", "image", "Imagem", "image"));
7403
+ if (has("video")) insert.push(button("", "videocam", "Vídeo", "video"));
7404
+ if (has("table")) insert.push(button("", "table", "Tabela", "table"));
7405
+ if (has("columns")) insert.push(button("", "view_column", "Colunas", "columns"));
7406
+ insert.push(button("insertHorizontalRule", "horizontal_rule", "Linha horizontal"));
7407
+ }
7408
+ const tools = [];
7409
+ if (preset === "standard" || preset === "full") {
7410
+ if (has("source")) tools.push(button("", "code", "Ver HTML", "source"));
7411
+ if (has("fullscreen")) tools.push(button("", "fullscreen", "Tela cheia", "fullscreen"));
7412
+ }
7413
+ const groups = [history, select + inline];
7414
+ if (preset !== "minimal") groups.push(lists);
7415
+ if (preset !== "minimal" && preset !== "basic") {
7416
+ groups.push(color, alignGroup, indentation, blocks);
7417
+ }
7418
+ if (insert.length) groups.push(insert.join(""));
7419
+ if (tools.length) groups.push(tools.join(""));
7420
+ toolbar.innerHTML = groups.map((group) => `<div class="tb-group">${group}</div>`).join("") + `<div class="panel" data-panel hidden></div>`;
7421
+ toolbar.querySelector(".tb-btn")?.setAttribute("tabindex", "0");
7422
+ };
7423
+ updateToolbar_fn = function() {
7424
+ const editor = this.editor;
7425
+ if (!editor) return;
7426
+ const range2 = getRange(editor, __privateGet(this, _lastRange));
7427
+ if (!range2) return;
7428
+ const setActive = (cmd, active) => {
7429
+ const button = this.root.querySelector(`[data-cmd="${cmd}"]`);
7430
+ if (!button) return;
7431
+ button.classList.toggle("active", active);
7432
+ button.setAttribute("aria-pressed", String(active));
7433
+ };
7434
+ const ancestor = (selector) => {
7435
+ const node = range2.startContainer;
7436
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
7437
+ const match = el?.closest(selector);
7438
+ return Boolean(match && editor.contains(match));
7439
+ };
7440
+ setActive("bold", ancestor("strong,b"));
7441
+ setActive("italic", ancestor("em,i"));
7442
+ setActive("underline", ancestor("u"));
7443
+ setActive("strikeThrough", ancestor("s,del,strike"));
7444
+ setActive("code", ancestor("code"));
7445
+ setActive("insertUnorderedList", ancestor("ul"));
7446
+ setActive("insertOrderedList", ancestor("ol"));
7447
+ const block = getBlock(editor, range2);
7448
+ const blockTag = block?.tagName.toLowerCase() ?? "p";
7449
+ const blockSelect = this.root.querySelector("[data-block]");
7450
+ if (blockSelect) {
7451
+ const value = Array.from(blockSelect.options).some((option) => option.value === blockTag) ? blockTag : "p";
7452
+ blockSelect.value = value;
7453
+ }
7454
+ const alignment = block ? block.style.textAlign || "left" : "left";
7455
+ setActive("justifyLeft", alignment === "left" || alignment === "start" || alignment === "");
7456
+ setActive("justifyCenter", alignment === "center");
7457
+ setActive("justifyRight", alignment === "right" || alignment === "end");
7458
+ setActive("justifyFull", alignment === "justify");
7459
+ };
7460
+ handleToolbarKeydown_fn = function(event) {
7461
+ const active = this.root.activeElement;
7462
+ if (!active || !active.classList.contains("tb-btn")) return;
7463
+ if (!["ArrowRight", "ArrowLeft", "Home", "End"].includes(event.key)) return;
7464
+ const controls = Array.from(
7465
+ this.toolbarEl?.querySelectorAll(
7466
+ ".tb-btn:not(:disabled), .tb-select:not(:disabled), .tb-color input:not(:disabled)"
7467
+ ) ?? []
7468
+ );
7469
+ if (!controls.length) return;
7470
+ event.preventDefault();
7471
+ let index = controls.indexOf(active);
7472
+ if (event.key === "Home") index = 0;
7473
+ else if (event.key === "End") index = controls.length - 1;
7474
+ else index = (index + (event.key === "ArrowRight" ? 1 : -1) + controls.length) % controls.length;
7475
+ const next = controls[index];
7476
+ if (!next) return;
7477
+ __privateMethod(this, _FxEditor_instances, moveToolbarTabStop_fn).call(this, next);
7478
+ next.focus();
7479
+ };
7480
+ moveToolbarTabStop_fn = function(active) {
7481
+ this.toolbarEl?.querySelectorAll(".tb-btn").forEach((button) => {
7482
+ button.setAttribute("tabindex", button === active ? "0" : "-1");
7483
+ });
7484
+ };
7485
+ trapDialogFocus_fn = function(event) {
7486
+ if (!__privateGet(this, _dialogResolve) || event.key !== "Tab") return;
7487
+ const dialog = this.root.querySelector(".dialog");
7488
+ if (!dialog) return;
7489
+ const focusables = Array.from(
7490
+ dialog.querySelectorAll(
7491
+ 'button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])'
7492
+ )
7493
+ );
7494
+ if (!focusables.length) return;
7495
+ const first = focusables[0];
7496
+ const last = focusables[focusables.length - 1];
7497
+ const active = this.root.activeElement;
7498
+ if (event.shiftKey && (active === first || !active || !dialog.contains(active))) {
7499
+ event.preventDefault();
7500
+ last.focus();
7501
+ } else if (!event.shiftKey && (active === last || !active || !dialog.contains(active))) {
7502
+ event.preventDefault();
7503
+ first.focus();
7504
+ }
7505
+ };
7506
+ execCommand_fn = function(cmd) {
7507
+ const editor = this.editor;
7508
+ if (!editor) return;
7509
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7510
+ switch (cmd) {
7511
+ case "undo":
7512
+ if (!nativeExec("undo")) __privateMethod(this, _FxEditor_instances, historyUndo_fn).call(this);
7513
+ break;
7514
+ case "redo":
7515
+ if (!nativeExec("redo")) __privateMethod(this, _FxEditor_instances, historyRedo_fn).call(this);
7516
+ break;
7517
+ case "bold":
7518
+ toggleInline(editor, "strong");
7519
+ break;
7520
+ case "italic":
7521
+ toggleInline(editor, "em");
7522
+ break;
7523
+ case "underline":
7524
+ toggleInline(editor, "u");
7525
+ break;
7526
+ case "strikeThrough":
7527
+ toggleInline(editor, "s");
7528
+ break;
7529
+ case "code":
7530
+ toggleInline(editor, "code");
7531
+ break;
7532
+ case "justifyLeft":
7533
+ align(editor, "left");
7534
+ break;
7535
+ case "justifyCenter":
7536
+ align(editor, "center");
7537
+ break;
7538
+ case "justifyRight":
7539
+ align(editor, "right");
7540
+ break;
7541
+ case "justifyFull":
7542
+ align(editor, "justify");
7543
+ break;
7544
+ case "insertUnorderedList":
7545
+ toggleList(editor, false);
7546
+ break;
7547
+ case "insertOrderedList":
7548
+ toggleList(editor, true);
7549
+ break;
7550
+ case "removeFormat":
7551
+ removeFormat(editor);
7552
+ break;
7553
+ case "insertHorizontalRule":
7554
+ insertHtml(editor, "<hr><p><br></p>");
7555
+ break;
7556
+ case "foreColor":
7557
+ case "hiliteColor":
7558
+ nativeExec("styleWithCSS", "true");
7559
+ nativeExec(cmd, this.root.querySelector(`[data-color="${cmd}"]`)?.value);
7560
+ break;
7561
+ default:
7562
+ nativeExec(cmd);
7563
+ }
7564
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7565
+ };
7566
+ afterCommand_fn = function() {
7567
+ __privateSet(this, _dirty, true);
7568
+ __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
7569
+ __privateMethod(this, _FxEditor_instances, decorateMediaFigures_fn).call(this);
7570
+ __privateMethod(this, _FxEditor_instances, updateEmpty_fn).call(this);
7571
+ __privateMethod(this, _FxEditor_instances, scheduleEmit_fn).call(this);
7572
+ __privateMethod(this, _FxEditor_instances, scheduleSnapshot_fn).call(this);
7573
+ __privateMethod(this, _FxEditor_instances, updateToolbar_fn).call(this);
7574
+ };
7575
+ action_fn = function(action) {
7576
+ const editor = this.editor;
7577
+ if (!editor) return;
7578
+ switch (action) {
7579
+ case "link":
7580
+ void __privateMethod(this, _FxEditor_instances, openLinkDialog_fn).call(this);
7581
+ break;
7582
+ case "image":
7583
+ void __privateMethod(this, _FxEditor_instances, openImageDialog_fn).call(this);
7584
+ break;
7585
+ case "video":
7586
+ void __privateMethod(this, _FxEditor_instances, openVideoDialog_fn).call(this);
7587
+ break;
7588
+ case "table":
7589
+ __privateMethod(this, _FxEditor_instances, showTablePanel_fn).call(this);
7590
+ break;
7591
+ case "columns":
7592
+ __privateMethod(this, _FxEditor_instances, showColumnsPanel_fn).call(this);
7593
+ break;
7594
+ case "source":
7595
+ __privateMethod(this, _FxEditor_instances, toggleSource_fn).call(this);
7596
+ break;
7597
+ case "fullscreen":
7598
+ this.fullscreen = !this.fullscreen;
7599
+ break;
7600
+ case "blockquote":
7601
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7602
+ formatBlock(editor, "blockquote");
7603
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7604
+ break;
7605
+ case "codeblock":
7606
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7607
+ formatBlock(editor, "pre");
7608
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7609
+ break;
7610
+ case "indent":
7611
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7612
+ indent(editor, 24);
7613
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7614
+ break;
7615
+ case "outdent":
7616
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7617
+ indent(editor, -24);
7618
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7619
+ break;
7620
+ case "clear":
7621
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7622
+ removeFormat(editor);
7623
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7624
+ break;
7625
+ case "unlink":
7626
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7627
+ unlink(editor);
7628
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7629
+ break;
7630
+ }
7631
+ };
7632
+ handleKeydown_fn = function(event) {
7633
+ if (event.isComposing) return;
7634
+ const active = this.root.activeElement;
7635
+ const overlay = this.root.querySelector(".media-overlay");
7636
+ if (__privateGet(this, _mediaFigure) && (event.key === "Delete" || event.key === "Backspace") && (active === __privateGet(this, _mediaFigure) || __privateGet(this, _mediaFigure).contains(active) || Boolean(overlay && active && overlay.contains(active)))) {
7637
+ event.preventDefault();
7638
+ __privateMethod(this, _FxEditor_instances, deleteMediaFigure_fn).call(this);
7639
+ return;
7640
+ }
7641
+ const mod = event.ctrlKey || event.metaKey;
7642
+ const key2 = event.key.toLowerCase();
7643
+ if (mod && !event.altKey) {
7644
+ if (key2 === "b") {
7645
+ event.preventDefault();
7646
+ __privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "bold");
7647
+ return;
7648
+ }
7649
+ if (key2 === "i") {
7650
+ event.preventDefault();
7651
+ __privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "italic");
7652
+ return;
7653
+ }
7654
+ if (key2 === "u") {
7655
+ event.preventDefault();
7656
+ __privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "underline");
7657
+ return;
7658
+ }
7659
+ if (key2 === "k") {
7660
+ event.preventDefault();
7661
+ void __privateMethod(this, _FxEditor_instances, openLinkDialog_fn).call(this);
7662
+ return;
7663
+ }
7664
+ if (key2 === "z" && !event.shiftKey) {
7665
+ event.preventDefault();
7666
+ __privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "undo");
7667
+ return;
7668
+ }
7669
+ if (key2 === "y" || key2 === "z" && event.shiftKey) {
7670
+ event.preventDefault();
7671
+ __privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "redo");
7672
+ return;
7673
+ }
7674
+ }
7675
+ if (event.key === "Escape") {
7676
+ if (__privateGet(this, _dialogResolve)) __privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null);
7677
+ else if (__privateGet(this, _sourceMode)) __privateMethod(this, _FxEditor_instances, cancelSource_fn).call(this);
7678
+ else if (this.fullscreen) this.fullscreen = false;
7679
+ else if (__privateGet(this, _linkAnchor)) __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
7680
+ else if (__privateGet(this, _mediaFigure)) __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
7681
+ else __privateMethod(this, _FxEditor_instances, hidePanel_fn).call(this);
7682
+ return;
7683
+ }
7684
+ if (event.key === "Tab" && __privateMethod(this, _FxEditor_instances, tabInTable_fn).call(this, event.shiftKey)) {
7685
+ event.preventDefault();
7686
+ }
7687
+ };
7688
+ tabInTable_fn = function(backwards) {
7689
+ const editor = this.editor;
7690
+ if (!editor) return false;
7691
+ const range2 = getRange(editor, __privateGet(this, _lastRange));
7692
+ if (!range2) return false;
7693
+ const cell = closestInEditor(editor, range2.startContainer, "td,th");
7694
+ const table = cell?.closest("table");
7695
+ if (!cell || !table) return false;
7696
+ const cells = Array.from(table.querySelectorAll("td,th"));
7697
+ let index = cells.indexOf(cell);
7698
+ if (index < 0) return false;
7699
+ if (backwards) {
7700
+ index = Math.max(0, index - 1);
7701
+ } else {
7702
+ index++;
7703
+ if (index >= cells.length) {
7704
+ const row = cell.parentElement;
7705
+ if (!row) return false;
7706
+ const clone = row.cloneNode(true);
7707
+ for (const cloned of Array.from(clone.querySelectorAll("td,th"))) {
7708
+ cloned.innerHTML = "<p><br></p>";
7709
+ }
7710
+ row.after(clone);
7711
+ cells.push(...Array.from(clone.querySelectorAll("td,th")));
7712
+ index = cells.indexOf(clone.querySelector("td,th"));
7713
+ }
7714
+ }
7715
+ const target = cells[index];
7716
+ if (!target) return false;
7717
+ const next = document.createRange();
7718
+ next.selectNodeContents(target);
7719
+ next.collapse(true);
7720
+ const selection = document.getSelection();
7721
+ selection?.removeAllRanges();
7722
+ selection?.addRange(next);
7723
+ return true;
7724
+ };
7725
+ handleBeforeInput_fn = function(event) {
7726
+ if (this.disabled || this.readonly || !this.maxlength) return;
7727
+ const type = event.inputType;
7728
+ if (type === "insertFromPaste" || type === "insertFromDrop" || type.startsWith("history") || type.startsWith("format")) {
7729
+ return;
7730
+ }
7731
+ const extra = event.data?.length ?? (type === "insertParagraph" || type === "insertLineBreak" ? 1 : 0);
7732
+ if (!extra) return;
7733
+ if (!__privateMethod(this, _FxEditor_instances, fits_fn).call(this, extra)) {
7734
+ event.preventDefault();
7735
+ __privateMethod(this, _FxEditor_instances, limitError_fn).call(this);
7736
+ }
7737
+ };
7738
+ handlePaste_fn = function(event) {
7739
+ const clipboard = event.clipboardData;
7740
+ if (!clipboard) return;
7741
+ const file = Array.from(clipboard.files ?? []).find((item) => item.type.startsWith("image/"));
7742
+ if (file) {
7743
+ event.preventDefault();
7744
+ void __privateMethod(this, _FxEditor_instances, handleImageFile_fn).call(this, file);
7745
+ return;
7746
+ }
7747
+ const html = clipboard.getData("text/html");
7748
+ const text = clipboard.getData("text/plain");
7749
+ if (html.length > MAX_PASTE_BYTES || text.length > MAX_PASTE_BYTES) return;
7750
+ event.preventDefault();
7751
+ if (!__privateMethod(this, _FxEditor_instances, fits_fn).call(this, text.length)) {
7752
+ __privateMethod(this, _FxEditor_instances, limitError_fn).call(this);
7753
+ return;
7754
+ }
7755
+ if (this.pasteMode === "plain" || !html) {
7756
+ __privateMethod(this, _FxEditor_instances, insertPlainText_fn).call(this, text);
7757
+ return;
7758
+ }
7759
+ const clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, html);
7760
+ if (!clean) {
7761
+ __privateMethod(this, _FxEditor_instances, insertPlainText_fn).call(this, text);
7762
+ return;
7763
+ }
7764
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7765
+ insertHtml(this.editor, clean);
7766
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7767
+ };
7768
+ handleDrop_fn = function(event) {
7769
+ const transfer = event.dataTransfer;
7770
+ if (!transfer) return;
7771
+ event.preventDefault();
7772
+ const file = Array.from(transfer.files ?? []).find((item) => item.type.startsWith("image/"));
7773
+ if (file) {
7774
+ void __privateMethod(this, _FxEditor_instances, handleImageFile_fn).call(this, file);
7775
+ return;
7776
+ }
7777
+ const html = transfer.getData("text/html");
7778
+ const text = transfer.getData("text/plain");
7779
+ if (!__privateMethod(this, _FxEditor_instances, fits_fn).call(this, text.length)) {
7780
+ __privateMethod(this, _FxEditor_instances, limitError_fn).call(this);
7781
+ return;
7782
+ }
7783
+ if (html) {
7784
+ insertHtml(this.editor, __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, html));
7785
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7786
+ } else if (text) {
7787
+ __privateMethod(this, _FxEditor_instances, insertPlainText_fn).call(this, text);
7788
+ }
7789
+ };
7790
+ insertPlainText_fn = function(text) {
7791
+ const paragraphs = text.replace(/\r\n/g, "\n").split(/\n{2,}/).map((block) => `<p>${escapeText(block).replace(/\n/g, "<br>")}</p>`).join("");
7792
+ insertHtml(this.editor, paragraphs || "<p><br></p>");
7793
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7794
+ };
7795
+ toggleSource_fn = function() {
7796
+ if (__privateGet(this, _sourceMode)) {
7797
+ __privateMethod(this, _FxEditor_instances, applySource_fn).call(this);
7798
+ return;
7799
+ }
7800
+ const source = this.source;
7801
+ if (!source) return;
7802
+ source.value = __privateMethod(this, _FxEditor_instances, serialize_fn).call(this);
7803
+ __privateSet(this, _sourceMode, true);
7804
+ __privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
7805
+ source.focus();
7806
+ };
7807
+ applySource_fn = function() {
7808
+ const source = this.source;
7809
+ if (!source) return;
7810
+ const clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, source.value);
7811
+ __privateSet(this, _sourceMode, false);
7812
+ __privateSet(this, _value, clean);
7813
+ __privateSet(this, _dirty, false);
7814
+ __privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, clean);
7815
+ __privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
7816
+ this.updateFormValue();
7817
+ __privateMethod(this, _FxEditor_instances, emitChange_fn).call(this);
7818
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7819
+ };
7820
+ cancelSource_fn = function() {
7821
+ __privateSet(this, _sourceMode, false);
7822
+ __privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, __privateGet(this, _value));
7823
+ __privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
7824
+ };
7825
+ openDialog_fn = function(title, fields) {
7826
+ const backdrop = this.root.querySelector(".backdrop");
7827
+ const titleEl = this.root.querySelector(".dialog h3");
7828
+ const body = this.root.querySelector(".dialog-body");
7829
+ if (!backdrop || !titleEl || !body) return Promise.resolve(null);
7830
+ titleEl.textContent = title;
7831
+ body.innerHTML = fields.map((field) => __privateMethod(this, _FxEditor_instances, fieldHtml_fn).call(this, field)).join("");
7832
+ __privateSet(this, _dialogReturnFocus, this.root.activeElement ?? this.editor);
7833
+ backdrop.removeAttribute("hidden");
7834
+ const first = body.querySelector("input,select");
7835
+ first?.focus();
7836
+ return new Promise((resolve) => {
7837
+ __privateSet(this, _dialogResolve, resolve);
7838
+ });
7839
+ };
7840
+ fieldHtml_fn = function(field) {
7841
+ const label = esc(field.label);
7842
+ if (field.type === "select") {
7843
+ const options = (field.options ?? []).map((option) => `<option value="${esc(option.value)}" ${option.value === field.value ? "selected" : ""}>${esc(option.label)}</option>`).join("");
7844
+ return `<label class="field"><span>${label}</span><select data-field="${esc(field.name)}">${options}</select></label>`;
7845
+ }
7846
+ const type = field.type === "url" ? "url" : field.type === "number" ? "number" : "text";
7847
+ return `<label class="field"><span>${label}</span><input data-field="${esc(field.name)}" type="${type}" value="${esc(field.value ?? "")}" placeholder="${esc(field.placeholder ?? "")}"></label>`;
7848
+ };
7849
+ submitDialog_fn = function() {
7850
+ const body = this.root.querySelector(".dialog-body");
7851
+ if (!body) return;
7852
+ const result = {};
7853
+ for (const field of Array.from(body.querySelectorAll("[data-field]"))) {
7854
+ result[field.dataset.field ?? ""] = field.value.trim();
7855
+ }
7856
+ __privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, result);
7857
+ };
7858
+ openLinkDialog_fn = async function(existing) {
7859
+ __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
7860
+ const editor = this.editor;
7861
+ if (!editor) return;
7862
+ if (existing) __privateMethod(this, _FxEditor_instances, selectAnchor_fn).call(this, existing);
7863
+ let range2 = getRange(editor, __privateGet(this, _lastRange));
7864
+ if (!range2) {
7865
+ placeCaretAtEnd(editor);
7866
+ range2 = getRange(editor);
7867
+ __privateSet(this, _lastRange, range2?.cloneRange() ?? null);
7868
+ }
7869
+ const anchor = existing ?? closestInEditor(editor, range2?.startContainer ?? null, "a");
7870
+ const selected = range2 && !range2.collapsed ? range2.toString() : anchor?.textContent ?? "";
7871
+ const result = await __privateMethod(this, _FxEditor_instances, openDialog_fn).call(this, "Link", [
7872
+ { name: "url", label: "URL", type: "url", value: anchor?.getAttribute("href") ?? "", placeholder: "https://..." },
7873
+ { name: "text", label: "Texto (quando não há seleção)", value: selected, placeholder: "Texto do link" },
7874
+ {
7875
+ name: "target",
7876
+ label: "Abrir em",
7877
+ type: "select",
7878
+ value: anchor?.getAttribute("target") ?? "_blank",
7879
+ options: [
7880
+ { value: "_blank", label: "Nova aba" },
7881
+ { value: "_self", label: "Mesma aba" }
7882
+ ]
7883
+ }
7884
+ ]);
7885
+ if (!result) return;
7886
+ const href = sanitizeLinkUrl(result.url ?? "");
7887
+ if (!href) {
7888
+ __privateMethod(this, _FxEditor_instances, error_fn).call(this, "URL de link inválida.");
7889
+ return;
7890
+ }
7891
+ const target = result.target === "_self" ? "_self" : "_blank";
7892
+ const label = (result.text ?? "").trim() || (anchor ? void 0 : selected || href);
7893
+ if (anchor) {
7894
+ anchor.setAttribute("href", href);
7895
+ anchor.setAttribute("target", target);
7896
+ anchor.setAttribute("rel", "noopener noreferrer");
7897
+ if (label) anchor.textContent = label;
7898
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7899
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7900
+ return;
7901
+ }
7902
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7903
+ applyLink(editor, href, label ?? (selected || href), target);
7904
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7905
+ };
7906
+ openImageDialog_fn = async function(existing) {
7907
+ __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
7908
+ const figure = existing?.closest("figure");
7909
+ const caption = figure?.querySelector("figcaption")?.textContent ?? "";
7910
+ const alignValue = figure?.getAttribute("data-align") ?? "center";
7911
+ const widthValue = figure?.style.width?.replace("%", "") || "100";
7912
+ const dialog = __privateMethod(this, _FxEditor_instances, openDialog_fn).call(this, existing ? "Editar imagem" : "Inserir imagem", [
7913
+ { name: "src", label: "URL da imagem", type: "url", value: existing?.getAttribute("src") ?? "", placeholder: "https://..." },
7914
+ { name: "alt", label: "Texto alternativo", value: existing?.getAttribute("alt") ?? "" },
7915
+ { name: "caption", label: "Legenda", value: caption },
7916
+ {
7917
+ name: "width",
7918
+ label: "Largura",
7919
+ type: "select",
7920
+ value: widthValue,
7921
+ options: [
7922
+ { value: "25", label: "25%" },
7923
+ { value: "50", label: "50%" },
7924
+ { value: "75", label: "75%" },
7925
+ { value: "100", label: "100%" }
7926
+ ]
7927
+ },
7928
+ {
7929
+ name: "align",
7930
+ label: "Alinhamento",
7931
+ type: "select",
7932
+ value: alignValue,
7933
+ options: [
7934
+ { value: "left", label: "Esquerda" },
7935
+ { value: "center", label: "Centro" },
7936
+ { value: "right", label: "Direita" }
7937
+ ]
7938
+ }
7939
+ ]);
7940
+ const body = this.root.querySelector(".dialog-body");
7941
+ if (body) {
7942
+ const upload = document.createElement("button");
7943
+ upload.type = "button";
7944
+ upload.className = "btn";
7945
+ upload.textContent = "Escolher arquivo…";
7946
+ upload.addEventListener("click", () => {
7947
+ __privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null);
7948
+ __privateGet(this, _imagePicker)?.click();
7949
+ });
7950
+ body.appendChild(upload);
7951
+ }
7952
+ const result = await dialog;
7953
+ if (!result) return;
7954
+ const src = sanitizeMediaUrl(result.src ?? "", "img");
7955
+ if (!src) {
7956
+ __privateMethod(this, _FxEditor_instances, error_fn).call(this, "URL de imagem inválida.");
7957
+ return;
7958
+ }
7959
+ if (existing && figure) {
7960
+ existing.setAttribute("src", src);
7961
+ existing.setAttribute("alt", result.alt ?? "");
7962
+ figure.setAttribute("data-align", result.align || "center");
7963
+ figure.style.width = `${result.width || 100}%`;
7964
+ const cap = figure.querySelector("figcaption");
7965
+ if (result.caption) {
7966
+ if (cap) cap.textContent = result.caption;
7967
+ else figure.insertAdjacentHTML("beforeend", `<figcaption>${escapeText(result.caption)}</figcaption>`);
7968
+ } else {
7969
+ cap?.remove();
7970
+ }
7971
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
7972
+ return;
7973
+ }
7974
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
7975
+ __privateMethod(this, _FxEditor_instances, insertImage_fn).call(this, src, result.alt ?? "", result.caption ?? "", result.width || "100", result.align || "center");
7976
+ };
7977
+ toEmbedUrl_fn = function(value) {
7978
+ try {
7979
+ const url = new URL(value);
7980
+ const host = url.hostname.replace(/^www\./, "");
7981
+ if (host === "youtu.be") {
7982
+ return `https://www.youtube-nocookie.com/embed/${url.pathname.slice(1)}`;
7983
+ }
7984
+ if (host === "youtube.com" || host === "m.youtube.com") {
7985
+ const id = url.searchParams.get("v");
7986
+ if (id) return `https://www.youtube-nocookie.com/embed/${id}`;
7987
+ if (url.pathname.startsWith("/shorts/")) {
7988
+ return `https://www.youtube-nocookie.com/embed/${url.pathname.split("/")[2] ?? ""}`;
7989
+ }
7990
+ }
7991
+ if (host === "vimeo.com") {
7992
+ const id = url.pathname.split("/").filter(Boolean)[0];
7993
+ if (id) return `https://player.vimeo.com/video/${id}`;
7994
+ }
7995
+ return value;
7996
+ } catch {
7997
+ return value;
7998
+ }
7999
+ };
8000
+ openVideoDialog_fn = async function() {
8001
+ const result = await __privateMethod(this, _FxEditor_instances, openDialog_fn).call(this, "Inserir vídeo", [
8002
+ { name: "src", label: "URL do vídeo ou embed", type: "url", placeholder: "https://.../video.mp4 ou YouTube/Vimeo" },
8003
+ { name: "caption", label: "Legenda", value: "" },
8004
+ {
8005
+ name: "width",
8006
+ label: "Largura",
8007
+ type: "select",
8008
+ value: "100",
8009
+ options: [
8010
+ { value: "50", label: "50%" },
8011
+ { value: "75", label: "75%" },
8012
+ { value: "100", label: "100%" }
8013
+ ]
8014
+ },
8015
+ {
8016
+ name: "align",
8017
+ label: "Alinhamento",
8018
+ type: "select",
8019
+ value: "center",
8020
+ options: [
8021
+ { value: "left", label: "Esquerda" },
8022
+ { value: "center", label: "Centro" },
8023
+ { value: "right", label: "Direita" }
8024
+ ]
8025
+ }
8026
+ ]);
8027
+ if (!result) return;
8028
+ const src = result.src ?? "";
8029
+ const embed = sanitizeEmbedUrl(__privateMethod(this, _FxEditor_instances, toEmbedUrl_fn).call(this, src));
8030
+ if (embed) {
8031
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
8032
+ __privateMethod(this, _FxEditor_instances, insertEmbed_fn).call(this, embed, result.caption ?? "", result.width || "100", result.align || "center");
8033
+ return;
8034
+ }
8035
+ const media = sanitizeMediaUrl(src, "video");
8036
+ if (!media) {
8037
+ __privateMethod(this, _FxEditor_instances, error_fn).call(this, "URL de vídeo inválida.");
8038
+ return;
8039
+ }
8040
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
8041
+ __privateMethod(this, _FxEditor_instances, insertVideo_fn).call(this, media, result.caption ?? "", result.width || "100", result.align || "center");
8042
+ };
8043
+ decorateMediaFigures_fn = function() {
8044
+ const editor = this.editor;
8045
+ if (!editor || __privateGet(this, _sourceMode)) return;
8046
+ for (const figure of Array.from(
8047
+ editor.querySelectorAll("figure.fx-image, figure.fx-video, figure.fx-embed")
8048
+ )) {
8049
+ if (figure.querySelector(":scope > .fx-media-quick")) {
8050
+ __privateMethod(this, _FxEditor_instances, syncQuickActions_fn).call(this, figure);
8051
+ continue;
8052
+ }
8053
+ for (const kind of ["left", "center", "right", "delete"]) {
8054
+ const label = kind === "delete" ? "Excluir mídia" : kind === "left" ? "Alinhar à esquerda" : kind === "center" ? "Centralizar" : "Alinhar à direita";
8055
+ const button = document.createElement("button");
8056
+ button.type = "button";
8057
+ button.className = `fx-media-quick quick-${kind}`;
8058
+ button.title = label;
8059
+ button.setAttribute("aria-label", label);
8060
+ if (kind === "delete") button.dataset.mediaDelete = "";
8061
+ else button.dataset.mediaAlign = kind;
8062
+ button.innerHTML = QUICK_SVG[kind];
8063
+ button.addEventListener("click", (event) => {
8064
+ event.preventDefault();
8065
+ event.stopPropagation();
8066
+ if (kind === "delete") {
8067
+ figure.remove();
8068
+ __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
8069
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8070
+ } else {
8071
+ figure.setAttribute("data-align", kind);
8072
+ __privateMethod(this, _FxEditor_instances, syncQuickActions_fn).call(this, figure);
8073
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8074
+ }
8075
+ });
8076
+ figure.appendChild(button);
8077
+ }
8078
+ __privateMethod(this, _FxEditor_instances, syncQuickActions_fn).call(this, figure);
8079
+ }
8080
+ };
8081
+ syncQuickActions_fn = function(figure) {
8082
+ const align2 = figure.getAttribute("data-align") || "center";
8083
+ for (const button of Array.from(
8084
+ figure.querySelectorAll(":scope > .fx-media-quick[data-media-align]")
8085
+ )) {
8086
+ button.classList.toggle("active", button.dataset.mediaAlign === align2);
8087
+ }
8088
+ };
8089
+ selectAnchor_fn = function(anchor) {
8090
+ const editor = this.editor;
8091
+ if (!editor) return;
8092
+ editor.focus({ preventScroll: true });
8093
+ const range2 = document.createRange();
8094
+ range2.selectNodeContents(anchor);
8095
+ const selection = document.getSelection();
8096
+ if (!selection) return;
8097
+ selection.removeAllRanges();
8098
+ selection.addRange(range2);
8099
+ __privateSet(this, _lastRange, range2.cloneRange());
8100
+ };
8101
+ handleLinkOverlayClick_fn = function(event) {
8102
+ const action = event.target.closest("[data-link-action]");
8103
+ const anchor = __privateGet(this, _linkAnchor);
8104
+ if (!action || !anchor) return;
8105
+ event.preventDefault();
8106
+ event.stopPropagation();
8107
+ const kind = action.dataset.linkAction;
8108
+ if (kind === "open") __privateMethod(this, _FxEditor_instances, openLink_fn).call(this, anchor);
8109
+ else if (kind === "edit") void __privateMethod(this, _FxEditor_instances, openLinkDialog_fn).call(this, anchor);
8110
+ else if (kind === "remove") __privateMethod(this, _FxEditor_instances, removeLink_fn).call(this, anchor);
8111
+ };
8112
+ openLink_fn = function(anchor) {
8113
+ const href = anchor.getAttribute("href") ?? "";
8114
+ if (!href) return;
8115
+ __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
8116
+ const target = anchor.getAttribute("target") === "_self" ? "_self" : "_blank";
8117
+ window.open(href, target, target === "_blank" ? "noopener,noreferrer" : void 0);
8118
+ };
8119
+ removeLink_fn = function(anchor) {
8120
+ const parent = anchor.parentNode;
8121
+ if (!parent) return;
8122
+ while (anchor.firstChild) parent.insertBefore(anchor.firstChild, anchor);
8123
+ anchor.remove();
8124
+ __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
8125
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8126
+ this.editor?.focus({ preventScroll: true });
8127
+ };
8128
+ showLinkOverlay_fn = function(anchor) {
8129
+ __privateSet(this, _linkAnchor, anchor);
8130
+ const overlay = this.root.querySelector(".link-overlay");
8131
+ if (!overlay) return;
8132
+ const target = anchor.getAttribute("target") === "_self" ? "mesma janela" : "nova aba";
8133
+ const open = overlay.querySelector('[data-link-action="open"]');
8134
+ if (open) open.title = `Abrir na ${target}`;
8135
+ overlay.hidden = false;
8136
+ __privateMethod(this, _FxEditor_instances, positionLinkOverlay_fn).call(this);
8137
+ };
8138
+ hideLinkOverlay_fn = function() {
8139
+ __privateSet(this, _linkAnchor, null);
8140
+ const overlay = this.root.querySelector(".link-overlay");
8141
+ if (overlay) overlay.hidden = true;
8142
+ };
8143
+ positionLinkOverlay_fn = function() {
8144
+ const overlay = this.root.querySelector(".link-overlay");
8145
+ const anchor = __privateGet(this, _linkAnchor);
8146
+ const editor = this.editor;
8147
+ if (!overlay) return;
8148
+ if (!anchor || !editor || __privateGet(this, _sourceMode) || !anchor.isConnected || !editor.contains(anchor)) {
8149
+ overlay.hidden = true;
8150
+ return;
8151
+ }
8152
+ const body = overlay.parentElement;
8153
+ if (!body) return;
8154
+ const bodyRect = body.getBoundingClientRect();
8155
+ const rect = anchor.getBoundingClientRect();
8156
+ if (!rect.width || !rect.height || rect.bottom < bodyRect.top || rect.top > bodyRect.bottom) {
8157
+ overlay.hidden = true;
8158
+ return;
8159
+ }
8160
+ overlay.hidden = false;
8161
+ const width = overlay.offsetWidth || 180;
8162
+ const height = overlay.offsetHeight || 30;
8163
+ const left = Math.max(4, Math.min(rect.left - bodyRect.left, bodyRect.width - width - 4));
8164
+ let top = rect.bottom - bodyRect.top + 6;
8165
+ if (top + height > bodyRect.height) top = rect.top - bodyRect.top - height - 6;
8166
+ overlay.style.left = `${left}px`;
8167
+ overlay.style.top = `${Math.max(4, top)}px`;
8168
+ };
8169
+ handleEditorClick_fn = function(event) {
8170
+ const target = event.target;
8171
+ const anchor = target.closest("a[href]");
8172
+ if (anchor && this.editor?.contains(anchor)) {
8173
+ __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
8174
+ __privateMethod(this, _FxEditor_instances, showLinkOverlay_fn).call(this, anchor);
8175
+ if (event.ctrlKey || event.metaKey) {
8176
+ event.preventDefault();
8177
+ __privateMethod(this, _FxEditor_instances, openLink_fn).call(this, anchor);
8178
+ }
8179
+ return;
8180
+ }
8181
+ __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
8182
+ const figure = target.closest("figure.fx-image, figure.fx-video, figure.fx-embed");
8183
+ if (figure) __privateMethod(this, _FxEditor_instances, showMediaOverlay_fn).call(this, figure);
8184
+ else __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
8185
+ };
8186
+ showMediaOverlay_fn = function(figure) {
8187
+ __privateSet(this, _mediaFigure, figure);
8188
+ figure.setAttribute("tabindex", "0");
8189
+ if (figure.classList.contains("fx-image")) figure.focus({ preventScroll: true });
8190
+ const overlay = this.root.querySelector(".media-overlay");
8191
+ if (!overlay) return;
8192
+ overlay.hidden = false;
8193
+ __privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
8194
+ };
8195
+ hideMediaOverlay_fn = function() {
8196
+ __privateGet(this, _mediaFigure)?.removeAttribute("tabindex");
8197
+ __privateSet(this, _mediaFigure, null);
8198
+ const overlay = this.root.querySelector(".media-overlay");
8199
+ if (!overlay) return;
8200
+ overlay.hidden = true;
8201
+ const size = overlay.querySelector(".media-size");
8202
+ if (size) size.textContent = "";
8203
+ };
8204
+ deleteMediaFigure_fn = function() {
8205
+ const figure = __privateGet(this, _mediaFigure);
8206
+ if (!figure) return;
8207
+ figure.remove();
8208
+ __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
8209
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8210
+ this.editor?.focus({ preventScroll: true });
8211
+ };
8212
+ positionMediaOverlay_fn = function() {
8213
+ const overlay = this.root.querySelector(".media-overlay");
8214
+ const figure = __privateGet(this, _mediaFigure);
8215
+ const editor = this.editor;
8216
+ if (!overlay) return;
8217
+ if (!figure || !editor || __privateGet(this, _sourceMode) || !figure.isConnected || !editor.contains(figure)) {
8218
+ overlay.hidden = true;
8219
+ return;
8220
+ }
8221
+ const body = overlay.parentElement;
8222
+ if (!body) return;
8223
+ const bodyRect = body.getBoundingClientRect();
8224
+ const rect = figure.getBoundingClientRect();
8225
+ if (!rect.width || !rect.height || rect.bottom < bodyRect.top || rect.top > bodyRect.bottom) {
8226
+ overlay.hidden = true;
8227
+ return;
8228
+ }
8229
+ overlay.hidden = false;
8230
+ overlay.style.left = `${rect.left - bodyRect.left}px`;
8231
+ overlay.style.top = `${rect.top - bodyRect.top}px`;
8232
+ overlay.style.width = `${rect.width}px`;
8233
+ overlay.style.height = `${rect.height}px`;
8234
+ const size = overlay.querySelector(".media-size");
8235
+ if (size) size.textContent = `${Math.round(rect.width)} × ${Math.round(rect.height)}`;
8236
+ };
8237
+ startResize_fn = function(event) {
8238
+ const handle = event.target.closest("[data-handle]");
8239
+ const figure = __privateGet(this, _mediaFigure);
8240
+ const editor = this.editor;
8241
+ if (!handle || !figure || !editor) return;
8242
+ event.preventDefault();
8243
+ event.stopPropagation();
8244
+ __privateMethod(this, _FxEditor_instances, endResize_fn).call(this);
8245
+ const parent = figure.parentElement;
8246
+ if (!parent) return;
8247
+ const parentWidth = parent.getBoundingClientRect().width || editor.clientWidth;
8248
+ if (!parentWidth) return;
8249
+ const rect = figure.getBoundingClientRect();
8250
+ const startWidth = rect.width ? rect.width / parentWidth * 100 : Number.parseFloat(figure.style.width) || 100;
8251
+ const startX = event.clientX;
8252
+ const direction = handle.dataset.handle?.includes("w") ? -1 : 1;
8253
+ const move = (moveEvent) => {
8254
+ const deltaPct = (moveEvent.clientX - startX) * direction * 100 / parentWidth;
8255
+ const width = Math.min(100, Math.max(15, startWidth + deltaPct));
8256
+ figure.style.width = `${width.toFixed(1).replace(/\.0$/, "")}%`;
8257
+ __privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
8258
+ };
8259
+ const up = () => {
8260
+ __privateMethod(this, _FxEditor_instances, endResize_fn).call(this);
8261
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8262
+ __privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
8263
+ };
8264
+ window.addEventListener("pointermove", move);
8265
+ window.addEventListener("pointerup", up, { once: true });
8266
+ window.addEventListener("pointercancel", up, { once: true });
8267
+ __privateSet(this, _resizeDispose, () => {
8268
+ window.removeEventListener("pointermove", move);
8269
+ window.removeEventListener("pointerup", up);
8270
+ window.removeEventListener("pointercancel", up);
8271
+ });
8272
+ };
8273
+ endResize_fn = function() {
8274
+ var _a;
8275
+ (_a = __privateGet(this, _resizeDispose)) == null ? void 0 : _a.call(this);
8276
+ __privateSet(this, _resizeDispose, null);
8277
+ };
8278
+ handleDoubleClick_fn = function(event) {
8279
+ const target = event.target;
8280
+ const image = target.closest("img");
8281
+ if (image) {
8282
+ event.preventDefault();
8283
+ void __privateMethod(this, _FxEditor_instances, openImageDialog_fn).call(this, image);
8284
+ }
8285
+ };
8286
+ insertImage_fn = function(src, alt, caption, width, align2) {
8287
+ const html = `<figure class="fx-image" contenteditable="false" data-align="${escapeAttr(align2)}" style="width:${escapeAttr(width)}%"><img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}" loading="lazy" decoding="async">${caption ? `<figcaption>${escapeText(caption)}</figcaption>` : ""}</figure><p><br></p>`;
8288
+ insertHtml(this.editor, html);
8289
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8290
+ };
8291
+ insertVideo_fn = function(src, caption, width, align2 = "center") {
8292
+ const html = `<figure class="fx-video" contenteditable="false" data-align="${escapeAttr(align2)}" style="width:${escapeAttr(width)}%"><video controls preload="metadata" src="${escapeAttr(src)}" style="width:100%"></video>${caption ? `<figcaption>${escapeText(caption)}</figcaption>` : ""}</figure><p><br></p>`;
8293
+ insertHtml(this.editor, html);
8294
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8295
+ };
8296
+ insertEmbed_fn = function(src, caption, width, align2 = "center") {
8297
+ const html = `<figure class="fx-embed" contenteditable="false" data-align="${escapeAttr(align2)}" style="width:${escapeAttr(width)}%"><iframe src="${escapeAttr(src)}" title="${escapeAttr(caption || "Vídeo")}" loading="lazy" allowfullscreen sandbox="allow-scripts allow-same-origin allow-presentation" referrerpolicy="strict-origin-when-cross-origin" style="width:100%;aspect-ratio:16/9;border:0"></iframe>${caption ? `<figcaption>${escapeText(caption)}</figcaption>` : ""}</figure><p><br></p>`;
8298
+ insertHtml(this.editor, html);
8299
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8300
+ };
8301
+ insertTable_fn = function(rows, cols) {
8302
+ const rowCount = Math.min(20, Math.max(1, rows));
8303
+ const colCount = Math.min(12, Math.max(1, cols));
8304
+ const width = (100 / colCount).toFixed(4).replace(/\.?0+$/, "");
8305
+ const colsHtml = `<colgroup>${Array.from({ length: colCount }, () => `<col style="width:${width}%">`).join("")}</colgroup>`;
8306
+ const head = `<thead><tr>${Array.from({ length: colCount }, () => '<th scope="col"><p><br></p></th>').join("")}</tr></thead>`;
8307
+ const bodyRows = Math.max(0, rowCount - 1);
8308
+ const body = bodyRows ? `<tbody>${Array.from(
8309
+ { length: bodyRows },
8310
+ () => `<tr>${Array.from({ length: colCount }, () => "<td><p><br></p></td>").join("")}</tr>`
8311
+ ).join("")}</tbody>` : "";
8312
+ const html = `<figure class="fx-table"><table>${colsHtml}${head}${body}</table></figure><p><br></p>`;
8313
+ insertHtml(this.editor, html);
8314
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8315
+ };
8316
+ insertColumns_fn = function(cols) {
8317
+ const count = Math.min(4, Math.max(1, cols));
8318
+ const width = (100 / count).toFixed(4).replace(/\.?0+$/, "");
8319
+ const cells = Array.from(
8320
+ { length: count },
8321
+ () => `<td style="width:${width}%;vertical-align:top;padding:8px"><p><br></p></td>`
8322
+ ).join("");
8323
+ const html = `<table class="fx-grid" role="presentation" data-cols="${count}" data-stack="sm" style="width:100%;border-collapse:collapse"><tbody><tr>${cells}</tr></tbody></table><p><br></p>`;
8324
+ insertHtml(this.editor, html);
8325
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8326
+ };
8327
+ showTablePanel_fn = function() {
8328
+ const panel = this.root.querySelector("[data-panel]");
8329
+ if (!panel) return;
8330
+ const table = __privateMethod(this, _FxEditor_instances, currentTable_fn).call(this);
8331
+ const grid = Array.from({ length: 64 }, (_, index) => {
8332
+ const rows = Math.floor(index / 8) + 1;
8333
+ const cols = index % 8 + 1;
8334
+ return `<button type="button" data-grid="${rows},${cols}" aria-label="${rows} por ${cols}"></button>`;
8335
+ }).join("");
8336
+ panel.innerHTML = `<p class="panel-title">Inserir tabela</p><div class="grid-picker">${grid}</div>` + (table ? `<p class="panel-title" style="margin-top:8px">Tabela atual</p><div class="panel-actions"><button type="button" class="btn" data-table-action="add-row">+ Linha</button><button type="button" class="btn" data-table-action="add-col">+ Coluna</button><button type="button" class="btn" data-table-action="del-row">− Linha</button><button type="button" class="btn" data-table-action="del-col">− Coluna</button><button type="button" class="btn" data-table-action="toggle-header">Cabeçalho</button><button type="button" class="btn" data-table-action="merge-cells">Mesclar</button><button type="button" class="btn" data-table-action="split-cell">Dividir</button><button type="button" class="btn" data-table-action="del-table">Excluir tabela</button></div>` : "");
8337
+ panel.removeAttribute("hidden");
8338
+ panel.querySelectorAll("[data-grid]").forEach((button) => {
8339
+ button.addEventListener("click", () => {
8340
+ const [rows, cols] = (button.dataset.grid ?? "").split(",").map(Number);
8341
+ __privateMethod(this, _FxEditor_instances, hidePanel_fn).call(this);
8342
+ __privateMethod(this, _FxEditor_instances, insertTable_fn).call(this, rows, cols);
8343
+ });
8344
+ });
8345
+ panel.querySelectorAll("[data-table-action]").forEach((button) => {
8346
+ button.addEventListener("click", () => {
8347
+ __privateMethod(this, _FxEditor_instances, tableAction_fn).call(this, button.dataset.tableAction ?? "");
8348
+ __privateMethod(this, _FxEditor_instances, hidePanel_fn).call(this);
8349
+ });
8350
+ });
8351
+ };
8352
+ showColumnsPanel_fn = function() {
8353
+ const panel = this.root.querySelector("[data-panel]");
8354
+ if (!panel) return;
8355
+ panel.innerHTML = `<p class="panel-title">Inserir colunas</p><div class="panel-actions">` + [1, 2, 3, 4].map((count) => `<button type="button" class="btn" data-columns="${count}">${count}</button>`).join("") + `</div>`;
8356
+ panel.removeAttribute("hidden");
8357
+ panel.querySelectorAll("[data-columns]").forEach((button) => {
8358
+ button.addEventListener("click", () => {
8359
+ __privateMethod(this, _FxEditor_instances, hidePanel_fn).call(this);
8360
+ __privateMethod(this, _FxEditor_instances, insertColumns_fn).call(this, Number(button.dataset.columns ?? 1));
8361
+ });
8362
+ });
8363
+ };
8364
+ hidePanel_fn = function() {
8365
+ this.root.querySelector("[data-panel]")?.setAttribute("hidden", "");
8366
+ };
8367
+ currentTable_fn = function() {
8368
+ const editor = this.editor;
8369
+ if (!editor) return null;
8370
+ const range2 = getRange(editor, __privateGet(this, _lastRange));
8371
+ return closestInEditor(editor, range2?.startContainer ?? null, "table");
8372
+ };
8373
+ tableAction_fn = function(action) {
8374
+ const table = __privateMethod(this, _FxEditor_instances, currentTable_fn).call(this);
8375
+ if (!table) return;
8376
+ const editor = this.editor;
8377
+ const range2 = editor ? getRange(editor, __privateGet(this, _lastRange)) : null;
8378
+ const cell = editor ? closestInEditor(editor, range2?.startContainer ?? null, "td,th") : null;
8379
+ const row = cell?.parentElement;
8380
+ if (action === "merge-cells") {
8381
+ if (!range2) return;
8382
+ const merged = mergeSelectedCells(table, range2);
8383
+ if (!merged) return;
8384
+ placeCaretAtEnd(merged);
8385
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8386
+ return;
8387
+ }
8388
+ if (action === "split-cell") {
8389
+ if (!cell) return;
8390
+ const created = splitCell(table, cell);
8391
+ if (!created.length) return;
8392
+ placeCaretAtEnd(cell);
8393
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8394
+ return;
8395
+ }
8396
+ if (action === "add-row" && row) {
8397
+ const clone = row.cloneNode(true);
8398
+ for (const cloneCell of Array.from(clone.querySelectorAll("td,th"))) {
8399
+ cloneCell.innerHTML = "<p><br></p>";
8400
+ }
8401
+ row.after(clone);
8402
+ } else if (action === "add-col" && cell) {
8403
+ const index = Array.from(row?.children ?? []).indexOf(cell);
8404
+ for (const currentRow of Array.from(table.rows)) {
8405
+ const target = currentRow.cells[Math.max(0, index)];
8406
+ const clone = (target ?? currentRow.cells[currentRow.cells.length - 1])?.cloneNode(false);
8407
+ if (!clone) continue;
8408
+ clone.innerHTML = "<p><br></p>";
8409
+ currentRow.insertBefore(clone, target?.nextSibling ?? null);
8410
+ }
8411
+ } else if (action === "del-row" && row && table.rows.length > 1) {
8412
+ row.remove();
8413
+ } else if (action === "del-col" && cell && table.rows[0]?.cells.length > 1) {
8414
+ const index = Array.from(row?.children ?? []).indexOf(cell);
8415
+ for (const currentRow of Array.from(table.rows)) {
8416
+ currentRow.cells[index]?.remove();
8417
+ }
8418
+ } else if (action === "toggle-header") {
8419
+ const thead = table.tHead;
8420
+ if (thead && thead.rows.length) {
8421
+ const headerRow = thead.rows[0];
8422
+ for (const headerCell of Array.from(headerRow.cells)) {
8423
+ const td = document.createElement("td");
8424
+ td.innerHTML = headerCell.innerHTML;
8425
+ headerCell.replaceWith(td);
8426
+ }
8427
+ const tbody = table.tBodies[0] ?? table.createTBody();
8428
+ tbody.insertBefore(headerRow, tbody.firstChild);
8429
+ thead.remove();
8430
+ } else {
8431
+ const firstRow = table.rows[0];
8432
+ if (firstRow) {
8433
+ for (const firstCell of Array.from(firstRow.cells)) {
8434
+ const th = document.createElement("th");
8435
+ th.setAttribute("scope", "col");
8436
+ th.innerHTML = firstCell.innerHTML;
8437
+ firstCell.replaceWith(th);
8438
+ }
8439
+ table.createTHead().appendChild(firstRow);
8440
+ }
8441
+ }
8442
+ } else if (action === "del-table") {
8443
+ const figure = table.closest("figure.fx-table");
8444
+ (figure ?? table).remove();
8445
+ }
8446
+ __privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
8447
+ };
8448
+ handleImageFile_fn = async function(file) {
8449
+ if (!file.type.startsWith("image/")) {
8450
+ __privateMethod(this, _FxEditor_instances, error_fn).call(this, "O arquivo selecionado não é uma imagem.");
8451
+ return;
8452
+ }
8453
+ if (file.type === "image/svg+xml") {
8454
+ __privateMethod(this, _FxEditor_instances, error_fn).call(this, "SVG não é permitido por segurança. Use PNG, JPEG, WebP ou GIF.");
8455
+ return;
8456
+ }
8457
+ if (file.size > MAX_IMAGE_BYTES) {
8458
+ __privateMethod(this, _FxEditor_instances, error_fn).call(this, "Imagem muito grande (máximo 8 MB).");
8459
+ return;
8460
+ }
8461
+ let src = "";
8462
+ let alt = file.name.replace(/\.[a-z0-9]+$/i, "");
8463
+ try {
8464
+ if (this.uploadImage) {
8465
+ const uploaded = await this.uploadImage(file);
8466
+ if (typeof uploaded === "string") src = uploaded;
8467
+ else {
8468
+ src = uploaded.url;
8469
+ if (uploaded.alt) alt = uploaded.alt;
8470
+ }
8471
+ } else {
8472
+ src = await __privateMethod(this, _FxEditor_instances, fileToDataUrl_fn).call(this, file);
8473
+ }
8474
+ } catch {
8475
+ __privateMethod(this, _FxEditor_instances, error_fn).call(this, "Não foi possível processar a imagem.");
8476
+ return;
8477
+ }
8478
+ if (!src || !sanitizeMediaUrl(src, "img")) {
8479
+ __privateMethod(this, _FxEditor_instances, error_fn).call(this, "A imagem resultante não tem uma URL segura.");
8480
+ return;
8481
+ }
8482
+ __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
8483
+ __privateMethod(this, _FxEditor_instances, insertImage_fn).call(this, src, alt, "", "100", "center");
8484
+ };
8485
+ fileToDataUrl_fn = async function(file) {
8486
+ const dataUrl = await new Promise((resolve, reject) => {
8487
+ const reader = new FileReader();
8488
+ reader.onload = () => resolve(String(reader.result ?? ""));
8489
+ reader.onerror = () => reject(new Error("read-error"));
8490
+ reader.readAsDataURL(file);
8491
+ });
8492
+ if (file.type === "image/gif") return dataUrl;
8493
+ return new Promise((resolve) => {
8494
+ const image = new Image();
8495
+ image.onload = () => {
8496
+ const maxWidth = 1600;
8497
+ const scale = Math.min(1, maxWidth / (image.naturalWidth || maxWidth));
8498
+ if (scale >= 1) {
8499
+ resolve(dataUrl);
8500
+ return;
8501
+ }
8502
+ const canvas = document.createElement("canvas");
8503
+ canvas.width = Math.round((image.naturalWidth || maxWidth) * scale);
8504
+ canvas.height = Math.round((image.naturalHeight || maxWidth) * scale);
8505
+ const context = canvas.getContext("2d");
8506
+ if (!context) {
8507
+ resolve(dataUrl);
8508
+ return;
8509
+ }
8510
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
8511
+ resolve(canvas.toDataURL("image/webp", 0.85));
8512
+ };
8513
+ image.onerror = () => resolve(dataUrl);
8514
+ image.src = dataUrl;
8515
+ });
8516
+ };
8517
+ error_fn = function(message) {
8518
+ this.dispatchEvent(
8519
+ new CustomEvent("editor-error", {
8520
+ bubbles: true,
8521
+ composed: true,
8522
+ detail: { message }
8523
+ })
8524
+ );
8525
+ };
8526
+ _FxEditor.styles = `${FENIX_ICON_BASE_CSS}
8527
+ ${EDITOR_STYLES}`;
8528
+ let FxEditor = _FxEditor;
8529
+ function defineFxEditor() {
8530
+ return defineElement("fx-editor", FxEditor);
8531
+ }
8532
+ defineFxEditor();
8533
+ const _FxDialog = class _FxDialog extends FxElement {
8534
+ constructor() {
8535
+ super(...arguments);
8536
+ this._previouslyFocused = null;
8537
+ }
8538
+ static get observedAttributes() {
8539
+ return ["open", "size", "heading"];
8540
+ }
8541
+ get open() {
8542
+ return this.hasAttr("open");
8543
+ }
8544
+ set open(value) {
8545
+ this.toggleAttr("open", value);
8546
+ }
8547
+ render() {
8548
+ const isOpen = this.open;
8549
+ const heading = this.getAttr("heading");
8550
+ this.setTemplate(`
8551
+ <div class="overlay" part="overlay" ${isOpen ? "" : "hidden"}>
8552
+ <div class="dialog" role="dialog" aria-modal="true" aria-label="${esc(heading)}" tabindex="-1">
8553
+ <header>
8554
+ <h2>${esc(heading)}</h2>
8555
+ <button type="button" class="close" part="close" aria-label="Fechar">×</button>
8556
+ </header>
8557
+ <div class="body"><slot></slot></div>
8558
+ <footer part="footer"><slot name="footer"></slot></footer>
8559
+ </div>
8560
+ </div>
8561
+ `);
8562
+ if (!isOpen) return;
8563
+ this._cleanup?.();
8564
+ this._cleanup = void 0;
8565
+ const overlay = this.root.querySelector(".overlay");
8566
+ const closeBtn = this.root.querySelector(".close");
8567
+ const close = () => {
8568
+ this.open = false;
8569
+ this._cleanup?.();
8570
+ this._cleanup = void 0;
8571
+ this._restoreFocus();
8572
+ this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
8573
+ };
8574
+ overlay?.addEventListener("click", (e) => {
8575
+ if (e.target === overlay) close();
8576
+ });
8577
+ closeBtn?.addEventListener("click", close);
8578
+ const onKey = (e) => {
8579
+ if (e.key === "Escape") {
8580
+ e.stopPropagation();
8581
+ close();
8582
+ return;
8583
+ }
8584
+ if (e.key === "Tab") this._trapFocus(e);
8585
+ };
8586
+ document.addEventListener("keydown", onKey);
8587
+ this._cleanup = () => document.removeEventListener("keydown", onKey);
8588
+ requestAnimationFrame(() => {
8589
+ this._previouslyFocused = document.activeElement;
8590
+ this.root.querySelector(".dialog")?.focus();
8591
+ });
8592
+ this.dispatchEvent(new CustomEvent("open", { bubbles: true, composed: true }));
8593
+ }
8594
+ /** Mantém o foco dentro do modal (WCAG 2.4.3 / 2.1.2). */
8595
+ _trapFocus(e) {
8596
+ const dialog = this.root.querySelector(".dialog");
8597
+ if (!dialog) return;
8598
+ const focusables = dialog.querySelectorAll(
8599
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
8600
+ );
8601
+ if (!focusables.length) return;
8602
+ const first = focusables[0];
8603
+ const last = focusables[focusables.length - 1];
8604
+ const active = this.root.activeElement;
8605
+ if (e.shiftKey && (active === first || active === dialog)) {
8606
+ e.preventDefault();
8607
+ last.focus();
8608
+ } else if (!e.shiftKey && active === last) {
8609
+ e.preventDefault();
8610
+ first.focus();
8611
+ }
8612
+ }
8613
+ _restoreFocus() {
8614
+ if (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();
8615
+ this._previouslyFocused = null;
8616
+ }
8617
+ disconnectedCallback() {
8618
+ super.disconnectedCallback();
8619
+ this._cleanup?.();
8620
+ this._cleanup = void 0;
8621
+ this._restoreFocus();
8622
+ }
8623
+ };
8624
+ _FxDialog.styles = css`
8625
+ :host { display: contents; }
8626
+ .overlay {
8627
+ position: fixed;
8628
+ inset: 0;
8629
+ background: color-mix(in srgb, var(--fx-text-default, #000) 45%, transparent);
8630
+ backdrop-filter: blur(3px);
8631
+ -webkit-backdrop-filter: blur(3px);
8632
+ display: flex;
8633
+ align-items: center;
8634
+ justify-content: center;
8635
+ z-index: var(--fx-z-modal, 1100);
8636
+ animation: fade var(--fx-motion-duration-fast) var(--fx-motion-easing);
8637
+ }
8638
+ .overlay[hidden] { display: none; }
8639
+ @keyframes fade { from { opacity: 0; } to { opacity: 1; } }
8640
+ .dialog {
8641
+ background: var(--fx-surface-background);
8642
+ border: 1px solid var(--fx-border-default);
8643
+ border-radius: var(--fx-radius-lg);
8644
+ box-shadow: var(--fx-shadow-xl);
8645
+ /* Largura customizável por instância: defina --fx-dialog-width no elemento. */
8646
+ width: min(var(--fx-dialog-width, 560px), calc(100vw - 32px));
8647
+ max-height: calc(100vh - 64px);
8648
+ display: flex;
8649
+ flex-direction: column;
8650
+ font-family: var(--fx-font-family);
8651
+ animation: pop var(--fx-motion-duration-normal) var(--fx-motion-easing);
8652
+ }
8653
+ @keyframes pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
8654
+ :host([size='sm']) .dialog { width: min(var(--fx-dialog-width, 400px), calc(100vw - 32px)); }
8655
+ :host([size='lg']) .dialog { width: min(var(--fx-dialog-width, 800px), calc(100vw - 32px)); }
8656
+ /* Mobile: dialog ocupa praticamente toda a largura da tela. */
8657
+ @media (max-width: 640px) {
8658
+ .dialog, :host([size='sm']) .dialog, :host([size='lg']) .dialog {
8659
+ width: calc(100vw - 16px);
8660
+ max-width: calc(100vw - 16px);
8661
+ }
8662
+ }
8663
+ header {
8664
+ display: flex;
8665
+ align-items: center;
8666
+ justify-content: space-between;
8667
+ padding: var(--fx-space-lg) var(--fx-space-xl);
8668
+ border-bottom: 1px solid var(--fx-border-default);
8669
+ }
8670
+ h2 {
8671
+ margin: 0;
8672
+ font-size: calc(var(--fx-font-size) + 2px);
8673
+ font-weight: 600;
8674
+ color: var(--fx-text-default);
8675
+ }
8676
+ .close {
8677
+ border: none;
8678
+ background: transparent;
8679
+ color: var(--fx-text-muted);
8680
+ font-size: calc(var(--fx-font-size) + 6px);
8681
+ cursor: pointer;
8682
+ line-height: 1;
8683
+ padding: 0 var(--fx-space-xs);
8684
+ border-radius: var(--fx-radius-full);
8685
+ }
8686
+ .close:hover { color: var(--fx-color-danger); }
8687
+ .body {
8688
+ padding: var(--fx-space-lg) var(--fx-space-xl);
8689
+ overflow-y: auto;
8690
+ color: var(--fx-text-default);
8691
+ font-size: var(--fx-font-size);
8692
+ }
8693
+ footer {
8694
+ padding: var(--fx-space-md) var(--fx-space-xl);
8695
+ border-top: 1px solid var(--fx-border-default);
8696
+ display: flex;
8697
+ justify-content: flex-end;
8698
+ gap: var(--fx-space-sm);
8699
+ }
8700
+ footer[hidden] { display: none; }
5056
8701
  `;
5057
8702
  let FxDialog = _FxDialog;
5058
8703
  function defineFxDialog() {
@@ -7854,12 +11499,13 @@ const CARD_CSS = `
7854
11499
  border-left: 4px solid var(--kind);
7855
11500
  border-radius: var(--fx-radius-md, 8px);
7856
11501
  box-shadow: var(--fx-shadow-lg, 0 10px 30px rgba(0,0,0,.14));
7857
- padding: var(--fx-space-sm, 8px) var(--fx-space-md, 12px);
11502
+ padding: var(--fx-space-xs, 8px) var(--fx-space-md, 12px);
7858
11503
  font-family: inherit;
7859
11504
  animation: fx-toast-in .25s ease;
7860
11505
  }
7861
11506
  @keyframes fx-toast-in { from { opacity: 0; transform: translateY(-6px); } }
7862
11507
  .card.leaving { opacity: 0; transition: opacity .2s ease; }
11508
+ .card.no-title { min-height: 48px; }
7863
11509
  .icon {
7864
11510
  width: 22px; height: 22px; border-radius: 50%;
7865
11511
  background: var(--kind);
@@ -7966,7 +11612,16 @@ class FxToast extends HTMLElement {
7966
11612
  this.attachShadow({ mode: "open" });
7967
11613
  }
7968
11614
  static get observedAttributes() {
7969
- return ["kind", "title", "message", "duration", "icon", "mode", "progress", "progress-origin"];
11615
+ return [
11616
+ "kind",
11617
+ "title",
11618
+ "message",
11619
+ "duration",
11620
+ "icon",
11621
+ "mode",
11622
+ "progress",
11623
+ "progress-origin"
11624
+ ];
7970
11625
  }
7971
11626
  /**
7972
11627
  * Exibe a barra de contagem regressiva na base do card.
@@ -8043,7 +11698,9 @@ class FxToast extends HTMLElement {
8043
11698
  this.disposed = true;
8044
11699
  if (this.timer) clearTimeout(this.timer);
8045
11700
  this.timer = null;
8046
- this.dispatchEvent(new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }));
11701
+ this.dispatchEvent(
11702
+ new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false })
11703
+ );
8047
11704
  }
8048
11705
  attributeChangedCallback(name) {
8049
11706
  if (name === "duration") {
@@ -8053,7 +11710,8 @@ class FxToast extends HTMLElement {
8053
11710
  }
8054
11711
  const safe = this.safeDuration;
8055
11712
  this.counting = safe > 0;
8056
- if (safe > 0 && this.isConnected && !this.disposed) __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
11713
+ if (safe > 0 && this.isConnected && !this.disposed)
11714
+ __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
8057
11715
  }
8058
11716
  if (name === "duration" || name === "progress" || name === "progress-origin" || name === "kind") {
8059
11717
  __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
@@ -8105,7 +11763,10 @@ pauseTimer_fn = function() {
8105
11763
  if (!this.timer) return;
8106
11764
  clearTimeout(this.timer);
8107
11765
  this.timer = null;
8108
- this.remaining = Math.max(0, this.remaining - (Date.now() - this.startedAt));
11766
+ this.remaining = Math.max(
11767
+ 0,
11768
+ this.remaining - (Date.now() - this.startedAt)
11769
+ );
8109
11770
  };
8110
11771
  resumeTimer_fn = function() {
8111
11772
  if (!this.counting || this.timer || this.disposed) return;
@@ -8135,15 +11796,23 @@ syncProgress_fn = function() {
8135
11796
  return;
8136
11797
  }
8137
11798
  if (!bar) {
8138
- this.shadowRoot.querySelector(".card").insertAdjacentHTML("beforeend", '<span class="progress" part="progress" aria-hidden="true"></span>');
11799
+ this.shadowRoot.querySelector(".card").insertAdjacentHTML(
11800
+ "beforeend",
11801
+ '<span class="progress" part="progress" aria-hidden="true"></span>'
11802
+ );
8139
11803
  }
8140
11804
  const el = this.shadowRoot.querySelector(".progress");
8141
11805
  el.style.setProperty("--toast-duration", `${dur}ms`);
8142
- el.style.setProperty("--toast-progress-origin", `${this.progressOrigin} center`);
11806
+ el.style.setProperty(
11807
+ "--toast-progress-origin",
11808
+ `${this.progressOrigin} center`
11809
+ );
8143
11810
  el.classList.remove("run");
8144
11811
  if (mode !== "paused") el.classList.add("run");
8145
11812
  card.classList.add("entering");
8146
- requestAnimationFrame(() => requestAnimationFrame(() => card.classList.remove("entering")));
11813
+ requestAnimationFrame(
11814
+ () => requestAnimationFrame(() => card.classList.remove("entering"))
11815
+ );
8147
11816
  };
8148
11817
  render_fn = function() {
8149
11818
  const kind = this.getAttribute("kind") || "info";
@@ -8156,7 +11825,7 @@ render_fn = function() {
8156
11825
  const iconContent = isFxIcon ? iconName : KIND_ICON[kind] ?? KIND_ICON.info;
8157
11826
  this.shadowRoot.innerHTML = `
8158
11827
  <style>${CARD_CSS}</style>
8159
- <div class="card" style="--kind:${color}" role="status" part="card">
11828
+ <div class="card${title ? "" : " no-title"}" style="--kind:${color}" role="status" part="card">
8160
11829
  <span class="icon${isFxIcon ? " fx-icon" : ""}">${iconContent}</span>
8161
11830
  <div class="body">
8162
11831
  ${title ? '<div class="title" part="title"></div>' : ""}
@@ -8196,7 +11865,10 @@ render_fn = function() {
8196
11865
  btn.title = "Copiar mensagem";
8197
11866
  }, 1200);
8198
11867
  });
8199
- this.shadowRoot.querySelector(".close").addEventListener("click", () => this.dismiss());
11868
+ this.shadowRoot.querySelector(".close").addEventListener(
11869
+ "click",
11870
+ () => this.dismiss()
11871
+ );
8200
11872
  };
8201
11873
  /**
8202
11874
  * Mostra "Ver mais" somente quando a mensagem passou de 2 linhas.
@@ -8214,7 +11886,9 @@ syncMore_fn = function() {
8214
11886
  more.hidden = !(msg.scrollHeight - msg.clientHeight > 1);
8215
11887
  };
8216
11888
  function regionFor(position) {
8217
- let region = document.querySelector(`[data-fx-toast-region="${position}"]`);
11889
+ let region = document.querySelector(
11890
+ `[data-fx-toast-region="${position}"]`
11891
+ );
8218
11892
  if (!region) {
8219
11893
  region = document.createElement("div");
8220
11894
  region.setAttribute("data-fx-toast-region", position);
@@ -8249,7 +11923,10 @@ class ToastApi {
8249
11923
  push(kind, title, messageOrOpts, opts) {
8250
11924
  const id = ++this.seq;
8251
11925
  const message = typeof messageOrOpts === "string" ? messageOrOpts : messageOrOpts?.message;
8252
- const o = { ...typeof messageOrOpts === "object" && messageOrOpts ? messageOrOpts : void 0, ...opts };
11926
+ const o = {
11927
+ ...typeof messageOrOpts === "object" && messageOrOpts ? messageOrOpts : void 0,
11928
+ ...opts
11929
+ };
8253
11930
  const el = document.createElement("fx-toast");
8254
11931
  el.setAttribute("kind", kind);
8255
11932
  el.setAttribute("title", title);
@@ -8274,7 +11951,8 @@ class ToastApi {
8274
11951
  const FenixToast = new ToastApi();
8275
11952
  globalThis.FenixToast = FenixToast;
8276
11953
  function defineFxToast() {
8277
- if (!customElements.get("fx-toast")) customElements.define("fx-toast", FxToast);
11954
+ if (!customElements.get("fx-toast"))
11955
+ customElements.define("fx-toast", FxToast);
8278
11956
  }
8279
11957
  defineFxToast();
8280
11958
  const _FxTooltip = class _FxTooltip extends FxElement {
@@ -9476,17 +13154,16 @@ const _FxPagination = class _FxPagination extends FxElement {
9476
13154
  const pages = this.pages;
9477
13155
  const current = Math.min(Math.max(1, this.page), pages);
9478
13156
  const opts = (this.getAttr("rows-options", "5,10,20,50") || "").split(",").map((s) => s.trim()).filter(Boolean);
9479
- const window_ = [];
9480
- for (let p = Math.max(1, current - 2); p <= Math.min(pages, current + 2); p++) window_.push(p);
9481
- const from = total0(current, this.rows);
13157
+ const pageButtons = pageRange(current, pages).map(
13158
+ (item) => item === "ellipsis" ? `<span class="ellipsis" part="ellipsis" aria-hidden="true">…</span>` : `<button type="button" class="nav ${item === current ? "active" : ""}" part="page" data-go="${item}"${item === current ? ' aria-current="page"' : ""}>${item}</button>`
13159
+ ).join("");
13160
+ const from = (current - 1) * this.rows + 1;
9482
13161
  const to = Math.min(current * this.rows, this.total);
9483
13162
  this.setTemplate(`
9484
13163
  <span class="info">${this.total ? `${from}-${to} de ${this.total}` : "0 itens"}</span>
9485
13164
  <button type="button" class="nav" part="first" aria-label="Primeira página" data-go="1" ${current <= 1 ? "disabled" : ""}>${fenixIconHtml("first_page")}</button>
9486
13165
  <button type="button" class="nav" part="prev" aria-label="Página anterior" data-go="${current - 1}" ${current <= 1 ? "disabled" : ""}>${fenixIconHtml("navigate_before")}</button>
9487
- ${window_[0] !== 1 && pages > 5 ? `<button type="button" class="nav" data-go="1">1</button>` : ""}
9488
- ${window_.map((p) => `<button type="button" class="nav ${p === current ? "active" : ""}" data-go="${p}">${p}</button>`).join("")}
9489
- ${window_[window_.length - 1] !== pages && pages > 5 ? `<button type="button" class="nav" data-go="${pages}">${pages}</button>` : ""}
13166
+ ${pageButtons}
9490
13167
  <button type="button" class="nav" part="next" aria-label="Próxima página" data-go="${current + 1}" ${current >= pages ? "disabled" : ""}>${fenixIconHtml("navigate_next")}</button>
9491
13168
  <button type="button" class="nav" part="last" aria-label="Última página" data-go="${pages}" ${current >= pages ? "disabled" : ""}>${fenixIconHtml("last_page")}</button>
9492
13169
  ${opts.length ? `
@@ -9494,9 +13171,6 @@ const _FxPagination = class _FxPagination extends FxElement {
9494
13171
  ${opts.map((o) => `<option value="${esc(o)}">${esc(o)}</option>`).join("")}
9495
13172
  </fx-select>` : ""}
9496
13173
  `);
9497
- function total0(page, rows) {
9498
- return (page - 1) * rows + 1;
9499
- }
9500
13174
  this.root.querySelectorAll(".nav[data-go]").forEach((btn) => {
9501
13175
  btn.addEventListener("click", () => {
9502
13176
  const target = Number(btn.dataset.go);
@@ -9528,6 +13202,7 @@ _FxPagination.styles = css`
9528
13202
  :host {
9529
13203
  display: flex;
9530
13204
  align-items: center;
13205
+ flex-wrap: wrap;
9531
13206
  gap: var(--fx-space-sm);
9532
13207
  font-family: var(--fx-font-family);
9533
13208
  font-size: var(--fx-font-size);
@@ -9565,6 +13240,18 @@ _FxPagination.styles = css`
9565
13240
  color: #fff;
9566
13241
  font-weight: 600;
9567
13242
  }
13243
+ /* Elipse entre a janela de páginas e as bordas: ocupa o mesmo espaço de
13244
+ um botão (para o rodapé não "pular" ao navegar) mas não é clicável. */
13245
+ .ellipsis {
13246
+ display: inline-flex;
13247
+ align-items: center;
13248
+ justify-content: center;
13249
+ min-width: var(--fx-size-sm);
13250
+ height: var(--fx-size-sm);
13251
+ color: var(--fx-text-muted);
13252
+ font-size: calc(var(--fx-font-size) - 2px);
13253
+ user-select: none;
13254
+ }
9568
13255
  .info { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); }
9569
13256
  fx-select { vertical-align: middle; }
9570
13257
  fx-select::part(trigger) {
@@ -9900,8 +13587,8 @@ const _FxKnob = class _FxKnob extends FxFormElement {
9900
13587
  const min = this.min;
9901
13588
  const max = this.max;
9902
13589
  const value = this.clampValue(this.value);
9903
- const range = max - min;
9904
- const pct = range > 0 ? (value - min) / range : 0;
13590
+ const range2 = max - min;
13591
+ const pct = range2 > 0 ? (value - min) / range2 : 0;
9905
13592
  const sizeNum = this.getSizeNum();
9906
13593
  const strokeW = this.strokeWidth;
9907
13594
  const radius = (sizeNum - strokeW) / 2;
@@ -11792,6 +15479,7 @@ defineFxAccordion();
11792
15479
  defineFxAccordionPanel();
11793
15480
  export {
11794
15481
  DEFAULT_PRESET,
15482
+ FX_EDITOR_CONTENT_CSS,
11795
15483
  FX_JSX_TYPES,
11796
15484
  FenixToast,
11797
15485
  FenixUI,
@@ -11814,6 +15502,7 @@ export {
11814
15502
  FxDrawer,
11815
15503
  FxDropdown,
11816
15504
  FxDropdownItem,
15505
+ FxEditor,
11817
15506
  FxElement,
11818
15507
  FxEmptyState,
11819
15508
  FxFileUpload,
@@ -11863,8 +15552,15 @@ export {
11863
15552
  kebabToCamel,
11864
15553
  lightTokens,
11865
15554
  listPresets,
15555
+ normalizeForEditor,
15556
+ normalizeForStorage,
15557
+ normalizeHtml,
11866
15558
  resetTheme,
11867
15559
  h as sanitizeData,
15560
+ sanitizeEmbedUrl,
15561
+ sanitizeHtml,
15562
+ sanitizeLinkUrl,
15563
+ sanitizeMediaUrl,
11868
15564
  setTokens,
11869
15565
  j as stripTags,
11870
15566
  theme,