<commit_before>/**
 * Copyright (c) Tiny Technologies, Inc. All rights reserved.
 * Licensed under the LGPL or a commercial license.
 * For LGPL see License.txt in the project root for license information.
 * For commercial licenses see https://www.tiny.cloud/
 */

import { AlloyComponent, Boxes, Docking, Focusing, Receiving } from '@ephox/alloy';
import { HTMLElement } from '@ephox/dom-globals';
import { Cell, Option, Result } from '@ephox/katamari';
import { Classes, Compare, Css, Element, Focus, Height, Traverse, Width } from '@ephox/sugar';

import Editor from 'tinymce/core/api/Editor';
import * as Channels from '../../Channels';

const visibility = {
  fadeInClass: 'tox-editor-dock-fadein',
  fadeOutClass: 'tox-editor-dock-fadeout',
  transitionClass: 'tox-editor-dock-transition'
};

const updateContentFlow = (header: AlloyComponent): void => {
  const elm = header.element();
  Traverse.parent(elm).each((parentElem: Element<HTMLElement>) => {
    if (Docking.isDocked(header)) {
      const parentWidth = Width.get(parentElem);
      Css.set(elm, 'width', parentWidth + 'px');
      const headerHeight = Height.getOuter(elm);
      Css.set(parentElem, 'padding-top', headerHeight + 'px');
    } else {
      Css.remove(elm, 'width');
      Css.remove(parentElem, 'padding-top');
    }
  });
};

const updateSinkVisibility = (sinkElem: Element<HTMLElement>, visible: boolean): void => {
  if (visible) {
    Classes.remove(sinkElem, [ visibility.fadeOutClass ]);
    Classes.add(sinkElem, [ visibility.transitionClass, visibility.fadeInClass ]);
  } else {
    Classes.remove(sinkElem, [ visibility.fadeInClass ]);
    Classes.add(sinkElem, [ visibility.fadeOutClass, visibility.transitionClass ]);
  }
};

const restoreFocus = (headerElem: Element, focusedElem: Element) => {
  // When the header is hidden, then the element that was focused will be lost
  // so we need to restore it if nothing else has already been focused (eg anything other than the body)
  const ownerDoc = Traverse.owner(focusedElem);
  Focus.active(ownerDoc).filter((activeElm) => {
    // Don't try to refocus the same element
    return !Compare.eq(focusedElem, activeElm);
  }).filter((activeElm) => {
    // Only attempt to refocus if the current focus is the body or is in the header element
    return Compare.eq(activeElm, Element.fromDom(ownerDoc.dom().body)) || Compare.contains(headerElem, activeElm);
  }).each(() => Focus.focus(focusedElem));
};

const findFocusedElem = (rootElm: Element, lazySink: () => Result<AlloyComponent, Error>): Option<Element> => {
  // Check to see if an element is focused inside the header or inside the sink
  // and if so store the element so we can restore it later
  return Focus.search(rootElm).orThunk(() => {
    return lazySink().toOption().bind((sink) => Focus.search(sink.element()));
  });
};

const setup = (editor: Editor, lazyHeader: () => Option<AlloyComponent>): void => {
  if (!editor.inline) {
    // No need to update the content flow in inline mode as the header always floats
    editor.on('ResizeWindow ResizeEditor ResizeContent', () => {
      lazyHeader().each(updateContentFlow);
    });

    // Need to reset the docking position on skin loaded as the original position will have
    // changed due the skins styles being applied.
    // Note: Inline handles it's own skin loading, as it needs to do other initial positioning
    editor.on('SkinLoaded', () => {
      lazyHeader().each(Docking.reset);
    });
  }
};

const isDocked = (lazyHeader: () => Option<AlloyComponent>): boolean => {
  return lazyHeader().map(Docking.isDocked).getOr(false);
};

const getBehaviours = (editor: Editor, lazySink: () => Result<AlloyComponent, Error>) => {
  const focusedElm = Cell<Option<Element>>(Option.none());

  const runOnSinkElement = (f: (sink: Element) => void) => {
    lazySink().each((sink) => f(sink.element()));
  };

  const onDockingSwitch = (comp: AlloyComponent) => {
    if (!editor.inline) {
      updateContentFlow(comp);
    }
    comp.getSystem().broadcastOn( [ Channels.reposition() ], { });
    lazySink().each((sink) => sink.getSystem().broadcastOn( [ Channels.reposition() ], { }));
  };

  return [
    Docking.config({
      leftAttr: 'data-dock-left',
      topAttr: 'data-dock-top',
      positionAttr: 'data-dock-pos',
      contextual: {
        lazyContext (comp) {
          const headerHeight = Height.getOuter(comp.element());
          const container = editor.inline ? editor.getBody() : editor.getContainer();
          const box = Boxes.box(Element.fromDom(container));
          // Force the header to hide before it overflows outside the container
          const boxHeight = box.height() - headerHeight;
          return Option.some(Boxes.bounds(box.x(), box.y(), box.width(), boxHeight));
        },
        onShow: () => {
          runOnSinkElement((elem) => updateSinkVisibility(elem, true));
        },
        onShown: (comp) => {
          runOnSinkElement((elem) => Classes.remove(elem, [ visibility.transitionClass, visibility.fadeInClass ]));<commit_msg>TINY-3982: Fixed the inline header not disappearing when the content went outside the viewport<commit_after>/**
 * Copyright (c) Tiny Technologies, Inc. All rights reserved.
 * Licensed under the LGPL or a commercial license.
 * For LGPL see License.txt in the project root for license information.
 * For commercial licenses see https://www.tiny.cloud/
 */

import { AlloyComponent, Boxes, Docking, Focusing, Receiving } from '@ephox/alloy';
import { HTMLElement } from '@ephox/dom-globals';
import { Cell, Option, Result } from '@ephox/katamari';
import { Classes, Compare, Css, Element, Focus, Height, Traverse, Width } from '@ephox/sugar';

import Editor from 'tinymce/core/api/Editor';
import * as Channels from '../../Channels';

const visibility = {
  fadeInClass: 'tox-editor-dock-fadein',
  fadeOutClass: 'tox-editor-dock-fadeout',
  transitionClass: 'tox-editor-dock-transition'
};

const updateContentFlow = (header: AlloyComponent): void => {
  const elm = header.element();
  Traverse.parent(elm).each((parentElem: Element<HTMLElement>) => {
    if (Docking.isDocked(header)) {
      const parentWidth = Width.get(parentElem);
      Css.set(elm, 'width', parentWidth + 'px');
      const headerHeight = Height.getOuter(elm);
      Css.set(parentElem, 'padding-top', headerHeight + 'px');
    } else {
      Css.remove(elm, 'width');
      Css.remove(parentElem, 'padding-top');
    }
  });
};

const updateSinkVisibility = (sinkElem: Element<HTMLElement>, visible: boolean): void => {
  if (visible) {
    Classes.remove(sinkElem, [ visibility.fadeOutClass ]);
    Classes.add(sinkElem, [ visibility.transitionClass, visibility.fadeInClass ]);
  } else {
    Classes.remove(sinkElem, [ visibility.fadeInClass ]);
    Classes.add(sinkElem, [ visibility.fadeOutClass, visibility.transitionClass ]);
  }
};

const restoreFocus = (headerElem: Element, focusedElem: Element) => {
  // When the header is hidden, then the element that was focused will be lost
  // so we need to restore it if nothing else has already been focused (eg anything other than the body)
  const ownerDoc = Traverse.owner(focusedElem);
  Focus.active(ownerDoc).filter((activeElm) => {
    // Don't try to refocus the same element
    return !Compare.eq(focusedElem, activeElm);
  }).filter((activeElm) => {
    // Only attempt to refocus if the current focus is the body or is in the header element
    return Compare.eq(activeElm, Element.fromDom(ownerDoc.dom().body)) || Compare.contains(headerElem, activeElm);
  }).each(() => Focus.focus(focusedElem));
};

const findFocusedElem = (rootElm: Element, lazySink: () => Result<AlloyComponent, Error>): Option<Element> => {
  // Check to see if an element is focused inside the header or inside the sink
  // and if so store the element so we can restore it later
  return Focus.search(rootElm).orThunk(() => {
    return lazySink().toOption().bind((sink) => Focus.search(sink.element()));
  });
};

const setup = (editor: Editor, lazyHeader: () => Option<AlloyComponent>): void => {
  if (!editor.inline) {
    // No need to update the content flow in inline mode as the header always floats
    editor.on('ResizeWindow ResizeEditor ResizeContent', () => {
      lazyHeader().each(updateContentFlow);
    });

    // Need to reset the docking position on skin loaded as the original position will have
    // changed due the skins styles being applied.
    // Note: Inline handles it's own skin loading, as it needs to do other initial positioning
    editor.on('SkinLoaded', () => {
      lazyHeader().each(Docking.reset);
    });
  }
};

const isDocked = (lazyHeader: () => Option<AlloyComponent>): boolean => {
  return lazyHeader().map(Docking.isDocked).getOr(false);
};

const getBehaviours = (editor: Editor, lazySink: () => Result<AlloyComponent, Error>) => {
  const focusedElm = Cell<Option<Element>>(Option.none());

  const runOnSinkElement = (f: (sink: Element) => void) => {
    lazySink().each((sink) => f(sink.element()));
  };

  const onDockingSwitch = (comp: AlloyComponent) => {
    if (!editor.inline) {
      updateContentFlow(comp);
    }
    comp.getSystem().broadcastOn( [ Channels.reposition() ], { });
    lazySink().each((sink) => sink.getSystem().broadcastOn( [ Channels.reposition() ], { }));
  };

  return [
    Docking.config({
      leftAttr: 'data-dock-left',
      topAttr: 'data-dock-top',
      positionAttr: 'data-dock-pos',
      contextual: {
        lazyContext (comp) {
          const headerHeight = Height.getOuter(comp.element());
          const container = editor.inline ? editor.getContentAreaContainer() : editor.getContainer();
          const box = Boxes.box(Element.fromDom(container));
          // Force the header to hide before it overflows outside the container
          const boxHeight = box.height() - headerHeight;
          return Option.some(Boxes.bounds(box.x(), box.y(), box.width(), boxHeight));
        },
        onShow: () => {
          runOnSinkElement((elem) => updateSinkVisibility(elem, true));
        },
        onShown: (comp) => {
          runOnSinkElement((elem) => Classes.remove(elem, [ visibility.transitionClass, visibility.fadeInClass ]));
{'fraction_non_alphanumeric': 0.14306683628535083, 'fraction_numerical': 0.0003914277326548586, 'mean_word_length': 3.7292919944470153, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 77, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2404821', 'n_tokens_mistral': 3109, 'n_tokens_neox': 3062, 'n_words': 821}