@vuu-ui/vuu-utils 0.5.14 → 0.5.15

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.
Files changed (40) hide show
  1. package/package.json +12 -13
  2. package/src/DataWindow.ts +111 -0
  3. package/src/array-utils.ts +37 -0
  4. package/src/column-utils.ts +224 -0
  5. package/src/data-utils.ts +60 -0
  6. package/src/date-utils.ts +4 -0
  7. package/src/event-emitter.ts +139 -0
  8. package/src/getUniqueId.ts +1 -0
  9. package/{types/packages/vuu-utils/src/index.d.ts → src/index.ts} +0 -0
  10. package/src/input-utils.ts +50 -0
  11. package/src/invariant.ts +10 -0
  12. package/src/nanoid/index.ts +30 -0
  13. package/src/range-utils.ts +72 -0
  14. package/src/round-decimal.ts +126 -0
  15. package/src/row-utils.ts +44 -0
  16. package/src/sort-utils.ts +71 -0
  17. package/src/text-utils.ts +9 -0
  18. package/test/DataWindow.test.ts +108 -0
  19. package/tsconfig-emit-types.json +10 -0
  20. package/LICENSE +0 -201
  21. package/cjs/index.js +0 -2
  22. package/cjs/index.js.map +0 -7
  23. package/esm/index.js +0 -2
  24. package/esm/index.js.map +0 -7
  25. package/types/packages/vuu-utils/src/DataWindow.d.ts +0 -39
  26. package/types/packages/vuu-utils/src/array-utils.d.ts +0 -3
  27. package/types/packages/vuu-utils/src/column-utils.d.ts +0 -32
  28. package/types/packages/vuu-utils/src/data-utils.d.ts +0 -9
  29. package/types/packages/vuu-utils/src/date-utils.d.ts +0 -1
  30. package/types/packages/vuu-utils/src/event-emitter.d.ts +0 -19
  31. package/types/packages/vuu-utils/src/getUniqueId.d.ts +0 -1
  32. package/types/packages/vuu-utils/src/input-utils.d.ts +0 -2
  33. package/types/packages/vuu-utils/src/invariant.d.ts +0 -1
  34. package/types/packages/vuu-utils/src/nanoid/index.d.ts +0 -1
  35. package/types/packages/vuu-utils/src/range-utils.d.ts +0 -21
  36. package/types/packages/vuu-utils/src/round-decimal.d.ts +0 -1
  37. package/types/packages/vuu-utils/src/row-utils.d.ts +0 -10
  38. package/types/packages/vuu-utils/src/sort-utils.d.ts +0 -5
  39. package/types/packages/vuu-utils/src/text-utils.d.ts +0 -1
  40. package/types/showcase/src/examples/DataGrid/columnMetaData.d.ts +0 -233
package/package.json CHANGED
@@ -1,22 +1,21 @@
1
1
  {
2
2
  "name": "@vuu-ui/vuu-utils",
3
- "version": "0.5.14",
3
+ "version": "0.5.15",
4
4
  "author": "heswell",
5
+ "main": "src/index.ts",
5
6
  "license": "Apache-2.0",
7
+ "types": "src/index.ts",
8
+ "scripts": {
9
+ "build": "node ../../scripts/run-build.mjs",
10
+ "test": "vitest run",
11
+ "type-defs": "node ../../scripts/build-type-defs.mjs"
12
+ },
6
13
  "devDependencies": {
7
- "@vuu-ui/vuu-datagrid-types": "0.5.14",
8
- "@vuu-ui/vuu-protocol-types": "0.5.14"
14
+ "@vuu-ui/vuu-datagrid-types": "0.5.15",
15
+ "@vuu-ui/vuu-protocol-types": "0.5.15"
9
16
  },
10
17
  "peerDependencies": {
11
18
  "react": "^17.0.2",
12
19
  "react-dom": "^17.0.2"
13
- },
14
- "files": [
15
- "cjs",
16
- "esm",
17
- "/types"
18
- ],
19
- "module": "esm/index.js",
20
- "main": "cjs/index.js",
21
- "types": "types/index.d.ts"
22
- }
20
+ }
21
+ }
@@ -0,0 +1,111 @@
1
+ import { WindowRange } from "./range-utils";
2
+ import { metadataKeys } from "./column-utils";
3
+
4
+ export type DataItem = string | number | boolean;
5
+ export type DataRow = [
6
+ /** index */
7
+ number,
8
+ /** render index */
9
+ number,
10
+ /** isLeaf */
11
+ boolean,
12
+ /** isExpanded */
13
+ boolean,
14
+ /** depth */
15
+ number,
16
+ /** child count */
17
+ number,
18
+ /** key */
19
+ string,
20
+ /** selected */
21
+ number,
22
+ /** data values */
23
+ ...DataItem[]
24
+ ];
25
+ export type RangeLike = { from: number; to: number };
26
+
27
+ const { KEY } = metadataKeys;
28
+
29
+ const log = (message: string) =>
30
+ console.log(`%c[DataWindow] ${message}`, "color: purple;font-weight: bold;");
31
+ export class DataWindow {
32
+ private range: WindowRange;
33
+ public data: DataRow[];
34
+ public rowCount = 0;
35
+ constructor({ from, to }: RangeLike) {
36
+ log(`constructor ${from} - ${to}`);
37
+ this.range = new WindowRange(from, to);
38
+ //internal data is always 0 based, we add range.from to determine an offset
39
+ this.data = new Array(to - from);
40
+ // window.dataWindow = this.data;
41
+ // log(`constructor initial range ${from} - ${to}`);
42
+ }
43
+
44
+ setRowCount = (rowCount: number) => {
45
+ // log(`rowCount => ${rowCount}`);
46
+ if (rowCount < this.data.length) {
47
+ this.data.length = rowCount;
48
+ }
49
+ this.rowCount = rowCount;
50
+ };
51
+
52
+ // return true if existing row was updated
53
+ add(data: DataRow) {
54
+ const [index] = data;
55
+ if (this.isWithinRange(index)) {
56
+ const internalIndex = index - this.range.from;
57
+ const isUpdate = this.data[internalIndex] !== undefined;
58
+ this.data[internalIndex] = data;
59
+ return isUpdate;
60
+ } else {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ getAtIndex(index: number) {
66
+ return this.range.isWithin(index) &&
67
+ this.data[index - this.range.from] != null
68
+ ? this.data[index - this.range.from]
69
+ : undefined;
70
+ }
71
+
72
+ getByKey(key: string) {
73
+ return this.data.find((row) => row[KEY] === key);
74
+ }
75
+
76
+ isWithinRange(index: number) {
77
+ return this.range.isWithin(index) && index <= this.rowCount;
78
+ }
79
+
80
+ setRange(from: number, to: number) {
81
+ log(`setRange ${from} ${to}`);
82
+ if (from !== this.range.from || to !== this.range.to) {
83
+ const [overlapFrom, overlapTo] = this.range.overlap(from, to);
84
+ const newData = new Array(to - from);
85
+ for (let i = overlapFrom; i < overlapTo; i++) {
86
+ const data = this.getAtIndex(i);
87
+ if (data) {
88
+ const index = i - from;
89
+ newData[index] = data;
90
+ }
91
+ }
92
+ this.data = newData;
93
+ this.range.from = from;
94
+ this.range.to = to;
95
+ }
96
+ }
97
+
98
+ hasData(from: number, to: number) {
99
+ const offset = this.range.from;
100
+ const start = from - offset;
101
+ const end = Math.min(to - offset - 1, this.rowCount - 1);
102
+ return this.data[start] !== undefined && this.data[end] !== undefined;
103
+ }
104
+
105
+ getData(from: number, to: number): any[] {
106
+ const { from: clientFrom, to: clientTo } = this.range;
107
+ const startOffset = Math.max(0, from - clientFrom);
108
+ const endOffset = Math.min(to - clientFrom, this.rowCount ?? to);
109
+ return this.data.slice(startOffset, endOffset);
110
+ }
111
+ }
@@ -0,0 +1,37 @@
1
+ export type PartitionTest<T> = (value: T, index: number) => boolean;
2
+
3
+ export function partition<T>(
4
+ array: T[],
5
+ test: PartitionTest<T>,
6
+ pass: T[] = [],
7
+ fail: T[] = []
8
+ ): [T[], T[]] {
9
+ for (let i = 0, len = array.length; i < len; i++) {
10
+ (test(array[i], i) ? pass : fail).push(array[i]);
11
+ }
12
+ return [pass, fail];
13
+ }
14
+
15
+ // Note order of items can be different between arrays
16
+ // If an identityProperty is not defined, item identity is used
17
+ export function itemsChanged<T = unknown>(
18
+ currentItems: T[],
19
+ newItems: T[],
20
+ identityProperty?: string
21
+ ) {
22
+ if (currentItems.length !== newItems.length) {
23
+ return true;
24
+ }
25
+ if (identityProperty === undefined) {
26
+ return !currentItems.every((item) => newItems.includes(item));
27
+ } else {
28
+ return currentItems.some(
29
+ (currentItem) =>
30
+ newItems.findIndex(
31
+ (newItem) =>
32
+ (newItem as { [key: string]: unknown })[identityProperty] ===
33
+ (currentItem as { [key: string]: unknown })[identityProperty]
34
+ ) === -1
35
+ );
36
+ }
37
+ }
@@ -0,0 +1,224 @@
1
+ import {
2
+ ColumnDescriptor,
3
+ GroupColumnDescriptor,
4
+ KeyedColumnDescriptor,
5
+ } from "@vuu-ui/vuu-datagrid-types";
6
+ import { VuuGroupBy } from "@vuu-ui/vuu-protocol-types";
7
+ import { instrumentPriceColumns } from "../../../showcase/src/examples/DataGrid/columnMetaData";
8
+ import { Row } from "./row-utils";
9
+
10
+ export interface ColumnMap {
11
+ [columnName: string]: number;
12
+ }
13
+
14
+ const SORT_ASC = "asc";
15
+
16
+ export type SortCriteriaItem = string | [string, "asc"]; // TODO where is 'desc'?
17
+
18
+ export function mapSortCriteria(
19
+ sortCriteria: SortCriteriaItem[],
20
+ columnMap: ColumnMap,
21
+ metadataOffset = 0
22
+ ): [number, "asc"][] {
23
+ return sortCriteria.map((s) => {
24
+ if (typeof s === "string") {
25
+ return [columnMap[s] + metadataOffset, "asc"];
26
+ } else if (Array.isArray(s)) {
27
+ const [columnName, sortDir] = s;
28
+ return [columnMap[columnName] + metadataOffset, sortDir || SORT_ASC];
29
+ } else {
30
+ throw Error("columnUtils.mapSortCriteria invalid input");
31
+ }
32
+ });
33
+ }
34
+
35
+ export const isKeyedColumn = (
36
+ column: ColumnDescriptor
37
+ ): column is KeyedColumnDescriptor => {
38
+ return typeof (column as KeyedColumnDescriptor).key === "number";
39
+ };
40
+
41
+ export const isNumericColumn = ({ serverDataType }: ColumnDescriptor) =>
42
+ serverDataType === undefined
43
+ ? false
44
+ : serverDataType === "int" ||
45
+ serverDataType === "long" ||
46
+ serverDataType === "double";
47
+
48
+ export const toColumnDescriptor = (name: string): ColumnDescriptor => ({
49
+ name,
50
+ });
51
+
52
+ const EMPTY_COLUMN_MAP = {} as const;
53
+
54
+ export function buildColumnMap(
55
+ columns?: (KeyedColumnDescriptor | string)[]
56
+ ): ColumnMap {
57
+ const start = metadataKeys.count;
58
+ if (columns) {
59
+ return columns.reduce((map, column, i) => {
60
+ if (typeof column === "string") {
61
+ map[column] = start + i;
62
+ } else if (typeof column.key === "number") {
63
+ map[column.name] = column.key;
64
+ } else {
65
+ map[column.name] = start + i;
66
+ }
67
+ return map;
68
+ }, {} as ColumnMap);
69
+ } else {
70
+ return EMPTY_COLUMN_MAP;
71
+ }
72
+ }
73
+
74
+ export function projectUpdates(updates: number[]): number[] {
75
+ const results: number[] = [];
76
+ const metadataOffset = metadataKeys.count - 2;
77
+ for (let i = 0; i < updates.length; i += 3) {
78
+ results[i] = updates[i] + metadataOffset;
79
+ results[i + 1] = updates[i + 1];
80
+ results[i + 2] = updates[i + 2];
81
+ }
82
+ return results;
83
+ }
84
+
85
+ export function projectColumns(
86
+ tableRowColumnMap: ColumnMap,
87
+ columns: ColumnDescriptor[]
88
+ ) {
89
+ const columnCount = columns.length;
90
+ const { IDX, RENDER_IDX, DEPTH, COUNT, KEY, SELECTED, count } = metadataKeys;
91
+ return (startIdx: number, offset: number, selectedRows: Row[] = []) =>
92
+ (row: Row, i: number) => {
93
+ // selectedRows are indices of rows within underlying dataset (not sorted or filtered)
94
+ // row is the original row from this set, with original index in IDX pos, which might
95
+ // be overwritten with a different value below if rows are sorted/filtered
96
+ const baseRowIdx: any = row[IDX]; // TODO
97
+ const out = [];
98
+ for (let i = 0; i < columnCount; i++) {
99
+ const colIdx = tableRowColumnMap[columns[i].name];
100
+ out[count + i] = row[colIdx];
101
+ }
102
+
103
+ out[IDX] = startIdx + i + offset;
104
+ out[RENDER_IDX] = 0;
105
+ out[DEPTH] = 0;
106
+ out[COUNT] = 0;
107
+ out[KEY] = row[tableRowColumnMap.KEY];
108
+ out[SELECTED] = selectedRows.includes(baseRowIdx) ? 1 : 0;
109
+ return out;
110
+ };
111
+ }
112
+
113
+ export const metadataKeys = {
114
+ IDX: 0,
115
+ RENDER_IDX: 1,
116
+ IS_LEAF: 2,
117
+ IS_EXPANDED: 3,
118
+ DEPTH: 4,
119
+ COUNT: 5,
120
+ KEY: 6,
121
+ SELECTED: 7,
122
+ count: 8,
123
+ // TODO following only used in datamodel
124
+ PARENT_IDX: "parent_idx",
125
+ IDX_POINTER: "idx_pointer",
126
+ FILTER_COUNT: "filter_count",
127
+ NEXT_FILTER_IDX: "next_filter_idx",
128
+ } as const;
129
+
130
+ // This method mutates the passed columns array
131
+ const insertColumn = (
132
+ columns: KeyedColumnDescriptor[],
133
+ column: KeyedColumnDescriptor
134
+ ) => {
135
+ const { originalIdx } = column;
136
+ if (typeof originalIdx === "number") {
137
+ for (let i = 0; i < columns.length; i++) {
138
+ const { originalIdx: colIdx = -1 } = columns[i];
139
+ if (colIdx > originalIdx) {
140
+ columns.splice(i, 0, column);
141
+ return columns;
142
+ }
143
+ }
144
+ }
145
+ columns.push(column);
146
+ return columns;
147
+ };
148
+
149
+ export const flattenColumnGroup = (
150
+ columns: KeyedColumnDescriptor[]
151
+ ): KeyedColumnDescriptor[] => {
152
+ if (columns[0]?.isGroup) {
153
+ const [groupColumn, ...nonGroupedColumns] = columns as [
154
+ GroupColumnDescriptor,
155
+ ...KeyedColumnDescriptor[]
156
+ ];
157
+ groupColumn.columns.forEach((groupColumn) => {
158
+ insertColumn(nonGroupedColumns, groupColumn);
159
+ });
160
+ return nonGroupedColumns;
161
+ } else {
162
+ return columns;
163
+ }
164
+ };
165
+
166
+ export function extractGroupColumn(
167
+ columns: KeyedColumnDescriptor[],
168
+ groupBy?: VuuGroupBy
169
+ ): [GroupColumnDescriptor | null, KeyedColumnDescriptor[]] {
170
+ if (groupBy && groupBy.length > 0) {
171
+ const flattenedColumns = flattenColumnGroup(columns);
172
+ // Note: groupedColumns will be in column order, not groupBy order
173
+ const [groupedColumns, rest] = flattenedColumns.reduce(
174
+ (result, column, i) => {
175
+ const [g, r] = result;
176
+ if (groupBy.includes(column.name)) {
177
+ g.push({
178
+ ...column,
179
+ originalIdx: i,
180
+ });
181
+ } else {
182
+ r.push(column);
183
+ }
184
+
185
+ return result;
186
+ },
187
+ [[], []] as [KeyedColumnDescriptor[], KeyedColumnDescriptor[]]
188
+ );
189
+ if (groupedColumns.length !== groupBy.length) {
190
+ throw Error(
191
+ `extractGroupColumn: no column definition found for all groupBy cols ${JSON.stringify(
192
+ groupBy
193
+ )} `
194
+ );
195
+ }
196
+ const groupCount = groupBy.length;
197
+ const groupCols: KeyedColumnDescriptor[] = groupBy.map((name, idx) => {
198
+ // Keep the cols in same order defined on groupBy
199
+ const column = groupedColumns.find(
200
+ (col) => col.name === name
201
+ ) as KeyedColumnDescriptor;
202
+ return {
203
+ ...column,
204
+ groupLevel: groupCount - idx,
205
+ };
206
+ });
207
+
208
+ const groupCol = {
209
+ key: -1,
210
+ name: "group-col",
211
+ heading: ["group-col"],
212
+ isGroup: true,
213
+ columns: groupCols,
214
+ width: groupCols.map((c) => c.width).reduce((a, b) => a + b) + 100,
215
+ } as GroupColumnDescriptor;
216
+
217
+ return [groupCol, rest];
218
+ }
219
+ return [null, flattenColumnGroup(columns)];
220
+ }
221
+
222
+ export const isGroupColumn = (
223
+ column: KeyedColumnDescriptor
224
+ ): column is GroupColumnDescriptor => column.isGroup === true;
@@ -0,0 +1,60 @@
1
+ export type valueChangeDirection = "up1" | "up2" | "down1" | "down2" | "";
2
+
3
+ export const UP1 = "up1";
4
+ export const UP2 = "up2";
5
+ export const DOWN1 = "down1";
6
+ export const DOWN2 = "down2";
7
+
8
+ export const isValidNumber = (n: unknown): n is number =>
9
+ typeof n === "number" && isFinite(n);
10
+
11
+ export function getMovingValueDirection(
12
+ newValue: number,
13
+ direction?: valueChangeDirection,
14
+ prevValue?: number,
15
+ /** the number of decimal places to take into account when highlighting a change */
16
+ decimalPlaces?: number
17
+ ): valueChangeDirection {
18
+ if (
19
+ !isFinite(newValue) ||
20
+ prevValue === undefined ||
21
+ direction === undefined
22
+ ) {
23
+ return "";
24
+ } else {
25
+ let diff = newValue - prevValue;
26
+ if (diff) {
27
+ // make sure there is still a diff when reduced to number of decimals to be displayed
28
+ if (typeof decimalPlaces === "number") {
29
+ diff =
30
+ +newValue.toFixed(decimalPlaces) - +prevValue.toFixed(decimalPlaces);
31
+ }
32
+ }
33
+
34
+ if (diff) {
35
+ if (direction === "") {
36
+ if (diff < 0) {
37
+ return DOWN1;
38
+ } else {
39
+ return UP1;
40
+ }
41
+ } else if (diff > 0) {
42
+ if (direction === DOWN1 || direction === DOWN2 || direction === UP2) {
43
+ return UP1;
44
+ } else {
45
+ return UP2;
46
+ }
47
+ } else if (
48
+ direction === UP1 ||
49
+ direction === UP2 ||
50
+ direction === DOWN2
51
+ ) {
52
+ return DOWN1;
53
+ } else {
54
+ return DOWN2;
55
+ }
56
+ } else {
57
+ return "";
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,4 @@
1
+ //TODO
2
+ export const formatDate = (date: Date, format: string) => {
3
+ return date.toUTCString();
4
+ };
@@ -0,0 +1,139 @@
1
+ export interface Event {}
2
+
3
+ export type EventListener = (evtName: string, ...args: any[]) => void;
4
+
5
+ export type EventListenerMap = {
6
+ [eventName: string]: EventListener[] | EventListener;
7
+ };
8
+
9
+ function isArrayOfListeners(
10
+ listeners: EventListener | EventListener[]
11
+ ): listeners is EventListener[] {
12
+ return Array.isArray(listeners);
13
+ }
14
+
15
+ function isOnlyListener(
16
+ listeners: EventListener | EventListener[]
17
+ ): listeners is EventListener {
18
+ return !Array.isArray(listeners);
19
+ }
20
+
21
+ export interface IEventEmitter {
22
+ emit: (type: string, ...args: unknown[]) => void;
23
+ }
24
+
25
+ export class EventEmitter implements IEventEmitter {
26
+ private _events?: EventListenerMap;
27
+
28
+ constructor() {
29
+ this._events = {};
30
+ }
31
+
32
+ addListener(type: string, listener: EventListener) {
33
+ if (!this._events) {
34
+ this._events = {};
35
+ }
36
+
37
+ const listeners = this._events[type];
38
+
39
+ if (!listeners) {
40
+ this._events[type] = listener;
41
+ } else if (isArrayOfListeners(listeners)) {
42
+ listeners.push(listener);
43
+ } else if (isOnlyListener(listeners)) {
44
+ this._events[type] = [listeners, listener];
45
+ }
46
+ }
47
+
48
+ removeListener(type: string, listener: EventListener) {
49
+ if (!this._events || !this._events[type]) {
50
+ return;
51
+ }
52
+
53
+ const listenerOrListeners = this._events[type];
54
+ let position = -1;
55
+
56
+ if (listenerOrListeners === listener) {
57
+ delete this._events[type];
58
+ } else if (Array.isArray(listenerOrListeners)) {
59
+ for (let i = length; i-- > 0; ) {
60
+ if (listenerOrListeners[i] === listener) {
61
+ position = i;
62
+ break;
63
+ }
64
+ }
65
+
66
+ if (position < 0) {
67
+ return;
68
+ }
69
+
70
+ if (listenerOrListeners.length === 1) {
71
+ listenerOrListeners.length = 0;
72
+ delete this._events[type];
73
+ } else {
74
+ listenerOrListeners.splice(position, 1);
75
+ }
76
+ }
77
+ }
78
+
79
+ removeAllListeners(type: string) {
80
+ if (!this._events) {
81
+ return;
82
+ } else if (type === undefined) {
83
+ delete this._events;
84
+ } else {
85
+ delete this._events[type];
86
+ }
87
+ }
88
+
89
+ emit(type: string, ...args: unknown[]) {
90
+ if (this._events) {
91
+ const handler = this._events[type];
92
+ if (handler) {
93
+ invokeHandler(handler, type, args);
94
+ }
95
+ const wildcardHandler = this._events["*"];
96
+ if (wildcardHandler) {
97
+ invokeHandler(wildcardHandler, type, args);
98
+ }
99
+ }
100
+ }
101
+
102
+ once(type: string, listener: EventListener) {
103
+ const handler = (evtName: string, message: unknown) => {
104
+ this.removeListener(evtName, handler);
105
+ listener(evtName, message);
106
+ };
107
+
108
+ this.on(type, handler);
109
+ }
110
+
111
+ on(type: string, listener: EventListener) {
112
+ return this.addListener(type, listener);
113
+ }
114
+ }
115
+
116
+ function invokeHandler(
117
+ handler: EventListener | EventListener[],
118
+ type: string,
119
+ args: unknown[]
120
+ ) {
121
+ if (isArrayOfListeners(handler)) {
122
+ handler.slice().forEach((listener) => invokeHandler(listener, type, args));
123
+ } else {
124
+ switch (args.length) {
125
+ case 0:
126
+ handler(type);
127
+ break;
128
+ case 1:
129
+ handler(type, args[0]);
130
+ break;
131
+ case 2:
132
+ handler(type, args[0], args[1]);
133
+ break;
134
+ // slower
135
+ default:
136
+ handler.call(null, type, ...args);
137
+ }
138
+ }
139
+ }
@@ -0,0 +1 @@
1
+ export const getUniqueId = () => `hw-${Math.round(Math.random() * 1e5)}`;
@@ -0,0 +1,50 @@
1
+ const actionKeys = {
2
+ Enter: 'Enter',
3
+ Delete: 'Delete'
4
+ };
5
+
6
+ const navigationKeys = {
7
+ Home: 'Home',
8
+ End: 'End',
9
+ ArrowRight: 'ArrowRight',
10
+ ArrowLeft: 'ArrowLeft',
11
+ ArrowDown: 'ArrowDown',
12
+ ArrowUp: 'ArrowUp',
13
+ Tab: 'Tab'
14
+ };
15
+ const functionKeys = {
16
+ F1: 'F1',
17
+ F2: 'F2',
18
+ F3: 'F3',
19
+ F4: 'F4',
20
+ F5: 'F5',
21
+ F6: 'F6',
22
+ F7: 'F7',
23
+ F8: 'F8',
24
+ F9: 'F9',
25
+ F10: 'F10',
26
+ F11: 'F11',
27
+ F12: 'F12'
28
+ };
29
+
30
+ const specialKeys = {
31
+ ...actionKeys,
32
+ ...navigationKeys,
33
+ ...functionKeys
34
+ };
35
+ type specialKey = keyof typeof specialKeys;
36
+
37
+ const isSpecialKey = (key: string): key is specialKey => key in specialKeys;
38
+
39
+ export const isCharacterKey = (evt: KeyboardEvent) => {
40
+ if (isSpecialKey(evt.key)) {
41
+ return false;
42
+ }
43
+ if (typeof evt.which === 'number' && evt.which > 0) {
44
+ return !evt.ctrlKey && !evt.metaKey && !evt.altKey && evt.which !== 8;
45
+ }
46
+ };
47
+
48
+ export const isQuoteKey = (evt: KeyboardEvent) => {
49
+ return evt.key === '"' || evt.key === "'";
50
+ };
@@ -0,0 +1,10 @@
1
+ export function invariant(condition: boolean, message: string) {
2
+ if (!condition) {
3
+ const error = new Error(message);
4
+ error.name = 'Invariant Violation';
5
+ // TODO what is framesToPop?
6
+ // @ts-ignore
7
+ error.framesToPop = 1; // we don't care about invariant's own frame
8
+ throw error;
9
+ }
10
+ }