UNPKG

@progress/kendo-ui

Version:

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

4,702 lines 179 kB
const require_kendo_core = require("./kendo.core-kmRaf_Lg.js");
require("./kendo.icons.js");
require("./kendo.html.button.js");
require("./kendo.button.js");
require("./kendo.data-T0mqWOrQ.js");
require("./kendo.dropdownbutton.js");
require("./kendo.menu.js");
require("./kendo.toolbar.js");
require("./kendo.avatar.js");
const require_kendo_promptbox = require("./kendo.promptbox-CrpKT-40.js");
//#region ../src/chat/collaborators/data-manager.collaborator.ts
var AuxiliaryReferenceStore = class {
	constructor(pinnedMessages) {
		this.resolvedReferenceMessages = [];
		this.pinnedMessages = pinnedMessages;
	}
	getMessageById(id) {
		if (!id) return null;
		return this._getPinnedMessageById(id) || this._getResolvedReferenceMessageById(id);
	}
	getPinnedMessage() {
		for (let index = this.pinnedMessages.length - 1; index >= 0; index--) if (this.pinnedMessages[index].isPinned) return this.pinnedMessages[index];
		return this.pinnedMessages[this.pinnedMessages.length - 1] || null;
	}
	syncPinnedMessageState(message, isPinned, setPinnedState) {
		const auxiliaryPinnedMessage = this._getPinnedMessageById(message?.id);
		if (auxiliaryPinnedMessage && auxiliaryPinnedMessage !== message) setPinnedState(auxiliaryPinnedMessage, isPinned);
	}
	cacheResolvedReferenceMessages(messages) {
		(messages || []).forEach((message) => {
			if (!message?.id) return;
			const existingMessage = this._getResolvedReferenceMessageById(message.id);
			if (existingMessage) {
				const existingIndex = this.resolvedReferenceMessages.indexOf(existingMessage);
				this.resolvedReferenceMessages.splice(existingIndex, 1, message);
				return;
			}
			this.resolvedReferenceMessages.push(message);
		});
	}
	_getPinnedMessageById(id) {
		return this._getMessageFromCollectionById(this.pinnedMessages, id);
	}
	_getResolvedReferenceMessageById(id) {
		return this._getMessageFromCollectionById(this.resolvedReferenceMessages, id);
	}
	_getMessageFromCollectionById(collection, id) {
		if (!id) return null;
		for (let index = 0; index < collection.length; index++) if (String(collection[index].id) === String(id)) return collection[index];
		return null;
	}
};
var RemoteRangeBookkeeping = class {
	constructor() {
		this.startIndex = 0;
		this.initialized = false;
	}
	reset() {
		this.startIndex = 0;
		this.initialized = false;
	}
	setStart(startIndex) {
		this.startIndex = Math.max(0, startIndex);
		this.initialized = true;
	}
	sync(totalCount, loadedCount) {
		this.setStart(Math.max(0, totalCount - loadedCount));
		return this.getRange(loadedCount);
	}
	getRange(loadedCount) {
		const startIndex = this.initialized ? this.startIndex : 0;
		return {
			startIndex,
			endIndex: startIndex + loadedCount
		};
	}
};
/**
* DataManager handles all data operations for the Chat component.
* Manages message storage, retrieval, updates, and data source configuration.
*
* This is an internal collaborator - not a shared service.
*/
var DataManager = class DataManager {
	static {
		this.DEFAULT_OPTIONS = {
			autoSync: true,
			schema: { model: {
				id: "id",
				fields: {
					id: { type: "string" },
					text: { type: "string" },
					authorId: { type: "string" },
					authorName: { type: "string" },
					authorImageUrl: { type: "string" },
					authorImageAltText: { type: "string" },
					replyToId: {
						type: "string",
						defaultValue: null
					},
					isDeleted: {
						type: "boolean",
						defaultValue: false
					},
					isPinned: {
						type: "boolean",
						defaultValue: false
					},
					isTyping: {
						type: "boolean",
						defaultValue: false
					},
					timestamp: { type: "date" },
					files: { parse: (value) => value || [] },
					suggestedActions: { parse: (value) => value || [] },
					status: {
						type: "string",
						defaultValue: null
					},
					failed: {
						type: "boolean",
						defaultValue: false
					},
					attachments: { parse: (value) => value || null },
					attachmentLayout: {
						type: "string",
						defaultValue: null
					}
				}
			} }
		};
	}
	static {
		this.FIELD_MAP = {
			text: "textField",
			authorId: "authorIdField",
			authorName: "authorNameField",
			authorImageUrl: "authorImageUrlField",
			authorImageAltText: "authorImageAltTextField",
			id: "idField",
			timestamp: "timestampField",
			files: "filesField",
			suggestedActions: "suggestedActionsField",
			replyToId: "replyToIdField",
			isDeleted: "isDeletedField",
			isPinned: "isPinnedField",
			isTyping: "isTypingField",
			status: "statusField",
			failed: "failedField",
			attachments: "attachmentsField",
			attachmentLayout: "attachmentLayoutField"
		};
	}
	/**
	* Constructs a new DataManager instance.
	* @param context - Context with chat options and services
	*/
	constructor(context) {
		this.context = context;
		this.options = this.buildOptions(context.chatOptions);
		this.dataSource = this.createDataSource(context.chatOptions);
		this.auxiliaryReferences = new AuxiliaryReferenceStore(context.chatOptions.pinnedMessages || []);
		this.remoteRange = new RemoteRangeBookkeeping();
	}
	_isRemoteDataSource() {
		return !!this.dataSource?.options?.serverPaging;
	}
	_setPinnedState(message, isPinned) {
		if (!message) return;
		if (typeof message.set === "function") {
			message.set("isPinned", isPinned);
			return;
		}
		message.isPinned = isPinned;
	}
	/**
	* Builds internal options from chat options.
	*/
	buildOptions(chatOptions) {
		let options;
		if (chatOptions.dataSource instanceof kendo.data.DataSource) options = $.extend(true, {}, DataManager.DEFAULT_OPTIONS, chatOptions.dataSource.options);
		else if (Array.isArray(chatOptions.dataSource)) options = $.extend(true, {}, DataManager.DEFAULT_OPTIONS, { data: chatOptions.dataSource });
		else options = $.extend(true, {}, DataManager.DEFAULT_OPTIONS, chatOptions.dataSource);
		options.autoAssignId = chatOptions.autoAssignId;
		this.mapFields(options.schema.model.fields, chatOptions);
		return options;
	}
	/**
	* Creates and configures a data source for the chat component.
	*/
	createDataSource(chatOptions) {
		if (chatOptions.dataSource instanceof kendo.data.DataSource) return chatOptions.dataSource;
		return kendo.data.DataSource.create(this.options);
	}
	/**
	* Maps field configuration from chat options to data source field definitions.
	*/
	mapFields(fields, chatOptions) {
		for (const key in fields) if (Object.prototype.hasOwnProperty.call(fields, key) && DataManager.FIELD_MAP[key]) {
			const optionKey = DataManager.FIELD_MAP[key];
			fields[key].from = chatOptions[optionKey];
		}
	}
	/**
	* Gets the data source instance.
	*/
	getDataSource() {
		return this.dataSource;
	}
	/**
	* Gets the current data source view.
	*/
	getView() {
		return this.dataSource.view();
	}
	/**
	* Gets the last message in the data source view.
	*/
	getLastMessage() {
		const view = this.getView();
		return view.length ? view[view.length - 1] : null;
	}
	/**
	* Gets a message by ID from the data source.
	*/
	getMessageById(id) {
		if (!id) return null;
		return this.getLoadedMessageById(id) || this.getAuxiliaryMessageById(id);
	}
	getAuxiliaryMessageById(id) {
		if (!id) return null;
		return this.auxiliaryReferences.getMessageById(id);
	}
	cacheResolvedReferenceMessages(messages) {
		this.auxiliaryReferences.cacheResolvedReferenceMessages(messages);
	}
	getLoadedMessageById(id) {
		if (!id) return null;
		return this.dataSource.get(id);
	}
	/**
	* Gets a message by UID from the data source.
	*/
	getMessageByUid(uid) {
		return this.dataSource.getByUid(uid);
	}
	/**
	* Gets a file by UID from a message's files array.
	*/
	getFileByUid(message, uid) {
		if (!message || !uid) return null;
		return message.files.find((file) => file.uid === uid) || null;
	}
	/**
	* Gets the currently pinned message.
	*/
	getPinnedMessage() {
		return this.dataSource.data().find((m) => m.isPinned) || this.auxiliaryReferences.getPinnedMessage() || null;
	}
	/**
	* Adds a new message to the data source.
	*/
	postMessage(message, currentUserId) {
		const messageInput = typeof message === "string" ? { text: message } : message;
		const messageData = {
			id: this.options.autoAssignId ? require_kendo_core.utilsService.guid() : void 0,
			timestamp: /* @__PURE__ */ new Date(),
			files: [],
			...messageInput,
			authorId: messageInput.authorId?.toString() || currentUserId
		};
		return this.dataSource.add(messageData);
	}
	/**
	* Updates a message in the data source.
	*/
	updateMessage(message, newData) {
		if (!message) return null;
		let targetMessage = message;
		if (!(message instanceof kendo.data.ObservableObject)) targetMessage = this.getLoadedMessageById(message.id);
		if (!targetMessage) return null;
		for (const key in newData) if (Object.prototype.hasOwnProperty.call(newData, key)) targetMessage.set(key, newData[key]);
		return targetMessage;
	}
	/**
	* Marks a message as deleted.
	*/
	removeMessage(message) {
		if (!message) return false;
		message.set("isDeleted", true);
		return true;
	}
	/**
	* Pins a message.
	*/
	pinMessage(message) {
		if (!message) return false;
		const pinnedMessage = this.getPinnedMessage();
		if (pinnedMessage && String(pinnedMessage.id) !== String(message.id)) {
			this._setPinnedState(pinnedMessage, false);
			this.auxiliaryReferences.syncPinnedMessageState(pinnedMessage, false, this._setPinnedState.bind(this));
		}
		this._setPinnedState(message, true);
		this.auxiliaryReferences.syncPinnedMessageState(message, true, this._setPinnedState.bind(this));
		return message;
	}
	/**
	* Clears the currently pinned message.
	*/
	clearPinnedMessage() {
		const pinnedMessage = this.getPinnedMessage();
		this._setPinnedState(pinnedMessage, false);
		this.auxiliaryReferences.syncPinnedMessageState(pinnedMessage, false, this._setPinnedState.bind(this));
	}
	resetRemoteRange() {
		this.remoteRange.reset();
	}
	setRemoteRangeStart(startIndex) {
		this.remoteRange.setStart(startIndex);
	}
	syncRemoteRange(totalCount = this.getTotalCount()) {
		if (!this._isRemoteDataSource()) {
			this.resetRemoteRange();
			return this.getLoadedRange();
		}
		return this.remoteRange.sync(totalCount, this.getLoadedCount());
	}
	getLoadedCount() {
		return this.dataSource.data().length;
	}
	getLoadedRange() {
		const loadedCount = this.getLoadedCount();
		if (!this._isRemoteDataSource()) return {
			startIndex: 0,
			endIndex: loadedCount
		};
		return this.remoteRange.getRange(loadedCount);
	}
	isRangeLoaded(startIndex, endIndex) {
		const loadedRange = this.getLoadedRange();
		return startIndex >= loadedRange.startIndex && endIndex <= loadedRange.endIndex;
	}
	getMessagesInRange(startIndex, endIndex) {
		const data = this.dataSource.data();
		const loadedRange = this.getLoadedRange();
		const safeStart = Math.max(startIndex, loadedRange.startIndex);
		const safeEnd = Math.min(endIndex, loadedRange.endIndex);
		const result = [];
		if (safeEnd <= safeStart) return result;
		const localStart = safeStart - loadedRange.startIndex;
		const localEnd = safeEnd - loadedRange.startIndex;
		for (let i = localStart; i < localEnd; i++) result.push(data[i]);
		return result;
	}
	getMessageIndexById(id) {
		if (!id) return -1;
		const data = this.dataSource.data();
		const loadedRange = this.getLoadedRange();
		for (let i = 0; i < data.length; i++) if (String(data[i].id) === String(id)) return loadedRange.startIndex + i;
		return -1;
	}
	getTotalCount() {
		if (this._isRemoteDataSource()) return this.dataSource.total();
		return this.dataSource.data().length;
	}
};
//#endregion
//#region ../src/chat/constants.ts
/**
* Chat component constants
*
* This module contains all constants used throughout the Chat widget
* and its collaborators.
*/
/** Namespace for event binding */
const NS = ".kendoChat";
/** Message width display modes */
const MESSAGE_WIDTH_MODE = {
	STANDARD: "standard",
	FULL: "full"
};
/** Message status values */
const MESSAGE_STATUS = {
	SENT: "sent",
	DELIVERED: "delivered",
	SEEN: "seen",
	FAILED: "failed"
};
/** Files layout modes */
const FILES_LAYOUT_MODE = {
	HORIZONTAL: "horizontal",
	VERTICAL: "vertical",
	WRAP: "wrap"
};
/** Suggested actions layout modes */
const SUGGESTED_ACTIONS_LAYOUT_MODE = {
	SCROLL: "scroll",
	WRAP: "wrap",
	SCROLLBUTTONS: "scrollbuttons"
};
/** Icon identifiers used in the Chat component */
const ICONS = {
	attachment: "paperclip",
	attachmentMenu: "more-vertical",
	checkCircle: "check-circle",
	chevronDown: "chevron-down",
	chevronLeft: "chevron-left",
	chevronRight: "chevron-right",
	chevronUp: "chevron-up",
	download: "download",
	fileError: "file-error",
	filePdf: "file-pdf",
	microphoneOutline: "microphone",
	pin: "pin",
	undo: "undo",
	copy: "copy",
	trash: "trash",
	send: "paper-plane",
	stop: "stop",
	upload: "upload",
	warningTriangle: "warning-triangle",
	x: "x",
	arrowDown: "arrow-down",
	retry: "arrow-rotate-cw"
};
/** Command identifiers for menu actions */
const COMMANDS = {
	reply: "reply",
	copy: "copy",
	pin: "pin",
	delete: "delete",
	download: "download"
};
/** CSS class names used in the Chat component */
const STYLES = {
	active: "k-active",
	selected: "k-selected",
	focus: "k-focus",
	expanded: "k-expanded",
	disabled: "k-disabled",
	hidden: "k-hidden",
	generating: "k-generating",
	noAvatar: "k-no-avatar",
	file: "k-file-box",
	fileInfo: "k-file-info",
	fileName: "k-file-name",
	fileSize: "k-file-size",
	fileWrapper: "k-file-box-wrapper",
	fileWrapperScrollableStart: "k-file-box-wrapper-scrollable-start",
	filesScroll: "k-files-scroll",
	filesVertical: "k-files-vertical",
	filesWrap: "k-files-wrap",
	avatar: "k-avatar",
	bubble: "k-bubble",
	bubbleContent: "k-bubble-content",
	bubbleExpandable: "k-bubble-expandable",
	bubbleExpandableIndicator: "k-bubble-expandable-indicator",
	chatBubble: "k-chat-bubble",
	chatBubbleText: "k-chat-bubble-text",
	button: "k-button",
	buttonDefaults: "k-button-md k-rounded-md k-button-solid k-button-solid-base",
	buttonIcon: "k-button-icon",
	buttonPrimary: "k-button-md k-rounded-md k-button-flat k-button-flat-primary",
	iconButton: "k-icon-button",
	menuButton: "k-menu-button",
	chatSend: "k-chat-send",
	chatUpload: "k-chat-upload",
	downloadButton: "k-chat-download-button",
	downloadButtonWrapper: "k-chat-download-button-wrapper",
	canvas: "k-chat-canvas",
	card: "k-card",
	cardAction: "k-card-action",
	cardActions: "k-card-actions",
	cardActionsVertical: "k-actions-vertical",
	cardBody: "k-card-body",
	cardDeck: "k-card-deck",
	cardDeckScrollWrap: "k-card-deck-scrollwrap",
	cardList: "k-card-list",
	cardMedia: "k-card-media",
	cardRich: "k-card-type-rich",
	cardSubtitle: "k-card-subtitle",
	cardTitle: "k-card-title",
	cardWrapper: "k-card-container",
	dropzoneHint: "k-dropzone-hint",
	dropzoneIcon: "k-dropzone-icon",
	dropzoneInner: "k-dropzone-inner",
	externalDropzone: "k-external-dropzone",
	header: "k-chat-header",
	message: "k-message",
	messageAuthor: "k-message-author",
	messageGroup: "k-message-group",
	messageGroupContent: "k-message-group-content",
	messageGroupFullWidth: "k-message-group-full-width",
	messageGroupReceiver: "k-message-group-receiver",
	messageGroupSender: "k-message-group-sender",
	messageListContent: "k-message-list-content",
	messageListContentEmpty: "k-message-list-content-empty",
	messageRemoved: "k-message-removed",
	messageStatus: "k-message-status",
	messageTime: "k-message-time",
	messageInfo: "k-message-info",
	messageFailed: "k-message-failed",
	messageFailedContent: "k-message-failed-content",
	messageFailedText: "k-message-failed-text",
	messageToolbar: "k-chat-message-toolbar",
	messageRetryButton: "k-resend-button",
	messagePinned: "k-message-pinned",
	messageReference: "k-message-reference",
	messageReferenceContent: "k-message-reference-content",
	messageReferenceReceiver: "k-message-reference-receiver",
	messageReferenceSender: "k-message-reference-sender",
	scrollButtonIconLeft: "chevron-left",
	scrollButtonIconRight: "chevron-right",
	suggestion: "k-suggestion",
	suggestionGroup: "k-suggestion-group",
	suggestionGroupScrollable: "k-suggestion-group-scrollable",
	suggestionGroupWrap: "k-suggestion-group-wrap",
	suggestionsScroll: "k-suggestions-scroll",
	suggestionScrollWrap: "k-suggestion-scrollwrap",
	suggestionScrollWrapGradient: "k-suggestion-scrollwrap-gradient",
	timestamp: "k-timestamp",
	typingIndicator: "k-typing-indicator",
	viewWrapper: "k-message-list",
	wrapper: "k-chat",
	messageBox: "k-message-box",
	messageBoxWrapper: "k-message-box-wrapper",
	messageBoxSeparator: "k-separator",
	spacer: "k-spacer",
	scrollFab: "k-chat-scroll-fab",
	fab: "k-fab"
};
/** Data attribute references for element selection */
const REFERENCES = {
	fileButton: "ref-chat-file-button",
	fileMenuButton: "ref-chat-file-menu-button",
	fileWrapper: "ref-chat-file-wrapper",
	fileCloseButton: "ref-chat-file-close-button",
	attachmentActionButton: "ref-chat-attachment-action-button",
	userStatusWrapper: "ref-chat-user-status-wrapper",
	bubbleExpandableIndicator: "ref-chat-bubble-expandable-indicator",
	messageReferencePinWrapper: "ref-chat-message-reference-pin-wrapper",
	messageReferenceReplyWrapper: "ref-chat-message-reference-reply-wrapper",
	pinnedMessageCloseButton: "ref-chat-pinned-message-close-button",
	replyMessageCloseButton: "ref-chat-reply-message-close-button",
	leftScrollButton: "ref-chat-left-scroll-button",
	rightScrollButton: "ref-chat-right-scroll-button",
	sendButton: "ref-chat-message-box-send-button",
	speechToTextButton: "ref-chat-message-box-speech-to-text-button",
	suggestionGroup: "ref-chat-suggestion-group",
	fileUploadInput: "ref-chat-file-upload-input",
	viewWrapper: "ref-chat-view-wrapper",
	scrollToBottomButton: "ref-chat-scroll-to-bottom-button",
	retryButton: "ref-chat-message-retry-button"
};
/** Event names triggered by the Chat component */
const EVENTS = {
	sendMessage: "sendMessage",
	suggestionClick: "suggestionClick",
	unpin: "unpin",
	input: "input",
	toolbarAction: "toolbarAction",
	fileMenuAction: "fileMenuAction",
	contextMenuAction: "contextMenuAction",
	download: "download",
	fileSelect: "fileSelect",
	fileRemove: "fileRemove",
	executeAction: "executeAction",
	resendMessage: "resendMessage",
	referencedMessageClick: "referencedMessageClick",
	suggestedActionClick: "suggestedActionClick",
	messageToolbarExecute: "messageToolbarExecute",
	fileMenuExecute: "fileMenuExecute",
	downloadAllFiles: "downloadAllFiles",
	expandableToggle: "expandableToggle",
	replyMessageCloseButtonClick: "replyMessageCloseButtonClick"
};
/** Common DOM event names */
const CLICK = "click";
const FOCUS = "focus";
const BLUR = "blur";
const CHANGE = "change";
//#endregion
//#region ../src/chat/collaborators/accessibility-manager.collaborator.ts
const NAVIGATION_NS = `${NS}navigation`;
const ON_FOCUS_MESSAGE_TIME_SELECTOR = `.${STYLES.messageTime}[data-timestamp-visibility='onFocus']`;
function toggleOnFocusMessageTime($bubble, visible) {
	$bubble.closest(`.${STYLES.message}`).find(ON_FOCUS_MESSAGE_TIME_SELECTOR).toggleClass(STYLES.hidden, !visible);
}
let isKeyEvent = null;
let isShiftKey = false;
/**
* AccesibilityManager handles ARIA attributes and keyboard navigation
* for the Chat component according to WCAG 2.1 AA compliance.
*
* This is an internal collaborator - not a shared service.
*/
var AccesibilityManager = class {
	/**
	* Constructs a new AccesibilityManager instance.
	* @param context - Context with wrapper, options, and services
	*/
	constructor(context) {
		this.context = context;
	}
	/**
	* Sets up ARIA attributes for the Chat component.
	*/
	setupAriaAttributes() {
		const messages = this.context.getOptions().messages;
		const messageList = this.context.wrapper.find(`.${STYLES.viewWrapper}`);
		if (messageList.length) messageList.attr("role", "log").attr("aria-live", "polite").attr("aria-label", messages.messageListLabel);
		this.setupBubbleTabNavigation();
		this.setupSuggestionAccessibility();
		this.setupButtonAccessibility(messages);
		this.setupExpandableIndicators();
		this.setupScrollButtonsAria();
		this.setupMessageBoxAria(messages);
	}
	/**
	* Sets up tabindex navigation for chat bubbles.
	*/
	setupBubbleTabNavigation() {
		const allBubbles = this.context.wrapper.find(`.${STYLES.bubble}`);
		const interactableBubbles = allBubbles.filter(function() {
			return $(this).find(`.${STYLES.typingIndicator}`).length === 0;
		});
		const typingIndicatorBubbles = allBubbles.filter(function() {
			return $(this).find(`.${STYLES.typingIndicator}`).length > 0;
		});
		interactableBubbles.attr("tabindex", "0");
		typingIndicatorBubbles.attr("tabindex", "-1");
		interactableBubbles.off(`${FOCUS}${NAVIGATION_NS} ${BLUR}${NAVIGATION_NS} ${CLICK}${NAVIGATION_NS}`).on(`${FOCUS}${NAVIGATION_NS}`, function() {
			const $this = $(this);
			allBubbles.each(function() {
				toggleOnFocusMessageTime($(this), false);
			});
			if (isKeyEvent) allBubbles.removeClass(STYLES.selected);
			allBubbles.removeClass(STYLES.focus);
			if (isKeyEvent) $this.addClass(STYLES.selected);
			$this.addClass(STYLES.focus);
			toggleOnFocusMessageTime($this, true);
			isKeyEvent = false;
		}).on(`${BLUR}${NAVIGATION_NS}`, function() {
			if (isKeyEvent && !isShiftKey) $(this).removeClass(STYLES.selected);
			toggleOnFocusMessageTime($(this), false);
			$(this).removeClass(STYLES.focus);
		}).on(`${CLICK}${NAVIGATION_NS}`, function(e) {
			if ($(e.target).closest(REFERENCES.fileMenuButton)) return;
			$(this).trigger(FOCUS);
		});
	}
	/**
	* Sets up accessibility for suggestion groups.
	*/
	setupSuggestionAccessibility() {
		this.context.wrapper.find(`.${STYLES.suggestionGroup}`).each((_index, element) => {
			const $group = $(element);
			$group.attr("role", "group");
			$group.find(`.${STYLES.suggestion}`).each((_suggestionIndex, suggestionElement) => {
				$(suggestionElement).attr("role", "button").attr("tabindex", "0");
			});
		});
	}
	/**
	* Sets up accessibility for various buttons.
	*/
	setupButtonAccessibility(messages) {
		this.context.wrapper.find(".k-input-suffix > .k-button").each((_index, element) => {
			const $button = $(element);
			if (!$button.attr("role") && element.nodeName.toLowerCase() !== "button") $button.attr("role", "button");
			if (!$button.attr("aria-label") && !$button.attr("title")) {
				if ($button.hasClass(STYLES.chatSend)) $button.attr("aria-label", messages.actionButton || messages.sendButton);
				else if ($button.hasClass("k-chat-upload")) $button.attr("aria-label", messages.fileButton);
			}
			if ($button.hasClass(STYLES.chatSend) && $button.hasClass("k-disabled")) $button.attr("aria-disabled", "true");
		});
		this.context.wrapper.find(`.${STYLES.downloadButton}`).each((_index, element) => {
			const $button = $(element);
			if (!$button.attr("role") && element.nodeName.toLowerCase() !== "button") $button.attr("role", "button");
			if (!$button.attr("aria-label") && !$button.attr("title")) $button.attr("aria-label", messages.downloadAll);
		});
		this.setupCloseButtonAria(REFERENCES.pinnedMessageCloseButton, messages.pinnedMessageCloseButton);
		this.setupCloseButtonAria(REFERENCES.replyMessageCloseButton, messages.replyMessageCloseButton);
		this.context.wrapper.find(`[${REFERENCES.fileMenuButton}]`).each((_index, element) => {
			const $button = $(element);
			$button.attr("aria-label", messages.fileMenuButton);
			$button.attr("title", messages.fileMenuButton);
		});
	}
	/**
	* Sets up ARIA attributes for close buttons.
	*/
	setupCloseButtonAria(reference, label) {
		this.context.wrapper.find(`[${reference}]`).each((_index, element) => {
			const $button = $(element);
			if (!$button.attr("role") && element.nodeName.toLowerCase() !== "button") $button.attr("role", "button");
			if (!$button.attr("aria-label") && !$button.attr("title")) {
				$button.attr("aria-label", label);
				$button.attr("title", label);
			}
		});
	}
	/**
	* Sets up ARIA attributes for expandable indicators.
	*/
	setupExpandableIndicators() {
		this.context.wrapper.find(`.${STYLES.bubbleExpandableIndicator}`).each((_index, element) => {
			const $indicator = $(element);
			$indicator.attr("role", "button").attr("tabindex", "0");
			const label = $indicator.closest(`.${STYLES.bubble}`).hasClass(STYLES.expanded) ? "Collapse message" : "Expand message";
			$indicator.attr("aria-label", label);
		});
	}
	/**
	* Sets up ARIA attributes for scroll buttons.
	*/
	setupScrollButtonsAria() {
		const dir = this.context.getOptions().dir;
		const leftScrollButton = this.context.wrapper.find(`[${REFERENCES.leftScrollButton}]`);
		const rightScrollButton = this.context.wrapper.find(`[${REFERENCES.rightScrollButton}]`);
		const leftText = dir === "rtl" ? "Scroll Right" : "Scroll Left";
		const rightText = dir === "rtl" ? "Scroll Left" : "Scroll Right";
		leftScrollButton.attr("aria-label", leftText);
		leftScrollButton.attr("title", leftText);
		rightScrollButton.attr("aria-label", rightText);
		rightScrollButton.attr("title", rightText);
	}
	/**
	* Sets up ARIA attributes for the message box.
	*/
	setupMessageBoxAria(messages) {
		this.context.wrapper.find(`.${STYLES.messageBox}`).find("textarea, input:not([type='hidden']):not([type='file']):not([type='button']):not([type='submit']):not([type='reset']):not([type='checkbox']):not([type='radio'])").first().attr("aria-label", messages.messageBoxLabel);
	}
	/**
	* Sets up ARIA attributes for file close buttons.
	*/
	setFileCloseButtonAria(container) {
		container.find(`[${REFERENCES.fileCloseButton}]`).each((_index, element) => {
			const $button = $(element);
			if (!$button.attr("role") && element.nodeName.toLowerCase() !== "button") $button.attr("role", "button");
			if (!$button.attr("aria-label") && !$button.attr("title")) {
				$button.attr("aria-label", "Remove selected file");
				$button.attr("title", "Remove selected file");
			}
		});
	}
	/**
	* Updates ARIA attributes for expandable indicators when their state changes.
	*/
	updateExpandableIndicatorAria(indicator, isExpanded) {
		const label = isExpanded ? "Collapse message" : "Expand message";
		indicator.attr("aria-label", label);
	}
	/**
	* Handles keyboard navigation for the Chat component.
	*/
	handleKeyDown(e, messageBox) {
		if (!$(e.target).closest(this.context.wrapper).length) return null;
		const target = $(e.target);
		const key = e.keyCode || e.which;
		isKeyEvent = true;
		isShiftKey = e.shiftKey;
		if (target.hasClass(STYLES.bubble)) this.handleBubbleKeyDown(e, key);
		if (target.hasClass(STYLES.suggestion)) this.handleClickableElementKeyDown(e, key);
		if (target.hasClass(STYLES.bubbleExpandableIndicator)) this.handleExpandableIndicatorKeyDown(e, key, target);
		if (target.hasClass("k-input-inner")) this.handleMessageInputKeyDown(e, key, messageBox);
		if (target.closest(".k-input-suffix .k-button").length) this.handleClickableElementKeyDown(e, key);
		return null;
	}
	/**
	* Handles keyboard navigation within bubbles.
	*/
	handleBubbleKeyDown(e, key) {
		const currentBubble = $(e.target);
		if (currentBubble.find(`.${STYLES.typingIndicator}`).length > 0) return;
		const bubbles = this.context.wrapper.find(`.${STYLES.bubble}`).filter(function() {
			return $(this).find(`.${STYLES.typingIndicator}`).length === 0;
		});
		const currentIndex = bubbles.index(currentBubble);
		const keys = require_kendo_core.utilsService.keys;
		switch (key) {
			case keys.UP:
				e.preventDefault();
				this.focusBubbleAtIndex(bubbles, currentIndex - 1);
				break;
			case keys.DOWN:
				e.preventDefault();
				this.focusBubbleAtIndex(bubbles, currentIndex + 1);
				break;
			case keys.HOME:
				e.preventDefault();
				this.focusBubbleAtIndex(bubbles, 0);
				break;
			case keys.END:
				e.preventDefault();
				this.focusBubbleAtIndex(bubbles, bubbles.length - 1);
				break;
		}
	}
	/**
	* Focuses a bubble at the specified index.
	*/
	focusBubbleAtIndex(bubbles, index) {
		if (bubbles.length === 0 || index < 0 || index >= bubbles.length) return;
		const targetBubble = bubbles.eq(index);
		targetBubble.attr("data-keyboard-focus", "true");
		targetBubble.focus();
		targetBubble.removeAttr("data-keyboard-focus");
	}
	/**
	* Handles keyboard events for expandable indicators.
	*/
	handleExpandableIndicatorKeyDown(e, key, target) {
		const keys = require_kendo_core.utilsService.keys;
		if (key === keys.ENTER || key === keys.SPACEBAR) {
			e.preventDefault();
			target.trigger(CLICK);
			const isExpanded = target.closest(`.${STYLES.bubble}`).hasClass(STYLES.expanded);
			this.updateExpandableIndicatorAria(target, !isExpanded);
		}
	}
	/**
	* Handles keyboard events for the message input.
	* Note: Enter key handling is delegated to PromptBox component.
	*/
	handleMessageInputKeyDown(e, key, messageBox) {}
	/**
	* Handles keyboard events for clickable elements (suggestions, buttons).
	*/
	handleClickableElementKeyDown(e, key) {
		const keys = require_kendo_core.utilsService.keys;
		if (key === keys.ENTER || key === keys.SPACEBAR) {
			e.preventDefault();
			$(e.target).trigger(CLICK);
		}
	}
};
//#endregion
//#region ../src/chat/collaborators/message-toolbar.collaborator.ts
/**
* MessageToolbar provides toolbar functionality for chat messages.
* Handles quick actions that can be performed on messages through toolbar buttons.
*
* This is an internal collaborator that extends Widget for event handling.
*/
var MessageToolbar = class MessageToolbar extends require_kendo_core.Widget {
	static {
		this.options = { name: "MessageToolbar" };
	}
	/**
	* Constructs a new MessageToolbar instance.
	*/
	constructor(element, options) {
		super(element, $.extend(true, {}, MessageToolbar.options, options));
		this.extendItemsConfig();
		this.create();
		this.attachEvents();
	}
	/**
	* Creates the underlying Kendo UI ToolBar.
	*/
	create() {
		const existingToolBar = this.element.data("kendoToolBar");
		if (existingToolBar) existingToolBar.destroy();
		const toolbarOptions = $.extend({}, this.options, {
			fillMode: "flat",
			size: "small"
		});
		delete toolbarOptions.name;
		this.toolbar = new kendo.ui.ToolBar(this.element, toolbarOptions);
	}
	/**
	* Extends items configuration with default properties.
	*/
	extendItemsConfig() {
		const items = this.options.items;
		if (items) items.forEach((item) => {
			item.attributes = item.attributes || {};
			const ariaLabel = item.attributes["aria-label"];
			item.attributes["data-command"] = item.name.toLowerCase();
			item.attributes["aria-label"] = ariaLabel ?? item.name;
			item.type = "button";
			item.fillMode = "flat";
			item.overflow = "never";
		});
	}
	/**
	* Attaches event handlers to the toolbar.
	*/
	attachEvents() {
		this.toolbar.bind(CLICK, this.onClick.bind(this));
	}
	/**
	* Handles toolbar button clicks.
	*/
	onClick(e) {
		const message = e.target.closest("." + STYLES.message);
		const command = e.target.data("command");
		if (command) this.executeCommand(command, e.target, message);
	}
	/**
	* Executes a command from the toolbar.
	*/
	executeCommand(command, item, message) {
		this.trigger("execute", {
			type: command,
			item,
			message
		});
	}
	destroy() {
		if (this.toolbar) {
			this.toolbar.destroy();
			this.toolbar = null;
		}
		this.element.empty();
	}
};
//#endregion
//#region ../src/chat/collaborators/file-menu.collaborator.ts
/**
* FileMenu provides dropdown button functionality for file attachments.
* Handles actions like download, preview, and delete for individual files.
*
* This is an internal collaborator that extends Widget for event handling.
*/
var FileMenu = class FileMenu extends require_kendo_core.Widget {
	static {
		this.options = {
			name: "ChatFileMenu",
			items: []
		};
	}
	/**
	* Constructs a new FileMenu instance.
	*/
	constructor(element, options) {
		super(element, $.extend(true, {}, FileMenu.options, options));
		this.setCommandAttributes();
		this.createDropdownButton();
	}
	/**
	* Creates a dropdown button on the element.
	*/
	createDropdownButton() {
		this.dropdownButton = new kendo.ui.DropDownButton(this.element, {
			items: this.options.items,
			fillMode: "flat",
			icon: ICONS.attachmentMenu,
			size: "small"
		});
		this.dropdownButton.bind("click", this.onClick.bind(this)).bind("open", this.onOpen.bind(this)).bind("close", this.onClose.bind(this));
	}
	/**
	* Sets command attributes on menu items for identification.
	*/
	setCommandAttributes() {
		this.options.items.forEach((item) => {
			item.attributes = item.attributes || {};
			item.attributes["data-command"] = item.name.toLowerCase();
			item.id = item.name.toLowerCase();
		});
	}
	/**
	* Handles dropdown button item click.
	*/
	onClick(e) {
		const command = e.id;
		const file = $(e.sender.element).closest("." + STYLES.file);
		const message = $(e.sender.element).closest("." + STYLES.message);
		if (command) this.executeCommand(command, $(e.sender.element), file, message);
	}
	/**
	* Handles dropdown open event.
	*/
	onOpen(e) {
		let setActive = true;
		const target = $(e.sender.element);
		if (target.closest("." + STYLES.messageRemoved).length || target.find("." + STYLES.typingIndicator).length) {
			e.preventDefault();
			setActive = false;
		}
		this.setActive(target, setActive);
	}
	/**
	* Handles dropdown close event.
	*/
	onClose(e) {
		const target = $(e.sender.element);
		const message = target.closest("." + STYLES.message);
		this.setActive(target, false);
		this.trigger("close", { message });
	}
	/**
	* Executes a command from the file menu.
	*/
	executeCommand(command, item, file, message) {
		this.trigger("execute", {
			type: command,
			item,
			file,
			message
		});
	}
	/**
	* Sets the active state for a target element.
	*/
	setActive(target, active) {
		const bubble = target.closest("." + STYLES.bubble);
		if (active) bubble.addClass(STYLES.active);
		else bubble.removeClass(STYLES.active);
	}
	destroy() {
		if (this.dropdownButton) {
			this.dropdownButton.destroy();
			this.dropdownButton = null;
		}
		super.destroy();
	}
};
//#endregion
//#region ../src/chat/templates/common.template.ts
/**
* Renders an avatar element for a message author.
*/
const renderAvatar = (url, altText) => {
	return new kendo.ui.Avatar("<div>", {
		type: "image",
		image: url,
		alt: require_kendo_core.htmlService.encode(altText ?? "")
	}).wrapper[0].outerHTML;
};
/**
* Renders a typing indicator animation.
*/
const renderTypingIndicator = () => {
	return `<div class="${STYLES.typingIndicator}">
        <span></span>
        <span></span>
        <span></span>
    </div>`;
};
/**
* Renders a single file attachment item.
* Uses small-sized buttons for menu/download actions per Chat v3 spec.
*/
const renderFile = (file, closeButton, fileMenuButton = true) => {
	const closeButtonHtml = closeButton ? new kendo.ui.Button(`<button class="" ${REFERENCES.fileCloseButton}>`, {
		icon: ICONS.x,
		fillMode: "flat",
		size: "xsmall"
	}).wrapper[0].outerHTML : "";
	const fileMenuButtonHtml = fileMenuButton && !closeButton ? new kendo.ui.Button(`<button class="${STYLES.menuButton}" ${REFERENCES.fileMenuButton}>`, {
		icon: ICONS.attachmentMenu,
		fillMode: "flat",
		size: "xsmall"
	}).wrapper[0].outerHTML : "";
	return `<li class="${STYLES.file}" data-uid="${require_kendo_core.htmlService.encode(file.uid)}">
        ${kendo.ui.icon({
		icon: require_kendo_core.fileUtilsService.getFileGroup(file.extension, true),
		size: "xlarge"
	})}
        <div class="${STYLES.fileInfo}">
            <span class="${STYLES.fileName}">${require_kendo_core.htmlService.encode(file.name)}</span>
            <span class="${STYLES.fileSize}">${require_kendo_core.htmlService.encode(require_kendo_core.fileUtilsService.getFileSizeMessage(file.size))}</span>
        </div>
        ${closeButtonHtml}
        ${fileMenuButtonHtml}
    </li>`;
};
//#endregion
//#region ../src/chat/templates/file.template.ts
/**
* Renders a list of file attachments with the specified layout mode.
* @param files - Array of files to render
* @param downloadAll - Whether to show a "download all" button
* @param messages - Localization messages
* @param closeButton - Whether to show close buttons on files
* @param layoutMode - Layout mode: "horizontal" | "vertical" | "wrap"
*/
const renderFiles = (files, downloadAll, messages, closeButton, layoutMode = FILES_LAYOUT_MODE.VERTICAL) => {
	if (!files?.length) return "";
	let fileItems = "";
	files.forEach((file) => {
		fileItems += renderFile(file, closeButton ?? false, true);
	});
	let layoutClass = "";
	if (layoutMode === FILES_LAYOUT_MODE.VERTICAL) layoutClass = ` ${STYLES.filesVertical}`;
	else if (layoutMode === FILES_LAYOUT_MODE.WRAP) layoutClass = ` ${STYLES.filesWrap}`;
	let html = `<ul class="${STYLES.fileWrapper}${layoutClass}" ${REFERENCES.fileWrapper}>`;
	if (layoutMode === FILES_LAYOUT_MODE.HORIZONTAL) html += `<div class="${STYLES.filesScroll}">${fileItems}</div>`;
	else html += fileItems;
	html += "</ul>";
	if (downloadAll && files.length > 1 && messages?.downloadAll) html += `<div class="${STYLES.downloadButtonWrapper}">
            ${new kendo.ui.Button(`<button class="${STYLES.downloadButton}">${messages.downloadAll}</button>`, {
		icon: ICONS.download,
		fillMode: "flat",
		size: "small"
	}).wrapper[0].outerHTML}
        </div>`;
	return html;
};
/**
* Renders a message reference (for reply or pinned messages).
*/
const renderMessageReference = (context) => {
	const { text, files, isOwnMessage, isPinMessage, isDeleted, renderCloseButton, renderFileMenuButton, messages } = context;
	const messageReferenceSenderStyle = isOwnMessage ? STYLES.messageReferenceSender : STYLES.messageReferenceReceiver;
	const pinMessageReferenceStyle = isPinMessage ? STYLES.messagePinned : "";
	const closeButtonReference = isPinMessage ? REFERENCES.pinnedMessageCloseButton : REFERENCES.replyMessageCloseButton;
	const wrapperReference = isPinMessage ? REFERENCES.messageReferencePinWrapper : REFERENCES.messageReferenceReplyWrapper;
	let content = require_kendo_core.htmlService.convertTextUrlToLink(text || "");
	if (!content) content = files?.length ? renderFile(files[0], false, renderFileMenuButton ?? false) : "";
	if (isDeleted && messages) content = isOwnMessage ? require_kendo_core.htmlService.encode(messages.selfMessageDeleted) : require_kendo_core.htmlService.encode(messages.otherMessageDeleted);
	return `<div class="${STYLES.messageReference} ${messageReferenceSenderStyle} ${pinMessageReferenceStyle}" ${wrapperReference}>
        ${isPinMessage ? kendo.ui.icon({ icon: ICONS.pin }) : ""}
        <div class="${STYLES.messageReferenceContent}">${content}</div>
        <span class="${STYLES.spacer}"></span>
        ${renderCloseButton ? new kendo.ui.Button(`<button ${closeButtonReference}>`, {
		icon: ICONS.x,
		fillMode: "flat",
		size: "xsmall"
	}).wrapper[0].outerHTML : ""}
    </div>`;
};
//#endregion
//#region ../src/chat/templates/message.template.ts
/**
* Renders the retry button for failed messages.
*/
const renderRetryButton = (message, retryText) => {
	if (!message.failed || !message.isOwnMessage) return "";
	return kendo.html.renderButton(`<button class="${STYLES.messageRetryButton}" ${REFERENCES.retryButton} title="${retryText}" aria-label="${retryText}" data-uid="${message.uid || ""}"></button>`, {
		icon: ICONS.retry,
		size: "xsmall",
		fillMode: "clear"
	});
};
const renderFailedContent = (failedText) => {
	return `<span class="${STYLES.messageFailedContent}">
        ${kendo.ui.icon({
		icon: ICONS.warningTriangle,
		size: "xsmall"
	})}
        <span class="${STYLES.messageFailedText}">${require_kendo_core.htmlService.encode(failedText)}</span>
    </span>`;
};
/**
* Renders the message status indicator.
* Supports custom status settings with icon, text, and cssClass for enhanced display.
*/
const renderMessageStatus = (status, message, statusTemplate, statusSettings) => {
	if (!status) return "";
	if (statusTemplate) return statusTemplate({
		status,
		message
	});
	const settings = statusSettings?.[status];
	const statusClass = `${STYLES.messageStatus}${settings?.cssClass ? " " + settings.cssClass : ""}`;
	let iconHtml = "";
	if (settings?.icon) iconHtml = kendo.ui.icon({ icon: settings.icon });
	const statusText = settings?.text !== void 0 ? settings.text : status.charAt(0).toUpperCase() + status.slice(1);
	return `<span class="${statusClass}">${iconHtml}${require_kendo_core.htmlService.encode(statusText)}</span>`;
};
/**
* Renders a single attachment card.
*/
const renderAttachment = (attachment, message, attachmentIndex, attachmentTemplate) => {
	if (attachmentTemplate) return attachmentTemplate({
		attachment,
		message
	});
	const title = attachment.title ? `<div class="${STYLES.cardTitle}">${require_kendo_core.htmlService.encode(attachment.title)}</div>` : "";
	const subtitle = attachment.subtitle ? `<div class="${STYLES.cardSubtitle}">${require_kendo_core.htmlService.encode(attachment.subtitle)}</div>` : "";
	const image = attachment.thumbnailUrl ? `<div class="${STYLES.cardMedia}"><img src="${require_kendo_core.htmlService.encode(attachment.thumbnailUrl)}" alt="" /></div>` : "";
	const actions = attachment.actions?.length ? `<div class="${STYLES.cardActions}">${attachment.actions.map((action, actionIndex) => {
		const label = action.title || action.text;
		return `<button class="${STYLES.cardAction} ${STYLES.button}" ${REFERENCES.attachmentActionButton} data-attachment-index="${attachmentIndex}" data-action-index="${actionIndex}">${require_kendo_core.htmlService.encode(label)}</button>`;
	}).join("")}</div>` : "";
	return `<div class="${STYLES.card}">
        ${image}
        <div class="${STYLES.cardBody}">
            ${title}
            ${subtitle}
        </div>
        ${actions}
    </div>`;
};
/**
* Renders attachments with the specified layout.
*/
const renderAttachments = (attachments, message, layout = "list", attachmentTemplate) => {
	if (!attachments?.length) return "";
	const attachmentsHtml = attachments.map((attachment, attachmentIndex) => renderAttachment(attachment, message, attachmentIndex, attachmentTemplate)).join("");
	if (layout === "carousel") return `<div class="${STYLES.cardDeckScrollWrap}">
            ${new kendo.ui.Button(`<button ${REFERENCES.leftScrollButton}>`, { icon: ICONS.chevronLeft }).wrapper[0].outerHTML}
            <div class="${STYLES.cardDeck}">${attachmentsHtml}</div>
            ${new kendo.ui.Button(`<button ${REFERENCES.rightScrollButton}>`, { icon: ICONS.chevronRight }).wrapper[0].outerHTML}
        </div>`;
	return `<div class="${STYLES.cardList}">${attachmentsHtml}</div>`;
};
/**
* Renders a single chat message.
*/
const renderMessage = (message, replyMessage, downloadAll, messages, expandable, messageTimeFormat, skipSanitization, statusTemplate, filesLayoutMode = FILES_LAYOUT_MODE.VERTICAL, filesTemplate, contentTemplate, attachmentTemplate, attachmentLayout, timestampVisibility = "onFocus", statusSettings, showMessageTime = true) => {
	const isDeleted = message.isDeleted;
	const isFailedStatus = message.status === MESSAGE_STATUS.FAILED;
	const isFailed = message.isOwnMessage && (message.failed || isFailedStatus);
	const messageStatus = !message.isOwnMessage && isFailedStatus ? void 0 : message.status;
	const expandableClasses = expandable && !message.isTyping ? [STYLES.bubbleExpandable, STYLES.expanded] : [];
	const replyMessageHtml = replyMessage ? renderMessageReference({
		text: replyMessage.text,
		files: replyMessage.files,
		isOwnMessage: replyMessage.isOwnMessage,
		isPinMessage: false,
		renderCloseButton: false,
		renderFileMenuButton: false
	}) : "";
	let messageContent = "";
	if (message.isTyping && !message.isOwnMessage) messageContent = renderTypingIndicator();
	else if (contentTemplate && !isDeleted) messageContent = contentTemplate(message);
	else if (isDeleted) messageContent = message.isOwnMessage ? require_kendo_core.htmlService.encode(messages.selfMessageDeleted) : require_kendo_core.htmlService.encode(messages.otherMessageDeleted);
	else messageContent = require_kendo_core.htmlService.convertTextUrlToLink(message.text || "", skipSanitization);
	const timeAttributes = timestampVisibility === "onFocus" ? ` data-timestamp-visibility="${timestampVisibility}"` : "";
	const timeClasses = [STYLES.messageTime, timestampVisibility === "onFocus" ? STYLES.hidden : ""].filter(Boolean).join(" ");
	const timeHtml = showMessageTime ? `<time class="${timeClasses}"${timeAttributes}>${require_kendo_core.formatterService.toString(require_kendo_core.dateParserService.parseDate(message.timestamp), messageTimeFormat)}</time>` : "";
	const statusHtml = renderMessageStatus(messageStatus, message, statusTemplate, statusSettings || null);
	const retryHtml = isFailed ? renderRetryButton(message, messages.retryMessage || "Retry") : "";
	const failedContentHtml = isFailed ? renderFailedContent(statusSettings?.[MESSAGE_STATUS.FAILED]?.text || "Failed to send") : "";
	const messageInfoHtml = `<div class="${STYLES.messageInfo}">${statusHtml}${timeHtml}${failedContentHtml}</div>`;
	let filesHtml = "";
	if (message.files?.length && !isDeleted) filesHtml = filesTemplate ? filesTemplate(message.files, downloadAll, messages, false, filesLayoutMode) : renderFiles(message.files, downloadAll, messages, false, filesLayoutMode);
	return `<div class="${STYLES.message}${isDeleted ? " " + STYLES.messageRemoved : ""}${isFailed ? " " + STYLES.messageFailed : ""}" data-uid="${require_kendo_core.htmlService.encode(message.uid ?? "")}">
        ${retryHtml}
        <div class="${STYLES.chatBubble} ${STYLES.bubble} ${expandableClasses.join(" ")}">
            <div class="${STYLES.bubbleContent}">
                ${replyMessageHtml}
                <span class="${STYLES.chatBubbleText}">${messageContent}</span>
                ${filesHtml}
            </div>
            ${expandable && !message.isTyping ? `<span class="${STYLES.bubbleExpandableIndicator}" ${REFERENCES.bubbleExpandableIndicator}>${kendo.ui.icon({ icon: ICONS.chevronUp })}</span>` : ""}
        </div>
        ${messageInfoHtml}
        ${!isDeleted ? `<div class="${STYLES.messageToolbar}"></div>` : ""}
    </div>`;
};
/**
* Renders a message group with author avatar and multiple messages.
*/
const renderMessageGroup = (context) => {
	const { message, author, isOwnMessage, replyMessage, downloadAll = true, messages, expandable = false, fullWidth = false, messageTimeFormat = "ddd MMM dd yyyy", timestampTemplate, statusTemplate, showTimestamp = false, messageTemplate, filesTemplate, skipSanitization = false, messageSettings, filesLayoutMode = FILES_LAYOUT_MODE.VERTICAL, contentTemplate, attachmentTemplate, attachmentLayout, userStatusTemplate, timestampVisibility = "onFocus", statusSettings, showMessageTime = true } = context;
	const effectiveShowAvatar = messageSettings?.showAvatar !== void 0 ? messageSettings.showAvatar && author && author.imageUrl : author && author.imageUrl;
	const effectiveShowUsername = messageSettings?.showUsername !== void 0 ? messageSettings.showUsername : true;
	const effectiveExpandable = messageSettings?.allowMessageCollapse !== void 0 ? messageSettings.allowMessageCollapse : expandable;
	const effectiveFullWidth = messageSettings?.messageWidthMode === "full" || fullWidth;
	const groupClasses = [
		STYLES.messageGroup,
		isOwnMessage ? STYLES.messageGroupSender : STYLES.messageGroupReceiver,
		effectiveShowAvatar ? "" : STYLES.noAvatar,
		effectiveFullWidth ? STYLES.messageGroupFullWidth : ""
	].filter(Boolean).join(" ");
	let timestampContent = "";
	if (showTimestamp && message.timestamp) {
		const messageDate = require_kendo_core.dateParserService.parseDate(message.timestamp);
		if (require_kendo_core.utilsService.isFunction(timestampTemplate)) timestampContent = timestampTemplate({
			date: messageDate,
			message
		});
		else {
			const relativeDateText = require_kendo_core.dateUtilsService.getRelativeDateString(messageDate);
			timestampContent = `<div class="${STYLES.timestamp}">${relativeDateText}</div>`;
		}
	}
	const userStatusHtml = effectiveShowAvatar && userStatusTemplate ? userStatusTemplate({ message: {
		...message,
		author
	} }) : "";
	const effectiveAttachmentLayout = message.attachmentLayout || attachmentLayout || "list";
	const attachmentsHtml = !message.isDeleted ? renderAttachments(message.attachments, message, effectiveAttachmentLayout, attachmentTemplate) : "";
	let messageHtml;
	if (messageTemplate) messageHtml = messageTemplate({
		...message,
		isOwnMessage,
		author
	}, replyMessage ?? null, downloadAll, messages, effectiveExpandable, messageTimeFormat, skipSanitization, statusTemplate);
	else messageHtml = renderMessage({
		...message,
		isOwnMessage,
		author
	}, replyMessage ?? null, downloadAll, messages, effectiveExpandable, messageTimeFormat, skipSanitization, statusTemplate, filesLayoutMode, filesTemplate, contentTemplate, attachmentTemplate, attachmentLayout, timestampVisibility, statusSettings, showMessageTime);
	return `${showTimestamp && timestampContent ? timestampContent : ""}
        <div class="${groupClasses}">${userStatusHtml ? `<div ${REFERENCES.userStatusWrapper}>${userStatusHtml}</div>` : ""}
            ${effectiveShowAvatar ? renderAvatar(author.imageUrl, author.imageAltText) : ""}
            <div class="${STYLES.messageGroupContent}">
                ${effectiveShowUsername ? `<span class="${STYLES.messageAuthor}">${require_kendo_core.htmlService.encode(author.name ?? "")}</span>` : ""}
                ${messageHtml}
            </div>
        </div>
        ${attachmentsHtml}`;
};
//#endregion
//#region ../src/chat/templates/suggestions.template.ts
/**
* Renders a list of suggestions/suggested actions with the specified layout mode.
* @param suggestions - Array of suggestions to render
* @param layoutMode - Layout mode: "scroll" | "wrap" | "scrollbuttons"
* @param isRtl - Whether RTL direction is used
*/
const renderSuggestions = (suggestions) => {
	if (!suggestions?.length) return "";
	const suggestionItems = suggestions.map((suggestion) => `<span class="${STYLES.suggestion}">${require_kendo_core.htmlService.encode(suggestion.text)}</span>`).join("");
	return `<div class="${STYLES.suggestionGroup}" ${REFERENCES.suggestionGroup}>${suggestionItems}</div>`;
};
/**
* Renders suggestions with the specified layout mode from an array of suggestions.
* @param suggestions - Array of suggestions to render
* @param layoutMode - Layout mode: "scroll" | "wrap" | "scrollbuttons"
* @param isRtl - Whether RTL direction is used
*/
const renderSuggestionsWithLayout = (suggestions, layoutMode = SUGGESTED_ACTIONS_LAYOUT_MODE.SCROLL, isRtl = false) => {
	if (!suggestions?.length) return "";
	const suggestionItems = suggestions.map((suggestion) => `<span class="${STYLES.suggestion}">${require_kendo_core.htmlService.encode(suggestion.text)}</span>`).join("");
	return wrapSuggestionsWithLayout(suggestionItems, layoutMode, isRtl);
};
/**
* Wraps pre-rendered suggestion content with the specified layout mode.
* Use this when you have custom-rendered content from a template.
* @param content - Pre-rendered HTML content (suggestion items)
* @param layoutMode - Layout mode: "scroll" | "wrap" | "scrollbuttons"
* @param isRtl - Whether RTL direction is used
*/
const wrapSuggestionsWithLayout = (content, layoutMode = SUGGESTED_ACTIONS_LAYOUT_MODE.SCROLL, isRtl = false) => {
	if (!content) return "";
	if (layoutMode === SUGGESTED_ACTIONS_LAYOUT_MODE.WRAP) return `<div class="${STYLES.suggestionGroup}" ${REFERENCES.suggestionGroup}>${content}</div>`;
	const layoutClass = layoutMode === SUGGESTED_ACTIONS_LAYOUT_MODE.SCROLL ? STYLES.suggestionGroupScrollable : "";
	const groupClass = layoutClass ? `${STYLES.suggestionGroup} ${layoutClass}` : STYLES.suggestionGroup;
	const scrollContent = `<div class="${STYLES.suggestionsScroll}">${content}</div>`;
	const suggestionsElement = `<div class="${groupClass}" ${REFERENCES.suggestionGroup}>${scrollContent}</div>`;
	if (layoutMode === SUGGESTED_ACTIONS_LAYOUT_MODE.SCROLLBUTTONS) return renderScrollableSuggestions(suggestionsElement, isRtl);
	return suggestionsElement;
};
/**
* Renders scrollable suggestions with navigation buttons and gradient styling.
*/
const renderScrollableSuggestions = (suggestionsElement, isRtl) => {
	const leftIcon = isRtl ? ICONS.chevronRight : ICONS.chevronLeft;
	const rightIcon = isRtl ? ICONS.chevronLeft : ICONS.chevronRight;
	return `<div class="${STYLES.suggestionScrollWrap} ${STYLES.suggestionScrollWrapGradient}">
        ${new kendo.ui.Button(`<button ${REFERENCES.leftScrollButton}>`, { icon: leftIcon }).wrapper[0].outerHTML}
        ${suggestionsElement}
        ${new kendo.ui.Button(`<button ${REFERENCES.rightScrollButton}>`, { icon: rightIcon }).wrapper[0].outerHTML}
    </div>`;
};
/**
* Renders the chat header using Toolbar.
* @param items - Array of toolbar items configuration
* @param headerTemplate - Optional custom header template that overrides items
*/
const renderHeader = (items, headerTemplate) => {
	if (headerTemplate) return `<div class="${STYLES.header}">${headerTemplate()}</div>`;
	if (!items || items.length === 0) return "";
	const toolbarElement = $(`<div class="${STYLES.header}">`);
	new kendo.ui.ToolBar(toolbarElement, { items });
	return toolbarElement[0].outerHTML;
};
//#endregion
//#region ../src/chat/collaborators/chat-view/message-renderer.ts
var MessageRenderer = class {
	constructor(context) {
		this.context = context;
	}
	renderMessage(message) {
		const options = this.context.options;
		const componentMessages = options.messages;
		const author = {
			id: message.authorId,
			name: message.authorName,
			imageUrl: message.authorImageUrl,
			imageAltText: message.authorImageAltText
		};
		const isOwnMessage = String(author.id) === String(options.authorId);
		const replyMessage = this.context.getReplyMessage(message);
		const messageTimeFormat = options.messageTimeFormat;
		const skipSanitization = options.skipSanitization;
		const statusTemplate = options.messageStatusTemplate;
		const userSettings = isOwnMessage ? options.authorMessageSettings : options.receiverMessageSettings;
		const messageSettings = this._mergeMessageSettings(options, userSettings);
		const expandable = messageSettings.allowMessageCollapse ?? options.allowMessageCollapse;
		const fullWidth = messageSettings.messageWidthMode === MESSAGE_WIDTH_MODE.FULL;
		if (this._handleExistingMessageUpdate({
			message,
			author,
			isOwnMessage,
			replyMessage,
			componentMessages,
			expandable,
			messageTimeFormat,
			skipSanitization,
			statusTemplate,
			messageSettings
		})) return;
		const targetGroupElement = this._findTargetMessageGroup();
		if (this._canGroupWithLastMessage(targetGroupElement, author.id) && targetGroupElement.length) this._addMessageToExistingGroup({
			message,
			author,
			isOwnMessage,
			replyMessage,
			componentMessages,
			expandable,
			targetGroupElement,
			messageTimeFormat,
			skipSanitization,
			statusTemplate,
			messageSettings
		});
		else {
			const showTimestamp = this._shouldShowTimestamp(message);
			this._createNewMessageGroup({
				message,
				author,
				isOwnMessage,
				replyMessage,
				componentMessages,
				expandable,
				fullWidth,
				messageTimeFormat,
				showTimestamp,
				skipSanitization,
				statusTemplate,
				messageSettings
			});
		}
	}
	_mergeMessageSettings(options, userSettings) {
		const merged = {
			showAvatar: options.showAvatar,
			showUsername: options.showUsername,
			allowMessageCollapse: options.allowMessageCollapse,
			messageWidthMode: options.messageWidthMode,
			messageToolbarActions: options.messageToolbarActions,
			messageActions: options.messageActions,
			messageTemplate: options.messageTemplate,
			messageContentTemplate: options.messageContentTemplate
		};
		if (userSettings) {
			if (userSettings.showAvatar !== void 0) merged.showAvatar = userSettings.showAvatar;
			if (userSettings.showUsername !== void 0) merged.showUsername = userSettings.showUsername;
			if (userSettings.allowMessageCollapse !== void 0) merged.allowMessageCollapse = userSettings.allowMessageCollapse;
			if (userSettings.messageWidthMode !== void 0) merged.messageWidthMode = userSettings.messageWidthMode;
			if (userSettings.enableFileActions !== void 0) merged.enableFileActions = userSettings.enableFileActions;
			if (userSettings.enableContextMenuActions !== void 0) merged.enableContextMenuActions = userSettings.enableContextMenuActions;
			if (userSettings.messageToolbarActions !== void 0) merged.messageToolbarActions = userSettings.messageToolbarActions;
			if (userSettings.messageActions !== void 0) merged.messageActions = userSettings.messageActions;
			if (userSettings.messageTemplate !== void 0) merged.messageTemplate = userSettings.messageTemplate;
			if (userSettings.messageContentTemplate !== void 0) merged.messageContentTemplate = userSettings.messageContentTemplate;
		}
		return merged;
	}
	_getMessageTimeVisibility() {
		return this.context.options.timestampVisibility;
	}
	_shouldShowTimestamp(message) {
		if (!message.timestamp) return false;
		const lastNonTypingMessage = this.context.list.find("." + STYLES.message).filter(function() {
			return $(this).find("." + STYLES.typingIndicator).length === 0;
		}).last();
		if (!lastNonTypingMessage.length) return true;
		const lastMessageData = this.context.dataItem(lastNonTypingMessage);
		if (!lastMessageData || !lastMessageData.timestamp) return true;
		const currentMessageDate = require_kendo_core.dateParserService.parseDate(message.timestamp);
		const lastMessageDate = require_kendo_core.dateParserService.parseDate(lastMessageData.timestamp);
		if (!currentMessageDate || !lastMessageDate) return false;
		const currentDay = new Date(currentMessageDate);
		currentDay.setHours(0, 0, 0, 0);
		const lastDay = new Date(lastMessageDate);
		lastDay.setHours(0, 0, 0, 0);
		return currentDay.getTime() !== lastDay.getTime();
	}
	_shouldRenderUserStatus(author, messageSettings) {
		return messageSettings?.showAvatar !== void 0 ? !!(messageSettings.showAvatar && author?.imageUrl) : !!author?.imageUrl;
	}
	_syncUserStatus(targetGroupElement, message, author, messageSettings) {
		const userStatusWrapper = targetGroupElement.find(`[${REFERENCES.userStatusWrapper}]`);
		const userStatusTemplate = this.context.options.userStatusTemplate;
		if (!userStatusTemplate || !this._shouldRenderUserStatus(author, messageSettings)) {
			userStatusWrapper.remove();
			return;
		}
		const userStatusElement = $(`<div ${REFERENCES.userStatusWrapper}>${userStatusTemplate({ message: {
			...message,
			author
		} })}</div>`);
		userStatusWrapper.remove();
		targetGroupElement.prepend(userStatusElement);
	}
	_isLastMessageInGroup(messageElement) {
		return messageElement.closest("." + STYLES.messageGroup).find("." + STYLES.message).filter(function() {
			return $(this).find("." + STYLES.typingIndicator).length === 0;
		}).last().is(messageElement);
	}
	_handleExistingMessageUpdate(context) {
		const { message, author, isOwnMessage, replyMessage, componentMessages, expandable, messageTimeFormat, skipSanitization, statusTemplate, messageSettings } = context;
		const options = this.context.options;
		const existingMessageElement = this.context.list.find("." + STYLES.message + `[data-uid="${require_kendo_core.htmlService.encode(message.uid ?? "")}"]`);
		const messageTimeVisibility = this._getMessageTimeVisibility();
		const messageTemplate = messageSettings?.messageTemplate || null;
		const contentTemplate = messageSettings?.messageContentTemplate || null;
		const filesTemplate = options.filesTemplate;
		if (existingMessageElement.length) {
			const isLastMessageInGroup = this._isLastMessageInGroup(existingMessageElement);
			let messageHtml;
			if (messageTemplate) messageHtml = messageTemplate({
				...message,
				isOwnMessage,
				author
			}, replyMessage, true, componentMessages, messageSettings?.allowMessageCollapse ?? expandable, messageTimeFormat, skipSanitization, statusTemplate);
			else messageHtml = renderMessage({
				...message,
				isOwnMessage,
				author
			}, replyMessage, true, componentMessages, messageSettings?.allowMessageCollapse ?? expandable, messageTimeFormat, skipSanitization, statusTemplate, options.filesLayoutMode, filesTemplate, contentTemplate, options.attachmentTemplate, options.attachmentLayout, messageTimeVisibility, options.messageStatusSettings, messageTimeVisibility !== "hidden");
			const updatedMessageElement = $(messageHtml);
			updatedMessageElement.data("messageData", message);
			existingMessageElement.replaceWith(updatedMessageElement);
			this.context.initMessageToolbar(updatedMessageElement.find("." + STYLES.messageToolbar), messageSettings?.messageToolbarActions);
			this.context.initFileMenus(updatedMessageElement, messageSettings?.enableFileActions);
			if (isLastMessageInGroup) this._syncUserStatus(updatedMessageElement.closest("." + STYLES.messageGroup), message, author, messageSettings);
			return true;
		}
		return false;
	}
	_findTargetMessageGroup() {
		let lastGroupElement = this.context.list.children("." + STYLES.messageGroup).last();
		let targetGroupElement = lastGroupElement;
		const messagesInLastGroup = lastGroupElement.find("." + STYLES.message);
		if (messagesInLastGroup.length === 1) {
			if (messagesInLastGroup.first().find("." + STYLES.typingIndicator).length > 0) targetGroupElement = lastGroupElement.prev("." + STYLES.messageGroup);
		}
		return targetGroupElement;
	}
	_canGroupWithLastMessage(targetGroupElement, authorId) {
		const lastMessageInTargetGroup = targetGroupElement.find("." + STYLES.message).filter(function() {
			return $(this).find("." + STYLES.typingIndicator).length === 0;
		}).last();
		const lastMessageData = this.context.dataItem(lastMessageInTargetGroup);
		return String(lastMessageData?.authorId) === String(authorId);
	}
	_addMessageToExistingGroup(context) {
		const { message, author, isOwnMessage, replyMessage, componentMessages, expandable, targetGroupElement, messageTimeFormat, skipSanitization, statusTemplate, messageSettings } = context;
		const options = this.context.options;
		const messageGroupContent = targetGroupElement.find("." + STYLES.messageGroupContent);
		const messageTimeVisibility = this._getMessageTimeVisibility();
		const messageTemplate = messageSettings?.messageTemplate || null;
		const contentTemplate = messageSettings?.messageContentTemplate || null;
		const filesTemplate = options.filesTemplate;
		let messageHtml;
		if (messageTemplate) messageHtml = messageTemplate({
			...message,
			isOwnMessage,
			author
		}, replyMessage, true, componentMessages, messageSettings?.allowMessageCollapse ?? expandable, messageTimeFormat, skipSanitization, statusTemplate);
		else messageHtml = renderMessage({
			...message,
			isOwnMessage,
			author
		}, replyMessage, true, componentMessages, messageSettings?.allowMessageCollapse ?? expandable, messageTimeFormat, skipSanitization, statusTemplate, options.filesLayoutMode, filesTemplate, contentTemplate, options.attachmentTemplate, options.attachmentLayout, messageTimeVisibility, options.messageStatusSettings, messageTimeVisibility !== "hidden");
		const messageElement = $(messageHtml);
		messageElement.data("messageData", message);
		messageGroupContent.append(messageElement);
		this.context.initMessageToolbar(messageElement.find("." + STYLES.messageToolbar), messageSettings?.messageToolbarActions);
		this.context.initFileMenus(messageElement, messageSettings?.enableFileActions);
		if (!message.isDeleted) {
			const effectiveAttachmentLayout = message.attachmentLayout || options.attachmentLayout || "list";
			const attachmentsHtml = renderAttachments(message.attachments, message, effectiveAttachmentLayout, options.attachmentTemplate);
			if (attachmentsHtml) targetGroupElement.after($(attachmentsHtml));
		}
		this._syncUserStatus(targetGroupElement, message, author, messageSettings);
		this._moveTypingIndicatorsToEnd();
	}
	_createNewMessageGroup(context) {
		const { message, author, isOwnMessage, replyMessage, componentMessages, expandable, fullWidth, messageTimeFormat, showTimestamp, skipSanitization, statusTemplate, messageSettings } = context;
		const options = this.context.options;
		const messageGroupTemplate = options.messageGroupTemplate;
		const timestampTemplate = options.timestampTemplate;
		const messageTemplate = messageSettings?.messageTemplate || null;
		const contentTemplate = messageSettings?.messageContentTemplate || null;
		const groupElement = $(messageGroupTemplate({
			message,
			author,
			isOwnMessage,
			replyMessage,
			downloadAll: true,
			messages: componentMessages,
			expandable,
			fullWidth,
			messageTimeFormat,
			timestampTemplate,
			statusTemplate,
			showTimestamp,
			messageTemplate,
			contentTemplate,
			filesTemplate: options.filesTemplate,
			skipSanitization,
			messageSettings,
			filesLayoutMode: options.filesLayoutMode,
			attachmentTemplate: options.attachmentTemplate,
			attachmentLayout: options.attachmentLayout,
			userStatusTemplate: options.userStatusTemplate,
			timestampVisibility: this._getMessageTimeVisibility(),
			statusSettings: options.messageStatusSettings,
			showMessageTime: this._getMessageTimeVisibility() !== "hidden"
		}));
		groupElement.find("." + STYLES.message).first().data("messageData", message);
		this.context.list.append(groupElement);
		this.context.initMessageToolbar(groupElement.find("." + STYLES.messageToolbar), messageSettings?.messageToolbarActions);
		this.context.initFileMenus(groupElement, messageSettings?.enableFileActions);
		this._moveTypingIndicatorsToEnd();
		return groupElement;
	}
	_moveTypingIndicatorsToEnd() {
		const renderer = this;
		const ctx = this.context;
		const options = ctx.options;
		const messageGroupTemplate = options.messageGroupTemplate;
		ctx.list.find("." + STYLES.message).filter(function() {
			return $(this).find("." + STYLES.typingIndicator).length > 0;
		}).each(function() {
			const typingMessage = $(this);
			const messageGroup = typingMessage.closest("." + STYLES.messageGroup);
			if (messageGroup.find("." + STYLES.message).length === 1) messageGroup.detach().appendTo(ctx.list);
			else {
				typingMessage.remove();
				const message = ctx.dataItem(typingMessage);
				if (!message) return;
				const author = {
					id: message.authorId,
					name: message.authorName,
					imageUrl: message.authorImageUrl,
					imageAltText: message.authorImageAltText
				};
				const isOwnMessage = String(author.id) === String(options.authorId);
				const userSettings = isOwnMessage ? options.authorMessageSettings : options.receiverMessageSettings;
				const messageSettings = renderer._mergeMessageSettings(options, userSettings);
				const typingGroupElement = $(messageGroupTemplate({
					message,
					author,
					isOwnMessage,
					downloadAll: true,
					messages: options.messages,
					expandable: messageSettings.allowMessageCollapse ?? options.allowMessageCollapse,
					fullWidth: messageSettings.messageWidthMode === MESSAGE_WIDTH_MODE.FULL,
					messageTimeFormat: options.messageTimeFormat,
					statusTemplate: options.messageStatusTemplate,
					showTimestamp: false,
					timestampTemplate: options.timestampTemplate,
					messageTemplate: messageSettings.messageTemplate || null,
					contentTemplate: messageSettings.messageContentTemplate || null,
					filesTemplate: options.filesTemplate,
					skipSanitization: options.skipSanitization,
					messageSettings,
					filesLayoutMode: options.filesLayoutMode,
					attachmentTemplate: options.attachmentTemplate,
					attachmentLayout: options.attachmentLayout,
					userStatusTemplate: options.userStatusTemplate,
					timestampVisibility: renderer._getMessageTimeVisibility(),
					statusSettings: options.messageStatusSettings,
					showMessageTime: renderer._getMessageTimeVisibility() !== "hidden"
				}));
				ctx.list.append(typingGroupElement);
			}
		});
	}
	prependMessages(messages) {
		if (!messages.length) return;
		const options = this.context.options;
		const componentMessages = options.messages;
		const messageGroupTemplate = options.messageGroupTemplate;
		const timestampTemplate = options.timestampTemplate;
		let currentGroupElement = null;
		let currentAuthorId = null;
		const fragment = document.createDocumentFragment();
		const $fragment = $(fragment);
		for (let i = 0; i < messages.length; i++) {
			const message = messages[i];
			const author = {
				id: message.authorId,
				name: message.authorName,
				imageUrl: message.authorImageUrl,
				imageAltText: message.authorImageAltText
			};
			const isOwnMessage = String(author.id) === String(options.authorId);
			const replyMessage = this.context.getReplyMessage(message);
			const userSettings = isOwnMessage ? options.authorMessageSettings : options.receiverMessageSettings;
			const messageSettings = this._mergeMessageSettings(options, userSettings);
			const statusTemplate = options.messageStatusTemplate;
			if (String(author.id) === currentAuthorId && currentGroupElement) {
				const messageGroupContent = currentGroupElement.find("." + STYLES.messageGroupContent);
				const messageTemplate = messageSettings.messageTemplate || null;
				const contentTemplate = messageSettings.messageContentTemplate || null;
				let messageHtml;
				if (messageTemplate) messageHtml = messageTemplate({
					...message,
					isOwnMessage,
					author
				}, replyMessage, true, componentMessages, messageSettings.allowMessageCollapse ?? options.allowMessageCollapse, options.messageTimeFormat, options.skipSanitization, statusTemplate);
				else messageHtml = renderMessage({
					...message,
					isOwnMessage,
					author
				}, replyMessage, true, componentMessages, messageSettings.allowMessageCollapse ?? options.allowMessageCollapse, options.messageTimeFormat, options.skipSanitization, statusTemplate, options.filesLayoutMode, options.filesTemplate, contentTemplate, options.attachmentTemplate, options.attachmentLayout, options.timestampVisibility, options.messageStatusSettings, options.timestampVisibility !== "hidden");
				const messageElement = $(messageHtml);
				messageGroupContent.append(messageElement);
				this.context.initMessageToolbar(messageElement.find("." + STYLES.messageToolbar), messageSettings?.messageToolbarActions);
				this.context.initFileMenus(messageElement, messageSettings?.enableFileActions);
			} else {
				const showTimestamp = i === 0 && message.timestamp;
				const messageTemplate = messageSettings.messageTemplate || null;
				const contentTemplate = messageSettings.messageContentTemplate || null;
				currentGroupElement = $(messageGroupTemplate({
					message,
					author,
					isOwnMessage,
					replyMessage,
					downloadAll: true,
					messages: componentMessages,
					expandable: messageSettings.allowMessageCollapse ?? options.allowMessageCollapse,
					fullWidth: messageSettings.messageWidthMode === MESSAGE_WIDTH_MODE.FULL,
					messageTimeFormat: options.messageTimeFormat,
					timestampTemplate,
					statusTemplate,
					showTimestamp: !!showTimestamp,
					messageTemplate,
					skipSanitization: options.skipSanitization,
					messageSettings,
					filesLayoutMode: options.filesLayoutMode,
					contentTemplate,
					attachmentTemplate: options.attachmentTemplate,
					attachmentLayout: options.attachmentLayout,
					userStatusTemplate: options.userStatusTemplate,
					timestampVisibility: options.timestampVisibility,
					statusSettings: options.messageStatusSettings,
					showMessageTime: options.timestampVisibility !== "hidden"
				}));
				$fragment.append(currentGroupElement);
				this.context.initMessageToolbar(currentGroupElement.find("." + STYLES.messageToolbar), messageSettings?.messageToolbarActions);
				this.context.initFileMenus(currentGroupElement, messageSettings?.enableFileActions);
				currentAuthorId = String(author.id);
			}
		}
		const firstExistingGroup = this.context.list.children("." + STYLES.messageGroup).first();
		if (currentGroupElement && firstExistingGroup.length) {
			const lastPrependedAuthorId = currentAuthorId;
			const firstExistingMessage = firstExistingGroup.find("." + STYLES.message).first();
			const firstExistingData = this.context.dataItem(firstExistingMessage);
			if (firstExistingData && String(firstExistingData.authorId) === lastPrependedAuthorId) {
				const existingContent = firstExistingGroup.find("." + STYLES.messageGroupContent);
				const messagesToMove = currentGroupElement.find("." + STYLES.messageGroupContent).children();
				existingContent.prepend(messagesToMove);
				currentGroupElement.remove();
			}
		}
		this.context.list.prepend(fragment);
	}
};
//#endregion
//#region ../src/chat/collaborators/chat-view/scroll-manager.ts
var ScrollManager = class {
	constructor(context) {
		this.scrollToBottomButton = null;
		this.separator = null;
		this._scrollHandler = null;
		this._clickHandler = null;
		this._olderLoadDebounceTimeout = null;
		this._newerLoadDebounceTimeout = null;
		this._isLoadingOlder = false;
		this._allOlderLoaded = false;
		this._isLoadingNewer = false;
		this._allNewerLoaded = true;
		this._scrolledAwayFromTop = false;
		this._scrolledAwayFromBottom = false;
		this._lastObservedClientHeight = 0;
		this._resizeObserverService = null;
		this.context = context;
		this._initScrollToBottomButton();
		this._initSeparator();
		this._attachEvents();
	}
	_initScrollToBottomButton() {
		if (this.context.options.scrollToBottomButton === false) {
			this.scrollToBottomButton = null;
			return;
		}
		const buttonHtml = kendo.html.renderButton(`<button class="${STYLES.scrollFab} ${STYLES.hidden}" ${REFERENCES.scrollToBottomButton} aria-label="Scroll to bottom"></button>`, {
			icon: ICONS.arrowDown,
			rounded: "full",
			themeColor: "base"
		});
		this.scrollToBottomButton = $(buttonHtml).appendTo(this.context.element);
		const cssStyles = {
			position: "sticky",
			bottom: "16px",
			transform: "translateX(-50%)",
			zIndex: 1
		};
		const isContextElementRtl = this.context.element.css("direction") === "rtl";
		cssStyles[isContextElementRtl ? "right" : "left"] = "50%";
		this.scrollToBottomButton.css(cssStyles);
	}
	_attachEvents() {
		this._scrollHandler = this._onScroll.bind(this);
		this._clickHandler = this._scrollToBottomButtonClick.bind(this);
		this.context.element.on("scroll.kendoChat.scrollManager", this._scrollHandler).on("click.kendoChat.scrollManager", `[${REFERENCES.scrollToBottomButton}]`, this._clickHandler);
		const containerElement = this.context.element[0];
		if (containerElement) this._resizeObserverService = require_kendo_core.domUtilsService.createResizeObserver({
			element: containerElement,
			onResize: () => this._onContainerResize(),
			debounceTime: 1
		});
	}
	_onContainerResize() {
		const el = this.context.element[0];
		if (!el) return;
		const previousClientHeight = this._lastObservedClientHeight;
		const newClientHeight = el.clientHeight;
		this._lastObservedClientHeight = newClientHeight;
		if (previousClientHeight === 0 || newClientHeight === previousClientHeight) return;
		if (el.scrollHeight - el.scrollTop - newClientHeight + (newClientHeight - previousClientHeight) <= 100) this.scrollToBottom();
	}
	_initSeparator() {
		this.separator = $(`<div class="${STYLES.messageBoxSeparator} ${STYLES.hidden}"></div>`);
		this.context.element.after(this.separator);
	}
	updateSeparator() {
		if (!this.separator) return;
		const el = this.context.element[0];
		if (el.scrollHeight > el.clientHeight) this.separator.removeClass(STYLES.hidden);
		else this.separator.addClass(STYLES.hidden);
	}
	_updateScrollToBottomButtonVisibility() {
		if (!this.scrollToBottomButton) return;
		const scrollTop = this.context.element.scrollTop() || 0;
		const scrollHeight = this.context.element.prop("scrollHeight") || 0;
		const clientHeight = this.context.element.prop("clientHeight") || 0;
		const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
		if (!(this.context.hasLatestMessageRendered ? this.context.hasLatestMessageRendered() : true) || distanceFromBottom > 100) this.scrollToBottomButton.removeClass(STYLES.hidden);
		else this.scrollToBottomButton.addClass(STYLES.hidden);
	}
	_onScroll() {
		this._updateScrollToBottomButtonVisibility();
		this.updateSeparator();
		const el = this.context.element[0];
		const scrollTop = el.scrollTop;
		const distanceFromBottom = el.scrollHeight - scrollTop - el.clientHeight;
		if (!this._scrolledAwayFromTop && scrollTop > 200) {
			this._clearOlderLoadDebounce();
			this._scrolledAwayFromTop = true;
		}
		if (!this._scrolledAwayFromBottom && distanceFromBottom > 100) {
			this._clearNewerLoadDebounce();
			this._scrolledAwayFromBottom = true;
		}
		if (scrollTop <= 200 && this._scrolledAwayFromTop && this.context.onScrollNearTop) {
			this._scrolledAwayFromTop = false;
			if (!this._isLoadingOlder && !this._allOlderLoaded) this._debounceOlderLoad();
		}
		if (distanceFromBottom <= 100 && this._scrolledAwayFromBottom && this.context.onScrollNearBottom) {
			this._scrolledAwayFromBottom = false;
			if (!this._isLoadingNewer && !this._allNewerLoaded) this._debounceNewerLoad();
		}
	}
	_getEndlessScrollDebounceDelay() {
		return Math.max(0, this.context.options.endlessScrollDebounceDelay || 0);
	}
	_clearOlderLoadDebounce() {
		if (this._olderLoadDebounceTimeout) {
			clearTimeout(this._olderLoadDebounceTimeout);
			this._olderLoadDebounceTimeout = null;
		}
	}
	_clearNewerLoadDebounce() {
		if (this._newerLoadDebounceTimeout) {
			clearTimeout(this._newerLoadDebounceTimeout);
			this._newerLoadDebounceTimeout = null;
		}
	}
	_isNearTopEdge() {
		return (this.context.element[0]?.scrollTop || 0) <= 200;
	}
	_isNearBottomEdge() {
		const element = this.context.element[0];
		if (!element) return false;
		return element.scrollHeight - element.scrollTop - element.clientHeight <= 100;
	}
	_debounceOlderLoad() {
		const delay = this._getEndlessScrollDebounceDelay();
		this._clearOlderLoadDebounce();
		if (!delay) {
			this.context.onScrollNearTop?.();
			return;
		}
		this._olderLoadDebounceTimeout = setTimeout(() => {
			this._olderLoadDebounceTimeout = null;
			if (this._isNearTopEdge() && !this._isLoadingOlder && !this._allOlderLoaded) this.context.onScrollNearTop?.();
		}, delay);
	}
	_debounceNewerLoad() {
		const delay = this._getEndlessScrollDebounceDelay();
		this._clearNewerLoadDebounce();
		if (!delay) {
			this.context.onScrollNearBottom?.();
			return;
		}
		this._newerLoadDebounceTimeout = setTimeout(() => {
			this._newerLoadDebounceTimeout = null;
			if (this._isNearBottomEdge() && !this._isLoadingNewer && !this._allNewerLoaded) this.context.onScrollNearBottom?.();
		}, delay);
	}
	_scrollToBottomButtonClick(e) {
		e.preventDefault();
		if (this.context.onScrollToLatest && this.context.isLatestRangeActive && !this.context.isLatestRangeActive()) {
			this.context.onScrollToLatest();
			return;
		}
		this.scrollToBottom();
	}
	_getMessageElement(uid) {
		return this.context.list.find("." + STYLES.message + `[data-uid="${require_kendo_core.htmlService.encode(uid)}"]`);
	}
	_getPreviousMessageElement(messageElement) {
		return messageElement.prevAll("." + STYLES.message).first();
	}
	_getPinnedHeight() {
		const pinnedElement = this.context.element.find("." + STYLES.messagePinned);
		return pinnedElement.length ? pinnedElement.outerHeight() || 0 : 0;
	}
	_getAutoScrollThreshold(pinnedHeight) {
		const availableHeight = Math.max((this.context.element.prop("clientHeight") || 0) - pinnedHeight, 0);
		const threshold = this.context.options.autoScrollThreshold;
		if (typeof threshold === "number") return Math.max(threshold, 0);
		if (typeof threshold === "string") {
			const trimmedThreshold = threshold.trim();
			if (trimmedThreshold.endsWith("%")) {
				const percentage = parseFloat(trimmedThreshold.slice(0, -1));
				if (!isNaN(percentage)) return Math.max(availableHeight * percentage / 100, 0);
			}
			const pixelThreshold = parseFloat(trimmedThreshold);
			if (!isNaN(pixelThreshold)) return Math.max(pixelThreshold, 0);
		}
		return 0;
	}
	preserveScrollPosition(callback) {
		const container = this.context.element[0];
		const scrollTop = container.scrollTop;
		const previousScrollHeight = container.scrollHeight;
		callback();
		const newScrollHeight = container.scrollHeight;
		container.scrollTo({
			top: scrollTop + (newScrollHeight - previousScrollHeight),
			behavior: "instant"
		});
	}
	scrollToBottom() {
		const container = this.context.element[0];
		container.scrollTo({
			top: container.scrollHeight,
			behavior: "instant"
		});
	}
	isNearBottom() {
		const scrollTop = this.context.element.scrollTop() || 0;
		const scrollHeight = this.context.element.prop("scrollHeight") || 0;
		const clientHeight = this.context.element.prop("clientHeight") || 0;
		return scrollHeight - scrollTop - clientHeight <= 100;
	}
	scrollToMessage(uid) {
		if (!uid) return false;
		const messageElement = this._getMessageElement(uid);
		if (!messageElement.length) return false;
		const pinnedHeight = this._getPinnedHeight();
		return require_kendo_core.domUtilsService.scrollToElement(this.context.element, messageElement, {
			position: "top",
			offset: -pinnedHeight
		});
	}
	scrollIncomingMessage(uid) {
		if (!uid) return false;
		const messageElement = this._getMessageElement(uid);
		if (!messageElement.length) return false;
		const container = this.context.element;
		const containerElement = container[0];
		const containerOffset = container.offset();
		const messageOffset = messageElement.offset();
		if (!containerElement || !containerOffset || !messageOffset) return false;
		const scrollTop = container.scrollTop() || 0;
		const containerHeight = containerElement.clientHeight || 0;
		const pinnedHeight = this._getPinnedHeight();
		const threshold = this._getAutoScrollThreshold(pinnedHeight);
		const preferredScrollTop = messageOffset.top - containerOffset.top + scrollTop - pinnedHeight - threshold;
		const previousMessageElement = this._getPreviousMessageElement(messageElement);
		const maxScrollTop = containerElement.scrollHeight - containerHeight;
		let targetScrollTop = preferredScrollTop;
		if (previousMessageElement.length) {
			const previousMessageOffset = previousMessageElement.offset();
			if (previousMessageOffset) {
				const previousMessageBottom = previousMessageOffset.top - containerOffset.top + scrollTop + (previousMessageElement.outerHeight() || 0);
				const minimumPreviousContext = Math.min(threshold, 8);
				const visibleTop = preferredScrollTop + pinnedHeight;
				if (minimumPreviousContext > 0 && previousMessageBottom < visibleTop + minimumPreviousContext) targetScrollTop = previousMessageBottom - pinnedHeight - minimumPreviousContext;
			}
		}
		targetScrollTop = Math.min(Math.max(targetScrollTop, 0), maxScrollTop);
		container.finish().animate({ scrollTop: targetScrollTop }, 0, "linear");
		return true;
	}
	resetEndlessState() {
		this._clearOlderLoadDebounce();
		this._clearNewerLoadDebounce();
		this._isLoadingOlder = false;
		this._allOlderLoaded = false;
		this._isLoadingNewer = false;
		this._allNewerLoaded = true;
		this._scrolledAwayFromTop = false;
		this._scrolledAwayFromBottom = false;
	}
	refreshState() {
		this._updateScrollToBottomButtonVisibility();
		this.updateSeparator();
	}
	destroy() {
		this._clearOlderLoadDebounce();
		this._clearNewerLoadDebounce();
		if (this._resizeObserverService) {
			this._resizeObserverService.destroy();
			this._resizeObserverService = null;
		}
		if (this.scrollToBottomButton) {
			this.scrollToBottomButton.remove();
			this.scrollToBottomButton = null;
		}
		if (this.separator) {
			this.separator.remove();
			this.separator = null;
		}
		this.context.element.off(".kendoChat.scrollManager");
	}
};
//#endregion
//#region ../src/chat/collaborators/chat-view/suggestion-renderer.ts
var SuggestionRenderer = class {
	constructor(context) {
		this.context = context;
	}
	renderSuggestedActions() {
		const options = this.context.options;
		const lastSuggestedActions = this.context.dataManager.getLastMessage()?.suggestedActions || [];
		const suggestedActionsElement = options.suggestedActionsTemplate(lastSuggestedActions);
		const layoutMode = this._getSuggestedActionsLayoutMode(options);
		const suggestedActionsWrapper = wrapSuggestionsWithLayout(suggestedActionsElement, layoutMode, options.dir === "rtl");
		this.removeSuggestedActions();
		this.context.list.append(lastSuggestedActions.length ? suggestedActionsWrapper : "");
		this.context.list.find(`[${REFERENCES.leftScrollButton}]`).addClass(STYLES.disabled);
	}
	removeSuggestedActions() {
		this.context.list.find("." + STYLES.suggestionGroup).remove();
		this.context.list.find("." + STYLES.suggestionScrollWrap).remove();
	}
	_getSuggestedActionsLayoutMode(options) {
		if (options.suggestedActionsLayoutMode) return options.suggestedActionsLayoutMode;
		if (options.suggestedActionsScrollable) return SUGGESTED_ACTIONS_LAYOUT_MODE.SCROLLBUTTONS;
		return SUGGESTED_ACTIONS_LAYOUT_MODE.SCROLL;
	}
};
//#endregion
//#region ../src/chat/collaborators/chat-view.collaborator.ts
var ChatView = class ChatView extends require_kendo_core.Widget {
	static {
		this.options = { name: "ChatView" };
	}
	_hideOnFocusMessageTimes() {
		this.element.find(`${"." + STYLES.messageTime}[data-timestamp-visibility='onFocus']`).addClass(STYLES.hidden);
	}
	_toggleOnFocusMessageTime(messageElement, visible) {
		messageElement.find(`${"." + STYLES.messageTime}[data-timestamp-visibility='onFocus']`).toggleClass(STYLES.hidden, !visible);
	}
	constructor(element, options, context) {
		super(element, $.extend(true, {}, ChatView.options, options));
		this._dragHandler = null;
		this.events = [
			EVENTS.suggestedActionClick,
			EVENTS.executeAction,
			EVENTS.messageToolbarExecute,
			EVENTS.fileMenuExecute,
			EVENTS.downloadAllFiles,
			EVENTS.expandableToggle,
			EVENTS.resendMessage
		];
		this.context = context;
		this._initList();
		this._initHelpers();
		this._attachEvents();
	}
	_initList() {
		const messages = this.options.messages;
		this.element.addClass(STYLES.viewWrapper).attr("role", "log").attr("aria-label", messages.messageListLabel).css("overflow-anchor", "none");
		this.list = $("<div>").addClass(STYLES.messageListContent).appendTo(this.element);
	}
	_initHelpers() {
		const options = this.options;
		this._messageRenderer = new MessageRenderer({
			list: this.list,
			options,
			dataManager: this.context.dataManager,
			dataItem: this.dataItem.bind(this),
			getReplyMessage: this.getReplyMessage.bind(this),
			initMessageToolbar: this._initMessageToolbar.bind(this),
			initFileMenus: this._initFileMenus.bind(this)
		});
		this._scrollManager = new ScrollManager({
			element: this.element,
			options,
			list: this.list,
			onScrollNearTop: this.context.onScrollNearTop,
			onScrollNearBottom: this.context.onScrollNearBottom,
			onScrollToLatest: this.context.onScrollToLatest,
			isLatestRangeActive: this.context.isLatestRangeActive,
			hasLatestMessageRendered: this.context.hasLatestMessageRendered
		});
		this._suggestionRenderer = new SuggestionRenderer({
			list: this.list,
			options,
			dataManager: this.context.dataManager
		});
	}
	_attachEvents() {
		this.element.on("click.kendoChat", this._listClick.bind(this)).on("click.kendoChat", "." + STYLES.message, this._messageClick.bind(this)).on("click.kendoChat", "." + STYLES.suggestion, this._suggestionClick.bind(this)).on("click.kendoChat", `[${REFERENCES.attachmentActionButton}]`, this._attachmentActionClick.bind(this)).on("click.kendoChat", `[${REFERENCES.leftScrollButton}]`, this._leftScrollButtonClick.bind(this)).on("click.kendoChat", `[${REFERENCES.rightScrollButton}]`, this._rightScrollButtonClick.bind(this)).on("click.kendoChat", "." + STYLES.downloadButton, this._downloadAllClick.bind(this)).on("click.kendoChat", `[${REFERENCES.retryButton}]`, this._retryButtonClick.bind(this));
		this._attachDragToScrollEvents();
	}
	_attachDragToScrollEvents() {
		this._dragHandler = require_kendo_core.domUtilsService.createDragToScrollHandler(this.element, {
			namespace: ".kendoChat.chatViewDrag",
			captureElement: this.element,
			delegateSelector: `.${STYLES.suggestionsScroll}, .${STYLES.cardDeck}`
		});
		this._dragHandler.attach();
	}
	_getScrollableElementFromButton(button) {
		const scrollWrap = button.closest(`.${STYLES.suggestionScrollWrap}, .${STYLES.cardDeckScrollWrap}`);
		if (scrollWrap.hasClass(STYLES.cardDeckScrollWrap)) return scrollWrap.children("." + STYLES.cardDeck);
		return scrollWrap.find("." + STYLES.suggestionsScroll);
	}
	_retryButtonClick(e) {
		e.preventDefault();
		e.stopPropagation();
		const button = $(e.currentTarget);
		const uid = button.attr("data-uid");
		const messageElement = button.closest("." + STYLES.message);
		const message = this._getMessageFromElement(messageElement);
		this.trigger(EVENTS.resendMessage, {
			message,
			uid
		});
	}
	_attachmentActionClick(e) {
		e.preventDefault();
		e.stopPropagation();
		const button = $(e.currentTarget);
		const attachmentIndex = Number(button.attr("data-attachment-index"));
		const actionIndex = Number(button.attr("data-action-index"));
		const messageElement = button.closest("." + STYLES.message);
		const message = this._getMessageFromElement(messageElement);
		const action = message?.attachments?.[attachmentIndex]?.actions?.[actionIndex];
		if (!action) return;
		this.trigger(EVENTS.executeAction, {
			action,
			message
		});
	}
	_getMessageFromElement(element) {
		const uid = element.attr("data-uid");
		if (!uid) return null;
		return this.context.dataManager.getMessageByUid(uid);
	}
	renderMessage(message) {
		this._messageRenderer.renderMessage(message);
	}
	_initMessageToolbar(element, perMessageActions) {
		const options = this.options;
		const messageToolbarActions = perMessageActions ?? options.messageToolbarActions;
		if (!messageToolbarActions || !messageToolbarActions.length || !element.length) {
			element.remove();
			return;
		}
		new MessageToolbar(element, {
			items: messageToolbarActions,
			dir: options.dir,
			messages: options.messages,
			resizable: false
		}).bind("execute", (e) => this.trigger(EVENTS.messageToolbarExecute, e));
	}
	_initFileMenus(element, enableFileActions) {
		const fileActions = this.options.fileActions;
		const fileMenuButtons = element.find(`[${REFERENCES.fileMenuButton}]`);
		if (enableFileActions === false || !fileActions?.length || !element.length) {
			fileMenuButtons.remove();
			return;
		}
		fileMenuButtons.each((_i, el) => {
			const $el = $(el);
			if (!$el.data("kendoDropDownButton")) new FileMenu($el, { items: fileActions }).bind("execute", (e) => this.trigger(EVENTS.fileMenuExecute, e));
		});
	}
	dataItem(message) {
		const uid = message && message.data("uid");
		if (uid) return this.context.dataManager.getMessageByUid(uid) || message.data("messageData") || null;
		return message?.data("messageData") || null;
	}
	fileDataItem(message, file) {
		const uid = file && file.data("uid");
		if (uid) return this.context.dataManager.getFileByUid(message, uid);
		return null;
	}
	getReplyMessage(message) {
		if (!message?.replyToId) return null;
		const replyMessage = this.context.dataManager.getMessageById(message.replyToId);
		if (replyMessage) replyMessage.isOwnMessage = String(replyMessage.authorId) === String(this.options.authorId);
		return replyMessage;
	}
	renderSuggestedActions() {
		this._suggestionRenderer.renderSuggestedActions();
	}
	clearMessages() {
		this.list.find("." + STYLES.messageGroup).remove();
		this.list.find("." + STYLES.timestamp).remove();
		this._suggestionRenderer.removeSuggestedActions();
	}
	removeMessage(uid) {
		const messageElement = this.list.find("." + STYLES.message + `[data-uid="${require_kendo_core.htmlService.encode(uid)}"]`);
		if (!messageElement.length) return false;
		const messageGroup = messageElement.closest("." + STYLES.messageGroup);
		if (messageGroup.find("." + STYLES.message).length === 1) messageGroup.remove();
		else messageElement.remove();
		return true;
	}
	scrollToBottom() {
		this._scrollManager.scrollToBottom();
	}
	isNearBottom() {
		return this._scrollManager.isNearBottom();
	}
	scrollToMessage(uid) {
		return this._scrollManager.scrollToMessage(uid);
	}
	scrollIncomingMessage(uid) {
		return this._scrollManager.scrollIncomingMessage(uid);
	}
	hasMessage(uid) {
		if (!uid) return false;
		return this.list.find("." + STYLES.message + `[data-uid="${require_kendo_core.htmlService.encode(uid)}"]`).length > 0;
	}
	updateSeparator() {
		this._scrollManager.updateSeparator();
	}
	refreshScrollState() {
		this._scrollManager.refreshState();
	}
	refreshDataManager(dataManager) {
		this.context.dataManager = dataManager;
	}
	showNoData(html) {
		this.list.html(html);
		this.list.addClass(STYLES.messageListContentEmpty);
	}
	hideNoData() {
		this.list.empty();
		this.list.removeClass(STYLES.messageListContentEmpty);
	}
	hasMessages() {
		return this.list.children("." + STYLES.messageGroup).length > 0;
	}
	_listClick(e) {
		const targetElement = $(e.target);
		if (targetElement.hasClass(STYLES.message) || targetElement.parents("." + STYLES.message).length) return;
		this._clearSelection(targetElement);
	}
	_suggestionClick(e) {
		const suggestionElement = $(e.currentTarget);
		if (suggestionElement.length) this.trigger(EVENTS.suggestedActionClick, { text: suggestionElement.text() });
	}
	_messageClick(e) {
		const target = $(e.target);
		const expandIcon = target.closest(`[${REFERENCES.bubbleExpandableIndicator}]`);
		if (this._allowMessageClick(target)) return;
		this._clearSelection(target);
		if (expandIcon.length) {
			const bubble = expandIcon.closest("." + STYLES.bubble);
			const isExpanded = bubble.hasClass(STYLES.expanded);
			bubble.toggleClass(STYLES.expanded, !isExpanded);
			expandIcon.html(kendo.ui.icon({ icon: isExpanded ? ICONS.chevronDown : ICONS.chevronUp }));
			this.trigger(EVENTS.expandableToggle, {
				indicator: expandIcon,
				isExpanded: !isExpanded
			});
			return;
		}
		const messageElement = $(e.currentTarget);
		messageElement.find("." + STYLES.bubble).addClass(STYLES.selected);
		this._toggleOnFocusMessageTime(messageElement, true);
	}
	_allowMessageClick(target) {
		const disallowedSelectors = [
			`[${REFERENCES.attachmentActionButton}]`,
			`[${REFERENCES.fileMenuButton}]`,
			"." + STYLES.messageToolbar,
			"." + STYLES.typingIndicator,
			"." + STYLES.downloadButton
		].join(", ");
		return target.closest(disallowedSelectors).length > 0 || target.children(disallowedSelectors).length > 0 || target.is(disallowedSelectors);
	}
	_clearSelection(target) {
		const selectedMessages = this.element.find("." + STYLES.selected);
		if (target.closest(`[${REFERENCES.bubbleExpandableIndicator}]`).length) return;
		this._hideOnFocusMessageTimes();
		selectedMessages.each((_index, element) => {
			$(element).removeClass(STYLES.selected);
		});
	}
	_leftScrollButtonClick(e) {
		e.preventDefault();
		const button = $(e.currentTarget);
		const scrollableElement = this._getScrollableElementFromButton(button);
		const isRtl = this.options.dir === "rtl";
		if (!scrollableElement.length) return;
		const position = require_kendo_core.domUtilsService.scrollByDelta(scrollableElement, isRtl ? 200 : -200);
		button.siblings(`[${REFERENCES.rightScrollButton}]`).removeClass(STYLES.disabled);
		if (position.atStart) button.addClass(STYLES.disabled);
	}
	_rightScrollButtonClick(e) {
		e.preventDefault();
		const button = $(e.currentTarget);
		const scrollableElement = this._getScrollableElementFromButton(button);
		const isRtl = this.options.dir === "rtl";
		if (!scrollableElement.length) return;
		const position = require_kendo_core.domUtilsService.scrollByDelta(scrollableElement, isRtl ? -200 : 200);
		button.siblings(`[${REFERENCES.leftScrollButton}]`).removeClass(STYLES.disabled);
		if (position.atEnd) button.addClass(STYLES.disabled);
	}
	_downloadAllClick(e) {
		const message = $(e.currentTarget).closest("." + STYLES.message);
		if (message.length) this.trigger(EVENTS.downloadAllFiles, { messageElement: message });
	}
	destroy() {
		super.destroy();
		this._scrollManager.destroy();
		if (this._dragHandler) this._dragHandler.destroy();
		this.element.find("[data-role='button']").each(function() {
			const fileMenu = $(this).data("kendoChatFileMenu");
			if (fileMenu) fileMenu.destroy();
		});
		this.element.empty();
		this.element.off(NS);
		this.list = null;
	}
};
//#endregion
//#region ../src/chat/collaborators/endless-range.collaborator.ts
var EndlessRangeManager = class {
	syncPageSize(isEndlessScrollEnabled, dataSource) {
		if (!isEndlessScrollEnabled || !dataSource || typeof dataSource.pageSize !== "function") return 20;
		let pageSize = Number(dataSource.pageSize());
		if (!Number.isFinite(pageSize) || pageSize < 1) {
			pageSize = 20;
			dataSource.pageSize(pageSize);
		}
		return Math.max(1, pageSize);
	}
	getLatestRange(pageSize, totalCount) {
		const endIndex = totalCount;
		return {
			startIndex: Math.max(0, endIndex - pageSize),
			endIndex
		};
	}
	getTargetRange(targetIndex, pageSize, totalCount) {
		if (totalCount <= pageSize) return {
			startIndex: 0,
			endIndex: totalCount
		};
		let startIndex = Math.max(0, targetIndex - Math.floor(pageSize / 2));
		let endIndex = Math.min(totalCount, startIndex + pageSize);
		startIndex = Math.max(0, endIndex - pageSize);
		return {
			startIndex,
			endIndex
		};
	}
	isLatestRenderedRange(renderEndIndex, totalCount) {
		return renderEndIndex === totalCount;
	}
	hasRenderedLatestMessage(renderEndIndex, totalCount) {
		return this.isLatestRenderedRange(renderEndIndex, totalCount);
	}
};
//#endregion
//#region ../src/chat/collaborators/endless-scroll-coordinator.collaborator.ts
var EndlessScrollCoordinator = class {
	constructor(context) {
		this.context = context;
	}
	requestLatestRemoteStartupRange() {
		this.context.beginLatestRemoteStartup();
		this._requestLatestRange(0, this.context.getPageSize(), "scroll-to-bottom", { scrollToBottom: true });
	}
	prepareLatestMessages() {
		if (!this.context.isEndlessScrollEnabled()) return;
		const totalCount = this.context.getTotalCount();
		if (!totalCount) return;
		const latestRange = this.context.getLatestRange(totalCount);
		if (this.context.isRemoteEndlessScroll() && !this.context.isRangeLoaded(latestRange.startIndex, latestRange.endIndex)) {
			this._requestLatestRange(latestRange.startIndex, latestRange.endIndex, "scroll-to-bottom", { scrollToBottom: true });
			return;
		}
		if (!this.context.isLatestRenderedRange(totalCount)) {
			this.context.renderRange(latestRange.startIndex, latestRange.endIndex, { scrollToBottom: true });
			return;
		}
		this.context.scrollToBottom();
		this.context.refreshScrollState();
	}
	navigateToReferencedMessage(id, message, loadedMessage) {
		if (loadedMessage?.uid && this.context.scrollToMessage(loadedMessage.uid)) return;
		if (!this.context.isEndlessScrollEnabled()) return;
		const targetIndex = this.context.getMessageIndexById(id);
		if (targetIndex < 0) {
			if (!this.context.isRemoteEndlessScroll()) return;
			this._requestLatestRange(0, this.context.getPageSize(), "reference-jump", { targetMessageId: id });
			return;
		}
		const targetRange = this.context.getTargetRange(targetIndex, this.context.getTotalCount());
		this.context.renderRange(targetRange.startIndex, targetRange.endIndex, { scrollToMessageUid: loadedMessage?.uid || message?.uid });
	}
	loadOlderMessages() {
		const renderedRange = this.context.getRenderedRange();
		if (this.context.isRemoteEndlessScroll()) {
			const loadedRange = this.context.getLoadedRange();
			if (loadedRange.startIndex <= 0) {
				this.context.updateEndlessState();
				return;
			}
			const pageSize = this.context.getPageSize();
			this._requestAdjacentRange(Math.max(0, loadedRange.startIndex - pageSize), loadedRange.startIndex, "older", "prepend", "older");
			return;
		}
		if (renderedRange.startIndex > 0) {
			const pageSize = this.context.getPageSize();
			this.context.prependLocalRange(Math.max(0, renderedRange.startIndex - pageSize), renderedRange.startIndex);
			return;
		}
		this.context.updateEndlessState();
	}
	loadNewerMessages() {
		const renderedRange = this.context.getRenderedRange();
		const totalCount = this.context.getTotalCount();
		if (this.context.isRemoteEndlessScroll()) {
			const loadedRange = this.context.getLoadedRange();
			if (loadedRange.endIndex >= totalCount) {
				this.context.updateEndlessState();
				return;
			}
			const pageSize = this.context.getPageSize();
			this._requestAdjacentRange(loadedRange.endIndex, Math.min(totalCount, loadedRange.endIndex + pageSize), "newer", "append", "newer");
			return;
		}
		if (renderedRange.endIndex >= totalCount) {
			this.context.updateEndlessState();
			return;
		}
		const pageSize = this.context.getPageSize();
		this.context.appendLocalRange(renderedRange.endIndex, Math.min(totalCount, renderedRange.endIndex + pageSize));
	}
	_requestLatestRange(startIndex, endIndex, reason, extras = {}) {
		this.context.requestRemoteRange(this._createRequest(startIndex, endIndex, "latest", reason, "replace", "all", extras));
	}
	_requestAdjacentRange(startIndex, endIndex, direction, mode, loadingState) {
		this.context.requestRemoteRange(this._createRequest(startIndex, endIndex, direction, "edge-scroll", mode, loadingState));
	}
	_createRequest(startIndex, endIndex, direction, reason, mode, loadingState, extras = {}) {
		return {
			startIndex,
			endIndex,
			direction,
			reason,
			mode,
			loadingState,
			...extras
		};
	}
};
//#endregion
//#region ../src/chat/collaborators/message-box.collaborator.ts
/**
* MessageBox handles the input area of the chat component.
* Delegates text input and file attachments entirely to PromptBox.
*
* This is an internal collaborator that extends Widget for event handling.
*/
var MessageBox = class MessageBox extends require_kendo_core.Widget {
	static {
		this.options = { name: "MessageBox" };
	}
	get textAreaInstance() {
		return this.promptBoxInstance || this.customInput;
	}
	/**
	* Constructs a new MessageBox instance.
	*/
	constructor(element, options, context) {
		super(element, $.extend(true, {}, MessageBox.options, options));
		this.events = [
			EVENTS.sendMessage,
			EVENTS.input,
			EVENTS.suggestionClick,
			EVENTS.replyMessageCloseButtonClick,
			EVENTS.fileSelect,
			EVENTS.fileRemove
		];
		this._generating = false;
		this._typing = false;
		this.customFiles = [];
		this._dragHandler = null;
		this.context = context;
		this.chatElement = context.chatElement;
		this._generating = false;
		this._initWrapper();
		this._initPromptBox();
		this._attachEvents();
		this._typing = false;
	}
	/**
	* Creates the wrapper element for the message box.
	*/
	_initWrapper() {
		const options = this.options;
		this.wrapper = $(`<div class="${STYLES.messageBoxWrapper}"></div>`);
		if (options.suggestions.length) {
			let suggestionsHtml;
			if (options.suggestionsTemplate) suggestionsHtml = options.suggestionsTemplate(options.suggestions);
			else {
				const layoutMode = this._getSuggestionsLayoutMode(options);
				suggestionsHtml = renderSuggestionsWithLayout(options.suggestions, layoutMode, options.dir === "rtl");
			}
			this.suggestions = this.wrapper.append(suggestionsHtml);
		}
		this.wrapper.find(`[${REFERENCES.leftScrollButton}]`).addClass(STYLES.disabled);
		this.chatElement.append(this.wrapper);
	}
	/**
	* Gets the effective suggestions layout mode, considering legacy options.
	*/
	_getSuggestionsLayoutMode(options) {
		if (options.suggestionsLayoutMode) return options.suggestionsLayoutMode;
		if (options.suggestionsScrollable) return SUGGESTED_ACTIONS_LAYOUT_MODE.SCROLLBUTTONS;
		return SUGGESTED_ACTIONS_LAYOUT_MODE.SCROLL;
	}
	/**
	* Initializes the PromptBox component for message input.
	*/
	_initPromptBox() {
		const options = this.options;
		if (options.messageBoxTemplate) {
			this._initCustomMessageBox(options);
			return;
		}
		const messages = options.messages || {};
		const messageBox = options.messageBox || {};
		const actionButtonOpts = options.actionButton;
		const fileAttachmentEnabled = options.fileAttachment !== false;
		const mode = messageBox.mode || "auto";
		const maxTextAreaHeight = messageBox.maxTextAreaHeight;
		const rows = messageBox.rows || 1;
		const startAffixTemplate = messageBox.startAffixTemplate;
		const endAffixTemplate = messageBox.endAffixTemplate;
		const topAffixTemplate = messageBox.topAffixTemplate;
		const actionButtonSettings = this._getActionButtonSettings(actionButtonOpts);
		const speechToTextSettings = this._getSpeechToTextSettings(options);
		const fileAttachmentSettings = this._getFileAttachmentSettings(options, fileAttachmentEnabled);
		const promptBox = new require_kendo_promptbox.PromptBox(this.element, {
			mode,
			maxTextAreaHeight,
			rows,
			_filesTemplate: this._getInternalFilesTemplate(options),
			startAffixTemplate,
			endAffixTemplate,
			topAffixTemplate,
			placeholder: messages.placeholder,
			speechToTextButton: speechToTextSettings,
			actionButton: actionButtonSettings,
			fileSelectButton: fileAttachmentSettings,
			messages: {
				placeholder: messages.placeholder,
				actionButton: messages.sendButton || actionButtonOpts?.text || messages.actionButton,
				actionButtonLoading: messages.sendButtonLoading || actionButtonOpts?.loadingText || actionButtonOpts?.stopText || messages.actionButtonLoading || messages.stopButton
			},
			input: this._input.bind(this),
			promptAction: this._promptBoxAction.bind(this),
			fileSelect: this._promptBoxFileSelect.bind(this),
			fileRemove: this._promptBoxFileRemove.bind(this)
		});
		promptBox.wrapper.appendTo(this.wrapper);
		this.promptBoxInstance = promptBox;
	}
	_initCustomMessageBox(options) {
		const messages = options.messages || {};
		const messageBox = $(`<div class="${STYLES.messageBox}"></div>`);
		const templateHtml = options.messageBoxTemplate();
		messageBox.append(templateHtml);
		this.wrapper.append(messageBox);
		this.customMessageBox = messageBox;
		this.customInput = this._findCustomInput(messageBox);
		this.customSendButton = this._findCustomSendButton(messageBox);
		if (this.customInput?.length) {
			this.customInput.attr("aria-label", messages.messageBoxLabel);
			if (!this.customInput.attr("placeholder") && messages.placeholder) this.customInput.attr("placeholder", messages.placeholder);
		}
		if (this.customSendButton?.length) {
			const sendLabel = messages.sendButton || "Send message";
			this._customSendButtonHtml = this.customSendButton.html();
			this._customSendButtonText = this.customSendButton.text();
			if (!this.customSendButton.attr("aria-label")) this.customSendButton.attr("aria-label", sendLabel);
			if (!this.customSendButton.attr("title")) this.customSendButton.attr("title", sendLabel);
		}
	}
	/**
	* Gets action button settings to pass to PromptBox.
	*/
	_getActionButtonSettings(actionButtonOpts) {
		return {
			enable: true,
			icon: actionButtonOpts?.icon,
			loadingIcon: actionButtonOpts?.loadingIcon || actionButtonOpts?.stopIcon,
			fillMode: actionButtonOpts?.fillMode,
			rounded: actionButtonOpts?.rounded,
			size: actionButtonOpts?.size,
			themeColor: actionButtonOpts?.themeColor,
			text: actionButtonOpts?.text,
			loadingText: actionButtonOpts?.loadingText || actionButtonOpts?.stopText
		};
	}
	/**
	* Gets speech-to-text settings to pass to PromptBox.
	*/
	_getSpeechToTextSettings(options) {
		if (options.speechToText === false) return false;
		if (require_kendo_core.utilsService.isObject(options.speechToText)) return options.speechToText;
		return null;
	}
	/**
	* Gets file attachment settings to pass to PromptBox.
	*/
	_getFileAttachmentSettings(options, enabled) {
		if (!enabled) return false;
		if (require_kendo_core.utilsService.isObject(options.fileAttachment)) return {
			showSpacer: true,
			_renderAffixLocation: "start",
			...options.fileAttachment
		};
		return {
			showSpacer: true,
			_renderAffixLocation: "start"
		};
	}
	_getInternalFilesTemplate(options) {
		if (!options.filesTemplate || options.filesTemplate === renderFiles) return null;
		return (files) => options.filesTemplate(files, false, options.messages, true);
	}
	/**
	* Handles PromptBox promptAction event.
	*/
	_promptBoxAction(e) {
		e.preventDefault();
		const actionType = e.actionType;
		const text = e.value || "";
		const files = e.files || [];
		if (actionType === "stop") {
			this.trigger(EVENTS.sendMessage, { generating: true });
			return;
		}
		if (!text.length && !files.length) return;
		const eventData = { message: {
			text,
			files
		} };
		this.trigger(EVENTS.sendMessage, eventData);
	}
	/**
	* Handles PromptBox fileSelect event.
	*/
	_promptBoxFileSelect(e) {
		const files = this.promptBoxInstance?.files() || [];
		this.trigger(EVENTS.fileSelect, { files });
	}
	/**
	* Handles PromptBox fileRemove event.
	*/
	_promptBoxFileRemove(e) {
		this.trigger(EVENTS.fileRemove, {
			file: e.file,
			files: e.files
		});
	}
	/**
	* Attaches event handlers to message box elements.
	*/
	_attachEvents() {
		this.wrapper.on("click.kendoChat", `[${REFERENCES.replyMessageCloseButton}]`, this._replyMessageCloseButtonClick.bind(this)).on("click.kendoChat", "." + STYLES.suggestion, this._suggestionClick.bind(this)).on("click.kendoChat", `[${REFERENCES.leftScrollButton}]`, this._leftScrollButtonClick.bind(this)).on("click.kendoChat", `[${REFERENCES.rightScrollButton}]`, this._rightScrollButtonClick.bind(this)).on("click.kendoChat", this._getCustomSendButtonSelector(), this._customSendButtonClick.bind(this)).on("input.kendoChat", this._getCustomInputSelector(), this._customInputChange.bind(this)).on("keydown.kendoChat", this._getCustomInputSelector(), this._customInputKeyDown.bind(this)).on("change.kendoChat", `.${STYLES.messageBox} input[type='file']`, this._customFileInputChange.bind(this));
		this._attachDragToScrollEvents();
	}
	/**
	* Attaches drag-to-scroll events for suggestions.
	*/
	_attachDragToScrollEvents() {
		const scrollContainer = this.wrapper.find("." + STYLES.suggestionsScroll);
		if (!scrollContainer.length) return;
		this._dragHandler = require_kendo_core.domUtilsService.createDragToScrollHandler(scrollContainer, {
			namespace: ".kendoChat.chatMessageBoxDrag",
			captureElement: this.chatElement
		});
		this._dragHandler.attach();
	}
	/**
	* Gets the current files from the PromptBox.
	*/
	getFiles() {
		if (this._usesCustomTemplate()) return [...this.customFiles];
		return this.promptBoxInstance?.files() || [];
	}
	/**
	* Sets files on the PromptBox.
	*/
	setFiles(files) {
		if (this._usesCustomTemplate()) {
			this.customFiles = [...files];
			return;
		}
		this.promptBoxInstance?.files(files);
	}
	/**
	* Clears all file attachments.
	*/
	clearFiles() {
		if (this._usesCustomTemplate()) {
			this.customFiles = [];
			this.customMessageBox?.find("input[type='file']").val("");
			return;
		}
		this.promptBoxInstance?.clearFiles();
	}
	_getPrefixContainer() {
		const container = this._getMessageBoxContainer();
		let header = container.find(".k-prompt-box-header");
		if (header.length === 0) {
			header = $("<div class=\"k-prompt-box-header\" ref-promptbox-header><div ref-promptbox-attachments-host></div></div>");
			container.prepend(header);
		}
		return header;
	}
	/**
	* Sets a reply message in the message box interface.
	*/
	setReplyMessage(message, isOwnMessage) {
		const options = this.options;
		const replyContainer = this.getReplyMessageContainer();
		const replyTemplate = options.messageReferenceTemplate({
			text: message.text,
			files: message.files,
			isOwnMessage,
			renderCloseButton: true,
			renderFileMenuButton: false
		});
		const prefixContainer = this._getPrefixContainer();
		prefixContainer.removeClass("k-hidden");
		if (replyContainer.length === 0) prefixContainer.prepend(replyTemplate);
		else replyContainer.replaceWith(replyTemplate);
	}
	/**
	* Removes the reply message from the message box interface.
	*/
	removeReplyMessage() {
		const replyContainer = this.getReplyMessageContainer();
		if (replyContainer.length > 0) {
			replyContainer.remove();
			this._updateHeaderVisibility();
		}
	}
	_updateHeaderVisibility() {
		const prefixContainer = this._getPrefixContainer();
		const files = this.getFiles();
		const hasFiles = files && files.length > 0;
		const hasReply = this.getReplyMessageContainer().length > 0;
		if (!hasFiles && !hasReply) prefixContainer.addClass("k-hidden");
	}
	/**
	* Gets the reply message container element.
	*/
	getReplyMessageContainer() {
		return this._getPrefixContainer().find(`[${REFERENCES.messageReferenceReplyWrapper}]`);
	}
	loading(value) {
		if (value === void 0) return this._generating;
		this._generating = value;
		if (this._usesCustomTemplate()) {
			this._setCustomLoadingState(value);
			return;
		}
		this.promptBoxInstance.loading(value);
	}
	value(newValue) {
		if (newValue === void 0) return this._getInputValue();
		this._setInputValue(newValue);
	}
	_input(e) {
		const currentValue = e?.value ?? this._getInputValue();
		this.trigger(EVENTS.input, { value: currentValue });
	}
	_replyMessageCloseButtonClick(e) {
		e.preventDefault();
		this.trigger(EVENTS.replyMessageCloseButtonClick);
	}
	_suggestionClick(e) {
		e.preventDefault();
		const options = this.options;
		const text = $(e.target).closest("." + STYLES.suggestion).text();
		if (!text.length) return;
		this.trigger(EVENTS.suggestionClick, {
			text,
			behavior: options.suggestionsBehavior || "send"
		});
		if (options.suggestionsBehavior === "insert") this._setInputValue(text);
	}
	_getInputValue() {
		if (this._usesCustomTemplate()) return this.customInput?.val() || "";
		return this.promptBoxInstance.value();
	}
	_setInputValue(value) {
		if (this._usesCustomTemplate()) {
			this.customInput?.val(value);
			return;
		}
		this.promptBoxInstance.value(value);
	}
	_usesCustomTemplate() {
		return !!this.customMessageBox?.length;
	}
	_getMessageBoxContainer() {
		if (this._usesCustomTemplate()) return this.customMessageBox;
		return this.promptBoxInstance.wrapper;
	}
	_findCustomInput(container) {
		return container.find("textarea, input:not([type='hidden']):not([type='file']):not([type='button']):not([type='submit']):not([type='reset']):not([type='checkbox']):not([type='radio'])").first();
	}
	_findCustomSendButton(container) {
		return container.find(`[${REFERENCES.sendButton}]`).first();
	}
	_getCustomInputSelector() {
		return `.${STYLES.messageBox} textarea, .${STYLES.messageBox} input:not([type='hidden']):not([type='file']):not([type='button']):not([type='submit']):not([type='reset']):not([type='checkbox']):not([type='radio'])`;
	}
	_getCustomSendButtonSelector() {
		return `.${STYLES.messageBox} [${REFERENCES.sendButton}]`;
	}
	_customSendButtonClick(e) {
		if (!this._usesCustomTemplate()) return;
		const button = $(e.currentTarget);
		if (this.customSendButton?.length && !button.is(this.customSendButton)) return;
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation?.();
		this._sendCustomTemplateMessage();
	}
	_customInputChange(e) {
		if (!this._usesCustomTemplate()) return;
		this._input({ value: $(e.currentTarget).val() || "" });
	}
	_customInputKeyDown(e) {
		if (!this._usesCustomTemplate()) return;
		if (!(e.key === "Enter" || e.which === 13 || e.keyCode === 13) || this._generating || !this.customSendButton?.length) return;
		if (e.shiftKey && $(e.currentTarget).is("textarea")) return;
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation?.();
		this._sendCustomTemplateMessage();
	}
	_customFileInputChange(e) {
		if (!this._usesCustomTemplate()) return;
		const input = e.currentTarget;
		const selectedFiles = Array.from(input.files || []).map((file) => ({
			uid: require_kendo_core.utilsService.guid(),
			name: file.name,
			size: file.size,
			extension: file.name.lastIndexOf(".") > -1 ? file.name.substring(file.name.lastIndexOf(".")).toLowerCase() : "",
			rawFile: file
		}));
		if (!selectedFiles.length) return;
		if (input.multiple) this.customFiles = [...this.customFiles, ...selectedFiles];
		else this.customFiles = selectedFiles;
		this.trigger(EVENTS.fileSelect, { files: [...this.customFiles] });
	}
	_sendCustomTemplateMessage() {
		if (this._generating) {
			this.trigger(EVENTS.sendMessage, { generating: true });
			return;
		}
		const text = this._getInputValue().trim();
		const files = this.getFiles();
		if (!text.length && !files.length) return;
		this.trigger(EVENTS.sendMessage, { message: {
			text,
			files
		} });
		this._setInputValue("");
		this.clearFiles();
		this.customInput?.trigger("focus");
	}
	_setCustomLoadingState(value) {
		const messages = this.options.messages || {};
		if (!this.customSendButton?.length) return;
		const label = value ? messages.sendButtonLoading || "Stop" : messages.sendButton || "Send message";
		this.customSendButton.toggleClass(STYLES.generating, value);
		this.customSendButton.attr("aria-label", label);
		this.customSendButton.attr("title", label);
		if (value) {
			if (this._customSendButtonText?.trim()) this.customSendButton.text(label);
			return;
		}
		if (this._customSendButtonText?.trim()) this.customSendButton.text(this._customSendButtonText);
		else if (this._customSendButtonHtml !== void 0) this.customSendButton.html(this._customSendButtonHtml);
	}
	_leftScrollButtonClick(e) {
		e.preventDefault();
		const button = $(e.currentTarget);
		const scrollableElement = this.wrapper.find(`.${STYLES.suggestionsScroll}`);
		const isRtl = this.options.dir === "rtl";
		const position = require_kendo_core.domUtilsService.scrollByDelta(scrollableElement, isRtl ? 200 : -200);
		this.wrapper.find(`[${REFERENCES.rightScrollButton}]`).removeClass(STYLES.disabled);
		if (position.atStart) button.addClass(STYLES.disabled);
	}
	_rightScrollButtonClick(e) {
		e.preventDefault();
		const button = $(e.currentTarget);
		const scrollableElement = this.wrapper.find(`.${STYLES.suggestionsScroll}`);
		const isRtl = this.options.dir === "rtl";
		const position = require_kendo_core.domUtilsService.scrollByDelta(scrollableElement, isRtl ? -200 : 200);
		this.wrapper.find(`[${REFERENCES.leftScrollButton}]`).removeClass(STYLES.disabled);
		if (position.atEnd) button.addClass(STYLES.disabled);
	}
	destroy() {
		if (this._dragHandler) this._dragHandler.destroy();
		if (this.promptBoxInstance) this.promptBoxInstance.destroy();
		this.customFiles = [];
		this.wrapper.remove();
	}
};
//#endregion
//#region ../src/chat/collaborators/message-menu.collaborator.ts
/**
* MessageMenu provides context menu functionality for chat messages.
* Handles actions like reply, copy, pin, and delete for individual messages.
*
* This is an internal collaborator that extends Widget for event handling.
*/
var MessageMenu = class MessageMenu extends require_kendo_core.Widget {
	static {
		this.options = {
			name: "ChatMessageMenu",
			filter: `.${STYLES.chatBubble}`,
			dataSource: [],
			keyboardAlignToAnchor: true
		};
	}
	/**
	* Constructs a new MessageMenu instance.
	*/
	constructor(element, options) {
		options = $.extend(true, {}, MessageMenu.options, options);
		options.dataSource = MessageMenu.normalizeActions(options.dataSource || []);
		super(element, options);
		this.create();
		this.attachEvents();
	}
	/**
	* Creates the underlying Kendo UI ContextMenu.
	*/
	create() {
		this.menu = new kendo.ui.ContextMenu(this.element, this.options);
	}
	/**
	* Attaches event handlers to the context menu.
	*/
	attachEvents() {
		this.menu.bind("select", this.onSelect.bind(this)).bind("open", this.onOpen.bind(this)).bind("close", this.onClose.bind(this));
	}
	/**
	* Sets command attributes on menu items for identification.
	*/
	static normalizeActions(actions) {
		return (actions || []).map((item) => ({
			...item,
			attr: {
				...item.attr || {},
				"data-command": item.name.toLowerCase()
			}
		}));
	}
	setActions(actions) {
		const dataSource = MessageMenu.normalizeActions(actions);
		this.options.dataSource = dataSource;
		this.menu.setOptions({ dataSource });
	}
	/**
	* Handles menu item selection.
	*/
	onSelect(e) {
		const item = $(e.item);
		const message = $(e.target).closest("." + STYLES.message);
		const command = item.data("command");
		if (command) this.executeCommand(command, item, message);
	}
	/**
	* Handles menu open event.
	*/
	onOpen(e) {
		let setActive = true;
		const target = $(e.target);
		const originalTarget = $(e.event?.target).length ? $(e.event.target) : target;
		const isMessageRemoved = target.closest("." + STYLES.messageRemoved).length > 0;
		const hasTypingIndicator = target.find("." + STYLES.typingIndicator).length > 0;
		const isAttachmentMenuButton = originalTarget.closest(`[${REFERENCES.fileMenuButton}]`).length > 0;
		const message = target.closest("." + STYLES.message);
		if (isMessageRemoved || hasTypingIndicator || isAttachmentMenuButton || !message.length) {
			e.preventDefault();
			setActive = false;
		}
		target.toggleClass(STYLES.active, setActive);
		if (this.trigger("open", { message })) {
			e.preventDefault();
			target.removeClass(STYLES.active);
		}
	}
	/**
	* Handles menu close event.
	*/
	onClose(e) {
		const target = $(e.target);
		const message = target.closest("." + STYLES.message);
		target.removeClass(STYLES.active);
		this.trigger("close", { message });
	}
	/**
	* Executes a command from the menu.
	*/
	executeCommand(command, item, message) {
		this.trigger("execute", {
			type: command,
			item,
			message
		});
	}
	/**
	* Toggles the visibility of the delete menu item.
	*/
	toggleDeleteVisibility(visible) {
		const deleteItem = this.element.find("[data-command='delete']");
		if (deleteItem.length) deleteItem.toggleClass(STYLES.hidden, !visible);
	}
	destroy() {
		if (this.menu) {
			this.menu.destroy();
			this.menu = null;
			this.element.remove();
		}
	}
};
//#endregion
//#region ../src/chat/collaborators/remote-range-loader.collaborator.ts
var RemoteRangeLoader = class {
	constructor(context) {
		this._pendingRemoteRangeRequests = {};
		this._activeLatestRecoveryRequestKey = null;
		this._queuedLatestRecoveryRequest = null;
		this.context = context;
	}
	reset() {
		this._pendingRemoteRangeRequests = {};
		this._activeLatestRecoveryRequestKey = null;
		this._queuedLatestRecoveryRequest = null;
	}
	requestRange(options) {
		const preparedRequest = this._prepareRequest(options);
		if (!preparedRequest) {
			this.context.updateEndlessState();
			return;
		}
		if (this._mergePendingRequest(preparedRequest) || this._queueLatestRecoveryRequestIfNeeded(preparedRequest)) return;
		const requestState = this._activateRequest(preparedRequest);
		this._dispatchRequest(requestState);
	}
	_prepareRequest(options) {
		const requestOptions = this._normalizeRequestOptions(options);
		if (!requestOptions) return null;
		return {
			key: this._getRemoteRangeRequestKey(requestOptions),
			options: requestOptions
		};
	}
	_normalizeRequestOptions(options) {
		const normalizedRange = this._normalizeRequestedRange(options);
		if (!normalizedRange) return null;
		return {
			...options,
			startIndex: normalizedRange.startIndex,
			endIndex: normalizedRange.endIndex
		};
	}
	_normalizeRequestedRange(options) {
		const totalCount = this.context.getTotalCount();
		const isLatestScrollToBottomRequest = this._isLatestScrollToBottomRequest(options);
		let safeStartIndex = Math.max(0, options.startIndex);
		let safeEndIndex = Math.max(safeStartIndex, options.endIndex);
		if (isLatestScrollToBottomRequest) {
			const requestedWindow = Math.max(1, safeEndIndex - safeStartIndex);
			return {
				startIndex: safeStartIndex,
				endIndex: safeStartIndex + requestedWindow
			};
		}
		safeStartIndex = Math.max(0, Math.min(safeStartIndex, totalCount));
		safeEndIndex = Math.max(safeStartIndex, Math.min(safeEndIndex, totalCount));
		if (safeEndIndex <= safeStartIndex) return null;
		return {
			startIndex: safeStartIndex,
			endIndex: safeEndIndex
		};
	}
	_mergePendingRequest(preparedRequest) {
		const pendingRequest = this._pendingRemoteRangeRequests[preparedRequest.key];
		if (!pendingRequest) return false;
		pendingRequest.options.scrollToBottom = pendingRequest.options.scrollToBottom || !!preparedRequest.options.scrollToBottom;
		pendingRequest.options.loadingState = this._mergeRemoteLoadingState(pendingRequest.options.loadingState, preparedRequest.options.loadingState);
		this.context.setRemoteLoadingState(pendingRequest.options.loadingState);
		return true;
	}
	_queueLatestRecoveryRequestIfNeeded(preparedRequest) {
		if (!this._isLatestScrollToBottomRequest(preparedRequest.options) || !this._activeLatestRecoveryRequestKey || this._activeLatestRecoveryRequestKey === preparedRequest.key) return false;
		this._queueLatestRecoveryRequest(preparedRequest.options);
		this.context.setRemoteLoadingState(this._queuedLatestRecoveryRequest.loadingState);
		return true;
	}
	_activateRequest(preparedRequest) {
		const requestState = {
			key: preparedRequest.key,
			options: preparedRequest.options
		};
		this._pendingRemoteRangeRequests[requestState.key] = requestState;
		if (this._isLatestScrollToBottomRequest(requestState.options)) this._activeLatestRecoveryRequestKey = requestState.key;
		this.context.setRemoteLoadingState(requestState.options.loadingState);
		return requestState;
	}
	_dispatchRequest(requestState) {
		const transportRead = this.context.getDataSource()?.transport?.read;
		if (typeof transportRead !== "function") {
			this._handleRequestError(requestState);
			return;
		}
		transportRead({
			data: this._createTransportRequestData(requestState.options),
			success: (response) => {
				this._handleRequestSuccess(requestState, response);
			},
			error: () => {
				this._handleRequestError(requestState);
			}
		});
	}
	_createTransportRequestData(options) {
		return this._buildRemoteRequestData(options.startIndex, options.endIndex, options.direction, options.reason, options.targetMessageId);
	}
	_handleRequestSuccess(requestState, response) {
		const dataSource = this.context.getDataSource();
		const lifecycle = this._createRequestLifecycle(requestState);
		if (!dataSource) {
			lifecycle.finalize();
			return;
		}
		try {
			const parsedResponse = this._parseResponse(dataSource, response, requestState.options);
			this._resolveMissingReplyReferences(requestState.options, parsedResponse.models, () => {
				this._applyResponse(requestState.options, parsedResponse);
				lifecycle.finalize();
			}, lifecycle.fail);
		} catch (error) {
			if (this._pendingRemoteRangeRequests[requestState.key]) lifecycle.finalize();
			throw error;
		}
	}
	_createRequestLifecycle(requestState) {
		return {
			finalize: () => {
				this._completeRequest(requestState);
			},
			fail: (error) => {
				this._completeRequest(requestState);
				throw this._normalizeReferenceResolverError(error);
			}
		};
	}
	_parseResponse(dataSource, response, requestOptions) {
		const parsedResponse = dataSource.reader.parse(response);
		const items = dataSource.reader.data(parsedResponse) || [];
		const responseTotal = Number(dataSource.reader.total(parsedResponse));
		const totalCount = this._extractRemoteResponseTotal(responseTotal);
		const models = this._createRemoteModels(items);
		const range = this._extractRemoteResponseRange(parsedResponse, models.length, totalCount, requestOptions);
		this._validateReferenceJumpResponse(requestOptions, models);
		this._validateAdjacentRangeResponse(requestOptions, range, models);
		return {
			range,
			models,
			totalCount
		};
	}
	_applyResponse(requestOptions, parsedResponse) {
		this.context.applyResponse(requestOptions, parsedResponse);
	}
	_handleRequestError(requestState) {
		this.context.updateEndlessState();
		this._completeRequest(requestState);
	}
	_completeRequest(requestState) {
		const queuedLatestRequest = this._clearRequestState(requestState.key);
		this.context.completeEndlessLoad(requestState.options.loadingState, () => {
			this._resumeQueuedLatestRecoveryRequest(queuedLatestRequest);
		});
	}
	_clearRequestState(requestKey) {
		const queuedLatestRequest = this._consumeQueuedLatestRecoveryRequest(requestKey);
		delete this._pendingRemoteRangeRequests[requestKey];
		if (this._activeLatestRecoveryRequestKey === requestKey) this._activeLatestRecoveryRequestKey = null;
		return queuedLatestRequest;
	}
	_resumeQueuedLatestRecoveryRequest(requestOptions) {
		if (requestOptions && this.context.canRequestRange()) this.requestRange(requestOptions);
	}
	_mergeRemoteLoadingState(current, next) {
		if (current === "all" || next === "all" || current !== next) return "all";
		return current;
	}
	_getRemoteRangeRequestKey(options) {
		return [
			options.startIndex,
			options.endIndex,
			options.direction,
			options.reason,
			options.mode,
			options.targetMessageId == null ? "" : String(options.targetMessageId)
		].join("|");
	}
	_isLatestScrollToBottomRequest(options) {
		return options.direction === "latest" && options.reason === "scroll-to-bottom" && options.mode === "replace";
	}
	_isReferenceJumpRequest(options) {
		return options.reason === "reference-jump" && options.mode === "replace" && options.targetMessageId != null;
	}
	_isLatestRecoveryRequest(options) {
		if (!this._isLatestScrollToBottomRequest(options)) return false;
		const renderedRange = this.context.getRenderedRange();
		return renderedRange.endIndex > renderedRange.startIndex;
	}
	_queueLatestRecoveryRequest(options) {
		if (!this._queuedLatestRecoveryRequest) {
			this._queuedLatestRecoveryRequest = { ...options };
			return;
		}
		this._queuedLatestRecoveryRequest = {
			...options,
			scrollToBottom: this._queuedLatestRecoveryRequest.scrollToBottom || !!options.scrollToBottom,
			loadingState: this._mergeRemoteLoadingState(this._queuedLatestRecoveryRequest.loadingState, options.loadingState)
		};
	}
	_consumeQueuedLatestRecoveryRequest(requestKey) {
		if (this._activeLatestRecoveryRequestKey !== requestKey || !this._queuedLatestRecoveryRequest) return null;
		const queuedRequest = this._queuedLatestRecoveryRequest;
		this._queuedLatestRecoveryRequest = null;
		return queuedRequest;
	}
	_buildRemoteRequestData(startIndex, endIndex, direction, reason, targetMessageId) {
		const totalCount = this.context.getTotalCount();
		const safeStartIndex = Math.max(0, startIndex);
		const safeEndIndex = Math.max(safeStartIndex, Math.min(endIndex, totalCount || endIndex));
		const take = Math.max(1, safeEndIndex - safeStartIndex);
		const page = totalCount ? Math.max(1, Math.ceil(Math.max(totalCount - safeEndIndex, 0) / take) + 1) : 1;
		if (direction === "latest" && reason === "scroll-to-bottom") return {
			page,
			pageSize: take,
			skip: safeStartIndex,
			take,
			direction,
			reason,
			intent: "latest",
			targetMessageId
		};
		if (reason === "reference-jump") return {
			page: 1,
			pageSize: take,
			skip: 0,
			take,
			direction,
			reason,
			targetMessageId
		};
		return {
			page,
			pageSize: take,
			skip: safeStartIndex,
			take,
			startIndex: safeStartIndex,
			endIndex: safeEndIndex,
			direction,
			reason,
			targetMessageId
		};
	}
	_extractRemoteResponseTotal(totalCount) {
		if (!Number.isInteger(totalCount) || totalCount < 0) throw new Error("The Chat endless response must include a valid total value.");
		return totalCount;
	}
	_extractRemoteResponseRange(parsedResponse, itemCount, totalCount, requestOptions) {
		const responseStartIndex = Number(parsedResponse?.startIndex);
		const responseEndIndex = Number(parsedResponse?.endIndex);
		if (!Number.isInteger(responseStartIndex) || !Number.isInteger(responseEndIndex)) throw new Error("The Chat endless response must include valid startIndex and endIndex values.");
		const startIndex = responseStartIndex;
		const endIndex = responseEndIndex;
		if (startIndex < 0 || endIndex < startIndex || endIndex > totalCount) throw new Error("The Chat endless response returned an invalid range window.");
		if (endIndex - startIndex !== itemCount) throw new Error("The Chat endless response count does not match the returned range.");
		if (this._isLatestScrollToBottomRequest(requestOptions) && endIndex !== totalCount) {
			if (!this._isLatestRecoveryRequest(requestOptions) || startIndex !== requestOptions.startIndex || endIndex !== requestOptions.endIndex) throw new Error("The Chat endless latest response must end at total.");
		}
		return {
			startIndex,
			endIndex
		};
	}
	_validateReferenceJumpResponse(requestOptions, models) {
		if (!this._isReferenceJumpRequest(requestOptions)) return;
		const targetMessageId = String(requestOptions.targetMessageId);
		if (!models.some((message) => String(message?.id) === targetMessageId)) throw new Error("The Chat endless jump response must contain the requested targetMessageId.");
	}
	_validateAdjacentRangeResponse(requestOptions, range, models) {
		if (requestOptions.mode !== "prepend" && requestOptions.mode !== "append") return;
		if (range.startIndex !== requestOptions.startIndex || range.endIndex !== requestOptions.endIndex) throw new Error("The Chat endless response must match the requested adjacent range.");
		const loadedRange = this.context.getLoadedRange();
		if (requestOptions.mode === "prepend" && range.endIndex !== loadedRange.startIndex) throw new Error("The Chat endless response must match the requested adjacent range.");
		if (requestOptions.mode === "append" && range.startIndex !== loadedRange.endIndex) throw new Error("The Chat endless response must match the requested adjacent range.");
		const loadedMessages = this.context.getLoadedMessagesInRange(loadedRange.startIndex, loadedRange.endIndex);
		const loadedIds = new Set(loadedMessages.map((message) => String(message.id)));
		if (models.some((message) => loadedIds.has(String(message.id)))) throw new Error("The Chat endless response must match the requested adjacent range.");
	}
	_createRemoteModels(items) {
		const model = this.context.getDataSource()?.reader?.model;
		return items.map((item) => {
			return model ? new model(item) : item;
		});
	}
	_collectMissingReplyTargetIds(requestOptions, models) {
		const missingIds = [];
		const seenIds = /* @__PURE__ */ new Set();
		const availableIds = /* @__PURE__ */ new Set();
		if (requestOptions.mode !== "replace") {
			const loadedRange = this.context.getLoadedRange();
			this.context.getLoadedMessagesInRange(loadedRange.startIndex, loadedRange.endIndex).forEach((message) => {
				if (message?.id != null) availableIds.add(String(message.id));
			});
		}
		models.forEach((message) => {
			if (message?.id != null) availableIds.add(String(message.id));
		});
		models.forEach((message) => {
			const replyToId = message?.replyToId;
			if (!replyToId) return;
			const key = String(replyToId);
			if (availableIds.has(key) || this.context.getReferenceMessageById(replyToId) || seenIds.has(key)) return;
			seenIds.add(key);
			missingIds.push(replyToId);
		});
		return missingIds;
	}
	_resolveMissingReplyReferences(requestOptions, models, onSuccess, onError) {
		const missingIds = this._collectMissingReplyTargetIds(requestOptions, models);
		if (!missingIds.length) {
			onSuccess();
			return;
		}
		const referenceResolver = this.context.getChatOptions().referenceResolver;
		if (typeof referenceResolver !== "function") {
			onError(/* @__PURE__ */ new Error("The Chat referenceResolver option is required to render off-batch reply previews."));
			return;
		}
		let completed = false;
		referenceResolver({
			value: missingIds,
			success: (dataItems) => {
				if (completed) return;
				completed = true;
				try {
					const resolvedMessages = this._mapResolvedReferenceMessages(missingIds, dataItems);
					this.context.cacheResolvedReferenceMessages(resolvedMessages);
					onSuccess();
				} catch (error) {
					onError(error);
				}
			},
			error: (error) => {
				if (completed) return;
				completed = true;
				onError(error);
			}
		});
	}
	_mapResolvedReferenceMessages(requestedIds, dataItems) {
		const resolvedById = {};
		(Array.isArray(dataItems) ? dataItems : []).forEach((item) => {
			this._validateResolvedReferenceMessage(item);
			const key = String(item.id);
			if (resolvedById[key]) return;
			resolvedById[key] = item;
		});
		return requestedIds.map((id) => {
			const resolvedMessage = resolvedById[String(id)];
			if (!resolvedMessage) throw new Error("The Chat referenceResolver must resolve every requested id.");
			return resolvedMessage;
		});
	}
	_validateResolvedReferenceMessage(item) {
		const hasId = item?.id !== void 0 && item?.id !== null && String(item.id) !== "";
		const hasAuthorId = item?.authorId !== void 0 && item?.authorId !== null && String(item.authorId) !== "";
		const hasText = typeof item?.text === "string" && item.text.length > 0;
		const hasFiles = !!item?.files && typeof item.files.length === "number" && item.files.length > 0;
		const isDeleted = item?.isDeleted === true;
		if (!hasId || !hasAuthorId || !hasText && !hasFiles && !isDeleted) throw new Error("The Chat referenceResolver must return items with id, authorId, and enough data to render the reply preview.");
	}
	_normalizeReferenceResolverError(error) {
		if (error instanceof Error) return error;
		if (typeof error === "string" || typeof error === "number" || typeof error === "boolean") return /* @__PURE__ */ new Error(`${error}`);
		if (typeof error === "symbol") return new Error(error.description || "The Chat referenceResolver failed.");
		if (typeof error === "object" && error) {
			if ("message" in error && typeof error.message === "string") return new Error(error.message);
			try {
				return new Error(JSON.stringify(error));
			} catch {
				return /* @__PURE__ */ new Error("The Chat referenceResolver failed.");
			}
		}
		return /* @__PURE__ */ new Error("The Chat referenceResolver failed.");
	}
};
//#endregion
//#region ../src/chat/helpers/command-handler.ts
var CommandHandler = class {
	constructor(context) {
		this.context = context;
	}
	commandExecute(e) {
		const message = this.context.dataItem(e.message);
		if (!message) return;
		const file = this.context.fileDataItem(message, e.file);
		const type = e.type;
		if (!file) switch (type) {
			case COMMANDS.reply:
				this.messageReply(message);
				break;
			case COMMANDS.copy:
				this.messageCopy(message);
				break;
			case COMMANDS.pin:
				this.messagePin(message);
				break;
			case COMMANDS.delete:
				this.messageDelete(message);
				break;
		}
		else {
			switch (type) {
				case COMMANDS.download:
					this.context.trigger(EVENTS.download, {
						files: [file],
						message
					});
					break;
			}
			this.context.trigger(EVENTS.fileMenuAction, {
				type,
				file,
				message
			});
		}
	}
	contextMenuExecute(e) {
		this.commandExecute(e);
		const message = this.context.dataItem(e.message);
		const type = e.type;
		this.context.trigger(EVENTS.contextMenuAction, {
			type,
			message
		});
	}
	messageReply(message) {
		const isOwnMessage = String(message.authorId) === String(this.context.getUserId());
		this.context.setCurrentMessageReplyId(message.id);
		this.context.setReplyMessage(message, isOwnMessage);
		this.context.setupAriaAttributes();
	}
	messageCopy(message) {
		if (navigator && navigator.clipboard) navigator.clipboard.writeText(message.text);
	}
	messagePin(message) {
		this.context.pinMessage(message);
	}
	messageDelete(message) {
		this.context.removeMessage(message);
	}
	messageContextMenuOpen(e) {
		const message = this.context.dataItem(e.message);
		if (!message) return;
		const isAuthor = String(message.authorId) === String(this.context.getUserId());
		if (!this.context.isContextMenuAllowed(isAuthor)) {
			e.preventDefault();
			return;
		}
		this.context.setContextMenuActions(isAuthor);
		this.context.toggleDeleteVisibility(isAuthor);
	}
};
//#endregion
//#region ../src/chat/helpers/event-wiring.ts
var EventWiring = class {
	constructor(context) {
		this.context = context;
	}
	_triggerSendMessage(message) {
		const ctx = this.context;
		ctx.trigger(EVENTS.sendMessage, { message });
		ctx.prepareLatestMessages();
		ctx.postMessage(message);
	}
	_prepareLatestAndTrigger(eventName, args) {
		const ctx = this.context;
		ctx.prepareLatestMessages();
		ctx.trigger(eventName, args);
	}
	attach() {
		this._attachViewEvents();
		this._attachMessageBoxEvents();
		this._attachContextMenuEvents();
		this._attachWrapperDomEvents();
	}
	_attachViewEvents() {
		const ctx = this.context;
		ctx.view.bind("suggestedActionClick", (args) => {
			if (ctx.options.suggestionsBehavior === "insert") {
				ctx.value(args.text);
				ctx.trigger(EVENTS.suggestionClick, { text: args.text });
			} else {
				const message = {
					text: args.text,
					files: []
				};
				this._triggerSendMessage(message);
			}
		}).bind("messageToolbarExecute", (args) => {
			const message = ctx.dataItem(args.message);
			ctx.commandExecute({
				type: args.type,
				message: args.message
			});
			ctx.trigger(EVENTS.toolbarAction, {
				type: args.type,
				message
			});
		}).bind("fileMenuExecute", (args) => {
			ctx.commandExecute(args);
		}).bind("downloadAllFiles", (args) => {
			const message = ctx.dataItem(args.messageElement);
			ctx.trigger(EVENTS.download, {
				files: message?.files,
				message
			});
		}).bind("executeAction", (args) => {
			ctx.trigger(EVENTS.executeAction, args);
		}).bind("resendMessage", (args) => {
			this._prepareLatestAndTrigger(EVENTS.resendMessage, args);
		}).bind("expandableToggle", (args) => {
			ctx.accessibility.updateExpandableIndicatorAria(args.indicator, args.isExpanded);
		});
	}
	_attachMessageBoxEvents() {
		const ctx = this.context;
		ctx.messageBox.bind("input", (args) => {
			ctx.trigger(EVENTS.input, { value: args.value });
		}).bind("sendMessage", (args) => {
			const generating = args.generating;
			if (generating) ctx.trigger(EVENTS.sendMessage, { generating });
			else {
				const message = {
					text: args.text || args.message?.text,
					files: args.files || args.message?.files || []
				};
				this._triggerSendMessage(message);
				ctx.clearReplyState();
			}
		}).bind("suggestionClick", (args) => {
			ctx.trigger(EVENTS.suggestionClick, { text: args.text });
			if (args.behavior !== "insert") {
				const message = {
					text: args.text,
					files: []
				};
				this._triggerSendMessage(message);
			}
		}).bind("fileSelect", (args) => {
			ctx.trigger(EVENTS.fileSelect, { files: args.files });
		}).bind("fileRemove", (args) => {
			ctx.trigger(EVENTS.fileRemove, {
				file: args.file,
				files: args.files
			});
		}).bind("replyMessageCloseButtonClick", () => {
			ctx.clearReplyState();
		});
	}
	_attachContextMenuEvents() {
		const ctx = this.context;
		ctx.messageContextMenu.bind("execute", ctx.contextMenuExecute.bind(ctx)).bind("open", ctx.messageContextMenuOpen.bind(ctx));
	}
	_attachWrapperDomEvents() {
		const ctx = this.context;
		ctx.wrapper.on("click.kendoChat", `[${REFERENCES.pinnedMessageCloseButton}]`, () => {
			ctx.trigger(EVENTS.unpin, { message: ctx.getPinnedMessage() });
			ctx.clearPinnedMessage();
		});
		ctx.wrapper.on("click.kendoChat", `[${REFERENCES.messageReferencePinWrapper}]`, (e) => {
			if ($(e.target).closest(`[${REFERENCES.pinnedMessageCloseButton}]`).length) return;
			if ($(e.target).is("a")) return;
			else {
				e.preventDefault();
				e.stopPropagation();
			}
			const pinnedMessage = ctx.getPinnedMessage();
			if (pinnedMessage) ctx.navigateToReferencedMessage(pinnedMessage.id);
		});
		ctx.wrapper.on("click.kendoChat", `[${REFERENCES.messageReferenceReplyWrapper}]`, (e) => {
			if ($(e.target).is("a")) return;
			else {
				e.preventDefault();
				e.stopPropagation();
			}
			const messageElement = $(e.currentTarget).closest("." + STYLES.message);
			const currentMessage = ctx.dataItem(messageElement);
			if (currentMessage && currentMessage.replyToId) ctx.navigateToReferencedMessage(currentMessage.replyToId);
		});
		ctx.wrapper.on("keydown.kendoChat", (e) => {
			ctx.accessibility.handleKeyDown(e, ctx.messageBoxRef);
		});
		ctx.wrapper.on(`focus${NS}`, `.${STYLES.bubble}`, (e) => {
			const bubbleElement = $(e.target);
			if (bubbleElement.find(`.${STYLES.typingIndicator}`).length > 0) return;
			if (bubbleElement.attr("data-keyboard-focus") === "true") {
				const messageElement = bubbleElement.closest(`.${STYLES.message}`);
				const message = ctx.dataItem(messageElement);
				if (message && message.uid) ctx.scrollToMessage(message.uid);
			}
		});
	}
};
//#endregion
//#region ../src/chat/chat.widget.ts
const DEFAULT_MESSAGE_ACTIONS = [
	{
		name: COMMANDS.reply,
		text: "Reply",
		icon: ICONS.undo
	},
	{
		name: COMMANDS.copy,
		text: "Copy",
		icon: ICONS.copy
	},
	{
		name: COMMANDS.pin,
		text: "Pin",
		icon: ICONS.pin
	},
	{
		name: COMMANDS.delete,
		text: "Delete",
		icon: ICONS.trash
	}
];
const DEFAULT_FILE_ACTIONS = [{
	name: COMMANDS.download,
	text: "Download",
	icon: ICONS.download
}];
const DEFAULT_MESSAGES = {
	messageListLabel: "Message list",
	placeholder: "Type a message...",
	sendButton: "Send message",
	sendButtonLoading: "Stop",
	actionButton: "Send message",
	actionButtonLoading: "Stop",
	stopButton: "Stop",
	speechToTextButton: "Toggle speech to text",
	fileButton: "Attach file",
	downloadAll: "Download all",
	selfMessageDeleted: "You removed this message.",
	otherMessageDeleted: "This message was removed by its sender.",
	stopGeneration: "Stop generation",
	messageBoxLabel: "Type your message here",
	pinnedMessageCloseButton: "Unpin message",
	replyMessageCloseButton: "Remove reply",
	fileMenuButton: "File menu",
	retryMessage: "Retry"
};
var Chat = class Chat extends require_kendo_core.Widget {
	static {
		this.options = {
			name: "Chat",
			scrollMode: "scrollable",
			pageSize: 20,
			endlessScrollDebounceDelay: 150,
			autoAssignId: true,
			autoBind: true,
			allowMessageCollapse: false,
			showUsername: true,
			showAvatar: true,
			timestampVisibility: "onFocus",
			suggestionsBehavior: "send",
			authorId: null,
			suggestionsScrollable: false,
			suggestedActionsScrollable: false,
			suggestionsLayoutMode: "scroll",
			suggestedActionsLayoutMode: "scroll",
			speechToText: true,
			fileAttachment: true,
			scrollToBottomButton: true,
			autoScrollThreshold: "20%",
			loading: false,
			messageBox: {
				mode: "multi",
				maxTextAreaHeight: 110
			},
			messageToolbarActions: [],
			messageActions: DEFAULT_MESSAGE_ACTIONS,
			fileActions: DEFAULT_FILE_ACTIONS,
			suggestions: [],
			headerItems: [],
			dir: "ltr",
			messageTimeFormat: "ddd MMM dd yyyy",
			width: null,
			height: null,
			messages: DEFAULT_MESSAGES,
			messageWidthMode: "standard",
			messageTemplate: null,
			messageGroupTemplate: renderMessageGroup,
			messageReferenceTemplate: renderMessageReference,
			filesTemplate: renderFiles,
			suggestionsTemplate: null,
			suggestedActionsTemplate: renderSuggestions,
			suggestedActionsField: "suggestedActions",
			timestampTemplate: null,
			messageStatusTemplate: null,
			messageStatusSettings: null,
			headerTemplate: null,
			noDataTemplate: null,
			messageContentTemplate: null,
			userStatusTemplate: null,
			attachmentTemplate: null,
			messageBoxTemplate: null,
			authorMessageSettings: null,
			receiverMessageSettings: null,
			actionButton: null,
			attachmentLayout: "list",
			filesLayoutMode: "vertical",
			pinnedMessages: [],
			referenceResolver: null,
			textField: "text",
			authorIdField: "authorId",
			authorNameField: "authorName",
			authorImageUrlField: "authorImageUrl",
			authorImageAltTextField: "authorImageAltText",
			idField: "id",
			timestampField: "timestamp",
			filesField: "files",
			replyToIdField: "replyToId",
			isDeletedField: "isDeleted",
			isPinnedField: "isPinned",
			isTypingField: "isTyping",
			statusField: "status",
			failedField: "failed",
			attachmentsField: "attachments",
			attachmentLayoutField: "attachmentLayout",
			skipSanitization: false
		};
	}
	/**
	* Creates a new Chat widget instance.
	*
	* @param element - The DOM element to attach the widget to
	* @param options - Configuration options
	* @param events - Optional event handlers
	*/
	constructor(element, options, events) {
		options = options || {};
		Chat.mergeContextMenuActions(options, Chat.options, "messageActions");
		Chat.mergeContextMenuActions(options, Chat.options, "fileActions");
		super(element, $.extend(true, {}, Chat.options, options));
		this.currentMessageReplyId = null;
		this._hasHeader = false;
		this._loading = false;
		this._renderStartIndex = 0;
		this._renderEndIndex = 0;
		this._endlessPageSize = 20;
		this._endlessRangeManager = new EndlessRangeManager();
		this.events = [
			EVENTS.sendMessage,
			EVENTS.suggestionClick,
			EVENTS.unpin,
			EVENTS.input,
			EVENTS.toolbarAction,
			EVENTS.fileMenuAction,
			EVENTS.contextMenuAction,
			EVENTS.download,
			EVENTS.fileSelect,
			EVENTS.fileRemove,
			EVENTS.executeAction,
			EVENTS.resendMessage,
			EVENTS.referencedMessageClick
		];
		this.options.messageActions = options.messageActions;
		this.options.fileActions = options.fileActions;
		if (events) this._events = events;
		this._init(options);
	}
	static mergeContextMenuActions(options, defaultOptions, property) {
		const userActions = options[property];
		if (Array.isArray(userActions)) {
			const defaultActions = defaultOptions[property] || [];
			const defaultMap = {};
			for (const action of defaultActions) defaultMap[action.name] = { ...action };
			options[property] = userActions.map((userAction) => {
				if (userAction.name in defaultMap) return {
					...defaultMap[userAction.name],
					...userAction
				};
				return { ...userAction };
			});
		} else options[property] = defaultOptions[property] || [];
	}
	_init(options) {
		this.currentMessageReplyId = null;
		this._hasHeader = !!options.headerItems?.length;
		this._initUser();
		this._initWrapper();
		this._initDataSource();
		this._validateRemoteEndlessStartup();
		this._initView();
		if (options.toolbar) require_kendo_core.utilsService.logToConsole("The 'toolbar' option has been deprecated.", "warn");
		this._initMessageBox();
		this._initMenus();
		this._initAccessibility();
		this._initRemoteRangeLoader();
		this._initEndlessScrollCoordinator();
		this._attachEvents();
		if (this.options.loading) this.loading(true);
		if (this._isEndlessScrollEnabled()) this.view._scrollManager._isLoadingOlder = true;
		if (this.options.autoBind) if (this._isRemoteEndlessScroll()) this._requestLatestRemoteStartupRange();
		else this.dataSource.fetch();
		this.scrollToBottom();
	}
	_validateRemoteEndlessStartup() {
		if (this._isRemoteEndlessScroll() && this.options.autoBind === false) throw new Error("The Chat remote endless mode does not support autoBind: false.");
	}
	_requestLatestRemoteStartupRange() {
		this._endlessScrollCoordinator.requestLatestRemoteStartupRange();
	}
	_initUser() {
		const options = this.options;
		if (options.authorId) {
			options.authorId = options.authorId.toString();
			return;
		}
		options.authorId = require_kendo_core.utilsService.guid();
	}
	_initWrapper() {
		const options = this.options;
		const height = options.height;
		const width = options.width;
		const headerItems = options.headerItems;
		const headerTemplate = options.headerTemplate;
		const uiElements = `<div ${REFERENCES.viewWrapper}></div>`;
		this.wrapper = this.element.addClass(STYLES.wrapper).attr("dir", options.dir).append(uiElements);
		if (this._hasHeader || headerTemplate) this.wrapper.prepend(renderHeader(headerItems, headerTemplate));
		if (height) this.wrapper.css({
			height,
			minHeight: height
		});
		if (width) this.wrapper.css({
			width,
			minWidth: width
		});
	}
	_initDataSource() {
		this._refreshHandler = this.refresh.bind(this);
		this._dataManager = new DataManager({ chatOptions: this.options });
		this.dataSource = this._dataManager.getDataSource();
		this._syncEndlessPageSize();
		this.dataSource.bind(CHANGE, this._refreshHandler);
	}
	_syncEndlessPageSize() {
		const options = this.options;
		this._endlessPageSize = this._endlessRangeManager.syncPageSize(this._isEndlessScrollEnabled(options), this.dataSource);
	}
	_getEndlessPageSize() {
		return this._endlessPageSize;
	}
	_unbindDataSource() {
		this.dataSource.unbind(CHANGE, this._refreshHandler);
		this._dataManager = null;
	}
	_initView() {
		const options = $.extend(true, {}, this.options);
		delete options.name;
		const element = this.wrapper.find(`[${REFERENCES.viewWrapper}]`);
		const context = { dataManager: this._dataManager };
		if (this._isEndlessScrollEnabled()) {
			context.onScrollNearTop = this._loadOlderMessages.bind(this);
			context.onScrollNearBottom = this._loadNewerMessages.bind(this);
			context.onScrollToLatest = this._prepareLatestMessages.bind(this);
			context.isLatestRangeActive = this._isLatestRenderedRange.bind(this);
			context.hasLatestMessageRendered = this._hasRenderedLatestMessage.bind(this);
		}
		this.view = new ChatView(element, options, context);
	}
	_initMessageBox() {
		const options = $.extend(true, {}, this.options);
		delete options.name;
		this.messageBox = new MessageBox($("<textarea></textarea>"), options, { chatElement: this.wrapper });
	}
	_initMenus() {
		const messageActions = this.options.messageActions;
		this.messageContextMenu = new MessageMenu($("<ul></ul>"), {
			dataSource: messageActions,
			target: this.wrapper
		});
	}
	_initAccessibility() {
		this.accessibility = new AccesibilityManager({
			wrapper: this.wrapper,
			getOptions: () => this.options
		});
		this.accessibility.setupAriaAttributes();
		this.accessibility.setupBubbleTabNavigation();
	}
	_initRemoteRangeLoader() {
		this._remoteRangeLoader = new RemoteRangeLoader({
			getDataSource: () => this.dataSource,
			getChatOptions: () => this.options,
			getTotalCount: () => this._getEndlessDataCount(),
			getLoadedRange: () => this._dataManager.getLoadedRange(),
			getRenderedRange: () => ({
				startIndex: this._renderStartIndex,
				endIndex: this._renderEndIndex
			}),
			getLoadedMessagesInRange: this._dataManager.getMessagesInRange.bind(this._dataManager),
			getLoadedMessageById: this._dataManager.getLoadedMessageById.bind(this._dataManager),
			getReferenceMessageById: this._dataManager.getAuxiliaryMessageById.bind(this._dataManager),
			cacheResolvedReferenceMessages: this._dataManager.cacheResolvedReferenceMessages.bind(this._dataManager),
			applyResponse: this._applyRemoteRangeResponse.bind(this),
			updateEndlessState: this._updateEndlessState.bind(this),
			completeEndlessLoad: this._completeEndlessLoad.bind(this),
			setRemoteLoadingState: this._setRemoteLoadingState.bind(this),
			canRequestRange: () => !!(this.view && this.dataSource)
		});
	}
	_initEndlessScrollCoordinator() {
		this._endlessScrollCoordinator = new EndlessScrollCoordinator({
			beginLatestRemoteStartup: this._beginLatestRemoteStartupRangeRequest.bind(this),
			isEndlessScrollEnabled: this._isEndlessScrollEnabled.bind(this),
			isRemoteEndlessScroll: this._isRemoteEndlessScroll.bind(this),
			getPageSize: this._getEndlessPageSize.bind(this),
			getTotalCount: this._getEndlessDataCount.bind(this),
			getRenderedRange: () => ({
				startIndex: this._renderStartIndex,
				endIndex: this._renderEndIndex
			}),
			getLoadedRange: () => this._dataManager.getLoadedRange(),
			getLatestRange: this._getLatestRange.bind(this),
			getTargetRange: this._getTargetRange.bind(this),
			isLatestRenderedRange: this._isLatestRenderedRange.bind(this),
			isRangeLoaded: (startIndex, endIndex) => this._dataManager.isRangeLoaded(startIndex, endIndex),
			getMessageIndexById: (id) => this._dataManager.getMessageIndexById(id),
			requestRemoteRange: (options) => this._remoteRangeLoader.requestRange(options),
			renderRange: (startIndex, endIndex, options = {}) => this._renderEndlessRange(startIndex, endIndex, options),
			scrollToBottom: this.scrollToBottom.bind(this),
			refreshScrollState: () => this.view.refreshScrollState(),
			scrollToMessage: this.scrollToMessage.bind(this),
			prependLocalRange: this._prependLocalEndlessRange.bind(this),
			appendLocalRange: this._appendLocalEndlessRange.bind(this),
			updateEndlessState: this._updateEndlessState.bind(this)
		});
	}
	_beginLatestRemoteStartupRangeRequest() {
		this.view._scrollManager._isLoadingOlder = true;
		this.view._scrollManager._isLoadingNewer = false;
		this.view.clearMessages();
	}
	_setRemoteLoadingState(direction) {
		if (!this.view) return;
		if (direction === "older" || direction === "all") this.view._scrollManager._isLoadingOlder = true;
		if (direction === "newer" || direction === "all") this.view._scrollManager._isLoadingNewer = true;
		this.view.refreshScrollState();
	}
	_applyRemoteRangeResponse(requestOptions, parsedResponse) {
		const nextModels = this._updateRemoteRangeWindow(requestOptions.mode, parsedResponse.range.startIndex, parsedResponse.models, parsedResponse.totalCount);
		if (requestOptions.mode === "prepend") this._applyPrependedRemoteRange(parsedResponse.range, nextModels);
		else if (requestOptions.mode === "append") this._applyAppendedRemoteRange(parsedResponse.range, nextModels);
		else this._applyReplacedRemoteRange(requestOptions, parsedResponse.range);
		this._refreshPinnedState(this._dataManager.getPinnedMessage());
	}
	_updateRemoteRangeWindow(mode, startIndex, models, totalCount) {
		const nextModels = models;
		const wasAutoSync = this.dataSource.options.autoSync;
		this.dataSource.options.autoSync = false;
		this.dataSource.unbind(CHANGE, this._refreshHandler);
		const data = this.dataSource.data();
		data.omitChangeEvent = true;
		try {
			if (mode === "replace") {
				data.splice(0, data.length, ...models);
				this._dataManager.setRemoteRangeStart(startIndex);
			} else if (mode === "prepend") {
				data.splice(0, 0, ...nextModels);
				this._dataManager.setRemoteRangeStart(startIndex);
			} else data.splice(data.length, 0, ...nextModels);
		} finally {
			data.omitChangeEvent = false;
		}
		this.dataSource.bind(CHANGE, this._refreshHandler);
		this.dataSource.options.autoSync = wasAutoSync;
		this.dataSource._total = totalCount;
		this.dataSource._pristineTotal = totalCount;
		return nextModels;
	}
	_applyPrependedRemoteRange(range, nextModels) {
		if (this.view && nextModels.length) this.view._scrollManager.preserveScrollPosition(() => {
			this.view._messageRenderer.prependMessages(nextModels);
		});
		this._renderStartIndex = Math.min(range.startIndex, this._renderStartIndex);
		this._renderEndIndex = Math.max(this._renderEndIndex, range.endIndex);
		this._updateEndlessState();
		this._finalizeIncrementalEndlessRender();
	}
	_applyAppendedRemoteRange(range, nextModels) {
		nextModels.forEach((message) => {
			this.renderMessage(message);
		});
		this._renderEndIndex = Math.max(this._renderEndIndex, range.endIndex);
		this._updateEndlessState();
		this._finalizeIncrementalEndlessRender();
	}
	_applyReplacedRemoteRange(requestOptions, range) {
		const targetMessage = requestOptions.targetMessageId ? this._dataManager.getLoadedMessageById(requestOptions.targetMessageId) : null;
		this._renderEndlessRange(range.startIndex, range.endIndex, {
			scrollToBottom: requestOptions.scrollToBottom,
			scrollToMessageUid: targetMessage?.uid
		});
	}
	_finalizeIncrementalEndlessRender() {
		if (!this.view) return;
		this.view.renderSuggestedActions();
		this.view.updateSeparator();
		this.view.refreshScrollState();
		if (!this.accessibility) return;
		this.accessibility.setupAriaAttributes();
		this.accessibility.setupBubbleTabNavigation();
	}
	_attachEvents() {
		const options = this.options;
		this._commandHandler = new CommandHandler({
			getUserId: this.getUserId.bind(this),
			dataItem: this.dataItem.bind(this),
			fileDataItem: this.fileDataItem.bind(this),
			removeMessage: this.removeMessage.bind(this),
			pinMessage: (message) => this._dataManager.pinMessage(message),
			setReplyMessage: (message, isOwnMessage) => this.messageBox.setReplyMessage(message, isOwnMessage),
			setCurrentMessageReplyId: (id) => {
				this.currentMessageReplyId = id;
			},
			setupAriaAttributes: () => this.accessibility.setupAriaAttributes(),
			setContextMenuActions: (isOwnMessage) => {
				const settings = isOwnMessage ? options.authorMessageSettings : options.receiverMessageSettings;
				this.messageContextMenu.setActions(settings?.messageActions ?? options.messageActions);
			},
			toggleDeleteVisibility: (isAuthor) => this.messageContextMenu.toggleDeleteVisibility(isAuthor),
			isContextMenuAllowed: (isOwnMessage) => {
				return (isOwnMessage ? options.authorMessageSettings : options.receiverMessageSettings)?.enableContextMenuActions !== false;
			},
			trigger: this.trigger.bind(this)
		});
		this._eventWiring = new EventWiring({
			options: this.options,
			wrapper: this.wrapper,
			view: this.view,
			messageBox: this.messageBox,
			messageContextMenu: this.messageContextMenu,
			value: this.value.bind(this),
			postMessage: this.postMessage.bind(this),
			clearReplyState: this.clearReplyState.bind(this),
			prepareLatestMessages: this._prepareLatestMessages.bind(this),
			commandExecute: this._commandHandler.commandExecute.bind(this._commandHandler),
			contextMenuExecute: this._commandHandler.contextMenuExecute.bind(this._commandHandler),
			messageContextMenuOpen: this._commandHandler.messageContextMenuOpen.bind(this._commandHandler),
			dataItem: this.dataItem.bind(this),
			scrollToMessage: this.scrollToMessage.bind(this),
			navigateToReferencedMessage: this._navigateToReferencedMessage.bind(this),
			clearPinnedMessage: this.clearPinnedMessage.bind(this),
			getPinnedMessage: () => this._dataManager.getPinnedMessage(),
			getMessageById: (id) => this._dataManager.getMessageById(id),
			trigger: this.trigger.bind(this),
			accessibility: this.accessibility,
			messageBoxRef: this.messageBox
		});
		this._eventWiring.attach();
		this.bind(this.events, this.options);
	}
	getUserId() {
		return this.options.authorId;
	}
	/**
	* Gets or sets the input value of the message box.
	* @param value - Optional value to set. If provided, sets the input value.
	* @returns The current input value when called without arguments.
	*/
	value(newValue) {
		if (newValue === void 0) return this.messageBox?.value() || "";
		if (this.messageBox) {
			this.messageBox.value(newValue);
			this.trigger(EVENTS.input, { value: newValue });
		}
	}
	/**
	* Sets new options for the widget.
	*/
	setOptions(options) {
		super.setOptions(options);
		this.destroy();
		const chat = new Chat(this.element, this.options);
		Object.assign(this, chat);
	}
	/**
	* Sets a new data source.
	*/
	setDataSource(dataSource) {
		if (this.dataSource) this._unbindDataSource();
		this._remoteRangeLoader.reset();
		this.view.clearMessages();
		this._renderStartIndex = 0;
		this._renderEndIndex = 0;
		if (this._isEndlessScrollEnabled()) this.view._scrollManager.resetEndlessState();
		this.options.dataSource = dataSource;
		this._initDataSource();
		this._validateRemoteEndlessStartup();
		if (this.options.autoBind) if (this._isRemoteEndlessScroll()) this._requestLatestRemoteStartupRange();
		else this.dataSource.fetch();
		if (this.view) this.view.refreshDataManager(this._dataManager);
	}
	/**
	* Posts a new message to the chat.
	*/
	postMessage(message) {
		if (this.currentMessageReplyId) {
			if (typeof message === "string") message = { text: message };
			message.replyToId = this.currentMessageReplyId;
		}
		return this._dataManager.postMessage(message, this.getUserId());
	}
	/**
	* Removes a message (marks as deleted).
	*/
	removeMessage(message) {
		return this._dataManager.removeMessage(message);
	}
	/**
	* Updates an existing message.
	*/
	updateMessage(message, newData) {
		return this._dataManager.updateMessage(message, newData);
	}
	/**
	* Gets a message by its UID.
	*/
	getMessageByUid(uid) {
		return this._dataManager.getMessageByUid(uid);
	}
	/**
	* Gets the data item for a message element.
	*/
	dataItem(message) {
		return this.view.dataItem(message);
	}
	/**
	* Gets the file data item for a file element.
	*/
	fileDataItem(message, file) {
		return this.view.fileDataItem(message, file);
	}
	/**
	* Clears all messages from the view.
	*/
	clearMessages() {
		this.view.clearMessages();
	}
	/**
	* @internal
	* Renders a message in the view.
	*/
	renderMessage(message) {
		this.view.renderMessage(message);
		this.accessibility.setupBubbleTabNavigation();
	}
	/**
	* @internal
	* Renders a pinned message.
	* The pinned message is positioned sticky inside the message list per Chat v3 spec.
	*/
	renderPinnedMessage(message) {
		const isOwnMessage = String(message.authorId) === String(this.getUserId());
		const options = this.options;
		const pinnedMessageElement = $(options.messageReferenceTemplate({
			text: message.text,
			files: message.files,
			isDeleted: message.isDeleted,
			isOwnMessage,
			messages: options.messages,
			isPinMessage: true,
			renderCloseButton: true,
			renderFileMenuButton: false
		}));
		this.wrapper.find(`[${REFERENCES.messageReferencePinWrapper}]`).remove();
		const messageList = this.wrapper.find("." + STYLES.viewWrapper);
		if (messageList.length) messageList.prepend(pinnedMessageElement);
		else if (this._hasHeader) pinnedMessageElement.insertAfter(this.wrapper.find("." + STYLES.header));
		else this.wrapper.prepend(pinnedMessageElement);
		this.view._initFileMenus(pinnedMessageElement);
	}
	/**
	* Clears the pinned message.
	*/
	clearPinnedMessage() {
		this._dataManager.clearPinnedMessage();
		this.wrapper.find("." + STYLES.messagePinned).remove();
	}
	/**
	* Clears the reply state.
	*/
	clearReplyState() {
		this.messageBox.removeReplyMessage();
		this.currentMessageReplyId = null;
	}
	/**
	* Scrolls the chat to the bottom.
	*/
	scrollToBottom() {
		this.view.scrollToBottom();
	}
	/**
	* Scrolls to a specific message.
	*/
	scrollToMessage(uid) {
		return this.view.scrollToMessage(uid);
	}
	_getEndlessDataCount() {
		return this._dataManager.getTotalCount();
	}
	_getLatestRange(totalCount = this._getEndlessDataCount()) {
		return this._endlessRangeManager.getLatestRange(this._getEndlessPageSize(), totalCount);
	}
	_getTargetRange(targetIndex, totalCount = this._getEndlessDataCount()) {
		return this._endlessRangeManager.getTargetRange(targetIndex, this._getEndlessPageSize(), totalCount);
	}
	_isLatestRenderedRange(totalCount = this._getEndlessDataCount()) {
		return this._endlessRangeManager.isLatestRenderedRange(this._renderEndIndex, totalCount);
	}
	_hasRenderedLatestMessage(totalCount = this._getEndlessDataCount()) {
		return this._endlessRangeManager.hasRenderedLatestMessage(this._renderEndIndex, totalCount);
	}
	_isEndlessScrollEnabled(options = this.options) {
		return options.scrollMode === "endless";
	}
	_updateEndlessState() {
		const options = this.options;
		if (!this._isEndlessScrollEnabled(options) || !this.view) return;
		const totalAvailable = this._getEndlessDataCount();
		this.view._scrollManager._allOlderLoaded = this._renderStartIndex <= 0;
		this.view._scrollManager._allNewerLoaded = this._renderEndIndex >= totalAvailable;
		this.view.refreshScrollState();
	}
	_completeEndlessLoad(direction, callback) {
		requestAnimationFrame(() => {
			if (!this.view) {
				callback?.();
				return;
			}
			if (direction === "older" || direction === "all") this.view._scrollManager._isLoadingOlder = false;
			if (direction === "newer" || direction === "all") this.view._scrollManager._isLoadingNewer = false;
			this.view.refreshScrollState();
			callback?.();
		});
	}
	_renderEndlessRange(startIndex, endIndex, options = {}) {
		const messages = this._dataManager.getMessagesInRange(startIndex, endIndex);
		this.view.clearMessages();
		messages.forEach((message) => {
			this.renderMessage(message);
		});
		this._renderStartIndex = startIndex;
		this._renderEndIndex = endIndex;
		this._updateEndlessState();
		this.view.renderSuggestedActions();
		this.view.updateSeparator();
		this.accessibility.setupAriaAttributes();
		this.accessibility.setupBubbleTabNavigation();
		requestAnimationFrame(() => {
			if (!this.view) return;
			if (options.scrollToBottom) this.scrollToBottom();
			else if (options.scrollToMessageUid) this.scrollToMessage(options.scrollToMessageUid);
			this.view.refreshScrollState();
		});
	}
	_prepareLatestMessages() {
		this._endlessScrollCoordinator.prepareLatestMessages();
	}
	_navigateToReferencedMessage(id) {
		const message = this._dataManager.getMessageById(id);
		const loadedMessage = this._dataManager.getLoadedMessageById(id);
		this.trigger(EVENTS.referencedMessageClick, { id });
		this._endlessScrollCoordinator.navigateToReferencedMessage(id, message, loadedMessage);
	}
	/**
	* Gets or sets the loading state (transforms send button to stop button).
	*/
	loading(value) {
		if (value === void 0) return this._loading;
		this._loading = value;
		if (this.wrapper) this.wrapper.toggleClass(STYLES.generating, value);
		if (this.messageBox) this.messageBox.loading(value);
	}
	/**
	* @deprecated Use `loading()` instead. Will be removed in future version.
	*/
	toggleSendButtonGenerating(value) {
		this.loading(value);
	}
	/**
	* @internal
	* Refreshes the chat view when data changes.
	*/
	refresh(e) {
		if (e?.action === "add") this._bumpRemoteEndlessTotalForLocalAdd(e.items);
		const state = this._createRefreshState(e);
		if (!e?.action) this._refreshFull(state);
		this._refreshPinnedState(state.pinnedMessage);
		if (e?.action === "sync" || e?.action === "add") this._refreshSync(state);
		this._finalizeRefresh();
	}
	_bumpRemoteEndlessTotalForLocalAdd(items) {
		if (!this._isEndlessScrollEnabled() || !this._isRemoteEndlessScroll() || !items?.length) return;
		const newTotal = this._dataManager.getLoadedRange().startIndex + this._dataManager.getLoadedCount();
		if (newTotal > this.dataSource.total()) {
			this.dataSource._total = newTotal;
			this.dataSource._pristineTotal = newTotal;
		}
	}
	_createRefreshState(e) {
		return {
			data: this.dataSource.data ? this.dataSource.data() : this.dataSource.view(),
			changedItems: e?.changedItems || [],
			pinnedMessage: this._dataManager.getPinnedMessage(),
			totalCount: this._getEndlessDataCount(),
			addedCount: e?.items?.length ?? 0
		};
	}
	_refreshFull(state) {
		this._refreshNoDataTemplate(state.data);
		if (this._isEndlessScrollEnabled()) {
			this._refreshEndlessFull(state.totalCount);
			return;
		}
		this._refreshStandardFull(state.data);
	}
	_refreshNoDataTemplate(data) {
		const options = this.options;
		if (data.length === 0 && options.noDataTemplate) this.view.showNoData(options.noDataTemplate());
		else if (options.noDataTemplate) this.view.hideNoData();
	}
	_refreshEndlessFull(totalCount) {
		this.view._scrollManager._isLoadingOlder = true;
		this.view._scrollManager._isLoadingNewer = false;
		this.view.clearMessages();
		if (totalCount <= 0) {
			this._resetEmptyEndlessRefresh();
			return;
		}
		if (this._isRemoteEndlessScroll()) {
			this._refreshRemoteEndlessFull(totalCount);
			return;
		}
		this._refreshLocalEndlessFull(totalCount);
	}
	_refreshRemoteEndlessFull(totalCount) {
		const latestRange = this._getLatestRange(totalCount);
		if (!this._dataManager.isRangeLoaded(latestRange.startIndex, latestRange.endIndex)) {
			this._remoteRangeLoader.requestRange({
				startIndex: latestRange.startIndex,
				endIndex: latestRange.endIndex,
				direction: "latest",
				reason: "scroll-to-bottom",
				mode: "replace",
				loadingState: "all",
				scrollToBottom: true
			});
			return;
		}
		this._renderEndlessRange(latestRange.startIndex, latestRange.endIndex, { scrollToBottom: true });
		this._completeEndlessLoad("all");
	}
	_refreshLocalEndlessFull(totalCount) {
		const latestRange = this._getLatestRange(totalCount);
		this._renderEndlessRange(latestRange.startIndex, latestRange.endIndex, { scrollToBottom: true });
		this._completeEndlessLoad("older");
	}
	_resetEmptyEndlessRefresh() {
		this._renderStartIndex = 0;
		this._renderEndIndex = 0;
		this._updateEndlessState();
		this.scrollToBottom();
		this._completeEndlessLoad("older");
	}
	_refreshStandardFull(data) {
		this.view.clearMessages();
		data.forEach((item) => {
			this.renderMessage(item);
		});
		this.scrollToBottom();
	}
	_refreshPinnedState(pinnedMessage) {
		if (pinnedMessage) {
			this.renderPinnedMessage(pinnedMessage);
			return;
		}
		this.wrapper.find(`[${REFERENCES.messageReferencePinWrapper}]`).remove();
	}
	_refreshSync(state) {
		if (this.options.noDataTemplate && !this.view.hasMessages()) this.view.hideNoData();
		const message = state.changedItems.length ? state.changedItems[0] : state.data[state.data.length - 1];
		const isOwnMessage = !!(message && String(message.authorId) === String(this.getUserId()));
		const wasNearBottom = this.view.isNearBottom();
		const isRenderedMessage = !!(message?.uid && this.view.hasMessage(message.uid));
		if (this._isEndlessScrollEnabled()) {
			this._refreshEndlessSync(message, isOwnMessage, wasNearBottom, isRenderedMessage, state.totalCount, state.addedCount);
			return;
		}
		this._refreshStandardSync(message, isOwnMessage, wasNearBottom);
	}
	_refreshEndlessSync(message, isOwnMessage, wasNearBottom, isRenderedMessage, totalCount, addedCount) {
		if (!this.view.hasMessages() && totalCount > 0) {
			this._prepareLatestMessages();
			return;
		}
		if (isOwnMessage && totalCount > 0) {
			this._refreshOwnEndlessSync(totalCount);
			return;
		}
		const extendsLatestRange = addedCount > 0 && this._canAppendOwnEndlessSync(totalCount);
		if (message && (isRenderedMessage || this._isLatestRenderedRange(totalCount) || extendsLatestRange)) {
			this._refreshVisibleEndlessSyncMessage(message, isOwnMessage, wasNearBottom, isRenderedMessage, totalCount);
			if (extendsLatestRange) this.scrollToBottom();
			return;
		}
		this._updateEndlessState();
	}
	_refreshOwnEndlessSync(totalCount) {
		if (this._isRemoteEndlessScroll() && this._canAppendOwnEndlessSync(totalCount)) {
			this._dataManager.getMessagesInRange(this._renderEndIndex, totalCount).forEach((message) => {
				this.renderMessage(message);
			});
			this._renderEndIndex = totalCount;
			this._updateEndlessState();
			this.scrollToBottom();
			return;
		}
		const latestRange = this._getLatestRange(totalCount);
		if (this._isRemoteEndlessScroll() && !this._dataManager.isRangeLoaded(latestRange.startIndex, latestRange.endIndex)) {
			this._prepareLatestMessages();
			return;
		}
		this._renderEndlessRange(latestRange.startIndex, latestRange.endIndex, { scrollToBottom: true });
	}
	_canAppendOwnEndlessSync(totalCount) {
		const loadedRange = this._dataManager.getLoadedRange();
		return this._renderEndIndex < totalCount && loadedRange.endIndex === totalCount && this._renderEndIndex >= loadedRange.startIndex;
	}
	_refreshVisibleEndlessSyncMessage(message, isOwnMessage, wasNearBottom, isRenderedMessage, totalCount) {
		this.renderMessage(message);
		if (!isRenderedMessage) this._renderEndIndex = totalCount;
		this._updateEndlessState();
		if (!isOwnMessage && wasNearBottom && message.uid) this.view.scrollIncomingMessage(message.uid);
	}
	_refreshStandardSync(message, isOwnMessage, wasNearBottom) {
		if (!message) return;
		this.renderMessage(message);
		if (isOwnMessage) this.scrollToBottom();
		else if (wasNearBottom && message.uid) this.view.scrollIncomingMessage(message.uid);
	}
	_finalizeRefresh() {
		this.view.renderSuggestedActions();
		this.view.updateSeparator();
		this.view.refreshScrollState();
		this.accessibility.setupAriaAttributes();
		this.accessibility.setupBubbleTabNavigation();
	}
	_isRemoteEndlessScroll() {
		const options = this.options;
		return !!(this._isEndlessScrollEnabled(options) && this.dataSource.options?.serverPaging);
	}
	_prependLocalEndlessRange(startIndex, endIndex) {
		const messages = this._dataManager.getMessagesInRange(startIndex, endIndex);
		if (!messages.length) {
			this._updateEndlessState();
			return;
		}
		this.view._scrollManager._isLoadingOlder = true;
		this.view._scrollManager.preserveScrollPosition(() => {
			this.view._messageRenderer.prependMessages(messages);
		});
		this._renderStartIndex = startIndex;
		this._updateEndlessState();
		this._completeEndlessLoad("older");
		this.accessibility.setupAriaAttributes();
		this.accessibility.setupBubbleTabNavigation();
	}
	_appendLocalEndlessRange(startIndex, endIndex) {
		const messages = this._dataManager.getMessagesInRange(startIndex, endIndex);
		if (!messages.length) {
			this._updateEndlessState();
			return;
		}
		this.view._scrollManager._isLoadingNewer = true;
		messages.forEach((message) => {
			this.renderMessage(message);
		});
		this._renderEndIndex = endIndex;
		this._updateEndlessState();
		this._completeEndlessLoad("newer");
		this.accessibility.setupAriaAttributes();
		this.accessibility.setupBubbleTabNavigation();
	}
	_loadOlderMessages() {
		this._endlessScrollCoordinator.loadOlderMessages();
	}
	_loadNewerMessages() {
		this._endlessScrollCoordinator.loadNewerMessages();
	}
	/**
	* @internal
	*/
	commandExecute(e) {
		this._commandHandler.commandExecute(e);
	}
	/**
	* @internal
	*/
	messageReply(message) {
		this._commandHandler.messageReply(message);
	}
	/**
	* @internal
	* Copies message text to clipboard.
	*/
	messageCopy(message) {
		this._commandHandler.messageCopy(message);
	}
	/**
	* @internal
	* Pins a message.
	*/
	messagePin(message) {
		this._commandHandler.messagePin(message);
	}
	/**
	* @internal
	* Deletes a message.
	*/
	messageDelete(message) {
		this._commandHandler.messageDelete(message);
	}
	/**
	* @internal
	*/
	messageContextMenuOpen(e) {
		this._commandHandler.messageContextMenuOpen(e);
	}
	/**
	* Destroys the widget and cleans up resources.
	*/
	destroy() {
		this._remoteRangeLoader.reset();
		if (this.dataSource) {
			this._unbindDataSource();
			this.dataSource = null;
		}
		if (this.view) {
			this.view.unbind();
			this.view.destroy();
			this.view = null;
		}
		if (this.messageBox) {
			this.messageBox.unbind();
			this.messageBox.destroy();
			this.messageBox = null;
		}
		if (this.messageContextMenu) this.messageContextMenu.destroy();
		if (this.wrapper) {
			this.wrapper.off();
			this.wrapper.empty();
			this.wrapper = null;
		}
		super.destroy();
	}
};
//#endregion
//#region ../src/kendo.chat.js
/**
* Kendo UI Chat Widget
*
* This file serves as the entry point for the Chat widget.
*/
const __meta__ = {
	id: "chat",
	name: "Chat",
	category: "web",
	description: "The Chat component.",
	depends: [
		"data",
		"draganddrop",
		"html.button",
		"textarea",
		"menu",
		"avatar",
		"toolbar",
		"speechtotextbutton"
	]
};
window.kendo.chat = {};
$.extend(window.kendo.chat, {
	ChatView: {},
	Component: {},
	Components: {},
	Templates: {},
	getTemplate: () => require_kendo_core.utilsService.logToConsole("The getTemplate method is deprecated. Use one of the built-in templates or append elements manually.", "warn"),
	getComponent: () => require_kendo_core.utilsService.logToConsole("The getComponent method is deprecated. Use one of the built-in templates or append elements manually.", "warn"),
	registerTemplate: () => require_kendo_core.utilsService.logToConsole("The registerTemplate method is deprecated. Use one of the built-in templates or append elements manually.", "warn"),
	registerComponent: () => require_kendo_core.utilsService.logToConsole("The registerComponent method is deprecated. Use one of the built-in templates or append elements manually.", "warn")
});
require_kendo_core.widgetRegistryService.register(Chat);
var kendo_chat_default = kendo;
//#endregion
Object.defineProperty(exports, "Chat", {
	enumerable: true,
	get: function() {
		return Chat;
	}
});
Object.defineProperty(exports, "__meta__", {
	enumerable: true,
	get: function() {
		return __meta__;
	}
});
Object.defineProperty(exports, "kendo_chat_default", {
	enumerable: true,
	get: function() {
		return kendo_chat_default;
	}
});