@poirazis/supercomponents-shared 1.0.8 → 1.0.11
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/dist/index.js +6348 -5517
- package/dist/index.umd.cjs +10 -10
- package/package.json +2 -1
- package/src/index.js +1 -0
- package/src/index.ts +1 -0
- package/src/lib/SuperForm/InnerForm.svelte +558 -0
- package/src/lib/SuperForm/SuperForm.svelte +149 -0
- package/src/lib/SuperForm/index.js +0 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@poirazis/supercomponents-shared",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.11",
|
4
4
|
"description": "Shared Svelte components library",
|
5
5
|
"type": "module",
|
6
6
|
"main": "dist/index.js",
|
@@ -37,6 +37,7 @@
|
|
37
37
|
},
|
38
38
|
"dependencies": {
|
39
39
|
"@sveltejs/svelte-virtual-list": "^3.0.1",
|
40
|
+
"@budibase/types": "^2.33.14",
|
40
41
|
"date-picker-svelte": "^2.16.0",
|
41
42
|
"svelte-dnd-action": "^0.9.61",
|
42
43
|
"svelte-fsm": "^1.2.0",
|
package/src/index.js
CHANGED
@@ -5,6 +5,7 @@ export { default as SuperButton } from "./lib/SuperButton/SuperButton.svelte";
|
|
5
5
|
|
6
6
|
// Form components
|
7
7
|
export { default as SuperField } from "./lib/SuperField/SuperField.svelte";
|
8
|
+
export { default as SuperForm } from "./lib/SuperForm/SuperForm.svelte";
|
8
9
|
|
9
10
|
// List components
|
10
11
|
export { default as SuperList } from "./lib/SuperList/SuperList.svelte";
|
package/src/index.ts
CHANGED
@@ -5,6 +5,7 @@ export { default as SuperButton } from "./lib/SuperButton/SuperButton.svelte";
|
|
5
5
|
|
6
6
|
// Form components
|
7
7
|
export { default as SuperField } from "./lib/SuperField/SuperField.svelte";
|
8
|
+
export { default as SuperForm } from "./lib/SuperForm/SuperForm.svelte";
|
8
9
|
|
9
10
|
// List components
|
10
11
|
export { default as SuperList } from "./lib/SuperList/SuperList.svelte";
|
@@ -0,0 +1,558 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import { setContext, getContext } from "svelte";
|
3
|
+
import type { Readable, Writable } from "svelte/store";
|
4
|
+
import { derived, get, writable } from "svelte/store";
|
5
|
+
const { createValidatorFromConstraints } = getContext("sdk");
|
6
|
+
import type { FieldSchema, FieldType, UIFieldValidationRule } from "./types";
|
7
|
+
|
8
|
+
type FieldInfo<T = any> = {
|
9
|
+
name: string;
|
10
|
+
step: number;
|
11
|
+
type: FieldType;
|
12
|
+
fieldState: {
|
13
|
+
fieldId: string;
|
14
|
+
value: T;
|
15
|
+
defaultValue: T;
|
16
|
+
disabled: boolean;
|
17
|
+
readonly: boolean;
|
18
|
+
validator: ((_value: T) => string | null) | null;
|
19
|
+
error: string | null | undefined;
|
20
|
+
lastUpdate: number;
|
21
|
+
};
|
22
|
+
fieldApi: {
|
23
|
+
setValue(_value: T): void;
|
24
|
+
validate(): boolean;
|
25
|
+
reset(): void;
|
26
|
+
};
|
27
|
+
fieldSchema: FieldSchema | {};
|
28
|
+
};
|
29
|
+
|
30
|
+
export let dataSource: any | undefined = undefined;
|
31
|
+
export let disabled: boolean = false;
|
32
|
+
export let readonly: boolean = false;
|
33
|
+
export let initialValues: Record<string, any> | undefined = undefined;
|
34
|
+
export let size: "Medium" | "Large" | undefined = undefined;
|
35
|
+
export let schema: Record<string, FieldSchema> | undefined = undefined;
|
36
|
+
export let definition: any | undefined = undefined;
|
37
|
+
export let disableSchemaValidation: boolean = false;
|
38
|
+
export let editAutoColumns: boolean = false;
|
39
|
+
|
40
|
+
// For internal use only, to disable context when being used with standalone fields
|
41
|
+
export let provideContext: boolean = true;
|
42
|
+
|
43
|
+
// We export this store so that when we remount the inner form we can still
|
44
|
+
// persist what step we're on
|
45
|
+
export let currentStep: Writable<number>;
|
46
|
+
|
47
|
+
const component = getContext("component");
|
48
|
+
const { styleable, Provider, ActionTypes } = getContext("sdk");
|
49
|
+
|
50
|
+
let fields: Writable<FieldInfo>[] = [];
|
51
|
+
const formState = writable({
|
52
|
+
values: {},
|
53
|
+
errors: {},
|
54
|
+
valid: true,
|
55
|
+
dirty: false,
|
56
|
+
currentStep: get(currentStep),
|
57
|
+
});
|
58
|
+
|
59
|
+
// Reactive derived stores to derive form state from field array
|
60
|
+
$: values = deriveFieldProperty(fields, (f) => f.fieldState.value);
|
61
|
+
$: errors = deriveFieldProperty(fields, (f) => f.fieldState.error);
|
62
|
+
$: enrichments = deriveBindingEnrichments(fields);
|
63
|
+
$: valid = !Object.values($errors).some((error) => error != null);
|
64
|
+
// Track if any field has been modified from its default value
|
65
|
+
$: dirty = derived(
|
66
|
+
fields,
|
67
|
+
(fieldsValue) => fieldsValue.some((field) => {
|
68
|
+
const { value, defaultValue } = field.fieldState;
|
69
|
+
// Compare current value with default value to determine if field is dirty
|
70
|
+
// Using JSON.stringify for deep comparison of objects and arrays
|
71
|
+
return JSON.stringify(value) !== JSON.stringify(defaultValue);
|
72
|
+
})
|
73
|
+
);
|
74
|
+
|
75
|
+
// Derive whether the current form step is valid
|
76
|
+
$: currentStepValid = derived(
|
77
|
+
[currentStep, ...fields],
|
78
|
+
([currentStepValue, ...fieldsValue]) => {
|
79
|
+
return !fieldsValue
|
80
|
+
.filter((f) => f.step === currentStepValue)
|
81
|
+
.some((f) => f.fieldState.error != null);
|
82
|
+
}
|
83
|
+
);
|
84
|
+
|
85
|
+
// Update form state store from derived stores
|
86
|
+
$: {
|
87
|
+
formState.set({
|
88
|
+
values: $values,
|
89
|
+
errors: $errors,
|
90
|
+
valid,
|
91
|
+
dirty: $dirty,
|
92
|
+
currentStep: $currentStep,
|
93
|
+
});
|
94
|
+
}
|
95
|
+
|
96
|
+
// Derive value of whole form
|
97
|
+
$: formValue = deriveFormValue(initialValues, $values, $enrichments);
|
98
|
+
|
99
|
+
// Create data context to provide
|
100
|
+
$: dataContext = {
|
101
|
+
...formValue,
|
102
|
+
|
103
|
+
// These static values are prefixed to avoid clashes with actual columns
|
104
|
+
__value: formValue,
|
105
|
+
__valid: valid,
|
106
|
+
__dirty: $dirty,
|
107
|
+
__currentStep: $currentStep,
|
108
|
+
__currentStepValid: $currentStepValid,
|
109
|
+
};
|
110
|
+
|
111
|
+
// Generates a derived store from an array of fields, comprised of a map of
|
112
|
+
// extracted values from the field array
|
113
|
+
const deriveFieldProperty = (
|
114
|
+
fieldStores: Readable<FieldInfo>[],
|
115
|
+
getProp: (_field: FieldInfo) => any
|
116
|
+
) => {
|
117
|
+
return derived(fieldStores, (fieldValues) => {
|
118
|
+
return fieldValues.reduce(
|
119
|
+
(map, field) => ({ ...map, [field.name]: getProp(field) }),
|
120
|
+
{}
|
121
|
+
);
|
122
|
+
});
|
123
|
+
};
|
124
|
+
|
125
|
+
// Derives any enrichments which need to be made so that bindings work for
|
126
|
+
// special data types like attachments
|
127
|
+
const deriveBindingEnrichments = (fieldStores: Readable<FieldInfo>[]) => {
|
128
|
+
return derived(fieldStores, (fieldValues) => {
|
129
|
+
const enrichments: Record<string, string> = {};
|
130
|
+
fieldValues.forEach((field) => {
|
131
|
+
if (field.type === "attachment") {
|
132
|
+
const value = field.fieldState.value;
|
133
|
+
let url = null;
|
134
|
+
if (Array.isArray(value) && value[0] != null) {
|
135
|
+
url = value[0].url;
|
136
|
+
}
|
137
|
+
enrichments[`${field.name}_first`] = url;
|
138
|
+
}
|
139
|
+
});
|
140
|
+
return enrichments;
|
141
|
+
});
|
142
|
+
};
|
143
|
+
|
144
|
+
// Derive the overall form value and deeply set all field paths so that we
|
145
|
+
// can support things like JSON fields.
|
146
|
+
const deriveFormValue = (
|
147
|
+
initialValues: Record<string, any> | undefined,
|
148
|
+
values: Record<string, any>,
|
149
|
+
enrichments: Record<string, string>
|
150
|
+
) => {
|
151
|
+
let formValue = cloneDeep(initialValues || {});
|
152
|
+
|
153
|
+
// We need to sort the keys to avoid a JSON field overwriting a nested field
|
154
|
+
const sortedFields = Object.entries(values || {})
|
155
|
+
.map(([key, value]) => {
|
156
|
+
const field = getField(key);
|
157
|
+
return {
|
158
|
+
key,
|
159
|
+
value,
|
160
|
+
lastUpdate: get(field).fieldState?.lastUpdate || 0,
|
161
|
+
};
|
162
|
+
})
|
163
|
+
.sort((a, b) => {
|
164
|
+
return a.lastUpdate - b.lastUpdate;
|
165
|
+
});
|
166
|
+
|
167
|
+
// Merge all values and enrichments into a single value
|
168
|
+
sortedFields.forEach(({ key, value }) => {
|
169
|
+
deepSet(formValue, key, value);
|
170
|
+
});
|
171
|
+
Object.entries(enrichments || {}).forEach(([key, value]) => {
|
172
|
+
deepSet(formValue, key, value);
|
173
|
+
});
|
174
|
+
return formValue;
|
175
|
+
};
|
176
|
+
|
177
|
+
// Searches the field array for a certain field
|
178
|
+
const getField = (name: string) => {
|
179
|
+
return fields.find((field) => get(field).name === name)!;
|
180
|
+
};
|
181
|
+
|
182
|
+
// Sanitises a value by ensuring it doesn't contain any invalid data
|
183
|
+
const sanitiseValue = (
|
184
|
+
value: any,
|
185
|
+
schema: FieldSchema | undefined,
|
186
|
+
type: FieldType
|
187
|
+
) => {
|
188
|
+
// Check arrays - remove any values not present in the field schema and
|
189
|
+
// convert any values supplied to strings
|
190
|
+
if (Array.isArray(value) && type === "array" && schema) {
|
191
|
+
const options = schema?.constraints?.inclusion || [];
|
192
|
+
return value
|
193
|
+
.map((opt) => String(opt))
|
194
|
+
.filter((opt) => options.includes(opt));
|
195
|
+
}
|
196
|
+
return value;
|
197
|
+
};
|
198
|
+
|
199
|
+
const formApi = {
|
200
|
+
registerField: (
|
201
|
+
field: string,
|
202
|
+
type: FieldType,
|
203
|
+
defaultValue: string | null = null,
|
204
|
+
fieldDisabled: boolean = false,
|
205
|
+
fieldReadOnly: boolean = false,
|
206
|
+
validationRules: UIFieldValidationRule[],
|
207
|
+
step: number = 1
|
208
|
+
) => {
|
209
|
+
if (!field) {
|
210
|
+
return;
|
211
|
+
}
|
212
|
+
// Create validation function based on field schema
|
213
|
+
const schemaConstraints = disableSchemaValidation
|
214
|
+
? null
|
215
|
+
: schema?.[field]?.constraints;
|
216
|
+
const validator = createValidatorFromConstraints(
|
217
|
+
schemaConstraints,
|
218
|
+
validationRules,
|
219
|
+
field,
|
220
|
+
definition
|
221
|
+
);
|
222
|
+
|
223
|
+
// Sanitise the default value to ensure it doesn't contain invalid data
|
224
|
+
defaultValue = sanitiseValue(defaultValue, schema?.[field], type);
|
225
|
+
|
226
|
+
// If we've already registered this field then keep some existing state
|
227
|
+
let initialValue = deepGet(initialValues, field) ?? defaultValue;
|
228
|
+
let initialError = null;
|
229
|
+
let fieldId = `id-${uuid()}`;
|
230
|
+
const existingField = getField(field);
|
231
|
+
if (existingField) {
|
232
|
+
const { fieldState } = get(existingField);
|
233
|
+
fieldId = fieldState.fieldId;
|
234
|
+
|
235
|
+
// Determine the initial value for this field, reusing the current
|
236
|
+
// value if one exists
|
237
|
+
if (fieldState.value != null && fieldState.value !== "") {
|
238
|
+
initialValue = fieldState.value;
|
239
|
+
}
|
240
|
+
|
241
|
+
// If this field has already been registered and we previously had an
|
242
|
+
// error set, then re-run the validator to see if we can unset it
|
243
|
+
if (fieldState.error) {
|
244
|
+
initialError = validator?.(initialValue);
|
245
|
+
}
|
246
|
+
}
|
247
|
+
|
248
|
+
// Auto columns are always disabled
|
249
|
+
const isAutoColumn = !!schema?.[field]?.autocolumn;
|
250
|
+
|
251
|
+
// Construct field info
|
252
|
+
const fieldInfo = writable<FieldInfo>({
|
253
|
+
name: field,
|
254
|
+
type,
|
255
|
+
step: step || 1,
|
256
|
+
fieldState: {
|
257
|
+
fieldId,
|
258
|
+
value: initialValue,
|
259
|
+
error: initialError,
|
260
|
+
disabled:
|
261
|
+
disabled || fieldDisabled || (isAutoColumn && !editAutoColumns),
|
262
|
+
readonly:
|
263
|
+
readonly || fieldReadOnly || (schema?.[field] as any)?.readonly,
|
264
|
+
defaultValue,
|
265
|
+
validator,
|
266
|
+
lastUpdate: Date.now(),
|
267
|
+
},
|
268
|
+
fieldApi: makeFieldApi(field),
|
269
|
+
fieldSchema: schema?.[field] ?? {},
|
270
|
+
});
|
271
|
+
|
272
|
+
// Add this field
|
273
|
+
if (existingField) {
|
274
|
+
const otherFields = fields.filter((info) => get(info).name !== field);
|
275
|
+
fields = [...otherFields, fieldInfo];
|
276
|
+
} else {
|
277
|
+
fields = [...fields, fieldInfo];
|
278
|
+
}
|
279
|
+
|
280
|
+
return fieldInfo;
|
281
|
+
},
|
282
|
+
validate: () => {
|
283
|
+
const stepFields = fields.filter(
|
284
|
+
(field) => get(field).step === get(currentStep)
|
285
|
+
);
|
286
|
+
// We want to validate every field (even if validation fails early) to
|
287
|
+
// ensure that all fields are populated with errors if invalid
|
288
|
+
let valid = true;
|
289
|
+
let hasScrolled = false;
|
290
|
+
stepFields.forEach((field) => {
|
291
|
+
const fieldValid = get(field).fieldApi.validate();
|
292
|
+
valid = valid && fieldValid;
|
293
|
+
if (!valid && !hasScrolled) {
|
294
|
+
handleScrollToField({ field: get(field) });
|
295
|
+
hasScrolled = true;
|
296
|
+
}
|
297
|
+
});
|
298
|
+
|
299
|
+
return valid;
|
300
|
+
},
|
301
|
+
reset: () => {
|
302
|
+
// Reset the form by resetting each individual field
|
303
|
+
fields.forEach((field) => {
|
304
|
+
get(field).fieldApi.reset();
|
305
|
+
});
|
306
|
+
},
|
307
|
+
changeStep: ({
|
308
|
+
type,
|
309
|
+
number,
|
310
|
+
}: {
|
311
|
+
type: "next" | "prev" | "first" | "specific";
|
312
|
+
number: any;
|
313
|
+
}) => {
|
314
|
+
if (type === "next") {
|
315
|
+
currentStep.update((step) => step + 1);
|
316
|
+
} else if (type === "prev") {
|
317
|
+
currentStep.update((step) => Math.max(1, step - 1));
|
318
|
+
} else if (type === "first") {
|
319
|
+
currentStep.set(1);
|
320
|
+
} else if (type === "specific" && number && !isNaN(number)) {
|
321
|
+
currentStep.set(parseInt(number));
|
322
|
+
}
|
323
|
+
},
|
324
|
+
setStep: (step: number) => {
|
325
|
+
if (step) {
|
326
|
+
currentStep.set(step);
|
327
|
+
}
|
328
|
+
},
|
329
|
+
setFieldValue: (fieldName: string, value: any) => {
|
330
|
+
const field = getField(fieldName);
|
331
|
+
if (!field) {
|
332
|
+
return;
|
333
|
+
}
|
334
|
+
const { fieldApi } = get(field);
|
335
|
+
fieldApi.setValue(value);
|
336
|
+
},
|
337
|
+
resetField: (fieldName: string) => {
|
338
|
+
const field = getField(fieldName);
|
339
|
+
if (!field) {
|
340
|
+
return;
|
341
|
+
}
|
342
|
+
const { fieldApi } = get(field);
|
343
|
+
fieldApi.reset();
|
344
|
+
},
|
345
|
+
};
|
346
|
+
|
347
|
+
// Creates an API for a specific field
|
348
|
+
const makeFieldApi = (field: string) => {
|
349
|
+
// Sets the value for a certain field and invokes validation
|
350
|
+
const setValue = (value: any, skipCheck = false) => {
|
351
|
+
const fieldInfo = getField(field);
|
352
|
+
const { fieldState } = get(fieldInfo);
|
353
|
+
const { validator } = fieldState;
|
354
|
+
|
355
|
+
// Skip if the value is the same
|
356
|
+
if (!skipCheck && fieldState.value === value) {
|
357
|
+
return false;
|
358
|
+
}
|
359
|
+
|
360
|
+
// Update field state
|
361
|
+
const error = validator?.(value);
|
362
|
+
fieldInfo.update((state) => {
|
363
|
+
state.fieldState.value = value;
|
364
|
+
state.fieldState.error = error;
|
365
|
+
state.fieldState.lastUpdate = Date.now();
|
366
|
+
return state;
|
367
|
+
});
|
368
|
+
|
369
|
+
return true;
|
370
|
+
};
|
371
|
+
|
372
|
+
// Clears the value of a certain field back to the default value
|
373
|
+
const reset = () => {
|
374
|
+
const fieldInfo = getField(field);
|
375
|
+
const { fieldState } = get(fieldInfo);
|
376
|
+
const newValue = fieldState.defaultValue;
|
377
|
+
|
378
|
+
// Update field state
|
379
|
+
fieldInfo.update((state) => {
|
380
|
+
state.fieldState.value = newValue;
|
381
|
+
state.fieldState.error = null;
|
382
|
+
state.fieldState.lastUpdate = Date.now();
|
383
|
+
return state;
|
384
|
+
});
|
385
|
+
};
|
386
|
+
|
387
|
+
// We don't want to actually remove the field state when deregistering, just
|
388
|
+
// remove any errors and validation
|
389
|
+
const deregister = () => {
|
390
|
+
const fieldInfo = getField(field);
|
391
|
+
fieldInfo.update((state) => {
|
392
|
+
state.fieldState.validator = null;
|
393
|
+
state.fieldState.error = null;
|
394
|
+
return state;
|
395
|
+
});
|
396
|
+
};
|
397
|
+
|
398
|
+
// Updates the disabled state of a certain field
|
399
|
+
const setDisabled = (fieldDisabled: boolean) => {
|
400
|
+
const fieldInfo = getField(field);
|
401
|
+
|
402
|
+
// Auto columns are always disabled
|
403
|
+
const isAutoColumn = !!schema?.[field]?.autocolumn;
|
404
|
+
|
405
|
+
// Update disabled state
|
406
|
+
fieldInfo.update((state) => {
|
407
|
+
state.fieldState.disabled = disabled || fieldDisabled || isAutoColumn;
|
408
|
+
return state;
|
409
|
+
});
|
410
|
+
};
|
411
|
+
|
412
|
+
return {
|
413
|
+
setValue,
|
414
|
+
reset,
|
415
|
+
setDisabled,
|
416
|
+
deregister,
|
417
|
+
validate: () => {
|
418
|
+
// Validate the field by force setting the same value again
|
419
|
+
const fieldInfo = getField(field);
|
420
|
+
setValue(get(fieldInfo).fieldState.value, true);
|
421
|
+
return !get(fieldInfo).fieldState.error;
|
422
|
+
},
|
423
|
+
};
|
424
|
+
};
|
425
|
+
|
426
|
+
// Provide form state and api for full control by children
|
427
|
+
setContext("form", {
|
428
|
+
formState,
|
429
|
+
formApi,
|
430
|
+
|
431
|
+
// Datasource is needed by attachment fields to be able to upload files
|
432
|
+
// to the correct table ID
|
433
|
+
dataSource,
|
434
|
+
});
|
435
|
+
|
436
|
+
// Provide form step context so that forms without any step components
|
437
|
+
// register their fields to step 1
|
438
|
+
setContext("form-step", writable(1));
|
439
|
+
|
440
|
+
const handleUpdateFieldValue = ({
|
441
|
+
type,
|
442
|
+
field,
|
443
|
+
value,
|
444
|
+
}: {
|
445
|
+
type: "set" | "reset";
|
446
|
+
field: string;
|
447
|
+
value: any;
|
448
|
+
}) => {
|
449
|
+
if (type === "set") {
|
450
|
+
formApi.setFieldValue(field, value);
|
451
|
+
} else {
|
452
|
+
formApi.resetField(field);
|
453
|
+
}
|
454
|
+
};
|
455
|
+
|
456
|
+
const handleScrollToField = (props: { field: FieldInfo | string }) => {
|
457
|
+
let field;
|
458
|
+
if (typeof props.field === "string") {
|
459
|
+
field = get(getField(props.field));
|
460
|
+
} else {
|
461
|
+
field = props.field;
|
462
|
+
}
|
463
|
+
const fieldId = field.fieldState.fieldId;
|
464
|
+
const fieldElement = document.getElementById(fieldId);
|
465
|
+
if (fieldElement) {
|
466
|
+
fieldElement.focus({ preventScroll: true });
|
467
|
+
}
|
468
|
+
const label = document.querySelector<HTMLElement>(
|
469
|
+
`label[for="${fieldId}"]`
|
470
|
+
);
|
471
|
+
if (label) {
|
472
|
+
label.style.scrollMargin = "100px";
|
473
|
+
label.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
474
|
+
}
|
475
|
+
};
|
476
|
+
|
477
|
+
// Action context to pass to children
|
478
|
+
const actions = [
|
479
|
+
{ type: ActionTypes.ValidateForm, callback: formApi.validate },
|
480
|
+
{ type: ActionTypes.ClearForm, callback: formApi.reset },
|
481
|
+
{ type: ActionTypes.ChangeFormStep, callback: formApi.changeStep },
|
482
|
+
{ type: ActionTypes.UpdateFieldValue, callback: handleUpdateFieldValue },
|
483
|
+
{ type: ActionTypes.ScrollTo, callback: handleScrollToField },
|
484
|
+
];
|
485
|
+
|
486
|
+
// Helper functions
|
487
|
+
function uuid() {
|
488
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
|
489
|
+
/[xy]/g,
|
490
|
+
function (c) {
|
491
|
+
var r = (Math.random() * 16) | 0,
|
492
|
+
v = c == "x" ? r : (r & 0x3) | 0x8;
|
493
|
+
return v.toString(16);
|
494
|
+
}
|
495
|
+
);
|
496
|
+
}
|
497
|
+
|
498
|
+
function cloneDeep(obj: any) {
|
499
|
+
if (obj === null || typeof obj !== "object") {
|
500
|
+
return obj;
|
501
|
+
}
|
502
|
+
|
503
|
+
if (Array.isArray(obj)) {
|
504
|
+
return obj.map((item) => cloneDeep(item));
|
505
|
+
}
|
506
|
+
|
507
|
+
const cloned: any = {};
|
508
|
+
for (const key in obj) {
|
509
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
510
|
+
cloned[key] = cloneDeep(obj[key]);
|
511
|
+
}
|
512
|
+
}
|
513
|
+
return cloned;
|
514
|
+
}
|
515
|
+
|
516
|
+
function deepGet(obj: any, path: string) {
|
517
|
+
if (!obj) return undefined;
|
518
|
+
const parts = path.split(".");
|
519
|
+
let current = obj;
|
520
|
+
|
521
|
+
for (let i = 0; i < parts.length; i++) {
|
522
|
+
if (current === null || current === undefined) {
|
523
|
+
return undefined;
|
524
|
+
}
|
525
|
+
current = current[parts[i]];
|
526
|
+
}
|
527
|
+
|
528
|
+
return current;
|
529
|
+
}
|
530
|
+
|
531
|
+
function deepSet(obj: any, path: string, value: any) {
|
532
|
+
if (!obj) return;
|
533
|
+
const parts = path.split(".");
|
534
|
+
let current = obj;
|
535
|
+
|
536
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
537
|
+
const part = parts[i];
|
538
|
+
if (!current[part] || typeof current[part] !== "object") {
|
539
|
+
current[part] = {};
|
540
|
+
}
|
541
|
+
current = current[part];
|
542
|
+
}
|
543
|
+
|
544
|
+
current[parts[parts.length - 1]] = value;
|
545
|
+
}
|
546
|
+
</script>
|
547
|
+
|
548
|
+
{#if provideContext}
|
549
|
+
<Provider {actions} data={dataContext}>
|
550
|
+
<div class={size}>
|
551
|
+
<slot />
|
552
|
+
</div>
|
553
|
+
</Provider>
|
554
|
+
{:else}
|
555
|
+
<div class={size}>
|
556
|
+
<slot />
|
557
|
+
</div>
|
558
|
+
{/if}
|