@rokkit/forms 1.0.0-next.122
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/src/forms-old/input/types.d.ts +7 -0
- package/dist/src/forms-old/lib/form.d.ts +95 -0
- package/dist/src/forms-old/lib/index.d.ts +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/input/index.d.ts +18 -0
- package/dist/src/lib/builder.svelte.d.ts +140 -0
- package/dist/src/lib/deprecated/nested.d.ts +48 -0
- package/dist/src/lib/deprecated/nested.spec.d.ts +1 -0
- package/dist/src/lib/deprecated/validator.d.ts +30 -0
- package/dist/src/lib/deprecated/validator.spec.d.ts +1 -0
- package/dist/src/lib/fields.d.ts +16 -0
- package/dist/src/lib/fields.spec.d.ts +1 -0
- package/dist/src/lib/index.d.ts +7 -0
- package/dist/src/lib/layout.d.ts +7 -0
- package/dist/src/lib/schema.d.ts +7 -0
- package/dist/src/lib/validation.d.ts +41 -0
- package/dist/src/types.d.ts +5 -0
- package/package.json +38 -0
- package/src/DataEditor.svelte +30 -0
- package/src/FieldLayout.svelte +48 -0
- package/src/FormRenderer.svelte +118 -0
- package/src/Input.svelte +75 -0
- package/src/InputField.svelte +55 -0
- package/src/ListEditor.svelte +44 -0
- package/src/NestedEditor.svelte +85 -0
- package/src/forms-old/CheckBox.svelte +56 -0
- package/src/forms-old/DataEditor.svelte +30 -0
- package/src/forms-old/FieldLayout.svelte +48 -0
- package/src/forms-old/Form.svelte +17 -0
- package/src/forms-old/Icon.svelte +76 -0
- package/src/forms-old/Item.svelte +25 -0
- package/src/forms-old/ListEditor.svelte +44 -0
- package/src/forms-old/Tabs.svelte +57 -0
- package/src/forms-old/Wrapper.svelte +12 -0
- package/src/forms-old/input/Input.svelte +17 -0
- package/src/forms-old/input/InputField.svelte +70 -0
- package/src/forms-old/input/InputSelect.svelte +23 -0
- package/src/forms-old/input/InputSwitch.svelte +19 -0
- package/src/forms-old/input/types.js +29 -0
- package/src/forms-old/lib/form.js +72 -0
- package/src/forms-old/lib/index.js +12 -0
- package/src/forms-old/mocks/CustomField.svelte +7 -0
- package/src/forms-old/mocks/CustomWrapper.svelte +8 -0
- package/src/forms-old/mocks/Register.svelte +25 -0
- package/src/index.js +7 -0
- package/src/inp/Input.svelte +17 -0
- package/src/inp/InputField.svelte +69 -0
- package/src/inp/InputSelect.svelte +23 -0
- package/src/inp/InputSwitch.svelte +19 -0
- package/src/input/InputCheckbox.svelte +74 -0
- package/src/input/InputColor.svelte +42 -0
- package/src/input/InputDate.svelte +54 -0
- package/src/input/InputDateTime.svelte +54 -0
- package/src/input/InputEmail.svelte +63 -0
- package/src/input/InputFile.svelte +45 -0
- package/src/input/InputMonth.svelte +54 -0
- package/src/input/InputNumber.svelte +57 -0
- package/src/input/InputPassword.svelte +60 -0
- package/src/input/InputRadio.svelte +60 -0
- package/src/input/InputRange.svelte +51 -0
- package/src/input/InputSelect.svelte +71 -0
- package/src/input/InputSwitch.svelte +29 -0
- package/src/input/InputTel.svelte +60 -0
- package/src/input/InputText.svelte +60 -0
- package/src/input/InputTextArea.svelte +59 -0
- package/src/input/InputTime.svelte +54 -0
- package/src/input/InputUrl.svelte +60 -0
- package/src/input/InputWeek.svelte +54 -0
- package/src/input/index.js +23 -0
- package/src/lib/Input.svelte +291 -0
- package/src/lib/builder.svelte.js +359 -0
- package/src/lib/deprecated/Form.svelte +17 -0
- package/src/lib/deprecated/FormRenderer.svelte +121 -0
- package/src/lib/deprecated/nested.js +192 -0
- package/src/lib/deprecated/nested.spec.js +512 -0
- package/src/lib/deprecated/validator.js +137 -0
- package/src/lib/deprecated/validator.spec.js +348 -0
- package/src/lib/fields.js +119 -0
- package/src/lib/fields.spec.js +250 -0
- package/src/lib/index.js +7 -0
- package/src/lib/layout.js +63 -0
- package/src/lib/schema.js +32 -0
- package/src/lib/validation.js +273 -0
- package/src/types.js +29 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* FormRenderer component with snippet-based rendering
|
|
4
|
+
* Handles defaultInput and custom child snippet selection based on override flag
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import InputField from './InputField.svelte'
|
|
8
|
+
import { FormBuilder } from './lib/builder.svelte.js'
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
// FormBuilder binding
|
|
12
|
+
|
|
13
|
+
// Direct props (alternative to builder)
|
|
14
|
+
data = $bindable(),
|
|
15
|
+
schema = null,
|
|
16
|
+
layout = null,
|
|
17
|
+
|
|
18
|
+
// Event handlers
|
|
19
|
+
onupdate = undefined,
|
|
20
|
+
onvalidate = undefined,
|
|
21
|
+
|
|
22
|
+
// Styling
|
|
23
|
+
className = '',
|
|
24
|
+
|
|
25
|
+
// Custom snippet
|
|
26
|
+
child,
|
|
27
|
+
|
|
28
|
+
// Pass through any other props
|
|
29
|
+
...props
|
|
30
|
+
} = $props()
|
|
31
|
+
|
|
32
|
+
// Use provided builder or create one from data/schema/layout
|
|
33
|
+
let formBuilder = $derived(new FormBuilder(data, schema, layout))
|
|
34
|
+
// Get elements from the builder
|
|
35
|
+
let elements = $derived(() => formBuilder.elements)
|
|
36
|
+
|
|
37
|
+
// Handle field value changes
|
|
38
|
+
function handleFieldChange(element, newValue) {
|
|
39
|
+
const fieldPath = element.scope.replace(/^#\//, '')
|
|
40
|
+
|
|
41
|
+
if (formBuilder) {
|
|
42
|
+
// Update through FormBuilder
|
|
43
|
+
formBuilder.updateField(fieldPath, newValue)
|
|
44
|
+
} else {
|
|
45
|
+
// Update data directly
|
|
46
|
+
updateNestedValue(data, fieldPath, newValue)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Call onupdate callback if provided
|
|
50
|
+
if (onupdate) {
|
|
51
|
+
const currentData = formBuilder ? formBuilder.data : data
|
|
52
|
+
onupdate(currentData)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Trigger validation if handler provided
|
|
56
|
+
if (onvalidate) {
|
|
57
|
+
onvalidate(fieldPath, newValue)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Helper function to update nested object values
|
|
62
|
+
function updateNestedValue(obj, path, value) {
|
|
63
|
+
const keys = path.split('/')
|
|
64
|
+
let current = obj
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
67
|
+
if (!(keys[i] in current)) {
|
|
68
|
+
current[keys[i]] = {}
|
|
69
|
+
}
|
|
70
|
+
current = current[keys[i]]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
current[keys[keys.length - 1]] = value
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle focus events for validation
|
|
77
|
+
function handleFieldFocus(element) {
|
|
78
|
+
// Could trigger validation on focus if needed
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Handle blur events for validation
|
|
82
|
+
function handleFieldBlur(element) {
|
|
83
|
+
if (onvalidate) {
|
|
84
|
+
const fieldPath = element.scope.replace(/^#\//, '')
|
|
85
|
+
const currentValue = element.value
|
|
86
|
+
onvalidate(fieldPath, currentValue, 'blur')
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<!-- Form container -->
|
|
92
|
+
<div data-form-root class={className} {...props}>
|
|
93
|
+
<!-- <div data-form-field data-scope={formBuilder.elements[0].scope}>
|
|
94
|
+
{@render defaultInput(formBuilder.elements[0])}
|
|
95
|
+
</div> -->
|
|
96
|
+
{#each formBuilder.elements as element, index (index)}
|
|
97
|
+
<div data-form-field data-scope={element.scope}>
|
|
98
|
+
{#if element.override && child}
|
|
99
|
+
{@render child(element)}
|
|
100
|
+
{:else}
|
|
101
|
+
{@render defaultInput(element)}
|
|
102
|
+
{/if}
|
|
103
|
+
</div>
|
|
104
|
+
{/each}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Default input snippet -->
|
|
108
|
+
{#snippet defaultInput(element)}
|
|
109
|
+
<InputField
|
|
110
|
+
name={element.scope}
|
|
111
|
+
type={element.type}
|
|
112
|
+
value={element.value}
|
|
113
|
+
{...element.props}
|
|
114
|
+
onchange={(newValue) => handleFieldChange(element, newValue)}
|
|
115
|
+
onfocus={() => handleFieldFocus(element)}
|
|
116
|
+
onblur={() => handleFieldBlur(element)}
|
|
117
|
+
/>
|
|
118
|
+
{/snippet}
|
package/src/Input.svelte
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { Icon } from '@rokkit/ui'
|
|
3
|
+
import InputCheckbox from './input/InputCheckbox.svelte'
|
|
4
|
+
import InputColor from './input/InputColor.svelte'
|
|
5
|
+
import InputDate from './input/InputDate.svelte'
|
|
6
|
+
import InputDateTime from './input/InputDateTime.svelte'
|
|
7
|
+
import InputEmail from './input/InputEmail.svelte'
|
|
8
|
+
import InputFile from './input/InputFile.svelte'
|
|
9
|
+
import InputMonth from './input/InputMonth.svelte'
|
|
10
|
+
import InputNumber from './input/InputNumber.svelte'
|
|
11
|
+
import InputPassword from './input/InputPassword.svelte'
|
|
12
|
+
import InputRange from './input/InputRange.svelte'
|
|
13
|
+
import InputTel from './input/InputTel.svelte'
|
|
14
|
+
import InputText from './input/InputText.svelte'
|
|
15
|
+
import InputTextArea from './input/InputTextArea.svelte'
|
|
16
|
+
import InputTime from './input/InputTime.svelte'
|
|
17
|
+
import InputUrl from './input/InputUrl.svelte'
|
|
18
|
+
import InputWeek from './input/InputWeek.svelte'
|
|
19
|
+
import InputSelect from './input/InputSelect.svelte'
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
type = 'text',
|
|
23
|
+
value = $bindable(),
|
|
24
|
+
onchange = null,
|
|
25
|
+
oninput = null,
|
|
26
|
+
onfocus = null,
|
|
27
|
+
onblur = null,
|
|
28
|
+
icon = null,
|
|
29
|
+
...restProps
|
|
30
|
+
} = $props()
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div data-input-root>
|
|
34
|
+
{#if icon}
|
|
35
|
+
<Icon name={icon} />
|
|
36
|
+
{/if}
|
|
37
|
+
{#if type === 'number'}
|
|
38
|
+
<InputNumber bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
39
|
+
{:else if type === 'integer'}
|
|
40
|
+
<InputNumber bind:value {onchange} {oninput} {onfocus} {onblur} step="1" {...restProps} />
|
|
41
|
+
{:else if type === 'password'}
|
|
42
|
+
<InputPassword bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
43
|
+
{:else if type === 'checkbox'}
|
|
44
|
+
<InputCheckbox bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
45
|
+
{:else if type === 'color'}
|
|
46
|
+
<InputColor bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
47
|
+
{:else if type === 'date'}
|
|
48
|
+
<InputDate bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
49
|
+
{:else if type === 'email'}
|
|
50
|
+
<InputEmail bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
51
|
+
{:else if type === 'tel'}
|
|
52
|
+
<InputTel bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
53
|
+
{:else if type === 'time'}
|
|
54
|
+
<InputTime bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
55
|
+
{:else if ['datetime', 'datetime-local'].includes(type)}
|
|
56
|
+
<InputDateTime bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
57
|
+
{:else if type === 'month'}
|
|
58
|
+
<InputMonth bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
59
|
+
{:else if type === 'range'}
|
|
60
|
+
<InputRange bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
61
|
+
{:else if type === 'select'}
|
|
62
|
+
<InputSelect bind:value {onchange} {onfocus} {onblur} {...restProps} />
|
|
63
|
+
{:else if type === 'textarea'}
|
|
64
|
+
<InputTextArea bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
65
|
+
{:else if type === 'url'}
|
|
66
|
+
<InputUrl bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
67
|
+
{:else if type === 'week'}
|
|
68
|
+
<InputWeek bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
69
|
+
<!-- {:else if type === 'switch'}
|
|
70
|
+
<InputWeek bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} /> -->
|
|
71
|
+
{:else}
|
|
72
|
+
<!-- Default to text input -->
|
|
73
|
+
<InputText bind:value {onchange} {oninput} {onfocus} {onblur} {...restProps} />
|
|
74
|
+
{/if}
|
|
75
|
+
</div>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { pick, omit, isNil } from 'ramda'
|
|
3
|
+
import { Icon } from '@rokkit/ui'
|
|
4
|
+
import Input from './Input.svelte'
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
class: className,
|
|
8
|
+
name,
|
|
9
|
+
value = $bindable(),
|
|
10
|
+
type,
|
|
11
|
+
required,
|
|
12
|
+
status,
|
|
13
|
+
disabled,
|
|
14
|
+
message,
|
|
15
|
+
nolabel,
|
|
16
|
+
icon,
|
|
17
|
+
label,
|
|
18
|
+
description,
|
|
19
|
+
onchange,
|
|
20
|
+
...restProps
|
|
21
|
+
} = $props()
|
|
22
|
+
|
|
23
|
+
let rootProps = pick(['id'], restProps)
|
|
24
|
+
let properties = {
|
|
25
|
+
required,
|
|
26
|
+
readOnly: disabled,
|
|
27
|
+
...omit(['id'], restProps),
|
|
28
|
+
name
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div
|
|
33
|
+
data-field-root
|
|
34
|
+
{...rootProps}
|
|
35
|
+
class={className}
|
|
36
|
+
data-field-state={status}
|
|
37
|
+
data-field-type={type}
|
|
38
|
+
data-field-required={required}
|
|
39
|
+
data-field-disabled={disabled}
|
|
40
|
+
data-field-empty={isNil(value)}
|
|
41
|
+
data-has-icon={!isNil(icon)}
|
|
42
|
+
>
|
|
43
|
+
<div data-field aria-label={description ?? label ?? name}>
|
|
44
|
+
{#if label && !nolabel}
|
|
45
|
+
<label for={name}>{label}</label>
|
|
46
|
+
{/if}
|
|
47
|
+
<Input id={name} bind:value {type} {...properties} {onchange} {icon} />
|
|
48
|
+
</div>
|
|
49
|
+
{#if description}
|
|
50
|
+
<div data-description>{description}</div>
|
|
51
|
+
{/if}
|
|
52
|
+
{#if message}
|
|
53
|
+
<div data-message>{message}</div>
|
|
54
|
+
{/if}
|
|
55
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import List from './List.svelte'
|
|
3
|
+
import FieldLayout from './FieldLayout.svelte'
|
|
4
|
+
import { defaultFields } from '@rokkit/core'
|
|
5
|
+
import { getContext } from 'svelte'
|
|
6
|
+
|
|
7
|
+
// const dispatch = createEventDispatcher()
|
|
8
|
+
const registry = getContext('registry')
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
class: className,
|
|
12
|
+
value,
|
|
13
|
+
fields = defaultFields,
|
|
14
|
+
schema,
|
|
15
|
+
path = [],
|
|
16
|
+
below = false,
|
|
17
|
+
children
|
|
18
|
+
} = $props()
|
|
19
|
+
|
|
20
|
+
let index = 0
|
|
21
|
+
let item = $state(value[index])
|
|
22
|
+
let location = $state([...path, index])
|
|
23
|
+
|
|
24
|
+
function handleSelect(event) {
|
|
25
|
+
index = event.detail.indices[0]
|
|
26
|
+
item = event.detail.item
|
|
27
|
+
location = [...path, ...event.detail.indices]
|
|
28
|
+
dispatch('select', { item: value, indices: path })
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<list-editor class="flex {className}">
|
|
33
|
+
<List
|
|
34
|
+
bind:items={value}
|
|
35
|
+
bind:value={item}
|
|
36
|
+
{fields}
|
|
37
|
+
using={registry.components}
|
|
38
|
+
on:select={handleSelect}
|
|
39
|
+
/>
|
|
40
|
+
<item-editor class="flex" class:below>
|
|
41
|
+
{@render children?.()}
|
|
42
|
+
<FieldLayout bind:value={item} {schema} path={location} />
|
|
43
|
+
</item-editor>
|
|
44
|
+
</list-editor>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { pick } from 'ramda'
|
|
3
|
+
import { createEventDispatcher } from 'svelte'
|
|
4
|
+
import { Tree, DataEditor, Tree as TreeTable, Tabs } from '@rokkit/ui'
|
|
5
|
+
import { generateTreeTable, deriveNestedSchema } from './lib'
|
|
6
|
+
|
|
7
|
+
const dispatch = createEventDispatcher()
|
|
8
|
+
|
|
9
|
+
export let value
|
|
10
|
+
export let schema = deriveNestedSchema(value)
|
|
11
|
+
export let using = {}
|
|
12
|
+
export let fields = {
|
|
13
|
+
text: 'key',
|
|
14
|
+
icon: 'type',
|
|
15
|
+
iconPrefix: 'type'
|
|
16
|
+
}
|
|
17
|
+
let node = {
|
|
18
|
+
schema: null,
|
|
19
|
+
layout: null
|
|
20
|
+
}
|
|
21
|
+
let nodeValue = value
|
|
22
|
+
let nodeType = null
|
|
23
|
+
let nodeItem = null
|
|
24
|
+
let columns = [
|
|
25
|
+
{ key: 'scope', path: true, label: 'path', fields: { text: 'key' } },
|
|
26
|
+
{ key: 'value', label: 'value', fields: { text: 'value', icon: 'type', iconPrefix: 'type' } }
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
function handleChange() {
|
|
30
|
+
dispatch('change', value)
|
|
31
|
+
}
|
|
32
|
+
function handleMove(event) {
|
|
33
|
+
dispatch('change', value)
|
|
34
|
+
const scope = event.detail.scope.split('/').slice(1)
|
|
35
|
+
|
|
36
|
+
node.schema = pick(['type', 'properties', 'items'], event.detail)
|
|
37
|
+
node.layout = event.detail.layout
|
|
38
|
+
|
|
39
|
+
nodeValue = value
|
|
40
|
+
nodeType = event.detail.type
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < scope.length; i++) {
|
|
43
|
+
nodeValue = nodeValue[scope[i]]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
$: tableData = node?.layout ? [] : generateTreeTable(nodeValue ?? value, 'scope', true)
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<container class="flex h-full w-full flex-row">
|
|
51
|
+
<aside class="border-r-neutral-subtle flex h-full w-80 border-r">
|
|
52
|
+
<Tree items={schema} {fields} class="h-full w-full" on:move={handleMove} />
|
|
53
|
+
</aside>
|
|
54
|
+
<content class="flex h-full w-full flex-col gap-4 overflow-hidden p-8">
|
|
55
|
+
<slot />
|
|
56
|
+
<section class="flex w-full flex-grow flex-col overflow-auto">
|
|
57
|
+
{#if !nodeValue}
|
|
58
|
+
<p>Select a node to edit</p>
|
|
59
|
+
<TreeTable data={tableData} {columns} class="" />
|
|
60
|
+
{:else if node.layout}
|
|
61
|
+
{#if nodeType === 'array'}
|
|
62
|
+
<p>Arrays are not supported yet.</p>
|
|
63
|
+
<Tabs options={nodeValue} bind:value={nodeItem} />
|
|
64
|
+
{#if nodeItem}
|
|
65
|
+
<DataEditor
|
|
66
|
+
bind:value={nodeItem}
|
|
67
|
+
layout={node.layout}
|
|
68
|
+
schema={node.schema.items}
|
|
69
|
+
{using}
|
|
70
|
+
/>
|
|
71
|
+
<pre>{JSON.stringify(nodeItem, null, 2)}</pre>
|
|
72
|
+
{/if}
|
|
73
|
+
{:else}
|
|
74
|
+
<DataEditor bind:value={nodeValue} {...node} {using} on:change={handleChange} />
|
|
75
|
+
{/if}
|
|
76
|
+
{:else}
|
|
77
|
+
<p>
|
|
78
|
+
No atomic attributes at this level. Select a child node to edit. Current value is below.
|
|
79
|
+
</p>
|
|
80
|
+
<TreeTable data={tableData} {columns} />
|
|
81
|
+
{/if}
|
|
82
|
+
</section>
|
|
83
|
+
<slot name="footer" />
|
|
84
|
+
</content>
|
|
85
|
+
</container>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { defaultStateIcons } from '@rokkit/core'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} Props
|
|
6
|
+
* @property {string} [class]
|
|
7
|
+
* @property {any} [id]
|
|
8
|
+
* @property {any} name
|
|
9
|
+
* @property {boolean} [value]
|
|
10
|
+
* @property {boolean} [readOnly]
|
|
11
|
+
* @property {any} [stateIcons]
|
|
12
|
+
* @property {number} [tabindex]
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** @type {Props} */
|
|
16
|
+
let {
|
|
17
|
+
class: classes = '',
|
|
18
|
+
id = null,
|
|
19
|
+
name,
|
|
20
|
+
value = $bindable(false),
|
|
21
|
+
readOnly = false,
|
|
22
|
+
stateIcons = defaultStateIcons.checkbox,
|
|
23
|
+
tabindex = 0
|
|
24
|
+
} = $props()
|
|
25
|
+
|
|
26
|
+
let state = $derived(value === null ? 'unknown' : value ? 'checked' : 'unchecked')
|
|
27
|
+
|
|
28
|
+
function toggle(event) {
|
|
29
|
+
event.preventDefault()
|
|
30
|
+
event.stopPropagation()
|
|
31
|
+
value = !value
|
|
32
|
+
}
|
|
33
|
+
function handleClick(event) {
|
|
34
|
+
if (!readOnly) toggle(event)
|
|
35
|
+
}
|
|
36
|
+
function handleKeydown(event) {
|
|
37
|
+
if (readOnly) return
|
|
38
|
+
if (event.key === 'Enter' || event.key === ' ') toggle(event)
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<rk-checkbox
|
|
43
|
+
{id}
|
|
44
|
+
class={classes}
|
|
45
|
+
class:disabled={readOnly}
|
|
46
|
+
role="checkbox"
|
|
47
|
+
aria-checked={state}
|
|
48
|
+
aria-disabled={readOnly}
|
|
49
|
+
onclick={handleClick}
|
|
50
|
+
onkeydown={handleKeydown}
|
|
51
|
+
{tabindex}
|
|
52
|
+
>
|
|
53
|
+
<input hidden type="checkbox" {name} {readOnly} bind:checked={value} />
|
|
54
|
+
|
|
55
|
+
<icon class={stateIcons[state]}></icon>
|
|
56
|
+
</rk-checkbox>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { setContext } from 'svelte'
|
|
3
|
+
import { types } from './input/types'
|
|
4
|
+
import Wrapper from './Wrapper.svelte'
|
|
5
|
+
import Item from './Item.svelte'
|
|
6
|
+
import Tabs from './Tabs.svelte'
|
|
7
|
+
import FieldLayout from './FieldLayout.svelte'
|
|
8
|
+
import { noop } from '@rokkit/core'
|
|
9
|
+
|
|
10
|
+
const registry = $state({})
|
|
11
|
+
setContext('registry', registry)
|
|
12
|
+
|
|
13
|
+
import { deriveSchemaFromValue, deriveLayoutFromValue, getSchemaWithLayout } from './lib'
|
|
14
|
+
|
|
15
|
+
let { value, schema = null, layout = null, using = {}, onchange = noop } = $props()
|
|
16
|
+
|
|
17
|
+
registry.editors = { ...types, ...using?.editors }
|
|
18
|
+
registry.components = { default: Item, ...using?.components }
|
|
19
|
+
registry.wrappers = { default: Wrapper, ...using?.wrappers }
|
|
20
|
+
registry.navigators = { default: Tabs, ...using?.navigators }
|
|
21
|
+
|
|
22
|
+
let schemaWithLayout = $derived.by(() => {
|
|
23
|
+
return getSchemaWithLayout(
|
|
24
|
+
schema ?? deriveSchemaFromValue(value),
|
|
25
|
+
layout ?? deriveLayoutFromValue(value)
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<FieldLayout schema={schemaWithLayout} bind:value {onchange} />
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
import { omit } from 'ramda'
|
|
4
|
+
import InputField from './input/InputField.svelte'
|
|
5
|
+
import FieldLayout from './FieldLayout.svelte'
|
|
6
|
+
|
|
7
|
+
// const dispatch = createEventDispatcher()
|
|
8
|
+
const registry = getContext('registry')
|
|
9
|
+
|
|
10
|
+
export let value = {}
|
|
11
|
+
export let schema = {}
|
|
12
|
+
export let path = []
|
|
13
|
+
|
|
14
|
+
function handle() {
|
|
15
|
+
dispatch('change', value)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let Wrapper = registry.wrappers[schema.wrapper] ?? registry.wrappers.default
|
|
19
|
+
let wrapperProps = omit(['wrapper', 'elements', 'key'], schema)
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#if !Array.isArray(schema.elements)}
|
|
23
|
+
<error> Invalid schema. Expected schema to include an 'elements' array. </error>
|
|
24
|
+
{:else}
|
|
25
|
+
<Wrapper {...wrapperProps}>
|
|
26
|
+
{#each schema.elements as item, index (index)}
|
|
27
|
+
{@const elementPath = item.key ? [...path, item.key] : path}
|
|
28
|
+
{@const props = { ...item.props, path: elementPath }}
|
|
29
|
+
{@const nested = Array.isArray(item.elements) && item.elements.length > 0}
|
|
30
|
+
{@const Component = item.component
|
|
31
|
+
? (registry.components[item.component] ?? registry.components.default)
|
|
32
|
+
: null}
|
|
33
|
+
|
|
34
|
+
{#if nested}
|
|
35
|
+
{#if item.key}
|
|
36
|
+
<FieldLayout {...props} schema={item} bind:value={value[item.key]} on:change={handle} />
|
|
37
|
+
{:else}
|
|
38
|
+
<FieldLayout {...props} schema={item} bind:value on:change={handle} />
|
|
39
|
+
{/if}
|
|
40
|
+
{:else if Component}
|
|
41
|
+
<Component {...item.props} value={item.key ? value[item.key] : null} />
|
|
42
|
+
{:else}
|
|
43
|
+
{@const name = elementPath.join('.')}
|
|
44
|
+
<InputField {name} bind:value={value[item.key]} {...item.props} on:change={handle} />
|
|
45
|
+
{/if}
|
|
46
|
+
{/each}
|
|
47
|
+
</Wrapper>
|
|
48
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
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>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { createEmitter } from '@rokkit/core'
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} Props
|
|
5
|
+
* @property {string} [class]
|
|
6
|
+
* @property {any} name
|
|
7
|
+
* @property {any} [state]
|
|
8
|
+
* @property {string} [size]
|
|
9
|
+
* @property {'img'|'button'|'checkbox'|'option'} [role]
|
|
10
|
+
* @property {any} [label]
|
|
11
|
+
* @property {boolean} [disabled]
|
|
12
|
+
* @property {number} [tabindex]
|
|
13
|
+
* @property {any} [checked]
|
|
14
|
+
* @event {MouseEvent} [onclick]
|
|
15
|
+
* @event {CustomEvent} [onchange]
|
|
16
|
+
* @event {MouseEvent} [onmouseenter]
|
|
17
|
+
* @event {MouseEvent} [onmouseleave]
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** @type {Props} */
|
|
21
|
+
let {
|
|
22
|
+
ref = $bindable(),
|
|
23
|
+
class: classes = '',
|
|
24
|
+
name,
|
|
25
|
+
state = null,
|
|
26
|
+
size = 'base',
|
|
27
|
+
role = 'img',
|
|
28
|
+
label = null,
|
|
29
|
+
disabled = false,
|
|
30
|
+
tabindex = $bindable(0),
|
|
31
|
+
checked = $bindable(null),
|
|
32
|
+
...events
|
|
33
|
+
} = $props()
|
|
34
|
+
|
|
35
|
+
let emitter = $derived(createEmitter(events, ['click', 'change', 'mouseenter', 'mouseleave']))
|
|
36
|
+
function handleClick(e) {
|
|
37
|
+
if (role === 'img') return
|
|
38
|
+
e.preventDefault()
|
|
39
|
+
|
|
40
|
+
if (!disabled) {
|
|
41
|
+
if (isCheckbox) {
|
|
42
|
+
checked = !checked
|
|
43
|
+
emitter?.change(checked)
|
|
44
|
+
}
|
|
45
|
+
emitter?.click()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let isCheckbox = $derived(role === 'checkbox' || role === 'option')
|
|
50
|
+
let validatedTabindex = $derived(role === 'img' || disabled ? -1 : tabindex)
|
|
51
|
+
let ariaChecked = $derived(
|
|
52
|
+
['checkbox', 'option'].includes(role) ? (checked !== null ? checked : false) : null
|
|
53
|
+
)
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
57
|
+
<rk-icon
|
|
58
|
+
bind:this={ref}
|
|
59
|
+
data-tag-icon
|
|
60
|
+
data-state={state}
|
|
61
|
+
data-tag-button={role === 'button' ? true : undefined}
|
|
62
|
+
data-tag-checkbox={isCheckbox ? true : undefined}
|
|
63
|
+
data-size={size}
|
|
64
|
+
data-disabled={disabled}
|
|
65
|
+
class={classes}
|
|
66
|
+
{role}
|
|
67
|
+
aria-label={label ?? name}
|
|
68
|
+
aria-checked={ariaChecked}
|
|
69
|
+
onclick={handleClick}
|
|
70
|
+
onkeydown={(e) => e.key === 'Enter' && e.currentTarget.click()}
|
|
71
|
+
tabindex={validatedTabindex}
|
|
72
|
+
onmouseenter={emitter.mouseenter}
|
|
73
|
+
onnmouseleave={emitter.nmouseleave}
|
|
74
|
+
>
|
|
75
|
+
<i class={name} aria-hidden="true"></i>
|
|
76
|
+
</rk-icon>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Icon from './Icon.svelte'
|
|
3
|
+
import { Proxy } from '@rokkit/states'
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} Props
|
|
6
|
+
* @property {import('@rokkit/states').NodeProxy} [value]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** @type {Props} */
|
|
10
|
+
let { value, fields } = $props()
|
|
11
|
+
let proxy = $derived(new Proxy(value, fields))
|
|
12
|
+
let content = $derived(proxy.get('text'))
|
|
13
|
+
let ariaLabel = $derived(proxy.get('label') ?? content)
|
|
14
|
+
let icon = $derived(proxy.get('icon'))
|
|
15
|
+
let image = $derived(proxy.get('image'))
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
{#if icon}
|
|
19
|
+
<Icon name={icon} label={ariaLabel} />
|
|
20
|
+
{:else if image}
|
|
21
|
+
<img src={image} alt={ariaLabel} />
|
|
22
|
+
{/if}
|
|
23
|
+
{#if content}
|
|
24
|
+
<p>{content}</p>
|
|
25
|
+
{/if}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// import List from './List.svelte'
|
|
3
|
+
import FieldLayout from './FieldLayout.svelte'
|
|
4
|
+
import { defaultFields } from '@rokkit/core'
|
|
5
|
+
import { getContext } from 'svelte'
|
|
6
|
+
|
|
7
|
+
// const dispatch = createEventDispatcher()
|
|
8
|
+
const registry = getContext('registry')
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
class: className,
|
|
12
|
+
value,
|
|
13
|
+
fields = defaultFields,
|
|
14
|
+
schema,
|
|
15
|
+
path = [],
|
|
16
|
+
below = false,
|
|
17
|
+
children
|
|
18
|
+
} = $props()
|
|
19
|
+
|
|
20
|
+
let index = 0
|
|
21
|
+
let item = $state(value[index])
|
|
22
|
+
let location = $state([...path, index])
|
|
23
|
+
|
|
24
|
+
function handleSelect(event) {
|
|
25
|
+
index = event.detail.indices[0]
|
|
26
|
+
item = event.detail.item
|
|
27
|
+
location = [...path, ...event.detail.indices]
|
|
28
|
+
dispatch('select', { item: value, indices: path })
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<list-editor class="flex {className}">
|
|
33
|
+
<!-- <List
|
|
34
|
+
bind:items={value}
|
|
35
|
+
bind:value={item}
|
|
36
|
+
{fields}
|
|
37
|
+
using={registry.components}
|
|
38
|
+
on:select={handleSelect}
|
|
39
|
+
/> -->
|
|
40
|
+
<item-editor class="flex" class:below>
|
|
41
|
+
{@render children?.()}
|
|
42
|
+
<FieldLayout bind:value={item} {schema} path={location} />
|
|
43
|
+
</item-editor>
|
|
44
|
+
</list-editor>
|