UNPKG

@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
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