flat-type-samurai
Version:
Advanced utility types for Typescript
1,933 lines (1,782 loc) • 94.8 kB
TypeScript
/**
* Returns the second argument if the first argument is `true` (defaults to `true`), otherwise returns the third argument (defaults to `false`)
* ```ts
* // valid
* type Case1 = If<true, 'valid'>
* // invalid
* type Case2 = If<false, 'valid', 'invalid'>
* ```
*/
type If<
Condition,
IfTrue = true,
IfFalse = false
> = Condition extends true ? IfTrue : IfFalse;
/**
* Returns a boolean if the passed type is `never`
* @example
* ```ts
* // true
* type Case1 = IsNever<never>
* // false
* type Case2 = IsNever<true>
* ```
*/
type IsNever<T> = [T] extends [never] ? true : false;
/**
* Returns the second argument if the first argument is `never` (defaults to `true`), otherwise returns the third argument (defaults to `false`)
* @example
* ```ts
* // 'valid'
* type Case1 = IfNever<never, 'valid'>
* // 'invalid'
* type Case2 = IfNever<never, 'valid', 'invalid'>
* ```
*/
type IfNever<T, IfTrue = true, IfFalse = false> = If<
IsNever<T>,
IfTrue,
IfFalse
>;
type NeverifyPropertiesOptions = {
makeOptional: boolean;
};
/**
* Turns all properties of an object to type `unknown`.
* If `makeOptional` option is `true`, makes all properties optional
* @example
* ```ts
* // {a:never; b:never}
* type Result = NeverifyProperties<{a: string; b: string}>
* ```
*/
type NeverifyProperties<
T extends object,
Options extends NeverifyPropertiesOptions = { makeOptional: false }
> = {
[K in keyof T]: never;
} extends infer Result
? If<Options["makeOptional"], Partial<Result>, Result>
: never;
/**
* Returns the first argument if it is an empty array, otherwise returns `never`
* @example
* ```ts
* // never
* type Result = EmptyArray<[1]>
* ```
*/
type EmptyArray<T extends readonly unknown[]> = T extends readonly [
unknown,
...unknown[]
]
? never
: T;
/**
* Returns the first argument if it is a non empty array, otherwise returns `never`
* @example
* ```ts
* // never
* type Result = EmptyArray<[1]>
* ```
*/
type NonEmptyArray<T extends readonly unknown[]> = If<
IsNever<EmptyArray<T>>,
T,
never
>;
/**
* Returns a boolean whether the passed argument is an empty array
* @example
* ```ts
* // false
* type Result - IsEmptyArray<[1]>
*/
type IsEmptyArray<T extends readonly unknown[]> = If<
IsNever<EmptyArray<T>>,
false,
true
>;
/**
* Returns a boolean whether the passed argument is a non empty array
* @example
* ```ts
* // true
* type Result - IsEmptyArray<[1]>
*/
type IsNonEmptyArray<T extends readonly unknown[]> = If<
IsNever<EmptyArray<T>>,
true,
false
>;
/**
* Returns the second argument if the first argument is an empty array (defaults to `true`), otherwise returns the third argument (defaults to `false`)
* @example
* ```ts
* // string
* type Result = IfEmptyArray<[], string, number>
* ```
*/
type IfEmptyArray<
T extends readonly unknown[],
IfTrue = true,
IfFalse = false
> = If<IsEmptyArray<T>, IfTrue, IfFalse>;
/**
* Returns the second argument if the first argument is a non empty array (defaults to `true`), otherwise returns the third argument (defaults to `false`)
* @example
* ```ts
* // string
* type Result = IfEmptyArray<[1], string, number>
* ```
*/
type IfNonEmptyArray<
T extends readonly unknown[],
IfTrue = true,
IfFalse = false
> = If<IsNonEmptyArray<T>, IfTrue, IfFalse>;
/**
* Accepts a boolean and returns `true` if the passed type is `false`, otherwise returns `true`
* @example
* ```ts
* // false
* type Case1 = Not<true>
* // true
* type Case2 = Not<false>
* ```
*/
type Not<T extends boolean> = T extends true ? false : true;
type PopOptions = {
includeRemoved: boolean;
};
/**
* Removes last element from the first array argument.
* If the `includeRemoved` option is `true` return removed element with the new array in the format of [rest, removed]
* @example
* ```ts
* // [1, 2]
* type Case1 = Pop<[1, 2, 3]>
* // [[1, 2], 3]
* type Case2 = Pop<[1, 2, 3], {includeRemoved: true}>
* ```
*/
type Pop<
T extends readonly unknown[],
Options extends PopOptions = {
includeRemoved: false;
}
> = IsEmptyArray<T> extends true
? never
: T extends readonly [...infer Rest extends readonly unknown[], infer Removed]
? If<Options["includeRemoved"], [Rest, Removed], Rest>
: never;
/**
* Returns boolean whether the first argument extends the second argument
* @example
* ```ts
* // true
* type Case1 = Extends<1, number>
* // false
* type Case2 = Extends<number, 1>
* ```
*/
type Extends<T, Base> = [T] extends [Base] ? true : false;
/**
* Returns boolean whether the first argument doesn't extend the second argument
* @example
* ```ts
* // false
* type Case1 = Extends<1, number>
* // true
* type Case2 = Extends<number, 1>
* ```
*/
type NotExtends<T, Base> = Not<Extends<T, Base>>;
/**
* Returns the third argument if the first argument extends the second argument (defaults to `true`), otherwise returns the fourth argument (defaults to `false`)
* @example
* ```ts
* // 'valid'
* type Case1 = IfExtends<1, number, 'valid'>
* // 'invalid'
* type Case2 = IfExtends<1, string, 'valid', 'invalid'>
* ```
*/
type IfExtends<T, Base, IfTrue = true, IfFalse = false> = If<
Extends<T, Base>,
IfTrue,
IfFalse
>;
/**
* Returns the third argument if the first argument doesn't extend the second argument (defaults to `true`), otherwise returns the fourth argument (defaults to `false`)
* @example
* ```ts
* // 'valid'
* type Case1 = IfExtends<1, string, 'valid'>
* // 'invalid'
* type Case2 = IfExtends<1, number, 'valid', 'invalid'>
* ```
*/
type IfNotExtends<T, Base, IfTrue = true, IfFalse = false> = If<
NotExtends<T, Base>,
IfTrue,
IfFalse
>;
/**
* Returns boolean whether the every element of first array argument extend the second argument
* @example
* ```ts
* // true
* type Case1 = ExtendsArr<[1, 2, 3], number>
* // false
* type Case1 = ExtendsArr<[1, '2', 3], number>
* ```
*/
type ExtendsArr<
T extends readonly unknown[],
Base
> = IsEmptyArray<T> extends true
? true
: Pop<
T,
{
includeRemoved: true;
}
> extends readonly [infer Rest extends readonly unknown[], infer Removed]
? Extends<Removed, Base> extends true
? ExtendsArr<Rest, Base>
: false
: false;
/**
* Returns result of logical multiplication of two params.
* @example
* ```ts
* // true
* type Case1 = And<true, true>
* // false
* type Case2 = And<false, true>
* ```
*/
type And<Condition1, Condition2> = IfExtends<
Condition1,
true,
Extends<Condition2, true>
>;
/**
* Returns result of logical multiplication of all elements inside the passed array type
* @example
* ```ts
* // true
* type Case1 = And<[true, true, true]>
* // false
* type Case2 = And<[true, true, false]>
* ```
*/
type AndArr<Conditions extends readonly unknown[]> = Extends<
Conditions[number],
true
>;
/**
* Returns a boolean whether the passed argument is literal string
* @example
* ```ts
* // true
* type Case1 = IsStringLiteral<'a'>
* // false
* type Case2 = IsStringLiteral<string>
* ```
*/
type IsStringLiteral<T extends string> = string extends T ? false : true;
/**
* Returns the second argument if the first argument is `false` (defaults to `true`), otherwise returns the third argument (defaults to `false`)
* @example
* ```ts
* // valid
* type Case1 = IfNot<false, 'valid'>
* // invalid
* type Case2 = IfNot<false, 'valid', 'invalid'>
* ```
*/
type IfNot<Condition, IfTrue = true, IfFalse = false> = If<
Condition,
IfFalse,
IfTrue
>;
type EmptyString<T extends string> = "" extends T
? string extends T
? never
: T
: never;
type NonEmptyString<T extends string> = string extends T
? string
: If<IsNever<EmptyString<T>>, T, never>;
type IsEmptyString<T extends string> = IfNot<IsNever<EmptyString<T>>>;
type IsNonEmptyString<T extends string> = IfNot<
IsNever<NonEmptyString<T>>
>;
type IfEmptyString<
T extends string,
IfTrue = true,
IfFalse = false
> = IfNot<IsNever<EmptyString<T>>, IfTrue, IfFalse>;
type IfNonEmptyString<
T extends string,
IfTrue = true,
IfFalse = false
> = IfNot<IsNever<NonEmptyString<T>>, IfTrue, IfFalse>;
type _AreAnagrams<
Str1 extends string,
Str2 extends string
> = IsEmptyString<Str1> extends true
? IsEmptyString<Str2> extends true
? true
: false
: Str1 extends `${infer First extends string}${infer Rest1 extends string}`
? Str2 extends `${infer Prev extends string}${First}${infer Rest2 extends string}`
? _AreAnagrams<Rest1, `${Prev}${Rest2}`>
: false
: never;
/**
* Returns a boolean whether two passed string literals are anagrams
* @example
* ```ts
* // true
* type Case1 = AreAnagrams<"name", "eman">
* // false
* type Case1 = AreAnagrams<"name", "emand">
* ```
*/
type AreAnagrams<Str1 extends string, Str2 extends string> = And<
IsStringLiteral<Str1>,
IsStringLiteral<Str2>
> extends true
? _AreAnagrams<Str1, Str2>
: false;
/**
* Returns a boolean whether the passed type is `any`
* @example
* ```ts
* // true
* type Result = IsAny<any>
* ```
*/
type IsAny<T> = 0 extends 1 & T ? true : false;
/**
* Returns the second argument if the first argument is `any` (defaults to `true`), otherwise returns the third argument (defaults to `false`)
* @example
* ```ts
* // string
* type Result = IfAny<any, string, number>
* ```
*/
type IfAny<T, IfTrue = true, IfFalse = false> = If<
IsAny<T>,
IfTrue,
IfFalse
>;
type AnifyPropertiesOptions = {
makeOptional: boolean;
};
/**
* Turns all properties of an object to type `any`.
* If `makeOptional` option is `true`, makes all properties optional
* @example
* ```ts
* // {a:any; b:any}
* type Result = AnifyProperties<{a: string; b: string}>
* ```
*/
type AnifyProperties<
T extends object,
Options extends AnifyPropertiesOptions = { makeOptional: false }
> = {
[K in keyof T]: any;
} extends infer Result
? If<Options["makeOptional"], Partial<Result>, Result>
: never;
/**
* Returns the type of the element of the passed array argument
* @example
* ```ts
* // string
* type Case1 = ArrayElementType<string[]>
* // "a" | "b"
* type Case1 = ArrayElementType<readonly ("a" | "b")[]>
* ```
*/
type ArrayElementType<T extends readonly unknown[]> = T extends Readonly<
Array<infer Item>
>
? Item
: never;
type LastCharacterOptions = {
includeRest: boolean;
};
type _LastCharacter<
T extends string,
Options extends LastCharacterOptions = {
includeRest: false;
},
Previous extends string = ""
> = string extends T
? string
: T extends `${infer First}${infer Rest}`
? IsEmptyString<Rest> extends true
? If<Options["includeRest"], [First, Previous], First>
: _LastCharacter<Rest, Options, `${Previous}${First}`>
: T;
/**
* Accepts a string argument and returns its first character.
* If the `includeRest` options is `true`, returns the first character and the rest of the string in the format of: [last, rest]
* @example
* ```ts
* // 'c'
* type Case1 = LastCharacter<'abc'>
* // ['c', 'ab']
* type Case2 = LastCharacter<'abc', {includeRest: true}>
* ```
*/
type LastCharacter<
T extends string,
Options extends LastCharacterOptions = {
includeRest: false;
}
> = _LastCharacter<T, Options>;
type EvenDigit = "0" | "2" | "4" | "6" | "8";
type Integer<T extends number> = `${T}` extends `${string}.${string}`
? never
: T;
type Float<T extends number> = If<IsNever<Integer<T>>, T, never>;
type Negative<T extends number> = `${T}` extends `-${string}`
? T
: never;
type Positive<T extends number> = If<IsNever<Negative<T>>, T, never>;
type PositiveInteger<T extends number> = Positive<Integer<T>>;
type NegativeInteger<T extends number> = Negative<Integer<T>>;
type PositiveFloat<T extends number> = Positive<Float<T>>;
type NegativeFloat<T extends number> = Negative<Float<T>>;
type Even<T extends number> = IfNot<
IsNever<Integer<T>>,
`${T}` extends `${string}${EvenDigit}` ? T : never,
never
>;
type Odd<T extends number> = IfNot<
IsNever<Integer<T>>,
If<IsNever<Even<T>>, T, never>,
never
>;
type IsInteger<T extends number> = Not<IsNever<Integer<T>>>;
type IsFloat<T extends number> = Not<IsNever<Float<T>>>;
type IsEven<T extends number> = If<
IsInteger<T>,
`${T}` extends `${string}${EvenDigit}` ? true : false
>;
type IsOdd<T extends number> = If<IsInteger<T>, Not<IsEven<T>>>;
type IsPositive<T extends number> = Not<IsNever<Positive<T>>>;
type IsNegative<T extends number> = Not<IsNever<Negative<T>>>;
type IsPositiveInteger<T extends number> = Not<
IsNever<PositiveInteger<T>>
>;
type IsNegativeInteger<T extends number> = Not<
IsNever<NegativeInteger<T>>
>;
type IsPositiveFloat<T extends number> = Not<IsNever<PositiveFloat<T>>>;
type IsNegativeFloat<T extends number> = Not<IsNever<NegativeFloat<T>>>;
type IfInteger<T extends number, IfTrue = true, IfFalse = false> = If<
IsInteger<T>,
IfTrue,
IfFalse
>;
type IfFloat<T extends number, IfTrue = true, IfFalse = false> = If<
IsFloat<T>,
IfTrue,
IfFalse
>;
type IfEven<T extends number, IfTrue = true, IfFalse = false> = If<
IsEven<T>,
IfTrue,
IfFalse
>;
type IfOdd<T extends number, IfTrue = true, IfFalse = false> = If<
IsOdd<T>,
IfTrue,
IfFalse
>;
type IfPositive<T extends number, IfTrue = true, IfFalse = false> = If<
IsPositive<T>,
IfTrue,
IfFalse
>;
type IfNegative<T extends number, IfTrue = true, IfFalse = false> = If<
IsNegative<T>,
IfTrue,
IfFalse
>;
type IfPositiveInteger<
T extends number,
IfTrue = true,
IfFalse = false
> = If<IsPositiveInteger<T>, IfTrue, IfFalse>;
type IfNegativeInteger<
T extends number,
IfTrue = true,
IfFalse = false
> = If<IsNegativeInteger<T>, IfTrue, IfFalse>;
type IfPositiveFloat<
T extends number,
IfTrue = true,
IfFalse = false
> = If<IsPositiveFloat<T>, IfTrue, IfFalse>;
type IfNegativeFloat<
T extends number,
IfTrue = true,
IfFalse = false
> = If<IsNegativeFloat<T>, IfTrue, IfFalse>;
type ParseNumber<T extends string | number> =
T extends `${infer NumT extends number}` ? NumT : never;
type Abs<T extends number> =
`${T}` extends `-${infer PositiveT extends number}` ? PositiveT : T;
type Negate<T extends number> = ParseNumber<`-${Abs<T>}`>;
/**
* Returns a boolean whether the first array argument is fixed length tuple
* @example
* ```ts
* // true
* type Case1 = IsTuple<[1, 2, 3]>
* // false
* type Case2 = IsTuple<number[]>
* ```
*/
type IsTuple<T extends readonly unknown[]> = NotExtends<
number,
T["length"]
>;
/**
* Type version of `Array.prototype.join()`. Joins the first array argument by the second argument
* @example
* ```ts
* // 'a-p-p-l-e'
* type Case1 = Join<["a", "p", "p", "l", "e"], "-">
* // '21212'
* type Case2 = Join<["2", "2", "2"], 1>
* // 'o'
* type Case3 = Join<["o"], "u">
* ```
*/
type Join<
T extends readonly (string | number)[],
Glue extends string | number
> = IsTuple<T> extends true
? T extends readonly [
infer First extends string | number,
...infer Rest extends readonly (string | number)[]
]
? IfEmptyArray<Rest, First, `${First}${Glue}${Join<Rest, Glue>}`>
: never
: never;
/**
* Transform numbers, booleans, strings, bigints to string.
* ```ts
* // 'true'
* type Result = Stringify<true>
*
* ```
*/
type Stringify<T> = T extends number | boolean | string | bigint
? `${T}`
: never;
type DecrementMap = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8];
type NegativeCarryMap = {
"-1": 9;
};
type _Decrement<
Number extends string,
Result extends string = ""
> = Number extends ""
? ParseNumber<Result>
: ParseNumber<LastCharacter<Number>> extends infer LastDigit extends number
? DecrementMap[LastDigit] extends infer Decremented extends number
? Number extends `${infer Rest}${LastDigit}`
? `${Decremented}` extends keyof NegativeCarryMap
? _Decrement<Rest, `${NegativeCarryMap[`${Decremented}`]}${Result}`>
: `${Rest}${Decremented}${Result}` extends infer FinalResult extends string
? ParseNumber<
FinalResult extends `0${infer FinalResultWithoutLeadingZero extends string}`
? FinalResultWithoutLeadingZero extends ""
? FinalResult
: FinalResultWithoutLeadingZero
: FinalResult
>
: never
: never
: never
: never;
type _DecrementNegativeOrZero<T extends number> = _Increment<
Stringify<T>
> extends infer PositiveDecrementResult extends number
? PositiveDecrementResult extends 0
? PositiveDecrementResult
: Negate<PositiveDecrementResult>
: never;
/**
* Accepts an integer and returns the decremented value of it. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // 5
* type Case1 = Decrement<6>
* // -7
* type Case2 = Decrement<-6>
* ```
*/
type Decrement<T extends number> = IsNegative<T> extends true
? _DecrementNegativeOrZero<Abs<T>>
: T extends 0
? _DecrementNegativeOrZero<T>
: _Decrement<Stringify<T>>;
type IncrementMap = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
type LastDigitMap = {
10: 0;
};
type _Increment<
Number extends string,
Result extends string = ""
> = IsEmptyString<Number> extends true
? ParseNumber<`1${Result}`>
: LastCharacter<Number> extends `${infer LastDigit extends number}`
? IncrementMap[LastDigit] extends infer Incremented extends number
? Number extends `${infer Rest}${LastDigit}`
? Incremented extends keyof LastDigitMap
? _Increment<Rest, `${LastDigitMap[Incremented]}${Result}`>
: ParseNumber<`${Rest}${Incremented}${Result}`>
: never
: never
: never;
/**
* Accepts an integer and returns the incremented value of it. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // 2
* type Case1 = Increment<1>
* // -9
* type Case1 = Increment<-10>
* ```
*/
type Increment<T extends number> = IsNegative<T> extends true
? _Decrement<
Stringify<Abs<T>>
> extends infer NegativeIncrementResult extends number
? NegativeIncrementResult extends 0
? NegativeIncrementResult
: Negate<NegativeIncrementResult>
: never
: _Increment<Stringify<T>>;
/**
* Returns a tuple of whole and fraction part of the passed float number
* @example
* ```ts
* [12, 25]
* type Case1 = GetFloatNumberParts<12.25>
* [12, 25]
* type Case2 = GetFloatNumberParts<-12.25>
* ```
*/
type GetFloatNumberParts<T extends number> = IsFloat<T> extends true
? `${Abs<T>}` extends `${infer Whole extends number}.${infer Fraction extends number}`
? [Whole, Fraction]
: never
: never;
/**
* Type version of `Math.ceil()`. Returns ceiled value of the passed number
* @example
* ```ts
* // 2
* type Case1 = Ceil<1.2>
* // -1
* type Case2 = Ceil<-1.2>
* ```
*/
type Ceil<T extends number> = IsFloat<T> extends true
? GetFloatNumberParts<T> extends [infer Whole extends number, unknown]
? IsNegative<T> extends true
? Negate<Whole>
: Increment<Whole>
: never
: T;
/**
* Returns a boolean whether the passed two arguments are equal
* @example
* ```ts
* // true
* type Case1 = IsEqual<string, string>
* // false
* type Case2 = IsEqual<1, 4>
* ```
*/
type IsEqual<T, U> = (<F>() => F extends T ? 1 : 2) extends <
F
>() => F extends U ? 1 : 2
? true
: false;
/**
* Returns a boolean whether the passed two arguments are not equal
* @example
* ```ts
* // true
* type Case1 = IsNotEqual<1, 4>
* // false
* type Case2 = IsNotEqual<string, string>
* ```
*/
type IsNotEqual<T, U> = Not<IsEqual<T, U>>;
/**
* Accepts two types and returns the third argument if the first two are equal (defaults to `true`), otherwise returns the fourth argument (defaults to `false`)
* @example
* ```ts
* // 'valid'
* type Case1 = IfEqual<string, string, 'valid'>
* // 'invalid'
* type Case1 = IfEqual<1, 4, 'valid', 'invalid'>
* ```
*/
type IfEqual<T, U, IfTrue = true, IfFalse = false> = If<
IsEqual<T, U>,
IfTrue,
IfFalse
>;
/**
* Accepts two types and returns the third argument if the first two are not equal (defaults to `true`), otherwise returns the fourth argument (defaults to `false`)
* @example
* ```ts
* // 'valid'
* type Case1 = IfNotEqual<1, 4, 'valid'>
* // 'invalid'
* type Case1 = IfNotEqual<string, string, 'valid', 'invalid'>
* ```
*/
type IfNotEqual<T, U, IfTrue = true, IfFalse = false> = If<
IsNotEqual<T, U>,
IfTrue,
IfFalse
>;
/**
* Accepts a string and removes leading characters specified in the second argument
* @example
* ```ts
* // 'bc'
* type Case1 = RemoveLeading<'aaabc', 'a'>
* // 'abc'
* type Case2 = RemoveLeading<'abc', 'd'>
* // ''
* type Case3 = RemoveLeading<'aaa', 'a'>
* // 'a'
* type Case3 = RemoveLeading<'aaa', 'aa'>
* ```
*/
type RemoveLeading<
T extends string,
Characters extends string
> = T extends `${Characters}${infer Rest extends string}`
? IsEmptyString<Rest> extends true
? Rest
: RemoveLeading<Rest, Characters>
: T;
type SubDecrementMap = {
"-9": -10;
"-8": -9;
"-7": -8;
"-6": -7;
"-5": -6;
"-4": -5;
"-3": -4;
"-2": -3;
"-1": -2;
"0": -1;
"1": 0;
"2": 1;
"3": 2;
"4": 3;
"5": 4;
"6": 5;
"7": 6;
"8": 7;
"9": 8;
};
type SubNegativeCarryMap = {
"-10": 0;
"-9": 1;
"-8": 2;
"-7": 3;
"-6": 4;
"-5": 5;
"-4": 6;
"-3": 7;
"-2": 8;
"-1": 9;
};
type SubMap = {
0: [0, -1, -2, -3, -4, -5, -6, -7, -8, -9];
1: [1, 0, -1, -2, -3, -4, -5, -6, -7, -8];
2: [2, 1, 0, -1, -2, -3, -4, -5, -6, -7];
3: [3, 2, 1, 0, -1, -2, -3, -4, -5, -6];
4: [4, 3, 2, 1, 0, -1, -2, -3, -4, -5];
5: [5, 4, 3, 2, 1, 0, -1, -2, -3, -4];
6: [6, 5, 4, 3, 2, 1, 0, -1, -2, -3];
7: [7, 6, 5, 4, 3, 2, 1, 0, -1, -2];
8: [8, 7, 6, 5, 4, 3, 2, 1, 0, -1];
9: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
};
type _RemoveLeadingZeros<T extends string> = ParseNumber<
RemoveLeading<T, "0"> extends infer WithoutLeadingZeros extends string
? IfEmptyString<WithoutLeadingZeros, "0", WithoutLeadingZeros>
: never
>;
type _Sub<
Num1 extends string,
Num2 extends string,
NegativeCarry extends 0 | 1 = 0,
Result extends string = ""
> = IsEmptyString<Num2> extends true
? NegativeCarry extends 0
? `${Num1}${Result}`
: `${Decrement<ParseNumber<Num1>>}${Result}`
: LastCharacter<Num1> extends `${infer Num1LastDigit extends keyof SubMap &
number}`
? LastCharacter<Num2> extends `${infer Num2LastDigit extends keyof SubMap[Num1LastDigit] &
number}`
? `${SubMap[Num1LastDigit][Num2LastDigit]}` extends infer DigitsSub extends keyof SubDecrementMap
? (
NegativeCarry extends 1
? Stringify<SubDecrementMap[DigitsSub]>
: DigitsSub
) extends infer DigitsSubWithCarry extends string
? Num1 extends `${infer Num1Rest}${Num1LastDigit}`
? Num2 extends `${infer Num2Rest}${Num2LastDigit}`
? DigitsSubWithCarry extends keyof SubNegativeCarryMap
? _Sub<
Num1Rest,
Num2Rest,
1,
`${SubNegativeCarryMap[DigitsSubWithCarry]}${Result}`
>
: _Sub<Num1Rest, Num2Rest, 0, `${DigitsSubWithCarry}${Result}`>
: never
: never
: never
: never
: never
: never;
/**
* Accepts two integers and returns their subtraction. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // 8
* type Case1 = Sub<10, 2>
* // -8
* type Case2 = Sub<2, 10>
* // 12
* type Case3 = Sub<2, -10>
* // -12
* type Case4 = Sub<-2, 10>
* ```
*/
type Sub<
Num1 extends number,
Num2 extends number
> = IsNegativeInteger<Num1> extends true
? IsNegativeInteger<Num2> extends true
? IsLowerThan<Num1, Num2> extends true
? Negate<
_RemoveLeadingZeros<_Sub<Stringify<Abs<Num1>>, Stringify<Abs<Num2>>>>
>
: _RemoveLeadingZeros<_Sub<Stringify<Abs<Num2>>, Stringify<Abs<Num1>>>>
: Sum<Abs<Num1>, Num2> extends infer Result extends number
? Negate<Result>
: never
: IsNegativeInteger<Num2> extends true
? Sum<Num1, Abs<Num2>>
: IsLowerThan<Num1, Num2> extends true
? Negate<_RemoveLeadingZeros<_Sub<Stringify<Num2>, Stringify<Num1>>>>
: _RemoveLeadingZeros<_Sub<Stringify<Num1>, Stringify<Num2>>>;
type SumIncrementMap = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19
];
type SumLastDigitMap = {
10: 0;
11: 1;
12: 2;
13: 3;
14: 4;
15: 5;
16: 6;
17: 7;
18: 8;
19: 9;
};
type SumMap = {
0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
3: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
4: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
5: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
6: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
7: [7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
8: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
9: [9, 10, 11, 12, 13, 14, 15, 16, 17, 18];
};
type _Sum<
Num1 extends string,
Num2 extends string,
Carry extends 0 | 1 = 0,
Result extends string = ""
> = IsEmptyString<Num1> extends true
? Carry extends 0
? ParseNumber<`${Num2}${Result}`>
: _Increment<Num2, Result>
: IsEmptyString<Num2> extends true
? Carry extends 0
? ParseNumber<`${Num1}${Result}`>
: _Increment<Num1, Result>
: LastCharacter<Num1> extends `${infer Num1LastDigit extends keyof SumMap &
number}`
? LastCharacter<Num2> extends `${infer Num2LastDigit extends keyof SumMap[Num1LastDigit] &
number}`
? SumMap[Num1LastDigit][Num2LastDigit] extends infer DigitsSum extends number
? (
Carry extends 1 ? SumIncrementMap[DigitsSum] : DigitsSum
) extends infer DigitsSumWithCarry extends number
? Num1 extends `${infer Num1Rest}${Num1LastDigit}`
? Num2 extends `${infer Num2Rest}${Num2LastDigit}`
? DigitsSumWithCarry extends keyof SumLastDigitMap
? _Sum<
Num1Rest,
Num2Rest,
1,
`${SumLastDigitMap[DigitsSumWithCarry]}${Result}`
>
: _Sum<Num1Rest, Num2Rest, 0, `${DigitsSumWithCarry}${Result}`>
: never
: never
: never
: never
: never
: never;
/**
* Accepts two integers and returns their sum. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // 13
* type Case1 = Sum<4, 9>
* // 5
* type Case2 = Sum<-4, 9>
* // -5
* type Case3 = Sum<4, -9>
* // -13
* type Case4 = Sum<-4, -9>
* ```
*/
type Sum<
Num1 extends number,
Num2 extends number
> = IsNegativeInteger<Num1> extends true
? IsNegativeInteger<Num2> extends true
? Negate<_Sum<Stringify<Abs<Num1>>, Stringify<Abs<Num2>>>>
: Sub<Num2, Abs<Num1>>
: IsNegativeInteger<Num2> extends true
? Sub<Num1, Abs<Num2>>
: _Sum<Stringify<Num1>, Stringify<Num2>>;
type _SumArr<
T extends readonly number[],
CurrentSum extends number = 0
> = IsEmptyArray<T> extends true
? CurrentSum
: Pop<T, { includeRemoved: true }> extends infer PopResult
? IsNever<PopResult> extends true
? CurrentSum
: PopResult extends [infer Rest extends number[], infer Num1 extends number]
? _SumArr<Rest, Sum<CurrentSum, Num1>>
: never
: CurrentSum;
/**
* Accepts an array of integers and returns the sum of its elements. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // 10
* type Case1 = SumArr<[1, 2, 3, 4]>
* // 2
* type Case2 = SumArr<[1, 2, 3, -4]>
* ```
*/
type SumArr<T extends readonly number[]> = IsTuple<T> extends true
? _SumArr<T>
: never;
type _StringLength<
S extends string,
Parts extends [string[], string[], string[], string[]] = [[], [], [], []]
> = S extends ""
? Sum<
Sum<Parts[0]["length"], Parts[1]["length"]>,
Sum<Parts[2]["length"], Parts[3]["length"]>
>
: S extends `${infer C1 extends string}${infer Rest1 extends string}`
? Rest1 extends `${infer C2 extends string}${infer Rest2 extends string}`
? Rest2 extends `${infer C3 extends string}${infer Rest3 extends string}`
? Rest3 extends `${infer C4 extends string}${infer Rest4 extends string}`
? _StringLength<
Rest4,
[
[...Parts[0], C1],
[...Parts[1], C2],
[...Parts[2], C3],
[...Parts[3], C4]
]
>
: _StringLength<
Rest3,
[[...Parts[0], C1], [...Parts[1], C2], [...Parts[2], C3], Parts[3]]
>
: _StringLength<
Rest2,
[[...Parts[0], C1], [...Parts[1], C2], Parts[2], Parts[3]]
>
: _StringLength<Rest1, [[...Parts[0], C1], Parts[1], Parts[2], Parts[3]]>
: _StringLength<S, Parts>;
/**
* Returns the length of the passed string. Range of string length `[0, 3968]`
* @example
* ```ts
* // 0
* type Case1 = StringLength<''>
* // 3
* type Case2 = StringLength<'xxx'>
* ```
*/
type StringLength<S extends string> = _StringLength<S>;
/**
* Accepts two strings, returns the third argument (defaults to `never`) if the first string is shorter, otherwise returns the fourth argument (defaults to `never`) if the second argument is shorter, if strings have the same length returns the fifth argument (defaults to `never`)
* @example
* ```ts
* // 'first shorter'
* type Case1 = CompareStringLength<'a', 'ab', 'first shorter'>
* // 'first longer'
* type Case2 = CompareStringLength<'abc', 'ab', 'first shorter', 'first longer'>
* // 'equal'
* type Case3 = CompareStringLength<'ab', 'ab', 'first shorter', 'first longer', 'equal'>
* ```
*/
type CompareStringLength<
Str1 extends string,
Str2 extends string,
IfStr1Shorter = never,
IfStr2Shorter = never,
IfEqual = never
> = IsEmptyString<Str1> extends true
? IsEmptyString<Str2> extends true
? IfEqual
: IfStr1Shorter
: IsEmptyString<Str2> extends true
? IfStr2Shorter
: Str1 extends `${string}${infer Str1Rest extends string}`
? Str2 extends `${string}${infer Str2Rest extends string}`
? CompareStringLength<
Str1Rest,
Str2Rest,
IfStr1Shorter,
IfStr2Shorter,
IfEqual
>
: never
: never;
/**
* Accepts two strings, returns a boolean whether the first string is shorter
* @example
* ```ts
* // true
* type Case1 = IsShorterString<'a', 'ab'>
* // false
* type Case2 = IsShorterString<'abc', 'ab'>
* ```
*/
type IsShorterString<
Str1 extends string,
Str2 extends string
> = CompareStringLength<Str1, Str2, true, false, false>;
/**
* Accepts two strings, returns a boolean whether the first string is longer
* @example
* ```ts
* // true
* type Case1 = IsLongerString<'ab', 'a'>
* // false
* type Case2 = IsLongerString<'a', 'ab'>
* ```
*/
type IsLongerString<
Str1 extends string,
Str2 extends string
> = CompareStringLength<Str1, Str2, false, true, false>;
/**
* Accepts two strings, returns a boolean whether strings have the same length
* @example
* ```ts
* // true
* type Case1 = IsSameLengthString<'ab', 'ab'>
* // false
* type Case2 = IsSameLengthString<'ab', 'abc'>
* ```
*/
type IsSameLengthString<
Str1 extends string,
Str2 extends string
> = CompareStringLength<Str1, Str2, false, false, true>;
/**
* Returns number of digits of the passed number
* @example
* ```ts
* // 3
* type Case1 = NumberLength<100>
* // 15
* type Case2 = NumberLength<100000000000000>
* ```
*/
type NumberLength<T extends number> = StringLength<Stringify<T>>;
/**
* Accepts two numbers, returns the third argument (defaults to `never`) if the first number is shorter, otherwise returns the fourth argument (defaults to `never`) if the second argument is shorter, if numbers have the same length returns the fifth argument (defaults to `never`)
* @example
* ```ts
* // 'first shorter'
* type Case1 = CompareNumberLength<1, 12, 'first shorter'>
* // 'first longer'
* type Case2 = CompareNumberLength<123, 12, 'first shorter', 'first longer'>
* // 'equal'
* type Case3 = CompareNumberLength<12, 12, 'first shorter', 'first longer', 'equal'>
* ```
*/
type CompareNumberLength<
Num1 extends number,
Num2 extends number,
IfNum1Shorter = never,
IfNum2Shorter = never,
IfEqual = never
> = CompareStringLength<
Stringify<Num1>,
Stringify<Num2>,
IfNum1Shorter,
IfNum2Shorter,
IfEqual
>;
/**
* Accepts two numbers, returns a boolean whether the first number is shorter
* @example
* ```ts
* // true
* type Case1 = IsShorterNumber<1, 10>
* // false
* type Case2 = IsShorterNumber<100, 10>
* ```
*/
type IsShorterNumber<
Num1 extends number,
Num2 extends number
> = CompareNumberLength<Num1, Num2, true, false, false>;
/**
* Accepts two numbers, returns a boolean whether the first number is longer
* @example
* ```ts
* // true
* type Case1 = IsLongerNumber<10, 1>
* // false
* type Case2 = IsLongerNumber<10, 100>
* ```
*/
type IsLongerNumber<
Num1 extends number,
Num2 extends number
> = CompareNumberLength<Num1, Num2, false, true, false>;
/**
* Accepts two numbers, returns a boolean whether numbers have the same length
* @example
* ```ts
* // true
* type Case1 = IsSameLengthNumber<10, 10>
* // false
* type Case2 = IsSameLengthNumber<10, 100>
* ```
*/
type IsSameLengthNumber<
Num1 extends number,
Num2 extends number
> = CompareNumberLength<Num1, Num2, false, false, true>;
type LowerThanMap = {
"0": ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
"1": ["2", "3", "4", "5", "6", "7", "8", "9"];
"2": ["3", "4", "5", "6", "7", "8", "9"];
"3": ["4", "5", "6", "7", "8", "9"];
"4": ["5", "6", "7", "8", "9"];
"5": ["6", "7", "8", "9"];
"6": ["7", "8", "9"];
"7": ["8", "9"];
"8": ["9"];
"9": [];
};
type _IsLowerThan<
Num1 extends string,
Num2 extends string
> = Num1 extends `${infer Num1Character extends keyof LowerThanMap}${infer Num1Rest extends string}`
? Num2 extends `${infer Num2Character extends string}${infer Num2Rest extends string}`
? IsEqual<Num1Character, Num2Character> extends true
? _IsLowerThan<Num1Rest, Num2Rest>
: Num2Character extends LowerThanMap[Num1Character][number]
? true
: false
: true
: false;
/**
* Returns a boolean whether the first passed integer is lower than the second integer. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // true
* type Case1 = IsLowerThan<1, 10>
* // false
* type Case2 = IsLowerThan<1, -10>
* ```
*/
type IsLowerThan<Num1 extends number, Num2 extends number> = IsEqual<
Num1,
Num2
> extends true
? false
: IsNegative<Num1> extends true
? IsNegative<Num2> extends false
? true
: CompareNumberLength<
Num1,
Num2,
false,
true,
Not<_IsLowerThan<Stringify<Abs<Num1>>, Stringify<Abs<Num2>>>>
>
: IsNegative<Num2> extends true
? false
: CompareNumberLength<
Num1,
Num2,
true,
false,
_IsLowerThan<Stringify<Abs<Num1>>, Stringify<Abs<Num2>>>
>;
/**
* Returns a boolean whether the first passed integer is lower than the second integer. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // 'valid'
* type Case1 = IfLowerThan<1, 10, 'valid'>
* // 'invalid'
* type Case2 = IfLowerThan<1, -10, 'valid', 'invalid'>
* ```
*/
type IfLowerThan<
Num1 extends number,
Num2 extends number,
IfTrue = true,
IfFalse = false
> = If<IsLowerThan<Num1, Num2>, IfTrue, IfFalse>;
/**
* Returns a boolean whether the first passed integer is lower or equal to the second integer. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // true
* type Case1 = IsLowerOrEqual<1, 10>
* // false
* type Case2 = IsLowerOrEqual<1, -10>
* ```
*/
type IsLowerOrEqual<Num1 extends number, Num2 extends number> = IsEqual<
Num1,
Num2
> extends true
? true
: IsLowerThan<Num1, Num2>;
/**
* Returns a boolean whether the first passed integer is greater than the second integer. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // true
* type Case1 = IsGreaterThan<10, 1>
* // false
* type Case2 = IsGreaterThan<-10, 1>
* ```
*/
type IsGreaterThan<
Num1 extends number,
Num2 extends number
> = IsLowerThan<Num2, Num1>;
/**
* Returns the third argument if the first argument (integer) is greater than the second argument (integer) (defaults to `true`), otherwise returns the fourth argument (defaults to `false`). Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* 'valid'
* type Case1 = IfGreaterThan<10, 1, 'valid'>
* // 'invalid'
* type Case2 = IfGreaterThan<-10, 1, 'valid', 'invalid'>
* ```
*/
type IfGreaterThan<
Num1 extends number,
Num2 extends number,
IfTrue = true,
IfFalse = false
> = IfLowerThan<Num2, Num1, IfTrue, IfFalse>;
/**
* Returns a boolean whether the first passed integer is greater than the second integer or equal. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // true
* type Case1 = IsGreaterThan<10, 1>
* // false
* type Case2 = IsGreaterThan<-10, 1>
* // true
* type Case3 = IsGreaterThan<10, 10>
* ```
*/
type IsGreaterOrEqual<
Num1 extends number,
Num2 extends number
> = IsEqual<Num1, Num2> extends true ? true : IsGreaterThan<Num1, Num2>;
/**
* Returns the third argument if the first argument (integer) is greater than the second argument (integer) or equal (defaults to `true`), otherwise returns the fourth argument (defaults to `false`). Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* 'valid'
* type Case1 = IfGreaterThan<10, 1, 'valid'>
* // 'invalid'
* type Case2 = IfGreaterThan<-10, 1, 'valid', 'invalid'>
* ```
*/
type IfGreaterOrEqual<
Num1 extends number,
Num2 extends number,
IfTrue = true,
IfFalse = false
> = If<
IsEqual<Num1, Num2> extends true ? true : IsGreaterThan<Num1, Num2>,
IfTrue,
IfFalse
>;
type IsBetweenOptions = {
minIncluded?: boolean;
maxIncluded?: boolean;
};
/**
* Returns a boolean whether the first integer argument is between the second and the third integer argument
* By default borders of the interval are included, which can be modified by the second argument.
* `minIncluded`, `maxIncluded` options show whether to include the lower and the higher borders respectively. Range: `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`
* @example
* ```ts
* // true
* type Case1 = IsBetween<1, 1, 10>
* // false
* type Case2 = IsBetween<1, 1, 10, {minIncluded: false}>
* // false
* type Case3 = IsBetween<10, 1, 10, {maxIncluded: false}>
* ```
*/
type IsBetween<
Num extends number,
Min extends number,
Max extends number,
Options extends IsBetweenOptions = {
minIncluded: true;
maxIncluded: true;
}
> = IsEqual<Num, Min> extends true
? Options["minIncluded"]
: IsEqual<Num, Max> extends true
? Options["maxIncluded"]
: And<IsGreaterThan<Num, Min>, IsLowerThan<Num, Max>>;
/**
* Type version of `String.prototype.split()`. Splits the first string argument by the second string argument
* @example
* ```ts
* // ['a', 'b', 'c']
* type Case1 = Split<'abc', ''>
* // ['a', 'b', 'c']
* type Case2 = Split<'a,b,c', ','>
* ```
*/
type Split<
Str extends string,
Del extends string | number
> = string extends Str
? string[]
: "" extends Str
? []
: Str extends `${infer T}${Del}${infer U}`
? [T, ...Split<U, Del>]
: [Str];
type _IsValidRGBParameter<T extends number> = IsInteger<T> extends true
? IsBetween<T, 0, 255>
: false;
type RGBOptions = {
separator: string;
};
type DefaultRGBOptions = {
separator: ", ";
};
/**
* Returns the first string argument if it is a valid RGB color, otherwise returns `never`.
* The second argument is an object type with `separator: string` property, which shows the separator between color parameters (defaults to `', '`)
* @example
* ```ts
* // rgb(23, 242, 0)
* type Case1 = RGB<'rgb(23, 242, 0)'>
* // never
* type Case2 = RGB<'rgb(324, 123, 3)'>
* // rgb(23,242,0)
* type Case3 = RGB<'rgb(23,242,0)', { separator: ',' }>
* ```
*/
type RGB<
T extends string,
Options extends RGBOptions = DefaultRGBOptions
> = T extends `rgb(${infer R extends number}${Options["separator"]}${infer G extends number}${Options["separator"]}${infer B extends number})`
? AndArr<
[
_IsValidRGBParameter<R>,
_IsValidRGBParameter<G>,
_IsValidRGBParameter<B>
]
> extends true
? T
: never
: never;
/**
* Returns a boolean whether the first string argument is a valid RGB color.
* The second argument is an object type with `separator: string` property, which shows the separator between color parameters (defaults to `', '`)
* @example
* ```ts
* // true
* type Case1 = IsRGB<'rgb(23, 242, 0)'>
* // false
* type Case2 = IsRGB<'rgb(324, 123, 3)'>
* // true
* type Case3 = IsRGB<'rgb(23,242,0)', { separator: ',' }>
* ```
*/
type IsRGB<
T extends string,
Options extends RGBOptions = DefaultRGBOptions
> = Not<IsNever<RGB<T, Options>>>;
/**
* Returns the third argument if the first argument is valid RGB color (defaults to `true`), otherwise returns the fourth argument (defaults to `false`)
* The second argument is an object type with `separator: string` property, which shows the separator between color parameters (defaults to `', '`)
* @example
* ```ts
* // 'true'
* type Case1 = IfRGB<'rgb(23, 242, 0)', 'true'>
* // 'invalid'
* type Case2 = IfRGB<'rgb(324, 123, 3)', 'valid', 'invalid'>
* // true
* type Case3 = IfRGB<'rgb(23,242,0)', { separator: ',' }>
* ```
*/
type IfRGB<
T extends string,
Options extends RGBOptions = DefaultRGBOptions,
IfTrue = true,
IfFalse = false
> = If<IsRGB<T, Options>, IfTrue, IfFalse>;
type _ValidHEXCharacters = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"A",
"B",
"C",
"D",
"E",
"F"
];
type _AllowedHEXLength = 3 | 4 | 6 | 8;
/**
* Returns the first string argument if it is a valid HEX color, otherwise returns `never`
* @example
* // '#000'
* type Case1 = HEX<'#000'>
* // never
* type Case2 = HEX<'#g00'>
* // '#0000'
* type Case3 = HEX<'#0000'>
* // never
* type Case4 = HEX<'#00000'>
* // '#000000'
* type Case5 = HEX<'#000000'>
* // '#00000000'
* type Case6 = HEX<'#00000000'>
*/
type HEX<T extends string> = (
Uppercase<T> extends `#${infer HEXWithoutHashTag extends string}`
? StringLength<HEXWithoutHashTag> extends _AllowedHEXLength
? ExtendsArr<Split<HEXWithoutHashTag, "">, _ValidHEXCharacters[number]>
: false
: false
) extends true
? T
: never;
/**
* Returns a boolean whether the first string argument is a valid HEX color.
* @example
* ```ts
* // true
* type Case1 = IsHEX<'#000'>
* // false
* type Case2 = IsHEX<'#g00'>
* ```
*/
type IsHEX<T extends string> = Not<IsNever<HEX<T>>>;
/**
* Returns the second argument if the first argument is valid HEX color (defaults to `true`), otherwise returns the third argument (defaults to `false`)
* @example
* ```ts
* // true
* type Case1 = IfHEX<'#000'>
* // false
* type Case2 = IfHEX<'#g00'>
* // 'valid'
* type Case3 = IfHEX<'#0000', 'valid'>
* // 'invalid'
* type Case4 = IfHEX<'#00000', 'valid', 'invalid'>
* ```
*/
type IfHEX<T extends string, IfTrue = true, IfFalse = false> = If<
IsHEX<T>,
IfTrue,
IfFalse
>;
type HSLOptions = {
separator: string;
};
type DefaultHSLOptions = {
separator: ", ";
};
/**
* Returns the first string argument if it is a valid HSL color, otherwise returns `never`.
* The second argument is an object type with `separator: string` property, which shows the separator between color parameters (defaults to `', '`)
* @example
* ```ts
* // hsl(100, 34%, 56%)
* type Case1 = HSL<'hsl(100, 34%, 56%)'>
* // never
* type Case2 = HSL<'hsl(100, 200%, 3)'>
* // hsl(100,34%,56%)
* type Case3 = HSL<'hsl(100,34%,56%)', { separator: ',' }>
* ```
*/
type HSL<
T extends string,
Options extends HSLOptions = DefaultHSLOptions
> = (
T extends `hsl(${infer H extends number}${Options["separator"]}${infer S extends number}%${Options["separator"]}${infer L extends number}%)`
? AndArr<[IsInteger<H>, IsInteger<S>, IsInteger<L>]> extends true
? AndArr<[IsBetween<S, 0, 100>, IsBetween<L, 0, 100>]>
: false
: false
) extends true
? T
: never;
/**
* Returns a boolean whether the first string argument is a valid HSL color.
* The second argument is an object type with `separator: string` property, which shows the separator between color parameters (defaults to `', '`)
* @example
* ```ts
* // true
* type Case1 = IsHSL<'hsl(100, 34%, 56%)'>
* // false
* type Case2 = IsHSL<'hsl(101, 200%, 3)'>
* // true
* type Case3 = IsHSL<'hsl(100,34%,56%)', { separator: ',' }>
* ```
*/
type IsHSL<
T extends string,
Options extends HSLOptions = DefaultHSLOptions
> = Not<IsNever<HSL<T, Options>>>;
/**
* Returns the third argument if the first argument is valid HSL color (defaults to `true`), otherwise returns the fourth argument (defaults to `false`)
* The second argument is an object type with `separator: string` property, which shows the separator between color parameters (defaults to `', '`)
* @example
* ```ts
* // 'true'
* type Case1 = IfHSL<'hsl(100, 34%, 56%)', 'true'>
* // 'invalid'
* type Case2 = IfHSL<'hsl(101, 200%, 3)', 'valid', 'invalid'>
* // true
* type Case3 = IfHSL<'hsl(100,34%,56%)', { separator: ',' }>
* ```
*/
type IfHSL<
T extends string,
Options extends HSLOptions = DefaultHSLOptions,
IfTrue = true,
IfFalse = false
> = If<IsHSL<T, Options>, IfTrue, IfFalse>;
type ColorOptions = {
rgbOptions?: RGBOptions;
hslOptions?: HSLOptions;
};
type DefaultColorOptions = {
rgbOptions: DefaultRGBOptions;
hslOptions: DefaultHSLOptions;
};
/**
* Returns the first string argument if it is a valid RGB or HEX or HSL color, otherwise returns `never`.
* The second argument is an object type with `rgbOptions: RGBOptions` and `hslOptions: hslOptions` properties, which can accept the separator between color parameters (defaults to `', '`)
* @example
* ```ts
* // rgb(23, 242, 0)
* type Case1 = Color<'rgb(23, 242, 0)'>
* // never
* type Case2 = Color<'rgb(324, 123, 3)'>
* // '#000000'
* type Case3 = HEX<'#000000'>
* // 'hsl(100,34%,56%)'
* type Case4 = Color<'hsl(100,34%,56%)', { hslOptions: { separator: ',' } }>
* ```
*/
type Color<
T extends string,
Options extends ColorOptions = DefaultColorOptions
> = RGB<T, Options["rgbOptions"]> | HEX<T> | HSL<T, Options["hslOptions"]>;
/**
* Returns a boolean whether the first string argument is a valid RGB or HEX or HSL color.
* The second argument is an object type with `rgbOptions: RGBOptions` and `hslOptions: hslOptions` properties, which can accept the separator between color parameters (defaults to `', '`)
* @example
* ```ts
* // true
* type Case1 = Color<'rgb(23, 242, 0)'>
* // false
* type Case2 = Color<'rgb(324, 123, 3)'>
* // true
* type Case3 = HEX<'#000000'>
* // true
* type Case4 = Color<'hsl(100,34%,56%)', { hslOptions: { separator: ',' } }>
* ```
*/
type IsColor<
T extends string,
Options extends ColorOptions = DefaultColorOptions
> = Not<IsNever<Color<T, Options>>>;
/**
* Returns the third argument if the first argument is valid RGB or HEX or HSL color (defaults to `true`), otherwise returns the fourth argument (defaults to `false`)
* The second argument is an object type with `rgbOptions: RGBOptions` and `hslOptions: hslOptions` properties, which can accept the separator between color parameters (defaults to `', '`)
* @example
* ```ts
* // 'valid'
* type Case1 = Color<'rgb(23, 242, 0)', 'valid'>
* // 'invalid'
* type Case2 = Color<'rgb(324, 123, 3)', 'valid', 'invalid'>
* // true
* type Case3 = Color<'#000000'>
* ```
*/
type IfColor<
T extends string,
Options extends ColorOptions = DefaultColorOptions,
IfTrue = true,
IfFalse = false
> = If<IsColor<T, Options>, IfTrue, IfFalse>;
/**
* Type version of `Array.prototype.concat()`. Concatenates two arrays into one.
* @example
* ```ts
* // [number, number, string, string]
* type Result = Concat<[number, number], [string, string]>
* ```
*/
type Concat<T extends readonly unknown[], U> = [
...T,
...(U extends readonly unknown[] ? U : [U])
];
/**
* Accepts an integer argument and returns a tuple of its digits
* @example
* ```ts
* // [1]
* type Case1 = DigitsTuple<1>
* // [1, 2, 3]
* type Case2 = DigitsTuple<123>
* // [1, 2, 3]
* type Case3 = DigitsTuple<-123>
* ```
*/
type DigitsTuple<T extends number> = number extends T
? number[]
: Split<Stringify<Abs<T>>, ""> extends infer Result
? {
[K in keyof Result]: Result[K] extends string
? ParseNumber<Result[K]>
: Result[K];
}
: never;
/**
* Pushes the second argument to the first array argument
* @example
* ```ts
* // [1, 2, 3, 4, 5]
* type Case1 = Push<[1, 2, 3, 4], 5>
* ```
*/
type Push<T extends readonly unknown[], U extends unknown> = [...T, U];
type _Repeat<
T extends string,
Count extends number,
Result extends string = "",
Iteration extends unknown[] = []
> = Iteration["length"] extends Count
? Result
: _Repeat<T, Count, `${T}${Result}`, Push<Iteration, unknown>>;
/**
* Repeats the first argument number of times specified in the second argument. Range `[0,999]`
* @example
* ```ts
* // 'x'
* type Case1 = Repeat<'x', 1>
* // 'xxxxx'
* type Case2 = Repeat<'x', 5>
* ```
*/
type Repeat<T extends string, Count extends number> = _Repeat<T, Count>;
/**
* Returns the first argument if it extends the second argument, returns the third argument otherwise
* @example
* ```ts
* // 1
* type Case1 = ReturnItselfIfExtends<1, number, 2>
* // 2
* type Case2 = ReturnItselfIfExtends<'1', number, 2>
* ```