mirror of
https://github.com/ONLYOFFICE/onlyoffice.github.io.git
synced 2026-02-10 18:05:06 +08:00
text input component
This commit is contained in:
128
sdkjs-plugins/content/icons/src/js/components/input/input.css
Normal file
128
sdkjs-plugins/content/icons/src/js/components/input/input.css
Normal file
@ -0,0 +1,128 @@
|
||||
.input-field-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
|
||||
.input-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input-field-main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-field-element {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
transition: all 0.3s ease;
|
||||
box-sizing: border-box;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
&::placeholder {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field-focused .input-field-element {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.input-field-disabled .input-field-element {
|
||||
background-color: #f5f7fa;
|
||||
color: #c0c4cc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Clear button */
|
||||
.input-field-clear {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 18px;
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
line-height: 1;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease;
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
/* Counter */
|
||||
.input-field-counter {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.input-field-counter-warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.input-field-counter-error {
|
||||
color: #f56c6c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Validation */
|
||||
.input-field-validation {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #f56c6c;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.input-field-invalid .input-field-element {
|
||||
border-color: #f56c6c;
|
||||
&:focus {
|
||||
border-color: #f56c6c;
|
||||
box-shadow: 0 0 0 2px rgba(245, 108, 108, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.input-field-valid .input-field-element {
|
||||
border-color: #67c23a;
|
||||
&:focus {
|
||||
border-color: #67c23a;
|
||||
box-shadow: 0 0 0 2px rgba(103, 194, 58, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Different input types */
|
||||
.input-field-password .input-field-element {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.input-field-search .input-field-element {
|
||||
padding-left: 36px;
|
||||
}
|
||||
|
||||
/* Sizes */
|
||||
.input-field-sm .input-field-element {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.input-field-lg .input-field-element {
|
||||
padding: 14px 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
341
sdkjs-plugins/content/icons/src/js/components/input/input.js
Normal file
341
sdkjs-plugins/content/icons/src/js/components/input/input.js
Normal file
@ -0,0 +1,341 @@
|
||||
class InputField {
|
||||
_container;
|
||||
_options;
|
||||
input;
|
||||
#clearButton;
|
||||
#counter;
|
||||
#counterCurrent;
|
||||
#counterMax;
|
||||
#validationElement;
|
||||
isFocused;
|
||||
isValid;
|
||||
_validationMessage;
|
||||
|
||||
constructor(container, options = {}) {
|
||||
this._container =
|
||||
typeof container === "string"
|
||||
? document.querySelector(container)
|
||||
: container;
|
||||
|
||||
this._options = {
|
||||
type: options.type || "text",
|
||||
placeholder: options.placeholder || "",
|
||||
value: options.value || "",
|
||||
disabled: options.disabled || false,
|
||||
readonly: options.readonly || false,
|
||||
required: options.required || false,
|
||||
maxLength: options.maxLength || null,
|
||||
minLength: options.minLength || null,
|
||||
pattern: options.pattern || null,
|
||||
showCounter: options.showCounter || false,
|
||||
showClear: options.showClear || false,
|
||||
validation: options.validation || null,
|
||||
...options,
|
||||
};
|
||||
|
||||
this.isFocused = false;
|
||||
this.isValid = true;
|
||||
this._validationMessage = "";
|
||||
|
||||
this.#init();
|
||||
}
|
||||
|
||||
#init() {
|
||||
this._createDOM();
|
||||
this.#bindEvents();
|
||||
this.#updateState();
|
||||
}
|
||||
|
||||
_createDOM() {
|
||||
this._container.innerHTML = "";
|
||||
this._container.classList.add("input-field-container");
|
||||
|
||||
this._container.innerHTML = `
|
||||
<div class="input-field ${
|
||||
this._options.disabled ? "input-field-disabled" : ""
|
||||
}">
|
||||
<div class="input-field-main">
|
||||
<input
|
||||
class="input-field-element"
|
||||
type="${this._options.type}"
|
||||
placeholder="${this._options.placeholder}"
|
||||
value="${this._options.value}"
|
||||
${this._options.disabled ? "disabled" : ""}
|
||||
${this._options.readonly ? "readonly" : ""}
|
||||
${this._options.required ? "required" : ""}
|
||||
${
|
||||
this._options.maxLength
|
||||
? `maxlength="${this._options.maxLength}"`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
this._options.pattern
|
||||
? `pattern="${this._options.pattern}"`
|
||||
: ""
|
||||
}
|
||||
>
|
||||
${
|
||||
this._options.showClear
|
||||
? `
|
||||
<button type="button" class="input-field-clear" style="display: none;">
|
||||
×
|
||||
</button>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
${
|
||||
this._options.showCounter
|
||||
? `
|
||||
<div class="input-field-counter">
|
||||
<span class="input-field-counter-current">0</span>
|
||||
/
|
||||
<span class="input-field-counter-max">${
|
||||
this._options.maxLength || "∞"
|
||||
}</span>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div class="input-field-validation" style="display: none;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Links to elements
|
||||
this.input = this._container.querySelector(".input-field-element");
|
||||
this.#clearButton = this._container.querySelector(".input-field-clear");
|
||||
this.#counter = this._container.querySelector(".input-field-counter");
|
||||
this.#counterCurrent = this._container.querySelector(
|
||||
".input-field-counter-current"
|
||||
);
|
||||
this.#counterMax = this._container.querySelector(
|
||||
".input-field-counter-max"
|
||||
);
|
||||
this.#validationElement = this._container.querySelector(
|
||||
".input-field-validation"
|
||||
);
|
||||
}
|
||||
|
||||
#bindEvents() {
|
||||
this.input.addEventListener("focus", () => this.#handleFocus());
|
||||
this.input.addEventListener("blur", () => this.#handleBlur());
|
||||
|
||||
this.input.addEventListener("input", (e) => this.#handleInput(e));
|
||||
|
||||
this.input.addEventListener("keydown", (e) => this.#handleKeydown(e));
|
||||
|
||||
if (this.#clearButton) {
|
||||
this.#clearButton.addEventListener("click", () => this.clear());
|
||||
}
|
||||
|
||||
this.input.addEventListener("change", () => this.validate());
|
||||
}
|
||||
|
||||
#handleFocus() {
|
||||
this.isFocused = true;
|
||||
this._container.classList.add("input-field-focused");
|
||||
this.#updateClearButton();
|
||||
}
|
||||
|
||||
#handleBlur() {
|
||||
this.isFocused = false;
|
||||
this._container.classList.remove("input-field-focused");
|
||||
this.validate();
|
||||
}
|
||||
|
||||
#handleInput(e) {
|
||||
this.#updateClearButton();
|
||||
this.#updateCounter();
|
||||
this.#triggerInputEvent(e);
|
||||
}
|
||||
|
||||
#handleKeydown(e) {
|
||||
if (e.key === "Escape" && this._options.showClear) {
|
||||
this.clear();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (e.key === "Enter") {
|
||||
this.#triggerSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
#updateClearButton() {
|
||||
if (this.#clearButton) {
|
||||
const hasValue = this.input.value.length > 0;
|
||||
this.#clearButton.style.display = hasValue ? "block" : "none";
|
||||
}
|
||||
}
|
||||
|
||||
#updateCounter() {
|
||||
if (this.#counter && this._options.maxLength) {
|
||||
const current = this.input.value.length;
|
||||
const max = this._options.maxLength;
|
||||
|
||||
this.#counterCurrent.textContent = current;
|
||||
this.#counterMax.textContent = max;
|
||||
|
||||
// Change color when near max
|
||||
if (current > max * 0.9) {
|
||||
this.#counter.classList.add("input-field-counter-warning");
|
||||
} else {
|
||||
this.#counter.classList.remove("input-field-counter-warning");
|
||||
}
|
||||
|
||||
if (current > max) {
|
||||
this.#counter.classList.add("input-field-counter-error");
|
||||
} else {
|
||||
this.#counter.classList.remove("input-field-counter-error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validate() {
|
||||
if (!this._options.validation) {
|
||||
this.isValid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
const value = this.input.value;
|
||||
let isValid = true;
|
||||
let message = "";
|
||||
|
||||
// Basic validation HTML
|
||||
if (this._options.required && !value.trim()) {
|
||||
isValid = false;
|
||||
message = "This field is required";
|
||||
} else if (
|
||||
this._options.minLength &&
|
||||
value.length < this._options.minLength
|
||||
) {
|
||||
isValid = false;
|
||||
message = `Minimum length is ${this._options.minLength} characters`;
|
||||
} else if (
|
||||
this._options.maxLength &&
|
||||
value.length > this._options.maxLength
|
||||
) {
|
||||
isValid = false;
|
||||
message = `Maximum length is ${this._options.maxLength} characters`;
|
||||
} else if (
|
||||
this._options.pattern &&
|
||||
!new RegExp(this._options.pattern).test(value)
|
||||
) {
|
||||
isValid = false;
|
||||
message = "Invalid format";
|
||||
}
|
||||
|
||||
// Custom validation
|
||||
if (isValid && typeof this._options.validation === "function") {
|
||||
const customValidation = this._options.validation(value);
|
||||
if (customValidation && !customValidation.isValid) {
|
||||
isValid = false;
|
||||
message = customValidation.message || "Invalid value";
|
||||
}
|
||||
}
|
||||
|
||||
this.isValid = isValid;
|
||||
this._validationMessage = message;
|
||||
this.updateValidationState();
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
updateValidationState() {
|
||||
if (this.#validationElement) {
|
||||
if (!this.isValid) {
|
||||
this.#validationElement.textContent = this._validationMessage;
|
||||
this.#validationElement.style.display = "block";
|
||||
this._container.classList.add("input-field-invalid");
|
||||
this._container.classList.remove("input-field-valid");
|
||||
} else if (this.input.value.length > 0) {
|
||||
this.#validationElement.style.display = "none";
|
||||
this._container.classList.add("input-field-valid");
|
||||
this._container.classList.remove("input-field-invalid");
|
||||
} else {
|
||||
this.#validationElement.style.display = "none";
|
||||
this._container.classList.remove(
|
||||
"input-field-valid",
|
||||
"input-field-invalid"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#updateState() {
|
||||
this.#updateClearButton();
|
||||
this.#updateCounter();
|
||||
this.validate();
|
||||
}
|
||||
|
||||
// Public API
|
||||
getValue() {
|
||||
return this.input.value;
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this.input.value = value;
|
||||
this.#updateState();
|
||||
this.#triggerChange();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.setValue("");
|
||||
this.input.focus();
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.input.focus();
|
||||
}
|
||||
|
||||
blur() {
|
||||
this.input.blur();
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.input.disabled = false;
|
||||
this._options.disabled = false;
|
||||
this._container.classList.remove("input-field-disabled");
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.input.disabled = true;
|
||||
this._options.disabled = true;
|
||||
this._container.classList.add("input-field-disabled");
|
||||
}
|
||||
|
||||
// Events
|
||||
#triggerInputEvent(e) {
|
||||
const event = new CustomEvent("inputfield:input", {
|
||||
detail: {
|
||||
value: this.input.value,
|
||||
originalEvent: e,
|
||||
},
|
||||
});
|
||||
this._container.dispatchEvent(event);
|
||||
}
|
||||
|
||||
#triggerChange() {
|
||||
const event = new CustomEvent("inputfield:change", {
|
||||
detail: {
|
||||
value: this.input.value,
|
||||
isValid: this.isValid,
|
||||
},
|
||||
});
|
||||
this._container.dispatchEvent(event);
|
||||
}
|
||||
|
||||
#triggerSubmit() {
|
||||
const event = new CustomEvent("inputfield:submit", {
|
||||
detail: {
|
||||
value: this.input.value,
|
||||
isValid: this.isValid,
|
||||
},
|
||||
});
|
||||
this._container.dispatchEvent(event);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._container.innerHTML = "";
|
||||
this._container.classList.remove("input-field-container");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
class SearchInput extends InputField {
|
||||
constructor(container, options = {}) {
|
||||
super(container, {
|
||||
type: "search",
|
||||
showClear: true,
|
||||
showSearchIcon: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
_createDOM() {
|
||||
super._createDOM();
|
||||
this._container.classList.add("input-field-search");
|
||||
|
||||
if (this._options.showSearchIcon) {
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "input-field-search-icon";
|
||||
icon.innerHTML = "🔍";
|
||||
icon.style.position = "absolute";
|
||||
icon.style.left = "12px";
|
||||
icon.style.top = "50%";
|
||||
icon.style.transform = "translateY(-50%)";
|
||||
icon.style.color = "#c0c4cc";
|
||||
|
||||
this._container.querySelector(".input-field-main").prepend(icon);
|
||||
this.input.style.paddingLeft = "36px";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user