@rokkit/forms 1.0.0-next.125 → 1.0.0-next.127
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 +251 -0
- package/dist/src/display/index.d.ts +5 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/input/index.d.ts +3 -0
- package/dist/src/lib/builder.svelte.d.ts +114 -4
- package/dist/src/lib/lookup.svelte.d.ts +87 -0
- package/dist/src/lib/renderers.d.ts +23 -0
- package/package.json +6 -4
- package/src/FieldLayout.svelte +4 -11
- package/src/FormRenderer.svelte +202 -61
- package/src/InfoField.svelte +26 -0
- package/src/Input.svelte +17 -61
- package/src/InputField.svelte +15 -11
- package/src/ValidationReport.svelte +52 -0
- package/src/display/DisplayCardGrid.svelte +68 -0
- package/src/display/DisplayList.svelte +31 -0
- package/src/display/DisplaySection.svelte +20 -0
- package/src/display/DisplayTable.svelte +68 -0
- package/src/display/DisplayValue.svelte +44 -0
- package/src/display/index.js +5 -0
- package/src/index.js +14 -0
- package/src/input/ArrayEditor.svelte +108 -0
- package/src/input/InputCheckbox.svelte +2 -3
- package/src/input/InputColor.svelte +6 -1
- package/src/input/InputDate.svelte +6 -1
- package/src/input/InputDateTime.svelte +6 -1
- package/src/input/InputEmail.svelte +6 -1
- package/src/input/InputFile.svelte +6 -2
- package/src/input/InputMonth.svelte +6 -1
- package/src/input/InputNumber.svelte +6 -1
- package/src/input/InputPassword.svelte +6 -1
- package/src/input/InputRange.svelte +6 -1
- package/src/input/InputSelect.svelte +31 -53
- package/src/input/InputSwitch.svelte +4 -15
- package/src/input/InputTel.svelte +6 -1
- package/src/input/InputText.svelte +6 -1
- package/src/input/InputTextArea.svelte +6 -1
- package/src/input/InputTime.svelte +6 -1
- package/src/input/InputToggle.svelte +28 -0
- package/src/input/InputUrl.svelte +6 -1
- package/src/input/InputWeek.svelte +6 -1
- package/src/input/index.js +3 -1
- package/src/lib/Input.svelte +3 -3
- package/src/lib/builder.svelte.js +425 -30
- package/src/lib/fields.js +2 -2
- package/src/lib/layout.js +2 -2
- package/src/lib/lookup.svelte.js +334 -0
- package/src/lib/renderers.js +83 -0
- package/src/lib/schema.js +1 -1
- package/src/types.js +0 -9
- package/dist/src/forms-old/input/types.d.ts +0 -7
- package/dist/src/forms-old/lib/form.d.ts +0 -95
- package/dist/src/forms-old/lib/index.d.ts +0 -1
- package/dist/src/lib/deprecated/nested.d.ts +0 -48
- package/dist/src/lib/deprecated/nested.spec.d.ts +0 -1
- package/dist/src/lib/deprecated/validator.d.ts +0 -30
- package/dist/src/lib/deprecated/validator.spec.d.ts +0 -1
- package/src/DataEditor.svelte +0 -30
- package/src/ListEditor.svelte +0 -44
- package/src/NestedEditor.svelte +0 -85
- package/src/forms-old/CheckBox.svelte +0 -56
- package/src/forms-old/DataEditor.svelte +0 -30
- package/src/forms-old/FieldLayout.svelte +0 -48
- package/src/forms-old/Form.svelte +0 -17
- package/src/forms-old/Icon.svelte +0 -76
- package/src/forms-old/Item.svelte +0 -25
- package/src/forms-old/ListEditor.svelte +0 -44
- package/src/forms-old/Tabs.svelte +0 -57
- package/src/forms-old/Wrapper.svelte +0 -12
- package/src/forms-old/input/Input.svelte +0 -17
- package/src/forms-old/input/InputField.svelte +0 -70
- package/src/forms-old/input/InputSelect.svelte +0 -23
- package/src/forms-old/input/InputSwitch.svelte +0 -19
- package/src/forms-old/input/types.js +0 -29
- package/src/forms-old/lib/form.js +0 -72
- package/src/forms-old/lib/index.js +0 -12
- package/src/forms-old/mocks/CustomField.svelte +0 -7
- package/src/forms-old/mocks/CustomWrapper.svelte +0 -8
- package/src/forms-old/mocks/Register.svelte +0 -25
- package/src/inp/Input.svelte +0 -17
- package/src/inp/InputField.svelte +0 -69
- package/src/inp/InputSelect.svelte +0 -23
- package/src/inp/InputSwitch.svelte +0 -19
- package/src/lib/deprecated/Form.svelte +0 -17
- package/src/lib/deprecated/FormRenderer.svelte +0 -121
- package/src/lib/deprecated/nested.js +0 -192
- package/src/lib/deprecated/nested.spec.js +0 -512
- package/src/lib/deprecated/validator.js +0 -137
- package/src/lib/deprecated/validator.spec.js +0 -348
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
export const messages = {
|
|
2
|
-
required: '{name} is required',
|
|
3
|
-
email: '{name} should be a valid email address',
|
|
4
|
-
url: '{name} should be a valid URL',
|
|
5
|
-
color: '{name} should be a valid color',
|
|
6
|
-
number: '{name} should be a valid number',
|
|
7
|
-
min: '{name} should be greater than or equal to {min}',
|
|
8
|
-
max: '{name} should be less than or equal to {max}',
|
|
9
|
-
pattern: '{name} should match the pattern {pattern}',
|
|
10
|
-
exclusiveMin: '{name} should be greater than {min}',
|
|
11
|
-
exclusiveMax: '{name} should be less than {max}',
|
|
12
|
-
minLength: '{name} should be at least {minLength} characters',
|
|
13
|
-
maxLength: '{name} should be at most {maxLength} characters',
|
|
14
|
-
minItems: '{name} should have at least {minItems} items',
|
|
15
|
-
maxItems: '{name} should have at most {maxItems} items',
|
|
16
|
-
uniqueItems: '{name} should have unique items',
|
|
17
|
-
contains: '{name} should contain {contains}',
|
|
18
|
-
exclude: '{name} should not contain {exclude}',
|
|
19
|
-
integer: '{name} should be an integer'
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const dataTypes = {
|
|
23
|
-
integer: {
|
|
24
|
-
editor: 'inputText',
|
|
25
|
-
props: { type: 'number', step: 1 },
|
|
26
|
-
availableProps: ['min', 'max']
|
|
27
|
-
},
|
|
28
|
-
number: {
|
|
29
|
-
editor: 'inputText',
|
|
30
|
-
props: { type: 'number', step: 0.01 },
|
|
31
|
-
availableProps: ['min', 'max']
|
|
32
|
-
},
|
|
33
|
-
range: {
|
|
34
|
-
editor: 'inputRange',
|
|
35
|
-
props: { type: 'range' }
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
string: {
|
|
39
|
-
default: 'inputText',
|
|
40
|
-
text: 'inputText',
|
|
41
|
-
password: 'inputPassword',
|
|
42
|
-
email: 'inputEmail',
|
|
43
|
-
url: 'inputUrl',
|
|
44
|
-
tel: 'inputTel',
|
|
45
|
-
date: 'inputDate',
|
|
46
|
-
'datetime-local': 'inputDateTimeLocal',
|
|
47
|
-
time: 'inputTime',
|
|
48
|
-
week: 'inputWeek',
|
|
49
|
-
month: 'inputMonth',
|
|
50
|
-
file: 'inputFile',
|
|
51
|
-
hidden: 'inputHidden',
|
|
52
|
-
color: 'inputColor',
|
|
53
|
-
colorpicker: 'inputColorPicker'
|
|
54
|
-
},
|
|
55
|
-
enum: {
|
|
56
|
-
default: 'inputSelect',
|
|
57
|
-
select: 'inputSelect',
|
|
58
|
-
radio: 'inputRadio'
|
|
59
|
-
},
|
|
60
|
-
boolean: {
|
|
61
|
-
default: 'inputCheckbox',
|
|
62
|
-
checkbox: 'inputCheckbox',
|
|
63
|
-
switch: 'inputSwitch',
|
|
64
|
-
radio: 'inputRadio'
|
|
65
|
-
},
|
|
66
|
-
array: {
|
|
67
|
-
default: 'inputArray'
|
|
68
|
-
},
|
|
69
|
-
object: {
|
|
70
|
-
default: 'inputObject'
|
|
71
|
-
}
|
|
72
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export { messages, dataTypes } from './form'
|
|
2
|
-
export { deriveSchemaFromValue } from './schema'
|
|
3
|
-
export { deriveLayoutFromValue } from './layout'
|
|
4
|
-
export { getSchemaWithLayout, findAttributeByPath } from './fields'
|
|
5
|
-
export {
|
|
6
|
-
deriveNestedSchema,
|
|
7
|
-
flattenAttributes,
|
|
8
|
-
flattenObject,
|
|
9
|
-
flattenElement,
|
|
10
|
-
generateIndex,
|
|
11
|
-
generateTreeTable
|
|
12
|
-
} from './nested'
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { setContext } from 'svelte'
|
|
3
|
-
// import { writable } from 'svelte/store'
|
|
4
|
-
import Wrapper from '..//Wrapper.svelte'
|
|
5
|
-
import Item from '../Item.svelte'
|
|
6
|
-
import Tabs from '../Tabs.svelte'
|
|
7
|
-
const registry = $state({})
|
|
8
|
-
setContext('registry', registry)
|
|
9
|
-
|
|
10
|
-
let { Template, using, properties } = $props()
|
|
11
|
-
|
|
12
|
-
using = {
|
|
13
|
-
editors: {},
|
|
14
|
-
components: {},
|
|
15
|
-
wrappers: {},
|
|
16
|
-
navigators: {},
|
|
17
|
-
...using
|
|
18
|
-
}
|
|
19
|
-
registry.editors = { ...using.editors }
|
|
20
|
-
registry.components = { default: Item, ...using.components }
|
|
21
|
-
registry.wrappers = { default: Wrapper, ...using.wrappers }
|
|
22
|
-
registry.navigators = { default: Tabs, ...using.navigators }
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<Template {...properties} />
|
package/src/inp/Input.svelte
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { getContext } from 'svelte'
|
|
3
|
-
import { types } from './types'
|
|
4
|
-
|
|
5
|
-
const registry = getContext('registry')
|
|
6
|
-
|
|
7
|
-
let { value, type = 'text', using = {}, ...restProps } = $props()
|
|
8
|
-
|
|
9
|
-
using = { ...types, ...using, ...registry?.editors }
|
|
10
|
-
let Template = using[type]
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
{#if type in using}
|
|
14
|
-
<Template bind:value {...restProps} on:change on:focus on:blur />
|
|
15
|
-
{:else}
|
|
16
|
-
<error>Type "{type}" is not supported by Input</error>
|
|
17
|
-
{/if}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { getContext } from 'svelte'
|
|
3
|
-
import { pick, omit } from 'ramda'
|
|
4
|
-
import Icon from '../Icon.svelte'
|
|
5
|
-
import Input from './Input.svelte'
|
|
6
|
-
import { types } from './types'
|
|
7
|
-
|
|
8
|
-
const registry = getContext('registry')
|
|
9
|
-
|
|
10
|
-
let {
|
|
11
|
-
class: className,
|
|
12
|
-
name,
|
|
13
|
-
value,
|
|
14
|
-
type,
|
|
15
|
-
required,
|
|
16
|
-
status,
|
|
17
|
-
disabled,
|
|
18
|
-
message,
|
|
19
|
-
using,
|
|
20
|
-
nolabel,
|
|
21
|
-
icon,
|
|
22
|
-
label,
|
|
23
|
-
description,
|
|
24
|
-
...restProps
|
|
25
|
-
} = $props()
|
|
26
|
-
|
|
27
|
-
using = { ...types, ...registry, ...using }
|
|
28
|
-
let pass = status === 'pass'
|
|
29
|
-
let fail = status === 'fail'
|
|
30
|
-
let warn = status === 'warn'
|
|
31
|
-
let rootProps = pick(['id'], restProps)
|
|
32
|
-
let properties = {
|
|
33
|
-
required,
|
|
34
|
-
readOnly: disabled,
|
|
35
|
-
...omit(['id'], restProps),
|
|
36
|
-
name
|
|
37
|
-
}
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
<input-field
|
|
41
|
-
{...rootProps}
|
|
42
|
-
class="flex flex-col input-{type} {className} "
|
|
43
|
-
class:disabled
|
|
44
|
-
class:pass
|
|
45
|
-
class:fail
|
|
46
|
-
class:warn
|
|
47
|
-
class:empty={!value}
|
|
48
|
-
>
|
|
49
|
-
{#if label && !nolabel && !['switch', 'checkbox'].includes(type)}
|
|
50
|
-
<label for={name} class:required>
|
|
51
|
-
{label}
|
|
52
|
-
</label>
|
|
53
|
-
{/if}
|
|
54
|
-
<field class="flex w-full flex-row items-center" aria-label={description ?? label ?? name}>
|
|
55
|
-
{#if icon}
|
|
56
|
-
<Icon name={icon} />
|
|
57
|
-
{/if}
|
|
58
|
-
{#if type === 'switch'}
|
|
59
|
-
<label for={name} class:required>{label}</label>
|
|
60
|
-
{/if}
|
|
61
|
-
<Input id={name} bind:value {type} {...properties} {using} on:change />
|
|
62
|
-
{#if type === 'checkbox'}
|
|
63
|
-
<label for={name} class:required>{label}</label>
|
|
64
|
-
{/if}
|
|
65
|
-
</field>
|
|
66
|
-
{#if message}
|
|
67
|
-
<message class={status}>{message}</message>
|
|
68
|
-
{/if}
|
|
69
|
-
</input-field>
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { getValue, defaultFields } from '@rokkit/core'
|
|
3
|
-
import Select from '../Select.svelte'
|
|
4
|
-
|
|
5
|
-
let { name, value, options = [], fields, onchange, ...restProps } = $props()
|
|
6
|
-
|
|
7
|
-
let selected = $state()
|
|
8
|
-
let configFields = $derived({ ...defaultFields, ...fields })
|
|
9
|
-
|
|
10
|
-
function handle(data) {
|
|
11
|
-
value = getValue(data.value, configFields)
|
|
12
|
-
onchange?.(data)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
$effect(() => {
|
|
16
|
-
if (value !== getValue(selected, configFields)) {
|
|
17
|
-
selected = options.find((option) => getValue(option, configFields) === value)
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<input {name} type="hidden" bind:value />
|
|
23
|
-
<Select name="" value={selected} {options} {fields} {...restProps} onchange={handle} />
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { getValue, defaultFields } from '@rokkit/core'
|
|
3
|
-
import Switch from '../Switch.svelte'
|
|
4
|
-
|
|
5
|
-
let { name, value, options, fields, ...restProps } = $props()
|
|
6
|
-
// let selected = $state(null)
|
|
7
|
-
let configFields = $derived({ ...defaultFields, ...fields })
|
|
8
|
-
function handle(data) {
|
|
9
|
-
value = getValue(data.value, configFields)
|
|
10
|
-
dispatch('change', data.value)
|
|
11
|
-
}
|
|
12
|
-
let selected = $derived(options.find((option) => getValue(option, configFields) === value))
|
|
13
|
-
// $effect(() => {
|
|
14
|
-
// selected = options.find((option) => getValue(option, configFields) === value)
|
|
15
|
-
// })
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<input {name} type="hidden" bind:value />
|
|
19
|
-
<Switch bind:value={selected} {options} {fields} {...restProps} onchange={handle} />
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import DataEditor from './DataEditor.svelte'
|
|
3
|
-
|
|
4
|
-
export let value
|
|
5
|
-
export let schema = null
|
|
6
|
-
export let layout = null
|
|
7
|
-
export let using = {}
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<form on:submit>
|
|
11
|
-
<DataEditor bind:value {schema} {layout} {using} />
|
|
12
|
-
<span>
|
|
13
|
-
<slot>
|
|
14
|
-
<button type="submit">Submit</button>
|
|
15
|
-
</slot>
|
|
16
|
-
</span>
|
|
17
|
-
</form>
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
// Props using Svelte 5 runes
|
|
3
|
-
let { elements = [], onUpdate = null } = $props()
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Handle field value changes
|
|
7
|
-
* @param {string} scope - Field scope/path
|
|
8
|
-
* @param {any} value - New value
|
|
9
|
-
*/
|
|
10
|
-
function handleChange(scope, value) {
|
|
11
|
-
onUpdate?.(scope, value)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Handle range input changes (convert to number)
|
|
16
|
-
* @param {string} scope - Field scope/path
|
|
17
|
-
* @param {Event} event - Input event
|
|
18
|
-
*/
|
|
19
|
-
function handleRangeChange(scope, event) {
|
|
20
|
-
const value = Number(event.target.value)
|
|
21
|
-
handleChange(scope, value)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Handle number input changes (convert to number)
|
|
26
|
-
* @param {string} scope - Field scope/path
|
|
27
|
-
* @param {Event} event - Input event
|
|
28
|
-
*/
|
|
29
|
-
function handleNumberChange(scope, event) {
|
|
30
|
-
const value = Number(event.target.value)
|
|
31
|
-
handleChange(scope, value)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Handle checkbox changes
|
|
36
|
-
* @param {string} scope - Field scope/path
|
|
37
|
-
* @param {Event} event - Input event
|
|
38
|
-
*/
|
|
39
|
-
function handleCheckboxChange(scope, event) {
|
|
40
|
-
const value = event.target.checked
|
|
41
|
-
handleChange(scope, value)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Handle text input changes
|
|
46
|
-
* @param {string} scope - Field scope/path
|
|
47
|
-
* @param {Event} event - Input event
|
|
48
|
-
*/
|
|
49
|
-
function handleTextChange(scope, event) {
|
|
50
|
-
const value = event.target.value
|
|
51
|
-
handleChange(scope, value)
|
|
52
|
-
}
|
|
53
|
-
</script>
|
|
54
|
-
|
|
55
|
-
<div class="space-y-4">
|
|
56
|
-
{#each elements as element (element.scope)}
|
|
57
|
-
<div class="form-element">
|
|
58
|
-
{#if element.type === 'range'}
|
|
59
|
-
<label class="text-surface-700 dark:text-surface-300 block text-sm font-medium">
|
|
60
|
-
{element.label}: {element.value}
|
|
61
|
-
<input
|
|
62
|
-
type="range"
|
|
63
|
-
value={element.value}
|
|
64
|
-
min={element.constraints?.min}
|
|
65
|
-
max={element.constraints?.max}
|
|
66
|
-
step={element.constraints?.step}
|
|
67
|
-
oninput={(e) => handleRangeChange(element.scope, e)}
|
|
68
|
-
class="bg-surface-200 dark:bg-surface-700 mt-1 h-2 w-full cursor-pointer appearance-none rounded-lg"
|
|
69
|
-
/>
|
|
70
|
-
</label>
|
|
71
|
-
{:else if element.type === 'number'}
|
|
72
|
-
<label class="text-surface-700 dark:text-surface-300 block text-sm font-medium">
|
|
73
|
-
{element.label}
|
|
74
|
-
<input
|
|
75
|
-
type="number"
|
|
76
|
-
value={element.value}
|
|
77
|
-
min={element.constraints?.min}
|
|
78
|
-
max={element.constraints?.max}
|
|
79
|
-
step={element.constraints?.step}
|
|
80
|
-
oninput={(e) => handleNumberChange(element.scope, e)}
|
|
81
|
-
class="focus:border-primary-500 focus:ring-primary-500 border-surface-300 dark:border-surface-600 dark:bg-surface-700 mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none dark:text-white"
|
|
82
|
-
/>
|
|
83
|
-
</label>
|
|
84
|
-
{:else if element.type === 'checkbox'}
|
|
85
|
-
<label class="text-surface-700 dark:text-surface-300 flex items-center text-sm font-medium">
|
|
86
|
-
<input
|
|
87
|
-
type="checkbox"
|
|
88
|
-
checked={element.value}
|
|
89
|
-
onchange={(e) => handleCheckboxChange(element.scope, e)}
|
|
90
|
-
class="text-primary-600 focus:ring-primary-500 border-surface-300 dark:border-surface-600 mr-2 h-4 w-4 rounded"
|
|
91
|
-
/>
|
|
92
|
-
{element.label}
|
|
93
|
-
</label>
|
|
94
|
-
{:else if element.type === 'select'}
|
|
95
|
-
<label class="text-surface-700 dark:text-surface-300 block text-sm font-medium">
|
|
96
|
-
{element.label}
|
|
97
|
-
<select
|
|
98
|
-
value={element.value}
|
|
99
|
-
onchange={(e) => handleTextChange(element.scope, e)}
|
|
100
|
-
class="focus:border-primary-500 focus:ring-primary-500 border-surface-300 dark:border-surface-600 dark:bg-surface-700 mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none dark:text-white"
|
|
101
|
-
>
|
|
102
|
-
{#each element.constraints?.options || [] as option, index (index)}
|
|
103
|
-
<option value={option}>{option}</option>
|
|
104
|
-
{/each}
|
|
105
|
-
</select>
|
|
106
|
-
</label>
|
|
107
|
-
{:else}
|
|
108
|
-
<!-- Default text input -->
|
|
109
|
-
<label class="text-surface-700 dark:text-surface-300 block text-sm font-medium">
|
|
110
|
-
{element.label}
|
|
111
|
-
<input
|
|
112
|
-
type="text"
|
|
113
|
-
value={element.value || ''}
|
|
114
|
-
oninput={(e) => handleTextChange(element.scope, e)}
|
|
115
|
-
class="focus:border-primary-500 focus:ring-primary-500 border-surface-300 dark:border-surface-600 dark:bg-surface-700 mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none dark:text-white"
|
|
116
|
-
/>
|
|
117
|
-
</label>
|
|
118
|
-
{/if}
|
|
119
|
-
</div>
|
|
120
|
-
{/each}
|
|
121
|
-
</div>
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { omit, pick } from 'ramda'
|
|
2
|
-
import { isObject } from '@rokkit/core'
|
|
3
|
-
import { typeOf } from '@rokkit/data'
|
|
4
|
-
import { deriveSchemaFromValue } from '../schema'
|
|
5
|
-
import { deriveLayoutFromValue } from '../layout'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Flattens an object into a flat object
|
|
9
|
-
*
|
|
10
|
-
* @param {Object} input - The object to flatten
|
|
11
|
-
* @param {String} scope - The scope of the object
|
|
12
|
-
*/
|
|
13
|
-
export function flattenObject(input, scope = '#') {
|
|
14
|
-
// eslint-disable-next-line no-use-before-define
|
|
15
|
-
return flattenAttributes(input, scope).reduce(
|
|
16
|
-
// eslint-disable-next-line no-use-before-define
|
|
17
|
-
(acc, item) => ({ ...acc, ...flattenElement(item) }),
|
|
18
|
-
{
|
|
19
|
-
[scope]: {
|
|
20
|
-
type: 'object',
|
|
21
|
-
value: input,
|
|
22
|
-
scope,
|
|
23
|
-
key: scope.split('/').slice(-1)[0]
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Flattens an object into an array of key-value pairs
|
|
30
|
-
*
|
|
31
|
-
* @param {Object} input - The object to flatten
|
|
32
|
-
* @param {String} scope - The scope of the object
|
|
33
|
-
*/
|
|
34
|
-
export function flattenAttributes(input, scope = '#') {
|
|
35
|
-
return Object.entries(input).map(([key, value]) => ({
|
|
36
|
-
key,
|
|
37
|
-
value,
|
|
38
|
-
type: typeOf(value),
|
|
39
|
-
scope: [scope, key].join('/')
|
|
40
|
-
}))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Derives a nested schema from an object
|
|
45
|
-
*
|
|
46
|
-
* @param {Object} input - The object to derive the schema from
|
|
47
|
-
* @param {String} scope - The scope of the object
|
|
48
|
-
* @returns {Object} The derived schema
|
|
49
|
-
*/
|
|
50
|
-
export function deriveNestedSchema(input, scope = '#') {
|
|
51
|
-
const elements = flattenAttributes(input)
|
|
52
|
-
const atoms = elements.filter(({ type }) => !['object', 'array'].includes(type))
|
|
53
|
-
|
|
54
|
-
const schema = {
|
|
55
|
-
type: 'object'
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (atoms.length > 0) {
|
|
59
|
-
schema.properties = atoms.reduce(
|
|
60
|
-
(acc, { key, type, value }) => ({
|
|
61
|
-
...acc,
|
|
62
|
-
[key]: {
|
|
63
|
-
type,
|
|
64
|
-
default: value
|
|
65
|
-
}
|
|
66
|
-
}),
|
|
67
|
-
{}
|
|
68
|
-
)
|
|
69
|
-
schema.layout = {
|
|
70
|
-
type: 'vertical',
|
|
71
|
-
elements: atoms.map((el) => ({ label: el.key, scope: el.scope }))
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (atoms.length < elements.length) {
|
|
76
|
-
// eslint-disable-next-line no-use-before-define
|
|
77
|
-
schema.children = deriveSchemaForChildren(elements, scope)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (scope !== '#') return schema
|
|
81
|
-
return schema.properties ? [schema] : schema.children
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Derives the children of an object
|
|
86
|
-
*
|
|
87
|
-
* @param {Array} elements - The elements to derive children from
|
|
88
|
-
* @param {String} scope - The scope of the object
|
|
89
|
-
* @returns {Array} The derived children
|
|
90
|
-
*/
|
|
91
|
-
function deriveSchemaForChildren(elements, scope) {
|
|
92
|
-
return [
|
|
93
|
-
...elements
|
|
94
|
-
.filter(({ type }) => type === 'object')
|
|
95
|
-
.map((item) => ({
|
|
96
|
-
...omit(['value', 'scope'], item),
|
|
97
|
-
scope: [scope, item.key].join('/'),
|
|
98
|
-
...deriveNestedSchema(item.value, [scope, item.key].join('/'))
|
|
99
|
-
})),
|
|
100
|
-
...elements
|
|
101
|
-
.filter(({ type }) => type === 'array')
|
|
102
|
-
.map((item) => ({
|
|
103
|
-
...omit(['value'], item),
|
|
104
|
-
default: [],
|
|
105
|
-
scope: [scope, item.key].join('/'),
|
|
106
|
-
items: deriveSchemaFromValue(item.value.length ? item.value[0] : null),
|
|
107
|
-
layout: deriveLayoutFromValue(item.value.length ? item.value[0] : null)
|
|
108
|
-
}))
|
|
109
|
-
]
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Flattens an element into a flat object
|
|
114
|
-
*
|
|
115
|
-
* @param {Object} element - The element to flatten
|
|
116
|
-
*/
|
|
117
|
-
export function flattenElement(element) {
|
|
118
|
-
if (element.type === 'object') {
|
|
119
|
-
return flattenObject(element.value, element.scope)
|
|
120
|
-
} else if (element.type === 'array') {
|
|
121
|
-
return element.value
|
|
122
|
-
.map((item, index) => ({
|
|
123
|
-
value: item,
|
|
124
|
-
scope: [element.scope, `[${index}]`].join('/'),
|
|
125
|
-
key: `[${index}]`,
|
|
126
|
-
type: typeOf(item)
|
|
127
|
-
}))
|
|
128
|
-
.reduce((acc, item) => ({ ...acc, ...flattenElement(item) }), {
|
|
129
|
-
[element.scope]: pick(['key', 'type', 'scope', 'value'], element)
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
return { [element.scope]: element }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Generates an index array referencing the input data
|
|
137
|
-
*
|
|
138
|
-
* @param {Object} data - The flat object to index
|
|
139
|
-
* @param {String} key - The key to use as index
|
|
140
|
-
*/
|
|
141
|
-
export function generateIndex(data, key = 'scope') {
|
|
142
|
-
const index = data
|
|
143
|
-
.map((item) => ({
|
|
144
|
-
...item,
|
|
145
|
-
_path: item[key],
|
|
146
|
-
_isParent: false,
|
|
147
|
-
_isExpanded: true,
|
|
148
|
-
_levels: []
|
|
149
|
-
}))
|
|
150
|
-
.sort((a, b) => a[key].localeCompare(b[key]))
|
|
151
|
-
.filter((item) => item[key] !== '#')
|
|
152
|
-
|
|
153
|
-
let levels = [0]
|
|
154
|
-
let current = 0
|
|
155
|
-
|
|
156
|
-
index.forEach((item, row) => {
|
|
157
|
-
const path = item._path.split('/').slice(1)
|
|
158
|
-
item._depth = path.length - 1
|
|
159
|
-
if (row === 0) {
|
|
160
|
-
item._levels = [0]
|
|
161
|
-
} else if (path.length > levels.length) {
|
|
162
|
-
index[row - 1]._isParent = true
|
|
163
|
-
item._levels = [...levels, 0]
|
|
164
|
-
} else {
|
|
165
|
-
current = levels[path.length - 1] + 1
|
|
166
|
-
item._levels = [...levels.slice(0, path.length - 1), current]
|
|
167
|
-
}
|
|
168
|
-
levels = item._levels
|
|
169
|
-
})
|
|
170
|
-
return index
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Generates a tree table from the input data
|
|
175
|
-
*
|
|
176
|
-
* @param {Object} data - The data to generate the tree table from
|
|
177
|
-
* @param {String} key - The key to use as index
|
|
178
|
-
* @param {Boolean} ellipsis - Whether to truncate the value
|
|
179
|
-
*/
|
|
180
|
-
export function generateTreeTable(data, key = 'scope', ellipsis = false) {
|
|
181
|
-
let result = []
|
|
182
|
-
if (Array.isArray(data)) result = generateIndex(data, key)
|
|
183
|
-
if (isObject(data)) result = generateIndex(Object.values(flattenObject(data)), key)
|
|
184
|
-
|
|
185
|
-
if (ellipsis) {
|
|
186
|
-
result = result.map((item) => ({
|
|
187
|
-
...omit(['value'], item),
|
|
188
|
-
value: ['array', 'object'].includes(item.type) ? '...' : item.value
|
|
189
|
-
}))
|
|
190
|
-
}
|
|
191
|
-
return result
|
|
192
|
-
}
|