@progress/kendo-ui
Version:
This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.
1,720 lines (1,707 loc) • 221 kB
JavaScript
const require_kendo_licensing = require('./kendo.licensing-B1rACieL.js');
//#region ../src/utils/mediaquery.js
let breakpoints;
const EVENT = "change";
const defaultBreakpoints = {
small: "(max-width: 500px)",
medium: "(min-width: 500.1px) and (max-width: 768px)",
large: "(min-width: 768.1px)"
};
function createMediaQuery(query) {
let mediaQueryList = window.matchMedia(query);
let onEnterCallbacks = [];
let onLeaveCallbacks = [];
let onChangeHandlers = [];
let kendoMediaQuery = { mediaQueryList };
const onChangeHandler = (ev) => {
onChangeHandlers.forEach((cb) => cb(ev));
if (ev.matches) {
onEnterCallbacks.forEach((cb) => cb(ev));
} else {
onLeaveCallbacks.forEach((cb) => cb(ev));
}
};
mediaQueryList.addEventListener(EVENT, onChangeHandler);
const onChange = (cb) => {
onChangeHandlers.push(cb);
return kendoMediaQuery;
};
const onEnter = (cb) => {
onEnterCallbacks.push(cb);
if (mediaQueryList.matches) {
const media = mediaQueryList.media;
const matches = true;
const ev = new MediaQueryListEvent(EVENT, {
media,
matches
});
cb(ev);
}
return kendoMediaQuery;
};
const onLeave = (cb) => {
onLeaveCallbacks.push(cb);
return kendoMediaQuery;
};
const destroy = () => {
if (mediaQueryList) {
mediaQueryList.removeEventListener(EVENT, onChangeHandler);
}
onEnterCallbacks = null;
onLeaveCallbacks = null;
onChangeHandlers = null;
mediaQueryList = null;
kendoMediaQuery = null;
};
kendoMediaQuery.onChange = onChange;
kendoMediaQuery.onEnter = onEnter;
kendoMediaQuery.onLeave = onLeave;
kendoMediaQuery.destroy = destroy;
return kendoMediaQuery;
}
function mediaQuery(query) {
if (!query) {
return;
}
breakpoints = breakpoints || Object.assign({}, defaultBreakpoints, kendo.defaults.breakpoints);
if (query in breakpoints) {
query = breakpoints[query];
}
return createMediaQuery(query);
}
//#endregion
//#region ../src/utils/convert-class.js
function fromESClass(ESClass) {
class ExtendedClass extends ESClass {
static extend(proto) {
const subclass = class extends ExtendedClass {
constructor() {
super();
if (proto && proto.init) {
proto.init.apply(this, arguments);
}
}
};
Object.assign(subclass.prototype, proto);
addInstanceGetter(subclass.prototype);
subclass.fn = subclass.prototype;
return subclass;
}
}
addInstanceGetter(ExtendedClass.prototype);
ExtendedClass.fn = ExtendedClass.prototype;
return ExtendedClass;
}
function addInstanceGetter(proto) {
Object.defineProperty(proto, "_instance", { get: function() {
return this;
} });
}
//#endregion
//#region ../src/core/models/dependency-container.ts
/**
* Service Container Models
*
* Simple DI container for Kendo widgets. Services can be:
* - Singleton: one instance shared everywhere
* - Scoped: new instance created per resolution
*/
let ServiceLifetime = /* @__PURE__ */ function(ServiceLifetime) {
ServiceLifetime["Singleton"] = "singleton";
ServiceLifetime["Scoped"] = "scoped";
return ServiceLifetime;
}({});
//#endregion
//#region ../src/core/models/date-utils.ts
/**
* Date utility type definitions
*/
/**
* Date field mapping for format parsing
*/
const DATE_FIELD_MAP = {
"G": "era",
"y": "year",
"q": "quarter",
"Q": "quarter",
"M": "month",
"L": "month",
"d": "day",
"E": "weekday",
"c": "weekday",
"e": "weekday",
"h": "hour",
"H": "hour",
"k": "hour",
"K": "hour",
"m": "minute",
"s": "second",
"a": "dayperiod",
"t": "dayperiod",
"x": "zone",
"X": "zone",
"z": "zone",
"Z": "zone"
};
/**
* Name types for date formatting
*/
const NAME_TYPES = {
month: {
type: "months",
minLength: 3,
standAlone: "L"
},
quarter: {
type: "quarters",
minLength: 3,
standAlone: "q"
},
weekday: {
type: "days",
minLength: {
E: 0,
c: 3,
e: 3
},
standAlone: "c"
},
dayperiod: {
type: "dayPeriods",
minLength: 0
},
era: {
type: "eras",
minLength: 0
}
};
//#endregion
//#region ../src/core/models/dom-utils.ts
/**
* Animation directions
*/
const DIRECTIONS = {
left: { reverse: "right" },
right: { reverse: "left" },
down: { reverse: "up" },
up: { reverse: "down" },
top: { reverse: "bottom" },
bottom: { reverse: "top" },
"in": { reverse: "out" },
out: { reverse: "in" }
};
//#endregion
//#region ../src/core/service-container.ts
/**
* Service Container
*
* A minimal DI container for resolving services in widget constructors.
* Uses class constructors as tokens for type-safe injection.
*
* Usage:
* ```ts
* // Registration (during kendo.core.js init)
* serviceContainer.singleton(UtilsService);
* serviceContainer.scoped(SomeScopedService);
*
* // Or register an existing instance
* serviceContainer.singletonInstance(UtilsService, existingUtilsService);
*
* // In widgets - use inject() with default parameter
* class Chat extends Widget {
* constructor(
* element: HTMLElement,
* options: ChatOptions,
* utils = inject(UtilsService)
* ) {
* super(element, options);
* this.utils = utils;
* }
* }
* ```
*/
var ServiceContainer = class {
constructor() {
this.registrations = new Map();
}
/**
* Register a class as a singleton service.
* One instance will be created and shared everywhere.
*/
singleton(ctor, factory) {
this.registrations.set(ctor, {
ctor,
factory: factory ?? (() => new ctor()),
lifetime: ServiceLifetime.Singleton,
instance: undefined
});
}
/**
* Register an existing instance as a singleton.
* Useful when the instance is already created (e.g., during kendo.core.js init).
*/
singletonInstance(ctor, instance) {
this.registrations.set(ctor, {
ctor,
factory: () => instance,
lifetime: ServiceLifetime.Singleton,
instance
});
}
/**
* Register a class as a scoped service.
* A new instance will be created each time it's resolved.
*/
scoped(ctor, factory) {
this.registrations.set(ctor, {
ctor,
factory: factory ?? (() => new ctor()),
lifetime: ServiceLifetime.Scoped,
instance: undefined
});
}
/**
* Resolve a service by its class constructor.
* - Singleton: returns cached instance (creates on first call)
* - Scoped: creates new instance each time
*/
resolve(ctor) {
const registration = this.registrations.get(ctor);
if (!registration) {
throw new Error(`Service ${ctor.name} is not registered`);
}
if (registration.lifetime === ServiceLifetime.Singleton) {
if (registration.instance === undefined) {
registration.instance = registration.factory();
}
return registration.instance;
}
return registration.factory();
}
/**
* Try to resolve a service, returns undefined if not registered.
*/
tryResolve(ctor) {
if (!this.has(ctor)) {
return undefined;
}
return this.resolve(ctor);
}
/**
* Check if a service is registered.
*/
has(ctor) {
return this.registrations.has(ctor);
}
/**
* Get the lifetime of a registered service.
*/
getLifetime(ctor) {
return this.registrations.get(ctor)?.lifetime;
}
};
/** Global service container instance */
const serviceContainer = new ServiceContainer();
/**
* Inject a service by its class constructor.
* Use as default parameter value in constructors.
*
* @example
* class Chat extends Widget {
* constructor(
* element: HTMLElement,
* options: ChatOptions,
* utils = inject(UtilsService)
* ) {
* this.utils = utils;
* }
* }
*/
function inject(ctor) {
return serviceContainer.resolve(ctor);
}
//#endregion
//#region ../src/core/services/kendo-jquery.service.ts
const STRING$5 = "string";
const UNDEFINED$2 = "undefined";
/**
* KendoJQuery Service
* Creates and manages the KendoJQuery wrapper around jQuery
*/
var KendoJQueryService = class {
constructor($, support, mouseEventNormalizer, eventMapService, utils, noDeprecateExtend) {
this.$ = $;
this.support = support;
this.mouseEventNormalizer = mouseEventNormalizer;
this.eventMapService = eventMapService;
this.utils = utils;
this.originalOn = $.fn.on;
this.kendoJQuery = this.createKendoJQuery(noDeprecateExtend);
this.rootjQuery = this.kendoJQuery(document);
}
/**
* Create the KendoJQuery constructor and prototype
*/
createKendoJQuery(noDeprecateExtend) {
const $ = this.$;
const self = this;
const kendoJQuery = function(selector, context) {
return new kendoJQuery.fn.init(selector, context);
};
noDeprecateExtend(true, kendoJQuery, $);
kendoJQuery.fn = kendoJQuery.prototype = new $();
kendoJQuery.fn.constructor = kendoJQuery;
kendoJQuery.fn.init = function(selector, context) {
if (context && context instanceof $ && !(context instanceof kendoJQuery)) {
context = kendoJQuery(context);
}
return $.fn.init.call(this, selector, context, self.rootjQuery);
};
kendoJQuery.fn.init.prototype = kendoJQuery.fn;
$.extend(kendoJQuery.fn, {
handler: function(handler) {
this.data("handler", handler);
return this;
},
autoApplyNS: function(ns) {
this.data("kendoNS", ns || self.utils.guid());
return this;
},
on: function(...args) {
const that = this;
const ns = that.data("kendoNS");
const on = self.originalOn;
if (args.length === 1) {
return on.call(that, args[0]);
}
let context = that;
const argsCopy = args.slice();
if (typeof argsCopy[argsCopy.length - 1] === UNDEFINED$2) {
argsCopy.pop();
}
const callback = argsCopy[argsCopy.length - 1];
const events = self.eventMapService.applyEventMap(argsCopy[0], ns);
if (self.support.mouseAndTouchPresent && events.search(/mouse|click/) > -1 && this[0] !== document.documentElement) {
self.mouseEventNormalizer.setupMouseMute();
const selector = argsCopy.length === 2 ? null : argsCopy[1];
const bustClick = events.indexOf("click") > -1 && events.indexOf("touchend") > -1;
on.call(this, {
touchstart: (e) => self.mouseEventNormalizer.muteMouse(e),
touchend: () => self.mouseEventNormalizer.unMuteMouse()
}, selector, { bustClick });
}
if (argsCopy[0].indexOf("keydown") !== -1 && argsCopy[1] && argsCopy[1].options) {
argsCopy[0] = events;
const widget = argsCopy[1];
const keyDownCallback = argsCopy[argsCopy.length - 1];
argsCopy[argsCopy.length - 1] = function(e) {
if (self.keyDownHandler(e, widget)) {
return keyDownCallback.apply(this, [e]);
}
};
on.apply(that, argsCopy);
return that;
}
if (typeof callback === STRING$5) {
context = that.data("handler");
const callbackFn = context[callback];
argsCopy[argsCopy.length - 1] = function(e) {
callbackFn.call(context, e);
};
}
argsCopy[0] = events;
on.apply(that, argsCopy);
return that;
},
kendoDestroy: function(ns) {
ns = ns || this.data("kendoNS");
if (ns) {
this.off("." + ns);
}
return this;
}
});
return kendoJQuery;
}
/**
* Get the KendoJQuery constructor function
*/
getConstructor() {
return this.kendoJQuery;
}
/**
* Create a KendoJQuery wrapper
*/
create(selector, context) {
return this.kendoJQuery(selector, context);
}
/**
* Handle keydown events for a widget
* Executes all kendoKeydown handlers and checks if event should be prevented
*/
keyDownHandler(e, widget) {
const events = widget._events.kendoKeydown;
if (!events) {
return true;
}
const eventsCopy = events.slice();
e.sender = widget;
e.preventKendoKeydown = false;
for (let idx = 0, length = eventsCopy.length; idx < length; idx++) {
eventsCopy[idx].call(widget, e);
}
return !e.preventKendoKeydown;
}
};
//#endregion
//#region ../src/core/base/class.ts
/**
* Base Class for Kendo UI.
* All Kendo classes inherit from this base class.
*/
var Class = class {
/**
* Creates a subclass with the given prototype.
* This is the legacy extend pattern used throughout Kendo UI.
*
* This method:
* 1. Creates a new constructor function that extends the current class
* 2. Copies all properties from proto to the new prototype
* 3. For plain object members, performs deep merge with base class members using $.extend
* 4. Sets up the extend method on the subclass for further inheritance
*
* @param proto - Object containing methods and properties for the subclass
* @returns New constructor function for the subclass
*
* @example
* var MyClass = Class.extend({
* init: function(options) {
* this.options = options;
* },
* myMethod: function() {
* return this.options;
* }
* });
*
* var instance = new MyClass({ foo: 'bar' });
*/
static extend(proto) {
const that = this;
const base = function() {};
base.prototype = that.prototype;
const subclass = proto && proto.init ? proto.init : function(...args) {
if (that.prototype.init) {
that.prototype.init.apply(this, args);
}
};
const fn = subclass.fn = subclass.prototype = new base();
if (proto) {
for (const member in proto) {
if (proto[member] != null && proto[member].constructor === Object) {
fn[member] = inject(KendoJQueryService).getConstructor().extend(true, {}, base.prototype[member], proto[member]);
} else {
fn[member] = proto[member];
}
}
}
fn.constructor = subclass;
subclass.extend = that.extend;
return subclass;
}
};
Class.fn = Class.prototype;
/**
* Define init and _initOptions on the prototype as ENUMERABLE properties.
*
* ES6 class methods are non-enumerable by default, which breaks the
* legacy extend() pattern because when we do:
* base.prototype = that.prototype;
* fn = new base();
*
* The inherited methods from the ES6 class prototype don't appear in
* for...in loops or when the object is logged/inspected. This causes
* issues when deepExtend copies class instances - the non-enumerable
* methods are not properly inherited through the prototype chain.
*
* By defining these methods directly on the prototype (like the original
* Kendo Class did), we ensure they're enumerable and properly inherited.
*/
Class.prototype.init = function(..._args) {};
Class.prototype._initOptions = function(options) {
this.options = inject(KendoJQueryService).getConstructor().extend(true, {}, this.options, options);
};
//#endregion
//#region ../src/core/base/observable.ts
/**
* Kendo UI Observable Class
*
* Provides event binding, unbinding, and triggering functionality.
* This is the foundation for all event-driven components in Kendo UI.
*
* Extends Class and supports both ES6 and legacy extend() patterns.
*
*
* @example
* // ES6 class inheritance
* class MyComponent extends Observable {
* doSomething() {
* this.trigger("change", { value: 42 });
* }
* }
*
* @example
* // Legacy extend pattern
* const MyComponent = Observable.extend({
* init: function() {
* Observable.fn.init.call(this);
* this.value = 0;
* },
* setValue: function(val) {
* this.value = val;
* this.trigger("change", { value: val });
* }
* });
*
* const component = new MyComponent();
* component.bind("change", function(e) {
* console.log("Value changed to:", e.value);
* });
* component.setValue(42);
*/
const STRING$4 = "string";
const FUNCTION$2 = "function";
/**
* preventDefault helper function.
* Called on event object to prevent default action.
* Uses regular function to preserve `this` context (the event object).
*/
function preventDefault() {
this._defaultPrevented = true;
}
/**
* isDefaultPrevented helper function.
* Called on event object to check if default was prevented.
* Uses regular function to preserve `this` context (the event object).
*/
function isDefaultPrevented() {
return this._defaultPrevented === true;
}
/**
* Observable class for event handling.
* All Kendo UI widgets and data components inherit from this class.
*/
var Observable = class extends Class {
/**
* Constructor - initializes the Observable's _events storage.
*/
constructor() {
super();
this._events = {};
}
/**
* Initialize the Observable instance.
* Sets up the internal _events storage.
* Accepts any arguments to allow subclasses to override with different signatures.
*
* Note: For direct Observable usage, this is called by the constructor.
* For subclasses, they should call Observable.fn.init.call(this) in their init.
*/
init(..._args) {
this._events = {};
}
/**
* Binds one or more event handlers to the observable.
*
* Supports multiple calling patterns:
* - bind("event", handler)
* - bind(["event1", "event2"], handler)
* - bind({ event1: handler1, event2: handler2 })
* - bind("event", handler, true) // one-time binding
*
* @param eventName - Event name, array of names, or object map
* @param handlers - Handler function or map of handlers
* @param one - If true, handler is removed after first invocation
* @returns this for chaining
*/
bind(eventName, handlers, one) {
const that = this;
let idx;
let length;
let original;
let handler;
const handlersIsFunction = typeof handlers === FUNCTION$2;
let events;
if (handlers === undefined) {
const eventMap = eventName;
for (idx in eventMap) {
that.bind(idx, eventMap[idx]);
}
return that;
}
const eventNames = typeof eventName === STRING$4 ? [eventName] : eventName;
for (idx = 0, length = eventNames.length; idx < length; idx++) {
const currentEventName = eventNames[idx];
handler = handlersIsFunction ? handlers : handlers[currentEventName];
if (handler) {
if (one) {
original = handler;
handler = (function(evtName, originalHandler) {
const wrappedHandler = function() {
that.unbind(evtName, wrappedHandler);
originalHandler.apply(that, arguments);
};
wrappedHandler.original = originalHandler;
return wrappedHandler;
})(currentEventName, original);
}
events = that._events[currentEventName] = that._events[currentEventName] || [];
events.push(handler);
}
}
return that;
}
/**
* Binds an event handler that will be removed after first invocation.
*
* @param eventNames - Event name or array of names
* @param handlers - Handler function or map of handlers
* @returns this for chaining
*/
one(eventNames, handlers) {
return this.bind(eventNames, handlers, true);
}
/**
* Binds an event handler at the beginning of the handler list.
* The handler will be invoked before other handlers.
*
* @param eventName - Event name or array of names
* @param handlers - Handler function or map of handlers
* @returns this for chaining
*/
first(eventName, handlers) {
const that = this;
let idx;
const eventNames = typeof eventName === STRING$4 ? [eventName] : eventName;
const length = eventNames.length;
let handler;
const handlersIsFunction = typeof handlers === FUNCTION$2;
let events;
for (idx = 0; idx < length; idx++) {
const currentEventName = eventNames[idx];
handler = handlersIsFunction ? handlers : handlers[currentEventName];
if (handler) {
events = that._events[currentEventName] = that._events[currentEventName] || [];
events.unshift(handler);
}
}
return that;
}
/**
* Triggers an event, invoking all bound handlers.
*
* @param eventName - Name of the event to trigger
* @param e - Optional event data object
* @returns true if preventDefault() was called, false otherwise
*/
trigger(eventName, e) {
const that = this;
let events = that._events[eventName];
let idx;
const length = events ? events.length : 0;
if (events) {
const eventObj = e || {};
eventObj.sender = that;
eventObj._defaultPrevented = false;
eventObj.preventDefault = preventDefault;
eventObj.isDefaultPrevented = isDefaultPrevented;
events = events.slice();
for (idx = 0; idx < length; idx++) {
events[idx].call(that, eventObj);
}
return eventObj._defaultPrevented === true;
}
return false;
}
/**
* Unbinds event handlers.
*
* - unbind() - removes all handlers for all events
* - unbind("event") - removes all handlers for specific event
* - unbind("event", handler) - removes specific handler
*
* @param eventName - Optional event name
* @param handler - Optional specific handler to remove
* @returns this for chaining
*/
unbind(eventName, handler) {
const that = this;
const events = eventName ? that._events[eventName] : undefined;
let idx;
if (eventName === undefined) {
that._events = {};
} else if (events) {
if (handler) {
for (idx = events.length - 1; idx >= 0; idx--) {
if (events[idx] === handler || events[idx].original === handler) {
events.splice(idx, 1);
}
}
} else {
that._events[eventName] = [];
}
}
return that;
}
};
/**
* Make Observable methods ENUMERABLE by deleting and reassigning them.
*
* ES6 class methods are non-enumerable by default, which breaks legacy patterns
* like `$.extend({}, observableInstance, ...)` which copies properties using
* for...in loops. By deleting and reassigning, we create enumerable properties.
*/
const proto = Observable.prototype;
const methods = [
"init",
"bind",
"one",
"first",
"trigger",
"unbind"
];
methods.forEach((method) => {
const fn = proto[method];
Object.defineProperty(proto, method, {
value: fn,
writable: true,
configurable: true,
enumerable: true
});
});
Observable.fn = Observable.prototype;
//#endregion
//#region ../src/core/services/dom-utils.service.ts
const PERCENT_REGEXP = /%/;
const BOX_SHADOW_REGEXP = /(\d+(?:\.?)\d*)px\s*(\d+(?:\.?)\d*)px\s*(\d+(?:\.?)\d*)px\s*(\d+)?/i;
/**
* Service providing DOM utility functions
*/
var DomUtilsService = class {
constructor(supportService, $, namespaceService, utils, kendo) {
this.supportService = supportService;
this.$ = $;
this.namespaceService = namespaceService;
this.utils = utils;
this.kendo = kendo;
this.animationQueue = [];
const win = window;
this.animationFrameFn = win.requestAnimationFrame || win.webkitRequestAnimationFrame || win.mozRequestAnimationFrame || win.oRequestAnimationFrame || win.msRequestAnimationFrame || ((callback) => {
setTimeout(callback, 1e3 / 60);
});
}
isElement(element) {
return element instanceof Element || element instanceof HTMLDocument;
}
/**
* Get outer width of element
*/
outerWidth(element, includeMargin, calculateFromHidden) {
const $element = this.$(element);
if (calculateFromHidden) {
return this.getHiddenDimensions($element, includeMargin).width;
}
return $element.outerWidth(includeMargin || false) || 0;
}
/**
* Get outer height of element
*/
outerHeight(element, includeMargin, calculateFromHidden) {
const $element = this.$(element);
if (calculateFromHidden) {
return this.getHiddenDimensions($element, includeMargin).height;
}
return $element.outerHeight(includeMargin || false) || 0;
}
/**
* Get computed styles for an element
*/
getComputedStyles(element, properties) {
const styles = {};
let computedStyle;
if (document.defaultView && document.defaultView.getComputedStyle) {
computedStyle = document.defaultView.getComputedStyle(element, "");
if (properties) {
this.$.each(properties, (_idx, value) => {
styles[value] = computedStyle.getPropertyValue(value);
});
}
} else {
computedStyle = element.currentStyle;
if (properties) {
this.$.each(properties, (_idx, value) => {
styles[value] = computedStyle[this.utils.toCamelCase(value)];
});
}
}
if (!this.utils.size(styles)) {
return computedStyle;
}
return styles;
}
/**
* Check if an element is scrollable
*/
isScrollable(element) {
const dataset = element.dataset;
if (dataset[this.namespaceService.ns + "scrollable"] === "false") {
return false;
}
if (typeof element?.className === "string" && element.className.indexOf("k-auto-scrollable") > -1) {
return true;
}
const overflow = this.getComputedStyles(element, ["overflow"]).overflow || "";
return overflow.indexOf("auto") > -1 || overflow.indexOf("scroll") > -1;
}
/**
* Get or set scroll left position (RTL-aware)
*/
scrollLeft(element, value) {
const webkit = this.supportService.browser.webkit;
const mozilla = this.supportService.browser.mozilla;
const browserVersion = this.supportService.browser.version;
if (element instanceof this.$ && value !== undefined) {
element.each((_i, e) => {
this.scrollLeft(e, value);
});
return;
}
const el = element instanceof this.$ ? element[0] : element;
if (!el) {
return;
}
const isRtl = this.supportService.isRtl(element);
if (value !== undefined) {
if (isRtl && webkit && (browserVersion < 85 || this.supportService.browser.safari)) {
el.scrollLeft = el.scrollWidth - el.clientWidth - value;
} else if (isRtl && (mozilla || webkit) && value > 0) {
el.scrollLeft = -value;
} else {
el.scrollLeft = value;
}
} else if (isRtl && webkit && (browserVersion < 85 || this.supportService.browser.safari)) {
return el.scrollWidth - el.clientWidth - el.scrollLeft;
} else {
return Math.abs(el.scrollLeft);
}
}
/**
* Get element offset position
*/
getOffset(element, type = "offset", positioned) {
const offset = element[type]();
const result = {
top: offset.top,
right: offset.right,
bottom: offset.bottom,
left: offset.left
};
if (this.supportService.browser.msie && (this.supportService.pointers || this.supportService.msPointers) && !positioned) {
const sign = this.supportService.isRtl(element) ? 1 : -1;
result.top -= window.pageYOffset - document.documentElement.scrollTop;
result.left -= window.pageXOffset + sign * document.documentElement.scrollLeft;
}
return result;
}
/**
* Get dimensions of a hidden element by temporarily cloning and showing it
*/
getHiddenDimensions(element, includeMargin) {
const clone = element.clone();
clone.css("display", "");
clone.css("visibility", "hidden");
clone.appendTo(this.$("body"));
const width = clone.outerWidth(includeMargin || false);
const height = clone.outerHeight(includeMargin || false);
clone.remove();
return {
width: width || 0,
height: height || 0
};
}
/**
* Parse effects string into object
*/
parseEffects(input) {
const effects = {};
const items = typeof input === "string" ? input.split(" ") : input;
this.$.each(items, function(idx) {
effects[idx] = this;
});
return effects;
}
/**
* Remove whitespace text nodes from an element
*/
stripWhitespace(element) {
if (document.createNodeIterator) {
const iterator = document.createNodeIterator(element, NodeFilter.SHOW_TEXT, (node) => {
return node.parentNode === element ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
});
while (iterator.nextNode()) {
const refNode = iterator.referenceNode;
if (refNode && !refNode.textContent?.trim()) {
refNode.parentNode?.removeChild(refNode);
}
}
}
}
/**
* Request animation frame wrapper
*/
animationFrame(callback) {
this.animationFrameFn.call(window, callback);
}
/**
* Queue an animation callback
*/
queueAnimation(callback) {
this.animationQueue.push(callback);
if (this.animationQueue.length === 1) {
this.runNextAnimation();
}
}
/**
* Run the next animation in queue
*/
runNextAnimation() {
this.animationFrame(() => {
if (this.animationQueue[0]) {
this.animationQueue.shift()();
if (this.animationQueue[0]) {
this.runNextAnimation();
}
}
});
}
/**
* Parse query string parameters from URL
*/
parseQueryStringParams(url) {
const queryString = url.split("?")[1] || "";
const params = {};
const paramParts = queryString.split(/&|=/);
const length = paramParts.length;
for (let idx = 0; idx < length; idx += 2) {
if (paramParts[idx] !== "") {
params[decodeURIComponent(paramParts[idx])] = decodeURIComponent(paramParts[idx + 1]);
}
}
return params;
}
/**
* Get element under cursor position
*/
elementUnderCursor(e) {
if (typeof e.x.client !== "undefined") {
return document.elementFromPoint(e.x.client, e.y.client);
}
return null;
}
/**
* Get wheel delta Y from jQuery event
*/
wheelDeltaY(jQueryEvent) {
const e = jQueryEvent.originalEvent;
const deltaY = e.wheelDeltaY;
let delta;
if (e.wheelDelta) {
if (deltaY === undefined || deltaY) {
delta = e.wheelDelta;
}
} else if (e.detail && e.axis === e.VERTICAL_AXIS) {
delta = -e.detail * 10;
}
return delta;
}
/**
* Get box-shadow dimensions from an element
*/
getShadows(element) {
const shadow = element.css("box-shadow");
const radius = shadow ? BOX_SHADOW_REGEXP.exec(shadow) || [
0,
0,
0,
0,
0
] : [
0,
0,
0,
0,
0
];
const blur = Math.max(+radius[3], +(radius[4] || 0));
return {
left: -radius[1] + blur,
right: +radius[1] + blur,
bottom: +radius[2] + blur
};
}
/**
* Wrap element with animation container
*/
wrap(element, autosize, resize, shouldCorrectWidth = true, autowidth) {
let parent = element.parent();
const windowOuterWidth = this.outerWidth(window);
parent.parent().removeClass("k-animation-container-sm");
if (!parent.hasClass("k-child-animation-container")) {
const width = element[0].style.width;
const height = element[0].style.height;
const percentWidth = PERCENT_REGEXP.test(width);
const percentHeight = PERCENT_REGEXP.test(height);
const forceDimensions = element.hasClass("k-tooltip") || element.is(".k-menu-horizontal.k-context-menu");
const calculateFromHidden = element.hasClass("k-tooltip");
const percentage = percentWidth || percentHeight;
let computedWidth = width;
let computedHeight = height;
if (!percentWidth && (!autosize || autosize && width || forceDimensions)) {
computedWidth = autosize ? this.outerWidth(element, false, calculateFromHidden) + 1 : this.outerWidth(element, false, calculateFromHidden);
}
if (!percentHeight && (!autosize || autosize && height) || forceDimensions) {
computedHeight = this.outerHeight(element, false, calculateFromHidden);
}
element.wrap(this.$("<div/>").addClass("k-child-animation-container").css({
width: autowidth ? "auto" : computedWidth,
height: computedHeight
}));
parent = element.parent();
parent.wrap(this.$("<div/>").addClass("k-animation-container").attr("role", "region"));
if (percentage) {
element.css({
width: "100%",
height: "100%"
});
}
} else {
this.wrapResize(element, autosize, shouldCorrectWidth);
}
parent = parent.parent();
if (windowOuterWidth < this.outerWidth(parent)) {
parent.addClass("k-animation-container-sm");
resize = true;
}
if (resize) {
this.wrapResize(element, autosize, shouldCorrectWidth);
}
return parent;
}
/**
* Resize wrapped element
*/
wrapResize(element, autosize, shouldCorrectWidth = true) {
const parent = element.parent();
const wrapper = element.closest(".k-animation-container");
const calculateFromHidden = element.hasClass("k-tooltip");
const visible = element.is(":visible");
const wrapperStyle = parent[0].style;
const elementHeight = element[0].style.height;
if (wrapper.is(":hidden")) {
wrapper.css({
display: "",
position: ""
});
}
const percentage = PERCENT_REGEXP.test(wrapperStyle.width) || PERCENT_REGEXP.test(wrapperStyle.height);
if (!percentage) {
if (!visible) {
element.add(parent).show();
}
if (shouldCorrectWidth) {
parent.css("width", "");
}
parent.css({ width: autosize ? this.outerWidth(element, false, calculateFromHidden) + 1 : this.outerWidth(element, false, calculateFromHidden) });
if (elementHeight === "auto") {
element.css({ height: this.outerHeight(parent) });
} else {
parent.css({ height: this.outerHeight(element) });
}
if (!visible) {
element.hide();
}
}
}
/**
* Scroll element horizontally by a delta
*/
scrollByDelta(element, delta) {
const isRtl = this.supportService.isRtl(element);
const srcOffset = isRtl ? -this.scrollLeft(element) : this.scrollLeft(element);
const scrollDestination = srcOffset + delta;
const scrollWidth = element[0].scrollWidth - element[0].clientWidth;
const animationProps = { "scrollLeft": scrollDestination };
element.finish().animate(animationProps, "fast", "linear");
const maxScroll = isRtl ? -scrollWidth : scrollWidth;
const newScrollLeft = isRtl ? Math.max(Math.min(scrollDestination, 0), maxScroll) : Math.min(Math.max(scrollDestination, 0), maxScroll);
return {
atStart: newScrollLeft === 0,
atEnd: newScrollLeft === maxScroll
};
}
/**
* Scroll element vertically by a delta
*/
scrollVerticalByDelta(element, delta, options = {}) {
const { duration = "fast", easing = "linear" } = options;
const currentScrollTop = element.scrollTop() || 0;
const targetScrollTop = currentScrollTop + delta;
const animationProps = { "scrollTop": targetScrollTop };
element.finish().animate(animationProps, duration, easing);
const maxScroll = element[0].scrollHeight - element[0].clientHeight;
const newScrollTop = Math.min(Math.max(targetScrollTop, 0), maxScroll);
return {
atTop: newScrollTop === 0,
atBottom: newScrollTop >= maxScroll
};
}
/**
* Smoothly scroll to a specific element within a container
*/
scrollToElement(container, targetElement, options = {}) {
if (!container.length || !targetElement.length) {
return false;
}
const { duration = 0, easing = "linear", position = "center", offset = 0, onComplete } = options;
const containerHeight = container.height() || 0;
const containerScrollTop = container.scrollTop() || 0;
const containerOffset = container.offset();
const targetOffset = targetElement.offset();
const targetHeight = targetElement.outerHeight() || 0;
const relativeTop = targetOffset.top - containerOffset.top + containerScrollTop;
let targetScrollTop;
switch (position) {
case "top":
targetScrollTop = relativeTop + offset;
break;
case "bottom":
targetScrollTop = relativeTop - containerHeight + targetHeight + offset;
break;
case "center":
default:
targetScrollTop = relativeTop - containerHeight / 2 + targetHeight / 2 + offset;
break;
}
const maxScroll = container[0].scrollHeight - container[0].clientHeight;
targetScrollTop = Math.min(Math.max(targetScrollTop, 0), maxScroll);
container.finish().animate({ scrollTop: targetScrollTop }, duration, easing, onComplete);
return true;
}
/**
* Add a value to an element's attribute, avoiding duplicates
* If the value already exists in the attribute, it won't be added again
*/
addAttribute(element, attribute, value) {
const current = element.attr(attribute) || "";
if (current.indexOf(value) < 0) {
element.attr(attribute, (current + " " + value).trim());
}
}
/**
* Remove an attribute from an element
*/
removeAttribute(element, attribute) {
element.removeAttr(attribute);
}
/**
* Toggle an attribute value on an element
* For regular attributes: adds if not present, removes if present
* For disabled/readonly: adds only when value is truthy
*/
toggleAttribute(element, attribute, value) {
const doesNotHaveAttribute = (element.attr(attribute) || "").indexOf(value) < 0;
const disabledReadonly = ["disabled", "readonly"].indexOf(attribute) > -1;
if (doesNotHaveAttribute && !disabledReadonly) {
this.addAttribute(element, attribute, value);
} else if (disabledReadonly && value) {
this.addAttribute(element, attribute, value);
} else {
this.removeAttribute(element, attribute);
}
}
/**
* Bind a callback to the window resize event.
* On Android, the callback is delayed by 600ms to handle orientation changes properly.
* @param callback - Function to call on resize
* @returns The handler function (may be wrapped on Android)
*/
onResize(callback) {
let handler = callback;
if (this.supportService.mobileOS && this.supportService.mobileOS.android) {
handler = function() {
setTimeout(callback, 600);
};
}
this.$(window).on(this.supportService.resize, handler);
return handler;
}
/**
* Unbind a resize callback from the window.
* @param callback - The handler returned from onResize
*/
unbindResize(callback) {
this.$(window).off(this.supportService.resize, callback);
}
/**
* Get a data attribute value from an element using kendo namespace.
* @param element - jQuery element
* @param key - Attribute key (without kendo namespace prefix)
* @returns The attribute value
*/
attrValue(element, key) {
return element.data(this.namespaceService.ns + key);
}
/**
* Get the kendo data attribute name with namespace prefix.
* @param value - The attribute name without prefix
* @returns The full attribute name (e.g., "data-kendo-role" or "data-role")
*/
attr(value) {
return "data-" + this.namespaceService.ns + value;
}
/**
* Get or set element dimensions.
* @param element - jQuery element
* @param dimensions - Optional dimensions to set
* @returns Object with width and height
*/
dimensions(element, dimensions) {
const domElement = element[0];
if (dimensions) {
element.css(dimensions);
}
return {
width: domElement.offsetWidth,
height: domElement.offsetHeight
};
}
/**
* Check if an event was triggered by an input element
* @param e - Event object
* @returns True if event target is a form input element
*/
triggeredByInput(e) {
return /^(label|input|textarea|select)$/i.test(e.target.tagName);
}
/**
* Apply inline styles from kendo data attributes to elements
* @param element - Container element to search within
* @param styleProps - Array of CSS property names to apply
*/
applyStylesFromKendoAttributes(element, styleProps) {
const $ = this.$;
const selector = styleProps.map((styleProp) => `[${this.attr(`style-${styleProp}`)}]`).join(",");
element.find(selector).addBack(selector).each((_, currentElement) => {
const $currentElement = $(currentElement);
styleProps.forEach((styleProp) => {
const kendoAttr = this.attr(`style-${styleProp}`);
if ($currentElement.attr(kendoAttr)) {
$currentElement.css(styleProp, $currentElement.attr(kendoAttr));
$currentElement.removeAttr(kendoAttr);
}
});
});
}
/**
* Show or hide a loading mask on a container element
* @param container - Container element for the mask
* @param toggle - Whether to show (true) or hide (false) the mask
* @param options - Optional configuration for the mask
*/
progress(container, toggle, options) {
let mask = container.find(".k-loading-mask");
const browser = this.supportService.browser;
const opts = this.$.extend({}, {
width: "100%",
height: "100%",
top: container.scrollTop(),
opacity: false
}, options);
const cssClass = opts.opacity ? "k-loading-mask k-opaque" : "k-loading-mask";
if (toggle) {
if (!mask.length) {
const isRtl = this.supportService.isRtl(container);
const leftRight = isRtl ? "right" : "left";
const containerScrollLeft = this.scrollLeft(container);
let webkitCorrection = 0;
if (browser.webkit && isRtl) {
webkitCorrection = container[0].scrollWidth - (container.width() || 0) - 2 * containerScrollLeft;
}
const loadingText = this.kendo.ui?.progress?.messages?.loading || "Loading...";
this.$(`<div class='${cssClass}'><span role='alert' aria-live='polite' class='k-loading-text'>${loadingText}</span><div class='k-loading-image'></div><div class='k-loading-color'></div></div>`).width(opts.width).height(opts.height).css("top", opts.top).css(leftRight, Math.abs(containerScrollLeft) + webkitCorrection).prependTo(container);
}
} else if (mask) {
mask.remove();
}
}
/**
* Get the actual target element from an event, handling touch events.
* For touch events, uses document.elementFromPoint with touch coordinates.
* @param e - Event object (mouse or touch)
* @returns The target element
*/
eventTarget(e) {
if (!this.supportService.touch) {
return e.target;
}
const originalEvent = e.originalEvent;
const touches = originalEvent?.changedTouches || e.changedTouches;
if (touches && touches.length > 0) {
return document.elementFromPoint(touches[0].clientX, touches[0].clientY);
}
return e.target;
}
/**
* Create a drag-to-scroll handler for horizontal scrolling via mouse/touch drag.
* Encapsulates the drag state and event handling for scrollable containers.
* @param scrollContainer - The element that will be scrolled (or parent for delegation)
* @param options - Configuration options including namespace, capture element, and delegate selector
* @returns Handler with attach() and destroy() methods
*/
createDragToScrollHandler(scrollContainer, options) {
return new DragToScrollHandlerImpl(this.$, scrollContainer, options);
}
};
var DragToScrollHandlerImpl = class DragToScrollHandlerImpl {
static {
this.DRAG_THRESHOLD = 5;
}
constructor($, scrollContainer, options) {
this.isDragging = false;
this.hasDragged = false;
this.dragStartX = 0;
this.scrollStartLeft = 0;
this.currentDragTarget = null;
this.$ = $;
this.scrollContainer = scrollContainer;
this.namespace = options.namespace;
this.captureElement = options.captureElement;
this.delegateSelector = options.delegateSelector;
this.onDragStart = this.onDragStart.bind(this);
this.onDragMove = this.onDragMove.bind(this);
this.onDragEnd = this.onDragEnd.bind(this);
this.preventClickOnce = this.preventClickOnce.bind(this);
}
attach() {
if (!this.captureElement) {
return;
}
if (this.delegateSelector) {
this.scrollContainer.on("mousedown" + this.namespace, this.delegateSelector, this.onDragStart).on("touchstart" + this.namespace, this.delegateSelector, this.onDragStart);
} else {
this.scrollContainer.on("mousedown" + this.namespace, this.onDragStart).on("touchstart" + this.namespace, this.onDragStart);
}
this.bindCaptureEvents();
}
destroy() {
this.scrollContainer.off(this.namespace);
if (this.captureElement) {
this.unbindCaptureEvents();
}
this.scrollContainer[0].removeEventListener("click", this.preventClickOnce, true);
}
getClientX(e) {
if (e.type.indexOf("touch") !== -1) {
const touch = e.originalEvent?.touches?.[0] || e.originalEvent?.changedTouches?.[0];
return touch ? touch.clientX : 0;
}
return e.clientX || e.originalEvent?.clientX || 0;
}
preventClickOnce(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
this.scrollContainer[0].removeEventListener("click", this.preventClickOnce, true);
}
onDragStart(e) {
const target = this.$(e.currentTarget);
if (!target.length) {
return;
}
this.isDragging = true;
this.hasDragged = false;
this.currentDragTarget = target[0];
this.dragStartX = this.getClientX(e);
this.scrollStartLeft = target[0].scrollLeft;
target.css("cursor", "grabbing");
target.css("user-select", "none");
}
onDragMove(e) {
if (!this.isDragging || !this.currentDragTarget) {
return;
}
const clientX = this.getClientX(e);
const deltaX = this.dragStartX - clientX;
if (!this.hasDragged && Math.abs(deltaX) >= DragToScrollHandlerImpl.DRAG_THRESHOLD) {
this.hasDragged = true;
}
if (this.hasDragged) {
e.preventDefault();
this.currentDragTarget.scrollLeft = this.scrollStartLeft + deltaX;
}
}
onDragEnd() {
if (!this.isDragging) {
return;
}
if (this.currentDragTarget) {
const target = this.$(this.currentDragTarget);
target.css("cursor", "");
target.css("user-select", "");
}
if (this.hasDragged) {
this.scrollContainer[0].addEventListener("click", this.preventClickOnce, true);
}
this.isDragging = false;
this.hasDragged = false;
this.currentDragTarget = null;
}
bindCaptureEvents() {
if (!this.captureElement) {
return;
}
this.$(document).on("mousemove" + this.namespace, this.onDragMove).on("touchmove" + this.namespace, this.onDragMove).on("mouseup" + this.namespace, this.onDragEnd).on("touchend" + this.namespace, this.onDragEnd);
}
unbindCaptureEvents() {
this.$(document).off(this.namespace);
}
};
//#endregion
//#region ../src/core/services/utils.service.ts
const OBJECT = "object";
const UNDEFINED$1 = "undefined";
/**
* Utility service providing general helper functions.
*/
var UtilsService = class {
constructor(kendo) {
this.kendo = kendo;
this.keys = {
INSERT: 45,
DELETE: 46,
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
ESC: 27,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
END: 35,
HOME: 36,
SPACEBAR: 32,
PAGEUP: 33,
PAGEDOWN: 34,
F2: 113,
F10: 121,
F12: 123,
SHIFT: 16,
NUMPAD_PLUS: 107,
NUMPAD_MINUS: 109,
NUMPAD_DOT: 110
};
this.days = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6
};
}
/**
* Get kendo.data namespace (accessed at call time for lazy loading)
*/
get kendoData() {
return this.kendo.data || {};
}
/**
* Convert camelCase to hyphen-case
*/
toHyphens(str) {
return str.replace(/([a-z][A-Z])/g, (g) => {
return g.charAt(0) + "-" + g.charAt(1).toLowerCase();
});
}
/**
* Convert hyphen-case to camelCase
*/
toCamelCase(str) {
return str.replace(/\-(\w)/g, (_strMatch, g1) => {
return g1.toUpperCase();
});
}
/**
* Count properties in an object (excluding toJSON for IE7 compat)
*/
size(obj) {
let result = 0;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key) && key !== "toJSON") {
result++;
}
}
return result;
}
/**
* Deep extend an object with one or more source objects.
*
* IMPORTANT BEHAVIOR NOTES:
* - Does NOT copy properties with undefined values (matches jQuery.extend behavior)
* - Handles Date objects by creating new Date instances
* - Handles objects with clone() method by calling clone()
* - Skips Kendo special types: ObservableArray, LazyObservableArray, DataSource, HierarchicalDataSource
* - Skips Array, RegExp, ArrayBuffer, and HTMLElement instances
* - Protects against prototype pollution (__proto__, constructor, prototype)
*/
deepExtend(destination, ...sources) {
for (let i = 0; i < sources.length; i++) {
this.deepExtendOne(destination, sources[i]);
}
return destination;
}
/**
* Deep extend destination with a single source object.
* This is the core implementation that handles all the special cases.
*/
deepExtendOne(destination, source) {
const ObservableArray = this.kendoData.ObservableArray;
const LazyObservableArray = this.kendoData.LazyObservableArray;
const DataSource = this.kendoData.DataSource;
const HierarchicalDataSource = this.kendoData.HierarchicalDataSource;
for (const property in source) {
if (property === "__proto__" || property === "constructor" || property === "prototype") {
continue;
}
const propValue = source[property];
const propType = typeof propValue;
let propInit = null;
if (propType === OBJECT && propValue !== null) {
propInit = propValue.constructor;
}
let isRegExp = propInit?.name === "RegExp";
let isArrayBuffer = propInit?.name === "ArrayBuffer";
let isDate = propInit?.name === "Date";
if (propInit && !Array.isArray(propValue) && propInit !== ObservableArray && propInit !== LazyObservableArray && propInit !== DataSource && propInit !== HierarchicalDataSource && !isRegExp && (!this.isFunction(window.ArrayBuffer) || !isArrayBuffer) && !(propValue instanceof HTMLElement)) {
if (isDate) {
destination[property] = new Date(propValue.getTime());
} else if (this.isCloneable(propValue)) {
destination[property] = propValue.clone();
} else {
const destProp = destination[property];
if (typeof destProp === OBJECT) {
destination[property] = destProp || {};
} else {
destination[property] = {};
}
this.deepExtendOne(destination[property], propValue);
}
} else if (propType !== UNDEFINED$1) {
destination[property] = propValue;
}
}
return destination;
}
/**
* Check if an object has a clone method
*/
isCloneable(obj) {
return typeof obj.clone === "function";
}
/**
* Create a throttled version of a function.
* The throttled function will only execute at most once per delay period.
* Includes a cancel() method to clear any pending execution.
*
* If delay is falsy (0, null, undefined), returns the original function unchanged.
*/
throttle(fn, delay) {
if (!delay || delay <= 0) {
return fn;
}
let timeout;
let lastExecTime = 0;
const throttled = function(...args) {
const that = this;
const elapsed = +new Date() - lastExecTime;
function exec() {
const result = fn.apply(that, args);
lastExecTime = +new Date();
return result;
}
if (!lastExecTime) {
return exec();
}
if (timeout) {
clearTimeout(timeout);
}
if (elapsed > delay) {
return exec();
} else {
timeout = setTimeout(exec, delay - elapsed);
}
};
throttled.cancel = function() {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
};
return throttled;
}
/**
* Generate a UUID.
* Uses crypto.randomUUID() when available (HTTPS only),
* falls back to crypto.getRandomValues().
*/
guid() {
const cryptoObj = window.crypto;
try {
return cryptoObj.randomUUID();
} catch (e) {
const randomValues = cryptoObj.getRandomValues(new Uint8Array(16));
return randomValues.reduce((acc, curr, i) => {
if (i === 4 || i === 6 || i === 8 || i === 10) {
acc += "-";
}
acc += curr.toString(16).padStart(2, "0");
return acc;
}, "");
}
}
/**
* Trim whitespace from a value.
* Converts to string first, returns empty string for falsy values.
*/
trim(value) {
if (value) {
return value.toString().trim();
}
return "";
}
/**
* Check if a value is present (not null and not undefined)
*/
isPresent(value) {
return value !== null && value !== undefined;
}
/**
* Check if a value is blank (null or undefined)
*/
isBlank(value) {
return value === null || value === undefined;
}
/**
* Check if a value is empty (has length 0)
*/
i