Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor tooltips #7045

Merged
merged 3 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 0 additions & 18 deletions material/overrides/assets/javascripts/custom.129bd6ad.min.js

This file was deleted.

18 changes: 18 additions & 0 deletions material/overrides/assets/javascripts/custom.e2e97759.min.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion material/overrides/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
{% endblock %}
{% block scripts %}
{{ super() }}
<script src="{{ 'assets/javascripts/custom.129bd6ad.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/custom.e2e97759.min.js' | url }}"></script>
{% endblock %}
29 changes: 29 additions & 0 deletions material/templates/assets/javascripts/bundle.3220b9d7.min.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

29 changes: 0 additions & 29 deletions material/templates/assets/javascripts/bundle.ae821067.min.js

This file was deleted.

This file was deleted.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

This file was deleted.

4 changes: 2 additions & 2 deletions material/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
{% endif %}
{% endblock %}
{% block styles %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.8b0efcb2.min.css' | url }}">
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.66ac8b77.min.css' | url }}">
{% if config.theme.palette %}
{% set palette = config.theme.palette %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.06af60db.min.css' | url }}">
Expand Down Expand Up @@ -249,7 +249,7 @@
</script>
{% endblock %}
{% block scripts %}
<script src="{{ 'assets/javascripts/bundle.ae821067.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.3220b9d7.min.js' | url }}"></script>
{% for script in config.extra_javascript %}
{{ script | script_tag }}
{% endfor %}
Expand Down
22 changes: 15 additions & 7 deletions src/templates/assets/javascripts/browser/element/hover/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@

import {
Observable,
debounceTime,
debounce,
defer,
fromEvent,
identity,
map,
merge,
startWith
startWith,
timer
} from "rxjs"

/* ----------------------------------------------------------------------------
Expand All @@ -37,20 +39,26 @@ import {
/**
* Watch element hover
*
* The second parameter allows to specify a timeout in milliseconds after which
* the hover state will be reset to `false`. This is useful for tooltips which
* should disappear after a certain amount of time, in order to allow the user
* to move the cursor from the host to the tooltip.
*
* @param el - Element
* @param duration - Debounce duration
* @param timeout - Timeout
*
* @returns Element hover observable
*/
export function watchElementHover(
el: HTMLElement, duration?: number
el: HTMLElement, timeout?: number
): Observable<boolean> {
return merge(
return defer(() => merge(
fromEvent(el, "mouseenter").pipe(map(() => true)),
fromEvent(el, "mouseleave").pipe(map(() => false))
)
.pipe(
duration ? debounceTime(duration) : identity,
startWith(false)
timeout ? debounce(active => timer(+!active * timeout)) : identity,
startWith(el.matches(":hover"))
)
)
}
39 changes: 39 additions & 0 deletions src/templates/assets/javascripts/browser/element/offset/_/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
startWith
} from "rxjs"

import { watchElementSize } from "../../size"

/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
Expand Down Expand Up @@ -62,6 +64,23 @@ export function getElementOffset(
}
}

/**
* Retrieve absolute element offset
*
* @param el - Element
*
* @returns Element offset
*/
export function getElementOffsetAbsolute(
el: HTMLElement
): ElementOffset {
const rect = el.getBoundingClientRect()
return {
x: rect.x + window.scrollX,
y: rect.y + window.scrollY
}
}

/* ------------------------------------------------------------------------- */

/**
Expand All @@ -84,3 +103,23 @@ export function watchElementOffset(
startWith(getElementOffset(el))
)
}

/**
* Watch absolute element offset
*
* @param el - Element
*
* @returns Element offset observable
*/
export function watchElementOffsetAbsolute(
el: HTMLElement
): Observable<ElementOffset> {
return merge(
watchElementOffset(el),
watchElementSize(document.body) // @todo find a better way for this
)
.pipe(
map(() => getElementOffsetAbsolute(el)),
startWith(getElementOffsetAbsolute(el))
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export function watchElementContentOffset(
): Observable<ElementOffset> {
return merge(
fromEvent(el, "scroll"),
fromEvent(window, "scroll"),
fromEvent(window, "resize")
)
.pipe(
Expand Down
50 changes: 29 additions & 21 deletions src/templates/assets/javascripts/browser/element/size/_/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,12 @@ const observer$ = defer(() => (
: of(undefined)
))
.pipe(
map(() => new ResizeObserver(entries => {
for (const entry of entries)
entry$.next(entry)
})),
switchMap(observer => merge(NEVER, of(observer))
.pipe(
finalize(() => observer.disconnect())
)
),
map(() => new ResizeObserver(entries => (
entries.forEach(entry => entry$.next(entry))
))),
switchMap(observer => merge(NEVER, of(observer)).pipe(
finalize(() => observer.disconnect())
)),
shareReplay(1)
)

Expand Down Expand Up @@ -136,16 +133,27 @@ export function getElementSize(
export function watchElementSize(
el: HTMLElement
): Observable<ElementSize> {
return observer$
.pipe(
tap(observer => observer.observe(el)),
switchMap(observer => entry$
.pipe(
filter(({ target }) => target === el),
finalize(() => observer.unobserve(el)),
map(() => getElementSize(el))
)
),
startWith(getElementSize(el))
)

// Compute target element - since inline elements cannot be observed by the
// current `ResizeObserver` implementation as provided by browsers, we need
// to determine the first containing parent element and use that one as a
// target, while we always compute the actual size from the element.
let target = el
while (target.clientWidth === 0)
if (target.parentElement)
target = target.parentElement
else
break

// Observe target element and recompute element size on resize - as described
// above, the target element is not necessarily the element of interest
return observer$.pipe(
tap(observer => observer.observe(target)),
switchMap(observer => entry$.pipe(
filter(entry => entry.target === target),
finalize(() => observer.unobserve(target))
)),
map(() => getElementSize(el)),
startWith(getElementSize(el))
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,40 @@ export function getElementContainer(
/* Return overflowing container */
return parent ? el : undefined
}

/**
* Retrieve all overflowing containers of an element, if any
*
* Note that this function has a slightly different behavior, so we should at
* some point consider refactoring how overflowing containers are handled.
*
* @param el - Element
*
* @returns Overflowing containers
*/
export function getElementContainers(
el: HTMLElement
): HTMLElement[] {
const containers: HTMLElement[] = []

// Walk up the DOM tree until we find an overflowing container
let parent = el.parentElement
while (parent) {
if (
el.clientWidth > parent.clientWidth ||
el.clientHeight > parent.clientHeight
)
containers.push(parent)

// Continue with parent element
parent = (el = parent).parentElement
}

// If the page is short, the body might not be overflowing and there might be
// no other containers, which is why we need to make sure the body is present
if (containers.length === 0)
containers.push(document.documentElement)

// Return overflowing containers
return containers
}
2 changes: 1 addition & 1 deletion src/templates/assets/javascripts/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ keyboard$
})

/* Set up patches */
patchEllipsis({ document$ })
patchEllipsis({ viewport$, document$ })
patchIndeterminate({ document$, tablet$ })
patchScrollfix({ document$ })
patchScrolllock({ viewport$, tablet$ })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import { Viewport, getElements } from "~/browser"
import { Component } from "../../_"
import {
Tooltip,
mountTooltip
} from "../../tooltip"
mountInlineTooltip2
} from "../../tooltip2"
import {
Annotation,
mountAnnotationBlock
Expand Down Expand Up @@ -131,6 +131,6 @@ export function mountContent(
/* Tooltips */
...getElements("[title]", el)
.filter(() => feature("content.tooltips"))
.map(child => mountTooltip(child))
.map(child => mountInlineTooltip2(child, { viewport$ }))
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ import {
watchElementSize,
watchElementVisibility
} from "~/browser"
import {
Tooltip,
mountInlineTooltip2
} from "~/components/tooltip2"
import { renderClipboardButton } from "~/templates"

import { Component } from "../../../_"
import {
Tooltip,
mountTooltip
} from "../../../tooltip"
import {
Annotation,
mountAnnotationList
Expand Down Expand Up @@ -200,7 +200,7 @@ export function mountCodeBlock(
const button = renderClipboardButton(parent.id)
parent.insertBefore(button, el)
if (feature("content.tooltips"))
content$.push(mountTooltip(button))
content$.push(mountInlineTooltip2(button, { viewport$ }))
}
}

Expand Down