@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,342 @@
1
+ import type { MergeOptions } from './Merge';
2
+ import { immutableMerge, immutableMergeCore, processImmutable } from './Merge';
3
+
4
+ interface IFakeStyle {
5
+ s1?: string;
6
+ s2?: number;
7
+ nm?: INoMerge;
8
+ }
9
+
10
+ interface INoMerge {
11
+ nm1?: number;
12
+ nm2?: number;
13
+ }
14
+
15
+ interface IFakeSettings {
16
+ root?: {
17
+ p1?: string;
18
+ p2?: number;
19
+ nm?: INoMerge;
20
+ style?: IFakeStyle;
21
+ };
22
+ fakeSlot?: {
23
+ ps1?: string;
24
+ ps2?: number;
25
+ style?: IFakeStyle;
26
+ };
27
+ }
28
+
29
+ const sett1: IFakeSettings = {
30
+ root: {
31
+ p2: 1,
32
+ nm: { nm1: 1 },
33
+ style: { s1: 'foo', s2: 2, nm: { nm1: 1 } },
34
+ },
35
+ fakeSlot: {
36
+ ps2: 2,
37
+ },
38
+ };
39
+
40
+ const sett2: IFakeSettings = {
41
+ root: {
42
+ p1: 'sett2',
43
+ p2: 2,
44
+ nm: { nm2: 2 },
45
+ },
46
+ fakeSlot: {
47
+ style: { s1: 'sett2' },
48
+ },
49
+ };
50
+
51
+ const sett1plus2: IFakeSettings = {
52
+ root: {
53
+ p1: 'sett2',
54
+ p2: 2,
55
+ nm: { nm2: 2 },
56
+ style: { s1: 'foo', s2: 2, nm: { nm1: 1 } },
57
+ },
58
+ fakeSlot: {
59
+ ps2: 2,
60
+ style: { s1: 'sett2' },
61
+ },
62
+ };
63
+
64
+ const sett3: IFakeSettings = {
65
+ root: {
66
+ p1: 'sett3',
67
+ style: { s2: 3, nm: { nm2: 2 } },
68
+ },
69
+ };
70
+
71
+ const sett1plus3: IFakeSettings = {
72
+ root: {
73
+ p1: 'sett3',
74
+ p2: 1,
75
+ nm: { nm1: 1 },
76
+ style: { s1: 'foo', s2: 3, nm: { nm2: 2 } },
77
+ },
78
+ fakeSlot: {
79
+ ps2: 2,
80
+ },
81
+ };
82
+
83
+ const sett1plus2plus3: IFakeSettings = {
84
+ root: {
85
+ p1: 'sett3',
86
+ p2: 2,
87
+ nm: { nm2: 2 },
88
+ style: { s1: 'foo', s2: 3, nm: { nm2: 2 } },
89
+ },
90
+ fakeSlot: {
91
+ ps2: 2,
92
+ style: { s1: 'sett2' },
93
+ },
94
+ };
95
+
96
+ const mergeOptions: MergeOptions = {
97
+ object: {
98
+ style: 0,
99
+ },
100
+ };
101
+
102
+ interface IDeepObj {
103
+ a: { b: { c: number } };
104
+ b: { c: { d: { d: string } } };
105
+ }
106
+
107
+ const deep1 = {
108
+ a: { b: { c: 1 } },
109
+ b: { c: { d: { d: 'foo' } } },
110
+ c: { e: 4 },
111
+ };
112
+
113
+ const deep2 = {
114
+ a: { b1: 3, b: { c: 2 } },
115
+ b: { c: { d2: 'bar' } },
116
+ c: { e2: { f: 'baz' } },
117
+ };
118
+
119
+ const deepMerged = {
120
+ a: { b1: 3, b: { c: 2 } },
121
+ b: { c: { d: { d: 'foo' }, d2: 'bar' } },
122
+ c: { e: 4, e2: { f: 'baz' } },
123
+ };
124
+
125
+ const singleToChange = {
126
+ a: { b: { c: { changeMe: { color: 'blue' } } } },
127
+ b: { d: { changeMe: { font: 'fixed' } } },
128
+ };
129
+
130
+ const singleWithChanges = {
131
+ a: { b: { c: { changeMe: { color: 'changed' } } } },
132
+ b: { d: { changeMe: { font: 'fixed' } } },
133
+ };
134
+
135
+ const _colorKey = 'color';
136
+
137
+ const changeMeHandler = (...objs: (Record<string, unknown> | undefined)[]) => {
138
+ // written always assuming only one entry
139
+ if (objs.length === 1) {
140
+ const firstObj = objs[0]!;
141
+
142
+ if (firstObj[_colorKey]) {
143
+ return { ...firstObj, color: 'changed' };
144
+ }
145
+
146
+ return firstObj;
147
+ }
148
+ return undefined;
149
+ };
150
+
151
+ const changeMeOption1: MergeOptions = {
152
+ a: {
153
+ b: {
154
+ c: {
155
+ changeMe: changeMeHandler,
156
+ },
157
+ },
158
+ },
159
+ };
160
+
161
+ const changeMeOption2: MergeOptions = {
162
+ object: {
163
+ object: {
164
+ object: {
165
+ changeMe: changeMeHandler,
166
+ },
167
+ changeMe: changeMeHandler,
168
+ },
169
+ },
170
+ };
171
+
172
+ describe('Immutable merge unit tests', () => {
173
+ test('merge one returns same object', () => {
174
+ const obj = {
175
+ a: 'a',
176
+ b: 2,
177
+ };
178
+ expect(immutableMerge(obj, undefined)).toBe(obj);
179
+ expect(immutableMerge(undefined, obj)).toBe(obj);
180
+ });
181
+
182
+ test('merge flat is like assign', () => {
183
+ const obj1 = { a: 'a', b: 1 };
184
+ const obj2 = { b: 2, c: true };
185
+ const merged = { a: 'a', b: 2, c: true };
186
+ expect(immutableMerge<any>(obj1, obj2)).toEqual(merged);
187
+ expect(immutableMergeCore<any>(0, obj1, obj2)).toEqual(merged);
188
+ expect(immutableMergeCore<any>(true, obj1, obj2)).toEqual(merged);
189
+ });
190
+
191
+ const dm1 = {
192
+ a: { b: { c: { foo: 'foo', bar: 'bar' } } },
193
+ d: { e: 1, f: { g: 'hello', h: 2 } },
194
+ };
195
+
196
+ const dm2 = {
197
+ a: { b: { c: { bar: 'bar2', baz: 'baz' } }, i: 'world' },
198
+ d: { j: 4 },
199
+ };
200
+
201
+ test('deep merge', () => {
202
+ expect(immutableMerge<any>(dm1, dm2)).toEqual({
203
+ a: { b: { c: { foo: 'foo', bar: 'bar2', baz: 'baz' } }, i: 'world' },
204
+ d: { e: 1, f: { g: 'hello', h: 2 }, j: 4 },
205
+ });
206
+ });
207
+
208
+ test('merge zero levels', () => {
209
+ expect(immutableMergeCore<any>(0, dm1, dm2)).toEqual(dm2);
210
+ });
211
+
212
+ test('merge one level deep', () => {
213
+ const result = {
214
+ a: dm2.a,
215
+ d: { ...dm1.d, ...dm2.d },
216
+ };
217
+ expect(immutableMergeCore<any>(1, dm1, dm2)).toEqual(result);
218
+ expect(immutableMergeCore<any>({ object: 0 }, dm1, dm2)).toEqual(result);
219
+ });
220
+
221
+ test('merge with empty object', () => {
222
+ const merged = immutableMergeCore(mergeOptions, sett1, {});
223
+ expect(merged).toBe(sett1);
224
+ const merged2 = immutableMergeCore(mergeOptions, {}, sett2);
225
+ expect(merged2).toBe(sett2);
226
+ });
227
+
228
+ test('merge sett1 and sett2', () => {
229
+ const merged = immutableMergeCore(mergeOptions, sett1, sett2) as IFakeSettings;
230
+ expect(merged).toEqual(sett1plus2);
231
+ expect(merged!.root.style).toBe(sett1.root.style);
232
+ expect(merged!.fakeSlot!.style).toBe(sett2.fakeSlot!.style);
233
+ });
234
+
235
+ test('merge sett1 and sett3', () => {
236
+ const merged = immutableMergeCore(mergeOptions, sett1, sett3) as IFakeSettings;
237
+ expect(merged).toEqual(sett1plus3);
238
+ expect(merged!.fakeSlot).toBe(sett1.fakeSlot);
239
+ });
240
+
241
+ test('merge three', () => {
242
+ const merged = immutableMergeCore(mergeOptions, sett1, sett2, sett3);
243
+ expect(merged).toEqual(sett1plus2plus3);
244
+ });
245
+
246
+ test('deepMerge', () => {
247
+ const merged = immutableMergeCore<any>(-1, deep1, deep2) as IDeepObj;
248
+ expect(merged).toEqual(deepMerged);
249
+ expect(merged.b.c.d).toBe(deep1.b.c.d);
250
+ expect(merged.a.b).not.toBe(deep2.a.b);
251
+ });
252
+
253
+ test('singleProcessNoChange', () => {
254
+ const merged = processImmutable({ object: true }, singleToChange);
255
+ expect(merged).toBe(singleToChange);
256
+ });
257
+
258
+ test('single process with change', () => {
259
+ const merged = processImmutable(changeMeOption1, singleToChange);
260
+ expect(merged).toEqual(singleWithChanges);
261
+ expect(merged).not.toBe(singleToChange);
262
+ expect((merged as any).b).toBe(singleToChange.b);
263
+ });
264
+
265
+ test('single process with change - alternative', () => {
266
+ const merged = processImmutable(changeMeOption2, singleToChange);
267
+ expect(merged).toEqual(singleWithChanges);
268
+ expect(merged).not.toBe(singleToChange);
269
+ expect((merged as any).b).toBe(singleToChange.b);
270
+ });
271
+
272
+ const withArray1 = {
273
+ baseArray: [1, 2, 3],
274
+ sub: { subArray: ['a', 'b', 'c'] },
275
+ };
276
+
277
+ const withArray2 = {
278
+ baseArray: [4, 5, 6],
279
+ sub: { subArray: ['d', 'e', 'f'] },
280
+ };
281
+
282
+ test('arrays overwrite each other', () => {
283
+ const merged = immutableMergeCore({ depth: -1 }, withArray1, withArray2);
284
+ expect(merged).toEqual(withArray2);
285
+ expect(merged).not.toBe(withArray2);
286
+ });
287
+
288
+ const withObj = {
289
+ a: { foo: 'bar' },
290
+ b: 2,
291
+ };
292
+
293
+ const withNonObj = {
294
+ a: 'hello',
295
+ b: 3,
296
+ };
297
+
298
+ test('last writer wins for objects and non-objects', () => {
299
+ const merged = immutableMerge<any>(withObj, withNonObj);
300
+ expect(merged).toEqual(withNonObj);
301
+ const merged2 = immutableMerge<any>(withNonObj, withObj);
302
+ expect(merged2).toEqual(withObj);
303
+ });
304
+
305
+ const arrayMerger = (...targets: any[]) => {
306
+ const arrays = targets.filter((t) => Array.isArray(t));
307
+ let result = [];
308
+ arrays.forEach((v) => (result = result.concat(...v)));
309
+ return result;
310
+ };
311
+
312
+ test('arrays can merge with handler', () => {
313
+ const merged = immutableMergeCore(
314
+ {
315
+ object: {
316
+ subArray: arrayMerger,
317
+ },
318
+ },
319
+ withArray1,
320
+ withArray2,
321
+ );
322
+ expect(merged).toEqual({
323
+ baseArray: [4, 5, 6],
324
+ sub: { subArray: ['a', 'b', 'c', 'd', 'e', 'f'] },
325
+ });
326
+ });
327
+
328
+ test('arrays can merge deeply', () => {
329
+ const merged = immutableMergeCore(
330
+ {
331
+ object: true,
332
+ array: 'appendArray',
333
+ },
334
+ withArray1,
335
+ withArray2,
336
+ );
337
+ expect(merged).toEqual({
338
+ baseArray: [1, 2, 3, 4, 5, 6],
339
+ sub: { subArray: ['a', 'b', 'c', 'd', 'e', 'f'] },
340
+ });
341
+ });
342
+ });
@@ -0,0 +1,255 @@
1
+ /**
2
+ * The basic options for recursion at a given level. Two types for two behaviors:
3
+ *
4
+ * boolean:
5
+ * - if true, recurse indefinitely
6
+ * - if false, don't recurse
7
+ *
8
+ * number:
9
+ * - if 0, don't recurse from this level
10
+ * - if > 0, recurse that many times then stop
11
+ * - if < 0, recurse indefinitely
12
+ */
13
+ export type RecursionOption = boolean | number;
14
+
15
+ /**
16
+ * a function that can be set to merge arguments
17
+ */
18
+ export type CustomRecursionHandler = (...vals: any[]) => any;
19
+
20
+ /**
21
+ * built in handler functions that can be applied for a given key
22
+ */
23
+ export type BuiltinRecursionHandlers = 'appendArray';
24
+
25
+ /**
26
+ * Handlers for recursion of a given key or type. These can either be functions or a reference to a supported
27
+ * built-in merge routine
28
+ */
29
+ export type RecursionHandler = BuiltinRecursionHandlers | CustomRecursionHandler;
30
+
31
+ /**
32
+ * Base object type for merges, avoids using object since that is too broad. In particular things like null and arrays
33
+ * are not valid object types for the purposes of this library.
34
+ */
35
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
36
+ export type ObjectBase = {};
37
+
38
+ /**
39
+ *
40
+ */
41
+ export type TypeofResult = 'undefined' | 'object' | 'boolean' | 'number' | 'string' | 'symbol' | 'bigint' | 'function';
42
+ export type ExpandedTypeof = TypeofResult | 'array' | 'null';
43
+
44
+ /**
45
+ * configuration object for the merge, key names are matched with a few exceptions:
46
+ * - object: matches non-array object types
47
+ * - array: matches array types
48
+ * - [key: string]: matches anything by name
49
+ */
50
+ export interface MergeOptions {
51
+ [objectTypeOrKeyName: string]: RecursionOption | RecursionHandler | MergeOptions;
52
+ }
53
+
54
+ /**
55
+ * built in handlers for the module
56
+ */
57
+ const _builtinHandlers: { [K in BuiltinRecursionHandlers]: CustomRecursionHandler } = {
58
+ appendArray: (...objs: any[]) => {
59
+ return [].concat(...objs);
60
+ },
61
+ };
62
+
63
+ /**
64
+ * This processes the various type options for merge core and turns them into a MergeConfig
65
+ * @param options - options passed into immutableMergeCore
66
+ */
67
+ function normalizeOptions(options: RecursionOption | MergeOptions): [MergeOptions, boolean] {
68
+ return typeof options === 'boolean'
69
+ ? [{ object: options }, options]
70
+ : typeof options === 'number'
71
+ ? [{ object: options >= 0 ? options : true }, options !== 0]
72
+ : [options, true];
73
+ }
74
+
75
+ /**
76
+ * Provide a more sensible type result that expands upon the built in typeof operator
77
+ * In particular this will differentiate arrays and nulls from standard objects
78
+ * @param val - value to check type
79
+ */
80
+ function getEntityType(val: unknown): ExpandedTypeof {
81
+ switch (typeof val) {
82
+ case 'object':
83
+ if (val === null) {
84
+ return 'null';
85
+ } else if (Array.isArray(val)) {
86
+ return 'array';
87
+ }
88
+ return 'object';
89
+ default:
90
+ return typeof val as TypeofResult;
91
+ }
92
+ }
93
+
94
+ /** resolve custom handlers if they are applicable */
95
+ function resolveIfHandler(option: RecursionHandler | RecursionOption | MergeOptions): CustomRecursionHandler | MergeOptions | undefined {
96
+ return typeof option === 'function' ? option : typeof option === 'string' ? _builtinHandlers[option] : undefined;
97
+ }
98
+
99
+ /** pass array configurations down, this allows for saying all arrays should be appended rather than replaced */
100
+ function getTypesMixin(config: MergeOptions): MergeOptions {
101
+ return config.array ? { array: config.array } : {};
102
+ }
103
+
104
+ /** resolve the object behaviors for configuration */
105
+ function resolveForObject(option: RecursionHandler | RecursionOption | MergeOptions, mixin: MergeOptions): MergeOptions | undefined {
106
+ if (typeof option === 'boolean') {
107
+ // booleans won't recurse if false, otherwise recurse infinitely
108
+ return option ? { object: option, ...mixin } : mixin;
109
+ } else if (typeof option === 'number') {
110
+ // numbers get decremented or set to false if we've reached zero. Negative values will have been converted to boolean true
111
+ return option === 0 ? mixin : { object: option - 1, ...mixin };
112
+ } else {
113
+ // otherwise it is an object type so just pass the child object through
114
+ return option as MergeOptions;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Figure out the handler for this property.
120
+ * It will either be a function, a config object to pass to a recursive call, or undefined
121
+ * in the undefined case, this key will be left as-is
122
+ */
123
+ function getHandlerForPropertyOfType(
124
+ config: MergeOptions,
125
+ propKey: string,
126
+ propType: string,
127
+ ): CustomRecursionHandler | MergeOptions | undefined {
128
+ let result: CustomRecursionHandler | MergeOptions | undefined = undefined;
129
+ const option = config[propKey] !== undefined ? config[propKey] : config[propType] !== undefined ? config[propType] : undefined;
130
+
131
+ if (option !== undefined) {
132
+ // try to resolve the option as a handler, either function or built-in first. This is the only option that is valid for non-object types.
133
+ result = resolveIfHandler(option);
134
+
135
+ // if it is an object then resolve boolean, number or config types
136
+ if (result === undefined && propType === 'object') {
137
+ result = resolveForObject(option, getTypesMixin(config));
138
+ }
139
+ }
140
+
141
+ return result;
142
+ }
143
+
144
+ /**
145
+ * Assign properties of source objects to a new target object. This is just a type wrapper around Object.assign
146
+ * @param objs - array of objects to merge
147
+ * @returns the result of object assign on the objects, typed to T
148
+ */
149
+ function assignToNewObject<T extends ObjectBase>(...objs: T[]): T {
150
+ return Object.assign({}, ...objs);
151
+ }
152
+
153
+ /**
154
+ * Filter a set of unknown values to only include those that extend ObjectBase
155
+ * @param values - array of values to filter
156
+ * @returns the filtered set of values
157
+ */
158
+ export function filterToObjects<T extends ObjectBase = ObjectBase>(values: unknown[]): T[] {
159
+ return values.filter((v) => v && getEntityType(v) === 'object' && Object.getOwnPropertyNames(v).length > 0) as T[];
160
+ }
161
+
162
+ /**
163
+ * This will merge two or more objects together using an immutable style merge pattern. If there is only one object or
164
+ * if there is only one object with values, that object itself will be returned, with two or more objects the keys within will
165
+ * be first merged with Object.assign and then optionally will recurse to merge sub objects as specified by the options.
166
+ *
167
+ * Note that this tries hard to not create extra objects, because of this merging an object with an empty object will not
168
+ * create a new object.
169
+ *
170
+ * @param mergeOptions - options driving behavior of the merge. See MergeOptions for a description
171
+ * @param singleMode - normally if there is only one branch to follow in the merge the routine will not recurse. If single mode
172
+ * is true the routine will progress through all branches of the hierarchy. Useful if using a processor function that needs to be run.
173
+ * @param objs - an array of objects to merge together
174
+ */
175
+ function immutableMergeWorker<T extends ObjectBase>(mergeOptions: RecursionOption | MergeOptions, singleMode: boolean, ...objs: T[]): T {
176
+ const setToMerge = filterToObjects<T>(objs);
177
+ const [options, mightRecurse] = normalizeOptions(mergeOptions);
178
+ const processSingle = singleMode && setToMerge.length === 1;
179
+
180
+ // there is work to do if there is more than one object to merge or if we are processing single objects
181
+ if (setToMerge.length > 1 || (processSingle && setToMerge.length === 1)) {
182
+ // now assign everything to get the normal property precedence (and merge all the keys)
183
+ let result = processSingle ? undefined : assignToNewObject(...setToMerge);
184
+ const processSet = result || setToMerge[0];
185
+
186
+ for (const key in processSet) {
187
+ if (processSet.hasOwnProperty(key)) {
188
+ // only process if there is potential work to do
189
+ if (mightRecurse) {
190
+ const originalVal = processSet[key];
191
+ const entityType = getEntityType(originalVal);
192
+ const handler = getHandlerForPropertyOfType(options, key, entityType);
193
+ if (handler !== undefined) {
194
+ const values = setToMerge.map((set) => set[key]).filter((v) => v !== undefined);
195
+ const updatedVal =
196
+ typeof handler === 'function'
197
+ ? handler(...values)
198
+ : immutableMergeWorker<ObjectBase>(handler, singleMode, ...filterToObjects(values));
199
+ if (updatedVal !== originalVal) {
200
+ result = result || assignToNewObject(...setToMerge);
201
+ result[key] = updatedVal;
202
+ }
203
+ }
204
+ }
205
+
206
+ // delete undefined keys from the object, otherwise there is no easy way to delete keys
207
+ if (!processSingle && result[key] === undefined) {
208
+ delete result[key];
209
+ }
210
+ }
211
+ }
212
+
213
+ // in the single processing case return the original if nothing changed, otherwise return result
214
+ return result || processSet;
215
+ }
216
+ return setToMerge.length > 0 ? setToMerge[0] : undefined;
217
+ }
218
+
219
+ /**
220
+ * Recursively immutable merge sets of objects infinitely deep. This behaves like a standard deep merge with arrays replacing
221
+ * one another rather than appending. If appending arrays is desireable this can be configured via immutableMergeCore
222
+ *
223
+ * @param objs - variable input array of typed objects to merge
224
+ */
225
+ export function immutableMerge<T extends ObjectBase>(...objs: (T | undefined)[]): T | undefined {
226
+ return immutableMergeWorker(true, false, ...objs);
227
+ }
228
+
229
+ /**
230
+ * Version of immutable merge that can be configured to behave in a variety of manners. See the documentation for details.
231
+ *
232
+ * @param options - configuration options for the merge, this dictates what keys will be handled in what way
233
+ * @param objs - set of objects to merge together
234
+ */
235
+ export function immutableMergeCore<T extends ObjectBase>(
236
+ options: RecursionOption | MergeOptions,
237
+ ...objs: (T | undefined)[]
238
+ ): T | undefined {
239
+ return immutableMergeWorker(options, false, ...objs);
240
+ }
241
+
242
+ /**
243
+ * Process one or more immutable objects ensuring that handlers are called on every entry that applies. If a single object
244
+ * is passed in and no changes are made, that object will be returned. If updates happen from the handlers it will return the
245
+ * minimally mutated object.
246
+ *
247
+ * The use case for this might be a style transformation on a deeply nested object. If no changes are made the object won't be
248
+ * updated but in the case where something needs to be transformed this will perform the minimal mutations.
249
+ *
250
+ * @param processors - set of processor functions for handling keys
251
+ * @param objs - one or more objects to process. If multiple objects are passed they will be merged
252
+ */
253
+ export function processImmutable<T extends ObjectBase>(options: MergeOptions, ...objs: (T | undefined)[]): T | undefined {
254
+ return immutableMergeWorker(options, true, ...objs);
255
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ // immutable-merge exports
2
+ export { immutableMerge, immutableMergeCore, processImmutable, filterToObjects } from './immutable-merge/Merge';
3
+ export type {
4
+ BuiltinRecursionHandlers,
5
+ CustomRecursionHandler,
6
+ MergeOptions,
7
+ ObjectBase,
8
+ RecursionHandler,
9
+ RecursionOption,
10
+ } from './immutable-merge/Merge';
11
+
12
+ // memo-cache exports
13
+ export type { GetMemoValue } from './memo-cache/getMemoCache';
14
+ export { getMemoCache } from './memo-cache/getMemoCache';
15
+ export { memoize } from './memo-cache/memoize';
16
+
17
+ // merge-props exports
18
+ export type { StyleProp } from './merge-props/mergeStyles.types';
19
+ export { mergeStyles } from './merge-props/mergeStyles';
20
+ export { mergeProps } from './merge-props/mergeProps';