UNPKG

@progress/kendo-ui

Version:

This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.

1,630 lines 51.7 kB
const require_kendo_core = require('./kendo.core-BrJwlWmG.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-sm",
	attachment: "paperclip-outline-alt-right"
};
/** 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",
	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
/**
* 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-outline",
		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} 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"
	};
	if (settings?.size) {
		buttonOptions.size = settings.size;
	}
	if (settings?.rounded) {
		buttonOptions.rounded = settings.rounded;
	}
	if (settings?.themeColor) {
		buttonOptions.themeColor = settings.themeColor;
	}
	const button = kendo.html.renderButton(`<button title="${buttonTitle}" aria-label="${buttonTitle}" class="${disabledClass}" ${REFERENCES.fileSelectButton} 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}" type="button"></button>`;
};
/**
* Renders the start affix container (before the input).
* Uses k-prompt-box-affix class; position is determined by DOM order.
*/
const renderStartAffix = (startAffixTemplate) => {
	const content = renderTemplate(startAffixTemplate);
	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 fileSelectButtonEnabled = fileSelectButtonConfig?.enable !== false;
	const fileSelectButton = fileSelectButtonConfig ? renderFileSelectButton(fileSelectButtonConfig.fileInputId, fileSelectButtonConfig.accept, fileSelectButtonConfig.multiple, fileSelectButtonConfig.settings, fileSelectButtonEnabled, messages) : "";
	const spacer = fileSelectButtonConfig?.showSpacer ? `<div class="${STYLES.spacer}"></div>` : "";
	const speechButton = showSpeechToText ? renderSpeechToTextButton(buttonSettingsConfig?.speechToTextButtonSettings) : "";
	const sendButton = showSendButton ? renderSendButton(messages, true, buttonSettingsConfig?.actionButtonSettings) : "";
	const content = `${endAffix}${fileSelectButton}${spacer}${speechButton}${sendButton}`.trim();
	if (!content) {
		return "";
	}
	return `<div class="${STYLES.promptBoxAffix}" ${REFERENCES.endAffix}>
        ${endAffix}
        ${fileSelectButton}
        ${spacer}
        ${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 fileSelectButtonEnabled = fileSelectButtonConfig?.enable !== false;
	const fileSelectButton = fileSelectButtonConfig ? renderFileSelectButton(fileSelectButtonConfig.fileInputId, fileSelectButtonConfig.accept, fileSelectButtonConfig.multiple, fileSelectButtonConfig.settings, fileSelectButtonEnabled, messages) : "";
	const speechButton = showSpeechToText ? renderSpeechToTextButton(buttonSettingsConfig?.speechToTextButtonSettings) : "";
	const sendButton = showSendButton ? renderSendButton(messages, true, buttonSettingsConfig?.actionButtonSettings) : "";
	const spacer = `<div class="${STYLES.spacer}"></div>`;
	if (!fileSelectButtonConfig?.showSpacer) {
		return `<div class="${STYLES.promptBoxAffix}" ${REFERENCES.endAffix}>
            ${startAffix}
            ${spacer}
            ${endAffix}
            ${fileSelectButton}
            ${speechButton}
            ${sendButton}
        </div>`;
	}
	return `<div class="${STYLES.promptBoxAffix}" ${REFERENCES.endAffix}>
        ${fileSelectButton}
        ${startAffix}
        ${spacer}
        ${endAffix}
        ${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 startAffix = isMultiMode ? "" : renderStartAffix(affixConfig.startAffixTemplate);
	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-sm";
				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-outline";
				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(DOT + STYLES.speechToTextButton);
		if (!speechToTextButton.length) {
			return;
		}
		const defaultSettings = {
			fillMode: "flat",
			size: "small",
			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 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 (!this.context.getOptions().headerTemplate) {
				header.addClass(STYLES.hidden);
			}
			return;
		}
		header.removeClass(STYLES.hidden);
		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 = this.context.fileUtilsService.getFileGroup(extension, true);
			const fileSizeMessage = this.context.fileUtilsService.getFileSizeMessage(size);
			const icon = kendo.ui.icon({
				icon: iconName,
				size: "xlarge"
			});
			const removeButton = kendo.html.renderButton(`<button type="button" ${REFERENCES.attachmentRemoveButton} data-uid="${this.context.htmlService.encode(uid)}" aria-label="Remove attachment" title="Remove attachment"></button>`, {
				icon: "x-mark-sm-outline",
				fillMode: "flat"
			});
			html += `<li class="${STYLES.fileBox}">`;
			html += icon;
			html += `<div class="${STYLES.fileInfo}">`;
			html += `<span class="${STYLES.fileName}">${this.context.htmlService.encode(name)}</span>`;
			html += `<span class="${STYLES.fileSize}">${this.context.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 = this.context.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 = this.context.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 (!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();
		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 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 require_kendo_core.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, utilsService = require_kendo_core.inject(require_kendo_core.UtilsService), htmlService = require_kendo_core.inject(require_kendo_core.HtmlService), fileUtilsService = require_kendo_core.inject(require_kendo_core.FileUtilsService), domUtilsService = require_kendo_core.inject(require_kendo_core.DomUtilsService)) {
		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.utilsService = utilsService;
		this.htmlService = htmlService;
		this.fileUtilsService = fileUtilsService;
		this.domUtilsService = domUtilsService;
		this._value = this.options.value || "";
		this._loading = this.options.loading || false;
		this._fileInputId = "promptbox_file_" + this.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 options = this.options;
		if (!options.startAffixTemplate) {
			return;
		}
		const startAffix = this.wrapper.find(`[${REFERENCES.startAffix}]`);
		const endAffix = this.wrapper.find(`[${REFERENCES.endAffix}]`);
		if (!startAffix.length || !endAffix.length) {
			return;
		}
		if (isMultiline) {
			const startAffixContent = startAffix.contents().detach();
			startAffix.addClass(STYLES.hidden);
			const spacer = $(`<div class="${STYLES.spacer}" ${REFERENCES.affixSpacer}></div>`);
			endAffix.prepend(spacer);
			endAffix.prepend(startAffixContent);
		} else {
			const spacer = endAffix.find(`[${REFERENCES.affixSpacer}]`);
			if (spacer.length) {
				const startAffixContent = spacer.prevAll().detach();
				startAffix.empty().append(startAffixContent);
				startAffix.removeClass(STYLES.hidden);
				spacer.remove();
			}
		}
	}
	_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,
			htmlService: this.htmlService,
			fileUtilsService: this.fileUtilsService,
			utilsService: this.utilsService,
			domUtilsService: this.domUtilsService,
			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.element.addClass(STYLES.hidden);
		this.wrapper.after(this.element);
	}
	/**
	* 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._getFileSelectButtonSettings(),
			speechToTextButtonSettings: speechSettings
		};
	}
	/**
	* Gets the current multiline state (for auto mode).
	*/
	isMultiline() {
		return this.inputManager.isMultiline();
	}
	/**
	* Attaches event handlers.
	*/
	_attachEvents() {
		this.wrapper.on(CLICK + NS, `[${REFERENCES.sendButton}]`, (e) => {
			e.preventDefault();
			this._handleSend();
		});
		this.wrapper.on(CLICK + NS, `[${REFERENCES.fileSelectButton}]`, (e) => {
			e.preventDefault();
			this.fileHandler.handleFileSelectClick();
		});
		this.wrapper.on(CLICK + NS, `[${REFERENCES.attachmentRemoveButton}]`, (e) => {
			e.preventDefault();
			const target = e.currentTarget;
			const uid = target.getAttribute("data-uid");
			if (uid) {
				this.fileHandler.removeAttachmentByUid(uid);
			}
		});
		this.wrapper.on(FOCUSIN + NS, (e) => {
			this._handleFocusIn(e);
		});
		this.wrapper.on(FOCUSOUT + NS, (e) => {
			this._handleFocusOut(e);
		});
	}
	/**
	* Detaches event handlers.
	*/
	_detachEvents() {
		this.wrapper.off(NS);
	}
	/**
	* Handles focus entering the widget.
	*/
	_handleFocusIn(e) {
		if (!this._hasFocus) {
			this._hasFocus = true;
			this.wrapper.addClass(STYLES.focus);
			this.trigger(EVENTS.focus);
		}
	}
	/**
	* Handles focus leaving the widget.
	*/
	_handleFocusOut(e) {
		const relatedTarget = e.relatedTarget;
		const isInsideWidget = relatedTarget && this.wrapper[0].contains(relatedTarget);
		if (!isInsideWidget && this._hasFocus) {
			this._hasFocus = false;
			this.wrapper.removeClass(STYLES.focus);
			this.trigger(EVENTS.blur);
		}
	}
	/**
	* Handles the send action.
	*/
	_handleSend() {
		const options = this.options;
		if (!options.enable || options.readonly || options.actionButton?.enable === false) {
			return;
		}
		if (this._loading) {
			const eventData = { actionType: "stop" };
			this.trigger(EVENTS.promptAction, eventData);
			this.loading(false);
			return;
		}
		const value = this._value;
		const hasValue = value?.trim();
		const hasFiles = this.fileHandler.hasFiles();
		if (!hasValue && !hasFiles) {
			return;
		}
		const eventData = {
			actionType: "send",
			value: hasValue ? value.trim() : "",
			files: this.fileHandler.getFiles()
		};
		this.trigger(EVENTS.promptAction, eventData);
		this.value("");
		this.clearFiles();
		this.focus();
	}
	/**
	* Handles input changes.
	*/
	_handleInput() {
		const oldValue = this._value;
		this._value = this.inputManager.getValue();
		this.actionButtonManager.updateState();
		this.trigger(EVENTS.input, { value: this._value });
		if (oldValue !== this._value) {
			this.trigger(EVENTS.valueChange, {
				oldValue,
				newValue: this._value
			});
		}
	}
	/**
	* Handles keydown events.
	* Enter sends the message in all modes.
	* Shift+Enter adds a newline in multi/auto modes.
	* Enter is ignored when loading to prevent accidental stops.
	*/
	_handleKeyDown(e) {
		const mode = this._getMode();
		const isEnter = e.key === "Enter" || e.which === 13 || e.keyCode === 13;
		if (isEnter) {
			if (e.shiftKey && mode !== LINE_MODE.SINGLE) {
				return;
			}
			e.preventDefault();
			if (!this._loading) {
				this._handleSend();
			}
		}
	}
};

//#endregion
//#region ../src/promptbox/index.ts
const cssProps = require_kendo_core.inject(require_kendo_core.CssPropertiesService);
cssProps.registerPrefix("PromptBox", "k-input-");
cssProps.registerValues("PromptBox", [{
	prop: "fillMode",
	values: cssProps.fillModeValues
}]);

//#endregion
//#region ../src/kendo.promptbox.js
/**
* Kendo UI PromptBox Widget
*
* This file serves as the entry point for the PromptBox widget.
*/
const __meta__ = {
	id: "promptbox",
	name: "PromptBox",
	category: "web",
	description: "The PromptBox component - a composite input for chat interfaces.",
	depends: [
		"core",
		"textarea",
		"textbox",
		"html.button"
	]
};
require_kendo_core.inject(require_kendo_core.WidgetRegistryService).register(PromptBox);
var kendo_promptbox_default = kendo;

//#endregion
Object.defineProperty(exports, 'PromptBox', {
  enumerable: true,
  get: function () {
    return PromptBox;
  }
});
Object.defineProperty(exports, '__meta__', {
  enumerable: true,
  get: function () {
    return __meta__;
  }
});
Object.defineProperty(exports, 'kendo_promptbox_default', {
  enumerable: true,
  get: function () {
    return kendo_promptbox_default;
  }
});