@vela-studio/ui 1.0.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.
- package/README.md +152 -0
- package/dist/index.d.ts +696 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +11786 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.js +10 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/style.css +1 -0
- package/index.ts +150 -0
- package/package.json +73 -0
- package/src/components/advanced/scripting/Scripting.vue +189 -0
- package/src/components/advanced/state/State.vue +231 -0
- package/src/components/advanced/trigger/Trigger.vue +256 -0
- package/src/components/basic/button/Button.vue +120 -0
- package/src/components/basic/container/Container.vue +22 -0
- package/src/components/chart/barChart/barChart.vue +176 -0
- package/src/components/chart/doughnutChart/doughnutChart.vue +128 -0
- package/src/components/chart/funnelChart/funnelChart.vue +128 -0
- package/src/components/chart/gaugeChart/gaugeChart.vue +144 -0
- package/src/components/chart/lineChart/lineChart.vue +188 -0
- package/src/components/chart/pieChart/pieChart.vue +114 -0
- package/src/components/chart/radarChart/radarChart.vue +115 -0
- package/src/components/chart/sankeyChart/sankeyChart.vue +144 -0
- package/src/components/chart/scatterChart/scatterChart.vue +162 -0
- package/src/components/chart/stackedBarChart/stackedBarChart.vue +184 -0
- package/src/components/content/html/Html.vue +104 -0
- package/src/components/content/iframe/Iframe.vue +111 -0
- package/src/components/content/markdown/Markdown.vue +174 -0
- package/src/components/controls/breadcrumb/Breadcrumb.vue +79 -0
- package/src/components/controls/buttonGroup/ButtonGroup.vue +93 -0
- package/src/components/controls/checkboxGroup/CheckboxGroup.vue +147 -0
- package/src/components/controls/dateRange/DateRange.vue +174 -0
- package/src/components/controls/multiSelect/MultiSelect.vue +155 -0
- package/src/components/controls/navButton/NavButton.vue +97 -0
- package/src/components/controls/pagination/Pagination.vue +94 -0
- package/src/components/controls/searchBox/SearchBox.vue +170 -0
- package/src/components/controls/select/Select.vue +134 -0
- package/src/components/controls/slider/Slider.vue +167 -0
- package/src/components/controls/switch/Switch.vue +107 -0
- package/src/components/data/cardGrid/CardGrid.vue +318 -0
- package/src/components/data/list/List.vue +282 -0
- package/src/components/data/pivot/Pivot.vue +270 -0
- package/src/components/data/table/Table.vue +150 -0
- package/src/components/data/timeline/Timeline.vue +315 -0
- package/src/components/group/Group.vue +75 -0
- package/src/components/kpi/box/Box.vue +98 -0
- package/src/components/kpi/countUp/CountUp.vue +193 -0
- package/src/components/kpi/progress/Progress.vue +159 -0
- package/src/components/kpi/stat/Stat.vue +205 -0
- package/src/components/kpi/text/Text.vue +74 -0
- package/src/components/layout/badge/Badge.vue +105 -0
- package/src/components/layout/col/Col.vue +114 -0
- package/src/components/layout/flex/Flex.vue +105 -0
- package/src/components/layout/grid/Grid.vue +89 -0
- package/src/components/layout/modal/Modal.vue +118 -0
- package/src/components/layout/panel/Panel.vue +162 -0
- package/src/components/layout/row/Row.vue +99 -0
- package/src/components/layout/tabs/Tabs.vue +117 -0
- package/src/components/media/image/Image.vue +132 -0
- package/src/components/media/video/Video.vue +115 -0
- package/src/components/v2/basic/BaseButton.vue +179 -0
- package/src/components/v2/kpi/KpiCard.vue +215 -0
- package/src/components/v2/layout/GridBox.vue +55 -0
- package/src/hooks/useDataSource.ts +123 -0
- package/src/types/gis.ts +251 -0
- package/src/utils/chartUtils.ts +349 -0
- package/src/utils/dataUtils.ts +403 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据提取工具函数(通用)
|
|
3
|
+
* 用于所有组件的数据源、路径解析、数据格式化等
|
|
4
|
+
* 图表、KPI、Text 等组件都使用这些工具函数提取数据
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 从对象中根据路径提取值
|
|
9
|
+
* 支持点号路径和数组索引,如: "data.chart.values" 或 "items[0].name"
|
|
10
|
+
*
|
|
11
|
+
* @param obj - 源数据对象
|
|
12
|
+
* @param path - 提取路径,可选(为空则返回 undefined)
|
|
13
|
+
* @returns 提取的值,或 undefined
|
|
14
|
+
*/
|
|
15
|
+
export function getValueByPath(obj: unknown, path: string | undefined): unknown {
|
|
16
|
+
if (!path || !obj) return undefined
|
|
17
|
+
try {
|
|
18
|
+
const keys = path.replace(/\[(\d+)\]/g, '.$1').split('.')
|
|
19
|
+
let result: unknown = obj
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
if (result === null || result === undefined) return undefined
|
|
22
|
+
result = (result as Record<string, unknown>)[key]
|
|
23
|
+
}
|
|
24
|
+
return result
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 设置对象路径上的值
|
|
32
|
+
* 支持点号路径和数组索引,如: "data.chart.values" 或 "items[0].name"
|
|
33
|
+
* 用于数据联动(DataBinding)等需要动态写入路径的场景
|
|
34
|
+
*
|
|
35
|
+
* @param obj - 目标对象
|
|
36
|
+
* @param path - 设置路径
|
|
37
|
+
* @param value - 要设置的值
|
|
38
|
+
* @returns 是否设置成功
|
|
39
|
+
*/
|
|
40
|
+
export function setValueByPath(obj: unknown, path: string, value: unknown): boolean {
|
|
41
|
+
if (!obj || typeof obj !== 'object') return false
|
|
42
|
+
if (!path) return false
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// 将路径分割为 tokens(支持数组索引和对象属性)
|
|
46
|
+
const tokens: Array<string | number> = []
|
|
47
|
+
const normalized = path
|
|
48
|
+
.replace(/\[(\d+)\]/g, '.$1')
|
|
49
|
+
.split('.')
|
|
50
|
+
.map((s) => s.trim())
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
|
|
53
|
+
for (const seg of normalized) {
|
|
54
|
+
if (/^\d+$/.test(seg)) tokens.push(Number(seg))
|
|
55
|
+
else tokens.push(seg)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (tokens.length === 0) return false
|
|
59
|
+
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
let cur: any = obj
|
|
62
|
+
|
|
63
|
+
// 遍历路径创建中间容器
|
|
64
|
+
for (let i = 0; i < tokens.length - 1; i++) {
|
|
65
|
+
const key = tokens[i] as string | number
|
|
66
|
+
const nextKey = tokens[i + 1]
|
|
67
|
+
|
|
68
|
+
if (cur[key] == null || typeof cur[key] !== 'object') {
|
|
69
|
+
// 下一个键是数字则创建数组,否则创建对象
|
|
70
|
+
cur[key] = typeof nextKey === 'number' ? [] : {}
|
|
71
|
+
}
|
|
72
|
+
cur = cur[key]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 设置最后一个键的值
|
|
76
|
+
const lastKey = tokens[tokens.length - 1] as string | number
|
|
77
|
+
cur[lastKey] = value
|
|
78
|
+
return true
|
|
79
|
+
} catch {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 从数据源提取单个数值
|
|
86
|
+
* 用于 KPI 组件(countUp, progress, badge 等)
|
|
87
|
+
*
|
|
88
|
+
* @param remoteData - 远程数据对象
|
|
89
|
+
* @param valuePath - 数值路径,可选
|
|
90
|
+
* @param defaultValue - 默认值
|
|
91
|
+
* @returns 提取的数值
|
|
92
|
+
*/
|
|
93
|
+
export function extractNumber(
|
|
94
|
+
remoteData: unknown,
|
|
95
|
+
valuePath: string | undefined,
|
|
96
|
+
defaultValue: number = 0,
|
|
97
|
+
): number {
|
|
98
|
+
if (!valuePath) return defaultValue
|
|
99
|
+
|
|
100
|
+
const value = getValueByPath(remoteData, valuePath)
|
|
101
|
+
if (typeof value === 'number') return value
|
|
102
|
+
if (value !== undefined && value !== null) {
|
|
103
|
+
const parsed = parseFloat(String(value))
|
|
104
|
+
return isNaN(parsed) ? defaultValue : parsed
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return defaultValue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 解析逗号分隔的数字输入
|
|
112
|
+
* 支持 JSON 数组格式和逗号分隔格式
|
|
113
|
+
* @param input - 输入字符串,如 "150, 230, 224" 或 "[150, 230, 224]"
|
|
114
|
+
* @param defaultValue - 默认值数组
|
|
115
|
+
*/
|
|
116
|
+
export function parseNumberInput(input: string | undefined, defaultValue: number[] = []): number[] {
|
|
117
|
+
if (!input) return defaultValue
|
|
118
|
+
|
|
119
|
+
// 尝试 JSON 解析
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(input)
|
|
122
|
+
if (Array.isArray(parsed)) {
|
|
123
|
+
return parsed
|
|
124
|
+
.map((v) => (typeof v === 'number' ? v : parseFloat(String(v))))
|
|
125
|
+
.filter((v) => !isNaN(v))
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// JSON 解析失败,尝试逗号分隔
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 逗号分隔解析
|
|
132
|
+
return input
|
|
133
|
+
.split(',')
|
|
134
|
+
.map((v) => parseFloat(v.trim()))
|
|
135
|
+
.filter((v) => !isNaN(v))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 解析逗号分隔的字符串输入
|
|
140
|
+
* 支持 JSON 数组格式和逗号分隔格式
|
|
141
|
+
* @param input - 输入字符串,如 "Mon, Tue, Wed" 或 '["Mon", "Tue", "Wed"]'
|
|
142
|
+
* @param defaultValue - 默认值数组
|
|
143
|
+
*/
|
|
144
|
+
export function parseStringInput(input: string | undefined, defaultValue: string[] = []): string[] {
|
|
145
|
+
if (!input) return defaultValue
|
|
146
|
+
|
|
147
|
+
// 尝试 JSON 解析
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(input)
|
|
150
|
+
if (Array.isArray(parsed)) {
|
|
151
|
+
return parsed.map((v) => String(v))
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
// JSON 解析失败,尝试逗号分隔
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 逗号分隔解析
|
|
158
|
+
return input
|
|
159
|
+
.split(',')
|
|
160
|
+
.map((v) => v.trim())
|
|
161
|
+
.filter((v) => v.length > 0)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 解析二维数组输入(用于散点图等)
|
|
166
|
+
* @param input - 输入字符串,如 "[[10, 8], [8, 7]]"
|
|
167
|
+
* @param defaultValue - 默认值数组
|
|
168
|
+
*/
|
|
169
|
+
export function parse2DArrayInput(
|
|
170
|
+
input: string | undefined,
|
|
171
|
+
defaultValue: Array<[number, number]> = [],
|
|
172
|
+
): Array<[number, number]> {
|
|
173
|
+
if (!input) return defaultValue
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const parsed = JSON.parse(input)
|
|
177
|
+
if (Array.isArray(parsed) && parsed.length > 0 && Array.isArray(parsed[0])) {
|
|
178
|
+
return parsed.map((item) => {
|
|
179
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
180
|
+
return [
|
|
181
|
+
typeof item[0] === 'number' ? item[0] : parseFloat(String(item[0])),
|
|
182
|
+
typeof item[1] === 'number' ? item[1] : parseFloat(String(item[1])),
|
|
183
|
+
] as [number, number]
|
|
184
|
+
}
|
|
185
|
+
return [0, 0] as [number, number]
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
// JSON 解析失败
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return defaultValue
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 从数据源提取数字数组
|
|
197
|
+
* @param remoteData - 远程数据对象
|
|
198
|
+
* @param dataPath - 数据路径
|
|
199
|
+
*/
|
|
200
|
+
export function extractNumberArray(
|
|
201
|
+
remoteData: unknown,
|
|
202
|
+
dataPath: string | undefined,
|
|
203
|
+
): number[] | undefined {
|
|
204
|
+
if (!dataPath) return undefined
|
|
205
|
+
|
|
206
|
+
const extractedData = getValueByPath(remoteData, dataPath)
|
|
207
|
+
if (Array.isArray(extractedData)) {
|
|
208
|
+
return extractedData.map((v) => (typeof v === 'number' ? v : parseFloat(String(v))))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return undefined
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 从数据源提取字符串数组
|
|
216
|
+
* @param remoteData - 远程数据对象
|
|
217
|
+
* @param dataPath - 数据路径
|
|
218
|
+
*/
|
|
219
|
+
export function extractStringArray(
|
|
220
|
+
remoteData: unknown,
|
|
221
|
+
dataPath: string | undefined,
|
|
222
|
+
): string[] | undefined {
|
|
223
|
+
if (!dataPath) return undefined
|
|
224
|
+
|
|
225
|
+
const extractedData = getValueByPath(remoteData, dataPath)
|
|
226
|
+
if (Array.isArray(extractedData)) {
|
|
227
|
+
return extractedData.map((v) => String(v))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return undefined
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 从数据源提取二维数组(用于散点图等)
|
|
235
|
+
* @param remoteData - 远程数据对象
|
|
236
|
+
* @param dataPath - 数据路径
|
|
237
|
+
*/
|
|
238
|
+
export function extract2DArray(
|
|
239
|
+
remoteData: unknown,
|
|
240
|
+
dataPath: string | undefined,
|
|
241
|
+
): Array<[number, number]> | undefined {
|
|
242
|
+
if (!dataPath) return undefined
|
|
243
|
+
|
|
244
|
+
const extractedData = getValueByPath(remoteData, dataPath)
|
|
245
|
+
if (Array.isArray(extractedData) && extractedData.length > 0 && Array.isArray(extractedData[0])) {
|
|
246
|
+
return extractedData.map((item) => {
|
|
247
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
248
|
+
return [
|
|
249
|
+
typeof item[0] === 'number' ? item[0] : parseFloat(String(item[0])),
|
|
250
|
+
typeof item[1] === 'number' ? item[1] : parseFloat(String(item[1])),
|
|
251
|
+
] as [number, number]
|
|
252
|
+
}
|
|
253
|
+
return [0, 0] as [number, number]
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return undefined
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 从数据源提取字符串值
|
|
262
|
+
* @param remoteData - 远程数据对象
|
|
263
|
+
* @param dataPath - 数据路径
|
|
264
|
+
*/
|
|
265
|
+
export function extractString(
|
|
266
|
+
remoteData: unknown,
|
|
267
|
+
dataPath: string | undefined,
|
|
268
|
+
): string | undefined {
|
|
269
|
+
if (!dataPath) return undefined
|
|
270
|
+
|
|
271
|
+
const extractedData = getValueByPath(remoteData, dataPath)
|
|
272
|
+
return extractedData ? String(extractedData) : undefined
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 解析 JSON 数组输入(通用)
|
|
277
|
+
* @param input - JSON 字符串输入
|
|
278
|
+
* @param defaultValue - 默认值
|
|
279
|
+
*/
|
|
280
|
+
export function parseJSONInput<T = unknown>(input: string | undefined, defaultValue: T): T {
|
|
281
|
+
if (!input) return defaultValue
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const parsed = JSON.parse(input)
|
|
285
|
+
return parsed as T
|
|
286
|
+
} catch (e) {
|
|
287
|
+
console.error('Failed to parse JSON input:', e)
|
|
288
|
+
return defaultValue
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 提取并规范化 Sankey 图节点数据
|
|
294
|
+
* @param remoteData - 远程数据对象
|
|
295
|
+
* @param dataPath - 数据路径
|
|
296
|
+
*/
|
|
297
|
+
export function extractSankeyNodes(
|
|
298
|
+
remoteData: unknown,
|
|
299
|
+
dataPath: string | undefined,
|
|
300
|
+
): Array<{ name: string; value?: number; depth?: number; itemStyle?: unknown }> | undefined {
|
|
301
|
+
if (!dataPath) return undefined
|
|
302
|
+
|
|
303
|
+
const nodesData = getValueByPath(remoteData, dataPath)
|
|
304
|
+
if (!Array.isArray(nodesData) || nodesData.length === 0) return undefined
|
|
305
|
+
|
|
306
|
+
return nodesData.map((node: unknown) => {
|
|
307
|
+
const obj =
|
|
308
|
+
typeof node === 'object' && node !== null ? (node as Record<string, unknown>) : undefined
|
|
309
|
+
const name = typeof node === 'string' ? node : String(obj?.name ?? obj?.id ?? '')
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
name,
|
|
313
|
+
value:
|
|
314
|
+
obj && typeof obj.value === 'number'
|
|
315
|
+
? (obj.value as number)
|
|
316
|
+
: obj && obj.value != null
|
|
317
|
+
? parseFloat(String(obj.value))
|
|
318
|
+
: undefined,
|
|
319
|
+
depth:
|
|
320
|
+
obj && typeof obj.depth === 'number'
|
|
321
|
+
? (obj.depth as number)
|
|
322
|
+
: obj && obj.depth != null
|
|
323
|
+
? parseFloat(String(obj.depth))
|
|
324
|
+
: undefined,
|
|
325
|
+
itemStyle: obj ? obj.itemStyle : undefined,
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 提取并规范化 Sankey 图连接数据
|
|
332
|
+
* @param remoteData - 远程数据对象
|
|
333
|
+
* @param dataPath - 数据路径
|
|
334
|
+
*/
|
|
335
|
+
export function extractSankeyLinks(
|
|
336
|
+
remoteData: unknown,
|
|
337
|
+
dataPath: string | undefined,
|
|
338
|
+
): Array<{ source: string; target: string; value: number }> | undefined {
|
|
339
|
+
if (!dataPath) return undefined
|
|
340
|
+
|
|
341
|
+
const linksData = getValueByPath(remoteData, dataPath)
|
|
342
|
+
if (!Array.isArray(linksData) || linksData.length === 0) return undefined
|
|
343
|
+
|
|
344
|
+
return linksData.map((link: unknown) => {
|
|
345
|
+
const l = link as Record<string, unknown>
|
|
346
|
+
return {
|
|
347
|
+
source: (l.source || l.from || '') as string,
|
|
348
|
+
target: (l.target || l.to || '') as string,
|
|
349
|
+
value: (l.value || 0) as number,
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* KPI 组件专用:提取多个字段的数据
|
|
356
|
+
* 用于 stat 组件等需要同时提取多个字段的场景
|
|
357
|
+
*
|
|
358
|
+
* @param remoteData - 远程数据对象
|
|
359
|
+
* @param paths - 字段路径映射 { title: 'data.title', value: 'data.value', ... }
|
|
360
|
+
* @returns 提取的数据对象
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* const data = extractMultipleFields(remoteData, {
|
|
364
|
+
* title: 'data.kpi.title',
|
|
365
|
+
* value: 'data.kpi.value',
|
|
366
|
+
* change: 'data.kpi.change'
|
|
367
|
+
* })
|
|
368
|
+
* // 返回: { title: '销售额', value: 12345, change: 5.2 }
|
|
369
|
+
*/
|
|
370
|
+
export function extractMultipleFields<T extends Record<string, string | undefined>>(
|
|
371
|
+
remoteData: unknown,
|
|
372
|
+
paths: T,
|
|
373
|
+
): Record<keyof T, unknown> {
|
|
374
|
+
const result: Record<string, unknown> = {}
|
|
375
|
+
|
|
376
|
+
for (const [key, path] of Object.entries(paths)) {
|
|
377
|
+
if (path && typeof path === 'string') {
|
|
378
|
+
result[key] = getValueByPath(remoteData, path)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return result as Record<keyof T, unknown>
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* 智能提取数据:优先使用路径提取,否则使用默认值
|
|
387
|
+
* 用于所有简单组件(text, countUp, progress, badge 等)
|
|
388
|
+
*
|
|
389
|
+
* @param remoteData - 远程数据对象
|
|
390
|
+
* @param path - 数据路径
|
|
391
|
+
* @param fallbackValue - 回退值(当路径未配置或提取失败时使用)
|
|
392
|
+
* @returns 提取的数据或回退值
|
|
393
|
+
*/
|
|
394
|
+
export function extractWithFallback<T>(
|
|
395
|
+
remoteData: unknown,
|
|
396
|
+
path: string | undefined,
|
|
397
|
+
fallbackValue: T,
|
|
398
|
+
): T {
|
|
399
|
+
if (!path) return fallbackValue
|
|
400
|
+
|
|
401
|
+
const extracted = getValueByPath(remoteData, path)
|
|
402
|
+
return (extracted ?? fallbackValue) as T
|
|
403
|
+
}
|