@pyreon/ui-core 0.11.1 → 0.11.2
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/package.json +8 -7
- package/src/PyreonUI.tsx +138 -0
- package/src/__tests__/PyreonUI.test.tsx +81 -0
- package/src/__tests__/compose.test.ts +32 -0
- package/src/__tests__/config.test.ts +102 -0
- package/src/__tests__/context.test.tsx +70 -0
- package/src/__tests__/hoistNonReactStatics.test.tsx +166 -0
- package/src/__tests__/isEmpty.test.ts +53 -0
- package/src/__tests__/isEqual.test.ts +114 -0
- package/src/__tests__/render.test.tsx +72 -0
- package/src/__tests__/useStableValue.test.ts +113 -0
- package/src/__tests__/utils.test.ts +537 -0
- package/src/compose.ts +11 -0
- package/src/config.ts +57 -0
- package/src/context.tsx +40 -0
- package/src/hoistNonReactStatics.ts +59 -0
- package/src/html/htmlElementAttrs.ts +106 -0
- package/src/html/htmlTags.ts +151 -0
- package/src/html/index.ts +11 -0
- package/src/index.ts +55 -0
- package/src/isEmpty.ts +20 -0
- package/src/isEqual.ts +27 -0
- package/src/render.tsx +44 -0
- package/src/types.ts +5 -0
- package/src/useStableValue.ts +21 -0
- package/src/utils.ts +157 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
type Base = HTMLElement
|
|
2
|
+
|
|
3
|
+
export interface HTMLElementAttrs {
|
|
4
|
+
a: HTMLAnchorElement
|
|
5
|
+
abbr: Base
|
|
6
|
+
address: Base
|
|
7
|
+
area: HTMLAreaElement
|
|
8
|
+
article: Base
|
|
9
|
+
aside: Base
|
|
10
|
+
audio: HTMLAudioElement
|
|
11
|
+
b: Base
|
|
12
|
+
bdi: Base
|
|
13
|
+
bdo: Base
|
|
14
|
+
big: Base
|
|
15
|
+
blockquote: HTMLQuoteElement
|
|
16
|
+
body: HTMLBodyElement
|
|
17
|
+
br: HTMLBRElement
|
|
18
|
+
button: HTMLButtonElement
|
|
19
|
+
canvas: HTMLCanvasElement
|
|
20
|
+
caption: Base
|
|
21
|
+
cite: HTMLQuoteElement
|
|
22
|
+
code: Base
|
|
23
|
+
col: HTMLTableColElement
|
|
24
|
+
colgroup: HTMLTableColElement
|
|
25
|
+
data: HTMLDataElement
|
|
26
|
+
datalist: HTMLDataListElement
|
|
27
|
+
dd: Base
|
|
28
|
+
del: HTMLModElement
|
|
29
|
+
details: HTMLDetailsElement
|
|
30
|
+
dfn: Base
|
|
31
|
+
dialog: HTMLDialogElement
|
|
32
|
+
div: HTMLDivElement
|
|
33
|
+
dl: HTMLDListElement
|
|
34
|
+
dt: Base
|
|
35
|
+
em: Base
|
|
36
|
+
embed: HTMLEmbedElement
|
|
37
|
+
fieldset: HTMLFieldSetElement
|
|
38
|
+
figcaption: Base
|
|
39
|
+
figure: Base
|
|
40
|
+
footer: Base
|
|
41
|
+
form: HTMLFormElement
|
|
42
|
+
h1: HTMLHeadingElement
|
|
43
|
+
h2: HTMLHeadingElement
|
|
44
|
+
h3: HTMLHeadingElement
|
|
45
|
+
h4: HTMLHeadingElement
|
|
46
|
+
h5: HTMLHeadingElement
|
|
47
|
+
h6: HTMLHeadingElement
|
|
48
|
+
header: Base
|
|
49
|
+
hr: HTMLHRElement
|
|
50
|
+
html: HTMLHtmlElement
|
|
51
|
+
i: Base
|
|
52
|
+
iframe: HTMLIFrameElement
|
|
53
|
+
img: HTMLImageElement
|
|
54
|
+
input: HTMLInputElement
|
|
55
|
+
ins: HTMLModElement
|
|
56
|
+
kbd: Base
|
|
57
|
+
label: HTMLLabelElement
|
|
58
|
+
legend: HTMLLegendElement
|
|
59
|
+
li: HTMLLIElement
|
|
60
|
+
main: Base
|
|
61
|
+
map: HTMLMapElement
|
|
62
|
+
mark: Base
|
|
63
|
+
meter: HTMLMeterElement
|
|
64
|
+
nav: Base
|
|
65
|
+
object: HTMLObjectElement
|
|
66
|
+
ol: HTMLOListElement
|
|
67
|
+
optgroup: HTMLOptGroupElement
|
|
68
|
+
option: HTMLOptionElement
|
|
69
|
+
output: HTMLOutputElement
|
|
70
|
+
p: HTMLParagraphElement
|
|
71
|
+
picture: Base
|
|
72
|
+
pre: HTMLPreElement
|
|
73
|
+
progress: HTMLProgressElement
|
|
74
|
+
q: HTMLQuoteElement
|
|
75
|
+
rp: Base
|
|
76
|
+
rt: Base
|
|
77
|
+
ruby: Base
|
|
78
|
+
s: Base
|
|
79
|
+
samp: Base
|
|
80
|
+
section: Base
|
|
81
|
+
select: HTMLSelectElement
|
|
82
|
+
small: Base
|
|
83
|
+
source: HTMLSourceElement
|
|
84
|
+
span: HTMLSpanElement
|
|
85
|
+
strong: Base
|
|
86
|
+
sub: Base
|
|
87
|
+
summary: Base
|
|
88
|
+
sup: Base
|
|
89
|
+
svg: SVGSVGElement
|
|
90
|
+
table: HTMLTableElement
|
|
91
|
+
tbody: HTMLTableSectionElement
|
|
92
|
+
td: HTMLTableCellElement
|
|
93
|
+
template: HTMLTemplateElement
|
|
94
|
+
textarea: HTMLTextAreaElement
|
|
95
|
+
tfoot: HTMLTableSectionElement
|
|
96
|
+
th: HTMLTableCellElement
|
|
97
|
+
thead: HTMLTableSectionElement
|
|
98
|
+
time: HTMLTimeElement
|
|
99
|
+
tr: HTMLTableRowElement
|
|
100
|
+
track: HTMLTrackElement
|
|
101
|
+
u: Base
|
|
102
|
+
ul: HTMLUListElement
|
|
103
|
+
var: Base
|
|
104
|
+
video: HTMLVideoElement
|
|
105
|
+
wbr: Base
|
|
106
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const HTML_TAGS = [
|
|
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
|
+
] as const
|
|
105
|
+
|
|
106
|
+
const HTML_TEXT_TAGS = [
|
|
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
|
+
] as const
|
|
148
|
+
|
|
149
|
+
export type HTMLTags = (typeof HTML_TAGS)[number]
|
|
150
|
+
export type HTMLTextTags = (typeof HTML_TEXT_TAGS)[number]
|
|
151
|
+
export { HTML_TAGS, HTML_TEXT_TAGS }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HTMLElementAttrs } from "./htmlElementAttrs"
|
|
2
|
+
import type { HTMLTags, HTMLTextTags } from "./htmlTags"
|
|
3
|
+
import { HTML_TAGS, HTML_TEXT_TAGS } from "./htmlTags"
|
|
4
|
+
|
|
5
|
+
type HTMLTagAttrsByTag<T extends HTMLTags> = T extends HTMLTags
|
|
6
|
+
? HTMLElementAttrs[T]
|
|
7
|
+
: Record<string, never>
|
|
8
|
+
|
|
9
|
+
export type { HTMLElementAttrs, HTMLTagAttrsByTag, HTMLTags, HTMLTextTags }
|
|
10
|
+
|
|
11
|
+
export { HTML_TAGS, HTML_TEXT_TAGS }
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import compose from "./compose"
|
|
2
|
+
import config, { init } from "./config"
|
|
3
|
+
import Provider, { context } from "./context"
|
|
4
|
+
import hoistNonReactStatics from "./hoistNonReactStatics"
|
|
5
|
+
import type { HTMLElementAttrs, HTMLTagAttrsByTag, HTMLTags, HTMLTextTags } from "./html"
|
|
6
|
+
import { HTML_TAGS, HTML_TEXT_TAGS } from "./html"
|
|
7
|
+
import type { IsEmpty } from "./isEmpty"
|
|
8
|
+
import isEmpty from "./isEmpty"
|
|
9
|
+
import isEqual from "./isEqual"
|
|
10
|
+
import type { PyreonUIProps, ThemeMode, ThemeModeInput } from "./PyreonUI"
|
|
11
|
+
import { PyreonUI, useMode } from "./PyreonUI"
|
|
12
|
+
import type { Render } from "./render"
|
|
13
|
+
import render from "./render"
|
|
14
|
+
import type { BreakpointKeys, Breakpoints } from "./types"
|
|
15
|
+
import useStableValue from "./useStableValue"
|
|
16
|
+
import { get, merge, omit, pick, set, throttle } from "./utils"
|
|
17
|
+
|
|
18
|
+
export type { CSSEngineConnector } from "./config"
|
|
19
|
+
|
|
20
|
+
export type {
|
|
21
|
+
BreakpointKeys,
|
|
22
|
+
Breakpoints,
|
|
23
|
+
HTMLElementAttrs,
|
|
24
|
+
HTMLTagAttrsByTag,
|
|
25
|
+
HTMLTags,
|
|
26
|
+
HTMLTextTags,
|
|
27
|
+
IsEmpty,
|
|
28
|
+
PyreonUIProps,
|
|
29
|
+
Render,
|
|
30
|
+
ThemeMode,
|
|
31
|
+
ThemeModeInput,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
compose,
|
|
36
|
+
config,
|
|
37
|
+
context,
|
|
38
|
+
get,
|
|
39
|
+
HTML_TAGS,
|
|
40
|
+
HTML_TEXT_TAGS,
|
|
41
|
+
hoistNonReactStatics,
|
|
42
|
+
init,
|
|
43
|
+
isEmpty,
|
|
44
|
+
isEqual,
|
|
45
|
+
merge,
|
|
46
|
+
omit,
|
|
47
|
+
Provider,
|
|
48
|
+
PyreonUI,
|
|
49
|
+
pick,
|
|
50
|
+
render,
|
|
51
|
+
set,
|
|
52
|
+
throttle,
|
|
53
|
+
useMode,
|
|
54
|
+
useStableValue,
|
|
55
|
+
}
|
package/src/isEmpty.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type IsEmpty = <T extends Record<number | string, any> | any[] | null | undefined>(
|
|
2
|
+
param: T,
|
|
3
|
+
) => T extends null | undefined
|
|
4
|
+
? true
|
|
5
|
+
: keyof T extends never
|
|
6
|
+
? true
|
|
7
|
+
: T extends T[]
|
|
8
|
+
? T[number] extends never
|
|
9
|
+
? true
|
|
10
|
+
: false
|
|
11
|
+
: false
|
|
12
|
+
|
|
13
|
+
const isEmpty = (<T extends Record<number | string, any> | any[] | null | undefined>(param: T) => {
|
|
14
|
+
if (!param) return true
|
|
15
|
+
if (typeof param !== "object") return true
|
|
16
|
+
if (Array.isArray(param)) return param.length === 0
|
|
17
|
+
return Object.keys(param).length === 0
|
|
18
|
+
}) as IsEmpty
|
|
19
|
+
|
|
20
|
+
export default isEmpty
|
package/src/isEqual.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const isArrayEqual = (a: unknown[], b: unknown[]): boolean => {
|
|
2
|
+
if (a.length !== b.length) return false
|
|
3
|
+
for (let i = 0; i < a.length; i++) {
|
|
4
|
+
if (!isEqual(a[i], b[i])) return false
|
|
5
|
+
}
|
|
6
|
+
return true
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const isObjectEqual = (a: Record<string, unknown>, b: Record<string, unknown>): boolean => {
|
|
10
|
+
const aKeys = Object.keys(a)
|
|
11
|
+
if (aKeys.length !== Object.keys(b).length) return false
|
|
12
|
+
for (const key of aKeys) {
|
|
13
|
+
if (!Object.hasOwn(b, key)) return false
|
|
14
|
+
if (!isEqual(a[key], b[key])) return false
|
|
15
|
+
}
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isEqual = (a: unknown, b: unknown): boolean => {
|
|
20
|
+
if (Object.is(a, b)) return true
|
|
21
|
+
if (typeof a !== typeof b || a == null || b == null || typeof a !== "object") return false
|
|
22
|
+
if (Array.isArray(a)) return Array.isArray(b) && isArrayEqual(a, b)
|
|
23
|
+
if (Array.isArray(b)) return false
|
|
24
|
+
return isObjectEqual(a as Record<string, unknown>, b as Record<string, unknown>)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default isEqual
|
package/src/render.tsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ComponentFn, Props, VNodeChild } from "@pyreon/core"
|
|
2
|
+
import { h } from "@pyreon/core"
|
|
3
|
+
|
|
4
|
+
type RenderProps<T extends Record<string, unknown> | undefined> = (props: Partial<T>) => VNodeChild
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Flexible element renderer that handles multiple content types:
|
|
8
|
+
* - Primitives (string, number) — returned as-is
|
|
9
|
+
* - Arrays — returned as-is
|
|
10
|
+
* - Component functions — created via h()
|
|
11
|
+
* - VNode objects — returned as-is (with props merged if provided)
|
|
12
|
+
* - Render props (functions) — called with attachProps
|
|
13
|
+
* - Falsy values — return null
|
|
14
|
+
*/
|
|
15
|
+
export type Render = <T extends Record<string, any> | undefined>(
|
|
16
|
+
content?: ComponentFn | string | VNodeChild | VNodeChild[] | RenderProps<T>,
|
|
17
|
+
attachProps?: T,
|
|
18
|
+
) => VNodeChild
|
|
19
|
+
|
|
20
|
+
const render: Render = (content, attachProps) => {
|
|
21
|
+
if (!content) return null
|
|
22
|
+
|
|
23
|
+
const t = typeof content
|
|
24
|
+
if (t === "string" || t === "number" || t === "boolean" || t === "bigint") {
|
|
25
|
+
return content as VNodeChild
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (Array.isArray(content)) {
|
|
29
|
+
return content as VNodeChild
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof content === "function") {
|
|
33
|
+
return h(content as string | ComponentFn, (attachProps ?? {}) as Props)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// VNode object — return directly
|
|
37
|
+
if (typeof content === "object") {
|
|
38
|
+
return content as VNodeChild
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return content as VNodeChild
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default render
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { signal } from "@pyreon/reactivity"
|
|
2
|
+
import isEqual from "./isEqual"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a referentially stable version of `value`. The returned reference
|
|
6
|
+
* only changes when the value is no longer deeply equal to the previous one.
|
|
7
|
+
*
|
|
8
|
+
* Pyreon equivalent of the React useStableValue hook — uses a signal
|
|
9
|
+
* internally to hold the stable reference.
|
|
10
|
+
*/
|
|
11
|
+
const useStableValue = <T>(value: T): T => {
|
|
12
|
+
const ref = signal(value)
|
|
13
|
+
|
|
14
|
+
if (!isEqual(ref.peek(), value)) {
|
|
15
|
+
ref.set(value)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return ref.peek()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default useStableValue
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
export const omit = <T extends Record<string, any>>(
|
|
2
|
+
obj: T | null | undefined,
|
|
3
|
+
keys?: readonly (string | keyof T)[],
|
|
4
|
+
): Partial<T> => {
|
|
5
|
+
if (obj == null) return {} as Partial<T>
|
|
6
|
+
if (!keys || keys.length === 0) return { ...obj }
|
|
7
|
+
const result: Record<string, any> = {}
|
|
8
|
+
const keysSet = new Set(keys as readonly string[])
|
|
9
|
+
for (const key in obj) {
|
|
10
|
+
if (Object.hasOwn(obj, key) && !keysSet.has(key)) {
|
|
11
|
+
result[key] = obj[key]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return result as Partial<T>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const pick = <T extends Record<string, any>>(
|
|
18
|
+
obj: T | null | undefined,
|
|
19
|
+
keys?: readonly (string | keyof T)[],
|
|
20
|
+
): Partial<T> => {
|
|
21
|
+
if (obj == null) return {} as Partial<T>
|
|
22
|
+
if (!keys || keys.length === 0) return { ...obj }
|
|
23
|
+
const result: Record<string, any> = {}
|
|
24
|
+
for (const key of keys) {
|
|
25
|
+
const k = key as string
|
|
26
|
+
if (Object.hasOwn(obj, k)) {
|
|
27
|
+
result[k] = obj[k]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result as Partial<T>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const PATH_RE = /[^.[\]]+/g
|
|
34
|
+
|
|
35
|
+
const parsePath = (path: string | string[]): string[] => {
|
|
36
|
+
if (Array.isArray(path)) return path
|
|
37
|
+
const parts = path.match(PATH_RE)
|
|
38
|
+
return parts ?? []
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isUnsafeKey = (key: string): boolean =>
|
|
42
|
+
key === "__proto__" || key === "prototype" || key === "constructor"
|
|
43
|
+
|
|
44
|
+
export const get = (obj: any, path: string | string[], defaultValue?: any): any => {
|
|
45
|
+
const keys = parsePath(path)
|
|
46
|
+
let result = obj
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
if (result == null || isUnsafeKey(key)) return defaultValue
|
|
49
|
+
result = result[key]
|
|
50
|
+
}
|
|
51
|
+
return result === undefined ? defaultValue : result
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const set = (
|
|
55
|
+
obj: Record<string, any>,
|
|
56
|
+
path: string | string[],
|
|
57
|
+
value: any,
|
|
58
|
+
): Record<string, any> => {
|
|
59
|
+
const keys = parsePath(path)
|
|
60
|
+
let current = obj
|
|
61
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
62
|
+
const key = keys[i] as string
|
|
63
|
+
if (isUnsafeKey(key)) return obj
|
|
64
|
+
const nextKey = keys[i + 1] as string
|
|
65
|
+
if (isUnsafeKey(nextKey)) return obj
|
|
66
|
+
if (current[key] == null) {
|
|
67
|
+
current[key] = /^\d+$/.test(nextKey) ? [] : {}
|
|
68
|
+
}
|
|
69
|
+
current = current[key]
|
|
70
|
+
}
|
|
71
|
+
const lastKey = keys[keys.length - 1]
|
|
72
|
+
if (lastKey != null && !isUnsafeKey(lastKey)) {
|
|
73
|
+
current[lastKey] = value
|
|
74
|
+
}
|
|
75
|
+
return obj
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const throttle = <T extends (...args: any[]) => any>(
|
|
79
|
+
fn: T,
|
|
80
|
+
wait: number = 0,
|
|
81
|
+
options?: { leading?: boolean; trailing?: boolean },
|
|
82
|
+
): T & { cancel: () => void } => {
|
|
83
|
+
const leading = options?.leading !== false
|
|
84
|
+
const trailing = options?.trailing !== false
|
|
85
|
+
let lastCallTime: number | undefined
|
|
86
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
87
|
+
let lastArgs: any[] | undefined
|
|
88
|
+
|
|
89
|
+
const invoke = (args: any[]) => {
|
|
90
|
+
lastCallTime = Date.now()
|
|
91
|
+
fn(...args)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const startTrailingTimer = (args: any[], delay: number) => {
|
|
95
|
+
lastArgs = args
|
|
96
|
+
if (timeoutId !== undefined) return
|
|
97
|
+
timeoutId = setTimeout(() => {
|
|
98
|
+
timeoutId = undefined
|
|
99
|
+
if (lastArgs) {
|
|
100
|
+
invoke(lastArgs)
|
|
101
|
+
lastArgs = undefined
|
|
102
|
+
}
|
|
103
|
+
}, delay)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const throttled = (...args: any[]) => {
|
|
107
|
+
const now = Date.now()
|
|
108
|
+
const elapsed = lastCallTime === undefined ? wait : now - lastCallTime
|
|
109
|
+
if (elapsed >= wait) {
|
|
110
|
+
if (leading) {
|
|
111
|
+
invoke(args)
|
|
112
|
+
} else {
|
|
113
|
+
lastCallTime = now
|
|
114
|
+
if (trailing) startTrailingTimer(args, wait)
|
|
115
|
+
}
|
|
116
|
+
} else if (trailing) {
|
|
117
|
+
startTrailingTimer(args, wait - elapsed)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
throttled.cancel = () => {
|
|
122
|
+
if (timeoutId !== undefined) {
|
|
123
|
+
clearTimeout(timeoutId)
|
|
124
|
+
timeoutId = undefined
|
|
125
|
+
}
|
|
126
|
+
lastArgs = undefined
|
|
127
|
+
lastCallTime = undefined
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return throttled as T & { cancel: () => void }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const isPlainObject = (value: unknown): value is Record<string, any> =>
|
|
134
|
+
value !== null &&
|
|
135
|
+
typeof value === "object" &&
|
|
136
|
+
!Array.isArray(value) &&
|
|
137
|
+
Object.getPrototypeOf(value) === Object.prototype
|
|
138
|
+
|
|
139
|
+
export const merge = <T extends Record<string, any>>(
|
|
140
|
+
target: T,
|
|
141
|
+
...sources: Record<string, any>[]
|
|
142
|
+
): T => {
|
|
143
|
+
for (const source of sources) {
|
|
144
|
+
if (source == null) continue
|
|
145
|
+
for (const key of Object.keys(source)) {
|
|
146
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue
|
|
147
|
+
const targetVal = (target as Record<string, unknown>)[key]
|
|
148
|
+
const sourceVal = source[key]
|
|
149
|
+
if (isPlainObject(targetVal) && isPlainObject(sourceVal)) {
|
|
150
|
+
;(target as Record<string, unknown>)[key] = merge({ ...targetVal }, sourceVal)
|
|
151
|
+
} else {
|
|
152
|
+
;(target as Record<string, unknown>)[key] = sourceVal
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return target
|
|
157
|
+
}
|