slate-angular 20.2.18 → 20.2.19

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.
@@ -1,5 +1,5 @@
1
1
  import { Editor, Range, Element, Transforms, Text as Text$1, Node, Path } from 'slate';
2
- import { EDITOR_TO_ELEMENT, NODE_TO_ELEMENT, DOMEditor, normalizeDOMPoint, isDOMSelection, IS_CHROME as IS_CHROME$1, hasShadowRoot, isDOMElement, NODE_TO_PARENT, NODE_TO_INDEX, IS_FOCUSED, isDOMNode, withDOM, NODE_TO_KEY, ELEMENT_TO_NODE, getDefaultView, EDITOR_TO_WINDOW, IS_READ_ONLY, EDITOR_TO_ON_CHANGE, TRIPLE_CLICK, isPlainTextOnlyPaste } from 'slate-dom';
2
+ import { EDITOR_TO_ELEMENT, NODE_TO_ELEMENT, DOMEditor, normalizeDOMPoint, isDOMSelection, IS_CHROME as IS_CHROME$1, hasShadowRoot, isDOMElement, NODE_TO_PARENT, NODE_TO_INDEX, isDOMNode, IS_FOCUSED, withDOM, NODE_TO_KEY, ELEMENT_TO_NODE, getDefaultView, EDITOR_TO_WINDOW, IS_READ_ONLY, EDITOR_TO_ON_CHANGE, TRIPLE_CLICK, isPlainTextOnlyPaste } from 'slate-dom';
3
3
  import { isKeyHotkey } from 'is-hotkey';
4
4
  import * as i0 from '@angular/core';
5
5
  import { TemplateRef, ComponentRef, IterableDiffers, inject, ViewContainerRef, forwardRef, HostBinding, Input, ChangeDetectionStrategy, Component, NgModule, ElementRef, ChangeDetectorRef, Directive, ViewChild } from '@angular/core';
@@ -353,95 +353,6 @@ const CustomDOMEditor = {
353
353
  }
354
354
  };
355
355
 
356
- /**
357
- * Symbols.
358
- */
359
- const PLACEHOLDER_SYMBOL = Symbol('placeholder');
360
- /**
361
- * Weak map for associating the html element with the component.
362
- */
363
- const ELEMENT_TO_COMPONENT = new WeakMap();
364
- const IS_ENABLED_VIRTUAL_SCROLL = new WeakMap();
365
- const EDITOR_TO_VIRTUAL_SCROLL_SELECTION = new WeakMap();
366
- const EDITOR_TO_AFTER_VIEW_INIT_QUEUE = new WeakMap();
367
-
368
- const AngularEditor = {
369
- ...CustomDOMEditor,
370
- /**
371
- * handle editor error.
372
- */
373
- onError(errorData) {
374
- if (errorData.nativeError) {
375
- throw errorData.nativeError;
376
- }
377
- },
378
- /**
379
- * onKeydown hook.
380
- */
381
- onKeydown(editor, data) {
382
- editor.onKeydown(data);
383
- },
384
- /**
385
- * onClick hook.
386
- */
387
- onClick(editor, data) {
388
- editor.onClick(data);
389
- },
390
- deleteCutData(editor) {
391
- editor.deleteCutData();
392
- },
393
- isLeafBlock(editor, node) {
394
- return Element.isElement(node) && !editor.isInline(node) && Editor.hasInlines(editor, node);
395
- },
396
- /**
397
- * move native selection to card-left or card-right
398
- * @param editor
399
- * @param blockCardNode
400
- * @param options
401
- */
402
- moveBlockCard(editor, blockCardNode, options) {
403
- const cursorNode = AngularEditor.getCardCursorNode(editor, blockCardNode, options);
404
- const window = AngularEditor.getWindow(editor);
405
- const domSelection = window.getSelection();
406
- domSelection.setBaseAndExtent(cursorNode, 1, cursorNode, 1);
407
- },
408
- /**
409
- * move slate selection to card-left or card-right
410
- * @param editor
411
- * @param path
412
- * @param options
413
- */
414
- moveBlockCardCursor(editor, path, options) {
415
- const cursor = {
416
- path,
417
- offset: options.direction === 'left' ? FAKE_LEFT_BLOCK_CARD_OFFSET : FAKE_RIGHT_BLOCK_CARD_OFFSET
418
- };
419
- Transforms.select(editor, { anchor: cursor, focus: cursor });
420
- },
421
- focus: (editor, options = { retries: 5 }) => {
422
- // Return if already focused
423
- if (IS_FOCUSED.get(editor)) {
424
- return;
425
- }
426
- // Return if no dom node is associated with the editor, which means the editor is not yet mounted
427
- // or has been unmounted. This can happen especially, while retrying to focus the editor.
428
- if (!EDITOR_TO_ELEMENT.get(editor)) {
429
- return;
430
- }
431
- IS_FOCUSED.set(editor, true);
432
- const el = DOMEditor.toDOMNode(editor, editor);
433
- const root = DOMEditor.findDocumentOrShadowRoot(editor);
434
- if (root.activeElement !== el) {
435
- // IS_FOCUSED should be set before calling el.focus() to ensure that
436
- // FocusedContext is updated to the correct value
437
- el.focus({ preventScroll: true });
438
- }
439
- },
440
- isEnabledVirtualScroll(editor) {
441
- return IS_ENABLED_VIRTUAL_SCROLL.get(editor);
442
- }
443
- };
444
-
445
356
  const IS_IOS = typeof navigator !== 'undefined' &&
446
357
  typeof window !== 'undefined' &&
447
358
  /iPad|iPhone|iPod/.test(navigator.userAgent) &&
@@ -480,493 +391,28 @@ const SLATE_DEBUG_KEY = '__SLATE_DEBUG__';
480
391
  const SLATE_DEBUG_KEY_SCROLL_TOP = '__SLATE_DEBUG_SCROLL_TOP__';
481
392
 
482
393
  /**
483
- * Hotkey mappings for each platform.
484
- */
485
- const HOTKEYS = {
486
- bold: 'mod+b',
487
- compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
488
- moveBackward: 'left',
489
- moveForward: 'right',
490
- moveUp: 'up',
491
- moveDown: 'down',
492
- moveWordBackward: 'ctrl+left',
493
- moveWordForward: 'ctrl+right',
494
- deleteBackward: 'shift?+backspace',
495
- deleteForward: 'shift?+delete',
496
- extendBackward: 'shift+left',
497
- extendForward: 'shift+right',
498
- italic: 'mod+i',
499
- splitBlock: 'shift?+enter',
500
- undo: 'mod+z'
501
- };
502
- const APPLE_HOTKEYS = {
503
- moveLineBackward: 'opt+up',
504
- moveLineForward: 'opt+down',
505
- moveWordBackward: 'opt+left',
506
- moveWordForward: 'opt+right',
507
- deleteBackward: ['ctrl+backspace', 'ctrl+h'],
508
- deleteForward: ['ctrl+delete', 'ctrl+d'],
509
- deleteLineBackward: 'cmd+shift?+backspace',
510
- deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
511
- deleteWordBackward: 'opt+shift?+backspace',
512
- deleteWordForward: 'opt+shift?+delete',
513
- extendLineBackward: 'opt+shift+up',
514
- extendLineForward: 'opt+shift+down',
515
- redo: 'cmd+shift+z',
516
- transposeCharacter: 'ctrl+t'
517
- };
518
- const WINDOWS_HOTKEYS = {
519
- deleteWordBackward: 'ctrl+shift?+backspace',
520
- deleteWordForward: 'ctrl+shift?+delete',
521
- redo: ['ctrl+y', 'ctrl+shift+z']
522
- };
523
- /**
524
- * Create a platform-aware hotkey checker.
394
+ * Symbols.
525
395
  */
526
- const create = (key) => {
527
- const generic = HOTKEYS[key];
528
- const apple = APPLE_HOTKEYS[key];
529
- const windows = WINDOWS_HOTKEYS[key];
530
- const isGeneric = generic && isKeyHotkey(generic);
531
- const isApple = apple && isKeyHotkey(apple);
532
- const isWindows = windows && isKeyHotkey(windows);
533
- return (event) => {
534
- if (isGeneric && isGeneric(event)) {
535
- return true;
536
- }
537
- if (IS_APPLE && isApple && isApple(event)) {
538
- return true;
539
- }
540
- if (!IS_APPLE && isWindows && isWindows(event)) {
541
- return true;
542
- }
543
- return false;
544
- };
545
- };
396
+ const PLACEHOLDER_SYMBOL = Symbol('placeholder');
546
397
  /**
547
- * Hotkeys.
398
+ * Weak map for associating the html element with the component.
548
399
  */
549
- const hotkeys = {
550
- isBold: create('bold'),
551
- isCompose: create('compose'),
552
- isMoveBackward: create('moveBackward'),
553
- isMoveForward: create('moveForward'),
554
- isMoveUp: create('moveUp'),
555
- isMoveDown: create('moveDown'),
556
- isDeleteBackward: create('deleteBackward'),
557
- isDeleteForward: create('deleteForward'),
558
- isDeleteLineBackward: create('deleteLineBackward'),
559
- isDeleteLineForward: create('deleteLineForward'),
560
- isDeleteWordBackward: create('deleteWordBackward'),
561
- isDeleteWordForward: create('deleteWordForward'),
562
- isExtendBackward: create('extendBackward'),
563
- isExtendForward: create('extendForward'),
564
- isExtendLineBackward: create('extendLineBackward'),
565
- isExtendLineForward: create('extendLineForward'),
566
- isItalic: create('italic'),
567
- isMoveLineBackward: create('moveLineBackward'),
568
- isMoveLineForward: create('moveLineForward'),
569
- isMoveWordBackward: create('moveWordBackward'),
570
- isMoveWordForward: create('moveWordForward'),
571
- isRedo: create('redo'),
572
- isSplitBlock: create('splitBlock'),
573
- isTransposeCharacter: create('transposeCharacter'),
574
- isUndo: create('undo')
575
- };
576
-
577
- function isTemplateRef(value) {
578
- return value && value instanceof TemplateRef;
579
- }
580
- function isComponentType(value) {
581
- return !isTemplateRef(value);
582
- }
583
- function isFlavourType(value) {
584
- return value && value.isFlavour === true;
585
- }
400
+ const ELEMENT_TO_COMPONENT = new WeakMap();
401
+ const EDITOR_TO_VIRTUAL_SCROLL_SELECTION = new WeakMap();
402
+ const EDITOR_TO_AFTER_VIEW_INIT_QUEUE = new WeakMap();
586
403
 
587
- const shallowCompare = (obj1, obj2) => Object.keys(obj1).length === Object.keys(obj2).length &&
588
- Object.keys(obj1).every(key => obj2.hasOwnProperty(key) && obj1[key] === obj2[key]);
589
- /**
590
- * Check if a list of decorator ranges are equal to another.
591
- *
592
- * PERF: this requires the two lists to also have the ranges inside them in the
593
- * same order, but this is an okay constraint for us since decorations are
594
- * kept in order, and the odd case where they aren't is okay to re-render for.
595
- */
596
- const isDecoratorRangeListEqual = (list, another) => {
597
- if (list.length !== another.length) {
598
- return false;
599
- }
600
- for (let i = 0; i < list.length; i++) {
601
- const range = list[i];
602
- const other = another[i];
603
- const { anchor: rangeAnchor, focus: rangeFocus, ...rangeOwnProps } = range;
604
- const { anchor: otherAnchor, focus: otherFocus, ...otherOwnProps } = other;
605
- if (!Range.equals(range, other) ||
606
- range[PLACEHOLDER_SYMBOL] !== other[PLACEHOLDER_SYMBOL] ||
607
- !shallowCompare(rangeOwnProps, otherOwnProps)) {
608
- return false;
404
+ class VirtualScrollDebugOverlay {
405
+ static { this.storageKey = 'slate_virtual_scroll_debug_overlay_state'; }
406
+ static { this.minWidth = 320; }
407
+ static { this.minHeight = 240; }
408
+ static { this.defaultWidth = 410; }
409
+ static { this.defaultHeight = 480; }
410
+ static getInstance(doc) {
411
+ if (!this.instance) {
412
+ this.instance = new VirtualScrollDebugOverlay(doc);
609
413
  }
610
- }
611
- return true;
612
- };
613
-
614
- const isValid = (value) => (Element.isElement(value) && value.children.length > 0 && value.children.every(child => isValid(child))) ||
615
- Text$1.isText(value);
616
- const check = (document) => {
617
- return document.every(value => Element.isElement(value) && isValid(value));
618
- };
619
- function normalize(document) {
620
- return document.filter(value => Element.isElement(value) && isValid(value));
621
- }
622
-
623
- const createThrottleRAF = () => {
624
- let timerId = null;
625
- const throttleRAF = (fn) => {
626
- const scheduleFunc = () => {
627
- timerId = requestAnimationFrame(() => {
628
- timerId = null;
629
- fn();
630
- });
631
- };
632
- if (timerId !== null) {
633
- cancelAnimationFrame(timerId);
634
- timerId = null;
635
- }
636
- scheduleFunc();
637
- };
638
- return throttleRAF;
639
- };
640
-
641
- const isClipboardReadSupported = () => {
642
- return 'clipboard' in navigator && 'read' in navigator.clipboard;
643
- };
644
- const isClipboardWriteSupported = () => {
645
- return 'clipboard' in navigator && 'write' in navigator.clipboard;
646
- };
647
- const isClipboardWriteTextSupported = () => {
648
- return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
649
- };
650
- const isClipboardFile = (item) => {
651
- return item.types.find(i => i.match(/^image\//));
652
- };
653
- const isInvalidTable = (nodes = []) => {
654
- return nodes.some(node => node.tagName.toLowerCase() === 'tr');
655
- };
656
- const stripHtml = (html) => {
657
- // See <https://github.com/developit/preact-markup/blob/4788b8d61b4e24f83688710746ee36e7464f7bbc/src/parse-markup.js#L60-L69>
658
- const doc = document.implementation.createHTMLDocument('');
659
- doc.documentElement.innerHTML = html.trim();
660
- return doc.body.textContent || doc.body.innerText || '';
661
- };
662
- const blobAsString = (blob) => {
663
- return new Promise((resolve, reject) => {
664
- const reader = new FileReader();
665
- reader.addEventListener('loadend', () => {
666
- const text = reader.result;
667
- resolve(text);
668
- });
669
- reader.addEventListener('error', () => {
670
- reject(reader.error);
671
- });
672
- reader.readAsText(blob);
673
- });
674
- };
675
- const completeTable = (fragment) => {
676
- const result = document.createDocumentFragment();
677
- const table = document.createElement('table');
678
- result.appendChild(table);
679
- table.appendChild(fragment);
680
- return result;
681
- };
682
-
683
- const setDataTransferClipboard = (dataTransfer, htmlText) => {
684
- dataTransfer?.setData(`text/html`, htmlText);
685
- };
686
- const setDataTransferClipboardText = (data, text) => {
687
- data?.setData(`text/plain`, text);
688
- };
689
- const getDataTransferClipboard = (data) => {
690
- const html = data?.getData(`text/html`);
691
- if (html) {
692
- const htmlClipboardData = getClipboardFromHTMLText(html);
693
- if (htmlClipboardData) {
694
- return htmlClipboardData;
695
- }
696
- const textData = getDataTransferClipboardText(data);
697
- if (textData) {
698
- return {
699
- html,
700
- ...textData
701
- };
702
- }
703
- else {
704
- return { html };
705
- }
706
- }
707
- else {
708
- const textData = getDataTransferClipboardText(data);
709
- return textData;
710
- }
711
- };
712
- const getDataTransferClipboardText = (data) => {
713
- if (!data) {
714
- return null;
715
- }
716
- const text = data?.getData(`text/plain`);
717
- if (text) {
718
- const htmlClipboardData = getClipboardFromHTMLText(text);
719
- if (htmlClipboardData) {
720
- return htmlClipboardData;
721
- }
722
- }
723
- return { text };
724
- };
725
-
726
- const setNavigatorClipboard = async (htmlText, data, text = '') => {
727
- let textClipboard = text;
728
- if (isClipboardWriteSupported()) {
729
- await navigator.clipboard.write([
730
- new ClipboardItem({
731
- 'text/html': new Blob([htmlText], {
732
- type: 'text/html'
733
- }),
734
- 'text/plain': new Blob([textClipboard ?? JSON.stringify(data)], { type: 'text/plain' })
735
- })
736
- ]);
737
- }
738
- };
739
- const getNavigatorClipboard = async () => {
740
- if (!isClipboardReadSupported()) {
741
- return null;
742
- }
743
- const clipboardItems = await navigator.clipboard.read();
744
- let clipboardData = {};
745
- if (Array.isArray(clipboardItems) && clipboardItems[0] instanceof ClipboardItem) {
746
- for (const item of clipboardItems) {
747
- if (isClipboardFile(item)) {
748
- const clipboardFiles = item.types.filter(type => type.match(/^image\//));
749
- const fileBlobs = await Promise.all(clipboardFiles.map(type => item.getType(type)));
750
- const urls = fileBlobs.filter(Boolean).map(blob => URL.createObjectURL(blob));
751
- const files = await Promise.all(urls.map(async (url) => {
752
- const blob = await (await fetch(url)).blob();
753
- return new File([blob], 'file', { type: blob.type });
754
- }));
755
- clipboardData = {
756
- ...clipboardData,
757
- files
758
- };
759
- }
760
- if (item.types.includes('text/html')) {
761
- const htmlContent = await blobAsString(await item.getType('text/html'));
762
- const htmlClipboardData = getClipboardFromHTMLText(htmlContent);
763
- if (htmlClipboardData) {
764
- clipboardData = { ...clipboardData, ...htmlClipboardData };
765
- return clipboardData;
766
- }
767
- if (htmlContent && htmlContent.trim()) {
768
- clipboardData = { ...clipboardData, html: htmlContent };
769
- }
770
- }
771
- if (item.types.includes('text/plain')) {
772
- const textContent = await blobAsString(await item.getType('text/plain'));
773
- clipboardData = {
774
- ...clipboardData,
775
- text: stripHtml(textContent)
776
- };
777
- }
778
- }
779
- }
780
- return clipboardData;
781
- };
782
-
783
- const SlateFragmentAttributeKey = 'data-slate-angular-fragment';
784
- /**
785
- * Get x-slate-fragment attribute from data-slate-angular-fragment
786
- */
787
- const catchSlateFragment = /data-slate-angular-fragment="(.+?)"/m;
788
- const getSlateFragmentAttribute = (htmlData) => {
789
- const [, fragment] = htmlData.match(catchSlateFragment) || [];
790
- return fragment;
791
- };
792
- /**
793
- * Check if a DOM node is an element node.
794
- */
795
- const isDOMText = (value) => {
796
- return isDOMNode(value) && value.nodeType === 3;
797
- };
798
- /**
799
- * Get a plaintext representation of the content of a node, accounting for block
800
- * elements which get a newline appended.
801
- *
802
- * The domNode must be attached to the DOM.
803
- */
804
- const getPlainText = (domNode) => {
805
- let text = '';
806
- if (isDOMText(domNode) && domNode.nodeValue) {
807
- return domNode.nodeValue;
808
- }
809
- if (isDOMElement(domNode)) {
810
- for (const childNode of Array.from(domNode.childNodes)) {
811
- text += getPlainText(childNode);
812
- }
813
- const display = getComputedStyle(domNode).getPropertyValue('display');
814
- if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
815
- text += '\n';
816
- }
817
- }
818
- return text;
819
- };
820
- /**
821
- * Get the dom selection from Shadow Root if possible, otherwise from the document
822
- */
823
- const getSelection = (root) => {
824
- if (root.getSelection != null) {
825
- return root.getSelection();
826
- }
827
- return document.getSelection();
828
- };
829
- const getContentHeight = (element) => {
830
- if (!element)
831
- return 0;
832
- const style = window.getComputedStyle(element);
833
- const boxSizing = style.boxSizing;
834
- const height = parseFloat(style.height) || 0;
835
- const paddingTop = parseFloat(style.paddingTop) || 0;
836
- const paddingBottom = parseFloat(style.paddingBottom) || 0;
837
- const totalPadding = paddingTop + paddingBottom;
838
- const borderTop = parseFloat(style.borderTopWidth) || 0;
839
- const borderBottom = parseFloat(style.borderBottomWidth) || 0;
840
- const totalBorder = borderTop + borderBottom;
841
- let contentHeight;
842
- if (boxSizing === 'border-box') {
843
- contentHeight = height - totalPadding - totalBorder;
844
- }
845
- else {
846
- contentHeight = height;
847
- }
848
- return Math.max(contentHeight, 0);
849
- };
850
- const getZeroTextNode = () => {
851
- return document.createTextNode('\uFEFF');
852
- };
853
-
854
- const buildHTMLText = (wrapper, attach, data) => {
855
- const stringObj = JSON.stringify(data);
856
- const encoded = window.btoa(encodeURIComponent(stringObj));
857
- attach.setAttribute(SlateFragmentAttributeKey, encoded);
858
- return wrapper.innerHTML;
859
- };
860
- const getClipboardFromHTMLText = (html) => {
861
- const fragmentAttribute = getSlateFragmentAttribute(html);
862
- if (fragmentAttribute) {
863
- try {
864
- const decoded = decodeURIComponent(window.atob(fragmentAttribute));
865
- const result = JSON.parse(decoded);
866
- if (result && Array.isArray(result) && result.length > 0) {
867
- return {
868
- elements: result
869
- };
870
- }
871
- }
872
- catch (error) {
873
- console.error(error);
874
- return null;
875
- }
876
- }
877
- return null;
878
- };
879
- const createClipboardData = (html, elements, text, files) => {
880
- const data = { elements, text, html, files };
881
- return data;
882
- };
883
- const getClipboardData = async (dataTransfer) => {
884
- let clipboardData = null;
885
- if (dataTransfer) {
886
- let filesData = {};
887
- if (dataTransfer.files.length) {
888
- filesData = { ...filesData, files: Array.from(dataTransfer.files) };
889
- }
890
- clipboardData = getDataTransferClipboard(dataTransfer);
891
- return { ...clipboardData, ...filesData };
892
- }
893
- if (isClipboardReadSupported()) {
894
- return await getNavigatorClipboard();
895
- }
896
- return clipboardData;
897
- };
898
- /**
899
- * @param wrapper get wrapper.innerHTML string which will be written in clipboard
900
- * @param attach attach must be child element of wrapper which will be attached json data
901
- * @returns void
902
- */
903
- const setClipboardData = async (clipboardData, wrapper, attach, dataTransfer) => {
904
- if (!clipboardData) {
905
- return;
906
- }
907
- const { elements, text } = clipboardData;
908
- if (isClipboardWriteSupported()) {
909
- const htmlText = buildHTMLText(wrapper, attach, elements);
910
- // TODO
911
- // maybe fail to write when copy some cell in table
912
- return await setNavigatorClipboard(htmlText, elements, text);
913
- }
914
- if (dataTransfer) {
915
- const htmlText = buildHTMLText(wrapper, attach, elements);
916
- setDataTransferClipboard(dataTransfer, htmlText);
917
- setDataTransferClipboardText(dataTransfer, text);
918
- return;
919
- }
920
- const htmlText = buildHTMLText(wrapper, attach, elements);
921
- // Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
922
- // Such as contextmenu copy in Firefox.
923
- if (isClipboardWriteTextSupported()) {
924
- return await navigator.clipboard.writeText(htmlText);
925
- }
926
- else {
927
- return await fallbackCopyText(htmlText);
928
- }
929
- };
930
- const fallbackCopyText = async (text) => {
931
- return new Promise((resolve, reject) => {
932
- const textArea = document.createElement('textarea');
933
- textArea.value = text;
934
- textArea.style.position = 'fixed';
935
- textArea.style.left = '-999999px';
936
- textArea.style.top = '-999999px';
937
- textArea.style.opacity = '0';
938
- document.body.appendChild(textArea);
939
- textArea.focus();
940
- textArea.select();
941
- try {
942
- const successful = document.execCommand('copy');
943
- document.body.removeChild(textArea);
944
- if (successful) {
945
- resolve();
946
- }
947
- else {
948
- reject(new Error('execCommand error'));
949
- }
950
- }
951
- catch (err) {
952
- document.body.removeChild(textArea);
953
- reject(err);
954
- }
955
- });
956
- };
957
-
958
- class VirtualScrollDebugOverlay {
959
- static { this.storageKey = 'slate_virtual_scroll_debug_overlay_state'; }
960
- static { this.minWidth = 320; }
961
- static { this.minHeight = 240; }
962
- static { this.defaultWidth = 410; }
963
- static { this.defaultHeight = 480; }
964
- static getInstance(doc) {
965
- if (!this.instance) {
966
- this.instance = new VirtualScrollDebugOverlay(doc);
967
- }
968
- this.instance.init();
969
- return this.instance;
414
+ this.instance.init();
415
+ return this.instance;
970
416
  }
971
417
  static log(doc, type, ...args) {
972
418
  this.getInstance(doc).log(type, ...args);
@@ -1276,376 +722,958 @@ class VirtualScrollDebugOverlay {
1276
722
  this.resizeHandle = resizeHandle;
1277
723
  this.applyState();
1278
724
  }
1279
- startDrag(event) {
1280
- if (event.button !== 0) {
1281
- return;
725
+ startDrag(event) {
726
+ if (event.button !== 0) {
727
+ return;
728
+ }
729
+ if (!this.container) {
730
+ return;
731
+ }
732
+ const rect = this.container.getBoundingClientRect();
733
+ this.isDragging = true;
734
+ this.wasDragged = false;
735
+ this.dragMoved = false;
736
+ this.dragOffsetX = event.clientX - rect.left;
737
+ this.dragOffsetY = event.clientY - rect.top;
738
+ this.container.style.transition = 'none';
739
+ this.doc.addEventListener('mousemove', this.onDragging);
740
+ this.doc.addEventListener('mouseup', this.onDragEnd);
741
+ if (!this.state.collapsed) {
742
+ event.preventDefault();
743
+ }
744
+ }
745
+ startResize(event) {
746
+ if (event.button !== 0 || this.state.collapsed) {
747
+ return;
748
+ }
749
+ if (!this.container) {
750
+ return;
751
+ }
752
+ const rect = this.container.getBoundingClientRect();
753
+ this.isResizing = true;
754
+ this.resizeStartX = event.clientX;
755
+ this.resizeStartY = event.clientY;
756
+ this.resizeStartWidth = rect.width;
757
+ this.resizeStartHeight = rect.height;
758
+ this.doc.addEventListener('mousemove', this.onResizing);
759
+ this.doc.addEventListener('mouseup', this.onResizeEnd);
760
+ event.preventDefault();
761
+ event.stopPropagation();
762
+ }
763
+ applyPosition() {
764
+ if (!this.container) {
765
+ return;
766
+ }
767
+ this.container.style.left = `${this.state.left}px`;
768
+ this.container.style.top = `${this.state.top}px`;
769
+ }
770
+ applySize() {
771
+ if (!this.container) {
772
+ return;
773
+ }
774
+ const width = Math.max(VirtualScrollDebugOverlay.minWidth, this.state.width || VirtualScrollDebugOverlay.defaultWidth);
775
+ const height = Math.max(VirtualScrollDebugOverlay.minHeight, this.state.height || VirtualScrollDebugOverlay.defaultHeight);
776
+ this.state.width = width;
777
+ this.state.height = height;
778
+ this.container.style.width = `${width}px`;
779
+ this.container.style.height = `${height}px`;
780
+ }
781
+ applyCollapsedState() {
782
+ if (!this.container || !this.contentWrapper || !this.bubble || !this.collapseToggle) {
783
+ return;
784
+ }
785
+ if (this.state.collapsed) {
786
+ this.container.style.width = '36px';
787
+ this.container.style.height = '36px';
788
+ this.container.style.minWidth = '';
789
+ this.container.style.minHeight = '';
790
+ this.container.style.padding = '0';
791
+ this.container.style.borderRadius = '50%';
792
+ this.container.style.display = 'flex';
793
+ this.container.style.flexDirection = 'row';
794
+ this.container.style.alignItems = 'center';
795
+ this.container.style.justifyContent = 'center';
796
+ this.container.style.cursor = 'move';
797
+ this.contentWrapper.style.display = 'none';
798
+ this.bubble.style.display = 'flex';
799
+ this.collapseToggle.textContent = '展开';
800
+ this.resizeHandle.style.display = 'none';
801
+ }
802
+ else {
803
+ this.applySize();
804
+ this.container.style.padding = '12px';
805
+ this.container.style.borderRadius = '10px';
806
+ this.container.style.display = 'flex';
807
+ this.container.style.flexDirection = 'column';
808
+ this.container.style.gap = '10px';
809
+ this.container.style.cursor = 'default';
810
+ this.contentWrapper.style.display = 'flex';
811
+ this.bubble.style.display = 'none';
812
+ this.collapseToggle.textContent = '折叠';
813
+ this.resizeHandle.style.display = 'block';
814
+ }
815
+ }
816
+ setCollapsed(collapsed) {
817
+ this.state.collapsed = collapsed;
818
+ this.applyCollapsedState();
819
+ this.persistState();
820
+ }
821
+ applyState() {
822
+ this.applyPosition();
823
+ this.applyCollapsedState();
824
+ }
825
+ loadState() {
826
+ try {
827
+ const raw = this.doc.defaultView?.localStorage?.getItem(VirtualScrollDebugOverlay.storageKey);
828
+ if (raw) {
829
+ const parsed = JSON.parse(raw);
830
+ if (typeof parsed.left === 'number') {
831
+ this.state.left = parsed.left;
832
+ }
833
+ if (typeof parsed.top === 'number') {
834
+ this.state.top = parsed.top;
835
+ }
836
+ if (typeof parsed.collapsed === 'boolean') {
837
+ this.state.collapsed = parsed.collapsed;
838
+ }
839
+ if (typeof parsed.width === 'number') {
840
+ this.state.width = parsed.width;
841
+ }
842
+ if (typeof parsed.height === 'number') {
843
+ this.state.height = parsed.height;
844
+ }
845
+ }
846
+ }
847
+ catch {
848
+ // ignore storage errors
849
+ }
850
+ }
851
+ persistState() {
852
+ try {
853
+ this.doc.defaultView?.localStorage?.setItem(VirtualScrollDebugOverlay.storageKey, JSON.stringify(this.state));
854
+ }
855
+ catch {
856
+ // ignore storage errors
857
+ }
858
+ }
859
+ appendLog(type, ...args) {
860
+ if (!this.logList) {
861
+ return;
862
+ }
863
+ const item = this.doc.createElement('div');
864
+ item.style.display = 'flex';
865
+ item.style.gap = '6px';
866
+ item.style.alignItems = 'flex-start';
867
+ item.style.wordBreak = 'break-all';
868
+ item.style.color = type === 'warn' ? '#fbbf24' : '#9ca3af';
869
+ const time = this.doc.createElement('span');
870
+ time.textContent = new Date().toLocaleTimeString();
871
+ time.style.color = '#6b7280';
872
+ time.style.flexShrink = '0';
873
+ time.style.width = '72px';
874
+ const text = this.doc.createElement('span');
875
+ text.textContent = `[${type}] ${args.map(arg => this.formatValue(arg)).join(' ')}`;
876
+ item.appendChild(time);
877
+ item.appendChild(text);
878
+ this.logList.appendChild(item);
879
+ }
880
+ formatValue(value) {
881
+ if (typeof value === 'string') {
882
+ return value;
1282
883
  }
1283
- if (!this.container) {
1284
- return;
884
+ try {
885
+ return JSON.stringify(value);
1285
886
  }
1286
- const rect = this.container.getBoundingClientRect();
1287
- this.isDragging = true;
1288
- this.wasDragged = false;
1289
- this.dragMoved = false;
1290
- this.dragOffsetX = event.clientX - rect.left;
1291
- this.dragOffsetY = event.clientY - rect.top;
1292
- this.container.style.transition = 'none';
1293
- this.doc.addEventListener('mousemove', this.onDragging);
1294
- this.doc.addEventListener('mouseup', this.onDragEnd);
1295
- if (!this.state.collapsed) {
1296
- event.preventDefault();
887
+ catch (error) {
888
+ return String(value);
1297
889
  }
1298
890
  }
1299
- startResize(event) {
1300
- if (event.button !== 0 || this.state.collapsed) {
1301
- return;
1302
- }
1303
- if (!this.container) {
1304
- return;
891
+ setScrollTopValue(value) {
892
+ if (this.distanceInput) {
893
+ this.distanceInput.value = String(value ?? 0);
1305
894
  }
1306
- const rect = this.container.getBoundingClientRect();
1307
- this.isResizing = true;
1308
- this.resizeStartX = event.clientX;
1309
- this.resizeStartY = event.clientY;
1310
- this.resizeStartWidth = rect.width;
1311
- this.resizeStartHeight = rect.height;
1312
- this.doc.addEventListener('mousemove', this.onResizing);
1313
- this.doc.addEventListener('mouseup', this.onResizeEnd);
1314
- event.preventDefault();
1315
- event.stopPropagation();
1316
895
  }
1317
- applyPosition() {
1318
- if (!this.container) {
1319
- return;
896
+ }
897
+
898
+ const SlateFragmentAttributeKey = 'data-slate-angular-fragment';
899
+ /**
900
+ * Get x-slate-fragment attribute from data-slate-angular-fragment
901
+ */
902
+ const catchSlateFragment = /data-slate-angular-fragment="(.+?)"/m;
903
+ const getSlateFragmentAttribute = (htmlData) => {
904
+ const [, fragment] = htmlData.match(catchSlateFragment) || [];
905
+ return fragment;
906
+ };
907
+ /**
908
+ * Check if a DOM node is an element node.
909
+ */
910
+ const isDOMText = (value) => {
911
+ return isDOMNode(value) && value.nodeType === 3;
912
+ };
913
+ /**
914
+ * Get a plaintext representation of the content of a node, accounting for block
915
+ * elements which get a newline appended.
916
+ *
917
+ * The domNode must be attached to the DOM.
918
+ */
919
+ const getPlainText = (domNode) => {
920
+ let text = '';
921
+ if (isDOMText(domNode) && domNode.nodeValue) {
922
+ return domNode.nodeValue;
923
+ }
924
+ if (isDOMElement(domNode)) {
925
+ for (const childNode of Array.from(domNode.childNodes)) {
926
+ text += getPlainText(childNode);
927
+ }
928
+ const display = getComputedStyle(domNode).getPropertyValue('display');
929
+ if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
930
+ text += '\n';
1320
931
  }
1321
- this.container.style.left = `${this.state.left}px`;
1322
- this.container.style.top = `${this.state.top}px`;
1323
932
  }
1324
- applySize() {
1325
- if (!this.container) {
1326
- return;
933
+ return text;
934
+ };
935
+ /**
936
+ * Get the dom selection from Shadow Root if possible, otherwise from the document
937
+ */
938
+ const getSelection = (root) => {
939
+ if (root.getSelection != null) {
940
+ return root.getSelection();
941
+ }
942
+ return document.getSelection();
943
+ };
944
+ const getContentHeight = (element) => {
945
+ if (!element)
946
+ return 0;
947
+ const style = window.getComputedStyle(element);
948
+ const boxSizing = style.boxSizing;
949
+ const height = parseFloat(style.height) || 0;
950
+ const paddingTop = parseFloat(style.paddingTop) || 0;
951
+ const paddingBottom = parseFloat(style.paddingBottom) || 0;
952
+ const totalPadding = paddingTop + paddingBottom;
953
+ const borderTop = parseFloat(style.borderTopWidth) || 0;
954
+ const borderBottom = parseFloat(style.borderBottomWidth) || 0;
955
+ const totalBorder = borderTop + borderBottom;
956
+ let contentHeight;
957
+ if (boxSizing === 'border-box') {
958
+ contentHeight = height - totalPadding - totalBorder;
959
+ }
960
+ else {
961
+ contentHeight = height;
962
+ }
963
+ return Math.max(contentHeight, 0);
964
+ };
965
+ const getZeroTextNode = () => {
966
+ return document.createTextNode('\uFEFF');
967
+ };
968
+
969
+ const SLATE_BLOCK_CARD_CLASS_NAME = 'slate-block-card';
970
+ class SlateBlockCard {
971
+ onInit() {
972
+ const nativeElement = document.createElement('div');
973
+ nativeElement.classList.add(SLATE_BLOCK_CARD_CLASS_NAME);
974
+ this.nativeElement = nativeElement;
975
+ this.createContent();
976
+ }
977
+ createContent() {
978
+ const leftCaret = document.createElement('span');
979
+ leftCaret.setAttribute(`card-target`, 'card-left');
980
+ leftCaret.classList.add('card-left');
981
+ leftCaret.appendChild(getZeroTextNode());
982
+ const rightCaret = document.createElement('span');
983
+ rightCaret.setAttribute(`card-target`, 'card-right');
984
+ rightCaret.classList.add('card-right');
985
+ rightCaret.appendChild(getZeroTextNode());
986
+ const center = document.createElement('div');
987
+ center.setAttribute(`card-target`, 'card-center');
988
+ this.nativeElement.appendChild(leftCaret);
989
+ this.nativeElement.appendChild(center);
990
+ this.nativeElement.appendChild(rightCaret);
991
+ this.centerContainer = center;
992
+ }
993
+ append() {
994
+ this.centerRootNodes.forEach(rootNode => !this.centerContainer.contains(rootNode) && this.centerContainer.appendChild(rootNode));
995
+ }
996
+ initializeCenter(rootNodes) {
997
+ this.centerRootNodes = rootNodes;
998
+ this.append();
999
+ }
1000
+ onDestroy() {
1001
+ this.nativeElement.remove();
1002
+ }
1003
+ }
1004
+ const getBlockCardByNativeElement = (nativeElement) => {
1005
+ const blockCardElement = nativeElement?.parentElement?.parentElement;
1006
+ if (blockCardElement && blockCardElement.classList.contains(SLATE_BLOCK_CARD_CLASS_NAME)) {
1007
+ return blockCardElement;
1008
+ }
1009
+ return null;
1010
+ };
1011
+
1012
+ const roundTo = (value, precision = 2) => {
1013
+ const factor = 10 ** precision;
1014
+ const n = Math.round(value * factor) / factor;
1015
+ return Object.is(n, -0) ? 0 : n;
1016
+ };
1017
+
1018
+ const VIRTUAL_TOP_HEIGHT_CLASS_NAME = 'virtual-top-height';
1019
+ const VIRTUAL_BOTTOM_HEIGHT_CLASS_NAME = 'virtual-bottom-height';
1020
+ const VIRTUAL_CENTER_OUTLET_CLASS_NAME = 'virtual-center-outlet';
1021
+ const isDebug = localStorage.getItem(SLATE_DEBUG_KEY) === 'true';
1022
+ const isDebugScrollTop = localStorage.getItem(SLATE_DEBUG_KEY_SCROLL_TOP) === 'true';
1023
+ const ELEMENT_KEY_TO_HEIGHTS = new WeakMap();
1024
+ const EDITOR_TO_BUSINESS_TOP = new WeakMap();
1025
+ const EDITOR_TO_VIRTUAL_SCROLL_CONFIG = new WeakMap();
1026
+ const EDITOR_TO_VIEWPORT_HEIGHT = new WeakMap();
1027
+ const EDITOR_TO_ROOT_NODE_WIDTH = new WeakMap();
1028
+ const EDITOR_TO_IS_FROM_SCROLL_TO = new WeakMap();
1029
+ const isValidNumber = (value) => {
1030
+ return typeof value === 'number' && !Number.isNaN(value);
1031
+ };
1032
+ const debugLog = (type, ...args) => {
1033
+ const doc = document;
1034
+ VirtualScrollDebugOverlay.log(doc, type, ...args);
1035
+ };
1036
+ const cacheHeightByElement = (editor, element, height) => {
1037
+ if (!AngularEditor.isEnabledVirtualScroll(editor)) {
1038
+ return;
1039
+ }
1040
+ if (!isValidNumber(height)) {
1041
+ console.error('cacheHeightByElement: height must be number', height);
1042
+ return;
1043
+ }
1044
+ const key = AngularEditor.findKey(editor, element);
1045
+ const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1046
+ heights.set(key.id, height);
1047
+ };
1048
+ const setMinHeightByElement = (editor, element, rootElementMarginBottom) => {
1049
+ if (!AngularEditor.isEnabledVirtualScroll(editor)) {
1050
+ return;
1051
+ }
1052
+ const realHeight = getCachedHeightByElement(editor, element);
1053
+ if (realHeight) {
1054
+ const nativeElement = AngularEditor.toDOMNode(editor, element);
1055
+ const blockCard = getBlockCardByNativeElement(nativeElement);
1056
+ if (blockCard) {
1057
+ const minHeight = realHeight - rootElementMarginBottom;
1058
+ blockCard.style.minHeight = minHeight + 'px';
1327
1059
  }
1328
- const width = Math.max(VirtualScrollDebugOverlay.minWidth, this.state.width || VirtualScrollDebugOverlay.defaultWidth);
1329
- const height = Math.max(VirtualScrollDebugOverlay.minHeight, this.state.height || VirtualScrollDebugOverlay.defaultHeight);
1330
- this.state.width = width;
1331
- this.state.height = height;
1332
- this.container.style.width = `${width}px`;
1333
- this.container.style.height = `${height}px`;
1334
1060
  }
1335
- applyCollapsedState() {
1336
- if (!this.container || !this.contentWrapper || !this.bubble || !this.collapseToggle) {
1061
+ };
1062
+ const clearMinHeightByElement = (editor, element) => {
1063
+ if (!AngularEditor.isEnabledVirtualScroll(editor)) {
1064
+ return;
1065
+ }
1066
+ const nativeElement = AngularEditor.toDOMNode(editor, element);
1067
+ const blockCard = getBlockCardByNativeElement(nativeElement);
1068
+ if (blockCard && blockCard.style.minHeight) {
1069
+ blockCard.style.minHeight = '';
1070
+ return true;
1071
+ }
1072
+ else {
1073
+ return false;
1074
+ }
1075
+ };
1076
+ const calcHeightByElement = (editor, element) => {
1077
+ const view = ELEMENT_TO_COMPONENT.get(element);
1078
+ if (!view) {
1079
+ return;
1080
+ }
1081
+ const height = view.calcHeight();
1082
+ cacheHeightByElement(editor, element, height);
1083
+ return height;
1084
+ };
1085
+ const measureHeightByIndics = (editor, indics, force = false) => {
1086
+ let hasChanged = false;
1087
+ indics.forEach((index, i) => {
1088
+ const element = editor.children[index];
1089
+ const preHeight = getCachedHeightByElement(editor, element);
1090
+ if (preHeight && !force) {
1091
+ if (isDebug) {
1092
+ const height = calcHeightByElement(editor, element);
1093
+ if (height !== preHeight) {
1094
+ debugLog('warn', 'calcHeightByElement: height not equal, index: ', index, 'preHeight: ', preHeight, 'height: ', height);
1095
+ }
1096
+ }
1337
1097
  return;
1338
1098
  }
1339
- if (this.state.collapsed) {
1340
- this.container.style.width = '36px';
1341
- this.container.style.height = '36px';
1342
- this.container.style.minWidth = '';
1343
- this.container.style.minHeight = '';
1344
- this.container.style.padding = '0';
1345
- this.container.style.borderRadius = '50%';
1346
- this.container.style.display = 'flex';
1347
- this.container.style.flexDirection = 'row';
1348
- this.container.style.alignItems = 'center';
1349
- this.container.style.justifyContent = 'center';
1350
- this.container.style.cursor = 'move';
1351
- this.contentWrapper.style.display = 'none';
1352
- this.bubble.style.display = 'flex';
1353
- this.collapseToggle.textContent = '展开';
1354
- this.resizeHandle.style.display = 'none';
1099
+ const currentHeight = calcHeightByElement(editor, element);
1100
+ if (isValidNumber(currentHeight) && currentHeight !== preHeight) {
1101
+ hasChanged = true;
1355
1102
  }
1356
- else {
1357
- this.applySize();
1358
- this.container.style.padding = '12px';
1359
- this.container.style.borderRadius = '10px';
1360
- this.container.style.display = 'flex';
1361
- this.container.style.flexDirection = 'column';
1362
- this.container.style.gap = '10px';
1363
- this.container.style.cursor = 'default';
1364
- this.contentWrapper.style.display = 'flex';
1365
- this.bubble.style.display = 'none';
1366
- this.collapseToggle.textContent = '折叠';
1367
- this.resizeHandle.style.display = 'block';
1103
+ if (isDebug && isValidNumber(currentHeight)) {
1104
+ debugLog('log', 'measureHeightByIndics: index: ', index, 'preHeight: ', preHeight, 'height: ', currentHeight);
1368
1105
  }
1106
+ });
1107
+ return hasChanged;
1108
+ };
1109
+ const getBusinessTop = (editor) => {
1110
+ return EDITOR_TO_BUSINESS_TOP.get(editor) ?? 0;
1111
+ };
1112
+ const getViewportHeight = (editor) => {
1113
+ return EDITOR_TO_VIEWPORT_HEIGHT.get(editor) ?? window.innerHeight;
1114
+ };
1115
+ const getScrollContainer = (editor) => {
1116
+ const config = EDITOR_TO_VIRTUAL_SCROLL_CONFIG.get(editor);
1117
+ return config?.scrollContainer || document.body;
1118
+ };
1119
+ const getCachedHeightByElement = (editor, element) => {
1120
+ const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1121
+ const key = AngularEditor.findKey(editor, element);
1122
+ const height = heights?.get(key.id);
1123
+ if (typeof height === 'number') {
1124
+ return height;
1369
1125
  }
1370
- setCollapsed(collapsed) {
1371
- this.state.collapsed = collapsed;
1372
- this.applyCollapsedState();
1373
- this.persistState();
1374
- }
1375
- applyState() {
1376
- this.applyPosition();
1377
- this.applyCollapsedState();
1126
+ if (heights?.has(key.id)) {
1127
+ console.error('getBlockHeight: invalid height value', key.id, height);
1378
1128
  }
1379
- loadState() {
1380
- try {
1381
- const raw = this.doc.defaultView?.localStorage?.getItem(VirtualScrollDebugOverlay.storageKey);
1382
- if (raw) {
1383
- const parsed = JSON.parse(raw);
1384
- if (typeof parsed.left === 'number') {
1385
- this.state.left = parsed.left;
1386
- }
1387
- if (typeof parsed.top === 'number') {
1388
- this.state.top = parsed.top;
1389
- }
1390
- if (typeof parsed.collapsed === 'boolean') {
1391
- this.state.collapsed = parsed.collapsed;
1392
- }
1393
- if (typeof parsed.width === 'number') {
1394
- this.state.width = parsed.width;
1395
- }
1396
- if (typeof parsed.height === 'number') {
1397
- this.state.height = parsed.height;
1398
- }
1129
+ return null;
1130
+ };
1131
+ const buildHeightsAndAccumulatedHeights = (editor, visibleStates) => {
1132
+ const children = (editor.children || []);
1133
+ const heights = new Array(children.length);
1134
+ const accumulatedHeights = new Array(children.length + 1);
1135
+ accumulatedHeights[0] = 0;
1136
+ for (let i = 0; i < children.length; i++) {
1137
+ const isVisible = visibleStates[i];
1138
+ let height = isVisible ? getCachedHeightByElement(editor, children[i]) : 0;
1139
+ if (height === null) {
1140
+ try {
1141
+ height = editor.getRoughHeight(children[i]);
1142
+ }
1143
+ catch (error) {
1144
+ console.error('buildHeightsAndAccumulatedHeights: getRoughHeight error', error);
1399
1145
  }
1400
1146
  }
1401
- catch {
1402
- // ignore storage errors
1403
- }
1147
+ heights[i] = height;
1148
+ accumulatedHeights[i + 1] = accumulatedHeights[i] + height;
1404
1149
  }
1405
- persistState() {
1406
- try {
1407
- this.doc.defaultView?.localStorage?.setItem(VirtualScrollDebugOverlay.storageKey, JSON.stringify(this.state));
1150
+ return { heights, accumulatedHeights };
1151
+ };
1152
+ const calculateVirtualTopHeight = (editor, startIndex, visibleStates) => {
1153
+ const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor, visibleStates);
1154
+ const virtualTopHeight = roundTo(accumulatedHeights[startIndex] ?? 0, 1);
1155
+ return virtualTopHeight;
1156
+ };
1157
+ const calcBusinessTop = (editor) => {
1158
+ const editable = AngularEditor.toDOMNode(editor, editor);
1159
+ const virtualTopElement = editable.querySelector(`.${VIRTUAL_TOP_HEIGHT_CLASS_NAME}`);
1160
+ const virtualTopBoundingTop = virtualTopElement?.getBoundingClientRect()?.top ?? 0;
1161
+ const virtualScrollConfig = EDITOR_TO_VIRTUAL_SCROLL_CONFIG.get(editor);
1162
+ const scrollContainer = virtualScrollConfig?.scrollContainer;
1163
+ const viewportBoundingTop = scrollContainer?.getBoundingClientRect()?.top ?? 0;
1164
+ const businessTop = Math.ceil(virtualTopBoundingTop) + Math.ceil(virtualScrollConfig.scrollTop) - Math.floor(viewportBoundingTop);
1165
+ EDITOR_TO_BUSINESS_TOP.set(editor, businessTop);
1166
+ if (isDebug) {
1167
+ debugLog('log', 'calcBusinessTop: ', businessTop);
1168
+ virtualTopElement.setAttribute('data-business-top', businessTop.toString());
1169
+ }
1170
+ console.log('virtualTopBoundingTop: ', virtualTopBoundingTop, 'virtualScrollConfig.scrollTop: ', virtualScrollConfig.scrollTop, 'viewportBoundingTop: ', viewportBoundingTop);
1171
+ return businessTop;
1172
+ };
1173
+ const scrollToElement = (editor, element, scrollTo) => {
1174
+ const children = editor.children;
1175
+ if (!children.length) {
1176
+ return;
1177
+ }
1178
+ const anchorIndex = children.findIndex(item => item === element);
1179
+ if (anchorIndex < 0) {
1180
+ return;
1181
+ }
1182
+ const visibleStates = editor.getAllVisibleStates();
1183
+ const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor, visibleStates);
1184
+ let businessTop = getBusinessTop(editor);
1185
+ if (businessTop === 0) {
1186
+ businessTop = calcBusinessTop(editor);
1187
+ }
1188
+ scrollTo((accumulatedHeights[anchorIndex] ?? 0) + businessTop);
1189
+ EDITOR_TO_IS_FROM_SCROLL_TO.set(editor, true);
1190
+ setTimeout(() => {
1191
+ console.log('scrollToElement: end scroll');
1192
+ EDITOR_TO_IS_FROM_SCROLL_TO.set(editor, false);
1193
+ }, 0);
1194
+ };
1195
+
1196
+ const AngularEditor = {
1197
+ ...CustomDOMEditor,
1198
+ /**
1199
+ * handle editor error.
1200
+ */
1201
+ onError(errorData) {
1202
+ if (errorData.nativeError) {
1203
+ throw errorData.nativeError;
1408
1204
  }
1409
- catch {
1410
- // ignore storage errors
1205
+ },
1206
+ /**
1207
+ * onKeydown hook.
1208
+ */
1209
+ onKeydown(editor, data) {
1210
+ editor.onKeydown(data);
1211
+ },
1212
+ /**
1213
+ * onClick hook.
1214
+ */
1215
+ onClick(editor, data) {
1216
+ editor.onClick(data);
1217
+ },
1218
+ deleteCutData(editor) {
1219
+ editor.deleteCutData();
1220
+ },
1221
+ isLeafBlock(editor, node) {
1222
+ return Element.isElement(node) && !editor.isInline(node) && Editor.hasInlines(editor, node);
1223
+ },
1224
+ /**
1225
+ * move native selection to card-left or card-right
1226
+ * @param editor
1227
+ * @param blockCardNode
1228
+ * @param options
1229
+ */
1230
+ moveBlockCard(editor, blockCardNode, options) {
1231
+ const cursorNode = AngularEditor.getCardCursorNode(editor, blockCardNode, options);
1232
+ const window = AngularEditor.getWindow(editor);
1233
+ const domSelection = window.getSelection();
1234
+ domSelection.setBaseAndExtent(cursorNode, 1, cursorNode, 1);
1235
+ },
1236
+ /**
1237
+ * move slate selection to card-left or card-right
1238
+ * @param editor
1239
+ * @param path
1240
+ * @param options
1241
+ */
1242
+ moveBlockCardCursor(editor, path, options) {
1243
+ const cursor = {
1244
+ path,
1245
+ offset: options.direction === 'left' ? FAKE_LEFT_BLOCK_CARD_OFFSET : FAKE_RIGHT_BLOCK_CARD_OFFSET
1246
+ };
1247
+ Transforms.select(editor, { anchor: cursor, focus: cursor });
1248
+ },
1249
+ focus: (editor, options = { retries: 5 }) => {
1250
+ // Return if already focused
1251
+ if (IS_FOCUSED.get(editor)) {
1252
+ return;
1411
1253
  }
1412
- }
1413
- appendLog(type, ...args) {
1414
- if (!this.logList) {
1254
+ // Return if no dom node is associated with the editor, which means the editor is not yet mounted
1255
+ // or has been unmounted. This can happen especially, while retrying to focus the editor.
1256
+ if (!EDITOR_TO_ELEMENT.get(editor)) {
1415
1257
  return;
1416
1258
  }
1417
- const item = this.doc.createElement('div');
1418
- item.style.display = 'flex';
1419
- item.style.gap = '6px';
1420
- item.style.alignItems = 'flex-start';
1421
- item.style.wordBreak = 'break-all';
1422
- item.style.color = type === 'warn' ? '#fbbf24' : '#9ca3af';
1423
- const time = this.doc.createElement('span');
1424
- time.textContent = new Date().toLocaleTimeString();
1425
- time.style.color = '#6b7280';
1426
- time.style.flexShrink = '0';
1427
- time.style.width = '72px';
1428
- const text = this.doc.createElement('span');
1429
- text.textContent = `[${type}] ${args.map(arg => this.formatValue(arg)).join(' ')}`;
1430
- item.appendChild(time);
1431
- item.appendChild(text);
1432
- this.logList.appendChild(item);
1433
- }
1434
- formatValue(value) {
1435
- if (typeof value === 'string') {
1436
- return value;
1259
+ IS_FOCUSED.set(editor, true);
1260
+ const el = DOMEditor.toDOMNode(editor, editor);
1261
+ const root = DOMEditor.findDocumentOrShadowRoot(editor);
1262
+ if (root.activeElement !== el) {
1263
+ // IS_FOCUSED should be set before calling el.focus() to ensure that
1264
+ // FocusedContext is updated to the correct value
1265
+ el.focus({ preventScroll: true });
1437
1266
  }
1438
- try {
1439
- return JSON.stringify(value);
1267
+ },
1268
+ isEnabledVirtualScroll(editor) {
1269
+ const config = EDITOR_TO_VIRTUAL_SCROLL_CONFIG.get(editor);
1270
+ return config?.enabled || false;
1271
+ }
1272
+ };
1273
+
1274
+ /**
1275
+ * Hotkey mappings for each platform.
1276
+ */
1277
+ const HOTKEYS = {
1278
+ bold: 'mod+b',
1279
+ compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
1280
+ moveBackward: 'left',
1281
+ moveForward: 'right',
1282
+ moveUp: 'up',
1283
+ moveDown: 'down',
1284
+ moveWordBackward: 'ctrl+left',
1285
+ moveWordForward: 'ctrl+right',
1286
+ deleteBackward: 'shift?+backspace',
1287
+ deleteForward: 'shift?+delete',
1288
+ extendBackward: 'shift+left',
1289
+ extendForward: 'shift+right',
1290
+ italic: 'mod+i',
1291
+ splitBlock: 'shift?+enter',
1292
+ undo: 'mod+z'
1293
+ };
1294
+ const APPLE_HOTKEYS = {
1295
+ moveLineBackward: 'opt+up',
1296
+ moveLineForward: 'opt+down',
1297
+ moveWordBackward: 'opt+left',
1298
+ moveWordForward: 'opt+right',
1299
+ deleteBackward: ['ctrl+backspace', 'ctrl+h'],
1300
+ deleteForward: ['ctrl+delete', 'ctrl+d'],
1301
+ deleteLineBackward: 'cmd+shift?+backspace',
1302
+ deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
1303
+ deleteWordBackward: 'opt+shift?+backspace',
1304
+ deleteWordForward: 'opt+shift?+delete',
1305
+ extendLineBackward: 'opt+shift+up',
1306
+ extendLineForward: 'opt+shift+down',
1307
+ redo: 'cmd+shift+z',
1308
+ transposeCharacter: 'ctrl+t'
1309
+ };
1310
+ const WINDOWS_HOTKEYS = {
1311
+ deleteWordBackward: 'ctrl+shift?+backspace',
1312
+ deleteWordForward: 'ctrl+shift?+delete',
1313
+ redo: ['ctrl+y', 'ctrl+shift+z']
1314
+ };
1315
+ /**
1316
+ * Create a platform-aware hotkey checker.
1317
+ */
1318
+ const create = (key) => {
1319
+ const generic = HOTKEYS[key];
1320
+ const apple = APPLE_HOTKEYS[key];
1321
+ const windows = WINDOWS_HOTKEYS[key];
1322
+ const isGeneric = generic && isKeyHotkey(generic);
1323
+ const isApple = apple && isKeyHotkey(apple);
1324
+ const isWindows = windows && isKeyHotkey(windows);
1325
+ return (event) => {
1326
+ if (isGeneric && isGeneric(event)) {
1327
+ return true;
1440
1328
  }
1441
- catch (error) {
1442
- return String(value);
1329
+ if (IS_APPLE && isApple && isApple(event)) {
1330
+ return true;
1443
1331
  }
1444
- }
1445
- setScrollTopValue(value) {
1446
- if (this.distanceInput) {
1447
- this.distanceInput.value = String(value ?? 0);
1332
+ if (!IS_APPLE && isWindows && isWindows(event)) {
1333
+ return true;
1448
1334
  }
1449
- }
1335
+ return false;
1336
+ };
1337
+ };
1338
+ /**
1339
+ * Hotkeys.
1340
+ */
1341
+ const hotkeys = {
1342
+ isBold: create('bold'),
1343
+ isCompose: create('compose'),
1344
+ isMoveBackward: create('moveBackward'),
1345
+ isMoveForward: create('moveForward'),
1346
+ isMoveUp: create('moveUp'),
1347
+ isMoveDown: create('moveDown'),
1348
+ isDeleteBackward: create('deleteBackward'),
1349
+ isDeleteForward: create('deleteForward'),
1350
+ isDeleteLineBackward: create('deleteLineBackward'),
1351
+ isDeleteLineForward: create('deleteLineForward'),
1352
+ isDeleteWordBackward: create('deleteWordBackward'),
1353
+ isDeleteWordForward: create('deleteWordForward'),
1354
+ isExtendBackward: create('extendBackward'),
1355
+ isExtendForward: create('extendForward'),
1356
+ isExtendLineBackward: create('extendLineBackward'),
1357
+ isExtendLineForward: create('extendLineForward'),
1358
+ isItalic: create('italic'),
1359
+ isMoveLineBackward: create('moveLineBackward'),
1360
+ isMoveLineForward: create('moveLineForward'),
1361
+ isMoveWordBackward: create('moveWordBackward'),
1362
+ isMoveWordForward: create('moveWordForward'),
1363
+ isRedo: create('redo'),
1364
+ isSplitBlock: create('splitBlock'),
1365
+ isTransposeCharacter: create('transposeCharacter'),
1366
+ isUndo: create('undo')
1367
+ };
1368
+
1369
+ function isTemplateRef(value) {
1370
+ return value && value instanceof TemplateRef;
1371
+ }
1372
+ function isComponentType(value) {
1373
+ return !isTemplateRef(value);
1374
+ }
1375
+ function isFlavourType(value) {
1376
+ return value && value.isFlavour === true;
1450
1377
  }
1451
1378
 
1452
- const SLATE_BLOCK_CARD_CLASS_NAME = 'slate-block-card';
1453
- class SlateBlockCard {
1454
- onInit() {
1455
- const nativeElement = document.createElement('div');
1456
- nativeElement.classList.add(SLATE_BLOCK_CARD_CLASS_NAME);
1457
- this.nativeElement = nativeElement;
1458
- this.createContent();
1459
- }
1460
- createContent() {
1461
- const leftCaret = document.createElement('span');
1462
- leftCaret.setAttribute(`card-target`, 'card-left');
1463
- leftCaret.classList.add('card-left');
1464
- leftCaret.appendChild(getZeroTextNode());
1465
- const rightCaret = document.createElement('span');
1466
- rightCaret.setAttribute(`card-target`, 'card-right');
1467
- rightCaret.classList.add('card-right');
1468
- rightCaret.appendChild(getZeroTextNode());
1469
- const center = document.createElement('div');
1470
- center.setAttribute(`card-target`, 'card-center');
1471
- this.nativeElement.appendChild(leftCaret);
1472
- this.nativeElement.appendChild(center);
1473
- this.nativeElement.appendChild(rightCaret);
1474
- this.centerContainer = center;
1475
- }
1476
- append() {
1477
- this.centerRootNodes.forEach(rootNode => !this.centerContainer.contains(rootNode) && this.centerContainer.appendChild(rootNode));
1478
- }
1479
- initializeCenter(rootNodes) {
1480
- this.centerRootNodes = rootNodes;
1481
- this.append();
1379
+ const shallowCompare = (obj1, obj2) => Object.keys(obj1).length === Object.keys(obj2).length &&
1380
+ Object.keys(obj1).every(key => obj2.hasOwnProperty(key) && obj1[key] === obj2[key]);
1381
+ /**
1382
+ * Check if a list of decorator ranges are equal to another.
1383
+ *
1384
+ * PERF: this requires the two lists to also have the ranges inside them in the
1385
+ * same order, but this is an okay constraint for us since decorations are
1386
+ * kept in order, and the odd case where they aren't is okay to re-render for.
1387
+ */
1388
+ const isDecoratorRangeListEqual = (list, another) => {
1389
+ if (list.length !== another.length) {
1390
+ return false;
1482
1391
  }
1483
- onDestroy() {
1484
- this.nativeElement.remove();
1392
+ for (let i = 0; i < list.length; i++) {
1393
+ const range = list[i];
1394
+ const other = another[i];
1395
+ const { anchor: rangeAnchor, focus: rangeFocus, ...rangeOwnProps } = range;
1396
+ const { anchor: otherAnchor, focus: otherFocus, ...otherOwnProps } = other;
1397
+ if (!Range.equals(range, other) ||
1398
+ range[PLACEHOLDER_SYMBOL] !== other[PLACEHOLDER_SYMBOL] ||
1399
+ !shallowCompare(rangeOwnProps, otherOwnProps)) {
1400
+ return false;
1401
+ }
1485
1402
  }
1403
+ return true;
1404
+ };
1405
+
1406
+ const isValid = (value) => (Element.isElement(value) && value.children.length > 0 && value.children.every(child => isValid(child))) ||
1407
+ Text$1.isText(value);
1408
+ const check = (document) => {
1409
+ return document.every(value => Element.isElement(value) && isValid(value));
1410
+ };
1411
+ function normalize(document) {
1412
+ return document.filter(value => Element.isElement(value) && isValid(value));
1486
1413
  }
1487
- const getBlockCardByNativeElement = (nativeElement) => {
1488
- const blockCardElement = nativeElement?.parentElement?.parentElement;
1489
- if (blockCardElement && blockCardElement.classList.contains(SLATE_BLOCK_CARD_CLASS_NAME)) {
1490
- return blockCardElement;
1491
- }
1492
- return null;
1414
+
1415
+ const createThrottleRAF = () => {
1416
+ let timerId = null;
1417
+ const throttleRAF = (fn) => {
1418
+ const scheduleFunc = () => {
1419
+ timerId = requestAnimationFrame(() => {
1420
+ timerId = null;
1421
+ fn();
1422
+ });
1423
+ };
1424
+ if (timerId !== null) {
1425
+ cancelAnimationFrame(timerId);
1426
+ timerId = null;
1427
+ }
1428
+ scheduleFunc();
1429
+ };
1430
+ return throttleRAF;
1493
1431
  };
1494
1432
 
1495
- const roundTo = (value, precision = 2) => {
1496
- const factor = 10 ** precision;
1497
- const n = Math.round(value * factor) / factor;
1498
- return Object.is(n, -0) ? 0 : n;
1433
+ const isClipboardReadSupported = () => {
1434
+ return 'clipboard' in navigator && 'read' in navigator.clipboard;
1435
+ };
1436
+ const isClipboardWriteSupported = () => {
1437
+ return 'clipboard' in navigator && 'write' in navigator.clipboard;
1438
+ };
1439
+ const isClipboardWriteTextSupported = () => {
1440
+ return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
1441
+ };
1442
+ const isClipboardFile = (item) => {
1443
+ return item.types.find(i => i.match(/^image\//));
1444
+ };
1445
+ const isInvalidTable = (nodes = []) => {
1446
+ return nodes.some(node => node.tagName.toLowerCase() === 'tr');
1447
+ };
1448
+ const stripHtml = (html) => {
1449
+ // See <https://github.com/developit/preact-markup/blob/4788b8d61b4e24f83688710746ee36e7464f7bbc/src/parse-markup.js#L60-L69>
1450
+ const doc = document.implementation.createHTMLDocument('');
1451
+ doc.documentElement.innerHTML = html.trim();
1452
+ return doc.body.textContent || doc.body.innerText || '';
1453
+ };
1454
+ const blobAsString = (blob) => {
1455
+ return new Promise((resolve, reject) => {
1456
+ const reader = new FileReader();
1457
+ reader.addEventListener('loadend', () => {
1458
+ const text = reader.result;
1459
+ resolve(text);
1460
+ });
1461
+ reader.addEventListener('error', () => {
1462
+ reject(reader.error);
1463
+ });
1464
+ reader.readAsText(blob);
1465
+ });
1466
+ };
1467
+ const completeTable = (fragment) => {
1468
+ const result = document.createDocumentFragment();
1469
+ const table = document.createElement('table');
1470
+ result.appendChild(table);
1471
+ table.appendChild(fragment);
1472
+ return result;
1499
1473
  };
1500
1474
 
1501
- const isDebug = localStorage.getItem(SLATE_DEBUG_KEY) === 'true';
1502
- const isDebugScrollTop = localStorage.getItem(SLATE_DEBUG_KEY_SCROLL_TOP) === 'true';
1503
- const ELEMENT_KEY_TO_HEIGHTS = new WeakMap();
1504
- const EDITOR_TO_BUSINESS_TOP = new WeakMap();
1505
- const EDITOR_TO_VIEWPORT_HEIGHT = new WeakMap();
1506
- const EDITOR_TO_ROOT_NODE_WIDTH = new WeakMap();
1507
- const EDITOR_TO_IS_FROM_SCROLL_TO = new WeakMap();
1508
- const isValidNumber = (value) => {
1509
- return typeof value === 'number' && !Number.isNaN(value);
1475
+ const setDataTransferClipboard = (dataTransfer, htmlText) => {
1476
+ dataTransfer?.setData(`text/html`, htmlText);
1510
1477
  };
1511
- const debugLog = (type, ...args) => {
1512
- const doc = document;
1513
- VirtualScrollDebugOverlay.log(doc, type, ...args);
1478
+ const setDataTransferClipboardText = (data, text) => {
1479
+ data?.setData(`text/plain`, text);
1514
1480
  };
1515
- const cacheHeightByElement = (editor, element, height) => {
1516
- if (!AngularEditor.isEnabledVirtualScroll(editor)) {
1517
- return;
1481
+ const getDataTransferClipboard = (data) => {
1482
+ const html = data?.getData(`text/html`);
1483
+ if (html) {
1484
+ const htmlClipboardData = getClipboardFromHTMLText(html);
1485
+ if (htmlClipboardData) {
1486
+ return htmlClipboardData;
1487
+ }
1488
+ const textData = getDataTransferClipboardText(data);
1489
+ if (textData) {
1490
+ return {
1491
+ html,
1492
+ ...textData
1493
+ };
1494
+ }
1495
+ else {
1496
+ return { html };
1497
+ }
1518
1498
  }
1519
- if (!isValidNumber(height)) {
1520
- console.error('cacheHeightByElement: height must be number', height);
1521
- return;
1499
+ else {
1500
+ const textData = getDataTransferClipboardText(data);
1501
+ return textData;
1522
1502
  }
1523
- const key = AngularEditor.findKey(editor, element);
1524
- const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1525
- heights.set(key.id, height);
1526
1503
  };
1527
- const setMinHeightByElement = (editor, element, rootElementMarginBottom) => {
1528
- if (!AngularEditor.isEnabledVirtualScroll(editor)) {
1529
- return;
1504
+ const getDataTransferClipboardText = (data) => {
1505
+ if (!data) {
1506
+ return null;
1530
1507
  }
1531
- const realHeight = getCachedHeightByElement(editor, element);
1532
- if (realHeight) {
1533
- const nativeElement = AngularEditor.toDOMNode(editor, element);
1534
- const blockCard = getBlockCardByNativeElement(nativeElement);
1535
- if (blockCard) {
1536
- const minHeight = realHeight - rootElementMarginBottom;
1537
- blockCard.style.minHeight = minHeight + 'px';
1508
+ const text = data?.getData(`text/plain`);
1509
+ if (text) {
1510
+ const htmlClipboardData = getClipboardFromHTMLText(text);
1511
+ if (htmlClipboardData) {
1512
+ return htmlClipboardData;
1538
1513
  }
1539
1514
  }
1515
+ return { text };
1540
1516
  };
1541
- const clearMinHeightByElement = (editor, element) => {
1542
- if (!AngularEditor.isEnabledVirtualScroll(editor)) {
1543
- return;
1544
- }
1545
- const nativeElement = AngularEditor.toDOMNode(editor, element);
1546
- const blockCard = getBlockCardByNativeElement(nativeElement);
1547
- if (blockCard && blockCard.style.minHeight) {
1548
- blockCard.style.minHeight = '';
1549
- return true;
1550
- }
1551
- else {
1552
- return false;
1517
+
1518
+ const setNavigatorClipboard = async (htmlText, data, text = '') => {
1519
+ let textClipboard = text;
1520
+ if (isClipboardWriteSupported()) {
1521
+ await navigator.clipboard.write([
1522
+ new ClipboardItem({
1523
+ 'text/html': new Blob([htmlText], {
1524
+ type: 'text/html'
1525
+ }),
1526
+ 'text/plain': new Blob([textClipboard ?? JSON.stringify(data)], { type: 'text/plain' })
1527
+ })
1528
+ ]);
1553
1529
  }
1554
1530
  };
1555
- const calcHeightByElement = (editor, element) => {
1556
- const view = ELEMENT_TO_COMPONENT.get(element);
1557
- if (!view) {
1558
- return;
1531
+ const getNavigatorClipboard = async () => {
1532
+ if (!isClipboardReadSupported()) {
1533
+ return null;
1559
1534
  }
1560
- const height = view.calcHeight();
1561
- cacheHeightByElement(editor, element, height);
1562
- return height;
1563
- };
1564
- const measureHeightByIndics = (editor, indics, force = false) => {
1565
- let hasChanged = false;
1566
- indics.forEach((index, i) => {
1567
- const element = editor.children[index];
1568
- const preHeight = getCachedHeightByElement(editor, element);
1569
- if (preHeight && !force) {
1570
- if (isDebug) {
1571
- const height = calcHeightByElement(editor, element);
1572
- if (height !== preHeight) {
1573
- debugLog('warn', 'calcHeightByElement: height not equal, index: ', index, 'preHeight: ', preHeight, 'height: ', height);
1535
+ const clipboardItems = await navigator.clipboard.read();
1536
+ let clipboardData = {};
1537
+ if (Array.isArray(clipboardItems) && clipboardItems[0] instanceof ClipboardItem) {
1538
+ for (const item of clipboardItems) {
1539
+ if (isClipboardFile(item)) {
1540
+ const clipboardFiles = item.types.filter(type => type.match(/^image\//));
1541
+ const fileBlobs = await Promise.all(clipboardFiles.map(type => item.getType(type)));
1542
+ const urls = fileBlobs.filter(Boolean).map(blob => URL.createObjectURL(blob));
1543
+ const files = await Promise.all(urls.map(async (url) => {
1544
+ const blob = await (await fetch(url)).blob();
1545
+ return new File([blob], 'file', { type: blob.type });
1546
+ }));
1547
+ clipboardData = {
1548
+ ...clipboardData,
1549
+ files
1550
+ };
1551
+ }
1552
+ if (item.types.includes('text/html')) {
1553
+ const htmlContent = await blobAsString(await item.getType('text/html'));
1554
+ const htmlClipboardData = getClipboardFromHTMLText(htmlContent);
1555
+ if (htmlClipboardData) {
1556
+ clipboardData = { ...clipboardData, ...htmlClipboardData };
1557
+ return clipboardData;
1558
+ }
1559
+ if (htmlContent && htmlContent.trim()) {
1560
+ clipboardData = { ...clipboardData, html: htmlContent };
1574
1561
  }
1575
1562
  }
1576
- return;
1577
- }
1578
- const currentHeight = calcHeightByElement(editor, element);
1579
- if (isValidNumber(currentHeight) && currentHeight !== preHeight) {
1580
- hasChanged = true;
1581
- }
1582
- if (isDebug && isValidNumber(currentHeight)) {
1583
- debugLog('log', 'measureHeightByIndics: index: ', index, 'preHeight: ', preHeight, 'height: ', currentHeight);
1563
+ if (item.types.includes('text/plain')) {
1564
+ const textContent = await blobAsString(await item.getType('text/plain'));
1565
+ clipboardData = {
1566
+ ...clipboardData,
1567
+ text: stripHtml(textContent)
1568
+ };
1569
+ }
1584
1570
  }
1585
- });
1586
- return hasChanged;
1587
- };
1588
- const getBusinessTop = (editor) => {
1589
- return EDITOR_TO_BUSINESS_TOP.get(editor) ?? 0;
1571
+ }
1572
+ return clipboardData;
1590
1573
  };
1591
- const getViewportHeight = (editor) => {
1592
- return EDITOR_TO_VIEWPORT_HEIGHT.get(editor) ?? window.innerHeight;
1574
+
1575
+ const buildHTMLText = (wrapper, attach, data) => {
1576
+ const stringObj = JSON.stringify(data);
1577
+ const encoded = window.btoa(encodeURIComponent(stringObj));
1578
+ attach.setAttribute(SlateFragmentAttributeKey, encoded);
1579
+ return wrapper.innerHTML;
1593
1580
  };
1594
- const getCachedHeightByElement = (editor, element) => {
1595
- const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1596
- const key = AngularEditor.findKey(editor, element);
1597
- const height = heights?.get(key.id);
1598
- if (typeof height === 'number') {
1599
- return height;
1600
- }
1601
- if (heights?.has(key.id)) {
1602
- console.error('getBlockHeight: invalid height value', key.id, height);
1581
+ const getClipboardFromHTMLText = (html) => {
1582
+ const fragmentAttribute = getSlateFragmentAttribute(html);
1583
+ if (fragmentAttribute) {
1584
+ try {
1585
+ const decoded = decodeURIComponent(window.atob(fragmentAttribute));
1586
+ const result = JSON.parse(decoded);
1587
+ if (result && Array.isArray(result) && result.length > 0) {
1588
+ return {
1589
+ elements: result
1590
+ };
1591
+ }
1592
+ }
1593
+ catch (error) {
1594
+ console.error(error);
1595
+ return null;
1596
+ }
1603
1597
  }
1604
1598
  return null;
1605
1599
  };
1606
- const buildHeightsAndAccumulatedHeights = (editor, visibleStates) => {
1607
- const children = (editor.children || []);
1608
- const heights = new Array(children.length);
1609
- const accumulatedHeights = new Array(children.length + 1);
1610
- accumulatedHeights[0] = 0;
1611
- for (let i = 0; i < children.length; i++) {
1612
- const isVisible = visibleStates[i];
1613
- let height = isVisible ? getCachedHeightByElement(editor, children[i]) : 0;
1614
- if (height === null) {
1615
- try {
1616
- height = editor.getRoughHeight(children[i]);
1617
- }
1618
- catch (error) {
1619
- console.error('buildHeightsAndAccumulatedHeights: getRoughHeight error', error);
1620
- }
1600
+ const createClipboardData = (html, elements, text, files) => {
1601
+ const data = { elements, text, html, files };
1602
+ return data;
1603
+ };
1604
+ const getClipboardData = async (dataTransfer) => {
1605
+ let clipboardData = null;
1606
+ if (dataTransfer) {
1607
+ let filesData = {};
1608
+ if (dataTransfer.files.length) {
1609
+ filesData = { ...filesData, files: Array.from(dataTransfer.files) };
1621
1610
  }
1622
- heights[i] = height;
1623
- accumulatedHeights[i + 1] = accumulatedHeights[i] + height;
1611
+ clipboardData = getDataTransferClipboard(dataTransfer);
1612
+ return { ...clipboardData, ...filesData };
1624
1613
  }
1625
- return { heights, accumulatedHeights };
1626
- };
1627
- const calculateVirtualTopHeight = (editor, startIndex, visibleStates) => {
1628
- const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor, visibleStates);
1629
- const virtualTopHeight = roundTo(accumulatedHeights[startIndex] ?? 0, 1);
1630
- return virtualTopHeight;
1614
+ if (isClipboardReadSupported()) {
1615
+ return await getNavigatorClipboard();
1616
+ }
1617
+ return clipboardData;
1631
1618
  };
1632
- const scrollToElement = (editor, element, scrollTo) => {
1633
- const children = editor.children;
1634
- if (!children.length) {
1619
+ /**
1620
+ * @param wrapper get wrapper.innerHTML string which will be written in clipboard
1621
+ * @param attach attach must be child element of wrapper which will be attached json data
1622
+ * @returns void
1623
+ */
1624
+ const setClipboardData = async (clipboardData, wrapper, attach, dataTransfer) => {
1625
+ if (!clipboardData) {
1635
1626
  return;
1636
1627
  }
1637
- const anchorIndex = children.findIndex(item => item === element);
1638
- if (anchorIndex < 0) {
1628
+ const { elements, text } = clipboardData;
1629
+ if (isClipboardWriteSupported()) {
1630
+ const htmlText = buildHTMLText(wrapper, attach, elements);
1631
+ // TODO
1632
+ // maybe fail to write when copy some cell in table
1633
+ return await setNavigatorClipboard(htmlText, elements, text);
1634
+ }
1635
+ if (dataTransfer) {
1636
+ const htmlText = buildHTMLText(wrapper, attach, elements);
1637
+ setDataTransferClipboard(dataTransfer, htmlText);
1638
+ setDataTransferClipboardText(dataTransfer, text);
1639
1639
  return;
1640
1640
  }
1641
- const visibleStates = editor.getAllVisibleStates();
1642
- const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor, visibleStates);
1643
- scrollTo((accumulatedHeights[anchorIndex] ?? 0) + getBusinessTop(editor));
1644
- EDITOR_TO_IS_FROM_SCROLL_TO.set(editor, true);
1645
- setTimeout(() => {
1646
- console.log('scrollToElement: end scroll');
1647
- EDITOR_TO_IS_FROM_SCROLL_TO.set(editor, false);
1648
- }, 0);
1641
+ const htmlText = buildHTMLText(wrapper, attach, elements);
1642
+ // Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
1643
+ // Such as contextmenu copy in Firefox.
1644
+ if (isClipboardWriteTextSupported()) {
1645
+ return await navigator.clipboard.writeText(htmlText);
1646
+ }
1647
+ else {
1648
+ return await fallbackCopyText(htmlText);
1649
+ }
1650
+ };
1651
+ const fallbackCopyText = async (text) => {
1652
+ return new Promise((resolve, reject) => {
1653
+ const textArea = document.createElement('textarea');
1654
+ textArea.value = text;
1655
+ textArea.style.position = 'fixed';
1656
+ textArea.style.left = '-999999px';
1657
+ textArea.style.top = '-999999px';
1658
+ textArea.style.opacity = '0';
1659
+ document.body.appendChild(textArea);
1660
+ textArea.focus();
1661
+ textArea.select();
1662
+ try {
1663
+ const successful = document.execCommand('copy');
1664
+ document.body.removeChild(textArea);
1665
+ if (successful) {
1666
+ resolve();
1667
+ }
1668
+ else {
1669
+ reject(new Error('execCommand error'));
1670
+ }
1671
+ }
1672
+ catch (err) {
1673
+ document.body.removeChild(textArea);
1674
+ reject(err);
1675
+ }
1676
+ });
1649
1677
  };
1650
1678
 
1651
1679
  const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
@@ -3341,10 +3369,10 @@ const forceOnDOMPaste = IS_SAFARI;
3341
3369
  class SlateEditable {
3342
3370
  set virtualScroll(config) {
3343
3371
  this.virtualScrollConfig = config;
3372
+ EDITOR_TO_VIRTUAL_SCROLL_CONFIG.set(this.editor, config);
3344
3373
  if (isDebugScrollTop) {
3345
3374
  debugLog('log', 'virtualScrollConfig scrollTop:', config.scrollTop);
3346
3375
  }
3347
- IS_ENABLED_VIRTUAL_SCROLL.set(this.editor, config.enabled);
3348
3376
  if (this.isEnabledVirtualScroll()) {
3349
3377
  this.tryUpdateVirtualViewport();
3350
3378
  }
@@ -3527,13 +3555,13 @@ class SlateEditable {
3527
3555
  if (this.isEnabledVirtualScroll()) {
3528
3556
  this.virtualScrollInitialized = true;
3529
3557
  this.virtualTopHeightElement = document.createElement('div');
3530
- this.virtualTopHeightElement.classList.add('virtual-top-height');
3558
+ this.virtualTopHeightElement.classList.add(VIRTUAL_TOP_HEIGHT_CLASS_NAME);
3531
3559
  this.virtualTopHeightElement.contentEditable = 'false';
3532
3560
  this.virtualBottomHeightElement = document.createElement('div');
3533
- this.virtualBottomHeightElement.classList.add('virtual-bottom-height');
3561
+ this.virtualBottomHeightElement.classList.add(VIRTUAL_BOTTOM_HEIGHT_CLASS_NAME);
3534
3562
  this.virtualBottomHeightElement.contentEditable = 'false';
3535
3563
  this.virtualCenterOutlet = document.createElement('div');
3536
- this.virtualCenterOutlet.classList.add('virtual-center-outlet');
3564
+ this.virtualCenterOutlet.classList.add(VIRTUAL_CENTER_OUTLET_CLASS_NAME);
3537
3565
  this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
3538
3566
  this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
3539
3567
  this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
@@ -3589,17 +3617,6 @@ class SlateEditable {
3589
3617
  });
3590
3618
  }
3591
3619
  }
3592
- calcBusinessTop() {
3593
- const virtualTopBoundingTop = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3594
- const viewportBoundingTop = this.virtualScrollConfig.scrollContainer?.getBoundingClientRect()?.top ?? 0;
3595
- const businessTop = Math.ceil(virtualTopBoundingTop) + Math.ceil(this.virtualScrollConfig.scrollTop) - Math.floor(viewportBoundingTop);
3596
- EDITOR_TO_BUSINESS_TOP.set(this.editor, businessTop);
3597
- if (isDebug) {
3598
- debugLog('log', 'calcBusinessTop: ', businessTop);
3599
- this.virtualTopHeightElement.setAttribute('data-business-top', businessTop.toString());
3600
- }
3601
- return businessTop;
3602
- }
3603
3620
  getChangedIndics(previousValue) {
3604
3621
  const remeasureIndics = [];
3605
3622
  this.inViewportChildren.forEach((child, index) => {
@@ -3733,7 +3750,7 @@ class SlateEditable {
3733
3750
  const elementLength = children.length;
3734
3751
  let businessTop = getBusinessTop(this.editor);
3735
3752
  if (businessTop === 0 && this.virtualScrollConfig.scrollTop > 0) {
3736
- businessTop = this.calcBusinessTop();
3753
+ businessTop = calcBusinessTop(this.editor);
3737
3754
  }
3738
3755
  const { heights, accumulatedHeights } = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
3739
3756
  const totalHeight = accumulatedHeights[elementLength] + businessTop;
@@ -5442,5 +5459,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
5442
5459
  * Generated bundle index. Do not edit.
5443
5460
  */
5444
5461
 
5445
- export { AngularEditor, BaseComponent, BaseElementComponent, BaseElementFlavour, BaseFlavour, BaseLeafComponent, BaseLeafFlavour, BaseTextComponent, BaseTextFlavour, BlockCardRef, DEFAULT_ELEMENT_HEIGHT, DefaultTextFlavour, EDITOR_TO_AFTER_VIEW_INIT_QUEUE, EDITOR_TO_BUSINESS_TOP, EDITOR_TO_IS_FROM_SCROLL_TO, EDITOR_TO_ROOT_NODE_WIDTH, EDITOR_TO_VIEWPORT_HEIGHT, EDITOR_TO_VIRTUAL_SCROLL_SELECTION, ELEMENT_KEY_TO_HEIGHTS, ELEMENT_TO_COMPONENT, FAKE_LEFT_BLOCK_CARD_OFFSET, FAKE_RIGHT_BLOCK_CARD_OFFSET, FlavourRef, HAS_BEFORE_INPUT_SUPPORT, IS_ANDROID, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_ENABLED_VIRTUAL_SCROLL, IS_FIREFOX, IS_FIREFOX_LEGACY, IS_IOS, IS_QQBROWSER, IS_SAFARI, IS_UC_MOBILE, IS_WECHATBROWSER, PLACEHOLDER_SYMBOL, SLATE_BLOCK_CARD_CLASS_NAME, SLATE_DEBUG_KEY, SLATE_DEBUG_KEY_SCROLL_TOP, SlateBlockCard, SlateChildrenOutlet, SlateEditable, SlateErrorCode, SlateFragmentAttributeKey, SlateModule, SlateString, VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT, VoidTextFlavour, blobAsString, buildHTMLText, buildHeightsAndAccumulatedHeights, cacheHeightByElement, calcHeightByElement, calculateVirtualTopHeight, check, clearMinHeightByElement, completeTable, createClipboardData, createText, createThrottleRAF, debugLog, defaultScrollSelectionIntoView, fallbackCopyText, getBlockCardByNativeElement, getBusinessTop, getCachedHeightByElement, getCardTargetAttribute, getClipboardData, getClipboardFromHTMLText, getContentHeight, getDataTransferClipboard, getDataTransferClipboardText, getNavigatorClipboard, getPlainText, getSelection, getSlateFragmentAttribute, getViewportHeight, getZeroTextNode, hasAfterContextChange, hasBeforeContextChange, hasBlockCard, hasBlockCardWithNode, hotkeys, isCardCenterByTargetAttr, isCardLeft, isCardLeftByTargetAttr, isCardRightByTargetAttr, isClipboardFile, isClipboardReadSupported, isClipboardWriteSupported, isClipboardWriteTextSupported, isComponentType, isDOMText, isDebug, isDebugScrollTop, isDecoratorRangeListEqual, isFlavourType, isInvalidTable, isTemplateRef, isValid, isValidNumber, measureHeightByIndics, normalize, roundTo, scrollToElement, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setMinHeightByElement, setNavigatorClipboard, shallowCompare, stripHtml, withAngular };
5462
+ export { AngularEditor, BaseComponent, BaseElementComponent, BaseElementFlavour, BaseFlavour, BaseLeafComponent, BaseLeafFlavour, BaseTextComponent, BaseTextFlavour, BlockCardRef, DEFAULT_ELEMENT_HEIGHT, DefaultTextFlavour, EDITOR_TO_AFTER_VIEW_INIT_QUEUE, EDITOR_TO_BUSINESS_TOP, EDITOR_TO_IS_FROM_SCROLL_TO, EDITOR_TO_ROOT_NODE_WIDTH, EDITOR_TO_VIEWPORT_HEIGHT, EDITOR_TO_VIRTUAL_SCROLL_CONFIG, EDITOR_TO_VIRTUAL_SCROLL_SELECTION, ELEMENT_KEY_TO_HEIGHTS, ELEMENT_TO_COMPONENT, FAKE_LEFT_BLOCK_CARD_OFFSET, FAKE_RIGHT_BLOCK_CARD_OFFSET, FlavourRef, HAS_BEFORE_INPUT_SUPPORT, IS_ANDROID, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_FIREFOX, IS_FIREFOX_LEGACY, IS_IOS, IS_QQBROWSER, IS_SAFARI, IS_UC_MOBILE, IS_WECHATBROWSER, PLACEHOLDER_SYMBOL, SLATE_BLOCK_CARD_CLASS_NAME, SLATE_DEBUG_KEY, SLATE_DEBUG_KEY_SCROLL_TOP, SlateBlockCard, SlateChildrenOutlet, SlateEditable, SlateErrorCode, SlateFragmentAttributeKey, SlateModule, SlateString, VIRTUAL_BOTTOM_HEIGHT_CLASS_NAME, VIRTUAL_CENTER_OUTLET_CLASS_NAME, VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT, VIRTUAL_TOP_HEIGHT_CLASS_NAME, VoidTextFlavour, blobAsString, buildHTMLText, buildHeightsAndAccumulatedHeights, cacheHeightByElement, calcBusinessTop, calcHeightByElement, calculateVirtualTopHeight, check, clearMinHeightByElement, completeTable, createClipboardData, createText, createThrottleRAF, debugLog, defaultScrollSelectionIntoView, fallbackCopyText, getBlockCardByNativeElement, getBusinessTop, getCachedHeightByElement, getCardTargetAttribute, getClipboardData, getClipboardFromHTMLText, getContentHeight, getDataTransferClipboard, getDataTransferClipboardText, getNavigatorClipboard, getPlainText, getScrollContainer, getSelection, getSlateFragmentAttribute, getViewportHeight, getZeroTextNode, hasAfterContextChange, hasBeforeContextChange, hasBlockCard, hasBlockCardWithNode, hotkeys, isCardCenterByTargetAttr, isCardLeft, isCardLeftByTargetAttr, isCardRightByTargetAttr, isClipboardFile, isClipboardReadSupported, isClipboardWriteSupported, isClipboardWriteTextSupported, isComponentType, isDOMText, isDebug, isDebugScrollTop, isDecoratorRangeListEqual, isFlavourType, isInvalidTable, isTemplateRef, isValid, isValidNumber, measureHeightByIndics, normalize, roundTo, scrollToElement, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setMinHeightByElement, setNavigatorClipboard, shallowCompare, stripHtml, withAngular };
5446
5463
  //# sourceMappingURL=slate-angular.mjs.map