leksy-editor 1.0.21 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,6 +98,9 @@ const app = createApp({
98
98
  | `pexelsApiKey` | API key for Pexels integration. |
99
99
  | `tenorApiKey` | API key for Tenor integration. |
100
100
  | `cssVariables` | Custom CSS styling variables for multiple themes and modes. |
101
+ | `autoHeight` | Automatically adjusts height based on content. |
102
+ | `maxHeight` | Maximum height for the editor. |
103
+ | `minHeight` | Minimum height for the editor. |
101
104
 
102
105
  ### CSS Customization
103
106
 
@@ -118,6 +121,7 @@ cssVariables: {
118
121
  textColor: "hsl(216, 10%, 20%)",
119
122
  resizerPosition: "#fff",
120
123
  resizerPositionBackground: "#333",
124
+ linkColor: "#0000ff",
121
125
  }
122
126
  ```
123
127
 
package/constant.js CHANGED
@@ -167,13 +167,14 @@ const CSS_VARIABLES = {
167
167
  resizerBackground: "hsl(211, 100%, 62%, 0.3)",
168
168
  shadow: "rgba(0, 0, 0, 0.3)",
169
169
  mention: " #efefef",
170
- mentionHighlight: " #ddd",
170
+ mentionHighlight: "#ddd",
171
171
  dashedBorder: "gray",
172
172
  tableResizer: "cornsilk",
173
173
  tableResizerOutline: "darkblue",
174
174
  textColor: "hsl(216, 10%, 20%)",
175
175
  resizerPosition: "#fff",
176
- resizerPositionBackground: " #333",
176
+ resizerPositionBackground: "#333",
177
+ linkColor: "#0000ff",
177
178
  }
178
179
 
179
180
  const CSS = {
@@ -194,11 +195,15 @@ const CSS = {
194
195
  --text-color: #000000;
195
196
  --resizer-position: #ffffff;
196
197
  --resizer-position-background: #333;
198
+ --link-color: blue;
197
199
  }
198
200
  `,
199
201
  IFRAME_TRIBUTE: `.tribute{height:auto;max-height:300px;max-width:500px;overflow:auto;display:block;z-index:100;border:1px solid var(--base-white-dark)}.tribute ul{margin:2px 0 0;padding:0;list-style:none;background-color:var(--mention); color:var(--text-color)}.tribute .category{color:var(--text-color); padding:5px;color:var(--primary);background-color:var(--base-white)}.tribute .item{padding:5px;cursor:pointer}.tribute .highlight{background-color:var(--mention-highlight)}`,
200
202
  IFRAME_BODY: `
201
203
  body { margin: 0}
204
+ a {
205
+ color: var(--link-color);
206
+ }
202
207
  ::-webkit-scrollbar {
203
208
  width: 6px;
204
209
  height: 6px;
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import './style.css'
2
2
  import { CLASSES, CSS, CSS_VARIABLES, ERRORS, REGEX, SVG } from "./constant"
3
3
  import PLUGINS, { applyOrderList, applyTextFormat } from './plugin';
4
- import { showAnchorPopover, changeAllToolbarState, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, debounce, destroyImageResizer, destroyTableEditPlugin, initImageResizer, initTableEditPlugin, makeToolbarButton, makeToolbarColor, makeToolbarDropdown, makeToolbarSelect, rgbToHex, updateTableResizerPosition, destroyAnchorPopover, changeToolbarHtmlByName, showRemoteCursor, syncRemoteChangesDebounce, applyRemoteChanges, updateCursorPositionDebounce } from './utilities';
4
+ import { showAnchorPopover, changeAllToolbarState, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, debounce, destroyImageResizer, destroyTableEditPlugin, initImageResizer, initTableEditPlugin, makeToolbarButton, makeToolbarColor, makeToolbarDropdown, makeToolbarSelect, rgbToHex, updateTableResizerPosition, destroyAnchorPopover, changeToolbarHtmlByName, showRemoteCursor, syncRemoteChangesDebounce, applyRemoteChanges, updateCursorPositionDebounce, buildTributeValues, updateHeight } from './utilities';
5
5
 
6
6
  class LeksyEditor {
7
7
 
@@ -21,6 +21,9 @@ class LeksyEditor {
21
21
  * @property {String} tenorApiKey
22
22
  * @property {Object} cssVariables
23
23
  * @property {Boolean} disablePastedColorStyles
24
+ * @property {Boolean} autoHeight
25
+ * @property {String} maxHeight
26
+ * @property {String} minHeight
24
27
  */
25
28
  /**
26
29
  *
@@ -46,7 +49,7 @@ class LeksyEditor {
46
49
  base: baseElement,
47
50
  toolbar: {},
48
51
  },
49
- state: { isCodeViewOpen: false, page: 1, totalPages: null, next: null },
52
+ state: { isCodeViewOpen: false, page: 1, totalPages: null, next: null, tribute: null },
50
53
  html: options.value || '<div><br></div>',
51
54
  cursor: {
52
55
  startIndex: 0,
@@ -127,6 +130,7 @@ class LeksyEditor {
127
130
  }
128
131
  },
129
132
  onChange: (html = core.elements.editor.innerHTML) => {
133
+ updateHeight(core, options)
130
134
  updateTableResizerPosition(options, core)
131
135
  if (html === '<br>' || html === '<div><br></div>') html = ''
132
136
  core.html = html;
@@ -243,6 +247,7 @@ class LeksyEditor {
243
247
  setContents: (html) => {
244
248
  core.html = html
245
249
  core.elements.editor.innerHTML = html
250
+ updateHeight(core, options)
246
251
  },
247
252
  getContents: () => core.html,
248
253
  onChange: () => { },
@@ -253,6 +258,46 @@ class LeksyEditor {
253
258
  uploadVideo: async () => { },
254
259
  focus: () => { core.elements.editor.focus() },
255
260
  getCore: () => core,
261
+ updateCssVariables: (newVariables) => {
262
+ const variableMap = {
263
+ primary: '--primary',
264
+ midDarker: '--white-mid-darker',
265
+ baseWhite: '--base-white',
266
+ whiteDark: '--base-white-dark',
267
+ shadow: '--shadow',
268
+ resizer: '--resizer',
269
+ resizerBackground: '--resizer-background',
270
+ mention: '--mention',
271
+ mentionHighlight: '--mention-highlight',
272
+ dashedBorder: '--dashed-border',
273
+ tableResizer: '--table-resizer',
274
+ tableResizerOutline: '--table-resizer-outline',
275
+ textColor: '--text-color',
276
+ resizerPosition: '--resizer-position',
277
+ resizerPositionBackground: '--resizer-position-background',
278
+ linkColor: '--link-color',
279
+ };
280
+
281
+ const mergedVariables = {
282
+ ...CSS_VARIABLES,
283
+ ...newVariables
284
+ };
285
+
286
+ Object.keys(variableMap).forEach(key => {
287
+ const cssVar = variableMap[key];
288
+ const value = mergedVariables[key];
289
+
290
+ if (value !== undefined) {
291
+ core.elements.iframeWindow.documentElement.style.setProperty(cssVar, value);
292
+ }
293
+ });
294
+ },
295
+ updateLabels: (labels = []) => {
296
+ if (!core.state.tribute) return;
297
+
298
+ const values = buildTributeValues(labels);
299
+ core.state.tribute.append(0, values, true);
300
+ },
256
301
  getLocalCursor: () => core.cursor,
257
302
  setRemoteCursor: (user, cursor) => showRemoteCursor(core, user, cursor),
258
303
  onLocalCursorChange: () => { },
@@ -477,10 +522,15 @@ class LeksyEditor {
477
522
  childList: true,
478
523
  subtree: true
479
524
  });
525
+
526
+ // font / layout reflow
527
+ const ro = new ResizeObserver(() => {
528
+ updateHeight(core, options)
529
+ });
530
+ ro.observe(contentEditableDiv);
480
531
  }
481
532
 
482
533
  /***************************** for css theme variables ********************************* */
483
-
484
534
  if (options.cssVariables) {
485
535
  const cssVariable = options.cssVariables;
486
536
  const _CSS_VARIABLES = structuredClone(CSS_VARIABLES)
@@ -507,13 +557,19 @@ class LeksyEditor {
507
557
  --text-color: ${_CSS_VARIABLES.textColor};
508
558
  --resizer-position: ${_CSS_VARIABLES.resizerPosition};
509
559
  --resizer-position-background: ${_CSS_VARIABLES.resizerPositionBackground};
560
+ --link-color: ${_CSS_VARIABLES.linkColor};
510
561
  }
511
562
  `;
512
563
  }
513
564
 
514
565
  const iframe = document.createElement('iframe');
515
566
  iframe.style.flex = '1 1 auto';
516
- iframe.style.height = '250px'
567
+
568
+ if (options.autoHeight) iframe.style.height = 'auto';
569
+ else iframe.style.height = options.minHeight ?? '250px';
570
+ iframe.style.minHeight = options.minHeight ?? '250px';
571
+ if (options.maxHeight) iframe.style.maxHeight = options.maxHeight;
572
+
517
573
  iframe.style.border = '0';
518
574
  core.elements.base.appendChild(iframe)
519
575
 
@@ -554,6 +610,11 @@ class LeksyEditor {
554
610
  contentEditableDiv.spellcheck = !!options.spellcheck
555
611
  contentEditableDiv.innerHTML = core.html;
556
612
  contentEditableDiv.className = 'content-editable';
613
+ if (options.autoHeight) contentEditableDiv.style.height = 'auto';
614
+ else contentEditableDiv.style.height = options.minHeight ?? '250px';
615
+ contentEditableDiv.style.minHeight = options.minHeight ?? '250px';
616
+ if (options.maxHeight) contentEditableDiv.style.maxHeight = options.maxHeight;
617
+ if (options.autoHeight) iframe.contentDocument.body.style.overflow = 'hidden';
557
618
 
558
619
  contentEditableDiv.oninput = (e) => {
559
620
  core.onChange(e.target.innerHTML)
@@ -718,18 +779,8 @@ class LeksyEditor {
718
779
  script.src = "https://cdnjs.cloudflare.com/ajax/libs/tributejs/5.1.3/tribute.js";
719
780
 
720
781
  script.onload = () => {
721
- const tribute = new iframe.contentWindow.Tribute({
722
- values: options.labels.flatMap(category => {
723
- const fields = [
724
- { key: category.name, isCategory: true },
725
- ...category.fields.map(field => ({
726
- key: field.name,
727
- value: field.value,
728
- }))
729
- ];
730
-
731
- return fields
732
- }),
782
+ core.state.tribute = new iframe.contentWindow.Tribute({
783
+ values: buildTributeValues(options.labels),
733
784
  noMatchTemplate: () => '<li>No match found</li>',
734
785
  menuItemTemplate: (item) => {
735
786
  if (item.original.isCategory) return `<div class="category">${item.original.key}</div>`;
@@ -741,7 +792,7 @@ class LeksyEditor {
741
792
  return `<span spellcheck="false" contentEditable="false" data-id="${item.original.value}">${item.original.key}</span>`;
742
793
  },
743
794
  });
744
- tribute.attach(contentEditableDiv);
795
+ core.state.tribute.attach(contentEditableDiv);
745
796
  makeObserver()
746
797
  };
747
798
  iframeDoc.body.appendChild(script);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leksy-editor",
3
- "version": "1.0.21",
3
+ "version": "1.1.0",
4
4
  "description": "Leksy Editor is an alternative to traditional WYSIWYG editors, designed primarily for creating mail templates, blogs, and documents without any content manipulation.",
5
5
  "main": "index.js",
6
6
  "directories": {
package/plugin.js CHANGED
@@ -542,22 +542,32 @@ const PLUGINS = {
542
542
  }
543
543
  }
544
544
  const body = document.createElement('div')
545
+ const linkLabel = document.createElement("label");
546
+ linkLabel.innerText = "Link";
547
+ linkLabel.style.display = "block";
548
+ linkLabel.style.paddingLeft = "4px";
549
+
545
550
  const inputLink = document.createElement('input');
546
551
  inputLink.value = anchorLink
547
552
  inputLink.type = 'text'
548
553
  inputLink.placeholder = 'Insert link, mail, phone no'
549
554
  inputLink.addEventListener('keydown', onInputKeydown);
550
555
 
556
+ const textLabel = document.createElement("label");
557
+ textLabel.innerText = "Link Text";
558
+ textLabel.style.display = "block";
559
+ textLabel.style.paddingLeft = "4px";
560
+ textLabel.style.marginTop = "8px";
561
+
551
562
  const inputText = document.createElement('input');
552
563
  inputText.value = anchorText
553
564
  inputText.type = 'text'
554
- inputText.style.marginTop = '8px'
555
565
  inputText.placeholder = 'Link text'
556
566
  inputText.addEventListener('keydown', onInputKeydown);
557
567
 
558
568
  const span = document.createElement('span');
559
569
  span.className = 'warning'
560
- body.append(inputLink, inputText, span);
570
+ body.append(linkLabel, inputLink, span, textLabel, inputText);
561
571
 
562
572
  const footer = document.createElement('div')
563
573
  const button = document.createElement('button')
@@ -622,7 +632,7 @@ const PLUGINS = {
622
632
  modal.close()
623
633
  core.elements.editor.focus();
624
634
  } else {
625
- span.innerText = 'Invalid link.'
635
+ span.innerText = 'Invalid URL'
626
636
  }
627
637
  };
628
638
  core.updateCaretPosition()
@@ -740,9 +750,9 @@ const PLUGINS = {
740
750
  const uploadBtn = document.createElement('button');
741
751
  uploadBtn.innerText = 'Choose Image';
742
752
  uploadBtn.type = 'button';
743
- uploadBtn.style.border = '2px solid grey';
753
+ uploadBtn.style.border = '1px solid grey';
744
754
  uploadBtn.style.padding = '4px';
745
- uploadBtn.style.borderRadius = '8px';
755
+ uploadBtn.style.borderRadius = '6px';
746
756
 
747
757
 
748
758
  // Span to show selected file name
@@ -754,17 +764,21 @@ const PLUGINS = {
754
764
  fileInput.click(); // Trigger file input
755
765
  });
756
766
 
767
+ const altLabel = document.createElement("label");
768
+ altLabel.innerText = "Alternative Text";
769
+ altLabel.style.display = "block";
770
+ altLabel.style.paddingLeft = "4px";
771
+ altLabel.style.marginTop = '10px';
757
772
 
758
773
  const altInput = document.createElement('input');
759
774
  altInput.type = 'text';
760
775
  altInput.placeholder = 'Alternative Text';
761
- altInput.style.marginTop = '10px';
762
776
  altInput.addEventListener('keydown', onInputKeydown);
763
777
 
764
778
  const span = document.createElement('span');
765
779
  span.className = 'warning';
766
780
 
767
- body.append(fileInput, uploadBtn, fileNameSpan, altInput, span);
781
+ body.append(fileInput, uploadBtn, fileNameSpan, altLabel, altInput, span);
768
782
 
769
783
  const footer = document.createElement('div');
770
784
  const button = document.createElement('button');
@@ -835,22 +849,34 @@ const PLUGINS = {
835
849
  }
836
850
 
837
851
  const body = document.createElement('div')
852
+
853
+ const linkLabel = document.createElement("label");
854
+ linkLabel.innerText = "Link";
855
+ linkLabel.style.display = "block";
856
+ linkLabel.style.paddingLeft = "4px";
857
+
838
858
  const input = document.createElement('input');
839
859
  input.value = ''
840
860
  input.type = 'text'
841
861
  input.placeholder = 'Insert the link'
842
862
  input.addEventListener('keydown', onInputKeydown);
843
863
 
864
+ const textLabel = document.createElement("label");
865
+ textLabel.innerText = "Alternative Text";
866
+ textLabel.style.display = "block";
867
+ textLabel.style.marginTop = "8px";
868
+ textLabel.style.paddingLeft = "4px";
869
+
844
870
  const altInput = document.createElement('input');
845
871
  altInput.value = ''
846
- altInput.style.marginTop = '10px'
872
+ altInput.style.marginTop = '4px'
847
873
  altInput.type = 'text'
848
874
  altInput.placeholder = 'Alternative Text'
849
875
  altInput.addEventListener('keydown', onInputKeydown);
850
876
 
851
877
  const span = document.createElement('span');
852
878
  span.className = 'warning'
853
- body.append(input, altInput, span);
879
+ body.append(linkLabel, input, span, textLabel, altInput);
854
880
 
855
881
  const footer = document.createElement('div')
856
882
  const button = document.createElement('button')
@@ -1085,6 +1111,10 @@ const PLUGINS = {
1085
1111
  body.className = "link"
1086
1112
 
1087
1113
  // Input for Embed URL
1114
+ const embedLabel = document.createElement("label");
1115
+ embedLabel.innerText = "Embed URL";
1116
+ embedLabel.style.display = "block";
1117
+ embedLabel.style.paddingLeft = "4px";
1088
1118
  const urlInput = document.createElement('input');
1089
1119
  urlInput.type = 'text';
1090
1120
  urlInput.placeholder = 'Enter Embed URL';
@@ -1092,6 +1122,10 @@ const PLUGINS = {
1092
1122
  urlInput.addEventListener('keydown', onInputKeydown);
1093
1123
 
1094
1124
  // Input for Width (%)
1125
+ const widthLabel = document.createElement("label");
1126
+ widthLabel.innerText = "Width";
1127
+ widthLabel.style.display = "block";
1128
+ widthLabel.style.paddingLeft = "4px";
1095
1129
  const widthInput = document.createElement('input');
1096
1130
  widthInput.type = 'text';
1097
1131
  widthInput.placeholder = 'Width (px)';
@@ -1099,6 +1133,10 @@ const PLUGINS = {
1099
1133
  widthInput.value = "400"; // Default to 400px
1100
1134
 
1101
1135
  // Input for Height (%)
1136
+ const heightLabel = document.createElement("label");
1137
+ heightLabel.innerText = "Height";
1138
+ heightLabel.style.display = "block";
1139
+ heightLabel.style.paddingLeft = "4px";
1102
1140
  const heightInput = document.createElement('input');
1103
1141
  heightInput.type = 'text';
1104
1142
  heightInput.placeholder = 'Height (px)';
@@ -1109,7 +1147,7 @@ const PLUGINS = {
1109
1147
  const span = document.createElement('span');
1110
1148
  span.className = 'warning';
1111
1149
 
1112
- body.append(urlInput, widthInput, heightInput, span);
1150
+ body.append(embedLabel, urlInput, widthLabel, widthInput, heightLabel, heightInput, span);
1113
1151
 
1114
1152
  const footer = document.createElement('div');
1115
1153
  const button = document.createElement('button');
package/utilities.js CHANGED
@@ -1859,23 +1859,33 @@ const editAnchorTag = (event, core, options) => {
1859
1859
 
1860
1860
  const body = document.createElement("div");
1861
1861
 
1862
+ const linkLabel = document.createElement("label");
1863
+ linkLabel.innerText = "Link";
1864
+ linkLabel.style.display = "block";
1865
+ linkLabel.style.paddingLeft = "4px";
1866
+
1862
1867
  // Link Input
1863
1868
  const inputLink = document.createElement("input");
1864
1869
  inputLink.value = anchorLink;
1865
1870
  inputLink.type = "text";
1866
- inputLink.placeholder = "Insert the link";
1871
+ inputLink.placeholder = "Insert link, mail, phone no";
1872
+
1873
+ const textLabel = document.createElement("label");
1874
+ textLabel.innerText = "Link Text";
1875
+ textLabel.style.display = "block";
1876
+ textLabel.style.marginTop = "8px";
1877
+ textLabel.style.paddingLeft = "4px";
1867
1878
 
1868
1879
  // Text Input
1869
1880
  const inputText = document.createElement("input");
1870
1881
  inputText.value = anchorText;
1871
1882
  inputText.type = "text";
1872
- inputText.style.marginTop = "8px";
1873
1883
  inputText.placeholder = "Link text";
1874
1884
 
1875
1885
  const span = document.createElement("span");
1876
1886
  span.className = "warning";
1877
1887
 
1878
- body.append(inputLink, inputText, span);
1888
+ body.append(linkLabel, inputLink, span, textLabel, inputText);
1879
1889
 
1880
1890
  // Footer (Buttons)
1881
1891
  const footer = document.createElement("div");
@@ -1944,10 +1954,10 @@ const destroyAnchorPopover = (core) => {
1944
1954
  };
1945
1955
 
1946
1956
  function getCharIndex(root, node, offset) {
1947
- const range = document.createRange();
1948
- range.selectNodeContents(root);
1949
- range.setEnd(node, offset);
1950
- return range.toString().length;
1957
+ const range = document.createRange();
1958
+ range.selectNodeContents(root);
1959
+ range.setEnd(node, offset);
1960
+ return range.toString().length;
1951
1961
  }
1952
1962
 
1953
1963
  const getNodeAndOffsetFromIndex = (root, index) => {
@@ -2155,43 +2165,43 @@ const syncRemoteChanges = (core) => {
2155
2165
  const syncRemoteChangesDebounce = debounce(syncRemoteChanges, 50)
2156
2166
 
2157
2167
  const applyRemoteChanges = (core, diffs) => {
2158
- const editor = core.elements.editor;
2159
- const iframeWindow = core.elements.iframeWindow;
2160
- if (!editor || !diffs?.diffs) return;
2161
-
2162
- const selection = iframeWindow.getSelection();
2163
- let saved = null;
2164
-
2165
- if (selection && selection.rangeCount > 0) {
2166
- const range = selection.getRangeAt(0);
2167
- if (editor.contains(range.startContainer) && editor.contains(range.endContainer)) {
2168
- // Save selection as indexes (pre-change)
2169
- saved = {
2170
- start: getCharIndex(editor, range.startContainer, range.startOffset),
2171
- end: getCharIndex(editor, range.endContainer, range.endOffset),
2172
- };
2173
- }
2174
- }
2175
-
2176
- requestAnimationFrame(() => {
2177
- dd.apply(editor, diffs.diffs); // Apply patch
2178
- core.state.previousDOM = editor.cloneNode(true); // Update snapshot
2179
-
2180
- if (saved) {
2181
- const newStart = getNodeAndOffsetFromIndex(editor, saved.start);
2182
- const newEnd = getNodeAndOffsetFromIndex(editor, saved.end);
2183
- if (newStart && newEnd) {
2184
- const newRange = iframeWindow.createRange();
2185
- newRange.setStart(newStart.node, newStart.offset);
2186
- newRange.setEnd(newEnd.node, newEnd.offset);
2187
- const sel = iframeWindow.getSelection();
2188
- if (sel) {
2189
- sel.removeAllRanges();
2190
- sel.addRange(newRange);
2168
+ const editor = core.elements.editor;
2169
+ const iframeWindow = core.elements.iframeWindow;
2170
+ if (!editor || !diffs?.diffs) return;
2171
+
2172
+ const selection = iframeWindow.getSelection();
2173
+ let saved = null;
2174
+
2175
+ if (selection && selection.rangeCount > 0) {
2176
+ const range = selection.getRangeAt(0);
2177
+ if (editor.contains(range.startContainer) && editor.contains(range.endContainer)) {
2178
+ // Save selection as indexes (pre-change)
2179
+ saved = {
2180
+ start: getCharIndex(editor, range.startContainer, range.startOffset),
2181
+ end: getCharIndex(editor, range.endContainer, range.endOffset),
2182
+ };
2191
2183
  }
2192
- }
2193
2184
  }
2194
- });
2185
+
2186
+ requestAnimationFrame(() => {
2187
+ dd.apply(editor, diffs.diffs); // Apply patch
2188
+ core.state.previousDOM = editor.cloneNode(true); // Update snapshot
2189
+
2190
+ if (saved) {
2191
+ const newStart = getNodeAndOffsetFromIndex(editor, saved.start);
2192
+ const newEnd = getNodeAndOffsetFromIndex(editor, saved.end);
2193
+ if (newStart && newEnd) {
2194
+ const newRange = iframeWindow.createRange();
2195
+ newRange.setStart(newStart.node, newStart.offset);
2196
+ newRange.setEnd(newEnd.node, newEnd.offset);
2197
+ const sel = iframeWindow.getSelection();
2198
+ if (sel) {
2199
+ sel.removeAllRanges();
2200
+ sel.addRange(newRange);
2201
+ }
2202
+ }
2203
+ }
2204
+ });
2195
2205
  }
2196
2206
 
2197
2207
  const isLinkValid = (link) => {
@@ -2215,6 +2225,41 @@ const formatLink = (value) => {
2215
2225
  return trimmed;
2216
2226
  };
2217
2227
 
2228
+ const buildTributeValues = (labels = []) => {
2229
+ return labels.flatMap(category => ([
2230
+ { key: category.name, isCategory: true },
2231
+ ...category.fields.map(field => ({
2232
+ key: field.name,
2233
+ value: field.value,
2234
+ }))
2235
+ ]));
2236
+ };
2237
+
2238
+ function toPx(value, el = document.body) {
2239
+ if (!value) return null;
2240
+ if (typeof value === 'number') return value;
2241
+
2242
+ const tmp = document.createElement('div');
2243
+ tmp.style.width = value;
2244
+ tmp.style.position = 'absolute';
2245
+ tmp.style.visibility = 'hidden';
2246
+ el.appendChild(tmp);
2247
+ const px = tmp.getBoundingClientRect().width;
2248
+ tmp.remove();
2249
+ return px;
2250
+ }
2251
+
2252
+ const updateHeight = (core, options) => {
2253
+ if (options.autoHeight) {
2254
+ let h = core.elements.editor.scrollHeight;
2255
+ const minH = toPx(options.minHeight, core.elements.iframeContainer.parentElement);
2256
+ const maxH = toPx(options.maxHeight, core.elements.iframeContainer.parentElement);
2257
+ if (minH != null) h = Math.max(h, minH);
2258
+ if (maxH != null) h = Math.min(h, maxH);
2259
+ core.elements.iframeContainer.style.height = h + 'px';
2260
+ }
2261
+ }
2262
+
2218
2263
  export {
2219
2264
  cleanHTML,
2220
2265
  debounce,
@@ -2243,4 +2288,7 @@ export {
2243
2288
  updateCursorPositionDebounce,
2244
2289
  isLinkValid,
2245
2290
  formatLink,
2291
+ buildTributeValues,
2292
+ toPx,
2293
+ updateHeight,
2246
2294
  }