@pyreon/ui-core 0.11.4 → 0.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -13
- package/lib/index.d.ts +19 -12
- package/lib/index.js +29 -19
- package/package.json +24 -24
- package/src/PyreonUI.tsx +28 -34
- package/src/__tests__/PyreonUI.test.tsx +40 -37
- package/src/__tests__/compose.test.ts +8 -8
- package/src/__tests__/config.test.ts +37 -37
- package/src/__tests__/context.test.tsx +28 -27
- package/src/__tests__/hoistNonReactStatics.test.tsx +44 -44
- package/src/__tests__/isEmpty.test.ts +15 -15
- package/src/__tests__/isEqual.test.ts +28 -28
- package/src/__tests__/render.test.tsx +28 -28
- package/src/__tests__/useStableValue.test.ts +23 -23
- package/src/__tests__/utils.test.ts +149 -149
- package/src/config.ts +7 -7
- package/src/context.tsx +43 -8
- package/src/hoistNonReactStatics.ts +1 -1
- package/src/html/htmlTags.ts +142 -142
- package/src/html/index.ts +3 -3
- package/src/index.ts +19 -17
- package/src/isEmpty.ts +1 -1
- package/src/isEqual.ts +1 -1
- package/src/render.tsx +6 -6
- package/src/useStableValue.ts +2 -2
- package/src/utils.ts +3 -3
package/src/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { StyledFunction } from
|
|
2
|
-
import { css, keyframes, styled } from
|
|
3
|
-
import type { HTMLTags } from
|
|
1
|
+
import type { StyledFunction } from '@pyreon/styler'
|
|
2
|
+
import { css, keyframes, styled } from '@pyreon/styler'
|
|
3
|
+
import type { HTMLTags } from './html'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Describes the shape of the CSS-in-JS engine.
|
|
@@ -19,7 +19,7 @@ interface PlatformConfig {
|
|
|
19
19
|
createMediaQueries?: (props: {
|
|
20
20
|
breakpoints: Record<string, number>
|
|
21
21
|
rootSize: number
|
|
22
|
-
css: CSSEngineConnector[
|
|
22
|
+
css: CSSEngineConnector['css']
|
|
23
23
|
}) => Record<string, (...args: any[]) => any>
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -36,9 +36,9 @@ class Configuration {
|
|
|
36
36
|
css = css
|
|
37
37
|
styled: StyledFunction = styled
|
|
38
38
|
keyframes = keyframes
|
|
39
|
-
component: string | HTMLTags =
|
|
40
|
-
textComponent: string | HTMLTags =
|
|
41
|
-
createMediaQueries: PlatformConfig[
|
|
39
|
+
component: string | HTMLTags = 'div'
|
|
40
|
+
textComponent: string | HTMLTags = 'span'
|
|
41
|
+
createMediaQueries: PlatformConfig['createMediaQueries'] = undefined
|
|
42
42
|
|
|
43
43
|
init = (props: InitConfig) => {
|
|
44
44
|
if (props.css) this.css = props.css
|
package/src/context.tsx
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
import type { VNodeChild } from
|
|
2
|
-
import {
|
|
3
|
-
import isEmpty from
|
|
4
|
-
import type { Breakpoints } from
|
|
1
|
+
import type { VNodeChild } from '@pyreon/core'
|
|
2
|
+
import { createReactiveContext, provide } from '@pyreon/core'
|
|
3
|
+
import isEmpty from './isEmpty'
|
|
4
|
+
import type { Breakpoints } from './types'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* Carries the theme object plus any extra provider props.
|
|
7
|
+
* Core context value shared across all @pyreon UI packages.
|
|
9
8
|
*/
|
|
10
|
-
|
|
9
|
+
export interface CoreContextValue {
|
|
10
|
+
theme: Record<string, unknown>
|
|
11
|
+
mode: 'light' | 'dark'
|
|
12
|
+
isDark: boolean
|
|
13
|
+
isLight: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Internal reactive context shared across all @pyreon packages.
|
|
18
|
+
* Carries the theme object, mode, and derived dark/light flags.
|
|
19
|
+
*
|
|
20
|
+
* ReactiveContext means useContext() returns `() => CoreContextValue`.
|
|
21
|
+
*/
|
|
22
|
+
const context = createReactiveContext<CoreContextValue>({
|
|
23
|
+
theme: {},
|
|
24
|
+
mode: 'light',
|
|
25
|
+
isDark: false,
|
|
26
|
+
isLight: true,
|
|
27
|
+
})
|
|
11
28
|
|
|
12
29
|
type Theme = Partial<
|
|
13
30
|
{
|
|
@@ -24,13 +41,31 @@ type ProviderType = Partial<
|
|
|
24
41
|
>
|
|
25
42
|
|
|
26
43
|
/**
|
|
44
|
+
* @internal Low-level provider — use `PyreonUI` from `@pyreon/ui-core` instead.
|
|
45
|
+
*
|
|
27
46
|
* Provider that feeds the internal Pyreon context with the theme.
|
|
28
47
|
* When no theme is supplied, renders children directly.
|
|
48
|
+
*
|
|
49
|
+
* @deprecated Prefer `<PyreonUI theme={theme}>` which handles all context layers.
|
|
29
50
|
*/
|
|
51
|
+
const __DEV__ = typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production'
|
|
52
|
+
|
|
30
53
|
function Provider({ theme, children, ...props }: ProviderType): VNodeChild {
|
|
54
|
+
if (__DEV__) {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.warn(
|
|
57
|
+
'[Pyreon] CoreProvider is internal. Use <PyreonUI theme={theme}> instead — it handles all context layers (styler, core, mode) in one component.',
|
|
58
|
+
)
|
|
59
|
+
}
|
|
31
60
|
if (isEmpty(theme) || !theme) return children ?? null
|
|
32
61
|
|
|
33
|
-
provide(context,
|
|
62
|
+
provide(context, () => ({
|
|
63
|
+
theme: theme as Record<string, unknown>,
|
|
64
|
+
mode: (props.mode as 'light' | 'dark') ?? 'light',
|
|
65
|
+
isDark: props.isDark as boolean ?? false,
|
|
66
|
+
isLight: props.isLight as boolean ?? true,
|
|
67
|
+
...props,
|
|
68
|
+
}))
|
|
34
69
|
|
|
35
70
|
return children ?? null
|
|
36
71
|
}
|
|
@@ -25,7 +25,7 @@ const hoistNonReactStatics = <T, S>(
|
|
|
25
25
|
source: S,
|
|
26
26
|
excludeList?: Record<string, true>,
|
|
27
27
|
): T => {
|
|
28
|
-
if (typeof source ===
|
|
28
|
+
if (typeof source === 'string') return target
|
|
29
29
|
|
|
30
30
|
const proto = Object.getPrototypeOf(source)
|
|
31
31
|
if (proto && proto !== Object.prototype) {
|
package/src/html/htmlTags.ts
CHANGED
|
@@ -1,149 +1,149 @@
|
|
|
1
1
|
const HTML_TAGS = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
2
|
+
'a',
|
|
3
|
+
'abbr',
|
|
4
|
+
'address',
|
|
5
|
+
'area',
|
|
6
|
+
'article',
|
|
7
|
+
'aside',
|
|
8
|
+
'audio',
|
|
9
|
+
'b',
|
|
10
|
+
'bdi',
|
|
11
|
+
'bdo',
|
|
12
|
+
'big',
|
|
13
|
+
'blockquote',
|
|
14
|
+
'body',
|
|
15
|
+
'br',
|
|
16
|
+
'button',
|
|
17
|
+
'canvas',
|
|
18
|
+
'caption',
|
|
19
|
+
'cite',
|
|
20
|
+
'code',
|
|
21
|
+
'col',
|
|
22
|
+
'colgroup',
|
|
23
|
+
'data',
|
|
24
|
+
'datalist',
|
|
25
|
+
'dd',
|
|
26
|
+
'del',
|
|
27
|
+
'details',
|
|
28
|
+
'dfn',
|
|
29
|
+
'dialog',
|
|
30
|
+
'div',
|
|
31
|
+
'dl',
|
|
32
|
+
'dt',
|
|
33
|
+
'em',
|
|
34
|
+
'embed',
|
|
35
|
+
'fieldset',
|
|
36
|
+
'figcaption',
|
|
37
|
+
'figure',
|
|
38
|
+
'footer',
|
|
39
|
+
'form',
|
|
40
|
+
'h1',
|
|
41
|
+
'h2',
|
|
42
|
+
'h3',
|
|
43
|
+
'h4',
|
|
44
|
+
'h5',
|
|
45
|
+
'h6',
|
|
46
|
+
'header',
|
|
47
|
+
'hr',
|
|
48
|
+
'html',
|
|
49
|
+
'i',
|
|
50
|
+
'iframe',
|
|
51
|
+
'img',
|
|
52
|
+
'input',
|
|
53
|
+
'ins',
|
|
54
|
+
'kbd',
|
|
55
|
+
'label',
|
|
56
|
+
'legend',
|
|
57
|
+
'li',
|
|
58
|
+
'main',
|
|
59
|
+
'map',
|
|
60
|
+
'mark',
|
|
61
|
+
'meter',
|
|
62
|
+
'nav',
|
|
63
|
+
'object',
|
|
64
|
+
'ol',
|
|
65
|
+
'optgroup',
|
|
66
|
+
'option',
|
|
67
|
+
'output',
|
|
68
|
+
'p',
|
|
69
|
+
'picture',
|
|
70
|
+
'pre',
|
|
71
|
+
'progress',
|
|
72
|
+
'q',
|
|
73
|
+
'rp',
|
|
74
|
+
'rt',
|
|
75
|
+
'ruby',
|
|
76
|
+
's',
|
|
77
|
+
'samp',
|
|
78
|
+
'section',
|
|
79
|
+
'select',
|
|
80
|
+
'small',
|
|
81
|
+
'source',
|
|
82
|
+
'span',
|
|
83
|
+
'strong',
|
|
84
|
+
'sub',
|
|
85
|
+
'summary',
|
|
86
|
+
'sup',
|
|
87
|
+
'svg',
|
|
88
|
+
'table',
|
|
89
|
+
'tbody',
|
|
90
|
+
'td',
|
|
91
|
+
'template',
|
|
92
|
+
'textarea',
|
|
93
|
+
'tfoot',
|
|
94
|
+
'th',
|
|
95
|
+
'thead',
|
|
96
|
+
'time',
|
|
97
|
+
'tr',
|
|
98
|
+
'track',
|
|
99
|
+
'u',
|
|
100
|
+
'ul',
|
|
101
|
+
'var',
|
|
102
|
+
'video',
|
|
103
|
+
'wbr',
|
|
104
104
|
] as const
|
|
105
105
|
|
|
106
106
|
const HTML_TEXT_TAGS = [
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
107
|
+
'abbr',
|
|
108
|
+
'b',
|
|
109
|
+
'bdi',
|
|
110
|
+
'bdo',
|
|
111
|
+
'big',
|
|
112
|
+
'blockquote',
|
|
113
|
+
'cite',
|
|
114
|
+
'code',
|
|
115
|
+
'del',
|
|
116
|
+
'div',
|
|
117
|
+
'dl',
|
|
118
|
+
'dt',
|
|
119
|
+
'em',
|
|
120
|
+
'figcaption',
|
|
121
|
+
'h1',
|
|
122
|
+
'h2',
|
|
123
|
+
'h3',
|
|
124
|
+
'h4',
|
|
125
|
+
'h5',
|
|
126
|
+
'h6',
|
|
127
|
+
'i',
|
|
128
|
+
'ins',
|
|
129
|
+
'kbd',
|
|
130
|
+
'label',
|
|
131
|
+
'legend',
|
|
132
|
+
'li',
|
|
133
|
+
'p',
|
|
134
|
+
'pre',
|
|
135
|
+
'q',
|
|
136
|
+
'rp',
|
|
137
|
+
'rt',
|
|
138
|
+
's',
|
|
139
|
+
'small',
|
|
140
|
+
'span',
|
|
141
|
+
'strong',
|
|
142
|
+
'sub',
|
|
143
|
+
'summary',
|
|
144
|
+
'sup',
|
|
145
|
+
'time',
|
|
146
|
+
'u',
|
|
147
147
|
] as const
|
|
148
148
|
|
|
149
149
|
export type HTMLTags = (typeof HTML_TAGS)[number]
|
package/src/html/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { HTMLElementAttrs } from
|
|
2
|
-
import type { HTMLTags, HTMLTextTags } from
|
|
3
|
-
import { HTML_TAGS, HTML_TEXT_TAGS } from
|
|
1
|
+
import type { HTMLElementAttrs } from './htmlElementAttrs'
|
|
2
|
+
import type { HTMLTags, HTMLTextTags } from './htmlTags'
|
|
3
|
+
import { HTML_TAGS, HTML_TEXT_TAGS } from './htmlTags'
|
|
4
4
|
|
|
5
5
|
type HTMLTagAttrsByTag<T extends HTMLTags> = T extends HTMLTags
|
|
6
6
|
? HTMLElementAttrs[T]
|
package/src/index.ts
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
import compose from
|
|
2
|
-
import config, { init } from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
1
|
+
import compose from './compose'
|
|
2
|
+
import config, { init } from './config'
|
|
3
|
+
import type { CoreContextValue } from './context'
|
|
4
|
+
import Provider, { context } from './context'
|
|
5
|
+
import hoistNonReactStatics from './hoistNonReactStatics'
|
|
6
|
+
import type { HTMLElementAttrs, HTMLTagAttrsByTag, HTMLTags, HTMLTextTags } from './html'
|
|
7
|
+
import { HTML_TAGS, HTML_TEXT_TAGS } from './html'
|
|
8
|
+
import type { IsEmpty } from './isEmpty'
|
|
9
|
+
import isEmpty from './isEmpty'
|
|
10
|
+
import isEqual from './isEqual'
|
|
11
|
+
import type { PyreonUIProps, ThemeMode, ThemeModeInput } from './PyreonUI'
|
|
12
|
+
import { PyreonUI, useMode } from './PyreonUI'
|
|
13
|
+
import type { Render } from './render'
|
|
14
|
+
import render from './render'
|
|
15
|
+
import type { BreakpointKeys, Breakpoints } from './types'
|
|
16
|
+
import useStableValue from './useStableValue'
|
|
17
|
+
import { get, merge, omit, pick, set, throttle } from './utils'
|
|
17
18
|
|
|
18
|
-
export type { CSSEngineConnector } from
|
|
19
|
+
export type { CSSEngineConnector } from './config'
|
|
19
20
|
|
|
20
21
|
export type {
|
|
21
22
|
BreakpointKeys,
|
|
22
23
|
Breakpoints,
|
|
24
|
+
CoreContextValue,
|
|
23
25
|
HTMLElementAttrs,
|
|
24
26
|
HTMLTagAttrsByTag,
|
|
25
27
|
HTMLTags,
|
package/src/isEmpty.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type IsEmpty = <T extends Record<number | string, any> | any[] | null | u
|
|
|
12
12
|
|
|
13
13
|
const isEmpty = (<T extends Record<number | string, any> | any[] | null | undefined>(param: T) => {
|
|
14
14
|
if (!param) return true
|
|
15
|
-
if (typeof param !==
|
|
15
|
+
if (typeof param !== 'object') return true
|
|
16
16
|
if (Array.isArray(param)) return param.length === 0
|
|
17
17
|
return Object.keys(param).length === 0
|
|
18
18
|
}) as IsEmpty
|
package/src/isEqual.ts
CHANGED
|
@@ -18,7 +18,7 @@ const isObjectEqual = (a: Record<string, unknown>, b: Record<string, unknown>):
|
|
|
18
18
|
|
|
19
19
|
const isEqual = (a: unknown, b: unknown): boolean => {
|
|
20
20
|
if (Object.is(a, b)) return true
|
|
21
|
-
if (typeof a !== typeof b || a == null || b == null || typeof a !==
|
|
21
|
+
if (typeof a !== typeof b || a == null || b == null || typeof a !== 'object') return false
|
|
22
22
|
if (Array.isArray(a)) return Array.isArray(b) && isArrayEqual(a, b)
|
|
23
23
|
if (Array.isArray(b)) return false
|
|
24
24
|
return isObjectEqual(a as Record<string, unknown>, b as Record<string, unknown>)
|
package/src/render.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ComponentFn, Props, VNodeChild } from
|
|
2
|
-
import { h } from
|
|
1
|
+
import type { ComponentFn, Props, VNodeChild } from '@pyreon/core'
|
|
2
|
+
import { h } from '@pyreon/core'
|
|
3
3
|
|
|
4
4
|
type RenderProps<T extends Record<string, unknown> | undefined> = (props: Partial<T>) => VNodeChild
|
|
5
5
|
|
|
@@ -21,7 +21,7 @@ const render: Render = (content, attachProps) => {
|
|
|
21
21
|
if (!content) return null
|
|
22
22
|
|
|
23
23
|
const t = typeof content
|
|
24
|
-
if (t ===
|
|
24
|
+
if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint') {
|
|
25
25
|
return content as VNodeChild
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -29,10 +29,10 @@ const render: Render = (content, attachProps) => {
|
|
|
29
29
|
return content as VNodeChild
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
if (typeof content ===
|
|
32
|
+
if (typeof content === 'function') {
|
|
33
33
|
// Extract key from props — it's a VNode concept, not a component prop.
|
|
34
34
|
// Passing key inside props causes JSX runtime warnings.
|
|
35
|
-
if (attachProps &&
|
|
35
|
+
if (attachProps && 'key' in attachProps) {
|
|
36
36
|
const { key, ...rest } = attachProps
|
|
37
37
|
return h(content as string | ComponentFn, rest as Props)
|
|
38
38
|
}
|
|
@@ -40,7 +40,7 @@ const render: Render = (content, attachProps) => {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// VNode object — return directly
|
|
43
|
-
if (typeof content ===
|
|
43
|
+
if (typeof content === 'object') {
|
|
44
44
|
return content as VNodeChild
|
|
45
45
|
}
|
|
46
46
|
|
package/src/useStableValue.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -39,7 +39,7 @@ const parsePath = (path: string | string[]): string[] => {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const isUnsafeKey = (key: string): boolean =>
|
|
42
|
-
key ===
|
|
42
|
+
key === '__proto__' || key === 'prototype' || key === 'constructor'
|
|
43
43
|
|
|
44
44
|
export const get = (obj: any, path: string | string[], defaultValue?: any): any => {
|
|
45
45
|
const keys = parsePath(path)
|
|
@@ -132,7 +132,7 @@ export const throttle = <T extends (...args: any[]) => any>(
|
|
|
132
132
|
|
|
133
133
|
const isPlainObject = (value: unknown): value is Record<string, any> =>
|
|
134
134
|
value !== null &&
|
|
135
|
-
typeof value ===
|
|
135
|
+
typeof value === 'object' &&
|
|
136
136
|
!Array.isArray(value) &&
|
|
137
137
|
Object.getPrototypeOf(value) === Object.prototype
|
|
138
138
|
|
|
@@ -143,7 +143,7 @@ export const merge = <T extends Record<string, any>>(
|
|
|
143
143
|
for (const source of sources) {
|
|
144
144
|
if (source == null) continue
|
|
145
145
|
for (const key of Object.keys(source)) {
|
|
146
|
-
if (key ===
|
|
146
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue
|
|
147
147
|
const targetVal = (target as Record<string, unknown>)[key]
|
|
148
148
|
const sourceVal = source[key]
|
|
149
149
|
if (isPlainObject(targetVal) && isPlainObject(sourceVal)) {
|