UNPKG

@progress/kendo-ui

Version:

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

1,416 lines 52.7 kB
const require_kendo_core = require("./kendo.core-kmRaf_Lg.js");
require("./kendo.icons.js");
require("./kendo.html.button.js");
require("./kendo.textbox.js");
require("./kendo.upload.js");
require("./kendo.textarea.js");
require("./kendo.speechtotextbutton.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"
};
//#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 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}" ${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;
	return `${kendo.html.renderButton(`<button title="${buttonTitle}" aria-label="${buttonTitle}" class="${disabledClass}" ${REFERENCES.fileSelectButton}${BUILT_IN_ATTR} type="button"${disabledAttr}${ariaDisabledAttr}></button>`, buttonOptions)}<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) => {
	return (fileSelectButtonConfig?.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) : "";
	if (!`${endAffix}${fileSelectButton}${speechButton}${sendButton}`.trim()) 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 header = renderHeader(headerTemplate, !!fileSelectButtonConfig);
	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();
		this.context.getInputElement().attr("aria-label", options.messages?.messageBoxTitle || "Prompt input");
		if (!options.enable) this.context.wrapper.attr("aria-disabled", "true");
		if (options.readonly) this.updateInputAccessibility();
	}
	updateInputAccessibility() {
		const options = this.context.getOptions();
		const input = this.context.getInputElement();
		if (!input) return;
		input.attr("aria-label", options.messages?.messageBoxTitle || "Prompt input");
		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 !== void 0) if (state.disabled) this.context.wrapper.attr("aria-disabled", "true");
		else this.context.wrapper.removeAttr("aria-disabled");
	}
};
//#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();
		if (this.context.isDisabled()) {
			sendButton.addClass(STYLES.disabled);
			return;
		}
		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}], .${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,
			messages: {
				start: "Start listening",
				stop: "Stop listening",
				...userSettings.messages
			},
			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 multiple = this.getFileSelectButtonSettings(options)?.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 fileInput = this.uploadInstance?.element?.[0] || 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 = require_kendo_core.fileUtilsService.getFileGroup(extension, true);
			const fileSizeMessage = require_kendo_core.fileUtilsService.getFileSizeMessage(size);
			const icon = kendo.ui.icon({
				icon: iconName,
				size: "xlarge"
			});
			const removeButton = kendo.html.renderButton(`<button type="button" ${REFERENCES.attachmentRemoveButton} data-uid="${require_kendo_core.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}">${require_kendo_core.htmlService.encode(name)}</span>`;
			html += `<span class="${STYLES.fileSize}">${require_kendo_core.htmlService.encode(fileSizeMessage)}</span>`;
			html += `</div>`;
			html += removeButton;
			html += `</li>`;
		}
		html += "</div></ul>";
		host.html(html);
		this._attachDragToScroll();
	}
	_attachDragToScroll() {
		const scrollContainer = this.context.wrapper.find(`[${REFERENCES.attachmentsHost}]`).find("." + STYLES.filesScroll);
		if (!scrollContainer.length) return;
		this._dragHandler = require_kendo_core.domUtilsService.createDragToScrollHandler(scrollContainer, {
			namespace: ".kendoPromptBox.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() || "";
					if (!restrictions.allowedExtensions.map((e) => e.toLowerCase().replace(".", "")).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 = require_kendo_core.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;
	}
};
//#endregion
//#region ../src/promptbox/collaborators/input-manager.collaborator.ts
const DEFAULT_MESSAGES$1 = {
	placeholder: "",
	actionButton: "Send prompt",
	actionButtonLoading: "Stop generating",
	messageBoxTitle: "Prompt input"
};
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() {
		if (this.context.getMode() !== 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) {
			if (el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight) {
				this.updateMultilineState(true);
				this.updateHeight();
			}
		} else {
			this.updateHeight();
			if (this.shouldCollapse()) {
				this.updateMultilineState(false);
				textarea.css("height", "");
			}
		}
	}
	updateMultiResize() {
		if (this.context.getMode() !== 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 = this.resolvePlaceholder(options, messages);
		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" : "";
		const placeholder = this.resolvePlaceholder(options, messages);
		let inputElement;
		if (mode === LINE_MODE.SINGLE) {
			inputElement = $(`<input type="text" class="${STYLES.promptBoxInput} ${STYLES.inputInner}" placeholder="${placeholder}" autocomplete="off" ${title} ${readonly} />`);
			this.inputInstance = {
				element: inputElement,
				value: (val) => {
					if (val === void 0) 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="${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 === void 0) return inputElement.val();
					inputElement.val(val);
				},
				enable: (enabled) => {
					inputElement.prop("disabled", !enabled);
				},
				focus: () => inputElement.trigger("focus"),
				destroy: () => {}
			};
		}
		return inputElement;
	}
	resolvePlaceholder(options, messages) {
		if (this.context.isPlaceholderExplicit ? this.context.isPlaceholderExplicit() : options.placeholder !== void 0) return options.placeholder ?? "";
		return messages.placeholder;
	}
	captureInitialDimensions() {
		if (this.context.getMode() === 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 rows = this.context.getOptions().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) {
		if (this.context.getMode() !== 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.kendoPromptBox", () => {
			this.updateAutoResize();
			this.updateMultiResize();
			this.context.callbacks.onInput();
		});
		inputElement.on("keydown.kendoPromptBox", (e) => {
			this.context.callbacks.onKeyDown(e);
		});
		inputElement.on("focus.kendoPromptBox", () => {
			this.context.callbacks.onFocus();
		});
		inputElement.on("blur.kendoPromptBox", () => {
			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: "",
	actionButton: "Send prompt",
	actionButtonLoading: "Stop generating",
	messageBoxTitle: "Prompt input",
	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: void 0,
			maxTextAreaHeight: void 0,
			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._isPlaceholderExplicit = false;
		this._isPlaceholderExplicit = !!options && Object.prototype.hasOwnProperty.call(options, "placeholder") && options.placeholder !== void 0;
		this._value = this.options.value || "";
		this._loading = this.options.loading || false;
		this._fileInputId = "promptbox_file_" + require_kendo_core.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();
		if (this._loading) this.actionButtonManager.setLoading(true);
		else this.actionButtonManager.updateState();
	}
	/**
	* Gets or sets the input value.
	*/
	value(newValue) {
		if (newValue === void 0) 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 === void 0) 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 === void 0) 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 === void 0) 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) {
		if (Object.prototype.hasOwnProperty.call(options, "placeholder")) this._isPlaceholderExplicit = options.placeholder !== void 0;
		if (options.headerTemplate !== void 0 || options.startAffixTemplate !== void 0 || options.endAffixTemplate !== void 0 || options.topAffixTemplate !== void 0 || options.actionButton !== void 0 || options.actionButtonSettings !== void 0 || options.fileSelectButton !== void 0 || options.speechToTextButton !== void 0 || options.mode !== void 0) {
			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 !== void 0 || options.messages?.placeholder !== void 0) this.inputManager.updatePlaceholder();
		if (options.messages?.actionButton !== void 0 || options.messages?.actionButtonLoading !== void 0) this.actionButtonManager.updateMessages();
		if (options.enable !== void 0) this.enable(options.enable);
		if (options.readonly !== void 0) this.readonly(options.readonly);
		if (options.loading !== void 0) this.loading(options.loading);
	}
	_initInputManager() {
		this.inputManager = new InputManager({
			wrapper: this.wrapper,
			getOptions: () => this.options,
			isPlaceholderExplicit: () => this._isPlaceholderExplicit,
			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() {
		return this.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 === void 0 || 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();
		if (!options.enable || options.readonly || fileSelectSettings?.enable === false) {
			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;
	}
	/**
	* 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 renderConfig = {
			affixConfig: {
				startAffixTemplate: options.startAffixTemplate,
				endAffixTemplate: options.endAffixTemplate,
				topAffixTemplate: options.topAffixTemplate
			},
			fileSelectButtonConfig: this._getFileSelectButtonConfig(),
			buttonSettingsConfig: this._getButtonSettingsConfig()
		};
		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}"></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}]`);
		if (!matches.not(BUILT_IN_SELECTOR).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;
		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 : void 0;
		return {
			actionButtonSettings: options.actionButton || void 0,
			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.kendoPromptBox", `[${REFERENCES.sendButton}]`, (e) => {
			e.preventDefault();
			this._handleSend();
		});
		this.wrapper.on("click.kendoPromptBox", `[${REFERENCES.fileSelectButton}]`, (e) => {
			e.preventDefault();
			this.fileHandler.handleFileSelectClick();
		});
		this.wrapper.on("click.kendoPromptBox", `[${REFERENCES.attachmentRemoveButton}]`, (e) => {
			e.preventDefault();
			const uid = e.currentTarget.getAttribute("data-uid");
			if (uid) this.fileHandler.removeAttachmentByUid(uid);
		});
		this.wrapper.on("focusin.kendoPromptBox", (e) => {
			this._handleFocusIn(e);
		});
		this.wrapper.on("focusout.kendoPromptBox", (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;
		if (!(relatedTarget && this.wrapper[0].contains(relatedTarget)) && 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) {
			this.trigger(EVENTS.promptAction, { actionType: "stop" });
			this.loading(false);
			return;
		}
		const trimmedValue = this._value?.trim();
		const hasFiles = this.fileHandler.hasFiles();
		if (!trimmedValue && !hasFiles) return;
		const eventData = {
			actionType: "send",
			value: trimmedValue ? trimmedValue : "",
			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();
		if (e.key === "Enter" || e.which === 13 || e.keyCode === 13) {
			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.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.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;
	}
});