@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.
Files changed (84) hide show
  1. package/dist/src/forms-old/input/types.d.ts +7 -0
  2. package/dist/src/forms-old/lib/form.d.ts +95 -0
  3. package/dist/src/forms-old/lib/index.d.ts +1 -0
  4. package/dist/src/index.d.ts +5 -0
  5. package/dist/src/input/index.d.ts +18 -0
  6. package/dist/src/lib/builder.svelte.d.ts +140 -0
  7. package/dist/src/lib/deprecated/nested.d.ts +48 -0
  8. package/dist/src/lib/deprecated/nested.spec.d.ts +1 -0
  9. package/dist/src/lib/deprecated/validator.d.ts +30 -0
  10. package/dist/src/lib/deprecated/validator.spec.d.ts +1 -0
  11. package/dist/src/lib/fields.d.ts +16 -0
  12. package/dist/src/lib/fields.spec.d.ts +1 -0
  13. package/dist/src/lib/index.d.ts +7 -0
  14. package/dist/src/lib/layout.d.ts +7 -0
  15. package/dist/src/lib/schema.d.ts +7 -0
  16. package/dist/src/lib/validation.d.ts +41 -0
  17. package/dist/src/types.d.ts +5 -0
  18. package/package.json +38 -0
  19. package/src/DataEditor.svelte +30 -0
  20. package/src/FieldLayout.svelte +48 -0
  21. package/src/FormRenderer.svelte +118 -0
  22. package/src/Input.svelte +75 -0
  23. package/src/InputField.svelte +55 -0
  24. package/src/ListEditor.svelte +44 -0
  25. package/src/NestedEditor.svelte +85 -0
  26. package/src/forms-old/CheckBox.svelte +56 -0
  27. package/src/forms-old/DataEditor.svelte +30 -0
  28. package/src/forms-old/FieldLayout.svelte +48 -0
  29. package/src/forms-old/Form.svelte +17 -0
  30. package/src/forms-old/Icon.svelte +76 -0
  31. package/src/forms-old/Item.svelte +25 -0
  32. package/src/forms-old/ListEditor.svelte +44 -0
  33. package/src/forms-old/Tabs.svelte +57 -0
  34. package/src/forms-old/Wrapper.svelte +12 -0
  35. package/src/forms-old/input/Input.svelte +17 -0
  36. package/src/forms-old/input/InputField.svelte +70 -0
  37. package/src/forms-old/input/InputSelect.svelte +23 -0
  38. package/src/forms-old/input/InputSwitch.svelte +19 -0
  39. package/src/forms-old/input/types.js +29 -0
  40. package/src/forms-old/lib/form.js +72 -0
  41. package/src/forms-old/lib/index.js +12 -0
  42. package/src/forms-old/mocks/CustomField.svelte +7 -0
  43. package/src/forms-old/mocks/CustomWrapper.svelte +8 -0
  44. package/src/forms-old/mocks/Register.svelte +25 -0
  45. package/src/index.js +7 -0
  46. package/src/inp/Input.svelte +17 -0
  47. package/src/inp/InputField.svelte +69 -0
  48. package/src/inp/InputSelect.svelte +23 -0
  49. package/src/inp/InputSwitch.svelte +19 -0
  50. package/src/input/InputCheckbox.svelte +74 -0
  51. package/src/input/InputColor.svelte +42 -0
  52. package/src/input/InputDate.svelte +54 -0
  53. package/src/input/InputDateTime.svelte +54 -0
  54. package/src/input/InputEmail.svelte +63 -0
  55. package/src/input/InputFile.svelte +45 -0
  56. package/src/input/InputMonth.svelte +54 -0
  57. package/src/input/InputNumber.svelte +57 -0
  58. package/src/input/InputPassword.svelte +60 -0
  59. package/src/input/InputRadio.svelte +60 -0
  60. package/src/input/InputRange.svelte +51 -0
  61. package/src/input/InputSelect.svelte +71 -0
  62. package/src/input/InputSwitch.svelte +29 -0
  63. package/src/input/InputTel.svelte +60 -0
  64. package/src/input/InputText.svelte +60 -0
  65. package/src/input/InputTextArea.svelte +59 -0
  66. package/src/input/InputTime.svelte +54 -0
  67. package/src/input/InputUrl.svelte +60 -0
  68. package/src/input/InputWeek.svelte +54 -0
  69. package/src/input/index.js +23 -0
  70. package/src/lib/Input.svelte +291 -0
  71. package/src/lib/builder.svelte.js +359 -0
  72. package/src/lib/deprecated/Form.svelte +17 -0
  73. package/src/lib/deprecated/FormRenderer.svelte +121 -0
  74. package/src/lib/deprecated/nested.js +192 -0
  75. package/src/lib/deprecated/nested.spec.js +512 -0
  76. package/src/lib/deprecated/validator.js +137 -0
  77. package/src/lib/deprecated/validator.spec.js +348 -0
  78. package/src/lib/fields.js +119 -0
  79. package/src/lib/fields.spec.js +250 -0
  80. package/src/lib/index.js +7 -0
  81. package/src/lib/layout.js +63 -0
  82. package/src/lib/schema.js +32 -0
  83. package/src/lib/validation.js +273 -0
  84. 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}
@@ -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>