Skip to content

Commit a7273e2

Browse files
arathunkujosevalim
authored andcommitted
Autocomplete preview: handle tab and arrow keys, fix lost focus on iframe load
1 parent 94c0711 commit a7273e2

16 files changed

+264
-269
lines changed

assets/css/autocomplete.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
display: none;
8383
}
8484

85-
.autocomplete-suggestions.previewing:has(.selected) .autocomplete-preview+.autocomplete-suggestion {
85+
.autocomplete-suggestions.previewing:has(.selected) .autocomplete-preview + .autocomplete-suggestion {
8686
display: block;
8787
}
8888

@@ -125,6 +125,10 @@
125125
display: none;
126126
}
127127

128+
.autocomplete-suggestion:hover .autocomplete-suggestion-preview-indicator {
129+
display: block;
130+
}
131+
128132
.autocomplete-suggestion-preview-indicator {
129133
float: right;
130134
border-radius: 100%;

assets/css/icons.css

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/*
2+
* Only used icons are included in the font
3+
* Icons can be generated at https://remixicon.com/
4+
* In assets/fonts/RemixIconCollection.remixicon there's easy to import on website list of icons
5+
*/
6+
17
@font-face {
28
font-family: 'remixicon';
39
src: url('../fonts/remixicon.woff2') format('woff2');
@@ -15,7 +21,6 @@
1521
--icon-arrow-up-s: "\ea78";
1622
--icon-arrow-down-s: "\ea4e";
1723
--icon-arrow-right-s: "\ea6e";
18-
--icon-arrow-left-s: "\ea64";
1924
--icon-add: "\ea13";
2025
--icon-subtract: "\f1af";
2126
--icon-error-warning: "\eca1";
@@ -39,7 +44,6 @@
3944
.ri-arrow-up-s-line:before { content: var(--icon-arrow-up-s); }
4045
.ri-arrow-down-s-line:before { content: var(--icon-arrow-down-s); }
4146
.ri-arrow-right-s-line:before { content: var(--icon-arrow-right-s); }
42-
.ri-arrow-left-s-line:before { content: var(--icon-arrow-left-s); }
4347
.ri-search-2-line:before { content: var(--icon-search-2-line); }
4448
.ri-menu-line:before { content: var(--icon-menu-line); }
4549
.ri-close-line:before { content: var(--icon-close-line); }
@@ -49,4 +53,4 @@
4953
.ri-information-line:before { content: var(--icon-information); }
5054
.ri-alert-line:before { content: var(--icon-alert); }
5155
.ri-double-quotes-l:before { content: var(--icon-double-quotes-l); }
52-
.ri-printer-line:before { content: var(--icon-printer-line); }
56+
.ri-printer-line:before { content: var(--icon-printer-line); }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
settings-3-line,add-line,subtract-line,arrow-up-s-line,arrow-down-s-line,arrow-right-s-line,search-2-line,menu-line,close-line,link-m,code-s-slash-line,error-warning-line,information-line,alert-line,double-quotes-l,printer-line

assets/js/autocomplete/autocomplete-list.js

Lines changed: 44 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getSuggestions } from './suggestions'
2-
import { isBlank, qs, qsAll } from '../helpers'
2+
import { isBlank, qs } from '../helpers'
33

44
export const AUTOCOMPLETE_CONTAINER_SELECTOR = '.autocomplete'
55
export const AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR = '.autocomplete-suggestions'
@@ -16,15 +16,6 @@ const state = {
1616
*/
1717
export function showAutocompleteList () {
1818
qs(AUTOCOMPLETE_CONTAINER_SELECTOR).classList.add('shown')
19-
20-
const buttons = qsAll('.autocomplete-suggestion-preview-indicator')
21-
buttons.forEach(button => {
22-
button.addEventListener('click', event => {
23-
event.preventDefault()
24-
event.stopPropagation()
25-
togglePreview()
26-
})
27-
})
2819
}
2920

3021
/**
@@ -88,8 +79,12 @@ export function selectedAutocompleteSuggestion () {
8879
* @param {Number} offset How much to move down (or up) the list.
8980
*/
9081
export function moveAutocompleteSelection (offset) {
91-
state.selectedIdx = newAutocompleteIndex(offset)
82+
setAutocompleteSelection(newAutocompleteIndex(offset))
83+
}
9284

85+
export function setAutocompleteSelection (index) {
86+
state.selectedIdx = index
87+
const suggestionList = qs(AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR)
9388
const selectedElement = qs(`${AUTOCOMPLETE_SUGGESTION_SELECTOR}.selected`)
9489
const elementToSelect = qs(`${AUTOCOMPLETE_SUGGESTION_SELECTOR}[data-index="${state.selectedIdx}"]`)
9590

@@ -98,102 +93,69 @@ export function moveAutocompleteSelection (offset) {
9893
}
9994

10095
if (elementToSelect) {
101-
elementToSelect.classList.add('selected')
102-
showPreview(elementToSelect)
96+
if (state.previewOpen) {
97+
removePreview()
98+
suggestionList.classList.add('previewing')
99+
const newContainer = document.createElement('div')
100+
newContainer.classList.add('autocomplete-preview')
101+
const iframe = document.createElement('iframe')
102+
const previewHref = elementToSelect.href.replace('.html', '.html?preview=true')
103+
// The minimum permissions necessary for the iframe to run JavaScript and communicate with the parent window.
104+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups')
105+
iframe.setAttribute('src', previewHref)
106+
newContainer.replaceChildren(iframe)
107+
elementToSelect.parentNode.insertBefore(newContainer, elementToSelect.nextSibling)
108+
}
103109

110+
elementToSelect.classList.add('selected')
104111
elementToSelect.scrollIntoView({ block: 'nearest' })
105112
} else {
106-
const list = qs(AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR)
107-
if (list) { list.scrollTop = 0 }
113+
if (suggestionList) { suggestionList.scrollTop = 0 }
108114
}
109115
}
110116

111117
/**
112118
* Toggles the preview state of the autocomplete list
113119
*/
114-
export function togglePreview () {
115-
state.previewOpen = !state.previewOpen
116-
const suggestionList = qs(AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR)
120+
export function togglePreview (elementToSelect) {
117121
if (state.previewOpen) {
118-
suggestionList.classList.add('previewing')
119-
const elementToSelect = qs(`${AUTOCOMPLETE_SUGGESTION_SELECTOR}[data-index="${state.selectedIdx}"]`)
120-
showPreview(elementToSelect)
122+
hidePreview()
121123
} else {
122-
suggestionList.classList.remove('previewing')
123-
}
124-
}
125-
126-
function expandPreview () {
127-
state.previewOpen = true
128-
const suggestionList = qs(AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR)
129-
if (suggestionList) {
130-
suggestionList.classList.add('previewing')
131-
}
132-
}
133-
134-
function closePreview () {
135-
state.previewOpen = false
136-
const suggestionList = qs(AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR)
137-
if (suggestionList) {
138-
suggestionList.classList.remove('previewing')
124+
showPreview(elementToSelect)
139125
}
140126
}
141127

142-
export function removePreview () {
128+
/**
129+
* Hides the preview state of the autocomplete list
130+
*/
131+
export function hidePreview () {
143132
state.previewOpen = false
144133
const suggestionList = qs(AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR)
145-
146-
if (suggestionList) {
147-
suggestionList.classList.remove('previewing')
148-
}
149-
150-
const container = previewContainer()
151-
152-
if (container) {
153-
container.remove()
154-
}
134+
suggestionList.classList.remove('previewing')
135+
removePreview()
155136
}
156137

138+
/**
139+
* Shows the preview state of the autocomplete list
140+
*/
157141
function showPreview (elementToSelect) {
158-
const container = previewContainer()
159-
160-
if (container) {
161-
container.remove()
162-
};
163-
142+
state.previewOpen = true
164143
const suggestionList = qs(AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR)
165144

166-
if (state.previewOpen && elementToSelect) {
167-
suggestionList.classList.add('previewing')
168-
const newContainer = document.createElement('div')
169-
newContainer.classList.add('autocomplete-preview')
170-
const iframe = document.createElement('iframe')
171-
const previewHref = elementToSelect.href.replace('.html', '.html?preview=true')
172-
// The minimum permissions necessary for the iframe to run JavaScript and communicate with the parent window.
173-
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups')
174-
iframe.setAttribute('src', previewHref)
175-
newContainer.replaceChildren(iframe)
176-
iframe.onload = () => {
177-
if (iframe.contentDocument) {
178-
iframe.contentDocument.addEventListener('keydown', event => {
179-
if (event.key === 'ArrowLeft') {
180-
closePreview()
181-
event.preventDefault()
182-
} else if (event.key === 'ArrowRight') {
183-
expandPreview()
184-
event.preventDefault()
185-
}
186-
})
187-
}
188-
}
189-
elementToSelect.parentNode.insertBefore(newContainer, elementToSelect.nextSibling)
145+
if(elementToSelect) {
146+
elementToSelect = elementToSelect.closest(AUTOCOMPLETE_SUGGESTION_SELECTOR)
190147
} else {
191-
suggestionList.classList.remove('previewing')
148+
elementToSelect = qs(`${AUTOCOMPLETE_SUGGESTION_SELECTOR}[data-index="${state.selectedIdx}"]`)
149+
}
150+
151+
if (elementToSelect) {
152+
setAutocompleteSelection(parseInt(elementToSelect.dataset.index))
192153
}
193154
}
194155

195-
function previewContainer () {
196-
return qs('.autocomplete-preview')
156+
function removePreview () {
157+
let preview = qs('.autocomplete-preview')
158+
if(preview) { preview.remove() }
197159
}
198160

199161
function newAutocompleteIndex (offset) {

assets/js/content.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ const LIVEBOOK_BADGE_ANCHOR_SELECTOR = '.livebook-badge'
88
/**
99
* Runs some general modifiers on the documentation content.
1010
*/
11-
export function initialize () {
12-
fixLinks()
13-
fixSpacebar()
11+
export function initialize (isPreview) {
12+
// Disabled on autocomplete preview because it moves the focus to inner iframe
13+
if (!isPreview) {
14+
fixSpacebar()
15+
}
16+
1417
setLivebookBadgeUrl()
18+
fixLinks()
1519
fixBlockquotes()
1620
}
1721

assets/js/entry/html.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,18 @@ import { initialize as initPreview} from '../preview'
2323

2424
onDocumentReady(() => {
2525
const params = new URLSearchParams(window.location.search)
26+
const isPreview = params.has('preview')
2627

2728
initTheme()
28-
initContent()
29+
initContent(isPreview)
2930
initMakeup()
3031
initTooltips()
3132
initHintsPage()
3233
initCopyButton()
3334
initOs()
3435
initTabsets()
3536

36-
if (params.has('preview')) {
37+
if (isPreview) {
3738
initPreview()
3839
} else {
3940
initVersions()

assets/js/handlebars/templates/autocomplete-suggestions.handlebars

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
Autocompletion results for <span class="bold">"{{term}}"</span>
77
</span>
88
<span class="press-return">
9-
Press <span class="bold">RETURN</span> for full-text search, <span class="bold">→</span> for expand previews,
10-
<span class="bold">←</span> for close previews
9+
Press <span class="bold">RETURN</span> for full-text search, <span class="bold">TAB</span> for previews
1110
</span>
1211
</div>
1312
<div>
@@ -24,13 +23,15 @@
2423
<span class="label">{{this}}</span>
2524
{{/each}}
2625
<div class="autocomplete-suggestion-preview-indicator autocomplete-suggestion-preview-indicator-open">
27-
<button type="button">
28-
<span class="ri-arrow-left-s-line" /> Close Preview
26+
<button onclick="onTogglePreviewClick(event)" class="icon-settings display-settings" title="Close preveiw" tabindex="-1">
27+
<i class="ri-close-line" aria-hidden="true"></i>
28+
Close preview
2929
</button>
3030
</div>
3131
<div class="autocomplete-suggestion-preview-indicator autocomplete-suggestion-preview-indicator-closed">
32-
<button type="button">
33-
Open Preview <span class="ri-arrow-right-s-line" />
32+
<button onclick="onTogglePreviewClick(event)" class="icon-settings display-settings" title="Open preview" tabindex="-1">
33+
<i class="ri-search-2-line" aria-hidden="true"></i>
34+
Open preview
3435
</button>
3536
</div>
3637
</div>
@@ -44,4 +45,4 @@
4445
{{/each}}
4546
</div>
4647
</div>
47-
</div>
48+
</div>

assets/js/preview.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ function preview (previewing) {
1717
function makeLinksOpenInParent () {
1818
const links = document.getElementsByTagName('a')
1919
for (const element of links) {
20-
console.log(element)
2120
if (element.getAttribute('target') !== '_blank') {
2221
element.setAttribute('target', '_parent')
2322
}

0 commit comments

Comments
 (0)