@progress/kendo-ui
Version:
This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.
1,502 lines (1,494 loc) • 53.9 kB
JavaScript
import { a as htmlService, c as Widget, d as utilsService, l as cssPropertiesService, n as widgetRegistryService, r as fileUtilsService, u as domUtilsService } from "./kendo.core-GmfAc-4k.js";
//#region ../src/promptbox/constants.ts
/**
* PromptBox component constants
*
* This module contains all constants used throughout the PromptBox widget.
*/
/** Namespace for event binding */
const NS = ".kendoPromptBox";
/** Line mode options */
const LINE_MODE = {
SINGLE: "single",
MULTI: "multi",
AUTO: "auto"
};
/** Icon identifiers used in the PromptBox component */
const ICONS = {
send: "paper-plane",
stop: "stop",
attachment: "paperclip"
};
/** CSS class names used in the PromptBox component */
const STYLES = {
promptBox: "k-prompt-box",
promptBoxContent: "k-prompt-box-content",
promptBoxHeader: "k-prompt-box-header",
promptBoxAffix: "k-prompt-box-affix",
promptBoxTextarea: "k-prompt-box-textarea",
promptBoxInput: "k-prompt-box-input",
promptBoxSingleline: "k-prompt-box-singleline",
promptBoxMultiline: "k-prompt-box-multiline",
input: "k-input",
inputInner: "k-input-inner",
disabled: "k-disabled",
hidden: "k-hidden",
focus: "k-focus",
generating: "k-generating",
active: "k-active",
speechToTextButton: "k-speech-to-text-button",
fileSelectButton: "k-file-select-button",
fileBoxWrapper: "k-file-box-wrapper",
fileBoxWrapperScrollableStart: "k-file-box-wrapper-scrollable-start",
filesScroll: "k-files-scroll",
fileBox: "k-file-box",
fileInfo: "k-file-info",
fileName: "k-file-name",
fileSize: "k-file-size",
spacer: "k-spacer"
};
/** Attribute references for element selection (used as [ref-*] selectors) */
const REFERENCES = {
sendButton: "ref-promptbox-send-button",
fileSelectButton: "ref-promptbox-file-select-button",
speechToTextButton: "ref-promptbox-speech-to-text-button",
fileInput: "ref-promptbox-file-input",
attachmentsHost: "ref-promptbox-attachments-host",
attachmentRemoveButton: "ref-promptbox-attachment-remove-button",
input: "ref-promptbox-input",
header: "ref-promptbox-header",
startAffix: "ref-promptbox-start-affix",
endAffix: "ref-promptbox-end-affix",
topAffix: "ref-promptbox-top-affix",
affixSpacer: "ref-promptbox-affix-spacer"
};
/** Event names triggered by the PromptBox component */
const EVENTS = {
valueChange: "valueChange",
input: "input",
promptAction: "promptAction",
fileSelect: "fileSelect",
fileRemove: "fileRemove",
speechToTextClick: "speechToTextClick",
speechToTextStart: "speechToTextStart",
speechToTextEnd: "speechToTextEnd",
speechToTextError: "speechToTextError",
speechToTextResult: "speechToTextResult",
focus: "focus",
blur: "blur",
inputFocus: "inputFocus",
inputBlur: "inputBlur",
multilineStateChange: "multilineStateChange"
};
/** Common DOM event names */
const CLICK = "click";
const INPUT = "input";
const KEYDOWN = "keydown";
const FOCUS = "focus";
const BLUR = "blur";
const CHANGE = "change";
const FOCUSIN = "focusin";
const FOCUSOUT = "focusout";
/** CSS selector prefix */
const DOT = ".";
//#endregion
//#region ../src/promptbox/templates/promptbox.template.ts
const BUILT_IN_ATTR = " data-promptbox-built-in=\"true\"";
/**
* Renders the PromptBox header section.
* Returns empty string when no headerTemplate (header is created dynamically when needed).
*/
const renderHeader = (headerTemplate, forceRender = false) => {
let content = "";
if (headerTemplate) {
content = headerTemplate();
}
if (!content && !forceRender) {
return "";
}
const hiddenClass = content ? "" : ` ${STYLES.hidden}`;
return `<div class="${STYLES.promptBoxHeader}${hiddenClass}" ${REFERENCES.header}><div ${REFERENCES.attachmentsHost}></div>${content}</div>`;
};
/**
* Renders a template function.
*/
const renderTemplate = (template) => {
if (!template) {
return "";
}
return template();
};
/**
* Renders the send button using kendo.html.renderButton.
*/
const renderSendButton = (messages, enable = true, settings) => {
const disabledClass = !enable ? ` ${STYLES.disabled}` : "";
const ariaDisabled = !enable ? " aria-disabled=\"true\"" : "";
const buttonOptions = {
icon: settings?.icon || "arrow-up",
size: settings?.size || "small",
rounded: settings?.rounded || "full"
};
if (settings?.fillMode) {
buttonOptions.fillMode = settings.fillMode;
}
if (settings?.themeColor) {
buttonOptions.themeColor = settings.themeColor;
}
return kendo.html.renderButton(`<button title="${messages.actionButton}" aria-label="${messages.actionButton}" aria-live="polite" class="${disabledClass}"${ariaDisabled} ${REFERENCES.sendButton}${BUILT_IN_ATTR} type="button"></button>`, buttonOptions);
};
/**
* Renders the file select button and hidden file input using kendo.html.renderButton.
*/
const renderFileSelectButton = (fileInputId, accept, multiple, settings, enable = true, messages) => {
const multipleAttr = multiple ? " multiple" : "";
const acceptAttr = accept ? ` accept="${accept}"` : "";
const disabledAttr = !enable ? " disabled" : "";
const ariaDisabledAttr = !enable ? " aria-disabled=\"true\"" : "";
const disabledClass = !enable ? ` ${STYLES.disabled}` : "";
const buttonTitle = settings?.text || messages?.fileSelectButton || "Attach file";
const buttonOptions = {
icon: settings?.icon || ICONS.attachment,
fillMode: settings?.fillMode || "clear",
size: settings?.size || "small",
rounded: settings?.rounded || "full"
};
if (settings?.themeColor) {
buttonOptions.themeColor = settings.themeColor;
}
const button = kendo.html.renderButton(`<button title="${buttonTitle}" aria-label="${buttonTitle}" class="${disabledClass}" ${REFERENCES.fileSelectButton}${BUILT_IN_ATTR} type="button"${disabledAttr}${ariaDisabledAttr}></button>`, buttonOptions);
return `${button}<input type="file" id="${fileInputId}" class="${STYLES.hidden}" ${REFERENCES.fileInput}${multipleAttr}${acceptAttr} />`;
};
/**
* Renders the speech-to-text button placeholder.
* The actual icon is rendered by the SpeechToTextButton component.
*/
const renderSpeechToTextButton = (settings) => {
return `<button class="${STYLES.speechToTextButton}" ${REFERENCES.speechToTextButton}${BUILT_IN_ATTR} type="button"></button>`;
};
const getFileSelectButtonLocation = (fileSelectButtonConfig) => {
const settings = fileSelectButtonConfig?.settings;
return settings?._renderAffixLocation === "start" ? "start" : "end";
};
const renderConfiguredFileSelectButton = (messages, fileSelectButtonConfig) => {
if (!fileSelectButtonConfig) {
return "";
}
const fileSelectButtonEnabled = fileSelectButtonConfig.enable !== false;
return renderFileSelectButton(fileSelectButtonConfig.fileInputId, fileSelectButtonConfig.accept, fileSelectButtonConfig.multiple, fileSelectButtonConfig.settings, fileSelectButtonEnabled, messages);
};
/**
* Renders the start affix container (before the input).
* Uses k-prompt-box-affix class; position is determined by DOM order.
*/
const renderStartAffix = (startAffixTemplate, startContent = "") => {
const content = `${startContent}${renderTemplate(startAffixTemplate)}`.trim();
if (!content) {
return "";
}
return `<div class="${STYLES.promptBoxAffix}" ${REFERENCES.startAffix}>${content}</div>`;
};
/**
* Renders the end affix container with buttons (after the input).
* Returns empty string when no content.
*/
const renderEndAffix = (messages, showSendButton, showSpeechToText = true, endAffixTemplate, fileSelectButtonConfig, buttonSettingsConfig) => {
const endAffix = renderTemplate(endAffixTemplate);
const fileSelectButton = getFileSelectButtonLocation(fileSelectButtonConfig) === "end" ? renderConfiguredFileSelectButton(messages, fileSelectButtonConfig) : "";
const speechButton = showSpeechToText ? renderSpeechToTextButton(buttonSettingsConfig?.speechToTextButtonSettings) : "";
const sendButton = showSendButton ? renderSendButton(messages, true, buttonSettingsConfig?.actionButtonSettings) : "";
const content = `${endAffix}${fileSelectButton}${speechButton}${sendButton}`.trim();
if (!content) {
return "";
}
return `<div class="${STYLES.promptBoxAffix}" ${REFERENCES.endAffix}>
${endAffix}
${fileSelectButton}
${speechButton}
${sendButton}
</div>`;
};
/**
* Renders the end affix container with start affix content (for multi mode).
* In multi mode: fileSelectButton + startAffix content + spacer + endAffix content + buttons
* The spacer is only rendered if there's content on both sides to separate.
*/
const renderEndAffixWithStartAffix = (messages, showSendButton, showSpeechToText = true, startAffixTemplate, endAffixTemplate, fileSelectButtonConfig, buttonSettingsConfig) => {
const startAffix = renderTemplate(startAffixTemplate);
const endAffix = renderTemplate(endAffixTemplate);
const fileSelectButton = renderConfiguredFileSelectButton(messages, fileSelectButtonConfig);
const speechButton = showSpeechToText ? renderSpeechToTextButton(buttonSettingsConfig?.speechToTextButtonSettings) : "";
const sendButton = showSendButton ? renderSendButton(messages, true, buttonSettingsConfig?.actionButtonSettings) : "";
const fileSelectButtonLocation = getFileSelectButtonLocation(fileSelectButtonConfig);
const startFileSelectButton = fileSelectButtonLocation === "start" ? fileSelectButton : "";
const endFileSelectButton = fileSelectButtonLocation === "end" ? fileSelectButton : "";
const startAffixContent = `${startFileSelectButton}${startAffix}`.trim();
const needsSpacer = !!startAffixContent;
return `<div class="${STYLES.promptBoxAffix}" ${REFERENCES.endAffix}>
${startAffixContent}
${needsSpacer ? `<div class="${STYLES.spacer}" ${REFERENCES.affixSpacer}></div>` : ""}
${endAffix}
${endFileSelectButton}
${speechButton}
${sendButton}
</div>`;
};
/**
* Renders the top affix section (only for multi mode).
* Note: This is placed at the beginning of content for multi-mode layout.
*/
const renderTopAffix = (topAffixTemplate) => {
const content = renderTemplate(topAffixTemplate);
if (!content) {
return "";
}
return `<div class="${STYLES.promptBoxAffix}" ${REFERENCES.topAffix}>${content}</div>`;
};
/**
* Renders the complete PromptBox structure.
* For multi mode: startAffix content is rendered inside the end affix container with a spacer.
* For auto mode: startAffix is rendered as a separate container (JS handles reorganization on expand).
*/
const renderPromptBox = (headerTemplate, messages, showSendButton, mode, showSpeechToText = true, renderConfig = {}) => {
const { affixConfig = {}, fileSelectButtonConfig, buttonSettingsConfig } = renderConfig;
const forceRenderHeader = !!fileSelectButtonConfig;
const header = renderHeader(headerTemplate, forceRenderHeader);
const topAffix = mode === LINE_MODE.MULTI ? renderTopAffix(affixConfig.topAffixTemplate) : "";
const isMultiMode = mode === LINE_MODE.MULTI;
const startFileSelectButton = !isMultiMode && getFileSelectButtonLocation(fileSelectButtonConfig) === "start" ? renderConfiguredFileSelectButton(messages, fileSelectButtonConfig) : "";
const startAffix = isMultiMode ? "" : renderStartAffix(affixConfig.startAffixTemplate, startFileSelectButton);
const endAffix = isMultiMode ? renderEndAffixWithStartAffix(messages, showSendButton, showSpeechToText, affixConfig.startAffixTemplate, affixConfig.endAffixTemplate, fileSelectButtonConfig, buttonSettingsConfig) : renderEndAffix(messages, showSendButton, showSpeechToText, affixConfig.endAffixTemplate, fileSelectButtonConfig, buttonSettingsConfig);
return `${header}
<div class="${STYLES.promptBoxContent}">
${topAffix}
${startAffix}
<span ${REFERENCES.input}></span>
${endAffix}
</div>`;
};
//#endregion
//#region ../src/promptbox/collaborators/accessibility-manager.collaborator.ts
var AccessibilityManager = class {
constructor(context) {
this.context = context;
}
applyInitial() {
const options = this.context.getOptions();
const input = this.context.getInputElement();
input.attr("aria-label", options.messages?.messageBoxTitle || "Message");
if (!options.enable) {
this.context.wrapper.attr("aria-disabled", "true");
}
if (options.readonly) {
this.updateInputAccessibility();
}
if (options.loading) {
this.context.wrapper.attr("aria-busy", "true");
}
}
updateInputAccessibility() {
const options = this.context.getOptions();
const input = this.context.getInputElement();
if (!input) {
return;
}
input.attr("aria-label", options.messages?.messageBoxTitle || "Message");
if (options.readonly) {
input.attr("aria-readonly", "true");
} else {
input.removeAttr("aria-readonly");
}
if (!options.enable) {
input.attr("aria-disabled", "true");
} else {
input.removeAttr("aria-disabled");
}
}
updateWrapper(state) {
if (state.disabled !== undefined) {
if (state.disabled) {
this.context.wrapper.attr("aria-disabled", "true");
} else {
this.context.wrapper.removeAttr("aria-disabled");
}
}
if (state.loading !== undefined) {
if (state.loading) {
this.context.wrapper.attr("aria-busy", "true");
} else {
this.context.wrapper.removeAttr("aria-busy");
}
}
}
};
//#endregion
//#region ../src/promptbox/collaborators/action-button-manager.collaborator.ts
var ActionButtonManager = class {
constructor(context) {
this.context = context;
}
getSendButton() {
return this.context.wrapper.find(`[${REFERENCES.sendButton}]`);
}
setLoading(loading) {
const sendButton = this.getSendButton();
const messages = this.context.getMessages();
const settings = this.context.getSettings();
if (loading) {
sendButton.addClass(`${STYLES.generating} ${STYLES.active}`);
sendButton.attr("title", messages.actionButtonLoading);
sendButton.attr("aria-label", messages.actionButtonLoading);
const icon = sendButton.find(".k-icon, .k-svg-icon");
if (icon.length) {
const loadingIcon = settings?.loadingIcon || "stop";
kendo.ui.icon(icon, { icon: loadingIcon });
}
} else {
sendButton.removeClass(`${STYLES.generating} ${STYLES.active}`);
sendButton.attr("title", messages.actionButton);
sendButton.attr("aria-label", messages.actionButton);
const icon = sendButton.find(".k-icon, .k-svg-icon");
if (icon.length) {
const sendIcon = settings?.icon || "arrow-up";
kendo.ui.icon(icon, { icon: sendIcon });
}
}
this.updateState();
}
updateState() {
const sendButton = this.getSendButton();
const isDisabled = this.context.isDisabled();
if (isDisabled) {
sendButton.addClass(STYLES.disabled);
sendButton.attr("aria-disabled", "true");
return;
}
sendButton.removeAttr("aria-disabled");
if (this.context.isLoading()) {
sendButton.removeClass(STYLES.disabled);
return;
}
if (this.context.hasContent()) {
sendButton.removeClass(STYLES.disabled);
} else {
sendButton.addClass(STYLES.disabled);
}
}
updateMessages() {
const sendButton = this.getSendButton();
const messages = this.context.getMessages();
if (this.context.isLoading()) {
sendButton.attr("title", messages.actionButtonLoading);
sendButton.attr("aria-label", messages.actionButtonLoading);
} else {
sendButton.attr("title", messages.actionButton);
sendButton.attr("aria-label", messages.actionButton);
}
}
};
//#endregion
//#region ../src/promptbox/collaborators/speech-handler.collaborator.ts
var SpeechHandler = class {
constructor(context) {
this.instance = null;
this.context = context;
}
init() {
const option = this.context.getSpeechToTextButtonOption();
if (option === false) {
return;
}
const speechToTextButton = this.context.wrapper.find(`[${REFERENCES.speechToTextButton}], ${DOT}${STYLES.speechToTextButton}`).first();
if (!speechToTextButton.length) {
return;
}
const defaultSettings = {
fillMode: "flat",
size: "small",
rounded: "full",
enable: true
};
const userSettings = typeof option === "object" && option !== null ? option : {};
const isEnabled = userSettings.enable !== false;
const buttonOptions = {
...defaultSettings,
...userSettings,
start: (e) => this.context.callbacks.onStart(e),
end: (e) => this.context.callbacks.onEnd(e),
result: (e) => this.context.callbacks.onResult(e),
error: (e) => this.context.callbacks.onError(e)
};
this.instance = new kendo.ui.SpeechToTextButton(speechToTextButton, buttonOptions);
if (!isEnabled) {
this.instance.enable(false);
}
}
enable(enabled) {
if (this.instance) {
this.instance.enable(enabled);
}
}
destroy() {
if (this.instance) {
this.instance.destroy();
this.instance = null;
}
}
};
//#endregion
//#region ../src/promptbox/collaborators/file-handler.collaborator.ts
var FileHandler = class {
constructor(context) {
this.files = [];
this._dragHandler = null;
this.context = context;
}
initUpload() {
const options = this.context.getOptions();
if (!this.showFileSelectButton(options)) {
return;
}
const fileInput = this.context.wrapper.find(`[${REFERENCES.fileInput}]`);
if (fileInput.length === 0) {
return;
}
const settings = this.getFileSelectButtonSettings(options);
const multiple = settings?.multiple !== false;
const upload = new kendo.ui.Upload(fileInput, {
multiple,
async: false,
uniqueFileUids: true,
select: this.onUploadSelect.bind(this)
});
upload?.wrapper?.addClass(STYLES.hidden);
this.uploadInstance = upload;
}
getFiles() {
return [...this.files];
}
setFiles(newFiles) {
this.files = [...newFiles];
this.renderAttachments();
this.context.callbacks.onFilesChanged();
}
clearFiles() {
this.files = [];
this.renderAttachments();
this.context.callbacks.onFilesChanged();
}
hasFiles() {
return this.files.length > 0;
}
handleFileSelectClick() {
const options = this.context.getOptions();
const settings = this.getFileSelectButtonSettings(options);
if (!options.enable || options.readonly || settings?.enable === false) {
return;
}
const uploadInput = this.uploadInstance?.element?.[0] || null;
const fileInput = uploadInput || this.context.wrapper.find(`[${REFERENCES.fileInput}]`)[0];
if (fileInput && typeof fileInput.click === "function") {
fileInput.click();
}
}
removeAttachmentByUid(uid) {
const removedFile = this.files.find((file) => this.getFileUid(file) === uid);
const newFiles = this.files.filter((file) => this.getFileUid(file) !== uid);
if (newFiles.length === this.files.length) {
return;
}
this.files = newFiles;
this.renderAttachments();
this.context.callbacks.onFilesChanged();
if (removedFile) {
this.context.callbacks.onFileRemove(removedFile, [...this.files]);
}
}
renderAttachments() {
this._destroyDragHandler();
const options = this.context.getOptions();
const header = this.context.wrapper.find(`[${REFERENCES.header}]`);
const host = this.context.wrapper.find(`[${REFERENCES.attachmentsHost}]`);
if (host.length === 0) {
return;
}
if (!this.files.length) {
host.empty();
if (!options.headerTemplate) {
header.addClass(STYLES.hidden);
}
return;
}
header.removeClass(STYLES.hidden);
if (options._filesTemplate) {
host.html(options._filesTemplate(this.files));
this._attachDragToScroll();
return;
}
let html = `<ul class="${STYLES.fileBoxWrapper} ${STYLES.fileBoxWrapperScrollableStart}">`;
html += `<div class="${STYLES.filesScroll}">`;
for (let i = 0; i < this.files.length; i++) {
const fileObj = this.files[i];
const rawFile = fileObj?.rawFile || fileObj;
const uid = this.getFileUid(fileObj);
const name = rawFile?.name || fileObj?.name || "";
const size = rawFile?.size || fileObj?.size || 0;
const extension = name.lastIndexOf(".") > -1 ? name.substring(name.lastIndexOf(".")).toLowerCase() : "";
const iconName = fileUtilsService.getFileGroup(extension, true);
const fileSizeMessage = fileUtilsService.getFileSizeMessage(size);
const icon = kendo.ui.icon({
icon: iconName,
size: "xlarge"
});
const removeButton = kendo.html.renderButton(`<button type="button" ${REFERENCES.attachmentRemoveButton} data-uid="${htmlService.encode(uid)}" aria-label="Remove attachment" title="Remove attachment"></button>`, {
icon: "x-circle",
fillMode: "flat"
});
html += `<li class="${STYLES.fileBox}">`;
html += icon;
html += `<div class="${STYLES.fileInfo}">`;
html += `<span class="${STYLES.fileName}">${htmlService.encode(name)}</span>`;
html += `<span class="${STYLES.fileSize}">${htmlService.encode(fileSizeMessage)}</span>`;
html += `</div>`;
html += removeButton;
html += `</li>`;
}
html += "</div></ul>";
host.html(html);
this._attachDragToScroll();
}
_attachDragToScroll() {
const host = this.context.wrapper.find(`[${REFERENCES.attachmentsHost}]`);
const scrollContainer = host.find("." + STYLES.filesScroll);
if (!scrollContainer.length) {
return;
}
this._dragHandler = domUtilsService.createDragToScrollHandler(scrollContainer, {
namespace: NS + ".promptBoxFileDrag",
captureElement: this.context.wrapper
});
this._dragHandler.attach();
}
_destroyDragHandler() {
if (this._dragHandler) {
this._dragHandler.destroy();
this._dragHandler = null;
}
}
destroy() {
this._destroyDragHandler();
if (this.uploadInstance) {
this.uploadInstance.destroy();
this.uploadInstance = null;
}
}
onUploadSelect(e) {
e.preventDefault();
const files = e.files;
const options = this.context.getOptions();
const settings = this.getFileSelectButtonSettings(options);
const validFiles = this.validateFiles(files, settings);
if (validFiles.length > 0) {
this.files = [...this.files, ...validFiles];
this.renderAttachments();
this.context.callbacks.onFilesChanged();
this.context.callbacks.onFileSelect(validFiles);
}
}
validateFiles(files, settings) {
const validFiles = [];
const restrictions = settings?.restrictions;
for (let i = 0; i < files.length; i++) {
const fileObj = files[i];
const file = fileObj.rawFile || fileObj;
let isValid = true;
if (restrictions) {
if (restrictions.allowedExtensions && restrictions.allowedExtensions.length > 0) {
const ext = file.name.split(".").pop()?.toLowerCase() || "";
const allowedExts = restrictions.allowedExtensions.map((e) => e.toLowerCase().replace(".", ""));
if (!allowedExts.includes(ext)) {
isValid = false;
}
}
if (restrictions.maxFileSize && file.size > restrictions.maxFileSize) {
isValid = false;
}
if (restrictions.minFileSize && file.size < restrictions.minFileSize) {
isValid = false;
}
}
if (isValid) {
validFiles.push(fileObj);
}
}
return validFiles;
}
getFileUid(file) {
if (!file) {
return "";
}
if (!file.uid) {
file.uid = utilsService.guid();
}
return file.uid;
}
showFileSelectButton(options) {
return options.fileSelectButton === true || typeof options.fileSelectButton === "object" && options.fileSelectButton !== null;
}
getFileSelectButtonSettings(options) {
if (typeof options.fileSelectButton === "object" && options.fileSelectButton !== null) {
return options.fileSelectButton;
}
return undefined;
}
};
//#endregion
//#region ../src/promptbox/collaborators/input-manager.collaborator.ts
const DEFAULT_MESSAGES$1 = {
placeholder: "Type a message...",
actionButton: "Send",
actionButtonLoading: "Stop",
messageBoxTitle: "Message"
};
var InputManager = class {
constructor(context) {
this.inputInstance = null;
this.singleRowHeight = 0;
this.singleLineWidth = 0;
this.initialHeight = 0;
this.minMultiRowHeight = 0;
this.multiline = false;
this.context = context;
}
init() {
const inputContainer = this.context.wrapper.find(`[${REFERENCES.input}]`);
const mode = this.context.getMode();
const inputElement = this.createInputElement(mode);
inputContainer.replaceWith(inputElement);
const value = this.context.getOptions().value || "";
if (value) {
inputElement.val(value);
}
this.captureInitialDimensions();
this.attachInputEvents();
}
getElement() {
return this.inputInstance?.element || null;
}
getValue() {
return this.inputInstance ? this.inputInstance.value() : "";
}
setValue(value) {
if (this.inputInstance) {
this.inputInstance.value(value);
}
this.updateAutoResize();
this.updateMultiResize();
}
enable(enabled) {
if (this.inputInstance) {
this.inputInstance.enable(enabled);
}
}
setReadonly(readonly) {
if (this.inputInstance?.element) {
this.inputInstance.element.prop("readonly", readonly);
}
}
focus() {
if (this.inputInstance) {
this.inputInstance.focus();
}
}
blur() {
if (this.inputInstance?.element) {
this.inputInstance.element.blur();
}
}
isMultiline() {
return this.multiline;
}
updateAutoResize() {
const mode = this.context.getMode();
if (mode !== LINE_MODE.AUTO || !this.inputInstance) {
return;
}
const textarea = this.inputInstance.element;
if (!textarea.is("textarea")) {
return;
}
const el = textarea[0];
if (el.offsetHeight > 0 && this.initialHeight < el.offsetHeight) {
this.captureInitialDimensions();
}
if (!this.multiline) {
const needsExpansion = el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight;
if (needsExpansion) {
this.updateMultilineState(true);
this.updateHeight();
}
} else {
this.updateHeight();
if (this.shouldCollapse()) {
this.updateMultilineState(false);
textarea.css("height", "");
}
}
}
updateMultiResize() {
const mode = this.context.getMode();
if (mode !== LINE_MODE.MULTI || !this.inputInstance) {
return;
}
const textarea = this.inputInstance.element;
if (!textarea.is("textarea")) {
return;
}
const el = textarea[0];
const options = this.context.getOptions();
if (el.offsetHeight > 0 && this.initialHeight < el.offsetHeight) {
this.captureInitialDimensions();
}
textarea.css("overflow", "hidden");
textarea.css("height", this.initialHeight + "px");
const scrollHeight = el.scrollHeight;
const newHeight = this.calculateMultiHeight(scrollHeight, options.maxTextAreaHeight);
if (newHeight === options.maxTextAreaHeight) {
textarea.css("overflow", "");
}
textarea.css("height", newHeight + "px");
}
updatePlaceholder() {
const options = this.context.getOptions();
const messages = $.extend({}, DEFAULT_MESSAGES$1, options.messages);
const placeholder = options.placeholder || messages.placeholder;
if (this.inputInstance?.element) {
this.inputInstance.element.attr("placeholder", placeholder);
}
}
destroy() {
if (this.inputInstance) {
this.inputInstance.element.off(NS);
this.inputInstance.destroy();
this.inputInstance = null;
}
}
createInputElement(mode) {
const options = this.context.getOptions();
const messages = $.extend({}, DEFAULT_MESSAGES$1, options.messages);
const title = options.title ? `title="${options.title}"` : "";
const readonly = options.readonly ? "readonly" : "";
let inputElement;
if (mode === LINE_MODE.SINGLE) {
inputElement = $(`<input type="text" class="${STYLES.promptBoxInput} ${STYLES.inputInner}" placeholder="${options.placeholder || messages.placeholder}" autocomplete="off" ${title} ${readonly} />`);
this.inputInstance = {
element: inputElement,
value: (val) => {
if (val === undefined) {
return inputElement.val();
}
inputElement.val(val);
},
enable: (enabled) => {
inputElement.prop("disabled", !enabled);
},
focus: () => inputElement.trigger("focus"),
destroy: () => {}
};
} else {
const rows = mode === LINE_MODE.AUTO ? 1 : options.rows || 1;
inputElement = $(`<textarea class="${STYLES.promptBoxTextarea} ${STYLES.inputInner}" placeholder="${options.placeholder || messages.placeholder}" rows="${rows}" aria-multiline="true" ${title} ${readonly}></textarea>`);
if (options.maxTextAreaHeight) {
inputElement.css("max-height", options.maxTextAreaHeight + "px");
inputElement.css("overflow-y", "auto");
}
this.inputInstance = {
element: inputElement,
value: (val) => {
if (val === undefined) {
return inputElement.val();
}
inputElement.val(val);
},
enable: (enabled) => {
inputElement.prop("disabled", !enabled);
},
focus: () => inputElement.trigger("focus"),
destroy: () => {}
};
}
return inputElement;
}
captureInitialDimensions() {
const mode = this.context.getMode();
if (mode === LINE_MODE.SINGLE || !this.inputInstance) {
return;
}
const textarea = this.inputInstance.element;
if (!textarea.is("textarea")) {
return;
}
const el = textarea[0];
const computedStyle = window.getComputedStyle(el);
const lineHeight = parseFloat(computedStyle.lineHeight) || 20;
const paddingTop = parseFloat(computedStyle.paddingTop) || 0;
const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0;
const options = this.context.getOptions();
const rows = options.rows || 1;
this.singleRowHeight = lineHeight + paddingTop + paddingBottom;
this.initialHeight = el.offsetHeight || this.singleRowHeight;
this.singleLineWidth = el.offsetWidth;
this.minMultiRowHeight = lineHeight * rows + paddingTop + paddingBottom;
}
shouldCollapse() {
if (!this.inputInstance || this.singleLineWidth <= 0) {
return false;
}
const textarea = this.inputInstance.element;
if (!textarea.is("textarea")) {
return false;
}
const el = textarea[0];
const currentHeight = el.style.height;
textarea.css({
overflow: "hidden",
width: this.singleLineWidth + "px",
whiteSpace: "nowrap",
height: this.initialHeight + "px"
});
const fitsSingleLine = el.scrollWidth <= this.singleLineWidth && el.scrollHeight <= this.initialHeight;
textarea.css({
overflow: "",
width: "",
whiteSpace: ""
});
if (currentHeight) {
textarea.css("height", currentHeight);
} else {
textarea.css("height", "");
}
return fitsSingleLine;
}
updateHeight() {
const options = this.context.getOptions();
if (!this.inputInstance) {
return;
}
const textarea = this.inputInstance.element;
if (!textarea.is("textarea")) {
return;
}
const el = textarea[0];
textarea.css("overflow", "hidden");
textarea.css("height", this.initialHeight + "px");
const scrollHeight = el.scrollHeight;
const newHeight = options.maxTextAreaHeight ? Math.min(scrollHeight, options.maxTextAreaHeight) : scrollHeight;
if (newHeight === options.maxTextAreaHeight) {
textarea.css("overflow", "");
}
textarea.css("height", newHeight + "px");
}
calculateMultiHeight(scrollHeight, maxTextAreaHeight) {
if (maxTextAreaHeight && scrollHeight > maxTextAreaHeight) {
return maxTextAreaHeight;
}
return Math.max(this.minMultiRowHeight, scrollHeight);
}
updateMultilineState(isMultiline) {
const mode = this.context.getMode();
if (mode !== LINE_MODE.AUTO) {
return;
}
if (this.multiline === isMultiline) {
return;
}
const wasMultiline = this.multiline;
this.multiline = isMultiline;
if (isMultiline) {
this.context.wrapper.addClass(STYLES.promptBoxMultiline);
} else {
this.context.wrapper.removeClass(STYLES.promptBoxMultiline);
}
this.context.callbacks.onMultilineStateChange(isMultiline, wasMultiline);
}
attachInputEvents() {
if (!this.inputInstance) {
return;
}
const inputElement = this.inputInstance.element;
inputElement.on(INPUT + NS, () => {
this.updateAutoResize();
this.updateMultiResize();
this.context.callbacks.onInput();
});
inputElement.on(KEYDOWN + NS, (e) => {
this.context.callbacks.onKeyDown(e);
});
inputElement.on(FOCUS + NS, () => {
this.context.callbacks.onFocus();
});
inputElement.on(BLUR + NS, () => {
this.context.callbacks.onBlur();
});
}
};
//#endregion
//#region ../src/promptbox/promptbox.widget.ts
const BUILT_IN_SELECTOR = "[data-promptbox-built-in=\"true\"]";
const BUILT_IN_FILE_SPACER_SELECTOR = ".k-spacer[data-promptbox-role=\"file-spacer\"]";
const DEFAULT_MESSAGES = {
placeholder: "Type a message...",
actionButton: "Send",
actionButtonLoading: "Stop",
messageBoxTitle: "Message",
fileSelectButton: "Attach file"
};
/**
* PromptBox widget - A composite input component for chat-like interfaces.
*
* Supports three modes:
* - "single": Single-line text input
* - "multi": Multi-line textarea with fixed rows
* - "auto": Auto-expanding textarea (starts from 1 row)
*/
var PromptBox = class PromptBox extends Widget {
static {
this.options = {
name: "PromptBox",
prefix: "",
mode: LINE_MODE.AUTO,
placeholder: "",
value: "",
enable: true,
readonly: false,
title: "",
fillMode: undefined,
maxTextAreaHeight: undefined,
rows: 1,
headerTemplate: null,
loading: false,
actionButton: null,
fileSelectButton: false,
speechToTextButton: true,
startAffixTemplate: null,
endAffixTemplate: null,
topAffixTemplate: null,
messages: DEFAULT_MESSAGES
};
}
/**
* Constructs a new PromptBox instance.
*/
constructor(element, options) {
super(element, $.extend(true, {}, PromptBox.options, options));
this.events = [
EVENTS.valueChange,
EVENTS.input,
EVENTS.promptAction,
EVENTS.fileSelect,
EVENTS.fileRemove,
EVENTS.speechToTextClick,
EVENTS.speechToTextStart,
EVENTS.speechToTextEnd,
EVENTS.speechToTextError,
EVENTS.speechToTextResult,
EVENTS.focus,
EVENTS.blur,
EVENTS.inputFocus,
EVENTS.inputBlur,
EVENTS.multilineStateChange
];
this._value = "";
this._loading = false;
this._readonly = false;
this._hasFocus = false;
this._fileInputId = "";
this._value = this.options.value || "";
this._loading = this.options.loading || false;
this._fileInputId = "promptbox_file_" + utilsService.guid();
this._initWrapper();
this._initInputManager();
this._initSpeechHandler();
this._initFileHandler();
this._attachEvents();
this.fileHandler.renderAttachments();
this._initAccessibilityManager();
this._initActionButtonManager();
this.bind(this.events, this.options);
if (!this.options.enable) {
this.enable(false);
}
if (this.options.readonly) {
this.readonly(true);
}
if (this._value && this._getMode() === LINE_MODE.AUTO) {
this.inputManager.updateAutoResize();
}
this.actionButtonManager.updateState();
}
/**
* Gets or sets the input value.
*/
value(newValue) {
if (newValue === undefined) {
return this._value;
}
this._value = newValue;
this.inputManager.setValue(newValue);
this.actionButtonManager.updateState();
}
/**
* Enables or disables the PromptBox.
* When enabling, this also clears the readonly state (similar to TextBox behavior).
*/
enable(enabled = true) {
const options = this.options;
options.enable = enabled;
if (enabled) {
this._readonly = false;
options.readonly = false;
this.inputManager.setReadonly(false);
this.wrapper.removeClass(STYLES.disabled);
} else {
this.wrapper.addClass(STYLES.disabled);
}
this.inputManager.enable(enabled);
this.speechHandler.enable(enabled);
this._updateFileSelectButtonState();
this.actionButtonManager.updateState();
this.accessibilityManager.updateWrapper({ disabled: !enabled });
this.accessibilityManager.updateInputAccessibility();
}
/**
* Gets or sets the read-only state of the PromptBox.
*/
readonly(value) {
if (value === undefined) {
return this._readonly;
}
this._readonly = value;
const options = this.options;
options.readonly = value;
this.inputManager.setReadonly(value);
this.accessibilityManager.updateInputAccessibility();
this.actionButtonManager.updateState();
this.speechHandler.enable(!value);
this._updateFileSelectButtonState();
}
/**
* Gets or sets the loading state (shows stop button instead of send).
*/
loading(value) {
if (value === undefined) {
return this._loading;
}
this._loading = value;
this.accessibilityManager.updateWrapper({ loading: value });
this.actionButtonManager.setLoading(value);
}
/**
* Focuses the input element.
*/
focus() {
this.inputManager.focus();
}
/**
* Blurs the input element.
*/
blur() {
this.inputManager.blur();
}
/**
* Gets or sets the attached files.
* @param newFiles - Optional array of files to set. If not provided, returns current files.
* @returns Array of attached files when getting, void when setting.
*/
files(newFiles) {
if (newFiles === undefined) {
return this.fileHandler.getFiles();
}
this.fileHandler.setFiles(newFiles);
}
/**
* Clears all attached files.
*/
clearFiles() {
this.fileHandler.clearFiles();
}
/**
* Destroys the widget.
*/
destroy() {
this._detachEvents();
this._destroyCollaborators();
super.destroy();
}
_destroyCollaborators() {
this.fileHandler.destroy();
this.speechHandler.destroy();
this.inputManager.destroy();
}
/**
* Updates widget options dynamically.
* For complex changes (templates, buttons, mode), the widget is rebuilt.
*/
setOptions(options) {
const requiresRebuild = options.headerTemplate !== undefined || options.startAffixTemplate !== undefined || options.endAffixTemplate !== undefined || options.topAffixTemplate !== undefined || options.actionButton !== undefined || options.actionButtonSettings !== undefined || options.fileSelectButton !== undefined || options.speechToTextButton !== undefined || options.mode !== undefined;
if (requiresRebuild) {
const currentValue = this._value;
const wasLoading = this._loading;
const attachments = this.options.attachments;
const currentFiles = this.fileHandler.getFiles();
this._detachEvents();
this._destroyCollaborators();
super.setOptions(options);
this.wrapper.remove();
this._initWrapper();
this._initInputManager();
this._initSpeechHandler();
this._initFileHandler();
this._attachEvents();
if (currentFiles.length > 0) {
this.fileHandler.setFiles(currentFiles);
}
this._initAccessibilityManager();
this._initActionButtonManager();
if (currentValue) {
this.value(currentValue);
}
if (wasLoading) {
this.loading(true);
}
if (attachments) {
this.options.attachments = attachments;
this.fileHandler.renderAttachments();
}
return;
}
super.setOptions(options);
if (options.placeholder !== undefined || options.messages?.placeholder !== undefined) {
this.inputManager.updatePlaceholder();
}
if (options.messages?.actionButton !== undefined || options.messages?.actionButtonLoading !== undefined) {
this.actionButtonManager.updateMessages();
}
if (options.enable !== undefined) {
this.enable(options.enable);
}
if (options.readonly !== undefined) {
this.readonly(options.readonly);
}
if (options.loading !== undefined) {
this.loading(options.loading);
}
}
_initInputManager() {
this.inputManager = new InputManager({
wrapper: this.wrapper,
getOptions: () => this.options,
getMode: () => this._getMode(),
callbacks: {
onInput: () => this._handleInput(),
onKeyDown: (e) => this._handleKeyDown(e),
onFocus: () => this.trigger(EVENTS.inputFocus),
onBlur: () => this.trigger(EVENTS.inputBlur),
onMultilineStateChange: (isMultiline, wasMultiline) => {
this._handleAffixReorganization(isMultiline);
this.trigger(EVENTS.multilineStateChange, {
isMultiline,
wasMultiline
});
}
}
});
this.inputManager.init();
}
/**
* Reorganizes affixes when auto mode switches between single-line and multiline.
* In multiline: startAffix content moves to endAffix (left side) with a spacer.
* In single-line: startAffix content returns to its original container.
*/
_handleAffixReorganization(isMultiline) {
const startAffix = this.wrapper.find(`[${REFERENCES.startAffix}]`).first();
const endAffix = this.wrapper.find(`[${REFERENCES.endAffix}]`).first();
if (!startAffix.length || !endAffix.length) {
return;
}
if (isMultiline) {
if (startAffix.hasClass(STYLES.hidden)) {
return;
}
const startAffixContent = startAffix.contents().detach();
if (!startAffixContent.length) {
return;
}
startAffix.addClass(STYLES.hidden);
endAffix.prepend($(`<div class="${STYLES.spacer}" ${REFERENCES.affixSpacer}></div>`));
endAffix.prepend(startAffixContent);
} else {
const spacer = endAffix.children(`[${REFERENCES.affixSpacer}]`).first();
if (!spacer.length) {
return;
}
const startAffixContent = spacer.prevAll().detach();
if (startAffixContent.length) {
startAffix.empty().append(startAffixContent);
}
startAffix.removeClass(STYLES.hidden);
spacer.remove();
}
this._syncFileSelectSpacer();
}
_initSpeechHandler() {
this.speechHandler = new SpeechHandler({
wrapper: this.wrapper,
getSpeechToTextButtonOption: () => this.options.speechToTextButton,
callbacks: {
onStart: (e) => this.trigger(EVENTS.speechToTextStart, e),
onEnd: (e) => this.trigger(EVENTS.speechToTextEnd, e),
onResult: (e) => {
const transcript = e.alternatives?.[0]?.transcript || "";
if (transcript) {
const currentValue = this._value || "";
const newValue = currentValue + (currentValue ? " " : "") + transcript;
this.value(newValue);
}
this.trigger(EVENTS.speechToTextResult, {
...e,
transcript
});
},
onError: (e) => this.trigger(EVENTS.speechToTextError, e)
}
});
this.speechHandler.init();
}
_initFileHandler() {
this.fileHandler = new FileHandler({
wrapper: this.wrapper,
getOptions: () => this.options,
callbacks: {
onFilesChanged: () => this.actionButtonManager.updateState(),
onFileSelect: (files) => this.trigger(EVENTS.fileSelect, { files }),
onFileRemove: (file, remainingFiles) => this.trigger(EVENTS.fileRemove, {
file,
files: remainingFiles
})
}
});
this.fileHandler.initUpload();
}
_initAccessibilityManager() {
this.accessibilityManager = new AccessibilityManager({
wrapper: this.wrapper,
getOptions: () => this.options,
getInputElement: () => this.inputManager.getElement()
});
this.accessibilityManager.applyInitial();
}
_initActionButtonManager() {
this.actionButtonManager = new ActionButtonManager({
wrapper: this.wrapper,
getMessages: () => this._getMessages(),
getSettings: () => this.options.actionButton,
isDisabled: () => {
const options = this.options;
const widgetDisabled = !options.enable;
const widgetReadonly = options.readonly;
const actionButtonDisabled = options.actionButton?.enable === false;
return widgetDisabled || widgetReadonly || actionButtonDisabled;
},
isLoading: () => this._loading,
hasContent: () => !!this._value?.trim() || this.fileHandler.hasFiles()
});
}
/**
* Gets the input mode (single, multi, or auto).
*/
_getMode() {
const options = this.options;
return options.mode || LINE_MODE.AUTO;
}
/**
* Gets the merged messages, including overrides from button settings.
*/
_getMessages() {
const options = this.options;
const actionButtonSettings = options.actionButton;
const fileSelectButtonSettings = typeof options.fileSelectButton === "object" ? options.fileSelectButton : null;
const baseMessages = $.extend({}, DEFAULT_MESSAGES, options.messages);
if (actionButtonSettings?.text) {
baseMessages.actionButton = actionButtonSettings.text;
}
if (actionButtonSettings?.loadingText) {
baseMessages.actionButtonLoading = actionButtonSettings.loadingText;
}
if (fileSelectButtonSettings?.text) {
baseMessages.fileSelectButton = fileSelectButtonSettings.text;
}
return baseMessages;
}
/**
* Gets whether the action button should be shown. Always returns true.
*/
_showActionButton() {
return true;
}
/**
* Gets whether the speech-to-text button should be shown.
*/
_showSpeechToTextButton() {
const options = this.options;
return options.speechToTextButton === true || options.speechToTextButton === null || options.speechToTextButton === undefined || typeof options.speechToTextButton === "object" && options.speechToTextButton !== null;
}
/**
* Gets whether the file select button should be shown.
*/
_showFileSelectButton() {
const options = this.options;
return options.fileSelectButton === true || typeof options.fileSelectButton === "object" && options.fileSelectButton !== null;
}
/**
* Updates the file select button state based on enable/readonly options.
*/
_updateFileSelectButtonState() {
const options = this.options;
const fileSelectButton = this.wrapper.find(`[${REFERENCES.fileSelectButton}]`);
if (!fileSelectButton.length) {
return;
}
const fileSelectSettings = this._getFileSelectButtonSettings();
const isDisabled = !options.enable || options.readonly || fileSelectSettings?.enable === false;
if (isDisabled) {
fileSelectButton.addClass(STYLES.disabled);
fileSelectButton.attr("aria-disabled", "true");
} else {
fileSelectButton.removeClass(STYLES.disabled);
fileSelectButton.removeAttr("aria-disabled");
}
}
/**
* Gets the file select button settings.
*/
_getFileSelectButtonSettings() {
const options = this.options;
if (typeof options.fileSelectButton === "object" && options.fileSelectButton !== null) {
return options.fileSelectButton;
}
return undefined;
}
/**
* Creates the wrapper element using wrap instead of replaceWith.
* This keeps the original element in the DOM so jQuery data binding works.
*/
_initWrapper() {
const options = this.options;
const messages = this._getMessages();
const mode = this._getMode();
let modeClass = "";
if (mode === LINE_MODE.SINGLE) {
modeClass = ` ${STYLES.promptBoxSingleline}`;
} else if (mode === LINE_MODE.MULTI) {
modeClass = ` ${STYLES.promptBoxMultiline}`;
}
const stylingClasses = this._getAppearanceClasses();
const affixConfig = {
startAffixTemplate: options.startAffixTemplate,
endAffixTemplate: options.endAffixTemplate,
topAffixTemplate: options.topAffixTemplate
};
const fileSelectButtonConfig = this._getFileSelectButtonConfig();
const buttonSettingsConfig = this._getButtonSettingsConfig();
const renderConfig = {
affixConfig,
fileSelectButtonConfig,
buttonSettingsConfig
};
const wrapperContent = renderPromptBox(options.headerTemplate, messages, this._showActionButton(), mode, this._showSpeechToTextButton(), renderConfig);
this.wrapper = this.element.wrap(`<div class="${STYLES.input}${stylingClasses} ${STYLES.promptBox}${modeClass}" role="group"></div>`).parent();
this.wrapper.prepend(wrapperContent);
this._adoptCustomAffixRefs();
this._syncFileSelectSpacer();
this.element.addClass(STYLES.hidden);
this.wrapper.after(this.element);
}
_adoptCustomAffixRefs() {
this._removeBuiltInDuplicate(this.wrapper.find(`[${REFERENCES.startAffix}]`).first(), REFERENCES.fileSelectButton);
this._removeBuiltInDuplicate(this.wrapper.find(`[${REFERENCES.startAffix}]`).first(), REFERENCES.sendButton);
this._removeBuiltInDuplicate(this.wrapper.find(`[${REFERENCES.startAffix}]`).first(), REFERENCES.speechToTextButton);
this._removeBuiltInDuplicate(this.wrapper.find(`[${REFERENCES.endAffix}]`).first(), REFERENCES.fileSelectButton);
this._removeBuiltInDuplicate(this.wrapper.find(`[${REFERENCES.endAffix}]`).first(), REFERENCES.sendButton);
this._removeBuiltInDuplicate(this.wrapper.find(`[${REFERENCES.endAffix}]`).first(), REFERENCES.speechToTextButton);
}
_removeBuiltInDuplicate(container, reference) {
if (!container?.length) {
return;
}
const matches = container.find(`[${reference}]`);
const customMatches = matches.not(BUILT_IN_SELECTOR);
if (!customMatches.length) {
return;
}
matches.filter(BUILT_IN_SELECTOR).remove();
}
_syncFileSelectSpacer() {
const settings = this._getFileSelectButtonSettings();
const endAffix = this.wrapper.find(`[${REFERENCES.endAffix}]`).first();
if (!endAffix.length) {
return;
}
endAffix.children(BUILT_IN_FILE_SPACER_SELECTOR).remove();
if (!settings?.showSpacer || settings._renderAffixLocation === "start") {
return;
}
const fileButtons = endAffix.find(`[${REFERENCES.fileSelectButton}]`);
const preferredFileButton = fileButtons.not(BUILT_IN_SELECTOR).first();
const fileButton = preferredFileButton.length ? preferredFileButton : fileButtons.first();
const hasTrailingControls = endAffix.find(`[${REFERENCES.speechToTextButton}], [${REFERENCES.sendButton}]`).length > 0;
if (!fileButton.length || !fileButton.parent().is(endAffix) || !hasTrailingControls) {
return;
}
fileButton.after(`<div class="${STYLES.spacer}" data-promptbox-role="file-spacer"></div>`);
}
/**
* Gets appearance CSS classes for fillMode.
* PromptBox only supports fillMode at the root level per kendo-themes spec.
*/
_getAppearanceClasses() {
const options = this.options;
if (options.fillMode) {
return ` k-input-${options.fillMode}`;
}
return "";
}
/**
* Gets the file select button configuration for template rendering.
*/
_getFileSelectButtonConfig() {
if (!this._showFileSelectButton()) {
return undefined;
}
const settings = this._getFileSelectButtonSettings();
let accept = settings?.accept;
if (!accept && settings?.restrictions?.allowedExtensions?.length) {
accept = settings.restrictions.allowedExtensions.map((ext) => ext.startsWith(".") ? ext : `.${ext}`).join(",");
}
return {
enable: settings?.enable,
fileInputId: this._fileInputId,
accept,
multiple: settings?.multiple,
showSpacer: settings?.showSpacer,
settings
};
}
/**
* Gets the button settings configuration for template rendering.
*/
_getButtonSettingsConfig() {
const options = this.options;
const speechOption = options.speechToTextButton;
const speechSettings = typeof speechOption === "object" && speechOption !== null ? speechOption : undefined;
return {
actionButtonSettings: options.actionButton || undefined,
fileSelectButtonSettings: this._getFile