@symbo.ls/utils 3.2.3 → 3.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.
Files changed (122) hide show
  1. package/LICENSE +25 -12
  2. package/README.md +106 -0
  3. package/array.js +162 -0
  4. package/cache.js +7 -0
  5. package/cdn.js +83 -0
  6. package/component.js +25 -0
  7. package/cookie.js +71 -0
  8. package/{src/detectHeight.js → detectHeight.js} +3 -3
  9. package/dist/cjs/array.js +168 -0
  10. package/dist/cjs/browser.js +0 -2
  11. package/dist/cjs/cache.js +26 -0
  12. package/dist/cjs/cdn.js +85 -0
  13. package/dist/cjs/component.js +45 -0
  14. package/dist/cjs/cookie.js +75 -0
  15. package/dist/cjs/date.js +1 -3
  16. package/dist/cjs/detectHeight.js +4 -6
  17. package/dist/cjs/element.js +136 -0
  18. package/dist/cjs/env.js +43 -0
  19. package/dist/cjs/events.js +39 -0
  20. package/dist/cjs/extends.js +434 -0
  21. package/dist/cjs/fibonacci.js +1 -3
  22. package/dist/cjs/files.js +1 -3
  23. package/dist/cjs/function.js +114 -0
  24. package/dist/cjs/globals.js +30 -0
  25. package/dist/cjs/if.js +48 -0
  26. package/dist/cjs/index.js +38 -425
  27. package/dist/cjs/key.js +38 -0
  28. package/dist/cjs/keys.js +201 -0
  29. package/dist/cjs/load.js +5 -7
  30. package/dist/cjs/log.js +34 -0
  31. package/dist/cjs/metadata.js +310 -0
  32. package/dist/cjs/methods.js +599 -0
  33. package/dist/cjs/node.js +35 -0
  34. package/dist/cjs/object.js +634 -0
  35. package/dist/cjs/projectKeys.js +35 -0
  36. package/dist/cjs/props.js +282 -0
  37. package/dist/cjs/scaling.js +5 -169
  38. package/dist/cjs/scope.js +44 -0
  39. package/dist/cjs/sharedLibraries.js +146 -0
  40. package/dist/cjs/state.js +173 -0
  41. package/dist/cjs/string-extra.js +72 -0
  42. package/dist/cjs/string.js +183 -0
  43. package/dist/cjs/style.js +1 -3
  44. package/dist/cjs/tags.js +220 -0
  45. package/dist/cjs/triggerEvent.js +112 -0
  46. package/dist/cjs/types.js +74 -0
  47. package/dist/cjs/update.js +42 -0
  48. package/dist/esm/array.js +148 -0
  49. package/dist/esm/browser.js +14 -0
  50. package/dist/esm/cache.js +6 -0
  51. package/dist/esm/cdn.js +65 -0
  52. package/dist/esm/component.js +25 -0
  53. package/dist/esm/cookie.js +55 -0
  54. package/dist/esm/date.js +11 -0
  55. package/dist/esm/detectHeight.js +42 -0
  56. package/dist/esm/element.js +116 -0
  57. package/dist/esm/env.js +23 -0
  58. package/dist/esm/events.js +19 -0
  59. package/dist/esm/extends.js +414 -0
  60. package/dist/esm/fibonacci.js +10 -0
  61. package/dist/esm/files.js +4 -0
  62. package/dist/esm/function.js +94 -0
  63. package/dist/esm/globals.js +10 -0
  64. package/dist/esm/if.js +28 -0
  65. package/dist/esm/index.js +37 -0
  66. package/dist/esm/key.js +18 -0
  67. package/dist/esm/keys.js +181 -0
  68. package/dist/esm/load.js +151 -0
  69. package/dist/esm/log.js +14 -0
  70. package/dist/esm/metadata.js +290 -0
  71. package/dist/esm/methods.js +569 -0
  72. package/dist/esm/node.js +15 -0
  73. package/dist/esm/object.js +621 -0
  74. package/dist/esm/projectKeys.js +16 -0
  75. package/dist/esm/props.js +262 -0
  76. package/dist/esm/scaling.js +15 -0
  77. package/dist/esm/scope.js +24 -0
  78. package/dist/esm/sharedLibraries.js +126 -0
  79. package/dist/esm/state.js +163 -0
  80. package/dist/esm/string-extra.js +52 -0
  81. package/dist/esm/string.js +163 -0
  82. package/dist/esm/style.js +20 -0
  83. package/dist/esm/tags.js +200 -0
  84. package/dist/esm/triggerEvent.js +92 -0
  85. package/dist/esm/types.js +54 -0
  86. package/dist/esm/update.js +22 -0
  87. package/dist/iife/index.js +5818 -0
  88. package/element.js +148 -0
  89. package/env.js +15 -0
  90. package/events.js +19 -0
  91. package/extends.js +519 -0
  92. package/function.js +145 -0
  93. package/globals.js +8 -0
  94. package/if.js +30 -0
  95. package/index.js +41 -0
  96. package/key.js +20 -0
  97. package/keys.js +176 -0
  98. package/log.js +12 -0
  99. package/metadata.js +392 -0
  100. package/methods.js +636 -0
  101. package/node.js +37 -0
  102. package/object.js +803 -0
  103. package/package.json +38 -22
  104. package/projectKeys.js +30 -0
  105. package/props.js +327 -0
  106. package/{src/scaling.js → scaling.js} +1 -1
  107. package/scope.js +32 -0
  108. package/sharedLibraries.js +184 -0
  109. package/state.js +203 -0
  110. package/{src/index.js → string-extra.js} +11 -17
  111. package/string.js +206 -0
  112. package/tags.js +176 -0
  113. package/triggerEvent.js +102 -0
  114. package/types.js +55 -0
  115. package/update.js +27 -0
  116. package/dist/cjs/package.json +0 -4
  117. /package/{src/browser.js → browser.js} +0 -0
  118. /package/{src/date.js → date.js} +0 -0
  119. /package/{src/fibonacci.js → fibonacci.js} +0 -0
  120. /package/{src/files.js → files.js} +0 -0
  121. /package/{src/load.js → load.js} +0 -0
  122. /package/{src/style.js → style.js} +0 -0
package/object.js ADDED
@@ -0,0 +1,803 @@
1
+ 'use strict'
2
+
3
+ import { window } from './globals.js'
4
+ import {
5
+ isFunction,
6
+ isObjectLike,
7
+ isObject,
8
+ isArray,
9
+ isString,
10
+ is
11
+ } from './types.js'
12
+ import { unstackArrayOfObjects } from './array.js'
13
+ import { stringIncludesAny } from './string.js'
14
+ import { isDOMNode } from './node.js'
15
+ import { METHODS_EXL } from './keys.js'
16
+
17
+ const ENV = process.env.NODE_ENV
18
+
19
+ const _startsWithDunder = (e) =>
20
+ e.charCodeAt(0) === 95 && e.charCodeAt(1) === 95
21
+
22
+ export const exec = (param, element, state, context) => {
23
+ if (isFunction(param)) {
24
+ if (!element) return
25
+ if (typeof param.call !== 'function') return param
26
+ return param.call(
27
+ element,
28
+ element,
29
+ state || element.state,
30
+ context || element.context
31
+ )
32
+ }
33
+ // If param is a non-function value and the context has handler-resolving
34
+ // plugins (e.g. funcql), try to resolve it into a callable function.
35
+ // This enables funcql schemas as property values (text, if, style, etc.).
36
+ // Only attempt for array/object params that could be schemas — skip
37
+ // primitives and DOM nodes to avoid overhead on normal DOMQL values.
38
+ if (
39
+ param != null &&
40
+ element?.context?.plugins &&
41
+ (isArray(param) || (isObject(param) && !isDOMNode(param)))
42
+ ) {
43
+ const plugins = element.context.plugins
44
+ for (const plugin of plugins) {
45
+ if (plugin.resolveHandler) {
46
+ const resolved = plugin.resolveHandler(param, element)
47
+ if (typeof resolved === 'function') {
48
+ return exec(resolved, element, state, context)
49
+ }
50
+ }
51
+ }
52
+ }
53
+ return param
54
+ }
55
+
56
+ export const map = (obj, extention, element) => {
57
+ for (const e in extention) {
58
+ obj[e] = exec(extention[e], element)
59
+ }
60
+ }
61
+
62
+ export const merge = (element, obj, excludeFrom = []) => {
63
+ const useSet = excludeFrom instanceof Set
64
+ for (const e in obj) {
65
+ if (!Object.prototype.hasOwnProperty.call(obj, e)) continue
66
+ if (_startsWithDunder(e)) continue
67
+ if (useSet ? excludeFrom.has(e) : excludeFrom.includes(e)) continue
68
+ if (element[e] === undefined) {
69
+ element[e] = obj[e]
70
+ }
71
+ }
72
+ return element
73
+ }
74
+
75
+ export const deepMerge = (element, extend, excludeFrom = METHODS_EXL) => {
76
+ return _deepMerge(element, extend, excludeFrom, null)
77
+ }
78
+
79
+ // Internal worker that carries an ancestors stack to break cycles. The stack
80
+ // is allocated lazily on first recursion so non-cyclic merges have zero
81
+ // overhead. We track only the (element, extend) pairs in the *current
82
+ // descent* — once a pair pops off the stack we forget it, so legitimate
83
+ // reuse of shared extend objects across siblings is unaffected.
84
+ const _deepMerge = (element, extend, excludeFrom, ancestors) => {
85
+ if (element === extend) return element
86
+ if (ancestors) {
87
+ for (let i = 0; i < ancestors.length; i += 2) {
88
+ if (ancestors[i] === element && ancestors[i + 1] === extend) return element
89
+ }
90
+ }
91
+ const useSet = excludeFrom instanceof Set
92
+ for (const e in extend) {
93
+ if (!Object.prototype.hasOwnProperty.call(extend, e)) continue
94
+ if (_startsWithDunder(e)) continue
95
+ if (useSet ? excludeFrom.has(e) : excludeFrom.includes(e)) continue
96
+ const elementProp = element[e]
97
+ const extendProp = extend[e]
98
+ if (isObjectLike(elementProp) && isObjectLike(extendProp)) {
99
+ const stack = ancestors || []
100
+ stack.push(element, extend)
101
+ _deepMerge(elementProp, extendProp, excludeFrom, stack)
102
+ stack.length -= 2
103
+ } else if (elementProp === undefined) {
104
+ element[e] = extendProp
105
+ }
106
+ }
107
+ return element
108
+ }
109
+
110
+ export const clone = (obj, excludeFrom = []) => {
111
+ const useSet = excludeFrom instanceof Set
112
+ const o = {}
113
+ for (const prop in obj) {
114
+ if (!Object.prototype.hasOwnProperty.call(obj, prop)) continue
115
+ if (_startsWithDunder(prop)) continue
116
+ if (useSet ? excludeFrom.has(prop) : excludeFrom.includes(prop)) continue
117
+ o[prop] = obj[prop]
118
+ }
119
+ return o
120
+ }
121
+
122
+ /**
123
+ * Enhanced deep clone function that combines features from multiple implementations
124
+ * @param {any} obj - Object to clone
125
+ * @param {Object} options - Configuration options
126
+ * @param {string[]} options.exclude - Properties to exclude from cloning
127
+ * @param {boolean} options.cleanUndefined - Remove undefined values
128
+ * @param {boolean} options.cleanNull - Remove null values
129
+ * @param {Window} options.window - Window object for cross-frame cloning
130
+ * @param {WeakMap} options.visited - WeakMap for tracking circular references
131
+ * @param {boolean} options.handleExtends - Whether to handle 'extends' arrays specially
132
+ * @returns {any} Cloned object
133
+ */
134
+ export const deepClone = (obj, options = {}) => {
135
+ const {
136
+ exclude = [],
137
+ cleanUndefined = false,
138
+ cleanNull = false,
139
+ window: targetWindow,
140
+ visited = new WeakMap(),
141
+ handleExtends = false
142
+ } = options
143
+
144
+ const contentWindow = targetWindow || window || globalThis
145
+
146
+ // Handle non-object types and special cases
147
+ if (!isObjectLike(obj) || isDOMNode(obj)) {
148
+ return obj
149
+ }
150
+
151
+ // Handle circular references
152
+ if (visited.has(obj)) {
153
+ return visited.get(obj)
154
+ }
155
+
156
+ // Create appropriate container based on type and window context
157
+ const isArr = isArray(obj)
158
+ const clone = isArr ? [] : {}
159
+
160
+ // Store the clone to handle circular references
161
+ visited.set(obj, clone)
162
+
163
+ // Convert exclude to Set for O(1) lookups when list is non-trivial
164
+ const excludeSet =
165
+ exclude instanceof Set
166
+ ? exclude
167
+ : exclude.length > 3
168
+ ? new Set(exclude)
169
+ : null
170
+
171
+ // Clone properties
172
+ for (const key in obj) {
173
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) continue
174
+
175
+ // Skip excluded properties
176
+ if (_startsWithDunder(key) || key === '__proto__') continue
177
+ if (excludeSet ? excludeSet.has(key) : exclude.includes(key)) continue
178
+
179
+ const value = obj[key]
180
+
181
+ // Skip based on cleanup options
182
+ if (cleanUndefined && value === undefined) continue
183
+ if (cleanNull && value === null) continue
184
+
185
+ // Handle special cases
186
+ if (isDOMNode(value)) {
187
+ clone[key] = value
188
+ continue
189
+ }
190
+
191
+ // Handle 'extends' array if enabled
192
+ if (handleExtends && key === 'extends' && isArray(value)) {
193
+ clone[key] = unstackArrayOfObjects(value, exclude)
194
+ continue
195
+ }
196
+
197
+ // Handle functions in cross-frame scenario
198
+ // Keep original function references — DOMQL handles window/document
199
+ // isolation via element.context, so eval'ing into iframe scope is
200
+ // unnecessary and breaks closure-dependent functions (e.g. resolveTheme)
201
+ if (isFunction(value)) {
202
+ clone[key] = value
203
+ continue
204
+ }
205
+
206
+ // Recursively clone objects
207
+ if (isObjectLike(value)) {
208
+ clone[key] = deepClone(value, {
209
+ ...options,
210
+ visited
211
+ })
212
+ } else {
213
+ clone[key] = value
214
+ }
215
+ }
216
+
217
+ return clone
218
+ }
219
+
220
+ /**
221
+ * Stringify object
222
+ */
223
+ export const deepStringifyFunctions = (obj, stringified = {}) => {
224
+ if (obj.node || obj.__ref || obj.parent || obj.__element || obj.parse) {
225
+ ;(obj.__element || obj.parent?.__element).warn(
226
+ 'Trying to clone element or state at',
227
+ obj
228
+ )
229
+ obj = obj.parse?.()
230
+ }
231
+
232
+ for (const prop in obj) {
233
+ const objProp = obj[prop]
234
+ if (isFunction(objProp)) {
235
+ stringified[prop] = objProp.toString()
236
+ } else if (isObject(objProp)) {
237
+ stringified[prop] = {}
238
+ deepStringifyFunctions(objProp, stringified[prop])
239
+ } else if (isArray(objProp)) {
240
+ const arr = (stringified[prop] = [])
241
+ for (let i = 0; i < objProp.length; i++) {
242
+ const v = objProp[i]
243
+ if (isObject(v)) {
244
+ arr[i] = {}
245
+ deepStringifyFunctions(v, arr[i])
246
+ } else if (isFunction(v)) {
247
+ arr[i] = v.toString()
248
+ } else {
249
+ arr[i] = v
250
+ }
251
+ }
252
+ } else {
253
+ stringified[prop] = objProp
254
+ }
255
+ }
256
+ return stringified
257
+ }
258
+
259
+ const OBJ_TO_STR_SPECIAL_CHARS = new Set([
260
+ '&',
261
+ '*',
262
+ '-',
263
+ ':',
264
+ '%',
265
+ '{',
266
+ '}',
267
+ '>',
268
+ '<',
269
+ '@',
270
+ '.',
271
+ '/',
272
+ '!',
273
+ ' '
274
+ ])
275
+
276
+ export const objectToString = (obj = {}, indent = 0) => {
277
+ // Handle empty object case
278
+ if (obj === null || typeof obj !== 'object') {
279
+ return String(obj)
280
+ }
281
+
282
+ // Handle empty object case - avoid Object.keys allocation
283
+ let hasKeys = false
284
+ for (const _k in obj) {
285
+ hasKeys = true
286
+ break
287
+ } // eslint-disable-line
288
+ if (!hasKeys) return '{}'
289
+
290
+ const spaces = ' '.repeat(indent)
291
+ let str = '{\n'
292
+
293
+ for (const key in obj) {
294
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) continue
295
+ const value = obj[key]
296
+ let keyNeedsQuotes = false
297
+ for (let i = 0; i < key.length; i++) {
298
+ if (OBJ_TO_STR_SPECIAL_CHARS.has(key[i])) {
299
+ keyNeedsQuotes = true
300
+ break
301
+ }
302
+ }
303
+ const stringedKey = keyNeedsQuotes ? `'${key}'` : key
304
+ str += `${spaces} ${stringedKey}: `
305
+
306
+ if (isArray(value)) {
307
+ str += '[\n'
308
+ for (const element of value) {
309
+ if (isObjectLike(element) && element !== null) {
310
+ str += `${spaces} ${objectToString(element, indent + 2)},\n`
311
+ } else if (isString(element)) {
312
+ str += `${spaces} '${element}',\n`
313
+ } else {
314
+ str += `${spaces} ${element},\n`
315
+ }
316
+ }
317
+ str += `${spaces} ]`
318
+ } else if (isObjectLike(value)) {
319
+ str += objectToString(value, indent + 1)
320
+ } else if (isString(value)) {
321
+ str += stringIncludesAny(value, ['\n', "'"])
322
+ ? `\`${value}\``
323
+ : `'${value}'`
324
+ } else {
325
+ str += value
326
+ }
327
+
328
+ str += ',\n'
329
+ }
330
+
331
+ str += `${spaces}}`
332
+ return str
333
+ }
334
+
335
+ const FN_PATTERNS = [
336
+ /^\(\s*\{[^}]*\}\s*\)\s*=>/,
337
+ /^(\([^)]*\)|[^=]*)\s*=>/,
338
+ /^function[\s(]/,
339
+ /^async\s+/,
340
+ /^\(\s*function/,
341
+ /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=>/
342
+ ]
343
+ const RE_JSON_LIKE = /^["[{]/
344
+
345
+ export const hasFunction = (str) => {
346
+ if (!str) return false
347
+
348
+ const trimmed = str.trim().replace(/\n\s*/g, ' ').trim()
349
+
350
+ if (trimmed === '' || trimmed === '{}' || trimmed === '[]') return false
351
+
352
+ const isFn = FN_PATTERNS.some((pattern) => pattern.test(trimmed))
353
+ if (!isFn) return false
354
+
355
+ const firstChar = trimmed.charCodeAt(0)
356
+ const hasArrow = trimmed.includes('=>')
357
+ // '{' = 123, '[' = 91
358
+ if (firstChar === 123 && !hasArrow) return false // object literal
359
+ if (firstChar === 91) return false // array literal
360
+ if (RE_JSON_LIKE.test(trimmed) && !hasArrow) return false
361
+
362
+ return true
363
+ }
364
+
365
+ // Indirect eval — invokes the global eval, which works in both browser and
366
+ // Node without depending on a `window` object. Required for SSR / Brender
367
+ // passes that hit prepareContext without a real DOM.
368
+ const __globalEval = (src) => (0, eval)(src) // eslint-disable-line no-eval
369
+
370
+ export const deepDestringifyFunctions = (
371
+ obj,
372
+ destringified = {},
373
+ opts = { window: { eval: __globalEval } }
374
+ ) => {
375
+ for (const prop in obj) {
376
+ if (!Object.prototype.hasOwnProperty.call(obj, prop)) continue
377
+
378
+ const objProp = obj[prop]
379
+
380
+ if (isString(objProp)) {
381
+ if (hasFunction(objProp)) {
382
+ try {
383
+ destringified[prop] = opts.window.eval(`(${objProp})`)
384
+ } catch (e) {
385
+ if (e) destringified[prop] = objProp
386
+ }
387
+ } else {
388
+ destringified[prop] = objProp
389
+ }
390
+ } else if (isArray(objProp)) {
391
+ const arr = (destringified[prop] = [])
392
+ for (let i = 0; i < objProp.length; i++) {
393
+ const arrProp = objProp[i]
394
+ if (isString(arrProp)) {
395
+ if (hasFunction(arrProp)) {
396
+ try {
397
+ arr.push(opts.window.eval(`(${arrProp})`))
398
+ } catch (e) {
399
+ if (e) arr.push(arrProp)
400
+ }
401
+ } else {
402
+ arr.push(arrProp)
403
+ }
404
+ } else if (isObject(arrProp)) {
405
+ arr.push(deepDestringifyFunctions(arrProp))
406
+ } else {
407
+ arr.push(arrProp)
408
+ }
409
+ }
410
+ } else if (isObject(objProp)) {
411
+ destringified[prop] = deepDestringifyFunctions(
412
+ objProp,
413
+ destringified[prop]
414
+ )
415
+ } else {
416
+ destringified[prop] = objProp
417
+ }
418
+ }
419
+ return destringified
420
+ }
421
+
422
+ /**
423
+ * Destringify a globalScope object so that function strings become real functions.
424
+ * All globalScope values are made available as local variables when eval'ing each
425
+ * function, so helpers can reference constants and other helpers naturally.
426
+ */
427
+ export const destringifyGlobalScope = (gs) => {
428
+ if (!gs || typeof gs !== 'object') return gs
429
+
430
+ // First pass: collect non-function values (constants, arrays, objects)
431
+ const result = {}
432
+ const fnEntries = []
433
+ for (const key of Object.keys(gs)) {
434
+ const val = gs[key]
435
+ if (isString(val) && hasFunction(val)) {
436
+ fnEntries.push([key, val])
437
+ } else {
438
+ result[key] = val
439
+ }
440
+ }
441
+
442
+ // Second pass: eval functions in a closure with all values in scope
443
+ for (const [key, fnStr] of fnEntries) {
444
+ try {
445
+ // Build a closure that exposes all current globalScope values
446
+ const varDecls = Object.keys(result)
447
+ .map((k) => `var ${k} = __gs__[${JSON.stringify(k)}];`)
448
+ .join('\n')
449
+ result[key] = window.eval(
450
+ `(function(__gs__) { ${varDecls}\n return (${fnStr}); })`
451
+ )(result)
452
+ } catch (e) {
453
+ // Fallback: try plain eval
454
+ try {
455
+ result[key] = window.eval(`(${fnStr})`)
456
+ } catch (_) {
457
+ result[key] = fnStr
458
+ }
459
+ }
460
+ }
461
+
462
+ return result
463
+ }
464
+
465
+ export const stringToObject = (str, opts = { verbose: true }) => {
466
+ try {
467
+ return str ? window.eval('(' + str + ')') : {} // eslint-disable-line
468
+ } catch (e) {
469
+ if (opts.verbose) console.warn(e)
470
+ }
471
+ }
472
+
473
+ export const hasOwnProperty = (o, ...args) =>
474
+ Object.prototype.hasOwnProperty.call(o, ...args)
475
+
476
+ export const isEmpty = (o) => {
477
+ for (const _ in o) return false // eslint-disable-line
478
+ return true
479
+ }
480
+
481
+ export const isEmptyObject = (o) => isObject(o) && isEmpty(o)
482
+
483
+ export const makeObjectWithoutPrototype = () => Object.create(null)
484
+
485
+ /**
486
+ * Overwrites object properties with another
487
+ */
488
+ export const overwrite = (element, params, opts = {}) => {
489
+ const excl = opts.exclude || []
490
+ const allowDunder = opts.preventUnderscore
491
+
492
+ for (const e in params) {
493
+ if (excl.includes(e) || (!allowDunder && _startsWithDunder(e))) continue
494
+ if (params[e] !== undefined) {
495
+ element[e] = params[e]
496
+ }
497
+ }
498
+
499
+ return element
500
+ }
501
+
502
+ export const overwriteShallow = (obj, params, excludeFrom = []) => {
503
+ const useSet = excludeFrom instanceof Set
504
+ for (const e in params) {
505
+ if (_startsWithDunder(e)) continue
506
+ if (useSet ? excludeFrom.has(e) : excludeFrom.includes(e)) continue
507
+ obj[e] = params[e]
508
+ }
509
+ return obj
510
+ }
511
+
512
+ /**
513
+ * Overwrites DEEPLY object properties with another
514
+ */
515
+ export const overwriteDeep = (
516
+ obj,
517
+ params,
518
+ opts = {},
519
+ visited = new WeakMap()
520
+ ) => {
521
+ if (
522
+ !isObjectLike(obj) ||
523
+ !isObjectLike(params) ||
524
+ isDOMNode(obj) ||
525
+ isDOMNode(params)
526
+ ) {
527
+ return params
528
+ }
529
+
530
+ if (visited.has(obj)) return visited.get(obj)
531
+ visited.set(obj, obj)
532
+
533
+ const excl = opts.exclude
534
+ const exclSet = excl ? (excl instanceof Set ? excl : new Set(excl)) : null
535
+ const forcedExclude = !opts.preventForce
536
+
537
+ for (const e in params) {
538
+ if (!Object.prototype.hasOwnProperty.call(params, e)) continue
539
+ if ((exclSet && exclSet.has(e)) || (forcedExclude && _startsWithDunder(e)))
540
+ continue
541
+
542
+ const objProp = obj[e]
543
+ const paramsProp = params[e]
544
+
545
+ if (isDOMNode(paramsProp)) {
546
+ obj[e] = paramsProp
547
+ } else if (isObjectLike(objProp) && isObjectLike(paramsProp)) {
548
+ obj[e] = overwriteDeep(objProp, paramsProp, opts, visited)
549
+ } else if (paramsProp !== undefined) {
550
+ obj[e] = paramsProp
551
+ }
552
+ }
553
+
554
+ return obj
555
+ }
556
+
557
+ /**
558
+ * Recursively compares two values to determine if they are deeply equal.
559
+ */
560
+ export const isEqualDeep = (param, element, visited = new Set()) => {
561
+ if (
562
+ typeof param !== 'object' ||
563
+ typeof element !== 'object' ||
564
+ param === null ||
565
+ element === null
566
+ ) {
567
+ return param === element
568
+ }
569
+
570
+ if (visited.has(param) || visited.has(element)) {
571
+ return true
572
+ }
573
+
574
+ visited.add(param)
575
+ visited.add(element)
576
+
577
+ const keysParam = Object.keys(param)
578
+ const keysElement = Object.keys(element)
579
+
580
+ if (keysParam.length !== keysElement.length) {
581
+ return false
582
+ }
583
+
584
+ for (let i = 0; i < keysParam.length; i++) {
585
+ const key = keysParam[i]
586
+ if (!Object.prototype.hasOwnProperty.call(element, key)) {
587
+ return false
588
+ }
589
+ if (!isEqualDeep(param[key], element[key], visited)) {
590
+ return false
591
+ }
592
+ }
593
+
594
+ return true
595
+ }
596
+
597
+ const DEEP_CONTAINS_IGNORED = new Set(['node', '__ref'])
598
+
599
+ export const deepContains = (
600
+ obj1,
601
+ obj2,
602
+ ignoredKeys = DEEP_CONTAINS_IGNORED
603
+ ) => {
604
+ if (obj1 === obj2) return true
605
+ if (!isObjectLike(obj1) || !isObjectLike(obj2)) return obj1 === obj2
606
+ if (isDOMNode(obj1) || isDOMNode(obj2)) return obj1 === obj2
607
+
608
+ const ignored =
609
+ ignoredKeys instanceof Set ? ignoredKeys : new Set(ignoredKeys)
610
+ const visited = new WeakSet()
611
+
612
+ function checkContains(target, source) {
613
+ if (visited.has(source)) return true
614
+ visited.add(source)
615
+
616
+ for (const key in source) {
617
+ if (!Object.prototype.hasOwnProperty.call(source, key)) continue
618
+ if (ignored.has(key)) continue
619
+ if (!Object.prototype.hasOwnProperty.call(target, key)) return false
620
+
621
+ const sourceValue = source[key]
622
+ const targetValue = target[key]
623
+
624
+ if (isDOMNode(sourceValue) || isDOMNode(targetValue)) {
625
+ if (sourceValue !== targetValue) return false
626
+ } else if (isObjectLike(sourceValue) && isObjectLike(targetValue)) {
627
+ if (!checkContains(targetValue, sourceValue)) return false
628
+ } else if (sourceValue !== targetValue) {
629
+ return false
630
+ }
631
+ }
632
+
633
+ return true
634
+ }
635
+
636
+ return checkContains(obj1, obj2)
637
+ }
638
+
639
+ export const removeFromObject = (obj, props) => {
640
+ if (props === undefined || props === null) return obj
641
+ if (is(props)('string', 'number')) {
642
+ delete obj[props]
643
+ } else if (isArray(props)) {
644
+ for (let i = 0; i < props.length; i++) delete obj[props[i]]
645
+ } else {
646
+ throw new Error(
647
+ 'Invalid input: props must be a string or an array of strings'
648
+ )
649
+ }
650
+ return obj
651
+ }
652
+
653
+ export const createObjectWithoutPrototype = (obj) => {
654
+ if (obj === null || typeof obj !== 'object') {
655
+ return obj
656
+ }
657
+
658
+ const newObj = Object.create(null)
659
+
660
+ for (const key in obj) {
661
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
662
+ newObj[key] = createObjectWithoutPrototype(obj[key])
663
+ }
664
+ }
665
+
666
+ return newObj
667
+ }
668
+
669
+ export const createNestedObject = (arr, lastValue) => {
670
+ if (arr.length === 0) return lastValue
671
+
672
+ const nestedObject = {}
673
+ let current = nestedObject
674
+
675
+ for (let i = 0; i < arr.length; i++) {
676
+ if (i === arr.length - 1 && lastValue) {
677
+ current[arr[i]] = lastValue
678
+ } else {
679
+ current[arr[i]] = {}
680
+ current = current[arr[i]]
681
+ }
682
+ }
683
+
684
+ return nestedObject
685
+ }
686
+
687
+ export const removeNestedKeyByPath = (obj, path) => {
688
+ if (!Array.isArray(path)) {
689
+ throw new Error('Path must be an array.')
690
+ }
691
+
692
+ let current = obj
693
+
694
+ for (let i = 0; i < path.length - 1; i++) {
695
+ if (current[path[i]] === undefined) return
696
+ current = current[path[i]]
697
+ }
698
+
699
+ const lastKey = path[path.length - 1]
700
+ if (current && Object.prototype.hasOwnProperty.call(current, lastKey)) {
701
+ delete current[lastKey]
702
+ }
703
+ }
704
+
705
+ export const setInObjectByPath = (obj, path, value) => {
706
+ if (!Array.isArray(path)) {
707
+ throw new Error('Path must be an array.')
708
+ }
709
+
710
+ let current = obj
711
+
712
+ for (let i = 0; i < path.length - 1; i++) {
713
+ if (!current[path[i]] || typeof current[path[i]] !== 'object') {
714
+ current[path[i]] = {}
715
+ }
716
+ current = current[path[i]]
717
+ }
718
+
719
+ current[path[path.length - 1]] = value
720
+
721
+ return obj
722
+ }
723
+
724
+ export const getInObjectByPath = (obj, path) => {
725
+ if (!Array.isArray(path)) {
726
+ throw new Error('Path must be an array.')
727
+ }
728
+
729
+ let current = obj
730
+
731
+ for (let i = 0; i < path.length; i++) {
732
+ if (current === undefined || current === null) {
733
+ return undefined
734
+ }
735
+ current = current[path[i]]
736
+ }
737
+
738
+ return current
739
+ }
740
+
741
+ export const detectInfiniteLoop = (arr) => {
742
+ const maxRepeats = 10
743
+ let pattern = []
744
+ let repeatCount = 0
745
+
746
+ for (let i = 0; i < arr.length; i++) {
747
+ if (pattern.length < 2) {
748
+ pattern.push(arr[i])
749
+ } else {
750
+ if (arr[i] === pattern[i % 2]) {
751
+ repeatCount++
752
+ } else {
753
+ pattern = [arr[i - 1], arr[i]]
754
+ repeatCount = 1
755
+ }
756
+
757
+ if (repeatCount >= maxRepeats * 2) {
758
+ if (ENV === 'test' || ENV === 'development') {
759
+ console.warn(
760
+ 'Warning: Potential infinite loop detected due to repeated sequence:',
761
+ pattern
762
+ )
763
+ }
764
+ return true
765
+ }
766
+ }
767
+ }
768
+ }
769
+
770
+ export const isCyclic = (obj) => {
771
+ const seen = new WeakSet()
772
+
773
+ function detect(obj) {
774
+ if (obj && typeof obj === 'object') {
775
+ if (seen.has(obj)) return true
776
+ seen.add(obj)
777
+ for (const key in obj) {
778
+ if (
779
+ Object.prototype.hasOwnProperty.call(obj, key) &&
780
+ detect(obj[key])
781
+ ) {
782
+ console.log(obj, 'cycle at ' + key)
783
+ return true
784
+ }
785
+ }
786
+ }
787
+ return false
788
+ }
789
+
790
+ return detect(obj)
791
+ }
792
+
793
+ export const excludeKeysFromObject = (obj, excludedKeys) => {
794
+ const excluded =
795
+ excludedKeys instanceof Set ? excludedKeys : new Set(excludedKeys)
796
+ const result = {}
797
+ for (const key in obj) {
798
+ if (Object.prototype.hasOwnProperty.call(obj, key) && !excluded.has(key)) {
799
+ result[key] = obj[key]
800
+ }
801
+ }
802
+ return result
803
+ }