Skip to content

Commit f028004

Browse files
authored
Merge pull request #7045 from squidfunk/refactor/tooltip-positioning
Refactor tooltips
2 parents 851e5bb + 29658ed commit f028004

File tree

25 files changed

+786
-104
lines changed

25 files changed

+786
-104
lines changed

material/overrides/assets/javascripts/custom.129bd6ad.min.js

-18
This file was deleted.

material/overrides/assets/javascripts/custom.e2e97759.min.js

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

material/overrides/assets/javascripts/custom.129bd6ad.min.js.map renamed to material/overrides/assets/javascripts/custom.e2e97759.min.js.map

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

material/overrides/main.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@
2323
{% endblock %}
2424
{% block scripts %}
2525
{{ super() }}
26-
<script src="{{ 'assets/javascripts/custom.129bd6ad.min.js' | url }}"></script>
26+
<script src="{{ 'assets/javascripts/custom.e2e97759.min.js' | url }}"></script>
2727
{% endblock %}

material/templates/assets/javascripts/bundle.3220b9d7.min.js

+29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

material/templates/assets/javascripts/bundle.3220b9d7.min.js.map

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

material/templates/assets/javascripts/bundle.ae821067.min.js

-29
This file was deleted.

material/templates/assets/javascripts/bundle.ae821067.min.js.map

-7
This file was deleted.

material/templates/assets/stylesheets/main.8b0efcb2.min.css renamed to material/templates/assets/stylesheets/main.66ac8b77.min.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

material/templates/assets/stylesheets/main.66ac8b77.min.css.map

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

material/templates/assets/stylesheets/main.8b0efcb2.min.css.map

-1
This file was deleted.

material/templates/base.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
{% endif %}
4545
{% endblock %}
4646
{% block styles %}
47-
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.8b0efcb2.min.css' | url }}">
47+
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.66ac8b77.min.css' | url }}">
4848
{% if config.theme.palette %}
4949
{% set palette = config.theme.palette %}
5050
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.06af60db.min.css' | url }}">
@@ -249,7 +249,7 @@
249249
</script>
250250
{% endblock %}
251251
{% block scripts %}
252-
<script src="{{ 'assets/javascripts/bundle.ae821067.min.js' | url }}"></script>
252+
<script src="{{ 'assets/javascripts/bundle.3220b9d7.min.js' | url }}"></script>
253253
{% for script in config.extra_javascript %}
254254
{{ script | script_tag }}
255255
{% endfor %}

src/templates/assets/javascripts/browser/element/hover/index.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@
2222

2323
import {
2424
Observable,
25-
debounceTime,
25+
debounce,
26+
defer,
2627
fromEvent,
2728
identity,
2829
map,
2930
merge,
30-
startWith
31+
startWith,
32+
timer
3133
} from "rxjs"
3234

3335
/* ----------------------------------------------------------------------------
@@ -37,20 +39,26 @@ import {
3739
/**
3840
* Watch element hover
3941
*
42+
* The second parameter allows to specify a timeout in milliseconds after which
43+
* the hover state will be reset to `false`. This is useful for tooltips which
44+
* should disappear after a certain amount of time, in order to allow the user
45+
* to move the cursor from the host to the tooltip.
46+
*
4047
* @param el - Element
41-
* @param duration - Debounce duration
48+
* @param timeout - Timeout
4249
*
4350
* @returns Element hover observable
4451
*/
4552
export function watchElementHover(
46-
el: HTMLElement, duration?: number
53+
el: HTMLElement, timeout?: number
4754
): Observable<boolean> {
48-
return merge(
55+
return defer(() => merge(
4956
fromEvent(el, "mouseenter").pipe(map(() => true)),
5057
fromEvent(el, "mouseleave").pipe(map(() => false))
5158
)
5259
.pipe(
53-
duration ? debounceTime(duration) : identity,
54-
startWith(false)
60+
timeout ? debounce(active => timer(+!active * timeout)) : identity,
61+
startWith(el.matches(":hover"))
5562
)
63+
)
5664
}

src/templates/assets/javascripts/browser/element/offset/_/index.ts

+39
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {
3030
startWith
3131
} from "rxjs"
3232

33+
import { watchElementSize } from "../../size"
34+
3335
/* ----------------------------------------------------------------------------
3436
* Types
3537
* ------------------------------------------------------------------------- */
@@ -62,6 +64,23 @@ export function getElementOffset(
6264
}
6365
}
6466

67+
/**
68+
* Retrieve absolute element offset
69+
*
70+
* @param el - Element
71+
*
72+
* @returns Element offset
73+
*/
74+
export function getElementOffsetAbsolute(
75+
el: HTMLElement
76+
): ElementOffset {
77+
const rect = el.getBoundingClientRect()
78+
return {
79+
x: rect.x + window.scrollX,
80+
y: rect.y + window.scrollY
81+
}
82+
}
83+
6584
/* ------------------------------------------------------------------------- */
6685

6786
/**
@@ -84,3 +103,23 @@ export function watchElementOffset(
84103
startWith(getElementOffset(el))
85104
)
86105
}
106+
107+
/**
108+
* Watch absolute element offset
109+
*
110+
* @param el - Element
111+
*
112+
* @returns Element offset observable
113+
*/
114+
export function watchElementOffsetAbsolute(
115+
el: HTMLElement
116+
): Observable<ElementOffset> {
117+
return merge(
118+
watchElementOffset(el),
119+
watchElementSize(document.body) // @todo find a better way for this
120+
)
121+
.pipe(
122+
map(() => getElementOffsetAbsolute(el)),
123+
startWith(getElementOffsetAbsolute(el))
124+
)
125+
}

src/templates/assets/javascripts/browser/element/offset/content/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export function watchElementContentOffset(
6666
): Observable<ElementOffset> {
6767
return merge(
6868
fromEvent(el, "scroll"),
69+
fromEvent(window, "scroll"),
6970
fromEvent(window, "resize")
7071
)
7172
.pipe(

src/templates/assets/javascripts/browser/element/size/_/index.ts

+29-21
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,12 @@ const observer$ = defer(() => (
8080
: of(undefined)
8181
))
8282
.pipe(
83-
map(() => new ResizeObserver(entries => {
84-
for (const entry of entries)
85-
entry$.next(entry)
86-
})),
87-
switchMap(observer => merge(NEVER, of(observer))
88-
.pipe(
89-
finalize(() => observer.disconnect())
90-
)
91-
),
83+
map(() => new ResizeObserver(entries => (
84+
entries.forEach(entry => entry$.next(entry))
85+
))),
86+
switchMap(observer => merge(NEVER, of(observer)).pipe(
87+
finalize(() => observer.disconnect())
88+
)),
9289
shareReplay(1)
9390
)
9491

@@ -136,16 +133,27 @@ export function getElementSize(
136133
export function watchElementSize(
137134
el: HTMLElement
138135
): Observable<ElementSize> {
139-
return observer$
140-
.pipe(
141-
tap(observer => observer.observe(el)),
142-
switchMap(observer => entry$
143-
.pipe(
144-
filter(({ target }) => target === el),
145-
finalize(() => observer.unobserve(el)),
146-
map(() => getElementSize(el))
147-
)
148-
),
149-
startWith(getElementSize(el))
150-
)
136+
137+
// Compute target element - since inline elements cannot be observed by the
138+
// current `ResizeObserver` implementation as provided by browsers, we need
139+
// to determine the first containing parent element and use that one as a
140+
// target, while we always compute the actual size from the element.
141+
let target = el
142+
while (target.clientWidth === 0)
143+
if (target.parentElement)
144+
target = target.parentElement
145+
else
146+
break
147+
148+
// Observe target element and recompute element size on resize - as described
149+
// above, the target element is not necessarily the element of interest
150+
return observer$.pipe(
151+
tap(observer => observer.observe(target)),
152+
switchMap(observer => entry$.pipe(
153+
filter(entry => entry.target === target),
154+
finalize(() => observer.unobserve(target))
155+
)),
156+
map(() => getElementSize(el)),
157+
startWith(getElementSize(el))
158+
)
151159
}

src/templates/assets/javascripts/browser/element/size/content/index.ts

+37
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,40 @@ export function getElementContainer(
6565
/* Return overflowing container */
6666
return parent ? el : undefined
6767
}
68+
69+
/**
70+
* Retrieve all overflowing containers of an element, if any
71+
*
72+
* Note that this function has a slightly different behavior, so we should at
73+
* some point consider refactoring how overflowing containers are handled.
74+
*
75+
* @param el - Element
76+
*
77+
* @returns Overflowing containers
78+
*/
79+
export function getElementContainers(
80+
el: HTMLElement
81+
): HTMLElement[] {
82+
const containers: HTMLElement[] = []
83+
84+
// Walk up the DOM tree until we find an overflowing container
85+
let parent = el.parentElement
86+
while (parent) {
87+
if (
88+
el.clientWidth > parent.clientWidth ||
89+
el.clientHeight > parent.clientHeight
90+
)
91+
containers.push(parent)
92+
93+
// Continue with parent element
94+
parent = (el = parent).parentElement
95+
}
96+
97+
// If the page is short, the body might not be overflowing and there might be
98+
// no other containers, which is why we need to make sure the body is present
99+
if (containers.length === 0)
100+
containers.push(document.documentElement)
101+
102+
// Return overflowing containers
103+
return containers
104+
}

src/templates/assets/javascripts/bundle.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ keyboard$
200200
})
201201

202202
/* Set up patches */
203-
patchEllipsis({ document$ })
203+
patchEllipsis({ viewport$, document$ })
204204
patchIndeterminate({ document$, tablet$ })
205205
patchScrollfix({ document$ })
206206
patchScrolllock({ viewport$, tablet$ })

src/templates/assets/javascripts/components/content/_/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import { Viewport, getElements } from "~/browser"
2828
import { Component } from "../../_"
2929
import {
3030
Tooltip,
31-
mountTooltip
32-
} from "../../tooltip"
31+
mountInlineTooltip2
32+
} from "../../tooltip2"
3333
import {
3434
Annotation,
3535
mountAnnotationBlock
@@ -131,6 +131,6 @@ export function mountContent(
131131
/* Tooltips */
132132
...getElements("[title]", el)
133133
.filter(() => feature("content.tooltips"))
134-
.map(child => mountTooltip(child))
134+
.map(child => mountInlineTooltip2(child, { viewport$ }))
135135
)
136136
}

src/templates/assets/javascripts/components/content/code/_/index.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ import {
4646
watchElementSize,
4747
watchElementVisibility
4848
} from "~/browser"
49+
import {
50+
Tooltip,
51+
mountInlineTooltip2
52+
} from "~/components/tooltip2"
4953
import { renderClipboardButton } from "~/templates"
5054

5155
import { Component } from "../../../_"
52-
import {
53-
Tooltip,
54-
mountTooltip
55-
} from "../../../tooltip"
5656
import {
5757
Annotation,
5858
mountAnnotationList
@@ -200,7 +200,7 @@ export function mountCodeBlock(
200200
const button = renderClipboardButton(parent.id)
201201
parent.insertBefore(button, el)
202202
if (feature("content.tooltips"))
203-
content$.push(mountTooltip(button))
203+
content$.push(mountInlineTooltip2(button, { viewport$ }))
204204
}
205205
}
206206

0 commit comments

Comments
 (0)