@fluentui-react-native/framework-base 0.1.1

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 (141) hide show
  1. package/CHANGELOG.json +20 -0
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +20 -0
  4. package/babel.config.js +1 -0
  5. package/eslint.config.js +3 -0
  6. package/lib/immutable-merge/Merge.d.ts +78 -0
  7. package/lib/immutable-merge/Merge.d.ts.map +1 -0
  8. package/lib/immutable-merge/Merge.js +179 -0
  9. package/lib/immutable-merge/Merge.js.map +1 -0
  10. package/lib/immutable-merge/Merge.test.d.ts +2 -0
  11. package/lib/immutable-merge/Merge.test.d.ts.map +1 -0
  12. package/lib/immutable-merge/Merge.test.js +262 -0
  13. package/lib/immutable-merge/Merge.test.js.map +1 -0
  14. package/lib/index.d.ts +9 -0
  15. package/lib/index.d.ts.map +1 -0
  16. package/lib/index.js +7 -0
  17. package/lib/index.js.map +1 -0
  18. package/lib/memo-cache/getCacheEntry.d.ts +22 -0
  19. package/lib/memo-cache/getCacheEntry.d.ts.map +1 -0
  20. package/lib/memo-cache/getCacheEntry.js +49 -0
  21. package/lib/memo-cache/getCacheEntry.js.map +1 -0
  22. package/lib/memo-cache/getCacheEntry.test.d.ts +2 -0
  23. package/lib/memo-cache/getCacheEntry.test.d.ts.map +1 -0
  24. package/lib/memo-cache/getCacheEntry.test.js +95 -0
  25. package/lib/memo-cache/getCacheEntry.test.js.map +1 -0
  26. package/lib/memo-cache/getMemoCache.d.ts +11 -0
  27. package/lib/memo-cache/getMemoCache.d.ts.map +1 -0
  28. package/lib/memo-cache/getMemoCache.js +29 -0
  29. package/lib/memo-cache/getMemoCache.js.map +1 -0
  30. package/lib/memo-cache/getMemoCache.test.d.ts +2 -0
  31. package/lib/memo-cache/getMemoCache.test.d.ts.map +1 -0
  32. package/lib/memo-cache/getMemoCache.test.js +78 -0
  33. package/lib/memo-cache/getMemoCache.test.js.map +1 -0
  34. package/lib/memo-cache/index.d.ts +4 -0
  35. package/lib/memo-cache/index.d.ts.map +1 -0
  36. package/lib/memo-cache/index.js +3 -0
  37. package/lib/memo-cache/index.js.map +1 -0
  38. package/lib/memo-cache/memoize.d.ts +6 -0
  39. package/lib/memo-cache/memoize.d.ts.map +1 -0
  40. package/lib/memo-cache/memoize.js +17 -0
  41. package/lib/memo-cache/memoize.js.map +1 -0
  42. package/lib/memo-cache/memoize.test.d.ts +2 -0
  43. package/lib/memo-cache/memoize.test.d.ts.map +1 -0
  44. package/lib/memo-cache/memoize.test.js +45 -0
  45. package/lib/memo-cache/memoize.test.js.map +1 -0
  46. package/lib/merge-props/index.d.ts +4 -0
  47. package/lib/merge-props/index.d.ts.map +1 -0
  48. package/lib/merge-props/index.js +3 -0
  49. package/lib/merge-props/index.js.map +1 -0
  50. package/lib/merge-props/mergeProps.d.ts +6 -0
  51. package/lib/merge-props/mergeProps.d.ts.map +1 -0
  52. package/lib/merge-props/mergeProps.js +17 -0
  53. package/lib/merge-props/mergeProps.js.map +1 -0
  54. package/lib/merge-props/mergeStyles.d.ts +16 -0
  55. package/lib/merge-props/mergeStyles.d.ts.map +1 -0
  56. package/lib/merge-props/mergeStyles.js +32 -0
  57. package/lib/merge-props/mergeStyles.js.map +1 -0
  58. package/lib/merge-props/mergeStyles.test.d.ts +2 -0
  59. package/lib/merge-props/mergeStyles.test.d.ts.map +1 -0
  60. package/lib/merge-props/mergeStyles.test.js +94 -0
  61. package/lib/merge-props/mergeStyles.test.js.map +1 -0
  62. package/lib/merge-props/mergeStyles.types.d.ts +12 -0
  63. package/lib/merge-props/mergeStyles.types.d.ts.map +1 -0
  64. package/lib/merge-props/mergeStyles.types.js +2 -0
  65. package/lib/merge-props/mergeStyles.types.js.map +1 -0
  66. package/lib-commonjs/immutable-merge/Merge.d.ts +78 -0
  67. package/lib-commonjs/immutable-merge/Merge.d.ts.map +1 -0
  68. package/lib-commonjs/immutable-merge/Merge.js +186 -0
  69. package/lib-commonjs/immutable-merge/Merge.js.map +1 -0
  70. package/lib-commonjs/immutable-merge/Merge.test.d.ts +2 -0
  71. package/lib-commonjs/immutable-merge/Merge.test.d.ts.map +1 -0
  72. package/lib-commonjs/immutable-merge/Merge.test.js +264 -0
  73. package/lib-commonjs/immutable-merge/Merge.test.js.map +1 -0
  74. package/lib-commonjs/index.d.ts +9 -0
  75. package/lib-commonjs/index.d.ts.map +1 -0
  76. package/lib-commonjs/index.js +18 -0
  77. package/lib-commonjs/index.js.map +1 -0
  78. package/lib-commonjs/memo-cache/getCacheEntry.d.ts +22 -0
  79. package/lib-commonjs/memo-cache/getCacheEntry.d.ts.map +1 -0
  80. package/lib-commonjs/memo-cache/getCacheEntry.js +53 -0
  81. package/lib-commonjs/memo-cache/getCacheEntry.js.map +1 -0
  82. package/lib-commonjs/memo-cache/getCacheEntry.test.d.ts +2 -0
  83. package/lib-commonjs/memo-cache/getCacheEntry.test.d.ts.map +1 -0
  84. package/lib-commonjs/memo-cache/getCacheEntry.test.js +97 -0
  85. package/lib-commonjs/memo-cache/getCacheEntry.test.js.map +1 -0
  86. package/lib-commonjs/memo-cache/getMemoCache.d.ts +11 -0
  87. package/lib-commonjs/memo-cache/getMemoCache.d.ts.map +1 -0
  88. package/lib-commonjs/memo-cache/getMemoCache.js +33 -0
  89. package/lib-commonjs/memo-cache/getMemoCache.js.map +1 -0
  90. package/lib-commonjs/memo-cache/getMemoCache.test.d.ts +2 -0
  91. package/lib-commonjs/memo-cache/getMemoCache.test.d.ts.map +1 -0
  92. package/lib-commonjs/memo-cache/getMemoCache.test.js +80 -0
  93. package/lib-commonjs/memo-cache/getMemoCache.test.js.map +1 -0
  94. package/lib-commonjs/memo-cache/index.d.ts +4 -0
  95. package/lib-commonjs/memo-cache/index.d.ts.map +1 -0
  96. package/lib-commonjs/memo-cache/index.js +8 -0
  97. package/lib-commonjs/memo-cache/index.js.map +1 -0
  98. package/lib-commonjs/memo-cache/memoize.d.ts +6 -0
  99. package/lib-commonjs/memo-cache/memoize.d.ts.map +1 -0
  100. package/lib-commonjs/memo-cache/memoize.js +21 -0
  101. package/lib-commonjs/memo-cache/memoize.js.map +1 -0
  102. package/lib-commonjs/memo-cache/memoize.test.d.ts +2 -0
  103. package/lib-commonjs/memo-cache/memoize.test.d.ts.map +1 -0
  104. package/lib-commonjs/memo-cache/memoize.test.js +47 -0
  105. package/lib-commonjs/memo-cache/memoize.test.js.map +1 -0
  106. package/lib-commonjs/merge-props/index.d.ts +4 -0
  107. package/lib-commonjs/merge-props/index.d.ts.map +1 -0
  108. package/lib-commonjs/merge-props/index.js +8 -0
  109. package/lib-commonjs/merge-props/index.js.map +1 -0
  110. package/lib-commonjs/merge-props/mergeProps.d.ts +6 -0
  111. package/lib-commonjs/merge-props/mergeProps.d.ts.map +1 -0
  112. package/lib-commonjs/merge-props/mergeProps.js +21 -0
  113. package/lib-commonjs/merge-props/mergeProps.js.map +1 -0
  114. package/lib-commonjs/merge-props/mergeStyles.d.ts +16 -0
  115. package/lib-commonjs/merge-props/mergeStyles.d.ts.map +1 -0
  116. package/lib-commonjs/merge-props/mergeStyles.js +38 -0
  117. package/lib-commonjs/merge-props/mergeStyles.js.map +1 -0
  118. package/lib-commonjs/merge-props/mergeStyles.test.d.ts +2 -0
  119. package/lib-commonjs/merge-props/mergeStyles.test.d.ts.map +1 -0
  120. package/lib-commonjs/merge-props/mergeStyles.test.js +96 -0
  121. package/lib-commonjs/merge-props/mergeStyles.test.js.map +1 -0
  122. package/lib-commonjs/merge-props/mergeStyles.types.d.ts +12 -0
  123. package/lib-commonjs/merge-props/mergeStyles.types.d.ts.map +1 -0
  124. package/lib-commonjs/merge-props/mergeStyles.types.js +3 -0
  125. package/lib-commonjs/merge-props/mergeStyles.types.js.map +1 -0
  126. package/package.json +58 -0
  127. package/src/immutable-merge/Merge.test.ts +342 -0
  128. package/src/immutable-merge/Merge.ts +255 -0
  129. package/src/index.ts +20 -0
  130. package/src/memo-cache/getCacheEntry.test.ts +120 -0
  131. package/src/memo-cache/getCacheEntry.ts +67 -0
  132. package/src/memo-cache/getMemoCache.test.ts +91 -0
  133. package/src/memo-cache/getMemoCache.ts +37 -0
  134. package/src/memo-cache/index.ts +3 -0
  135. package/src/memo-cache/memoize.test.ts +48 -0
  136. package/src/memo-cache/memoize.ts +17 -0
  137. package/src/merge-props/index.ts +3 -0
  138. package/src/merge-props/mergeProps.ts +20 -0
  139. package/src/merge-props/mergeStyles.test.ts +124 -0
  140. package/src/merge-props/mergeStyles.ts +40 -0
  141. package/src/merge-props/mergeStyles.types.ts +9 -0
@@ -0,0 +1,120 @@
1
+ import type { CacheEntry } from './getCacheEntry';
2
+ import { getCacheEntry } from './getCacheEntry';
3
+
4
+ interface TestObj {
5
+ id: number;
6
+ }
7
+
8
+ type TestEntry = CacheEntry<TestObj>;
9
+
10
+ function compareResults(base: TestEntry, args: any[], argsNoMatch?: any[]): void {
11
+ const e1 = getCacheEntry(base, args);
12
+ expect(getCacheEntry(base, args)).toBe(e1);
13
+
14
+ if (argsNoMatch) {
15
+ expect(getCacheEntry(base, argsNoMatch)).not.toBe(e1);
16
+ }
17
+ }
18
+
19
+ describe('Memo cache unit tests', () => {
20
+ test('undefined args to return noargs', () => {
21
+ const base: TestEntry = {};
22
+ expect(getCacheEntry(base, undefined)).toBe(base.noargs);
23
+ });
24
+
25
+ test('empty args array to return noargs entry', () => {
26
+ const base: TestEntry = {};
27
+ expect(getCacheEntry(base, [])).toBe(base.noargs);
28
+ });
29
+
30
+ test('undefined goes to empty entry', () => {
31
+ const base: TestEntry = {};
32
+ expect(getCacheEntry(base, [undefined])).toBe(base.empty);
33
+ });
34
+
35
+ test('null to go to empty entry', () => {
36
+ const base: TestEntry = {};
37
+ expect(getCacheEntry(base, [null])).toBe(base.empty);
38
+ });
39
+
40
+ test('string gets keyed correctly', () => {
41
+ const base: TestEntry = {};
42
+ const key = 'foo';
43
+ expect(getCacheEntry(base, [key])).toBe(base.str[key]);
44
+ });
45
+
46
+ test('number gets keyed correctly', () => {
47
+ const base: TestEntry = {};
48
+ const val = 235;
49
+ const key = val + '';
50
+ expect(getCacheEntry(base, [val])).toBe(base.str[key]);
51
+ });
52
+
53
+ test('bool gets keyed correctly', () => {
54
+ const base: TestEntry = {};
55
+ const val = true;
56
+ const key = val + '';
57
+ expect(getCacheEntry(base, [val])).toBe(base.str[key]);
58
+ });
59
+
60
+ test('false bool gets keyed correctly', () => {
61
+ const base: TestEntry = {};
62
+ const val = false;
63
+ const key = val + '';
64
+ expect(getCacheEntry(base, [val])).toBe(base.str[key]);
65
+ });
66
+
67
+ test('object gets keyed correctly', () => {
68
+ const base: TestEntry = {};
69
+ const key = {};
70
+ expect(getCacheEntry(base, [key])).toBe(base.obj.get(key));
71
+ });
72
+
73
+ test('function gets keyed correctly', () => {
74
+ const base: TestEntry = {};
75
+ const key = () => {
76
+ return 'hello world';
77
+ };
78
+ expect(getCacheEntry(base, [key])).toBe(base.obj.get(key));
79
+ });
80
+
81
+ test('basic string retrieval', () => {
82
+ compareResults({}, ['hello', 'world'], ['hello world']);
83
+ });
84
+
85
+ test('mixed keys with strings', () => {
86
+ compareResults({}, ['hello', 1, true, undefined, 'world'], ['hello', 1, true, '', 'world']);
87
+ });
88
+
89
+ test('basic object matches', () => {
90
+ const obj = {};
91
+ compareResults({}, [obj]);
92
+ });
93
+
94
+ test('mixed object and string matches', () => {
95
+ const obj = {};
96
+ compareResults({}, [obj, 'hello', 1, 'world']);
97
+ });
98
+
99
+ test('mixed obj and strings with obj at end', () => {
100
+ const obj = {};
101
+ const obj2 = {};
102
+ compareResults({}, ['hello', 1, 'world', obj], ['hello', 1, 'world', obj2]);
103
+ });
104
+
105
+ test('hybrid sets', () => {
106
+ const obj = {};
107
+ const obj2 = {};
108
+ compareResults({}, ['hello', obj, 'world', 1, false, obj2, undefined]);
109
+ });
110
+
111
+ test('sub cache on object', () => {
112
+ const obj = {};
113
+ const args1 = ['hello', obj];
114
+ const base = {};
115
+ const subRoot = getCacheEntry(base, args1);
116
+ const args2 = ['world', base];
117
+ const target = getCacheEntry(subRoot, args2);
118
+ expect(getCacheEntry(base, [...args1, ...args2])).toBe(target);
119
+ });
120
+ });
@@ -0,0 +1,67 @@
1
+ export type CacheEntry<T, TGet = any> = {
2
+ /** stores the cached value if any */
3
+ value?: T;
4
+
5
+ /** entry used for undefined and null values, these both collapse to the same type */
6
+ empty?: CacheEntry<TGet>;
7
+
8
+ /** entry used for the case where the array of args is null or length 0 */
9
+ noargs?: CacheEntry<TGet>;
10
+
11
+ /** all remaining non-object types are keyed as strings for lookups */
12
+ str?: { [key: string]: CacheEntry<TGet> };
13
+
14
+ /** object types are keyed in a weak map on object identity */
15
+ obj?: WeakMap<object, TGet>;
16
+ };
17
+
18
+ /**
19
+ * just wraps the common entry.foo = entry.foo || {} pattern
20
+ * @param entry - entry to ensure a key value for
21
+ * @param key - which key of that entry to ensure the value for
22
+ */
23
+ function ensureAndReturn(entry: CacheEntry<any>, key: keyof CacheEntry<any>): CacheEntry<any> | { [key: string]: CacheEntry<any> } {
24
+ if ((key as string) === '__proto__' || (key as string) === 'constructor' || (key as string) === 'prototype') {
25
+ throw new Error('Invalid key');
26
+ }
27
+ return (entry[key] = entry[key] || {});
28
+ }
29
+
30
+ /**
31
+ * Step one level deeper in the cache, based on the key value from the current location
32
+ *
33
+ * @param entry - base entry to work from
34
+ * @param val - value to use as the key for progressing to the next level of the cache
35
+ */
36
+ function jumpToCacheEntry(entry: CacheEntry<any>, val: any): CacheEntry<any> {
37
+ if (val === undefined || val === null) {
38
+ // undefined or null just routes directly to the empty object. This avoids the issues of string collisions with 'null' or 'undefined'
39
+ // when using the string key map, it also avoids creating the WeakMap (since null is technically typoef object), particularly in cases
40
+ // where null is just being set on non-object types.
41
+ return ensureAndReturn(entry, 'empty');
42
+ }
43
+ if (typeof val === 'object' || typeof val === 'function') {
44
+ // objects and functions will be treated as key values in a WeakMap
45
+ const byObj = (entry.obj = entry.obj || new WeakMap<object, CacheEntry<any>>());
46
+ return byObj.get(val) || byObj.set(val, {}).get(val);
47
+ }
48
+ // otherwise convert everything to a string and store it in the str object (using it as a map)
49
+ const key = val + '';
50
+ const byString = ensureAndReturn(entry, 'str');
51
+ return (byString[key] = byString[key] || {});
52
+ }
53
+
54
+ /**
55
+ * Given a base entry, either traverse or build the cache tree that matches the provided args
56
+ *
57
+ * @param entry - entry to use as the base of the cache walk
58
+ * @param args - array of arguments to use to progress deeper into the cache
59
+ */
60
+ export function getCacheEntry<T, TGet = any>(entry: CacheEntry<T>, args: any[]): CacheEntry<TGet> {
61
+ // in the case where the args array exists and is > 0 length:
62
+ // - walk the cache from entry, like a linked list, jumping to the next entry by key, building it up as you go
63
+ // - otherwise if there are no args just use the noargs branch
64
+ return args && args.length > 0
65
+ ? (args.reduce((previous: CacheEntry<any>, arg: any) => jumpToCacheEntry(previous, arg), entry) as CacheEntry<TGet>)
66
+ : ensureAndReturn(entry, 'noargs');
67
+ }
@@ -0,0 +1,91 @@
1
+ import { getMemoCache } from './getMemoCache';
2
+
3
+ interface TestObj {
4
+ id: number;
5
+ }
6
+
7
+ function getObjFactory() {
8
+ const obj: TestObj = { id: 0 };
9
+ return () => ({
10
+ id: obj.id++,
11
+ });
12
+ }
13
+
14
+ describe('getMemoCache unit tests', () => {
15
+ test('memoValue with null function', () => {
16
+ const memoValue = getMemoCache();
17
+ const [val] = memoValue(null, ['foo', 'bar']);
18
+ expect(val).toBeNull();
19
+ });
20
+
21
+ test('memoValue with undefined', () => {
22
+ const memoValue = getMemoCache();
23
+ const [val] = memoValue(undefined, ['foo', 'bar']);
24
+ expect(val).toBeUndefined();
25
+ });
26
+
27
+ test('memoValue with string', () => {
28
+ const memoValue = getMemoCache();
29
+ const [val] = memoValue('foo', ['bar', 'baz']);
30
+ expect(val).toEqual('foo');
31
+ });
32
+
33
+ test('memoValue with object', () => {
34
+ const memoValue = getMemoCache();
35
+ const obj = { foo: 'hello', bar: 2, baz: 'you' };
36
+ const [val] = memoValue(obj, ['hello', obj]);
37
+ expect(val).toBe(obj);
38
+ });
39
+
40
+ test('memoValue executes function', () => {
41
+ const memoValue = getMemoCache();
42
+ const fn = getObjFactory();
43
+ const v1 = fn();
44
+ const [v2] = memoValue(fn, ['bar', 'baz']);
45
+ expect(v1).not.toBe(v2);
46
+ expect(v1.id).not.toEqual(v2.id);
47
+ });
48
+
49
+ test('memo calls function only once', () => {
50
+ const memoValue = getMemoCache();
51
+ const fn = getObjFactory();
52
+ const keys = ['hello', 'world'];
53
+ const [o1] = memoValue(fn, keys);
54
+ const [o2] = memoValue(fn, keys);
55
+ expect(o2).toBe(o1);
56
+ });
57
+
58
+ test('memo calls function only once for empty inputs', () => {
59
+ const memoValue = getMemoCache();
60
+ const fn = getObjFactory();
61
+ const [o1] = memoValue(fn, undefined);
62
+ const [o2] = memoValue(fn, undefined);
63
+ expect(o2).toBe(o1);
64
+ });
65
+
66
+ test('sub caches are separate', () => {
67
+ const memoValue = getMemoCache();
68
+ const base1 = {};
69
+ const base2 = {};
70
+ const [, getCache1] = memoValue(null, [base1]);
71
+ const [, getCache2] = memoValue(null, [base2]);
72
+ const objKey = {};
73
+ const fn = getObjFactory();
74
+ const [o1] = getCache1(fn, [objKey]);
75
+ const [o2] = getCache2(fn, [objKey]);
76
+ const [o3] = getCache2(fn, [objKey]);
77
+ expect(o1).not.toBe(o2);
78
+ expect(o3).toBe(o2);
79
+ });
80
+
81
+ test('sub caches work on branches', () => {
82
+ const memoValue = getMemoCache();
83
+ const keys1 = [{}, 2, 'hello'];
84
+ const keys2 = [{}, true];
85
+ const fn = getObjFactory();
86
+ const [, getMemoValue] = memoValue(null, keys1);
87
+ const [val1] = getMemoValue(fn, keys2);
88
+ const [val2] = memoValue(fn, [...keys1, ...keys2]);
89
+ expect(val2).toBe(val1);
90
+ });
91
+ });
@@ -0,0 +1,37 @@
1
+ import type { CacheEntry } from './getCacheEntry';
2
+ import { getCacheEntry } from './getCacheEntry';
3
+
4
+ export type ValueFactory<T> = () => T;
5
+
6
+ /** signature for the cache function */
7
+ export type GetMemoValue<T, TGet = any> = (factory: T | ValueFactory<T>, keys: any[]) => [T, GetMemoValue<TGet>];
8
+
9
+ /** base node used to remember references when a globalKey is set */
10
+ const _baseEntry: CacheEntry<any> = {};
11
+
12
+ /**
13
+ * Primary functional worker used to implement the caching pattern
14
+ *
15
+ * @param entry - entry to use as the base of the cache traversal
16
+ * @param factory - generally a function who's results will be cached, and returned via the set of keys
17
+ * @param keys - an ordered array of values of any type, used as keys to look up the entry
18
+ */
19
+ function getMemoValueWorker<T, TGet = any>(entry: CacheEntry<any>, factory: T | ValueFactory<T>, keys: any[]): [T, GetMemoValue<TGet>] {
20
+ const foundEntry = getCacheEntry(entry, keys);
21
+ // check the key being set, not the value to disambiguate an undefined factory result/value from never having run the factory
22
+ if (!foundEntry.hasOwnProperty('value')) {
23
+ foundEntry.value = typeof factory === 'function' ? (factory as ValueFactory<T>)() : factory;
24
+ }
25
+ return [foundEntry.value, (fact: TGet | ValueFactory<TGet>, args: any[]) => getMemoValueWorker<TGet>(foundEntry, fact, args)];
26
+ }
27
+
28
+ /**
29
+ * Get a memo cache instance, this can either be completely self-contained or associated with a global key
30
+ *
31
+ * @param globalKey - optional object reference to use as a key for this cache. If specified it can be used
32
+ * to retrieve the same cache from the global call. If not specified the returned cache will be completely isolated.
33
+ */
34
+ export function getMemoCache<T = any>(globalKey?: object): GetMemoValue<T> {
35
+ const entry = globalKey ? getCacheEntry(_baseEntry, [globalKey]) : {};
36
+ return (fact: T | ValueFactory<T>, args: any[]) => getMemoValueWorker<T>(entry, fact, args);
37
+ }
@@ -0,0 +1,3 @@
1
+ export type { GetMemoValue } from './getMemoCache';
2
+ export { getMemoCache } from './getMemoCache';
3
+ export { memoize } from './memoize';
@@ -0,0 +1,48 @@
1
+ import { memoize } from './memoize';
2
+
3
+ let _globalCalls = 0;
4
+
5
+ describe('Memoize unit tests', () => {
6
+ test('simple function no args', () => {
7
+ const noArgsFn = memoize(() => {
8
+ return ++_globalCalls;
9
+ });
10
+ const result1 = noArgsFn();
11
+ expect(noArgsFn()).toEqual(result1);
12
+ });
13
+
14
+ test('simple function 1 arg', () => {
15
+ const incrementFn = memoize((bump: number) => {
16
+ ++_globalCalls;
17
+ return bump + 1;
18
+ });
19
+
20
+ expect(incrementFn(3)).toEqual(4);
21
+ const calls = _globalCalls;
22
+ incrementFn(3);
23
+ expect(_globalCalls).toEqual(calls);
24
+ incrementFn(4);
25
+ expect(_globalCalls).toEqual(calls + 1);
26
+ incrementFn(4);
27
+ expect(_globalCalls).toEqual(calls + 1);
28
+ });
29
+
30
+ test('void function 2 args', () => {
31
+ const voidFn = memoize((a: number, b: string) => {
32
+ if (a) {
33
+ ++_globalCalls;
34
+ } else if (b) {
35
+ ++_globalCalls;
36
+ } else {
37
+ ++_globalCalls;
38
+ }
39
+ });
40
+
41
+ voidFn(3, 'hello');
42
+ const calls = _globalCalls;
43
+ voidFn(3, 'hello');
44
+ expect(_globalCalls).toEqual(calls);
45
+ voidFn(3, 'world');
46
+ expect(_globalCalls).toEqual(calls + 1);
47
+ });
48
+ });
@@ -0,0 +1,17 @@
1
+ import { getMemoCache } from './getMemoCache';
2
+
3
+ /**
4
+ * This wraps a function to memoize the results using the standard javascript memoization pattern
5
+ * @param fn - function to memoize
6
+ */
7
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
8
+ export function memoize<T extends Function>(fn: T): T {
9
+ // create a unique cache that will be captured in the closure
10
+ const cache = getMemoCache<any>();
11
+ // create the closure which wraps the calling function
12
+ const closure = (...args: any[]) => {
13
+ return cache(() => fn(...(args || [])), args)[0];
14
+ };
15
+ // now return that closure strongly typed as the function.
16
+ return closure as unknown as T;
17
+ }
@@ -0,0 +1,3 @@
1
+ export type { StyleProp } from './mergeStyles.types';
2
+ export { mergeStyles } from './mergeStyles';
3
+ export { mergeProps } from './mergeProps';
@@ -0,0 +1,20 @@
1
+ import type { MergeOptions } from '../immutable-merge/Merge';
2
+ import { immutableMergeCore, filterToObjects } from '../immutable-merge/Merge';
3
+
4
+ import { mergeStyles } from './mergeStyles';
5
+
6
+ /**
7
+ * Props will not deeply merge with the exception of a style property. Also className needs to be handled specially.
8
+ */
9
+ const mergePropsOptions: MergeOptions = {
10
+ className: (...names: any[]) => names.filter((v) => v && typeof v === 'string').join(' '),
11
+ style: mergeStyles,
12
+ };
13
+
14
+ /**
15
+ * Merge props together, flattening and merging styles as appropriate
16
+ * @param props - props to merge together
17
+ */
18
+ export function mergeProps<TProps>(...props: (TProps | undefined)[]): TProps {
19
+ return immutableMergeCore(mergePropsOptions, ...filterToObjects<TProps>(props));
20
+ }
@@ -0,0 +1,124 @@
1
+ import type { ColorValue } from 'react-native';
2
+
3
+ import { flattenStyle, mergeAndFlattenStyles, mergeStyles } from './mergeStyles';
4
+ import type { StyleProp } from './mergeStyles.types';
5
+
6
+ interface IFakeStyle {
7
+ backgroundColor?: ColorValue;
8
+ color?: ColorValue;
9
+ fontFamily?: string;
10
+ borderWidth?: number;
11
+ ':hover'?: IFakeStyle;
12
+ }
13
+
14
+ type IFakeStyleProp = StyleProp<IFakeStyle>;
15
+
16
+ const s1: IFakeStyleProp = [
17
+ { backgroundColor: 'blue' },
18
+ [{ color: 'red', borderWidth: 1 }, { fontFamily: 'segoe' }, [{ backgroundColor: 'bodyBackground' }]],
19
+ ];
20
+
21
+ const s1flatten: IFakeStyleProp = {
22
+ backgroundColor: 'bodyBackground',
23
+ color: 'red',
24
+ borderWidth: 1,
25
+ fontFamily: 'segoe',
26
+ };
27
+
28
+ const s2: IFakeStyleProp = {
29
+ borderWidth: 2,
30
+ fontFamily: 'primary',
31
+ color: 'bodyText',
32
+ };
33
+
34
+ const sMerged: IFakeStyleProp = {
35
+ backgroundColor: 'bodyBackground',
36
+ borderWidth: 2,
37
+ fontFamily: 'primary',
38
+ color: 'bodyText',
39
+ };
40
+
41
+ const sSelector: IFakeStyleProp = {
42
+ borderWidth: 1,
43
+ ':hover': {
44
+ borderWidth: 2,
45
+ fontFamily: 'primary',
46
+ },
47
+ };
48
+
49
+ const sSelector2: IFakeStyleProp = {
50
+ backgroundColor: 'white',
51
+ ':hover': {
52
+ backgroundColor: 'black',
53
+ borderWidth: 3,
54
+ },
55
+ };
56
+
57
+ const sArraySelector: IFakeStyleProp = [[sSelector]];
58
+
59
+ const sArraySelector2: IFakeStyleProp = [sSelector2];
60
+
61
+ const sMergedSelectors: IFakeStyleProp = {
62
+ borderWidth: 1,
63
+ backgroundColor: 'white',
64
+ ':hover': {
65
+ borderWidth: 3,
66
+ fontFamily: 'primary',
67
+ backgroundColor: 'black',
68
+ },
69
+ };
70
+
71
+ describe('Style flatten and merge tests', () => {
72
+ test('flatten recursive arrays', () => {
73
+ const flattened = flattenStyle(s1);
74
+ expect(flattened).toEqual(s1flatten);
75
+ expect(flattened).not.toBe(s1);
76
+ });
77
+
78
+ test('flatten flat style returns style', () => {
79
+ const flattened = flattenStyle(s2);
80
+ expect(flattened).toBe(s2);
81
+ });
82
+
83
+ test('merge also flattens', () => {
84
+ const merged = mergeAndFlattenStyles(undefined, undefined, s1, s2);
85
+ expect(merged).toEqual(sMerged);
86
+ });
87
+
88
+ test('merge with sub objects', () => {
89
+ const merged = mergeAndFlattenStyles(undefined, undefined, sSelector, sSelector2);
90
+ expect(merged).toEqual(sMergedSelectors);
91
+ });
92
+
93
+ test('merge sub objects in arrays', () => {
94
+ const merged = mergeAndFlattenStyles(undefined, undefined, sArraySelector, sArraySelector2);
95
+ expect(merged).toEqual(sMergedSelectors);
96
+ });
97
+
98
+ test('memo recursive arrays', () => {
99
+ const flattened = mergeStyles(s1);
100
+ const flattened2 = mergeStyles(s1);
101
+ expect(flattened).toEqual(s1flatten);
102
+ expect(flattened2).toBe(flattened);
103
+ });
104
+
105
+ test('memo flat style', () => {
106
+ const flattened = mergeStyles(s2);
107
+ const flattened2 = mergeStyles(s2);
108
+ expect(flattened).toBe(s2);
109
+ expect(flattened2).toBe(flattened);
110
+ });
111
+
112
+ test('memo and flatten multiple', () => {
113
+ const flattened = mergeStyles(s1, s2);
114
+ const flattened2 = mergeStyles(s1, s2);
115
+ expect(flattened).toEqual(sMerged);
116
+ expect(flattened2).toBe(flattened);
117
+ });
118
+
119
+ test('memo styles ignores undefined values', () => {
120
+ const result1 = mergeStyles(s1, s2, undefined, s1flatten);
121
+ const result2 = mergeStyles(s1, undefined, s2, s1flatten);
122
+ expect(result2).toBe(result1);
123
+ });
124
+ });
@@ -0,0 +1,40 @@
1
+ import { immutableMerge } from '../immutable-merge/Merge';
2
+ import { getMemoCache } from '../memo-cache/getMemoCache';
3
+
4
+ import type { StyleProp } from './mergeStyles.types';
5
+
6
+ /**
7
+ * Take a react-native style, which may be a recursive array, and return as a flattened
8
+ * style. This is analagous to the flatten routine that is part of the style sheet API
9
+ *
10
+ * @param style - StyleProp<TStyle> to flatten, this can be a TStyle or an array
11
+ */
12
+ export function flattenStyle(style: StyleProp<object>): object {
13
+ return Array.isArray(style) ? immutableMerge(...style.map((v) => flattenStyle(v))) : style || {};
14
+ }
15
+
16
+ /**
17
+ * Merge styles together into a single flat object and optionally finalize them, can also be used to finalize a single style
18
+ *
19
+ * @param styles - array of styles to merge together. The styles will be flattened as part of the process
20
+ */
21
+ export function mergeAndFlattenStyles(...styles: StyleProp<object>[]): object | undefined {
22
+ // baseline merge and flatten the objects
23
+ return immutableMerge(
24
+ ...styles.map((styleProp: StyleProp<object>) => {
25
+ return flattenStyle(styleProp);
26
+ }),
27
+ );
28
+ }
29
+
30
+ const _styleCache = getMemoCache();
31
+
32
+ export function mergeStyles(...styles: StyleProp<object>[]): object | undefined {
33
+ // filter the style set to just objects (which might be arrays or plain style objects)
34
+ const inputs = styles.filter((s) => typeof s === 'object') as object[];
35
+
36
+ // now memo the results if there is more than one element or if the one element is an array
37
+ return inputs.length > 1 || (inputs.length === 1 && Array.isArray(inputs[0]))
38
+ ? _styleCache(() => mergeAndFlattenStyles(undefined, ...inputs), inputs)[0]
39
+ : inputs[0] || {};
40
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This is a copy of the react-native style prop type, copied here to avoid RN dependencies for web clients
3
+ */
4
+ type Falsy = undefined | null | false;
5
+ type RecursiveArray<T> = (T | RecursiveArray<T>)[];
6
+ /** Keep a brand of 'T' so that calls to `StyleSheet.flatten` can take `RegisteredStyle<T>` and return `T`. */
7
+ type RegisteredStyle<T> = number & { __registeredStyleBrand: T };
8
+
9
+ export type StyleProp<T> = T | RegisteredStyle<T> | RecursiveArray<T | RegisteredStyle<T> | Falsy> | Falsy;