<commit_before>import {Grid, ContentBox, Layoutable, Sizeable} from "core/layout"
import {div, position, size, scroll_size, show, hide, undisplay, children} from "core/dom"
import {sum, remove_at} from "core/util/array"
import {Location} from "core/enums"
import * as p from "core/properties"

import {LayoutDOM, LayoutDOMView} from "./layout_dom"
import {CallbackLike0} from "../callbacks/callback"
import {Model} from "../../model"

export class TabsView extends LayoutDOMView {
  model: Tabs

  protected header: Layoutable

  protected header_el: HTMLElement
  protected wrapper_el: HTMLElement
  protected scroll_el: HTMLElement
  protected headers_el: HTMLElement

  connect_signals(): void {
    super.connect_signals()
    this.connect(this.model.properties.tabs.change, () => this.rebuild())
    this.connect(this.model.properties.active.change, () => this.on_active_change())
  }

  get child_models(): LayoutDOM[] {
    return this.model.tabs.map((tab) => tab.child)
  }

  _update_layout(): void {
    const loc = this.model.tabs_location
    const vertical = loc == "above" || loc == "below"

    // XXX: this is a hack, this should be handled by "fit" policy in grid layout
    const {scroll_el, headers_el} = this
    this.header = new class extends ContentBox {
      protected _measure(viewport: Sizeable) {
        const min_headers = 3

        const scroll = size(scroll_el)
        const headers = children(headers_el).slice(0, min_headers).map((el) => size(el))

        const {width, height} = super._measure(viewport)
        if (vertical) {
          const min_width = scroll.width + sum(headers.map((size) => size.width))
          return {width: viewport.width != Infinity ? viewport.width : min_width, height}
        } else {
          const min_height = scroll.height + sum(headers.map((size) => size.height))
          return {width, height: viewport.height != Infinity ? viewport.height : min_height}
        }
      }
    }(this.header_el)
    if (vertical)
      this.header.set_sizing({width_policy: "fit", height_policy: "fixed"})
    else
      this.header.set_sizing({width_policy: "fixed", height_policy: "fit"})

    let row = 1
    let col = 1
    switch (loc) {
      case "above": row -= 1; break
      case "below": row += 1; break
      case "left":  col -= 1; break
      case "right": col += 1; break
    }

    const header = {layout: this.header, row, col}

    const panels = this.child_views.map((child_view) => {
      return {layout: child_view.layout, row: 1, col: 1}
    })

    this.layout = new Grid([header, ...panels])
    this.layout.set_sizing(this.box_sizing())
  }

  update_position(): void {
    super.update_position()

    this.header_el.style.position = "absolute" // XXX: do it in position()
    position(this.header_el, this.header.bbox)

    const loc = this.model.tabs_location
    const vertical = loc == "above" || loc == "below"

    const scroll_el_size = size(this.scroll_el)
    const headers_el_size = scroll_size(this.headers_el)
    if (vertical) {
      const {width} = this.header.bbox
      if (headers_el_size.width > width)
        this.wrapper_el.style.maxWidth = `${width - scroll_el_size.width}px`
      else
        undisplay(this.scroll_el)
    } else {
      const {height} = this.header.bbox
      if (headers_el_size.height > height)
        this.wrapper_el.style.maxHeight = `${height - scroll_el_size.height}px`
      else
        undisplay(this.scroll_el)
    }

    const {child_views} = this
    for (const child_view of child_views)
      hide(child_view.el)<commit_msg>Update tabs header when layout recomputes (#8780)<commit_after>import {Grid, ContentBox, Layoutable, Sizeable} from "core/layout"
import {div, position, size, scroll_size, show, hide, display, undisplay, children} from "core/dom"
import {sum, remove_at} from "core/util/array"
import {Location} from "core/enums"
import * as p from "core/properties"

import {LayoutDOM, LayoutDOMView} from "./layout_dom"
import {CallbackLike0} from "../callbacks/callback"
import {Model} from "../../model"

export class TabsView extends LayoutDOMView {
  model: Tabs

  protected header: Layoutable

  protected header_el: HTMLElement
  protected wrapper_el: HTMLElement
  protected scroll_el: HTMLElement
  protected headers_el: HTMLElement

  connect_signals(): void {
    super.connect_signals()
    this.connect(this.model.properties.tabs.change, () => this.rebuild())
    this.connect(this.model.properties.active.change, () => this.on_active_change())
  }

  get child_models(): LayoutDOM[] {
    return this.model.tabs.map((tab) => tab.child)
  }

  _update_layout(): void {
    const loc = this.model.tabs_location
    const vertical = loc == "above" || loc == "below"

    // XXX: this is a hack, this should be handled by "fit" policy in grid layout
    const {scroll_el, headers_el} = this
    this.header = new class extends ContentBox {
      protected _measure(viewport: Sizeable) {
        const min_headers = 3

        const scroll = size(scroll_el)
        const headers = children(headers_el).slice(0, min_headers).map((el) => size(el))

        const {width, height} = super._measure(viewport)
        if (vertical) {
          const min_width = scroll.width + sum(headers.map((size) => size.width))
          return {width: viewport.width != Infinity ? viewport.width : min_width, height}
        } else {
          const min_height = scroll.height + sum(headers.map((size) => size.height))
          return {width, height: viewport.height != Infinity ? viewport.height : min_height}
        }
      }
    }(this.header_el)
    if (vertical)
      this.header.set_sizing({width_policy: "fit", height_policy: "fixed"})
    else
      this.header.set_sizing({width_policy: "fixed", height_policy: "fit"})

    let row = 1
    let col = 1
    switch (loc) {
      case "above": row -= 1; break
      case "below": row += 1; break
      case "left":  col -= 1; break
      case "right": col += 1; break
    }

    const header = {layout: this.header, row, col}

    const panels = this.child_views.map((child_view) => {
      return {layout: child_view.layout, row: 1, col: 1}
    })

    this.layout = new Grid([header, ...panels])
    this.layout.set_sizing(this.box_sizing())
  }

  update_position(): void {
    super.update_position()

    this.header_el.style.position = "absolute" // XXX: do it in position()
    position(this.header_el, this.header.bbox)

    const loc = this.model.tabs_location
    const vertical = loc == "above" || loc == "below"

    const scroll_el_size = size(this.scroll_el)
    const headers_el_size = scroll_size(this.headers_el)
    if (vertical) {
      const {width} = this.header.bbox
      if (headers_el_size.width > width) {
        this.wrapper_el.style.maxWidth = `${width - scroll_el_size.width}px`
        display(this.scroll_el)
      } else {
        this.wrapper_el.style.maxWidth = ""
        undisplay(this.scroll_el)
      }
    } else {
      const {height} = this.header.bbox
      if (headers_el_size.height > height) {
        this.wrapper_el.style.maxHeight = `${height - scroll_el_size.height}px`
        display(this.scroll_el)
      } else {
        this.wrapper_el.style.maxHeight = ""
        undisplay(this.scroll_el)
      }
    }

    const {child_views} = this
    for (const child_view of child_views)
      hide(child_view.el)
{'fraction_non_alphanumeric': 0.12707032310616345, 'fraction_numerical': 0.0035297311973934292, 'mean_word_length': 3.065673289183223, 'pattern_counts': {'":': 8, '<': 3, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '656769', 'n_tokens_mistral': 2374, 'n_tokens_neox': 2309, 'n_words': 574}