@codemirror/autocomplete
Advanced tools
Comparing version 0.19.2 to 0.19.3
@@ -0,1 +1,17 @@ | ||
## 0.19.3 (2021-08-31) | ||
### Bug fixes | ||
Improve the sorting of completions by using `localeCompare`. | ||
Fix reading of autocompletions in NVDA screen reader. | ||
### New features | ||
The new `icons` option can be used to turn off icons in the completion list. | ||
The `optionClass` option can now be used to add CSS classes to the options in the completion list. | ||
It is now possible to inject additional content into rendered completion options with the `addToOptions` configuration option. | ||
## 0.19.2 (2021-08-25) | ||
@@ -2,0 +18,0 @@ |
@@ -30,2 +30,26 @@ import { EditorState, Transaction, StateCommand, Facet, Extension } from '@codemirror/state'; | ||
defaultKeymap?: boolean; | ||
/** | ||
This can be used to add additional CSS classes to completion | ||
options. | ||
*/ | ||
optionClass?: (completion: Completion) => string; | ||
/** | ||
By default, the library will render icons based on the | ||
completion's [type](https://codemirror.net/6/docs/ref/#autocomplete.Completion.type) in front of | ||
each option. Set this to false to turn that off. | ||
*/ | ||
icons?: boolean; | ||
/** | ||
This option can be used to inject additional content into | ||
options. The `render` function will be called for each visible | ||
completion, and should produce a DOM node to show. `position` | ||
determines where in the DOM the result appears, relative to | ||
other added widgets and the standard content. The default icons | ||
have position 20, the label position 50, and the detail position | ||
70. | ||
*/ | ||
addToOptions?: { | ||
render: (completion: Completion, state: EditorState) => Node | null; | ||
position: number; | ||
}[]; | ||
} | ||
@@ -32,0 +56,0 @@ |
@@ -300,8 +300,17 @@ import { Facet, combineConfig, StateEffect, StateField, Text, EditorSelection, Prec } from '@codemirror/state'; | ||
maxRenderedOptions: 100, | ||
defaultKeymap: true | ||
defaultKeymap: true, | ||
optionClass: () => "", | ||
icons: true, | ||
addToOptions: [] | ||
}, { | ||
defaultKeymap: (a, b) => a && b | ||
defaultKeymap: (a, b) => a && b, | ||
icons: (a, b) => a && b, | ||
optionClass: (a, b) => c => joinClass(a(c), b(c)), | ||
addToOptions: (a, b) => a.concat(b) | ||
}); | ||
} | ||
}); | ||
function joinClass(a, b) { | ||
return a ? b ? a + " " + b : a : b; | ||
} | ||
@@ -407,42 +416,47 @@ const MaxInfoWidth = 300; | ||
function createListBox(options, id, range) { | ||
const ul = document.createElement("ul"); | ||
ul.id = id; | ||
ul.setAttribute("role", "listbox"); | ||
ul.setAttribute("aria-expanded", "true"); | ||
for (let i = range.from; i < range.to; i++) { | ||
let { completion, match } = options[i]; | ||
const li = ul.appendChild(document.createElement("li")); | ||
li.id = id + "-" + i; | ||
let icon = li.appendChild(document.createElement("div")); | ||
icon.classList.add("cm-completionIcon"); | ||
if (completion.type) | ||
icon.classList.add(...completion.type.split(/\s+/g).map(cls => "cm-completionIcon-" + cls)); | ||
icon.setAttribute("aria-hidden", "true"); | ||
let labelElt = li.appendChild(document.createElement("span")); | ||
labelElt.className = "cm-completionLabel"; | ||
let { label, detail } = completion, off = 0; | ||
for (let j = 1; j < match.length;) { | ||
let from = match[j++], to = match[j++]; | ||
if (from > off) | ||
labelElt.appendChild(document.createTextNode(label.slice(off, from))); | ||
let span = labelElt.appendChild(document.createElement("span")); | ||
span.appendChild(document.createTextNode(label.slice(from, to))); | ||
span.className = "cm-completionMatchedText"; | ||
off = to; | ||
} | ||
if (off < label.length) | ||
labelElt.appendChild(document.createTextNode(label.slice(off))); | ||
if (detail) { | ||
let detailElt = li.appendChild(document.createElement("span")); | ||
function optionContent(config) { | ||
let content = config.addToOptions.slice(); | ||
if (config.icons) | ||
content.push({ | ||
render(completion) { | ||
let icon = document.createElement("div"); | ||
icon.classList.add("cm-completionIcon"); | ||
if (completion.type) | ||
icon.classList.add(...completion.type.split(/\s+/g).map(cls => "cm-completionIcon-" + cls)); | ||
icon.setAttribute("aria-hidden", "true"); | ||
return icon; | ||
}, | ||
position: 20 | ||
}); | ||
content.push({ | ||
render(completion, _s, match) { | ||
let labelElt = document.createElement("span"); | ||
labelElt.className = "cm-completionLabel"; | ||
let { label } = completion, off = 0; | ||
for (let j = 1; j < match.length;) { | ||
let from = match[j++], to = match[j++]; | ||
if (from > off) | ||
labelElt.appendChild(document.createTextNode(label.slice(off, from))); | ||
let span = labelElt.appendChild(document.createElement("span")); | ||
span.appendChild(document.createTextNode(label.slice(from, to))); | ||
span.className = "cm-completionMatchedText"; | ||
off = to; | ||
} | ||
if (off < label.length) | ||
labelElt.appendChild(document.createTextNode(label.slice(off))); | ||
return labelElt; | ||
}, | ||
position: 50 | ||
}, { | ||
render(completion) { | ||
if (!completion.detail) | ||
return null; | ||
let detailElt = document.createElement("span"); | ||
detailElt.className = "cm-completionDetail"; | ||
detailElt.textContent = detail; | ||
} | ||
li.setAttribute("role", "option"); | ||
} | ||
if (range.from) | ||
ul.classList.add("cm-completionListIncompleteTop"); | ||
if (range.to < options.length) | ||
ul.classList.add("cm-completionListIncompleteBottom"); | ||
return ul; | ||
detailElt.textContent = completion.detail; | ||
return detailElt; | ||
}, | ||
position: 80 | ||
}); | ||
return content.sort((a, b) => a.position - b.position).map(a => a.render); | ||
} | ||
@@ -488,2 +502,4 @@ function createInfoDialog(option, view) { | ||
let config = view.state.facet(completionConfig); | ||
this.optionContent = optionContent(config); | ||
this.optionClass = config.optionClass; | ||
this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions); | ||
@@ -501,3 +517,3 @@ this.dom = document.createElement("div"); | ||
}); | ||
this.list = this.dom.appendChild(createListBox(options, cState.id, this.range)); | ||
this.list = this.dom.appendChild(this.createListBox(options, cState.id, this.range)); | ||
this.list.addEventListener("scroll", () => { | ||
@@ -522,3 +538,3 @@ if (this.info) | ||
this.list.remove(); | ||
this.list = this.dom.appendChild(createListBox(open.options, cState.id, this.range)); | ||
this.list = this.dom.appendChild(this.createListBox(open.options, cState.id, this.range)); | ||
this.list.addEventListener("scroll", () => { | ||
@@ -582,2 +598,26 @@ if (this.info) | ||
} | ||
createListBox(options, id, range) { | ||
const ul = document.createElement("ul"); | ||
ul.id = id; | ||
ul.setAttribute("role", "listbox"); | ||
for (let i = range.from; i < range.to; i++) { | ||
let { completion, match } = options[i]; | ||
const li = ul.appendChild(document.createElement("li")); | ||
li.id = id + "-" + i; | ||
li.setAttribute("role", "option"); | ||
let cls = this.optionClass(completion); | ||
if (cls) | ||
li.className = cls; | ||
for (let source of this.optionContent) { | ||
let node = source(completion, this.view.state, match); | ||
if (node) | ||
li.appendChild(node); | ||
} | ||
} | ||
if (range.from) | ||
ul.classList.add("cm-completionListIncompleteTop"); | ||
if (range.to < options.length) | ||
ul.classList.add("cm-completionListIncompleteBottom"); | ||
return ul; | ||
} | ||
} | ||
@@ -717,10 +757,15 @@ // We allocate a new function instance every time the completion | ||
} | ||
const baseAttrs = { | ||
"aria-autocomplete": "list", | ||
"aria-expanded": "false" | ||
}; | ||
function makeAttrs(id, selected) { | ||
return { | ||
"aria-autocomplete": "list", | ||
"aria-expanded": "true", | ||
"aria-activedescendant": id + "-" + selected, | ||
"aria-owns": id | ||
"aria-controls": id | ||
}; | ||
} | ||
const baseAttrs = { "aria-autocomplete": "list" }, none = []; | ||
const none = []; | ||
function cmpOption(a, b) { | ||
@@ -730,4 +775,3 @@ let dScore = b.match[0] - a.match[0]; | ||
return dScore; | ||
let lA = a.completion.label, lB = b.completion.label; | ||
return lA < lB ? -1 : lA == lB ? 0 : 1; | ||
return a.completion.label.localeCompare(b.completion.label); | ||
} | ||
@@ -734,0 +778,0 @@ function getUserEvent(tr) { |
{ | ||
"name": "@codemirror/autocomplete", | ||
"version": "0.19.2", | ||
"version": "0.19.3", | ||
"description": "Autocompletion for the CodeMirror code editor", | ||
@@ -5,0 +5,0 @@ "scripts": { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
134511
3145