UNPKG

uicore-ts

Version:

UICore is a library to build native-like user interfaces using pure Typescript. No HTML is needed at all. Components are described as TS classes and all user interactions are handled explicitly. This library is strongly inspired by the UIKit framework tha

984 lines (666 loc) 22.8 kB
import { UICoreExtensionValueObject } from "./UICoreExtensionValueObject" import { UIObject } from "./UIObject" declare global { interface Array<T> { removeElementAtIndex(index: number): void; removeElement(element: T): void; insertElementAtIndex(index: number, element: T): void; replaceElementAtIndex(index: number, element: T): void; contains(element: T): boolean; findAsyncSequential(functionToCall: (value: any) => Promise<boolean>): Promise<any>; groupedBy<T>(keyFunction: (item: T) => any): { [key: string]: Array<T> } & Object; uniqueMap<T, R>(keyFunction: (item: T) => R): R[]; copy(): Array<T>; arrayByRepeating(numberOfRepetitions: number): Array<T>; arrayByTrimmingToLengthIfLonger(maxLength: number): Array<T>; anyMatch(predicate: (value: T, index: number, obj: T[]) => boolean): boolean noneMatch(predicate: (value: T, index: number, obj: T[]) => boolean): boolean allMatch(predicate: (value: T, index: number, obj: T[]) => boolean): boolean firstElement: T; lastElement: T; readonly summedValue: T; everyElement: UIEveryElementItem<T>; max(): number; min(): number; average(): number; isEqualToArray(array: Array<T>, keyPath?: string): boolean; } interface String { contains(string: string): boolean; readonly numericalValue: number; readonly integerValue: number; isAString: boolean; } interface Number { isANumber: boolean; readonly integerValue: number; constrainedValue(min: number, max: number): number; } interface Date { readonly dateString: string; } interface Object { forEach(callbackFunction: (value: any, key: string, stopLooping: () => void) => void): void; objectByCopyingValuesRecursivelyFromObject<T extends object>(object: T): T & this; readonly allValues: Array<any>; readonly allKeys: (keyof this)[]; } } export {} const YES = true const NO = false if ("removeElementAtIndex" in Array.prototype == NO) { (Array.prototype as any).removeElementAtIndex = function (this: Array<any>, index: number) { // @ts-ignore if (index >= 0 && index < this.length) { this.splice(index, 1) } } } // interface Array<T> { // // removeElementAtIndex(index: number); // // } if ("removeElement" in Array.prototype == NO) { (Array.prototype as any).removeElement = function (this: Array<any>, element: any) { this.removeElementAtIndex(this.indexOf(element)) } } // interface Array<T> { // // removeElement(element: T); // // } if ("insertElementAtIndex" in Array.prototype == NO) { (Array.prototype as any).insertElementAtIndex = function (this: Array<any>, index: number, element: any) { if (index >= 0 && index <= this.length) { this.splice(index, 0, element) } } } // interface Array<T> { // // insertElementAtIndex(index: number, element: T); // // } if ("replaceElementAtIndex" in Array.prototype == NO) { (Array.prototype as any).replaceElementAtIndex = function (this: Array<any>, index: number, element: any) { this.removeElementAtIndex(index) this.insertElementAtIndex(index, element) } } // interface Array<T> { // // replaceElementAtIndex(index: number, element: T); // // } if ("contains" in Array.prototype == NO) { (Array.prototype as any).contains = function (this: Array<any>, element: any) { return (this.indexOf(element) != -1) } } if ("containsAny" in Array.prototype == NO) { (Array.prototype as any).containsAny = function (this: Array<any>, elements: any[]) { return this.anyMatch(element => elements.contains(element)) } } // interface Array<T> { // // contains(element: T): boolean; // // containsAny(element: T[]): boolean; // // } if ("anyMatch" in Array.prototype == NO) { (Array.prototype as any).anyMatch = function ( this: Array<any>, functionToCall: (value: any, index: number, array: any[]) => boolean ) { // @ts-ignore return (this.findIndex(functionToCall) > -1) } } if ("noneMatch" in Array.prototype == NO) { (Array.prototype as any).noneMatch = function ( this: Array<any>, functionToCall: (value: any, index: number, array: any[]) => boolean ) { // @ts-ignore return (this.findIndex(functionToCall) == -1) } } if ("allMatch" in Array.prototype == NO) { (Array.prototype as any).allMatch = function ( this: Array<any>, functionToCall: (value: any, index: number, array: any[]) => boolean ) { function reversedFunction(value: any, index: number, array: any[]) { return !functionToCall(value, index, array) } // @ts-ignore return (this.findIndex(reversedFunction) == -1) } } if ("findAsyncSequential" in Array.prototype == NO) { (Array.prototype as any).findAsyncSequential = function ( this: Array<any>, functionToCall: (value: any) => Promise<boolean> ) { // https://stackoverflow.com/questions/55601062/using-an-async-function-in-array-find async function findAsyncSequential<T>( array: T[], predicate: (t: T) => Promise<boolean> ): Promise<T | undefined> { for (const t of array) { if (await predicate(t)) { return t } } return undefined } return findAsyncSequential(this, functionToCall) } } // interface Array<T> { // // anyMatch(predicate: (value: T, index: number, obj: T[]) => boolean): boolean // // noneMatch(predicate: (value: T, index: number, obj: T[]) => boolean): boolean // // allMatch(predicate: (value: T, index: number, obj: T[]) => boolean): boolean // // } if ("groupedBy" in Array.prototype == NO) { Array.prototype.groupedBy = function (this: Array<any>, funcProp) { return this.reduce(function (acc, val) { (acc[funcProp(val)] = acc[funcProp(val)] || []).push(val) return acc }, {}) } } if ("uniqueMap" in Array.prototype == NO) { Array.prototype.uniqueMap = function (this: Array<any>, funcProp) { const result: any[] = [] for (let i = 0; i < this.length; i++){ const element = this[i] const elementResult = funcProp(element) if (!result.contains(elementResult)) { result.push(elementResult); } } return result; } } // interface Array<T> { // // groupedBy(keyFunction: (item: T) => any): { [key: string]: Array<T> }; // // } if ("firstElement" in Array.prototype == NO) { Object.defineProperty(Array.prototype, "firstElement", { get: function firstElement(this: Array<any>) { return this[0] }, set: function (this: Array<any>, element: any) { if (this.length == 0) { this.push(element) return } this[0] = element } }) } if ("lastElement" in Array.prototype == NO) { Object.defineProperty(Array.prototype, "lastElement", { get: function lastElement(this: Array<any>) { return this[this.length - 1] }, set: function (this: Array<any>, element: any) { if (this.length == 0) { this.push(element) return } this[this.length - 1] = element } }) } if ("everyElement" in Array.prototype == NO) { Object.defineProperty(Array.prototype, "everyElement", { get: function everyElement(this: Array<any>) { const valueKeys: string[] = [] const targetFunction = (objects: any) => { return this.map((element) => { const thisObject = UIObject.valueForKeyPath( valueKeys.arrayByTrimmingToLengthIfLonger(valueKeys.length - 1).join("."), element ) || element const elementFunction = (UIObject.valueForKeyPath(valueKeys.join("."), element) as Function)?.bind( thisObject, objects ) return elementFunction?.() }) } const result: any = new Proxy( targetFunction, { get: (target, key: string, _receiver) => { if (key == "UI_elementValues") { return this.map(element => UIObject.valueForKeyPath( valueKeys.join("."), element )) } valueKeys.push(key) return result }, set: (target, key: string, value, _receiver) => { valueKeys.push(key) this.forEach(element => UIObject.setValueForKeyPath(valueKeys.join("."), value, element, YES)) return true } } ) return result }, set: function (this: Array<any>, element: any) { for (let i = 0; i < this.length; ++i) { this[i] = element } } }) } export type UIEveryElementItem<T> = { [P in keyof T]: UIEveryElementItem<T[P]> } & { UI_elementValues?: T[]; } & T // interface Array<T> { // // firstElement: T; // lastElement: T; // // everyElement: UIEveryElementItem<T>; // // } if ("copy" in Array.prototype == NO) { (Array.prototype as any).copy = function (this: Array<any>) { return this.slice(0) } } // interface Array<T> { // // copy(): Array<T>; // // } if ("arrayByRepeating" in Array.prototype == NO) { (Array.prototype as any).arrayByRepeating = function (this: Array<any>, numberOfRepetitions: number) { const result: any[] = [] for (let i = 0; i < numberOfRepetitions; i++) { this.forEach(element => result.push(element)) } return result } } // interface Array<T> { // // arrayByRepeating(numberOfRepetitions: number): Array<T>; // // } if ("arrayByTrimmingToLengthIfLonger" in Array.prototype == NO) { (Array.prototype as any).arrayByTrimmingToLengthIfLonger = function (this: Array<any>, maxLength: number) { const result = [] for (let i = 0; i < maxLength && i < this.length; i++) { result.push(this[i]) } return result } } // interface Array<T> { // // arrayByTrimmingToLengthIfLonger(maxLength: number): Array<T>; // // } if ("summedValue" in Array.prototype == NO) { Object.defineProperty(Array.prototype, "summedValue", { get: function summedValue(this: Array<any>) { return this.reduce(function (a, b) { return a + b }, 0) } }) } // interface Array<T> { // // readonly summedValue: T; // // max(): number; // min(): number; // // // } Array.prototype.max = function () { return Math.max.apply(null, this) } Array.prototype.min = function () { return Math.min.apply(null, this) } if (!Array.prototype.average) { Array.prototype.average = function () { if (this.length == 0) { return 0; } const sum = this.reduce((a, b) => a + b, 0) return sum / this.length } } // interface Array<T> { // // isEqualToArray(array: Array<T>, keyPath?: string): boolean; // // } if ("isEqualToArray" in Array.prototype == NO) { // attach the .equals method to Array's prototype to call it on any array Array.prototype.isEqualToArray = function (array: any[], keyPath?: string) { // if the other array is a falsy value, return if (!array) { return false } // compare lengths - can save a lot of time if (this.length != array.length) { return false } var i = 0 const l = this.length for (; i < l; i++) { // Check if we have nested arrays if (this[i] instanceof Array && array[i] instanceof Array && !keyPath) { // recurse into the nested arrays if (!this[i].isEqualToArray(array[i])) { return false } } else if (keyPath && UIObject.valueForKeyPath(keyPath, this[i]) != UIObject.valueForKeyPath( keyPath, array[i] )) { return false } else if (this[i] != array[i]) { // Warning - two different object instances will never be equal: {x:20} != {x:20} return false } } return true } // Hide method from for-in loops Object.defineProperty(Array.prototype, "isEqualToArray", { enumerable: false }) } if ("forEach" in Object.prototype == NO) { (Object.prototype as any).forEach = function ( this: Record<string, any>, callbackFunction: ( value: any, key: string, stopLooping: Function ) => void ) { const keys = Object.keys(this) let shouldStopLooping = NO function stopLooping() { shouldStopLooping = YES } keys.anyMatch(key => { callbackFunction(this[key], key, stopLooping) return shouldStopLooping }) } // Hide method from for-in loops Object.defineProperty(Object.prototype, "forEach", { enumerable: false }) } // interface Object { // // forEach(callbackFunction: (value: any, key: string) => void): void; // // } if ("allValues" in Object.prototype == NO) { Object.defineProperty(Object.prototype, "allValues", { get: function (this: Object) { const values: any[] = [] this.forEach((value: any) => { values.push(value) }) return values }, enumerable: NO }) } // interface Object { // // readonly allValues: Array<any>; // // } if ("allKeys" in Object.prototype == NO) { Object.defineProperty(Object.prototype, "allKeys", { get: function (this: Object) { return Object.keys(this) }, enumerable: NO }) } // interface Object { // // readonly allKeys: string[]; // // } if ("objectByCopyingValuesRecursivelyFromObject" in Object.prototype == NO) { (Object.prototype as any).objectByCopyingValuesRecursivelyFromObject = function (this: Object, object: any) { function isAnObject(item: any) { return (item && typeof item === "object" && !Array.isArray(item)) } function mergeRecursively(target: any, source: any) { const output = Object.assign({}, target) if (isAnObject(target) && isAnObject(source)) { Object.keys(source).forEach(function (key) { if (isAnObject(source[key])) { // if (!(key in target)) { // Object.assign(output, { [key]: source[key] }); // } // else { output[key] = mergeRecursively(target[key] ?? {}, source[key]) //} } else { Object.assign(output, { [key]: source[key] }) } }) } return output } return mergeRecursively(this, object) } // Hide method from for-in loops Object.defineProperty(Object.prototype, "objectByCopyingValuesRecursivelyFromObject", { enumerable: false }) } if ("asValueObject" in Object.prototype == NO) { (Object.prototype as any).asValueObject = function () { return new UICoreExtensionValueObject(this) } // Hide method from for-in loops Object.defineProperty(Object.prototype, "asValueObject", { enumerable: false }) } // interface Object { // // objectByCopyingValuesRecursivelyFromObject<T>(object: T): this & T; // // asValueObject(): this; // // } // export type Unpacked<T> = // T extends (infer U)[] // ? U // : T extends (...args: any[]) => infer U // ? U // : T extends Promise<infer U> // ? U // : T // // export type UnpackedObject<T> = { // [P in keyof T]: Unpacked<T[P]> // } // export function promisedProperties<ObjectType extends Record<string, any>>(object: ObjectType): UnpackedObject<ObjectType> { // // let promisedProperties: any[] = [] // const objectKeys = Object.keys(object) // // objectKeys.forEach((key) => promisedProperties.push(object[key])) // // // @ts-ignore // return Promise.all(promisedProperties) // .then((resolvedValues) => { // return resolvedValues.reduce((resolvedObject, property, index) => { // resolvedObject[objectKeys[index]] = property // return resolvedObject // }, object) // }) // // } // if ("promisedProperties" in Object.prototype == NO) { // // (Object.prototype as any).promisedProperties = function () { // // const result = promisedProperties(this); // // return result // // } // // // Hide method from for-in loops // Object.defineProperty(Object.prototype, "promisedProperties", { enumerable: false }); // // } // // // interface Object { // // readonly promisedProperties: UnpackedObject<this>; // // } if ("contains" in String.prototype == NO) { (String.prototype as any).contains = function (this: String, string: string) { return (this.indexOf(string) != -1) } // Hide method from for-in loops Object.defineProperty(Object.prototype, "contains", { enumerable: false }) } // interface String { // // contains(string): boolean; // // } if ("capitalizedString" in String.prototype == NO) { Object.defineProperty(Object.prototype, "capitalizedString", { get: function (this: String) { return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase() }, enumerable: NO }) } // interface String { // // readonly capitalizedString: string; // // } if ("numericalValue" in String.prototype == NO) { Object.defineProperty(String.prototype, "numericalValue", { get: function numericalValue(this: string) { return Number(this) } }) } if ("integerValue" in String.prototype == NO) { Object.defineProperty(String.prototype, "integerValue", { get: function integerValue(this: string) { return Number(this).integerValue } }) } // interface String { // // readonly numericalValue: number; // // } if ("isAString" in String.prototype == NO) { (String.prototype as any).isAString = YES } // interface String { // // isAString: boolean; // // } if ("isANumber" in Number.prototype == NO) { (Number.prototype as any).isANumber = YES } // interface Number { // // isANumber: boolean; // // } if ("integerValue" in Number.prototype == NO) { Object.defineProperty(Number.prototype, "integerValue", { get: function (this: number) { return parseInt("" + (Math.round(this) + 0.5)) }, enumerable: NO }) } if ("constrainedValue" in Number.prototype == NO) { (Number.prototype as any).constrainedValue = function (this: number, min: number, max: number) { if (this < min) { return min; } if (this > max) { return max; } return this } // Hide method from for-in loops Object.defineProperty(Number.prototype, "constrainedValue", { enumerable: NO }) } // interface Number { // // readonly integerValue: number; // // } if ("integerValue" in Boolean.prototype == NO) { Object.defineProperty(Boolean.prototype, "integerValue", { get: function (this: boolean) { if (this == true) { return 1 } return 0 } }) } // interface Boolean { // // readonly integerValue: number; // // } if ("dateString" in Date.prototype == NO) { Object.defineProperty(Date.prototype, "dateString", { get: function dateString(this: Date) { return ("0" + this.getDate()).slice(-2) + "-" + ("0" + (this.getMonth() + 1)).slice(-2) + "-" + this.getFullYear() + " " + ("0" + this.getHours()).slice(-2) + ":" + ("0" + this.getMinutes()).slice(-2) } }) } // interface Date { // // readonly dateString: string; // // }