editable-text updates
- Added nowrap attribute to not add the stretching aspect of the editable-text and to not have any text wrapping. To be used in cases when the input is a single line and it's width is stretched with flexbox, for example tags or itemBox value fields. - Added 'tight' attribute to set lower padding values on editable-text. To be applied on fields that are not as prominent as abstract or header as it makes the actual editable-text component smaller. - Minor style changes to not shift layout on focus on windows (using transparent border instead of 0 margin). - No overflow of textarea to avoid longer-than-needed textareas on windows. - Keep track if the component was focused on via mouse click or not. If the focus happened without prior mouse click, select all text. - Reset cursor and selection on blur. - Make sure the .input is always re-added if it disappears after drag-drop. - Added sizeToContent function to set max-width based on the width of the input. - Allow the browser to break up words more freely with overflow-wrap: anywhere to avoid stretching the itemBox with extra long header without spaces. - Apply 'nowrap' and 'tight' to tags so that a long tag does not stretch the itemBox or make the editable-text extra tall.
This commit is contained in:
parent
55b97cd397
commit
553d1f6b3c
3 changed files with 119 additions and 15 deletions
|
@ -29,8 +29,21 @@
|
|||
class EditableText extends XULElementBase {
|
||||
_input;
|
||||
|
||||
static observedAttributes = ['multiline', 'readonly', 'placeholder', 'label', 'aria-label', 'value'];
|
||||
static observedAttributes = [
|
||||
'multiline',
|
||||
'readonly',
|
||||
'placeholder',
|
||||
'label',
|
||||
'aria-label',
|
||||
'aria-labelledby',
|
||||
'value',
|
||||
'nowrap'
|
||||
];
|
||||
|
||||
get noWrap() {
|
||||
return this.hasAttribute('nowrap');
|
||||
}
|
||||
|
||||
get multiline() {
|
||||
return this.hasAttribute('multiline');
|
||||
}
|
||||
|
@ -68,6 +81,10 @@
|
|||
get ariaLabel() {
|
||||
return this.getAttribute('aria-label') || '';
|
||||
}
|
||||
|
||||
get ariaLabelledBy() {
|
||||
return this.getAttribute('aria-labelledby') || '';
|
||||
}
|
||||
|
||||
set ariaLabel(ariaLabel) {
|
||||
this.setAttribute('aria-label', ariaLabel);
|
||||
|
@ -115,6 +132,18 @@
|
|||
get ref() {
|
||||
return this._input;
|
||||
}
|
||||
|
||||
sizeToContent = () => {
|
||||
// Add a temp span, fetch it's width with current paddings and set max-width based on that
|
||||
let span = document.createElement("span");
|
||||
span.innerText = this.value;
|
||||
this.append(span);
|
||||
let size = span.getBoundingClientRect();
|
||||
let inlinePadding = getComputedStyle(this).getPropertyValue('--editable-text-padding-inline');
|
||||
let blockPadding = getComputedStyle(this).getPropertyValue('--editable-text-padding-block');
|
||||
this.style['max-width'] = `calc(${size.width}px + 2*${inlinePadding} + 2*${blockPadding})`;
|
||||
this.querySelector("span").remove();
|
||||
};
|
||||
|
||||
attributeChangedCallback() {
|
||||
this.render();
|
||||
|
@ -134,7 +163,7 @@
|
|||
input.type = 'autocomplete';
|
||||
}
|
||||
else {
|
||||
input = document.createElement('textarea');
|
||||
input = this.noWrap ? document.createElement('input') : document.createElement('textarea');
|
||||
input.rows = 1;
|
||||
}
|
||||
input.classList.add('input');
|
||||
|
@ -147,24 +176,38 @@
|
|||
let handleChange = () => {
|
||||
this.value = this._input.value;
|
||||
};
|
||||
input.addEventListener('mousedown', () => {
|
||||
this.setAttribute("mousedown", true);
|
||||
});
|
||||
input.addEventListener('input', handleInput);
|
||||
input.addEventListener('change', handleChange);
|
||||
input.addEventListener('focus', () => {
|
||||
this.dispatchEvent(new CustomEvent('focus'));
|
||||
this.classList.add("focused");
|
||||
// Select all text if focused via keyboard
|
||||
if (!this.getAttribute("mousedown")) {
|
||||
this._input.select();
|
||||
}
|
||||
this._input.dataset.initialValue = this._input.value;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
this.dispatchEvent(new CustomEvent('blur'));
|
||||
this.classList.remove("focused");
|
||||
this._input.scrollLeft = 0;
|
||||
this._input.setSelectionRange(0, 0);
|
||||
this.removeAttribute("mousedown");
|
||||
delete this._input.dataset.initialValue;
|
||||
});
|
||||
input.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
if (this.multiline === event.shiftKey) {
|
||||
event.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('escape_enter'));
|
||||
this._input.blur();
|
||||
}
|
||||
}
|
||||
else if (event.key === 'Escape') {
|
||||
this.dispatchEvent(new CustomEvent('escape_enter'));
|
||||
this._input.value = this.value = this._input.dataset.initialValue;
|
||||
this._input.blur();
|
||||
}
|
||||
|
@ -195,9 +238,19 @@
|
|||
}
|
||||
this._input.readOnly = this.readOnly;
|
||||
this._input.placeholder = this.label;
|
||||
this._input.setAttribute('aria-label', this.ariaLabel);
|
||||
if (this.ariaLabel.length) {
|
||||
this._input.setAttribute('aria-label', this.ariaLabel);
|
||||
}
|
||||
if (this.ariaLabelledBy.length) {
|
||||
this._input.setAttribute('aria-labelledby', this.ariaLabelledBy);
|
||||
}
|
||||
this._input.value = this.value;
|
||||
|
||||
// The actual input node can disappear if the component is moved
|
||||
if (this.childElementCount == 0) {
|
||||
this.replaceChildren(this._input);
|
||||
}
|
||||
|
||||
if (autocompleteEnabled) {
|
||||
this._input.setAttribute('autocomplete', 'on');
|
||||
this._input.setAttribute('autocompletepopup', autocompleteParams.popup || '');
|
||||
|
|
|
@ -340,6 +340,8 @@
|
|||
var valueElement = document.createXULElement("editable-text");
|
||||
valueElement.setAttribute('fieldname', 'tag');
|
||||
valueElement.setAttribute('flex', 1);
|
||||
valueElement.setAttribute('nowrap', true);
|
||||
valueElement.setAttribute('tight', true);
|
||||
valueElement.className = 'zotero-box-label';
|
||||
valueElement.readOnly = !this.editable;
|
||||
valueElement.value = valueText;
|
||||
|
|
|
@ -17,52 +17,101 @@ editable-text {
|
|||
--max-visible-lines: 20;
|
||||
}
|
||||
|
||||
&[nowrap] {
|
||||
--min-visible-lines: 1;
|
||||
}
|
||||
|
||||
&[tight] {
|
||||
@include comfortable {
|
||||
--editable-text-padding-inline: 4px;
|
||||
--editable-text-padding-block: 2px;
|
||||
}
|
||||
|
||||
@include compact {
|
||||
--editable-text-padding-inline: 3px;
|
||||
--editable-text-padding-block: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
// Fun auto-sizing approach from CSSTricks:
|
||||
// https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/
|
||||
|
||||
display: grid;
|
||||
|
||||
&::after {
|
||||
span {
|
||||
visibility: hidden;
|
||||
margin: 1px;
|
||||
width: fit-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:not([nowrap])::after {
|
||||
content: attr(value) ' ';
|
||||
visibility: hidden;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&::after, .input {
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
padding: var(--editable-text-padding-block) var(--editable-text-padding-inline);
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
&:not([nowrap])::after, &:not([nowrap]) .input {
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
max-height: calc(2ex * var(--max-visible-lines));
|
||||
}
|
||||
|
||||
.input {
|
||||
@include focus-ring;
|
||||
// Necessary for consistent padding, even if it's actually an <input>
|
||||
-moz-default-appearance: textarea;
|
||||
|
||||
min-height: calc(2ex * var(--min-visible-lines));
|
||||
margin: 0;
|
||||
|
||||
border: 1px solid transparent;
|
||||
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
padding: var(--editable-text-padding-block) var(--editable-text-padding-inline);
|
||||
|
||||
&:read-only, &:not(:focus) {
|
||||
appearance: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&:hover:not(:read-only, :focus) {
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--fill-tertiary);
|
||||
box-shadow: 0 0 0 1px var(--fill-quinary);
|
||||
margin: 0;
|
||||
box-shadow: 0 0 0 1px var(--fill-quinary);
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: var(--fill-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
&[multiline] {
|
||||
&::after, .input {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.input {
|
||||
min-height: 5em;
|
||||
}
|
||||
}
|
||||
&[nowrap] {
|
||||
.input:not(:focus, :hover) {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
&[hidden]{
|
||||
display: none;
|
||||
}
|
||||
// somehow it fixes extra tall textareas on windows that was rendered to have at least
|
||||
// 3 rows even when there's no text
|
||||
textarea {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue