UNPKG

@progress/kendo-angular-dropdowns

Version:

A wide variety of native Angular dropdown components including AutoComplete, ComboBox, DropDownList, DropDownTree, MultiColumnComboBox, MultiSelect, and MultiSelectTree

416 lines (415 loc) 12.6 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ /* eslint-disable no-bitwise */ import { isDocumentAvailable } from '@progress/kendo-angular-common'; import { getter as fieldAccessor } from '@progress/kendo-common'; /** * @hidden */ export const isPresent = (value) => value !== null && value !== undefined; /** * @hidden */ export const isNumber = (value) => !isNaN(value); /** * @hidden */ export const guid = () => { let id = ""; let i; let random; for (i = 0; i < 32; i++) { random = Math.random() * 16 | 0; if (i === 8 || i === 12 || i === 16 || i === 20) { id += "-"; } id += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16); } return id; }; /** * @hidden */ export const combineStr = (begin, end) => { return begin.concat(end.substr(end.toLowerCase().indexOf(begin.toLowerCase()) + begin.length)); }; /** * @hidden */ export const isWindowAvailable = () => typeof window !== 'undefined'; /** * @hidden */ export const isArray = (value) => Array.isArray(value); /** * @hidden */ export const isObject = (value) => isPresent(value) && typeof value === 'object'; /** * @hidden */ export const isEmptyString = (value) => typeof value === 'string' && value.length === 0; /** * @hidden */ export const resolveValuesInArray = (values, data = [], valueField) => values .map(value => { return data.find(item => getter(item, valueField) === value); }) .filter(value => value !== undefined); /** * @hidden */ export const validateComplexValues = (values, valueField) => isArray(values) && values.filter(item => { return isObject(item) && getter(item, valueField) !== undefined; }); /** * @hidden */ export const resolveAllValues = (value, data, valueField) => { const customValues = validateComplexValues(value, valueField) || []; const resolvedValues = resolveValuesInArray(value, data, valueField) || []; return resolvedValues.concat(customValues); }; /** * @hidden */ export const isObjectArray = (values) => { return isArray(values) && values.every(item => isObject(item)); }; /** * @hidden */ export const selectedIndices = (values, data, valueField) => { const extractedValues = data.map(item => { return isPresent(item) && isPresent(getter(item, valueField)) ? getter(item, valueField) : item; }); return values.reduce((arr, item) => { const value = isPresent(item) && isPresent(getter(item, valueField)) ? getter(item, valueField) : item; const index = extractedValues.indexOf(value); if (index !== -1) { arr.push(index); } return arr; }, []); }; /** * @hidden */ export const getter = (dataItem, field) => { if (!isPresent(dataItem)) { return null; } if (!isPresent(field) || !isObject(dataItem)) { return dataItem; } // creates a field accessor supporting nested fields processing const valueFrom = fieldAccessor(field); return valueFrom(dataItem); }; /** * @hidden */ export const resolveValue = (args) => { let dataItem; if (isPresent(args.value)) { const data = [args.defaultItem, ...args.data]; dataItem = data.find(element => getter(element, args.valueField) === args.value); return { dataItem: dataItem, focused: args.data.indexOf(dataItem), selected: args.data.indexOf(dataItem) }; } else if (args.index) { dataItem = args.data[args.index]; return { dataItem: args.data[args.index], focused: args.index, selected: args.index }; } return { dataItem: args.defaultItem, focused: -1, selected: -1 }; }; /** * @hidden */ export const sameCharsOnly = (word, character) => { for (let idx = 0; idx < word.length; idx++) { if (word.charAt(idx) !== character) { return false; } } return true; }; /** * @hidden */ export const shuffleData = (data, splitIndex, defaultItem) => { let result = data; if (defaultItem) { result = [defaultItem].concat(result); } return result.slice(splitIndex).concat(result.slice(0, splitIndex)); }; /** * @hidden */ export const matchText = (text, word, ignoreCase) => { if (!isPresent(text)) { return false; } let temp = String(text); if (ignoreCase) { temp = temp.toLowerCase(); } return temp.indexOf(word) === 0; }; /** * @hidden */ export const elementFromPoint = (x, y) => { if (!isDocumentAvailable()) { return; } return document.elementFromPoint(x, y); }; /** * @hidden * * Checks whether the passed object has all of the listed properties. */ export const hasProps = (obj, props) => { if (!isPresent(obj)) { return false; } return props.every(prop => obj.hasOwnProperty(prop)); }; /** * @hidden * * Checks whether an element is untouched by looking for the ng-untouched css class */ export const isUntouched = (element) => element.className.includes('ng-untouched'); /** * @hidden */ export const noop = (_) => { }; /** * IE element `matches` polyfill. * https://developer.mozilla.org/en-US/docs/Web/API/Element/matches */ const matches = (element, selector) => { const matcher = element.matches || element.msMatchesSelector || element.webkitMatchesSelector; if (!matcher) { return false; } return matcher.call(element, selector); }; /** * @hidden * * IE element `closest` polyfill. * https://developer.mozilla.org/en-US/docs/Web/API/Element/closest */ export const closest = (element, selector) => { let parent = element; while (parent !== null && parent.nodeType === 1) { if (matches(parent, selector)) { return parent; } parent = parent.parentElement || parent.parentNode; } return null; }; /** * @hidden * * Parses a provided value to its type 'number' representation. * If the parsed value (via Number(value)) is NaN, the provided default value is returned. * Uses 0 as default value if a second param is not provided. */ export const parseNumber = (num, defaultValue = 0) => { const normalizedValue = Number(num); return isNaN(normalizedValue) ? defaultValue : normalizedValue; }; /** * @hidden * * Checks whether the passed target element is inside the provided host or popupRef. */ export const inDropDown = (host, target, popupRef) => { return host.nativeElement.contains(target) || (popupRef && popupRef.popupElement.contains(target)); }; /** * @hidden * * Calculates the hierarchical level of an item, based on the provided index. * The result level is zero-based (starts from 0). */ export const getHierarchicalItemLevel = (index) => { return (index || '').split('_').length - 1; }; /** * @hidden * * Retrieves all descendant nodes' lookups which are currently registered in the provided lookup item as a flat array. */ export const fetchDescendentNodes = (lookup, filterExpression) => { if (!isPresent(lookup) || lookup.children.length === 0) { return []; } let descendants = lookup.children; if (isPresent(filterExpression)) { descendants = descendants.filter(descendent => filterExpression(descendent.item)); } descendants.forEach(child => descendants = descendants.concat(fetchDescendentNodes(child, filterExpression))); return descendants; }; /** * @hidden * * Retrieves the correct value based on the item's level and the provided value field/s. * Used in the MultiSelectTree component. */ export const valueFrom = ({ dataItem, index, level }, valueField) => { const fields = Array.isArray(valueField) ? valueField : [valueField]; // either use the explicitly provided value level, or infer it from the item index const valueLevel = isPresent(level) ? level : getHierarchicalItemLevel(index); // fall-back to the last available one, if the current node is in a deeper level const normalizedLevel = Math.min(valueLevel, fields.length - 1); const field = fields[normalizedLevel]; return fieldAccessor(field)(dataItem); }; /** * @hidden * Returns the size class based on the component and size input. */ export const getSizeClass = (component, size) => { const SIZE_CLASSES = { 'small': `k-${component}-sm`, 'medium': `k-${component}-md`, 'large': `k-${component}-lg` }; return SIZE_CLASSES[size]; }; /** * @hidden * Returns the rounded class based on the rounded input. */ export const getRoundedClass = (rounded) => { const ROUNDED_CLASSES = { 'small': 'k-rounded-sm', 'medium': 'k-rounded-md', 'large': 'k-rounded-lg', 'full': 'k-rounded-full' }; return ROUNDED_CLASSES[rounded]; }; /** * @hidden * Return the fillMode class based on the component and fillMode input. */ export const getFillModeClass = (component, fillMode) => { const FILLMODE_CLASSES = { 'solid': `k-${component}-solid`, 'flat': `k-${component}-flat`, 'outline': `k-${component}-outline` }; return FILLMODE_CLASSES[fillMode]; }; /** * @hidden */ export const filterAndMap = (arr, predicate, mapper) => arr.reduce((acc, curr) => predicate(curr) ? [...acc, mapper(curr)] : acc, []); /** * @hidden * * Checks if input is Japanese IME */ export const isJapanese = (input) => { const japaneseRegex = /[\u3000-\u303F]|[\u3040-\u309F]|[\u30A0-\u30FF]|[\uFF00-\uFFEF]|[\u4E00-\u9FAF]|[\u2605-\u2606]|[\u2190-\u2195]|\u203B/g; return japaneseRegex.test(input); }; /** * @hidden */ export const isLetter = (text) => { const isLetter = /[a-zA-Z]/; return isLetter.test(text) && text?.length === 1; }; /** * @hidden */ export const getTextField = (field, level) => { if (isArray(field)) { return field[level]; } return field; }; /** * @hidden */ export const getSearchableItems = (treeViewId, element) => { const nodeSeletor = `kendo-treeview[id='${treeViewId}'] li.k-treeview-item`; const liElements = Array.from(element.querySelectorAll(nodeSeletor)); return liElements.map((liElement) => { return { text: liElement.innerText, index: liElement.getAttribute('data-treeindex') }; }); }; /** * @hidden */ export const isTruthy = (value) => !!value; /** * @hidden */ export const updateActionSheetAdaptiveAppearance = (actionSheet, windowSize, renderer) => { const element = actionSheet['element'].nativeElement.querySelector('.k-actionsheet'); const animationContainer = actionSheet['element'].nativeElement.querySelector('.k-child-animation-container'); if (windowSize === 'medium') { renderer.removeClass(element, 'k-actionsheet-fullscreen'); renderer.addClass(element, 'k-actionsheet-bottom'); renderer.addClass(element, 'k-adaptive-actionsheet'); renderer.removeStyle(animationContainer, 'top'); renderer.removeStyle(animationContainer, 'height'); renderer.setStyle(animationContainer, 'bottom', '0px'); } else if (windowSize === 'small') { renderer.removeClass(element, 'k-actionsheet-bottom'); renderer.addClass(element, 'k-actionsheet-fullscreen'); renderer.addClass(element, 'k-adaptive-actionsheet'); renderer.setStyle(animationContainer, 'bottom', '0px'); renderer.setStyle(animationContainer, 'height', '100%'); } }; /** * @hidden */ export const setListBoxAriaLabelledBy = (optionsList, element, renderer) => { const listBox = optionsList.wrapper.nativeElement.querySelector('kendo-list ul'); const ariaLabel = element.nativeElement.getAttribute('aria-labelledby') || element.nativeElement.getAttribute('data-kendo-label-id'); if (ariaLabel) { renderer.setAttribute(listBox, 'aria-labelledby', ariaLabel); } }; /** * @hidden */ export const setActionSheetTitle = (element, actionSheetTitle) => { const ariaLabel = element.nativeElement.getAttribute('aria-labelledby') || element.nativeElement.getAttribute('data-kendo-label-id'); if (!actionSheetTitle && ariaLabel) { return document.getElementById(ariaLabel).innerText; } return actionSheetTitle; }; /** * @hidden */ export const animationDuration = 300;