wunderbaum 0.12.1 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wunderbaum",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "title": "A treegrid control.",
5
5
  "description": "JavaScript tree/grid/treegrid control.",
6
6
  "homepage": "https://github.com/mar10/wunderbaum",
@@ -52,6 +52,7 @@
52
52
  "@rollup/plugin-replace": "^6.0.2",
53
53
  "@rollup/plugin-terser": "^0.4.4",
54
54
  "@rollup/plugin-typescript": "^12.1.2",
55
+ "@types/firebase": "^2.4.32",
55
56
  "@types/jest": "^29.5.14",
56
57
  "@typescript-eslint/eslint-plugin": "^8.24.0",
57
58
  "@typescript-eslint/parser": "^8.24.0",
@@ -104,7 +105,7 @@
104
105
  "scripts": {
105
106
  "test": "npm run lint && npm run build:js && grunt ci --verbose",
106
107
  "api_docs": "typedoc && touch docs/api/.nojekyll && rm docs/unittest/*.*; cp test/unit/*.* docs/unittest",
107
- "format": "eslint src --fix && prettier src docs/demo -w && npm run lint",
108
+ "format": "eslint src docs/demo --fix && prettier src docs/demo -w && npm run lint",
108
109
  "lint": "prettier src docs/demo --check && eslint src docs/demo && tsc -t esnext --moduleResolution node --noEmit src/wunderbaum.ts",
109
110
  "build:minjs:umd": "terser build/wunderbaum.umd.js --compress --mangle --source-map \"base='build',url='wunderbaum.umd.min.js.map',filename='wunderbaum.umd.js'\" --output build/wunderbaum.umd.min.js",
110
111
  "build:minjs:esm": "terser build/wunderbaum.esm.js --compress --mangle --source-map \"base='build',url='wunderbaum.esm.min.js.map',filename='wunderbaum.esm.js'\" --output build/wunderbaum.esm.min.js",
package/src/common.ts CHANGED
@@ -4,7 +4,14 @@
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
6
 
7
- import { MatcherCallback, SourceListType, SourceObjectType } from "./types";
7
+ import {
8
+ ApplyCommandType,
9
+ NavigationType,
10
+ SourceListType,
11
+ SourceObjectType,
12
+ IconMapType,
13
+ MatcherCallback,
14
+ } from "./types";
8
15
  import * as util from "./util";
9
16
  import { WunderbaumNode } from "./wb_node";
10
17
 
@@ -28,13 +35,23 @@ export const RENDER_MAX_PREFETCH = 5;
28
35
  export const RENDER_MIN_PREFETCH = 5;
29
36
  /** Minimum column width if not set otherwise. */
30
37
  export const DEFAULT_MIN_COL_WIDTH = 4;
38
+ /**
39
+ * A value for `node.type` that by convention may be used to mark a node as directory.
40
+ * It may be used to sort 'directories' to the top.
41
+ */
42
+ export const NODE_TYPE_FOLDER = "folder";
31
43
  /** Regular expression to detect if a string describes an image URL (in contrast
32
44
  * to a class name). Strings are considered image urls if they contain '.' or '/'.
45
+ * `<` is ignored, because it is probably an html tag.
33
46
  */
34
- export const TEST_IMG = new RegExp(/\.|\//);
47
+ export const TEST_FILE_PATH = /^(?!.*<).*[/.]/;
48
+ /** Regular expression to detect if a string describes an HTML element. */
49
+ export const TEST_HTML = /</;
35
50
  // export const RECURSIVE_REQUEST_ERROR = "$recursive_request";
36
51
  // export const INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid";
37
52
 
53
+ /** Currently supported default icon maps. */
54
+ type IconLibrary = "bootstrap" | "fontawesome6";
38
55
  /**
39
56
  * Default node icons for icon libraries
40
57
  *
@@ -42,7 +59,7 @@ export const TEST_IMG = new RegExp(/\.|\//);
42
59
  * - 'fontawesome6' {@link https://fontawesome.com/icons}
43
60
  *
44
61
  */
45
- export const iconMaps: { [key: string]: { [key: string]: string } } = {
62
+ export const defaultIconMaps: { [key in IconLibrary]: IconMapType } = {
46
63
  bootstrap: {
47
64
  error: "bi bi-exclamation-triangle",
48
65
  // loading: "bi bi-hourglass-split wb-busy",
@@ -90,7 +107,7 @@ export const iconMaps: { [key: string]: { [key: string]: string } } = {
90
107
  radioChecked: "fa-solid fa-circle",
91
108
  radioUnchecked: "fa-regular fa-circle",
92
109
  radioUnknown: "fa-regular fa-circle-question",
93
- folder: "fa-solid fa-folder-closed",
110
+ folder: "fa-regular fa-folder-closed",
94
111
  folderOpen: "fa-regular fa-folder-open",
95
112
  folderLazy: "fa-solid fa-folder-plus",
96
113
  doc: "fa-regular fa-file",
@@ -140,7 +157,24 @@ export const RESERVED_TREE_SOURCE_KEYS: Set<string> = new Set([
140
157
  // ]);
141
158
 
142
159
  /** Map `KeyEvent.key` to navigation action. */
143
- export const KEY_TO_ACTION_DICT: { [key: string]: string } = {
160
+ export const KEY_TO_NAVIGATION_MAP: { [key: string]: NavigationType } = {
161
+ ArrowDown: "down",
162
+ ArrowLeft: "left",
163
+ ArrowRight: "right",
164
+ ArrowUp: "up",
165
+ Backspace: "parent",
166
+ End: "lastCol",
167
+ Home: "firstCol",
168
+ "Control+End": "last",
169
+ "Control+Home": "first",
170
+ "Meta+ArrowDown": "last", // macOs
171
+ "Meta+ArrowUp": "first", // macOs
172
+ PageDown: "pageDown",
173
+ PageUp: "pageUp",
174
+ };
175
+
176
+ /** Map `KeyEvent.key` to navigation action. */
177
+ export const KEY_TO_COMMAND_MAP: { [key: string]: ApplyCommandType } = {
144
178
  " ": "toggleSelect",
145
179
  "+": "expand",
146
180
  Add: "expand",
@@ -197,7 +231,9 @@ export function makeNodeTitleStartMatcher(s: string): MatcherCallback {
197
231
  };
198
232
  }
199
233
 
200
- /** Compare two nodes by title (case-insensitive). */
234
+ /** Compare two nodes by title (case-insensitive).
235
+ * @deprecated Use `key` option instead of `cmp` in sort methods.
236
+ */
201
237
  export function nodeTitleSorter(a: WunderbaumNode, b: WunderbaumNode): number {
202
238
  const x = a.title.toLowerCase();
203
239
  const y = b.title.toLowerCase();
@@ -205,6 +241,13 @@ export function nodeTitleSorter(a: WunderbaumNode, b: WunderbaumNode): number {
205
241
  return x === y ? 0 : x > y ? 1 : -1;
206
242
  }
207
243
 
244
+ // /** Compare nodes by title (case-insensitive). */
245
+ // export function nodeTitleKeyGetter(
246
+ // node: WunderbaumNode
247
+ // ): string | number | Array<any> {
248
+ // return node.title.toLowerCase();
249
+ // }
250
+
208
251
  /**
209
252
  * Convert 'flat' to 'nested' format.
210
253
  *
package/src/types.ts CHANGED
@@ -60,8 +60,13 @@ export type MatcherCallback = (node: WunderbaumNode) => boolean;
60
60
  export type WbIconBadgeCallback = (
61
61
  e: WbIconBadgeEventType
62
62
  ) => WbIconBadgeEventResultType;
63
- /** Passed to `sortChildren()` methods. Should return -1, 0, or 1. */
63
+ /**
64
+ * Passed to `sort()` methods. Should return -1, 0, or 1.
65
+ * @deprecated Use SortKeyCallback instead
66
+ */
64
67
  export type SortCallback = (a: WunderbaumNode, b: WunderbaumNode) => number;
68
+ /** Passed to `sort()` methods. Should return a representation that can be compared using `<`. */
69
+ export type SortKeyCallback = (node: WunderbaumNode) => string | number | any[];
65
70
  /** When set as option, called when the value is needed (e.g. `colspan` type definition). */
66
71
  export type BoolOptionResolver = (node: WunderbaumNode) => boolean;
67
72
  /** When set as option, called when the value is needed (e.g. `icon` type definition). */
@@ -99,6 +104,12 @@ export type NodeToDictCallback = (
99
104
  */
100
105
  export type NodeSelectCallback = (node: WunderbaumNode) => boolean | void;
101
106
 
107
+ /** @internal */
108
+ export type DeprecationOptions = {
109
+ since?: string;
110
+ hint?: string;
111
+ };
112
+
102
113
  /**
103
114
  * See also {@link WunderbaumNode.getOption|WunderbaumNode.getOption()}
104
115
  * to evaluate `node.NAME` setting and `tree.types[node.type].NAME`.
@@ -163,7 +174,7 @@ export interface WbNodeData {
163
174
  title: string;
164
175
  /** Pass true to set node tooltip to the node's title. Defaults to {@link WunderbaumOptions.tooltip}. */
165
176
  tooltip?: TooltipOption;
166
- /** Inherit shared settings from the matching entry in {@link WunderbaumOptions.types}. */
177
+ /** Inherit shared settings from the matching entry in `InitWunderbaumOptions.types`. */
167
178
  type?: string;
168
179
  /** Set to `true` to prevent selection. Defaults to {@link WunderbaumOptions.unselectable}. */
169
180
  unselectable?: boolean;
@@ -173,11 +184,37 @@ export interface WbNodeData {
173
184
  [key: string]: unknown;
174
185
  }
175
186
 
187
+ /** A plain object (dictionary) that defines node icons. */
188
+ export interface IconMapType {
189
+ error: string;
190
+ loading: string;
191
+ noData: string;
192
+ expanderExpanded: string;
193
+ expanderCollapsed: string;
194
+ expanderLazy: string;
195
+ checkChecked: string;
196
+ checkUnchecked: string;
197
+ checkUnknown: string;
198
+ radioChecked: string;
199
+ radioUnchecked: string;
200
+ radioUnknown: string;
201
+ folder: string;
202
+ folderOpen: string;
203
+ folderLazy: string;
204
+ doc: string;
205
+ colSortable: string;
206
+ colSortAsc: string;
207
+ colSortDesc: string;
208
+ colFilter: string;
209
+ colFilterActive: string;
210
+ colMenu: string;
211
+ [key: string]: string;
212
+ }
176
213
  /* -----------------------------------------------------------------------------
177
214
  * EVENT CALLBACK TYPES
178
215
  * ---------------------------------------------------------------------------*/
179
216
 
180
- /** A callback that receives a node instance and returns a string value. */
217
+ /** Retuen value of an event handler that can return `false` to prevent the default action. */
181
218
  export type WbCancelableEventResultType = false | void;
182
219
 
183
220
  export interface WbTreeEventType {
@@ -518,29 +555,43 @@ export interface WbEventInfo {
518
555
  // export type WbNodeCallbackType = (e: WbNodeEventType) => any;
519
556
  // export type WbRenderCallbackType = (e: WbRenderEventType) => void;
520
557
 
521
- export type FilterModeType = null | "dim" | "hide";
558
+ export type FilterModeType = null | "mark" | "dim" | "hide";
522
559
  export type SelectModeType = "single" | "multi" | "hier";
560
+
561
+ export type NavigationType =
562
+ | "down"
563
+ | "first"
564
+ | "firstCol"
565
+ | "last"
566
+ | "lastCol"
567
+ | "left"
568
+ | "nextMatch"
569
+ | "pageDown"
570
+ | "pageUp"
571
+ | "parent"
572
+ | "prevMatch"
573
+ | "right"
574
+ | "up";
575
+
523
576
  export type ApplyCommandType =
577
+ | NavigationType
524
578
  | "addChild"
525
579
  | "addSibling"
580
+ | "collapse"
581
+ | "collapseAll"
526
582
  | "copy"
527
583
  | "cut"
528
- | "down"
529
- | "first"
584
+ | "edit"
585
+ | "expand"
586
+ | "expandAll"
530
587
  | "indent"
531
- | "last"
532
- | "left"
533
588
  | "moveDown"
534
589
  | "moveUp"
535
590
  | "outdent"
536
- | "pageDown"
537
- | "pageUp"
538
- | "parent"
539
591
  | "paste"
540
592
  | "remove"
541
593
  | "rename"
542
- | "right"
543
- | "up";
594
+ | "toggleSelect";
544
595
 
545
596
  export type NodeFilterResponse = "skip" | "branch" | boolean | void;
546
597
  export type NodeFilterCallback = (node: WunderbaumNode) => NodeFilterResponse;
@@ -607,6 +658,23 @@ export enum NavModeEnum {
607
658
  row = "row",
608
659
  }
609
660
 
661
+ /** Translatable strings. */
662
+ export type TranslationsType = {
663
+ /** @default "Loading..." */
664
+ loading: string;
665
+ /** @default "Error" */
666
+ loadError: string;
667
+ /** @default "No data" */
668
+ noData: string;
669
+ /** @default " » " */
670
+ breadcrumbDelimiter: string;
671
+ /** @default "Found ${matches} of ${count}" */
672
+ queryResult: string;
673
+ /** @default "No result" */
674
+ noMatch: string;
675
+ /** @default "${match} of ${matches}" */
676
+ matchIndex: string;
677
+ };
610
678
  /* -----------------------------------------------------------------------------
611
679
  * METHOD OPTIONS TYPES
612
680
  * ---------------------------------------------------------------------------*/
@@ -681,8 +749,7 @@ export interface FilterNodesOptions {
681
749
  /**Hide expanders if all child nodes are hidden by filter @default false */
682
750
  hideExpanders?: boolean;
683
751
  /** Highlight matches by wrapping inside `<mark>` tags.
684
- * Does not work for filter callbacks.
685
- * @default true
752
+ * Does not work for filter callbacks. @default true
686
753
  */
687
754
  highlight?: boolean;
688
755
  /** Match end nodes only @default false */
@@ -693,6 +760,44 @@ export interface FilterNodesOptions {
693
760
  noData?: boolean | string;
694
761
  }
695
762
 
763
+ /** Possible values for {@link Wunderbaum.getState}. */
764
+ export interface GetStateOptions {
765
+ /** Include the active node's key (and expand its parents). @default true */
766
+ activeKey?: boolean;
767
+ /** Include the expanded keys. @default false */
768
+ expandedKeys?: boolean;
769
+ /** Include the selected keys. @default false */
770
+ selectedKeys?: boolean;
771
+ }
772
+
773
+ /** Possible values for {@link Wunderbaum.setState}. */
774
+ export interface SetStateOptions {
775
+ /** Recursively load lazy nodes as needed. @default false */
776
+ expandLazy?: boolean;
777
+ }
778
+
779
+ /** Used by {@link Wunderbaum.getState} and {@link Wunderbaum.setState}. */
780
+ export interface TreeStateDefinition {
781
+ /** List of expanded node's keys. */
782
+ expandedKeys: Array<string> | undefined;
783
+ /** The active node's key if any. */
784
+ activeKey: string | null;
785
+ /** The active column index if any. */
786
+ activeColIdx: number | null;
787
+ /** List of selected node's keys. */
788
+ selectedKeys: Array<string> | undefined;
789
+ }
790
+
791
+ /** Possible values for {@link Wunderbaum.loadLazyNodes} `options` argument. */
792
+ export interface LoadLazyNodesOptions {
793
+ /** Expand node (otherwise load, but keep collapsed). @default true */
794
+ expand?: boolean;
795
+ /** Force reloading even if already loaded. @default false */
796
+ force?: boolean;
797
+ /** Do not send events. @default false */
798
+ noEvents?: boolean;
799
+ }
800
+
696
801
  /** Possible values for {@link WunderbaumNode.makeVisible}. */
697
802
  export interface MakeVisibleOptions {
698
803
  /** Do not animate expand (currently not implemented). @default false */
@@ -830,7 +935,17 @@ export interface SetStatusOptions {
830
935
  }
831
936
 
832
937
  /**
833
- * Possible values for {@link WunderbaumNode.sortByProperty} `options` argument.
938
+ * Possible values for {@link Wunderbaum.reload} `options` argument.
939
+ */
940
+ export interface ReloadOptions {
941
+ /** Load this source instead. @default initial source (if loaded via ajax) */
942
+ source?: SourceType;
943
+ /** Reactivate currently active node if any. @default true */
944
+ reactivate?: boolean;
945
+ }
946
+
947
+ /**
948
+ * Possible values for {@link WunderbaumNode.resetNativeChildOrder} `options` argument.
834
949
  */
835
950
  export interface ResetOrderOptions {
836
951
  /** Sort descendants recursively. @default true */
@@ -842,31 +957,37 @@ export interface ResetOrderOptions {
842
957
  }
843
958
 
844
959
  /**
845
- * Possible values for {@link WunderbaumNode.sortByProperty} `options` argument.
960
+ * Possible values for {@link Wunderbaum.sort} and {@link WunderbaumNode.sort}
961
+ * `options` argument.
846
962
  */
847
- export interface SortByPropertyOptions {
848
- /** Column ID as defined in `tree.columns` definition. Required if updateColInfo is true.*/
849
- colId?: string;
963
+ export interface SortOptions {
850
964
  /** The name of the node property that will be used for sorting.
851
- * @default use the `colId` as property name.
965
+ * Mandatory, unless {@link key} or {@link colId} are given.
852
966
  */
853
967
  propName?: string;
854
- // /** If defined, this callback is used to extract the value to be sorted. */
855
- // vallueGetter?: NodePropertyGetterCallback;
856
- /** Sort order. @default Use value from column definition (rotated).*/
968
+ /** Callback that determines a node representation for comparison.
969
+ * @default {@link common.nodeTitleKeyGetter} */
970
+ key?: SortKeyCallback;
971
+ /** Callback that determines the order. @default {@link common.nodeTitleSorter}
972
+ * @deprecated use {@link key} instead
973
+ */
974
+ cmp?: SortCallback;
975
+ /** Sort order 'asc' or 'desc'.
976
+ * @default 'asc' (or if `updateColInfo` is true, the rotated status of the
977
+ * column definition.
978
+ * See also {@link WunderbaumOptions.sortFoldersFirst}.
979
+ */
857
980
  order?: SortOrderType;
981
+ /** Sort descendants recursively. @default true */
982
+ deep?: boolean;
983
+ /** Sort string values case insensitive. @default false */
984
+ caseInsensitive?: boolean;
858
985
  /**
859
986
  * Sort by this property if order is `undefined`.
860
987
  * See also {@link WunderbaumNode.resetNativeChildOrder}.
861
988
  * @default `_nativeIndex`.
862
989
  */
863
990
  nativeOrderPropName?: string;
864
- /** Sort string values case insensitive. @default false */
865
- caseInsensitive?: boolean;
866
- /** Sort descendants recursively. @default true */
867
- deep?: boolean;
868
- // /** Rotate sort order (asc -> desc -> none) before sorting. @default false */
869
- // rotateOrder?: boolean;
870
991
  /**
871
992
  * Rotate sort order (asc -> desc -> none) before sorting.
872
993
  * Update the sort icons in the column header
@@ -877,8 +998,16 @@ export interface SortByPropertyOptions {
877
998
  * @default false
878
999
  */
879
1000
  updateColInfo?: boolean;
1001
+ /** Column ID as defined in `tree.columns` definition. Required if updateColInfo is true.*/
1002
+ colId?: string;
880
1003
  }
881
1004
 
1005
+ /**
1006
+ * Possible values for {@link WunderbaumNode.sortByProperty} `options` argument.
1007
+ * @deprecated
1008
+ */
1009
+ export type SortByPropertyOptions = SortOptions;
1010
+
882
1011
  /** Options passed to {@link Wunderbaum.visitRows}. */
883
1012
  export interface VisitRowsOptions {
884
1013
  /** Skip filtered nodes and children of collapsed nodes. @default false */
@@ -897,6 +1026,19 @@ export interface VisitRowsOptions {
897
1026
  /* -----------------------------------------------------------------------------
898
1027
  * wb_ext_filter
899
1028
  * ---------------------------------------------------------------------------*/
1029
+
1030
+ /**
1031
+ * Passed as tree option.filer.connect to configure automatic integration of
1032
+ * filter UI controls. @experimental
1033
+ */
1034
+ export interface FilterConnectType {
1035
+ inputElem: string | HTMLInputElement | null;
1036
+ modeButton?: string | HTMLButtonElement | null;
1037
+ nextButton?: string | HTMLButtonElement | HTMLAnchorElement | null;
1038
+ prevButton?: string | HTMLButtonElement | HTMLAnchorElement | null;
1039
+ matchInfoElem?: string | HTMLElement | null;
1040
+ }
1041
+
900
1042
  /**
901
1043
  * Passed as tree options to configure default filtering behavior.
902
1044
  *
@@ -905,10 +1047,12 @@ export interface VisitRowsOptions {
905
1047
  */
906
1048
  export type FilterOptionsType = {
907
1049
  /**
908
- * Element or selector of an input control for filter query strings
1050
+ * Element or selector of input controls and buttons for filter query strings.
1051
+ * @experimental
1052
+ * @since 0.13
909
1053
  * @default null
910
1054
  */
911
- connectInput?: null | string | Element;
1055
+ connect?: null | FilterConnectType;
912
1056
  /**
913
1057
  * Re-apply last filter if lazy data is loaded
914
1058
  * @default true
@@ -1019,6 +1163,7 @@ export type DropEffectAllowedType =
1019
1163
 
1020
1164
  export type DropRegionType = "over" | "before" | "after";
1021
1165
  export type DropRegionTypeSet = Set<DropRegionType>;
1166
+ export type DropRegionTypeList = Array<DropRegionType>;
1022
1167
  // type AllowedDropRegionType =
1023
1168
  // | "after"
1024
1169
  // | "afterBefore"
@@ -1044,6 +1189,8 @@ export interface DropEventType extends WbNodeEventType {
1044
1189
  node: WunderbaumNode;
1045
1190
  /** The source node if any. */
1046
1191
  sourceNode: WunderbaumNode;
1192
+ /** The DataTransfer object. */
1193
+ dataTransfer: DataTransfer;
1047
1194
  }
1048
1195
 
1049
1196
  export type DndOptionsType = {
@@ -1189,7 +1336,9 @@ export type DndOptionsType = {
1189
1336
  */
1190
1337
  dragEnter?:
1191
1338
  | null
1192
- | ((e: DropEventType) => DropRegionType | DropRegionTypeSet | boolean);
1339
+ | ((
1340
+ e: DropEventType
1341
+ ) => DropRegionType | DropRegionTypeSet | DropRegionTypeList | boolean);
1193
1342
  /**
1194
1343
  * Callback(targetNode, data)
1195
1344
  * @default null
package/src/util.ts CHANGED
@@ -36,6 +36,8 @@ const ENTITY_MAP: { [key: string]: string } = {
36
36
  "/": "&#x2F;",
37
37
  };
38
38
 
39
+ export type NotPromise<T> = T extends Promise<any> ? never : T;
40
+
39
41
  export type FunctionType = (...args: any[]) => any;
40
42
  export type EventCallbackType = (e: Event) => boolean | void;
41
43
  type PromiseCallbackType = (val: any) => void;
@@ -175,7 +177,7 @@ export function each(
175
177
  return obj;
176
178
  }
177
179
 
178
- /** Shortcut for `throw new Error(msg)`.*/
180
+ /** Shortcut for `throw new Error(msg)`. */
179
181
  export function error(msg: string) {
180
182
  throw new Error(msg);
181
183
  }
@@ -424,19 +426,6 @@ export function elemFromSelector<T = HTMLElement>(obj: string | T): T | null {
424
426
  return obj as T;
425
427
  }
426
428
 
427
- // /** Return a EventTarget from selector or cast an existing element. */
428
- // export function eventTargetFromSelector(
429
- // obj: string | EventTarget
430
- // ): EventTarget | null {
431
- // if (!obj) {
432
- // return null;
433
- // }
434
- // if (typeof obj === "string") {
435
- // return document.querySelector(obj) as EventTarget;
436
- // }
437
- // return obj as EventTarget;
438
- // }
439
-
440
429
  /**
441
430
  * Return a canonical descriptive string for a keyboard or mouse event.
442
431
  *
@@ -796,6 +785,11 @@ export function toPixel(
796
785
  throw new Error(`Expected a string like '123px': ${defaults}`);
797
786
  }
798
787
 
788
+ /** Cast any value to <T>. */
789
+ export function unsafeCast<T>(value: any): T {
790
+ return value as T;
791
+ }
792
+
799
793
  /** Return the the boolean value of the first non-null element.
800
794
  * Example:
801
795
  * ```js
@@ -887,7 +881,7 @@ export function adaptiveThrottle(
887
881
  const throttledFn = (...args: any[]) => {
888
882
  if (waiting) {
889
883
  pendingArgs = args;
890
- // console.log(`adaptiveThrottle() queing request #${waiting}...`, args);
884
+ // console.log(`adaptiveThrottle() queueing request #${waiting}...`, args);
891
885
  waiting += 1;
892
886
  } else {
893
887
  // Prevent invocations while running or blocking
@@ -945,3 +939,69 @@ export function adaptiveThrottle(
945
939
  };
946
940
  return throttledFn;
947
941
  }
942
+
943
+ /**
944
+ * MurmurHash3 implementation for strings.
945
+ * @param key The input string to hash.
946
+ * @param asString Optional convert result to zero-padded string of 8 characters.
947
+ * @param seed Optional seed value.
948
+ * @returns A 32-bit hash as a number or string.
949
+ */
950
+ export function murmurHash3(
951
+ key: string,
952
+ asString = true,
953
+ seed: number = 0
954
+ ): number | string {
955
+ let h1 = seed;
956
+ const remainder = key.length & 3; // key.length % 4
957
+ const bytes = key.length - remainder;
958
+ const c1 = 0xcc9e2d51;
959
+ const c2 = 0x1b873593;
960
+
961
+ let i = 0;
962
+ while (i < bytes) {
963
+ let k1 =
964
+ (key.charCodeAt(i) & 0xff) |
965
+ ((key.charCodeAt(++i) & 0xff) << 8) |
966
+ ((key.charCodeAt(++i) & 0xff) << 16) |
967
+ ((key.charCodeAt(++i) & 0xff) << 24);
968
+ ++i;
969
+
970
+ k1 = Math.imul(k1, c1);
971
+ k1 = (k1 << 15) | (k1 >>> 17);
972
+ k1 = Math.imul(k1, c2);
973
+
974
+ h1 ^= k1;
975
+ h1 = (h1 << 13) | (h1 >>> 19);
976
+ h1 = Math.imul(h1, 5) + 0xe6546b64;
977
+ }
978
+
979
+ let k1 = 0;
980
+ switch (remainder) {
981
+ case 3:
982
+ k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
983
+ // fall through
984
+ case 2:
985
+ k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
986
+ // fall through
987
+ case 1:
988
+ k1 ^= key.charCodeAt(i) & 0xff;
989
+ k1 = Math.imul(k1, c1);
990
+ k1 = (k1 << 15) | (k1 >>> 17);
991
+ k1 = Math.imul(k1, c2);
992
+ h1 ^= k1;
993
+ }
994
+
995
+ h1 ^= key.length;
996
+ h1 ^= h1 >>> 16;
997
+ h1 = Math.imul(h1, 0x85ebca6b);
998
+ h1 ^= h1 >>> 13;
999
+ h1 = Math.imul(h1, 0xc2b2ae35);
1000
+ h1 ^= h1 >>> 16;
1001
+
1002
+ if (asString) {
1003
+ // Convert to 8 digit hex string
1004
+ return (h1 >>> 0).toString(16).padStart(8, "0");
1005
+ }
1006
+ return h1 >>> 0; // Convert to unsigned 32-bit integer
1007
+ }