slate-angular 20.2.17 → 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,365 +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 isDebug = localStorage.getItem(SLATE_DEBUG_KEY) === 'true';
1496
- const isDebugScrollTop = localStorage.getItem(SLATE_DEBUG_KEY_SCROLL_TOP) === 'true';
1497
- const ELEMENT_KEY_TO_HEIGHTS = new WeakMap();
1498
- const EDITOR_TO_BUSINESS_TOP = new WeakMap();
1499
- const EDITOR_TO_ROOT_NODE_WIDTH = new WeakMap();
1500
- const EDITOR_TO_IS_FROM_SCROLL_TO = new WeakMap();
1501
- const isValidNumber = (value) => {
1502
- return typeof value === 'number' && !Number.isNaN(value);
1433
+ const isClipboardReadSupported = () => {
1434
+ return 'clipboard' in navigator && 'read' in navigator.clipboard;
1503
1435
  };
1504
- const debugLog = (type, ...args) => {
1505
- const doc = document;
1506
- VirtualScrollDebugOverlay.log(doc, type, ...args);
1436
+ const isClipboardWriteSupported = () => {
1437
+ return 'clipboard' in navigator && 'write' in navigator.clipboard;
1507
1438
  };
1508
- const cacheHeightByElement = (editor, element, height) => {
1509
- if (!AngularEditor.isEnabledVirtualScroll(editor)) {
1510
- return;
1511
- }
1512
- if (!isValidNumber(height)) {
1513
- console.error('cacheHeightByElement: height must be number', height);
1514
- return;
1515
- }
1516
- const key = AngularEditor.findKey(editor, element);
1517
- const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1518
- heights.set(key.id, height);
1439
+ const isClipboardWriteTextSupported = () => {
1440
+ return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
1519
1441
  };
1520
- const setMinHeightByElement = (editor, element, rootElementMarginBottom) => {
1521
- if (!AngularEditor.isEnabledVirtualScroll(editor)) {
1522
- return;
1523
- }
1524
- const realHeight = getCachedHeightByElement(editor, element);
1525
- if (realHeight) {
1526
- const nativeElement = AngularEditor.toDOMNode(editor, element);
1527
- const blockCard = getBlockCardByNativeElement(nativeElement);
1528
- if (blockCard) {
1529
- const minHeight = realHeight - rootElementMarginBottom;
1530
- blockCard.style.minHeight = minHeight + 'px';
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;
1473
+ };
1474
+
1475
+ const setDataTransferClipboard = (dataTransfer, htmlText) => {
1476
+ dataTransfer?.setData(`text/html`, htmlText);
1477
+ };
1478
+ const setDataTransferClipboardText = (data, text) => {
1479
+ data?.setData(`text/plain`, text);
1480
+ };
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 };
1531
1497
  }
1532
1498
  }
1533
- };
1534
- const clearMinHeightByElement = (editor, element) => {
1535
- if (!AngularEditor.isEnabledVirtualScroll(editor)) {
1536
- return;
1499
+ else {
1500
+ const textData = getDataTransferClipboardText(data);
1501
+ return textData;
1537
1502
  }
1538
- const nativeElement = AngularEditor.toDOMNode(editor, element);
1539
- const blockCard = getBlockCardByNativeElement(nativeElement);
1540
- if (blockCard && blockCard.style.minHeight) {
1541
- blockCard.style.minHeight = '';
1542
- return true;
1503
+ };
1504
+ const getDataTransferClipboardText = (data) => {
1505
+ if (!data) {
1506
+ return null;
1543
1507
  }
1544
- else {
1545
- return false;
1508
+ const text = data?.getData(`text/plain`);
1509
+ if (text) {
1510
+ const htmlClipboardData = getClipboardFromHTMLText(text);
1511
+ if (htmlClipboardData) {
1512
+ return htmlClipboardData;
1513
+ }
1546
1514
  }
1515
+ return { text };
1547
1516
  };
1548
- const calcHeightByElement = (editor, element) => {
1549
- const view = ELEMENT_TO_COMPONENT.get(element);
1550
- if (!view) {
1551
- return;
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
+ ]);
1552
1529
  }
1553
- const height = view.calcHeight();
1554
- cacheHeightByElement(editor, element, height);
1555
- return height;
1556
1530
  };
1557
- const measureHeightByIndics = (editor, indics, force = false) => {
1558
- let hasChanged = false;
1559
- indics.forEach((index, i) => {
1560
- const element = editor.children[index];
1561
- const preHeight = getCachedHeightByElement(editor, element);
1562
- if (preHeight && !force) {
1563
- if (isDebug) {
1564
- const height = calcHeightByElement(editor, element);
1565
- if (height !== preHeight) {
1566
- debugLog('warn', 'calcHeightByElement: height not equal, index: ', index, 'preHeight: ', preHeight, 'height: ', height);
1531
+ const getNavigatorClipboard = async () => {
1532
+ if (!isClipboardReadSupported()) {
1533
+ return null;
1534
+ }
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 };
1567
1561
  }
1568
1562
  }
1569
- return;
1570
- }
1571
- const currentHeight = calcHeightByElement(editor, element);
1572
- if (isValidNumber(currentHeight) && currentHeight !== preHeight) {
1573
- hasChanged = true;
1574
- }
1575
- if (isDebug && isValidNumber(currentHeight)) {
1576
- debugLog('log', 'measureHeightByIndics: height not equal, 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
+ }
1577
1570
  }
1578
- });
1579
- return hasChanged;
1571
+ }
1572
+ return clipboardData;
1580
1573
  };
1581
- const getBusinessTop = (editor) => {
1582
- return EDITOR_TO_BUSINESS_TOP.get(editor) ?? 0;
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;
1583
1580
  };
1584
- const getCachedHeightByElement = (editor, element) => {
1585
- const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1586
- const key = AngularEditor.findKey(editor, element);
1587
- const height = heights?.get(key.id);
1588
- if (typeof height === 'number') {
1589
- return height;
1590
- }
1591
- if (heights?.has(key.id)) {
1592
- 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
+ }
1593
1597
  }
1594
1598
  return null;
1595
1599
  };
1596
- const buildHeightsAndAccumulatedHeights = (editor, visibleStates) => {
1597
- const children = (editor.children || []);
1598
- const heights = new Array(children.length);
1599
- const accumulatedHeights = new Array(children.length + 1);
1600
- accumulatedHeights[0] = 0;
1601
- for (let i = 0; i < children.length; i++) {
1602
- const isVisible = visibleStates[i];
1603
- let height = isVisible ? getCachedHeightByElement(editor, children[i]) : 0;
1604
- if (height === null) {
1605
- try {
1606
- height = editor.getRoughHeight(children[i]);
1607
- }
1608
- catch (error) {
1609
- console.error('buildHeightsAndAccumulatedHeights: getRoughHeight error', error);
1610
- }
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) };
1611
1610
  }
1612
- heights[i] = height;
1613
- accumulatedHeights[i + 1] = accumulatedHeights[i] + height;
1611
+ clipboardData = getDataTransferClipboard(dataTransfer);
1612
+ return { ...clipboardData, ...filesData };
1614
1613
  }
1615
- return { heights, accumulatedHeights };
1616
- };
1617
- const calculateVirtualTopHeight = (editor, startIndex, visibleStates) => {
1618
- const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor, visibleStates);
1619
- return accumulatedHeights[startIndex] ?? 0;
1614
+ if (isClipboardReadSupported()) {
1615
+ return await getNavigatorClipboard();
1616
+ }
1617
+ return clipboardData;
1620
1618
  };
1621
- const scrollToElement = (editor, element, scrollTo) => {
1622
- const children = editor.children;
1623
- 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) {
1624
1626
  return;
1625
1627
  }
1626
- const anchorIndex = children.findIndex(item => item === element);
1627
- 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);
1628
1639
  return;
1629
1640
  }
1630
- const visibleStates = editor.getAllVisibleStates();
1631
- const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor, visibleStates);
1632
- scrollTo((accumulatedHeights[anchorIndex] ?? 0) + getBusinessTop(editor));
1633
- EDITOR_TO_IS_FROM_SCROLL_TO.set(editor, true);
1634
- setTimeout(() => {
1635
- console.log('scrollToElement: end scroll');
1636
- EDITOR_TO_IS_FROM_SCROLL_TO.set(editor, false);
1637
- }, 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
+ });
1638
1677
  };
1639
1678
 
1640
1679
  const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
@@ -3330,10 +3369,10 @@ const forceOnDOMPaste = IS_SAFARI;
3330
3369
  class SlateEditable {
3331
3370
  set virtualScroll(config) {
3332
3371
  this.virtualScrollConfig = config;
3372
+ EDITOR_TO_VIRTUAL_SCROLL_CONFIG.set(this.editor, config);
3333
3373
  if (isDebugScrollTop) {
3334
3374
  debugLog('log', 'virtualScrollConfig scrollTop:', config.scrollTop);
3335
3375
  }
3336
- IS_ENABLED_VIRTUAL_SCROLL.set(this.editor, config.enabled);
3337
3376
  if (this.isEnabledVirtualScroll()) {
3338
3377
  this.tryUpdateVirtualViewport();
3339
3378
  }
@@ -3384,8 +3423,6 @@ class SlateEditable {
3384
3423
  this.virtualScrollConfig = {
3385
3424
  enabled: false,
3386
3425
  scrollTop: 0,
3387
- viewportHeight: 0,
3388
- viewportBoundingTop: 0,
3389
3426
  scrollContainer: null
3390
3427
  };
3391
3428
  this.inViewportChildren = [];
@@ -3518,13 +3555,13 @@ class SlateEditable {
3518
3555
  if (this.isEnabledVirtualScroll()) {
3519
3556
  this.virtualScrollInitialized = true;
3520
3557
  this.virtualTopHeightElement = document.createElement('div');
3521
- this.virtualTopHeightElement.classList.add('virtual-top-height');
3558
+ this.virtualTopHeightElement.classList.add(VIRTUAL_TOP_HEIGHT_CLASS_NAME);
3522
3559
  this.virtualTopHeightElement.contentEditable = 'false';
3523
3560
  this.virtualBottomHeightElement = document.createElement('div');
3524
- this.virtualBottomHeightElement.classList.add('virtual-bottom-height');
3561
+ this.virtualBottomHeightElement.classList.add(VIRTUAL_BOTTOM_HEIGHT_CLASS_NAME);
3525
3562
  this.virtualBottomHeightElement.contentEditable = 'false';
3526
3563
  this.virtualCenterOutlet = document.createElement('div');
3527
- this.virtualCenterOutlet.classList.add('virtual-center-outlet');
3564
+ this.virtualCenterOutlet.classList.add(VIRTUAL_CENTER_OUTLET_CLASS_NAME);
3528
3565
  this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
3529
3566
  this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
3530
3567
  this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
@@ -3548,6 +3585,17 @@ class SlateEditable {
3548
3585
  }
3549
3586
  });
3550
3587
  this.editorResizeObserver.observe(this.elementRef.nativeElement);
3588
+ if (this.virtualScrollConfig.scrollContainer) {
3589
+ this.editorScrollContainerResizeObserver = new ResizeObserver(entries => {
3590
+ const height = this.virtualScrollConfig.scrollContainer.getBoundingClientRect().height;
3591
+ EDITOR_TO_VIEWPORT_HEIGHT.set(this.editor, height);
3592
+ if (isDebug) {
3593
+ debugLog('log', 'editorScrollContainerResizeObserver calc viewport height: ', height);
3594
+ this.virtualTopHeightElement.setAttribute('viewport-height', height.toString());
3595
+ }
3596
+ });
3597
+ this.editorScrollContainerResizeObserver.observe(this.virtualScrollConfig.scrollContainer);
3598
+ }
3551
3599
  let pendingRemeasureIndics = [];
3552
3600
  this.indicsOfNeedBeMeasured$
3553
3601
  .pipe(tap((previousValue) => {
@@ -3582,9 +3630,9 @@ class SlateEditable {
3582
3630
  if (!this.virtualScrollInitialized) {
3583
3631
  return;
3584
3632
  }
3585
- this.virtualTopHeightElement.style.height = `${topHeight}px`;
3633
+ this.virtualTopHeightElement.style.height = `${roundTo(topHeight, 1)}px`;
3586
3634
  if (bottomHeight !== undefined) {
3587
- this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
3635
+ this.virtualBottomHeightElement.style.height = `${roundTo(bottomHeight, 1)}px`;
3588
3636
  }
3589
3637
  }
3590
3638
  getActualVirtualTopHeight() {
@@ -3629,7 +3677,7 @@ class SlateEditable {
3629
3677
  const refreshVirtualTopHeight = calculateVirtualTopHeight(this.editor, this.inViewportIndics[0], visibleStates);
3630
3678
  if (topHeight !== refreshVirtualTopHeight) {
3631
3679
  if (isDebug) {
3632
- debugLog('log', 'update top height since dirty state(正数减去高度,负数代表增加高度): ', topHeight - refreshVirtualTopHeight);
3680
+ debugLog('log', 'update top height since dirty state,增加高度: ', refreshVirtualTopHeight - topHeight);
3633
3681
  }
3634
3682
  this.setVirtualSpaceHeight(refreshVirtualTopHeight);
3635
3683
  return;
@@ -3668,9 +3716,11 @@ class SlateEditable {
3668
3716
  const newHeights = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
3669
3717
  const actualTopHeightAfterAdd = newHeights.accumulatedHeights[startIndexBeforeAdd];
3670
3718
  const newTopHeight = virtualView.top - (actualTopHeightAfterAdd - topHeightBeforeAdd);
3671
- this.setVirtualSpaceHeight(newTopHeight);
3672
- if (isDebug) {
3673
- debugLog('log', `update top height since will add element in top(正数减去高度,负数代表增加高度): ${actualTopHeightAfterAdd - topHeightBeforeAdd}`);
3719
+ if (topHeightBeforeAdd !== actualTopHeightAfterAdd) {
3720
+ this.setVirtualSpaceHeight(newTopHeight);
3721
+ if (isDebug) {
3722
+ debugLog('log', `update top height since will add element in top,减去高度: ${topHeightBeforeAdd - actualTopHeightAfterAdd}`);
3723
+ }
3674
3724
  }
3675
3725
  }
3676
3726
  }
@@ -3696,32 +3746,12 @@ class SlateEditable {
3696
3746
  };
3697
3747
  }
3698
3748
  const scrollTop = this.virtualScrollConfig.scrollTop;
3699
- let viewportHeight = this.virtualScrollConfig.viewportHeight ?? 0;
3700
- if (!viewportHeight) {
3701
- return {
3702
- inViewportChildren: [],
3703
- inViewportIndics: [],
3704
- top: 0,
3705
- bottom: 0,
3706
- heights: []
3707
- };
3708
- }
3749
+ let viewportHeight = getViewportHeight(this.editor);
3709
3750
  const elementLength = children.length;
3710
- if (!EDITOR_TO_BUSINESS_TOP.has(this.editor)) {
3711
- EDITOR_TO_BUSINESS_TOP.set(this.editor, 0);
3712
- setTimeout(() => {
3713
- const virtualTopBoundingTop = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3714
- const businessTop = Math.ceil(virtualTopBoundingTop) +
3715
- Math.ceil(this.virtualScrollConfig.scrollTop) -
3716
- Math.floor(this.virtualScrollConfig.viewportBoundingTop);
3717
- EDITOR_TO_BUSINESS_TOP.set(this.editor, businessTop);
3718
- if (isDebug) {
3719
- debugLog('log', 'businessTop', businessTop);
3720
- this.virtualTopHeightElement.setAttribute('data-business-top', businessTop.toString());
3721
- }
3722
- }, 100);
3751
+ let businessTop = getBusinessTop(this.editor);
3752
+ if (businessTop === 0 && this.virtualScrollConfig.scrollTop > 0) {
3753
+ businessTop = calcBusinessTop(this.editor);
3723
3754
  }
3724
- const businessTop = getBusinessTop(this.editor);
3725
3755
  const { heights, accumulatedHeights } = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
3726
3756
  const totalHeight = accumulatedHeights[elementLength] + businessTop;
3727
3757
  let startPosition = Math.max(scrollTop - businessTop, 0);
@@ -3955,6 +3985,9 @@ class SlateEditable {
3955
3985
  if (!hasDomSelectionInEditor && !AngularEditor.isFocused(this.editor)) {
3956
3986
  return;
3957
3987
  }
3988
+ if (AngularEditor.isReadOnly(this.editor) && (!selection || Range.isCollapsed(selection))) {
3989
+ return;
3990
+ }
3958
3991
  // If the DOM selection is in the editor and the editor selection is already correct, we're done.
3959
3992
  if (hasDomSelection && hasDomSelectionInEditor && selection && hasStringTarget(domSelection)) {
3960
3993
  const rangeFromDOMSelection = AngularEditor.toSlateRange(this.editor, domSelection, {
@@ -4900,6 +4933,7 @@ class SlateEditable {
4900
4933
  //#endregion
4901
4934
  ngOnDestroy() {
4902
4935
  this.editorResizeObserver?.disconnect();
4936
+ this.editorScrollContainerResizeObserver?.disconnect();
4903
4937
  NODE_TO_ELEMENT.delete(this.editor);
4904
4938
  this.manualListeners.forEach(manualListener => {
4905
4939
  manualListener();
@@ -5425,5 +5459,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
5425
5459
  * Generated bundle index. Do not edit.
5426
5460
  */
5427
5461
 
5428
- 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_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, 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, 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 };
5429
5463
  //# sourceMappingURL=slate-angular.mjs.map