@oscarpalmer/atoms 0.181.0 → 0.182.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -2876,6 +2876,64 @@ declare function setValue<Data extends PlainObject, Path extends NestedKeys<Data
2876
2876
  */
2877
2877
  declare function setValue<Data extends PlainObject>(data: Data, path: string, value: unknown, ignoreCase?: boolean): Data;
2878
2878
  //#endregion
2879
+ //#region src/kalas.d.ts
2880
+ declare class Events<Map extends Record<string, GenericCallback>> {
2881
+ #private;
2882
+ constructor(kalas: Kalas<Map>);
2883
+ /**
2884
+ * Subscribe to an event with a callback
2885
+ * @param event Event name
2886
+ * @param callback Callback function
2887
+ * @returns Unsubscriber function
2888
+ */
2889
+ subscribe<Event extends keyof Map>(event: Event, callback: Map[Event]): Unsubscriber;
2890
+ /**
2891
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
2892
+ * @param event Event name
2893
+ * @param callback Callback function
2894
+ * @returns Unsubscriber function
2895
+ */
2896
+ unsubscribe<Event extends keyof Map>(event: Event, callback?: Map[Event]): void;
2897
+ }
2898
+ declare class Kalas<Map extends Record<string, GenericCallback>> {
2899
+ #private;
2900
+ /**
2901
+ * Events interface for subscribing and unsubscribing to events
2902
+ */
2903
+ readonly events: Events<Map>;
2904
+ constructor(names: (keyof Map)[]);
2905
+ /**
2906
+ * Remove all event subscribers
2907
+ */
2908
+ clear(): void;
2909
+ /**
2910
+ * Emit an event with parameters
2911
+ * @param event Event name
2912
+ * @param parameters Event parameters
2913
+ */
2914
+ emit<Event extends keyof Map>(event: Event, ...parameters: Parameters<Map[Event]>): void;
2915
+ /**
2916
+ * Subscribe to an event with a callback
2917
+ * @param event Event name
2918
+ * @param callback Callback function
2919
+ * @returns Unsubscriber function
2920
+ */
2921
+ subscribe<Event extends keyof Map>(event: Event, callback: Map[Event]): Unsubscriber;
2922
+ /**
2923
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
2924
+ * @param event Event name
2925
+ * @param callback Callback function
2926
+ */
2927
+ unsubscribe<Event extends keyof Map>(event: Event, callback?: Map[Event]): void;
2928
+ }
2929
+ type Unsubscriber = () => void;
2930
+ /**
2931
+ * Create a Kalas _(party)_ for named events
2932
+ * @param names Event names
2933
+ * @returns Kalas instance
2934
+ */
2935
+ declare function kalas<Events extends Record<string, GenericCallback>>(names: (keyof Events)[]): Kalas<Events>;
2936
+ //#endregion
2879
2937
  //#region src/string/case.d.ts
2880
2938
  /**
2881
2939
  * Convert a string to camel case _(thisIsCamelCase)_
@@ -3099,6 +3157,60 @@ declare function includes(haystack: string, needle: string, ignoreCase?: boolean
3099
3157
  */
3100
3158
  declare function startsWith(haystack: string, needle: string, ignoreCase?: boolean): boolean;
3101
3159
  //#endregion
3160
+ //#region src/string/normalize.d.ts
3161
+ /**
3162
+ * Options for normalizing a string
3163
+ */
3164
+ type NormalizeOptions = {
3165
+ /**
3166
+ * Remove diacritical marks from the string? _(defaults to `true`)_
3167
+ */
3168
+ deburr?: boolean;
3169
+ /**
3170
+ * Convert the string to lower case? _(defaults to `true`)_
3171
+ */
3172
+ lowerCase?: boolean;
3173
+ /**
3174
+ * Trim the string? _(defaults to `true`)_
3175
+ */
3176
+ trim?: boolean;
3177
+ };
3178
+ /**
3179
+ * String normalizer function
3180
+ */
3181
+ type Normalizer = {
3182
+ /**
3183
+ * Normalize a string
3184
+ * @param value String to normalize
3185
+ * @returns Normalized string
3186
+ */
3187
+ (value: string): string;
3188
+ };
3189
+ /**
3190
+ * Deburr a string, removing diacritical marks
3191
+ * @param value String to deburr
3192
+ * @returns Deburred string
3193
+ */
3194
+ declare function deburr(value: string): string;
3195
+ /**
3196
+ * Initialize a string normalizer
3197
+ * @param options Normalization options
3198
+ * @returns Normalizer function
3199
+ */
3200
+ declare function initializeNormalizer(options?: NormalizeOptions): Normalizer;
3201
+ /**
3202
+ * Normalize a string
3203
+ *
3204
+ * By default, the string will be trimmed, deburred, and then lowercased
3205
+ * @param value String to normalize
3206
+ * @param options Normalization options
3207
+ * @returns Normalized string
3208
+ */
3209
+ declare function normalize(value: string, options?: NormalizeOptions): string;
3210
+ declare namespace normalize {
3211
+ var initialize: typeof initializeNormalizer;
3212
+ }
3213
+ //#endregion
3102
3214
  //#region src/string/template.d.ts
3103
3215
  /**
3104
3216
  * Options for templating strings
@@ -5207,4 +5319,4 @@ declare class SizedSet<Value = unknown> extends Set<Value> {
5207
5319
  get(value: Value, update?: boolean): Value | undefined;
5208
5320
  }
5209
5321
  //#endregion
5210
- export { AnyResult, ArrayComparisonSorter, ArrayKeySorter, ArrayOrPlainObject, ArrayPosition, ArrayValueSorter, Asserter, AsyncCancelableCallback, AttemptFlow, AttemptFlowPromise, type Beacon, type BeaconOptions, BuiltIns, CancelableCallback, CancelablePromise, type Color, Constructor, DiffOptions, DiffResult, DiffValue, EqualOptions, Err, EventPosition, ExtendedErr, ExtendedResult, Flow, FlowPromise, FulfilledPromise, FuzzyConfiguration, FuzzyOptions, FuzzyResult, FuzzySearchOptions, GenericAsyncCallback, GenericCallback, type HSLAColor, type HSLColor, Key$1 as Key, KeyedValue, type Logger, type Memoized, type MemoizedOptions, MergeOptions, Merger, NestedArray, NestedKeys, NestedPartial, NestedValue, NestedValues, NumericalKeys, NumericalValues, type Observable, type Observer, Ok, OnceAsyncCallback, OnceCallback, PROMISE_ABORT_EVENT, PROMISE_ABORT_OPTIONS, PROMISE_ERROR_NAME, PROMISE_MESSAGE_EXPECTATION_ATTEMPT, PROMISE_MESSAGE_EXPECTATION_RESULT, PROMISE_MESSAGE_EXPECTATION_TIMED, PROMISE_MESSAGE_TIMEOUT, PROMISE_STRATEGY_ALL, PROMISE_STRATEGY_DEFAULT, PROMISE_TYPE_FULFILLED, PROMISE_TYPE_REJECTED, PlainObject, Primitive, PromiseData, PromiseHandlers, PromiseOptions, PromiseParameters, PromiseStrategy, PromiseTimeoutError, PromisesItems, PromisesOptions, PromisesResult, PromisesUnwrapped, PromisesValue, PromisesValues, type Queue, QueueError, type QueueOptions, type Queued, type QueuedResult, type RGBAColor, type RGBColor, RejectedPromise, RequiredKeys, Result, ResultMatch, RetryError, RetryOptions, SORT_DIRECTION_ASCENDING, SORT_DIRECTION_DESCENDING, Shaken, Simplify, SizedMap, SizedSet, Smushed, SortDirection, Sorter, type Subscription, TemplateOptions, type Time, ToString, TypedArray, Unsmushed, UnwrapValue, assert, assertCondition, assertDefined, assertInstanceOf, assertIs, asyncAttempt, asyncDebounce, asyncFlow, asyncMatchResult, asyncOnce, asyncPipe, asyncThrottle, attempt, attemptAsyncFlow, attemptAsyncPipe, attemptFlow, attemptPipe, attemptPromise, average, beacon, between, camelCase, cancelable, capitalize, ceil, chunk, clamp, clone, compact, compare, count, debounce, dedent, delay, deregisterCloner, deregisterComparator, deregisterEqualizer, diff, difference, drop, endsWith, endsWithArray, equal, error, exclude, exists, filter, find, findLast, first, firstOrDefault, flatten, floor, flow, fromQuery, toPromise as fromResult, toPromise, fuzzy, fuzzyMatch, getArray, getArrayPosition, getColor, getError, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getSortedIndex, getString, getTimedPromise, getUuid, getValue, groupArraysBy, groupBy, handleResult, hasValue, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, ignoreKey, inMap, inSet, includes, includesArray, indexOf, indexOfArray, initializeEqualizer, initializeMerger, initializeSorter, initializeTemplater, insert, intersection, isArrayOrPlainObject, isColor, isConstructor, isEmpty, isError, isFulfilled, isHexColor, isHslColor, isHslLike, isHslaColor, isInstanceOf, isKey, isNonArrayOrPlainObject, isNonConstructor, isNonEmpty, isNonInstanceOf, isNonKey, isNonNullable, isNonNullableOrEmpty, isNonNullableOrWhitespace, isNonNumber, isNonNumerical, isNonObject, isNonPlainObject, isNonPrimitive, isNonTypedArray, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isRejected, isResult, isRgbColor, isRgbLike, isRgbaColor, isSorted, isTypedArray, join, kebabCase, last, lastIndexOf, lastOrDefault, logger, lowerCase, matchResult, max, median, memoize, merge, min, move, moveIndices, moveToIndex, noop, ok, omit, once, parse, partition, pascalCase, pick, pipe, promises, push, queue, range, registerCloner, registerComparator, registerEqualizer, resultPromises, retry, reverse, rgbToHex, rgbToHsl, rgbToHsla, round, select, setValue, settlePromise, shake, shuffle, single, slice, smush, snakeCase, sort, splice, startsWith, startsWithArray, sum, swap, take, template, throttle, timed, times, titleCase, toMap, toMapArrays, toQuery, toRecord, toRecordArrays, toResult, toSet, toggle, trim, truncate, tryDecode, tryEncode, union, unique, unsmush, unwrap, update, upperCase, words };
5322
+ export { AnyResult, ArrayComparisonSorter, ArrayKeySorter, ArrayOrPlainObject, ArrayPosition, ArrayValueSorter, Asserter, AsyncCancelableCallback, AttemptFlow, AttemptFlowPromise, type Beacon, type BeaconOptions, BuiltIns, CancelableCallback, CancelablePromise, type Color, Constructor, DiffOptions, DiffResult, DiffValue, EqualOptions, Err, EventPosition, type Events, ExtendedErr, ExtendedResult, Flow, FlowPromise, FulfilledPromise, FuzzyConfiguration, FuzzyOptions, FuzzyResult, FuzzySearchOptions, GenericAsyncCallback, GenericCallback, type HSLAColor, type HSLColor, type Kalas, Key$1 as Key, KeyedValue, type Logger, type Memoized, type MemoizedOptions, MergeOptions, Merger, NestedArray, NestedKeys, NestedPartial, NestedValue, NestedValues, NormalizeOptions, Normalizer, NumericalKeys, NumericalValues, type Observable, type Observer, Ok, OnceAsyncCallback, OnceCallback, PROMISE_ABORT_EVENT, PROMISE_ABORT_OPTIONS, PROMISE_ERROR_NAME, PROMISE_MESSAGE_EXPECTATION_ATTEMPT, PROMISE_MESSAGE_EXPECTATION_RESULT, PROMISE_MESSAGE_EXPECTATION_TIMED, PROMISE_MESSAGE_TIMEOUT, PROMISE_STRATEGY_ALL, PROMISE_STRATEGY_DEFAULT, PROMISE_TYPE_FULFILLED, PROMISE_TYPE_REJECTED, PlainObject, Primitive, PromiseData, PromiseHandlers, PromiseOptions, PromiseParameters, PromiseStrategy, PromiseTimeoutError, PromisesItems, PromisesOptions, PromisesResult, PromisesUnwrapped, PromisesValue, PromisesValues, type Queue, QueueError, type QueueOptions, type Queued, type QueuedResult, type RGBAColor, type RGBColor, RejectedPromise, RequiredKeys, Result, ResultMatch, RetryError, RetryOptions, SORT_DIRECTION_ASCENDING, SORT_DIRECTION_DESCENDING, Shaken, Simplify, SizedMap, SizedSet, Smushed, SortDirection, Sorter, type Subscription, TemplateOptions, type Time, ToString, TypedArray, Unsmushed, Unsubscriber, UnwrapValue, assert, assertCondition, assertDefined, assertInstanceOf, assertIs, asyncAttempt, asyncDebounce, asyncFlow, asyncMatchResult, asyncOnce, asyncPipe, asyncThrottle, attempt, attemptAsyncFlow, attemptAsyncPipe, attemptFlow, attemptPipe, attemptPromise, average, beacon, between, camelCase, cancelable, capitalize, ceil, chunk, clamp, clone, compact, compare, count, debounce, deburr, dedent, delay, deregisterCloner, deregisterComparator, deregisterEqualizer, diff, difference, drop, endsWith, endsWithArray, equal, error, exclude, exists, filter, find, findLast, first, firstOrDefault, flatten, floor, flow, fromQuery, toPromise as fromResult, toPromise, fuzzy, fuzzyMatch, getArray, getArrayPosition, getColor, getError, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getSortedIndex, getString, getTimedPromise, getUuid, getValue, groupArraysBy, groupBy, handleResult, hasValue, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, ignoreKey, inMap, inSet, includes, includesArray, indexOf, indexOfArray, initializeEqualizer, initializeMerger, initializeNormalizer, initializeSorter, initializeTemplater, insert, intersection, isArrayOrPlainObject, isColor, isConstructor, isEmpty, isError, isFulfilled, isHexColor, isHslColor, isHslLike, isHslaColor, isInstanceOf, isKey, isNonArrayOrPlainObject, isNonConstructor, isNonEmpty, isNonInstanceOf, isNonKey, isNonNullable, isNonNullableOrEmpty, isNonNullableOrWhitespace, isNonNumber, isNonNumerical, isNonObject, isNonPlainObject, isNonPrimitive, isNonTypedArray, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isRejected, isResult, isRgbColor, isRgbLike, isRgbaColor, isSorted, isTypedArray, join, kalas, kebabCase, last, lastIndexOf, lastOrDefault, logger, lowerCase, matchResult, max, median, memoize, merge, min, move, moveIndices, moveToIndex, noop, normalize, ok, omit, once, parse, partition, pascalCase, pick, pipe, promises, push, queue, range, registerCloner, registerComparator, registerEqualizer, resultPromises, retry, reverse, rgbToHex, rgbToHsl, rgbToHsla, round, select, setValue, settlePromise, shake, shuffle, single, slice, smush, snakeCase, sort, splice, startsWith, startsWithArray, sum, swap, take, template, throttle, timed, times, titleCase, toMap, toMapArrays, toQuery, toRecord, toRecordArrays, toResult, toSet, toggle, trim, truncate, tryDecode, tryEncode, union, unique, unsmush, unwrap, update, upperCase, words };
package/dist/index.mjs CHANGED
@@ -620,10 +620,10 @@ function select(array, ...parameters) {
620
620
  //#region src/array/single.ts
621
621
  function single(array, ...parameters) {
622
622
  const { matched } = findValues("all", array, parameters);
623
- if (matched.length > 1) throw new Error(MESSAGE);
623
+ if (matched.length > 1) throw new Error(MESSAGE$1);
624
624
  return matched[0];
625
625
  }
626
- const MESSAGE = "Multiple items were found";
626
+ const MESSAGE$1 = "Multiple items were found";
627
627
  //#endregion
628
628
  //#region src/array/slice.ts
629
629
  function drop(array, first, second) {
@@ -2289,6 +2289,97 @@ function setValue(data, path, value, ignoreCase) {
2289
2289
  }
2290
2290
  const EXPRESSION_INDEX = /^\d+$/;
2291
2291
  //#endregion
2292
+ //#region src/kalas.ts
2293
+ var Events = class {
2294
+ #kalas;
2295
+ constructor(kalas) {
2296
+ this.#kalas = kalas;
2297
+ }
2298
+ /**
2299
+ * Subscribe to an event with a callback
2300
+ * @param event Event name
2301
+ * @param callback Callback function
2302
+ * @returns Unsubscriber function
2303
+ */
2304
+ subscribe(event, callback) {
2305
+ return this.#kalas.subscribe(event, callback);
2306
+ }
2307
+ /**
2308
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
2309
+ * @param event Event name
2310
+ * @param callback Callback function
2311
+ * @returns Unsubscriber function
2312
+ */
2313
+ unsubscribe(event, callback) {
2314
+ return this.#kalas.unsubscribe(event, callback);
2315
+ }
2316
+ };
2317
+ var Kalas = class {
2318
+ #names;
2319
+ #subscribers = /* @__PURE__ */ new Map();
2320
+ constructor(names) {
2321
+ this.#names = new Set(names);
2322
+ Object.defineProperty(this, "events", { value: new Events(this) });
2323
+ }
2324
+ /**
2325
+ * Remove all event subscribers
2326
+ */
2327
+ clear() {
2328
+ this.#subscribers.clear();
2329
+ }
2330
+ /**
2331
+ * Emit an event with parameters
2332
+ * @param event Event name
2333
+ * @param parameters Event parameters
2334
+ */
2335
+ emit(event, ...parameters) {
2336
+ const subscribers = this.#subscribers.get(event);
2337
+ if (subscribers == null) return;
2338
+ for (const callback of subscribers) callback(...parameters);
2339
+ }
2340
+ /**
2341
+ * Subscribe to an event with a callback
2342
+ * @param event Event name
2343
+ * @param callback Callback function
2344
+ * @returns Unsubscriber function
2345
+ */
2346
+ subscribe(event, callback) {
2347
+ if (!this.#names.has(event) || typeof callback !== "function") return noop;
2348
+ let subscribers = this.#subscribers.get(event);
2349
+ if (subscribers == null) {
2350
+ subscribers = /* @__PURE__ */ new Set();
2351
+ this.#subscribers.set(event, subscribers);
2352
+ }
2353
+ subscribers.add(callback);
2354
+ return () => {
2355
+ subscribers?.delete(callback);
2356
+ };
2357
+ }
2358
+ /**
2359
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
2360
+ * @param event Event name
2361
+ * @param callback Callback function
2362
+ */
2363
+ unsubscribe(event, callback) {
2364
+ if (!this.#names.has(event) || (callback != null ? typeof callback !== "function" : false)) return;
2365
+ const subscribers = this.#subscribers.get(event);
2366
+ if (subscribers == null) return;
2367
+ if (callback == null) subscribers.clear();
2368
+ else subscribers.delete(callback);
2369
+ if (callback == null || subscribers.size === 0) this.#subscribers.delete(event);
2370
+ }
2371
+ };
2372
+ /**
2373
+ * Create a Kalas _(party)_ for named events
2374
+ * @param names Event names
2375
+ * @returns Kalas instance
2376
+ */
2377
+ function kalas(names) {
2378
+ if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string")) throw new Error(MESSAGE);
2379
+ return new Kalas(names);
2380
+ }
2381
+ const MESSAGE = "Kalas requires an array of event names.";
2382
+ //#endregion
2292
2383
  //#region src/string/case.ts
2293
2384
  /**
2294
2385
  * Convert a string to camel case _(thisIsCamelCase)_
@@ -2322,7 +2413,9 @@ function kebabCase(value) {
2322
2413
  * @returns Lower-cased string
2323
2414
  */
2324
2415
  function lowerCase(value) {
2325
- return typeof value === "string" ? value.toLocaleLowerCase() : "";
2416
+ if (typeof value !== "string") return "";
2417
+ memoizedLowerCase ??= memoize((v) => v.toLocaleLowerCase());
2418
+ return memoizedLowerCase.run(value);
2326
2419
  }
2327
2420
  /**
2328
2421
  * Convert a string to pascal case _(ThisIsPascalCase)_
@@ -2346,8 +2439,8 @@ function snakeCase(value) {
2346
2439
  * @returns Title-cased string
2347
2440
  */
2348
2441
  function titleCase(value) {
2349
- if (typeof value !== "string") return "";
2350
- memoizedTitleCase ??= memoize((v) => v.length < 1 ? capitalize(v) : join(words(v).map(capitalize), " "));
2442
+ if (typeof value !== "string" || value.length === 0) return "";
2443
+ memoizedTitleCase ??= memoize((v) => v.length < 2 ? capitalize(v) : join(words(v).map(capitalize), " "));
2351
2444
  return memoizedTitleCase.run(value);
2352
2445
  }
2353
2446
  function toCase(type, value, capitalizeAny, capitalizeFirst) {
@@ -2387,7 +2480,9 @@ function toCaseCallback(value) {
2387
2480
  * @returns Upper-cased string
2388
2481
  */
2389
2482
  function upperCase(value) {
2390
- return typeof value === "string" ? value.toLocaleUpperCase() : "";
2483
+ if (typeof value !== "string" || value.length === 0) return "";
2484
+ memoizedUpperCase ??= memoize((v) => v.toLocaleUpperCase());
2485
+ return memoizedUpperCase.run(value);
2391
2486
  }
2392
2487
  const CASE_CAMEL = "camel";
2393
2488
  const CASE_KEBAB = "kebab";
@@ -2408,7 +2503,9 @@ const delimiters = {
2408
2503
  [CASE_SNAKE]: DELIMITER_UNDERSCORE
2409
2504
  };
2410
2505
  let memoizedCapitalize;
2506
+ let memoizedLowerCase;
2411
2507
  let memoizedTitleCase;
2508
+ let memoizedUpperCase;
2412
2509
  //#endregion
2413
2510
  //#region src/is.ts
2414
2511
  /**
@@ -2822,6 +2919,95 @@ function truncate(value, length, suffix) {
2822
2919
  }
2823
2920
  const ZERO = "0";
2824
2921
  //#endregion
2922
+ //#region src/string/normalize.ts
2923
+ /**
2924
+ * Deburr a string, removing diacritical marks
2925
+ * @param value String to deburr
2926
+ * @returns Deburred string
2927
+ */
2928
+ function deburr(value) {
2929
+ if (typeof value !== "string") return "";
2930
+ deburrMemoizer ??= memoize((value) => {
2931
+ let deburred = value.normalize(DEBURR_NORMALIZATION).replace(DEBURR_PATTERN_SIMPLE, "");
2932
+ deburred = deburred.replace(DEBURR_PATTERN_CHARACTERS, (_, character) => DEBURR_CHARACTERS[character]);
2933
+ return deburred;
2934
+ });
2935
+ return deburrMemoizer.run(value);
2936
+ }
2937
+ function getNormalizeOptions(input) {
2938
+ const options = isPlainObject(input) ? input : {};
2939
+ return {
2940
+ deburr: options.deburr !== false,
2941
+ lowerCase: options.lowerCase !== false,
2942
+ trim: options.trim !== false
2943
+ };
2944
+ }
2945
+ /**
2946
+ * Initialize a string normalizer
2947
+ * @param options Normalization options
2948
+ * @returns Normalizer function
2949
+ */
2950
+ function initializeNormalizer(options) {
2951
+ const normalization = getNormalizeOptions(options);
2952
+ return (value) => normalizeString(value, normalization);
2953
+ }
2954
+ /**
2955
+ * Normalize a string
2956
+ *
2957
+ * By default, the string will be trimmed, deburred, and then lowercased
2958
+ * @param value String to normalize
2959
+ * @param options Normalization options
2960
+ * @returns Normalized string
2961
+ */
2962
+ function normalize(value, options) {
2963
+ return normalizeString(value, getNormalizeOptions(options));
2964
+ }
2965
+ normalize.initialize = initializeNormalizer;
2966
+ function normalizeString(value, options) {
2967
+ if (typeof value !== "string") return "";
2968
+ let result = value;
2969
+ if (options.trim) result = result.trim();
2970
+ if (options.deburr) result = deburr(result);
2971
+ if (options.lowerCase) result = lowerCase(result);
2972
+ return result;
2973
+ }
2974
+ const DEBURR_CHARACTERS = {
2975
+ Æ: "AE",
2976
+ æ: "ae",
2977
+ Ð: "D",
2978
+ ð: "d",
2979
+ Đ: "D",
2980
+ đ: "d",
2981
+ Ħ: "H",
2982
+ ħ: "h",
2983
+ IJ: "IJ",
2984
+ ij: "ij",
2985
+ İ: "I",
2986
+ ı: "i",
2987
+ ĸ: "k",
2988
+ Ŀ: "L",
2989
+ ŀ: "l",
2990
+ Ł: "L",
2991
+ ł: "l",
2992
+ Ŋ: "N",
2993
+ ŋ: "n",
2994
+ ʼn: "'n",
2995
+ Œ: "OE",
2996
+ œ: "oe",
2997
+ Ø: "O",
2998
+ ø: "o",
2999
+ ſ: "s",
3000
+ ß: "ss",
3001
+ Þ: "TH",
3002
+ þ: "th",
3003
+ Ŧ: "T",
3004
+ ŧ: "t"
3005
+ };
3006
+ const DEBURR_NORMALIZATION = "NFD";
3007
+ const DEBURR_PATTERN_CHARACTERS = new RegExp(`(${Object.keys(DEBURR_CHARACTERS).join("|")})`, "g");
3008
+ const DEBURR_PATTERN_SIMPLE = /[\u0300-\u036f]/g;
3009
+ let deburrMemoizer;
3010
+ //#endregion
2825
3011
  //#region src/string/template.ts
2826
3012
  function getTemplateOptions(input) {
2827
3013
  const options = isPlainObject(input) ? input : {};
@@ -5054,4 +5240,4 @@ var SizedSet = class extends Set {
5054
5240
  }
5055
5241
  };
5056
5242
  //#endregion
5057
- export { CancelablePromise, PROMISE_ABORT_EVENT, PROMISE_ABORT_OPTIONS, PROMISE_ERROR_NAME, PROMISE_MESSAGE_EXPECTATION_ATTEMPT, PROMISE_MESSAGE_EXPECTATION_RESULT, PROMISE_MESSAGE_EXPECTATION_TIMED, PROMISE_MESSAGE_TIMEOUT, PROMISE_STRATEGY_ALL, PROMISE_STRATEGY_DEFAULT, PROMISE_TYPE_FULFILLED, PROMISE_TYPE_REJECTED, PromiseTimeoutError, QueueError, RetryError, SORT_DIRECTION_ASCENDING, SORT_DIRECTION_DESCENDING, SizedMap, SizedSet, assert, assertCondition, assertDefined, assertInstanceOf, assertIs, asyncAttempt, asyncDebounce, asyncFlow, asyncMatchResult, asyncOnce, asyncPipe, asyncThrottle, attempt, attemptAsyncFlow, attemptAsyncPipe, attemptFlow, attemptPipe, attemptPromise, average, beacon, between, camelCase, cancelable, capitalize, ceil, chunk, clamp, clone, compact, compare, count, debounce, dedent, delay, deregisterCloner, deregisterComparator, deregisterEqualizer, diff, difference, drop, endsWith, endsWithArray, equal, error, exclude, exists, filter, find, findLast, first, firstOrDefault, flatten, floor, flow, fromQuery, toPromise as fromResult, toPromise, fuzzy, fuzzyMatch, getArray, getArrayPosition, getColor, getError, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getSortedIndex, getString, getTimedPromise, getUuid, getValue, groupArraysBy, groupBy, handleResult, hasValue, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, ignoreKey, inMap, inSet, includes, includesArray, indexOf, indexOfArray, initializeEqualizer, initializeMerger, initializeSorter, initializeTemplater, insert, intersection, isArrayOrPlainObject, isColor, isConstructor, isEmpty, isError, isFulfilled, isHexColor, isHslColor, isHslLike, isHslaColor, isInstanceOf, isKey, isNonArrayOrPlainObject, isNonConstructor, isNonEmpty, isNonInstanceOf, isNonKey, isNonNullable, isNonNullableOrEmpty, isNonNullableOrWhitespace, isNonNumber, isNonNumerical, isNonObject, isNonPlainObject, isNonPrimitive, isNonTypedArray, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isRejected, isResult, isRgbColor, isRgbLike, isRgbaColor, isSorted, isTypedArray, join, kebabCase, last, lastIndexOf, lastOrDefault, logger, lowerCase, matchResult, max, median, memoize, merge, min, move, moveIndices, moveToIndex, noop, ok, omit, once, parse, partition, pascalCase, pick, pipe, promises, push, queue, range, registerCloner, registerComparator, registerEqualizer, resultPromises, retry, reverse, rgbToHex, rgbToHsl, rgbToHsla, round, select, setValue, settlePromise, shake, shuffle, single, slice, smush, snakeCase, sort, splice, startsWith, startsWithArray, sum, swap, take, template, throttle, timed, times, titleCase, toMap, toMapArrays, toQuery, toRecord, toRecordArrays, toResult, toSet, toggle, trim, truncate, tryDecode, tryEncode, union, unique, unsmush, unwrap, update, upperCase, words };
5243
+ export { CancelablePromise, PROMISE_ABORT_EVENT, PROMISE_ABORT_OPTIONS, PROMISE_ERROR_NAME, PROMISE_MESSAGE_EXPECTATION_ATTEMPT, PROMISE_MESSAGE_EXPECTATION_RESULT, PROMISE_MESSAGE_EXPECTATION_TIMED, PROMISE_MESSAGE_TIMEOUT, PROMISE_STRATEGY_ALL, PROMISE_STRATEGY_DEFAULT, PROMISE_TYPE_FULFILLED, PROMISE_TYPE_REJECTED, PromiseTimeoutError, QueueError, RetryError, SORT_DIRECTION_ASCENDING, SORT_DIRECTION_DESCENDING, SizedMap, SizedSet, assert, assertCondition, assertDefined, assertInstanceOf, assertIs, asyncAttempt, asyncDebounce, asyncFlow, asyncMatchResult, asyncOnce, asyncPipe, asyncThrottle, attempt, attemptAsyncFlow, attemptAsyncPipe, attemptFlow, attemptPipe, attemptPromise, average, beacon, between, camelCase, cancelable, capitalize, ceil, chunk, clamp, clone, compact, compare, count, debounce, deburr, dedent, delay, deregisterCloner, deregisterComparator, deregisterEqualizer, diff, difference, drop, endsWith, endsWithArray, equal, error, exclude, exists, filter, find, findLast, first, firstOrDefault, flatten, floor, flow, fromQuery, toPromise as fromResult, toPromise, fuzzy, fuzzyMatch, getArray, getArrayPosition, getColor, getError, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getSortedIndex, getString, getTimedPromise, getUuid, getValue, groupArraysBy, groupBy, handleResult, hasValue, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, ignoreKey, inMap, inSet, includes, includesArray, indexOf, indexOfArray, initializeEqualizer, initializeMerger, initializeNormalizer, initializeSorter, initializeTemplater, insert, intersection, isArrayOrPlainObject, isColor, isConstructor, isEmpty, isError, isFulfilled, isHexColor, isHslColor, isHslLike, isHslaColor, isInstanceOf, isKey, isNonArrayOrPlainObject, isNonConstructor, isNonEmpty, isNonInstanceOf, isNonKey, isNonNullable, isNonNullableOrEmpty, isNonNullableOrWhitespace, isNonNumber, isNonNumerical, isNonObject, isNonPlainObject, isNonPrimitive, isNonTypedArray, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isRejected, isResult, isRgbColor, isRgbLike, isRgbaColor, isSorted, isTypedArray, join, kalas, kebabCase, last, lastIndexOf, lastOrDefault, logger, lowerCase, matchResult, max, median, memoize, merge, min, move, moveIndices, moveToIndex, noop, normalize, ok, omit, once, parse, partition, pascalCase, pick, pipe, promises, push, queue, range, registerCloner, registerComparator, registerEqualizer, resultPromises, retry, reverse, rgbToHex, rgbToHsl, rgbToHsla, round, select, setValue, settlePromise, shake, shuffle, single, slice, smush, snakeCase, sort, splice, startsWith, startsWithArray, sum, swap, take, template, throttle, timed, times, titleCase, toMap, toMapArrays, toQuery, toRecord, toRecordArrays, toResult, toSet, toggle, trim, truncate, tryDecode, tryEncode, union, unique, unsmush, unwrap, update, upperCase, words };
@@ -0,0 +1,61 @@
1
+ import { GenericCallback } from "./models.mjs";
2
+
3
+ //#region src/kalas.d.ts
4
+ declare class Events<Map extends Record<string, GenericCallback>> {
5
+ #private;
6
+ constructor(kalas: Kalas<Map>);
7
+ /**
8
+ * Subscribe to an event with a callback
9
+ * @param event Event name
10
+ * @param callback Callback function
11
+ * @returns Unsubscriber function
12
+ */
13
+ subscribe<Event extends keyof Map>(event: Event, callback: Map[Event]): Unsubscriber;
14
+ /**
15
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
16
+ * @param event Event name
17
+ * @param callback Callback function
18
+ * @returns Unsubscriber function
19
+ */
20
+ unsubscribe<Event extends keyof Map>(event: Event, callback?: Map[Event]): void;
21
+ }
22
+ declare class Kalas<Map extends Record<string, GenericCallback>> {
23
+ #private;
24
+ /**
25
+ * Events interface for subscribing and unsubscribing to events
26
+ */
27
+ readonly events: Events<Map>;
28
+ constructor(names: (keyof Map)[]);
29
+ /**
30
+ * Remove all event subscribers
31
+ */
32
+ clear(): void;
33
+ /**
34
+ * Emit an event with parameters
35
+ * @param event Event name
36
+ * @param parameters Event parameters
37
+ */
38
+ emit<Event extends keyof Map>(event: Event, ...parameters: Parameters<Map[Event]>): void;
39
+ /**
40
+ * Subscribe to an event with a callback
41
+ * @param event Event name
42
+ * @param callback Callback function
43
+ * @returns Unsubscriber function
44
+ */
45
+ subscribe<Event extends keyof Map>(event: Event, callback: Map[Event]): Unsubscriber;
46
+ /**
47
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
48
+ * @param event Event name
49
+ * @param callback Callback function
50
+ */
51
+ unsubscribe<Event extends keyof Map>(event: Event, callback?: Map[Event]): void;
52
+ }
53
+ type Unsubscriber = () => void;
54
+ /**
55
+ * Create a Kalas _(party)_ for named events
56
+ * @param names Event names
57
+ * @returns Kalas instance
58
+ */
59
+ declare function kalas<Events extends Record<string, GenericCallback>>(names: (keyof Events)[]): Kalas<Events>;
60
+ //#endregion
61
+ export { type Events, type Kalas, Unsubscriber, kalas };
package/dist/kalas.mjs ADDED
@@ -0,0 +1,93 @@
1
+ import { noop } from "./internal/function/misc.mjs";
2
+ //#region src/kalas.ts
3
+ var Events = class {
4
+ #kalas;
5
+ constructor(kalas) {
6
+ this.#kalas = kalas;
7
+ }
8
+ /**
9
+ * Subscribe to an event with a callback
10
+ * @param event Event name
11
+ * @param callback Callback function
12
+ * @returns Unsubscriber function
13
+ */
14
+ subscribe(event, callback) {
15
+ return this.#kalas.subscribe(event, callback);
16
+ }
17
+ /**
18
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
19
+ * @param event Event name
20
+ * @param callback Callback function
21
+ * @returns Unsubscriber function
22
+ */
23
+ unsubscribe(event, callback) {
24
+ return this.#kalas.unsubscribe(event, callback);
25
+ }
26
+ };
27
+ var Kalas = class {
28
+ #names;
29
+ #subscribers = /* @__PURE__ */ new Map();
30
+ constructor(names) {
31
+ this.#names = new Set(names);
32
+ Object.defineProperty(this, "events", { value: new Events(this) });
33
+ }
34
+ /**
35
+ * Remove all event subscribers
36
+ */
37
+ clear() {
38
+ this.#subscribers.clear();
39
+ }
40
+ /**
41
+ * Emit an event with parameters
42
+ * @param event Event name
43
+ * @param parameters Event parameters
44
+ */
45
+ emit(event, ...parameters) {
46
+ const subscribers = this.#subscribers.get(event);
47
+ if (subscribers == null) return;
48
+ for (const callback of subscribers) callback(...parameters);
49
+ }
50
+ /**
51
+ * Subscribe to an event with a callback
52
+ * @param event Event name
53
+ * @param callback Callback function
54
+ * @returns Unsubscriber function
55
+ */
56
+ subscribe(event, callback) {
57
+ if (!this.#names.has(event) || typeof callback !== "function") return noop;
58
+ let subscribers = this.#subscribers.get(event);
59
+ if (subscribers == null) {
60
+ subscribers = /* @__PURE__ */ new Set();
61
+ this.#subscribers.set(event, subscribers);
62
+ }
63
+ subscribers.add(callback);
64
+ return () => {
65
+ subscribers?.delete(callback);
66
+ };
67
+ }
68
+ /**
69
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
70
+ * @param event Event name
71
+ * @param callback Callback function
72
+ */
73
+ unsubscribe(event, callback) {
74
+ if (!this.#names.has(event) || (callback != null ? typeof callback !== "function" : false)) return;
75
+ const subscribers = this.#subscribers.get(event);
76
+ if (subscribers == null) return;
77
+ if (callback == null) subscribers.clear();
78
+ else subscribers.delete(callback);
79
+ if (callback == null || subscribers.size === 0) this.#subscribers.delete(event);
80
+ }
81
+ };
82
+ /**
83
+ * Create a Kalas _(party)_ for named events
84
+ * @param names Event names
85
+ * @returns Kalas instance
86
+ */
87
+ function kalas(names) {
88
+ if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string")) throw new Error(MESSAGE);
89
+ return new Kalas(names);
90
+ }
91
+ const MESSAGE = "Kalas requires an array of event names.";
92
+ //#endregion
93
+ export { kalas };
@@ -33,7 +33,9 @@ function kebabCase(value) {
33
33
  * @returns Lower-cased string
34
34
  */
35
35
  function lowerCase(value) {
36
- return typeof value === "string" ? value.toLocaleLowerCase() : "";
36
+ if (typeof value !== "string") return "";
37
+ memoizedLowerCase ??= memoize((v) => v.toLocaleLowerCase());
38
+ return memoizedLowerCase.run(value);
37
39
  }
38
40
  /**
39
41
  * Convert a string to pascal case _(ThisIsPascalCase)_
@@ -57,8 +59,8 @@ function snakeCase(value) {
57
59
  * @returns Title-cased string
58
60
  */
59
61
  function titleCase(value) {
60
- if (typeof value !== "string") return "";
61
- memoizedTitleCase ??= memoize((v) => v.length < 1 ? capitalize(v) : join(words(v).map(capitalize), " "));
62
+ if (typeof value !== "string" || value.length === 0) return "";
63
+ memoizedTitleCase ??= memoize((v) => v.length < 2 ? capitalize(v) : join(words(v).map(capitalize), " "));
62
64
  return memoizedTitleCase.run(value);
63
65
  }
64
66
  function toCase(type, value, capitalizeAny, capitalizeFirst) {
@@ -98,7 +100,9 @@ function toCaseCallback(value) {
98
100
  * @returns Upper-cased string
99
101
  */
100
102
  function upperCase(value) {
101
- return typeof value === "string" ? value.toLocaleUpperCase() : "";
103
+ if (typeof value !== "string" || value.length === 0) return "";
104
+ memoizedUpperCase ??= memoize((v) => v.toLocaleUpperCase());
105
+ return memoizedUpperCase.run(value);
102
106
  }
103
107
  const CASE_CAMEL = "camel";
104
108
  const CASE_KEBAB = "kebab";
@@ -119,6 +123,8 @@ const delimiters = {
119
123
  [CASE_SNAKE]: DELIMITER_UNDERSCORE
120
124
  };
121
125
  let memoizedCapitalize;
126
+ let memoizedLowerCase;
122
127
  let memoizedTitleCase;
128
+ let memoizedUpperCase;
123
129
  //#endregion
124
130
  export { camelCase, capitalize, kebabCase, lowerCase, pascalCase, snakeCase, titleCase, upperCase };
@@ -0,0 +1,55 @@
1
+ //#region src/string/normalize.d.ts
2
+ /**
3
+ * Options for normalizing a string
4
+ */
5
+ type NormalizeOptions = {
6
+ /**
7
+ * Remove diacritical marks from the string? _(defaults to `true`)_
8
+ */
9
+ deburr?: boolean;
10
+ /**
11
+ * Convert the string to lower case? _(defaults to `true`)_
12
+ */
13
+ lowerCase?: boolean;
14
+ /**
15
+ * Trim the string? _(defaults to `true`)_
16
+ */
17
+ trim?: boolean;
18
+ };
19
+ /**
20
+ * String normalizer function
21
+ */
22
+ type Normalizer = {
23
+ /**
24
+ * Normalize a string
25
+ * @param value String to normalize
26
+ * @returns Normalized string
27
+ */
28
+ (value: string): string;
29
+ };
30
+ /**
31
+ * Deburr a string, removing diacritical marks
32
+ * @param value String to deburr
33
+ * @returns Deburred string
34
+ */
35
+ declare function deburr(value: string): string;
36
+ /**
37
+ * Initialize a string normalizer
38
+ * @param options Normalization options
39
+ * @returns Normalizer function
40
+ */
41
+ declare function initializeNormalizer(options?: NormalizeOptions): Normalizer;
42
+ /**
43
+ * Normalize a string
44
+ *
45
+ * By default, the string will be trimmed, deburred, and then lowercased
46
+ * @param value String to normalize
47
+ * @param options Normalization options
48
+ * @returns Normalized string
49
+ */
50
+ declare function normalize(value: string, options?: NormalizeOptions): string;
51
+ declare namespace normalize {
52
+ var initialize: typeof initializeNormalizer;
53
+ }
54
+ //#endregion
55
+ export { NormalizeOptions, Normalizer, deburr, initializeNormalizer, normalize };
@@ -0,0 +1,93 @@
1
+ import { isPlainObject } from "../internal/is.mjs";
2
+ import { memoize } from "../function/memoize.mjs";
3
+ import { lowerCase } from "./case.mjs";
4
+ //#region src/string/normalize.ts
5
+ /**
6
+ * Deburr a string, removing diacritical marks
7
+ * @param value String to deburr
8
+ * @returns Deburred string
9
+ */
10
+ function deburr(value) {
11
+ if (typeof value !== "string") return "";
12
+ deburrMemoizer ??= memoize((value) => {
13
+ let deburred = value.normalize(DEBURR_NORMALIZATION).replace(DEBURR_PATTERN_SIMPLE, "");
14
+ deburred = deburred.replace(DEBURR_PATTERN_CHARACTERS, (_, character) => DEBURR_CHARACTERS[character]);
15
+ return deburred;
16
+ });
17
+ return deburrMemoizer.run(value);
18
+ }
19
+ function getNormalizeOptions(input) {
20
+ const options = isPlainObject(input) ? input : {};
21
+ return {
22
+ deburr: options.deburr !== false,
23
+ lowerCase: options.lowerCase !== false,
24
+ trim: options.trim !== false
25
+ };
26
+ }
27
+ /**
28
+ * Initialize a string normalizer
29
+ * @param options Normalization options
30
+ * @returns Normalizer function
31
+ */
32
+ function initializeNormalizer(options) {
33
+ const normalization = getNormalizeOptions(options);
34
+ return (value) => normalizeString(value, normalization);
35
+ }
36
+ /**
37
+ * Normalize a string
38
+ *
39
+ * By default, the string will be trimmed, deburred, and then lowercased
40
+ * @param value String to normalize
41
+ * @param options Normalization options
42
+ * @returns Normalized string
43
+ */
44
+ function normalize(value, options) {
45
+ return normalizeString(value, getNormalizeOptions(options));
46
+ }
47
+ normalize.initialize = initializeNormalizer;
48
+ function normalizeString(value, options) {
49
+ if (typeof value !== "string") return "";
50
+ let result = value;
51
+ if (options.trim) result = result.trim();
52
+ if (options.deburr) result = deburr(result);
53
+ if (options.lowerCase) result = lowerCase(result);
54
+ return result;
55
+ }
56
+ const DEBURR_CHARACTERS = {
57
+ Æ: "AE",
58
+ æ: "ae",
59
+ Ð: "D",
60
+ ð: "d",
61
+ Đ: "D",
62
+ đ: "d",
63
+ Ħ: "H",
64
+ ħ: "h",
65
+ IJ: "IJ",
66
+ ij: "ij",
67
+ İ: "I",
68
+ ı: "i",
69
+ ĸ: "k",
70
+ Ŀ: "L",
71
+ ŀ: "l",
72
+ Ł: "L",
73
+ ł: "l",
74
+ Ŋ: "N",
75
+ ŋ: "n",
76
+ ʼn: "'n",
77
+ Œ: "OE",
78
+ œ: "oe",
79
+ Ø: "O",
80
+ ø: "o",
81
+ ſ: "s",
82
+ ß: "ss",
83
+ Þ: "TH",
84
+ þ: "th",
85
+ Ŧ: "T",
86
+ ŧ: "t"
87
+ };
88
+ const DEBURR_NORMALIZATION = "NFD";
89
+ const DEBURR_PATTERN_CHARACTERS = new RegExp(`(${Object.keys(DEBURR_CHARACTERS).join("|")})`, "g");
90
+ const DEBURR_PATTERN_SIMPLE = /[\u0300-\u036f]/g;
91
+ let deburrMemoizer;
92
+ //#endregion
93
+ export { deburr, initializeNormalizer, normalize };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/atoms",
3
- "version": "0.181.0",
3
+ "version": "0.182.0",
4
4
  "description": "Atomic utilities for making your JavaScript better.",
5
5
  "keywords": [
6
6
  "helper",
package/src/index.ts CHANGED
@@ -25,10 +25,13 @@ export * from './internal/value/get';
25
25
  export * from './internal/value/has';
26
26
  export * from './internal/value/set';
27
27
 
28
+ export * from './kalas';
29
+
28
30
  export * from './string/case';
29
31
  export * from './string/fuzzy';
30
32
  export * from './string/index';
31
33
  export * from './string/match';
34
+ export * from './string/normalize';
32
35
  export * from './string/template';
33
36
 
34
37
  export * from './value/clone';
package/src/kalas.ts ADDED
@@ -0,0 +1,167 @@
1
+ import {noop} from './internal/function/misc';
2
+ import type {GenericCallback} from './models';
3
+
4
+ // #region Types
5
+
6
+ class Events<Map extends Record<string, GenericCallback>> {
7
+ readonly #kalas: Kalas<Map>;
8
+
9
+ constructor(kalas: Kalas<Map>) {
10
+ this.#kalas = kalas;
11
+ }
12
+
13
+ /**
14
+ * Subscribe to an event with a callback
15
+ * @param event Event name
16
+ * @param callback Callback function
17
+ * @returns Unsubscriber function
18
+ */
19
+ subscribe<Event extends keyof Map>(event: Event, callback: Map[Event]): Unsubscriber {
20
+ return this.#kalas.subscribe(event, callback);
21
+ }
22
+
23
+ /**
24
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
25
+ * @param event Event name
26
+ * @param callback Callback function
27
+ * @returns Unsubscriber function
28
+ */
29
+ unsubscribe<Event extends keyof Map>(event: Event, callback?: Map[Event]): void {
30
+ return this.#kalas.unsubscribe(event, callback);
31
+ }
32
+ }
33
+
34
+ class Kalas<Map extends Record<string, GenericCallback>> {
35
+ readonly #names: Set<keyof Map>;
36
+
37
+ readonly #subscribers = new Map<keyof Map, Set<Map[keyof Map]>>();
38
+
39
+ /**
40
+ * Events interface for subscribing and unsubscribing to events
41
+ */
42
+ declare readonly events: Events<Map>;
43
+
44
+ constructor(names: (keyof Map)[]) {
45
+ this.#names = new Set(names);
46
+
47
+ Object.defineProperty(this, 'events', {
48
+ value: new Events(this),
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Remove all event subscribers
54
+ */
55
+ clear(): void {
56
+ this.#subscribers.clear();
57
+ }
58
+
59
+ /**
60
+ * Emit an event with parameters
61
+ * @param event Event name
62
+ * @param parameters Event parameters
63
+ */
64
+ emit<Event extends keyof Map>(event: Event, ...parameters: Parameters<Map[Event]>) {
65
+ const subscribers = this.#subscribers.get(event);
66
+
67
+ if (subscribers == null) {
68
+ return;
69
+ }
70
+
71
+ for (const callback of subscribers) {
72
+ callback(...parameters);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Subscribe to an event with a callback
78
+ * @param event Event name
79
+ * @param callback Callback function
80
+ * @returns Unsubscriber function
81
+ */
82
+ subscribe<Event extends keyof Map>(event: Event, callback: Map[Event]): Unsubscriber {
83
+ if (!this.#names.has(event) || typeof callback !== 'function') {
84
+ return noop;
85
+ }
86
+
87
+ let subscribers = this.#subscribers.get(event);
88
+
89
+ if (subscribers == null) {
90
+ subscribers = new Set();
91
+
92
+ this.#subscribers.set(event, subscribers);
93
+ }
94
+
95
+ subscribers.add(callback);
96
+
97
+ return () => {
98
+ subscribers?.delete(callback);
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Unsubscribe from an event with a callback _(or all callbacks, if no callback is provided)_
104
+ * @param event Event name
105
+ * @param callback Callback function
106
+ */
107
+ unsubscribe<Event extends keyof Map>(event: Event, callback?: Map[Event]): void {
108
+ if (!this.#names.has(event) || (callback != null ? typeof callback !== 'function' : false)) {
109
+ return;
110
+ }
111
+
112
+ const subscribers = this.#subscribers.get(event);
113
+
114
+ if (subscribers == null) {
115
+ return;
116
+ }
117
+
118
+ if (callback == null) {
119
+ subscribers.clear();
120
+ } else {
121
+ subscribers.delete(callback);
122
+ }
123
+
124
+ if (callback == null || subscribers.size === 0) {
125
+ this.#subscribers.delete(event);
126
+ }
127
+ }
128
+ }
129
+
130
+ export type Unsubscriber = () => void;
131
+
132
+ // #endregion
133
+
134
+ // #region Functions
135
+
136
+ /**
137
+ * Create a Kalas _(party)_ for named events
138
+ * @param names Event names
139
+ * @returns Kalas instance
140
+ */
141
+ export function kalas<Events extends Record<string, GenericCallback>>(
142
+ names: (keyof Events)[],
143
+ ): Kalas<Events> {
144
+ if (
145
+ !Array.isArray(names) ||
146
+ names.length === 0 ||
147
+ !names.every(name => typeof name === 'string')
148
+ ) {
149
+ throw new Error(MESSAGE);
150
+ }
151
+
152
+ return new Kalas<Events>(names);
153
+ }
154
+
155
+ // #endregion
156
+
157
+ // #region Variables
158
+
159
+ const MESSAGE = 'Kalas requires an array of event names.';
160
+
161
+ // #endregion
162
+
163
+ // #region Exports
164
+
165
+ export {type Events, type Kalas};
166
+
167
+ // #endregion
@@ -58,7 +58,13 @@ export function kebabCase(value: string): string {
58
58
  * @returns Lower-cased string
59
59
  */
60
60
  export function lowerCase(value: string): string {
61
- return typeof value === 'string' ? value.toLocaleLowerCase() : '';
61
+ if (typeof value !== 'string') {
62
+ return '';
63
+ }
64
+
65
+ memoizedLowerCase ??= memoize(v => v.toLocaleLowerCase());
66
+
67
+ return memoizedLowerCase.run(value);
62
68
  }
63
69
 
64
70
  /**
@@ -85,12 +91,12 @@ export function snakeCase(value: string): string {
85
91
  * @returns Title-cased string
86
92
  */
87
93
  export function titleCase(value: string): string {
88
- if (typeof value !== 'string') {
94
+ if (typeof value !== 'string' || value.length === 0) {
89
95
  return '';
90
96
  }
91
97
 
92
98
  memoizedTitleCase ??= memoize(v =>
93
- v.length < 1 ? capitalize(v) : join(words(v).map(capitalize), ' '),
99
+ v.length < 2 ? capitalize(v) : join(words(v).map(capitalize), ' '),
94
100
  );
95
101
 
96
102
  return memoizedTitleCase.run(value);
@@ -167,7 +173,13 @@ function toCaseCallback(this: Options, value: string): string {
167
173
  * @returns Upper-cased string
168
174
  */
169
175
  export function upperCase(value: string): string {
170
- return typeof value === 'string' ? value.toLocaleUpperCase() : '';
176
+ if (typeof value !== 'string' || value.length === 0) {
177
+ return '';
178
+ }
179
+
180
+ memoizedUpperCase ??= memoize(v => v.toLocaleUpperCase());
181
+
182
+ return memoizedUpperCase.run(value);
171
183
  }
172
184
 
173
185
  // #endregion
@@ -207,6 +219,10 @@ const delimiters: Record<Case, string> = {
207
219
 
208
220
  let memoizedCapitalize: Memoized<(value: string) => string>;
209
221
 
222
+ let memoizedLowerCase: Memoized<(value: string) => string>;
223
+
210
224
  let memoizedTitleCase: Memoized<(value: string) => string>;
211
225
 
226
+ let memoizedUpperCase: Memoized<(value: string) => string>;
227
+
212
228
  // #endregion
@@ -0,0 +1,169 @@
1
+ import {memoize, type Memoized} from '../function/memoize';
2
+ import {isPlainObject} from '../internal/is';
3
+ import {lowerCase} from './case';
4
+
5
+ // #region Types
6
+
7
+ /**
8
+ * Options for normalizing a string
9
+ */
10
+ export type NormalizeOptions = {
11
+ /**
12
+ * Remove diacritical marks from the string? _(defaults to `true`)_
13
+ */
14
+ deburr?: boolean;
15
+ /**
16
+ * Convert the string to lower case? _(defaults to `true`)_
17
+ */
18
+ lowerCase?: boolean;
19
+ /**
20
+ * Trim the string? _(defaults to `true`)_
21
+ */
22
+ trim?: boolean;
23
+ };
24
+
25
+ /**
26
+ * String normalizer function
27
+ */
28
+ export type Normalizer = {
29
+ /**
30
+ * Normalize a string
31
+ * @param value String to normalize
32
+ * @returns Normalized string
33
+ */
34
+ (value: string): string;
35
+ };
36
+
37
+ type Options = Required<NormalizeOptions>;
38
+
39
+ // #endregion
40
+
41
+ // #region Functions
42
+
43
+ /**
44
+ * Deburr a string, removing diacritical marks
45
+ * @param value String to deburr
46
+ * @returns Deburred string
47
+ */
48
+ export function deburr(value: string): string {
49
+ if (typeof value !== 'string') {
50
+ return '';
51
+ }
52
+
53
+ deburrMemoizer ??= memoize(value => {
54
+ let deburred = value.normalize(DEBURR_NORMALIZATION).replace(DEBURR_PATTERN_SIMPLE, '');
55
+
56
+ deburred = deburred.replace(
57
+ DEBURR_PATTERN_CHARACTERS,
58
+ (_, character) => DEBURR_CHARACTERS[character as keyof typeof DEBURR_CHARACTERS],
59
+ );
60
+
61
+ return deburred;
62
+ });
63
+
64
+ return deburrMemoizer.run(value);
65
+ }
66
+
67
+ function getNormalizeOptions(input?: NormalizeOptions): Options {
68
+ const options = isPlainObject(input) ? input : {};
69
+
70
+ return {
71
+ deburr: options.deburr !== false,
72
+ lowerCase: options.lowerCase !== false,
73
+ trim: options.trim !== false,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Initialize a string normalizer
79
+ * @param options Normalization options
80
+ * @returns Normalizer function
81
+ */
82
+ export function initializeNormalizer(options?: NormalizeOptions): Normalizer {
83
+ const normalization = getNormalizeOptions(options);
84
+
85
+ return (value: string) => normalizeString(value, normalization);
86
+ }
87
+
88
+ /**
89
+ * Normalize a string
90
+ *
91
+ * By default, the string will be trimmed, deburred, and then lowercased
92
+ * @param value String to normalize
93
+ * @param options Normalization options
94
+ * @returns Normalized string
95
+ */
96
+ export function normalize(value: string, options?: NormalizeOptions): string {
97
+ return normalizeString(value, getNormalizeOptions(options));
98
+ }
99
+
100
+ normalize.initialize = initializeNormalizer;
101
+
102
+ function normalizeString(value: string, options: Options): string {
103
+ if (typeof value !== 'string') {
104
+ return '';
105
+ }
106
+
107
+ let result = value;
108
+
109
+ if (options.trim) {
110
+ result = result.trim();
111
+ }
112
+
113
+ if (options.deburr) {
114
+ result = deburr(result);
115
+ }
116
+
117
+ if (options.lowerCase) {
118
+ result = lowerCase(result);
119
+ }
120
+
121
+ return result;
122
+ }
123
+
124
+ // #endregion
125
+
126
+ // #region Variables
127
+
128
+ const DEBURR_CHARACTERS = {
129
+ Æ: 'AE',
130
+ æ: 'ae',
131
+ Ð: 'D',
132
+ ð: 'd',
133
+ Đ: 'D',
134
+ đ: 'd',
135
+ Ħ: 'H',
136
+ ħ: 'h',
137
+ IJ: 'IJ',
138
+ ij: 'ij',
139
+ İ: 'I',
140
+ ı: 'i',
141
+ ĸ: 'k',
142
+ Ŀ: 'L',
143
+ ŀ: 'l',
144
+ Ł: 'L',
145
+ ł: 'l',
146
+ Ŋ: 'N',
147
+ ŋ: 'n',
148
+ ʼn: "'n",
149
+ Œ: 'OE',
150
+ œ: 'oe',
151
+ Ø: 'O',
152
+ ø: 'o',
153
+ ſ: 's',
154
+ ß: 'ss',
155
+ Þ: 'TH',
156
+ þ: 'th',
157
+ Ŧ: 'T',
158
+ ŧ: 't',
159
+ };
160
+
161
+ const DEBURR_NORMALIZATION = 'NFD';
162
+
163
+ const DEBURR_PATTERN_CHARACTERS = new RegExp(`(${Object.keys(DEBURR_CHARACTERS).join('|')})`, 'g');
164
+
165
+ const DEBURR_PATTERN_SIMPLE = /[\u0300-\u036f]/g;
166
+
167
+ let deburrMemoizer: Memoized<typeof deburr>;
168
+
169
+ // #endregion