@rokkit/ui 1.0.0-next.107 → 1.0.0-next.108

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.d.ts CHANGED
@@ -22,7 +22,6 @@ export { default as Toggle } from "./Toggle.svelte";
22
22
  export { default as Switch } from "./Switch.svelte";
23
23
  export { default as List } from "./List.svelte";
24
24
  export { default as Accordion } from "./Accordion.svelte";
25
- export { default as NestedList } from "./NestedList.svelte";
26
25
  export { default as Tree } from "./Tree.svelte";
27
26
  export { default as Tabs } from "./Tabs.svelte";
28
27
  export { default as Select } from "./Select.svelte";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/ui",
3
- "version": "1.0.0-next.107",
3
+ "version": "1.0.0-next.108",
4
4
  "description": "Organisms are larger, more complex building blocks that are composed of multiple molecules",
5
5
  "author": "Jerry Thomas <me@jerrythomas.name>",
6
6
  "license": "MIT",
@@ -1,10 +1,18 @@
1
1
  <script>
2
2
  import { equals } from 'ramda'
3
- import { createEmitter, noop, getKeyFromPath, getSnippet } from '@rokkit/core'
4
- import { NestedProxy } from '@rokkit/states'
3
+ import {
4
+ createEmitter,
5
+ noop,
6
+ getKeyFromPath,
7
+ getSnippet,
8
+ defaultFields,
9
+ hasChildren
10
+ } from '@rokkit/core'
11
+ import { NestedController } from '@rokkit/states'
5
12
  import { navigator } from '@rokkit/actions'
6
13
  import Summary from './Summary.svelte'
7
14
  import Item from './Item.svelte'
15
+ import ListBody from './ListBody.svelte'
8
16
 
9
17
  /**
10
18
  * @typedef {Object} Props
@@ -35,39 +43,25 @@
35
43
  let emitter = $derived(
36
44
  createEmitter(events, ['collapse', 'change', 'expand', 'click', 'select', 'move'])
37
45
  )
38
- let wrapper = new NestedProxy(items, value, fields, { events, multiselect, autoCloseSiblings })
46
+ let wrapper = new NestedController(items, value, fields, {
47
+ events,
48
+ multiselect,
49
+ autoCloseSiblings
50
+ })
51
+ let derivedFields = $derived({ ...defaultFields, ...fields })
39
52
  </script>
40
53
 
41
- {#snippet listItems(nodes, onchange = noop)}
42
- {#each nodes as node}
43
- {@const template = getSnippet(extra, node.get('component')) ?? stub}
44
- {@const path = getKeyFromPath(node.path)}
45
- {@const props = node.get('props') || {}}
46
- <rk-list-item
47
- role="option"
48
- data-path={path}
49
- aria-selected={node.selected}
50
- aria-current={node.focused}
51
- >
52
- {#if template}
53
- {@render template(node, props, onchange)}
54
- {:else}
55
- <Item value={node.value} fields={node.fields} />
56
- {/if}
57
- </rk-list-item>
58
- {/each}
59
- {/snippet}
60
54
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
61
55
  <rk-accordion
62
56
  class={classes}
63
57
  tabindex="0"
64
- use:navigator={{ wrapper }}
58
+ use:navigator={{ wrapper, nested: true }}
65
59
  onactivate={() => (value = wrapper.value)}
66
60
  >
67
61
  {#if header}
68
62
  <rk-header>{@render header()}</rk-header>
69
63
  {/if}
70
- {#if wrapper.nodes.length === 0}
64
+ {#if items.length === 0}
71
65
  <rk-list-item role="presentation">
72
66
  {#if empty}
73
67
  {@render empty()}
@@ -76,22 +70,32 @@
76
70
  {/if}
77
71
  </rk-list-item>
78
72
  {/if}
79
- {#each wrapper.nodes as node, index}
73
+ {#each items as item, index}
74
+ {@const key = `${index}`}
75
+ {@const expanded = item[derivedFields.expanded]}
80
76
  <div
81
77
  class="flex flex-col"
82
- class:is-expanded={node.expanded}
83
- class:is-selected={node.selected}
78
+ class:is-expanded={expanded}
79
+ class:is-selected={wrapper.selectedKeys.has(key)}
84
80
  data-path={index}
85
81
  >
86
82
  <Summary
87
- bind:value={wrapper.nodes[index].value}
88
- fields={node.fields}
89
- expanded={node.expanded}
90
- hasChildren={node.hasChildren()}
83
+ bind:value={items[index]}
84
+ {fields}
85
+ {expanded}
86
+ hasChildren={hasChildren(item, derivedFields)}
91
87
  />
92
- {#if node.expanded}
88
+ {#if expanded}
93
89
  <rk-list role="listbox" tabindex="-1">
94
- {@render listItems(node.children, events.change)}
90
+ <ListBody
91
+ bind:items={items[fields.children]}
92
+ bind:value
93
+ fields={fields.fields ?? fields}
94
+ {selected}
95
+ {stub}
96
+ onchange={emitter.change}
97
+ {extra}
98
+ />
95
99
  </rk-list>
96
100
  {/if}
97
101
  </div>
package/src/Icon.svelte CHANGED
@@ -63,6 +63,7 @@
63
63
  onclick={handleClick}
64
64
  onkeydown={(e) => e.key === 'Enter' && e.currentTarget.click()}
65
65
  data-state={state}
66
+ data-tag="icon"
66
67
  tabindex={validatedTabindex}
67
68
  onmouseenter={emitter.mouseenter}
68
69
  onnmouseleave={emitter.nmouseleave}
package/src/Link.svelte CHANGED
@@ -12,8 +12,8 @@
12
12
 
13
13
  /** @type {Props} */
14
14
  let { class: classes = '', value, mapping = new FieldMapper(), href = '#', ...rest } = $props()
15
- let url = $derived(mapping.getAttribute(value, 'url') ?? href)
16
- let props = $derived({ ...mapping.getAttribute(value, 'props'), ...rest })
15
+ let url = $derived(mapping.get('url', value, href))
16
+ let props = $derived({ ...mapping.get('props', value, {}), ...rest })
17
17
  </script>
18
18
 
19
19
  <a href={url} class={classes} {...props}>
package/src/List.svelte CHANGED
@@ -1,8 +1,8 @@
1
1
  <script>
2
- import { createEmitter, noop, getKeyFromPath, getSnippet } from '@rokkit/core'
2
+ import { createEmitter } from '@rokkit/core'
3
3
  import { navigator } from '@rokkit/actions'
4
- import Item from './Item.svelte'
5
- import { ListProxy } from '@rokkit/states'
4
+ import ListBody from './ListBody.svelte'
5
+ import { ListController } from '@rokkit/states'
6
6
  import { omit, has } from 'ramda'
7
7
 
8
8
  /**
@@ -34,17 +34,21 @@
34
34
  ...events
35
35
  } = $props()
36
36
 
37
+ let selected = $state([])
38
+
37
39
  function handleAction(event) {
38
- value = wrapper.currentNode.value
39
- console.log(event.detail)
40
- if (has(event.detail.eventName, emitter)) {
41
- emitter[event.detail.eventName](event.detail.data)
40
+ const { name, data } = event.detail
41
+
42
+ if (has(name, emitter)) {
43
+ value = data.value
44
+ selected = data.selected
45
+ emitter[name](data)
42
46
  }
43
47
  }
44
48
 
45
49
  let emitter = createEmitter(events, ['select', 'change', 'move'])
46
50
  let extra = omit(['onselect', 'onchange', 'onmove'], events)
47
- let wrapper = new ListProxy(items, value, fields, { multiSelect })
51
+ let wrapper = new ListController(items, value, fields, { multiSelect })
48
52
  </script>
49
53
 
50
54
  <rk-list
@@ -59,30 +63,23 @@
59
63
  <rk-header>{@render header()}</rk-header>
60
64
  {/if}
61
65
  <rk-body>
62
- {#if wrapper.nodes.length === 0}
66
+ {#if items.length === 0}
63
67
  {#if empty}
64
68
  {@render empty()}
65
69
  {:else}
66
70
  <rk-message>No items found.</rk-message>
67
71
  {/if}
68
72
  {:else}
69
- {#each wrapper.nodes as node}
70
- {@const template = getSnippet(extra, node.get('component')) ?? stub}
71
- {@const path = getKeyFromPath(node.path)}
72
- {@const props = node.get('props') || {}}
73
- <rk-list-item
74
- role="option"
75
- data-path={path}
76
- aria-selected={node.selected}
77
- aria-current={node.focused}
78
- >
79
- {#if template}
80
- {@render template(node, props, emitter.change)}
81
- {:else}
82
- <Item value={node.value} fields={node.fields} />
83
- {/if}
84
- </rk-list-item>
85
- {/each}
73
+ <ListBody
74
+ bind:items
75
+ bind:value
76
+ {fields}
77
+ selectedKeys={wrapper.selectedKeys}
78
+ focusedKey={wrapper.focusedKey}
79
+ {stub}
80
+ onchange={emitter.change}
81
+ {extra}
82
+ />
86
83
  {/if}
87
84
  </rk-body>
88
85
  {#if footer}
@@ -0,0 +1,43 @@
1
+ <script>
2
+ import { getKeyFromPath, FieldMapper, getSnippet } from '@rokkit/core'
3
+ import Item from './Item.svelte'
4
+ import { equals } from 'ramda'
5
+
6
+ let {
7
+ items = $bindable([]),
8
+ value = null,
9
+ fields,
10
+ path = [],
11
+ onchange = () => {},
12
+ selectedKeys = new SvelteSet(),
13
+ focusedKey = null,
14
+ stub = null,
15
+ extra
16
+ } = $props()
17
+ let fm = $derived.by(() => {
18
+ return new FieldMapper(fields)
19
+ })
20
+ </script>
21
+
22
+ {#each items as item, index}
23
+ {@const template = getSnippet(extra, fm.get('snippet', item, stub))}
24
+ {@const pathKey = getKeyFromPath([...path, index])}
25
+ {@const props = fm.get('props', item) || {}}
26
+ <rk-list-item
27
+ role="option"
28
+ data-path={pathKey}
29
+ aria-selected={selectedKeys.has(pathKey)}
30
+ aria-current={focusedKey === pathKey}
31
+ >
32
+ <svelte:boundary>
33
+ {#if template}
34
+ {@render template(item, props, onchange)}
35
+ {#snippet failed()}
36
+ <Item value={item} {fields} />
37
+ {/snippet}
38
+ {:else}
39
+ <Item value={item} {fields} />
40
+ {/if}
41
+ </svelte:boundary>
42
+ </rk-list-item>
43
+ {/each}
@@ -1,33 +1,77 @@
1
1
  <script>
2
- import { defaultStateIcons, getLineTypes } from '@rokkit/core'
2
+ import { SvelteSet } from 'svelte/reactivity'
3
+ import {
4
+ defaultStateIcons,
5
+ getLineTypes,
6
+ getKeyFromPath,
7
+ getNestedFields,
8
+ hasChildren
9
+ } from '@rokkit/core'
3
10
  import Node from './Node.svelte'
4
11
  import NestedList from './NestedList.svelte'
5
12
 
6
13
  /**
7
14
  * @typedef {Object} Props
8
- * @property {Array<NodeProxy>} [items]
9
- * @property {import('./types').ConnectionType[]} [types]
10
- * @property {any} [value]
11
- * @property {import('./types').NodeStateIcons} icons
15
+ * @property {Array<NodeProxy>} [items=[]]
16
+ * @property {any} [value=null]
17
+ * @property {import('./types').FieldMapping} fields
18
+ * @property {number[]} [path=[]]
19
+ * @property {import('./types').NodeStateIcons} icons
20
+ * @property {import('./types').ConnectionType[]} [types=[]]
21
+ * @property {string} [focusedKey]
22
+ * @property {SvelteSet} [selectedKeys]
12
23
  */
13
24
 
14
25
  /** @type {Props} */
15
- let { items = $bindable([]), types = [], value = $bindable(null), icons = {} } = $props()
26
+ let {
27
+ items = $bindable([]),
28
+ value = $bindable(null),
29
+ fields,
30
+ path = [],
31
+ icons = {},
32
+ types = [],
33
+ focusedKey,
34
+ selectedKeys = new SvelteSet(),
35
+ stub,
36
+ snippets
37
+ } = $props()
16
38
 
17
39
  const stateIcons = $derived({ ...defaultStateIcons.node, ...icons })
40
+ const childFields = $derived(getNestedFields(fields))
41
+ // $inspect(childFields, items[0], expandedKeys)
18
42
  </script>
19
43
 
20
44
  <rk-nested-list role="tree">
21
45
  {#each items as item, index}
22
- {@const hasChildren = item.hasChildren()}
46
+ {@const nodePath = [...path, index]}
47
+ {@const key = getKeyFromPath(nodePath)}
48
+ {@const expanded = item[fields.expanded]}
23
49
  {@const nodeType = index === items.length - 1 ? 'last' : 'child'}
24
- {@const connectors = getLineTypes(hasChildren, types, nodeType)}
50
+ {@const connectors = getLineTypes(hasChildren(item, fields), types, nodeType)}
25
51
 
26
- <Node value={items[index]} types={connectors} {stateIcons}>
27
- {#if items[index].expanded}
28
- <!-- <div role="treeitem" aria-selected={false}> -->
29
- <NestedList items={item.children} {value} icons={stateIcons} types={connectors} />
30
- <!-- </div> -->
52
+ <Node
53
+ value={item}
54
+ {fields}
55
+ {stateIcons}
56
+ types={connectors}
57
+ focused={focusedKey === key}
58
+ selected={selectedKeys.has(key)}
59
+ {expanded}
60
+ path={nodePath}
61
+ {stub}
62
+ {snippets}
63
+ >
64
+ {#if hasChildren(item, fields) && expanded}
65
+ <NestedList
66
+ items={item[fields.children]}
67
+ {value}
68
+ fields={childFields}
69
+ path={nodePath}
70
+ icons={stateIcons}
71
+ types={connectors}
72
+ {focusedKey}
73
+ {selectedKeys}
74
+ />
31
75
  {/if}
32
76
  </Node>
33
77
  {/each}
package/src/Node.svelte CHANGED
@@ -5,41 +5,52 @@
5
5
  import Item from './Item.svelte'
6
6
 
7
7
  /**
8
- * @typedef {Object} Props
9
- * @property {any} value
10
- * @property {any} [types]
8
+ * @typedef {Object} Props
9
+ * @property {any} value
10
+ * @property {import('./types').FieldMapping} fields
11
+ * @property {any} [types]
11
12
  * @property {import('./types').NodeStateIcons} [stateIcons]
13
+ * @property {number[]} [path=[]]
14
+ * @property {boolean} [focused=false]
15
+ * @property {boolean} [selected=false]
16
+ * @property {boolean} [expanded=false]
17
+ * @property {Function} [children]
18
+ * @property {Function} [stub=null]
19
+ * @property {Object<string, Function>} [snippets={}]
12
20
  */
13
21
 
14
22
  /** @type {Props} */
15
23
  let {
16
- value = $bindable(),
24
+ value = $bindable(null),
25
+ fields,
17
26
  types = [],
18
27
  stateIcons = defaultStateIcons.node,
19
- stub = null,
28
+ path = [],
29
+ focused = false,
30
+ selected = false,
31
+ expanded = false,
20
32
  children,
21
- ...extra
33
+ stub = null,
34
+ snippets = {}
22
35
  } = $props()
23
36
 
37
+ let stateName = $derived(expanded ? 'opened' : 'closed')
24
38
  let icons = $derived({ ...defaultStateIcons.node, ...stateIcons })
25
- let stateName = $derived(value.expanded ? 'opened' : 'closed')
26
39
  let state = $derived(
27
- value.expanded
28
- ? { icon: icons.opened, label: 'collapse' }
29
- : { icon: icons.closed, label: 'expand' }
40
+ expanded ? { icon: icons.opened, label: 'collapse' } : { icon: icons.closed, label: 'expand' }
30
41
  )
31
42
 
32
- const template = getSnippet(value.get('component'), extra) ?? stub
33
- // $inspect(value.focused, value.expanded, value.selected, value.get('text'))
43
+ const template = getSnippet(value[fields.snippet], snippets, stub)
44
+ $inspect(template)
34
45
  </script>
35
46
 
36
47
  <rk-node
37
- aria-current={value.focused}
38
- aria-selected={value.selected}
39
- aria-expanded={value.expanded}
48
+ aria-current={focused}
49
+ aria-selected={selected}
50
+ aria-expanded={expanded}
40
51
  role="treeitem"
41
- data-path={getKeyFromPath(value.path)}
42
- data-depth={value.path.length}
52
+ data-path={getKeyFromPath(path)}
53
+ data-depth={path.length}
43
54
  >
44
55
  <div class="flex flex-row items-center">
45
56
  {#each types as type}
@@ -50,11 +61,16 @@
50
61
  {/if}
51
62
  {/each}
52
63
  <rk-item>
53
- {#if template}
64
+ <svelte:boundary>
65
+ <!-- {#if template} -->
54
66
  {@render template(value)}
55
- {:else}
56
- <Item value={value.value} fields={value.fields} />
57
- {/if}
67
+ {#snippet failed()}
68
+ <Item {value} {fields} />
69
+ {/snippet}
70
+ <!-- {:else}
71
+ <Item {value} {fields} />
72
+ {/if} -->
73
+ </svelte:boundary>
58
74
  </rk-item>
59
75
  </div>
60
76
  {@render children?.()}
@@ -38,8 +38,9 @@
38
38
  class:disabled={readOnly}
39
39
  >
40
40
  {#each options as item}
41
- {@const itemValue = mapping.getValue(item)}
42
- {@const label = mapping.getText(item)}
41
+ {@const label = mapping.get('text', item)}
42
+ {@const itemValue = mapping.get('value', item, label)}
43
+
43
44
  {@const state = equals(itemValue, value) ? 'on' : 'off'}
44
45
 
45
46
  <label class="flex {flexDirection} items-center gap-2">
@@ -65,7 +65,7 @@
65
65
  >
66
66
  {#each items as item, index}
67
67
  {@const segmentClass = 'col-' + (index + 1)}
68
- {@const props = mapping.getAttribute(item, 'props')}
68
+ {@const props = mapping.get('props', item, {})}
69
69
  {@const Template = item[mapping.fields.component]}
70
70
  {#if small && equals(index, activeIndex)}
71
71
  <rk-segment
package/src/Switch.svelte CHANGED
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import { equals } from 'ramda'
3
- import { noop } from '@rokkit/core'
3
+ import { noop, getSnippet, FieldMapper } from '@rokkit/core'
4
4
  import { keyboard } from '@rokkit/actions'
5
5
  import { defaultMapping } from './constants'
6
6
 
@@ -19,10 +19,12 @@
19
19
  class: classes = '',
20
20
  value = $bindable(),
21
21
  options = [false, true],
22
- mapping = defaultMapping,
22
+ fields,
23
23
  compact = false,
24
24
  disabled = false,
25
- onchange = noop
25
+ onchange = noop,
26
+ stub,
27
+ ...extra
26
28
  } = $props()
27
29
 
28
30
  let cursor = $state([])
@@ -50,6 +52,7 @@
50
52
  prev: ['ArrowLeft', 'ArrowUp']
51
53
  }
52
54
  let useComponent = $derived(!options.every((item) => [false, true].includes(item)))
55
+ let mapper = new FieldMapper(fields)
53
56
  </script>
54
57
 
55
58
  {#if !Array.isArray(options) || options.length < 2}
@@ -72,13 +75,13 @@
72
75
  onclick={handleClick}
73
76
  >
74
77
  {#each options as item, index (item)}
75
- {@const Template = useComponent ? mapping.getComponent(item) : null}
78
+ {@const Template = getSnippet(extra, mapper.get('snippet', item), stub)}
76
79
  <rk-item class="relative" role="option" aria-selected={equals(item, value)} data-path={index}>
77
80
  {#if equals(item, value)}
78
81
  <rk-indicator class="absolute bottom-0 left-0 right-0 top-0"></rk-indicator>
79
82
  {/if}
80
83
  {#if Template}
81
- <Template value={item} {mapping} />
84
+ <Template value={item} {fields} />
82
85
  {/if}
83
86
  </rk-item>
84
87
  {/each}
@@ -31,7 +31,7 @@
31
31
  path = null
32
32
  } = $props()
33
33
 
34
- const Template = $derived(mapping.getComponent(value))
34
+ const Template = $derived(mapping.get('component', value))
35
35
  </script>
36
36
 
37
37
  <td class={classes}>
package/src/Tabs.svelte CHANGED
@@ -1,6 +1,6 @@
1
1
  <script>
2
- import { defaultFields, defaultStateIcons, noop, getSnippet } from '@rokkit/core'
3
- import { ListProxy } from '@rokkit/states'
2
+ import { defaultFields, defaultStateIcons, noop, getSnippet, FieldMapper } from '@rokkit/core'
3
+ import { ListController } from '@rokkit/states'
4
4
  import { navigator } from '@rokkit/actions'
5
5
  import Icon from './Icon.svelte'
6
6
  import Item from './Item.svelte'
@@ -34,8 +34,6 @@
34
34
  ...extra
35
35
  } = $props()
36
36
 
37
- let cursor = $state([])
38
-
39
37
  function handleRemove(event) {
40
38
  if (typeof event.detail === Object) {
41
39
  event.detail[fields.isDeleted] = true
@@ -56,8 +54,9 @@
56
54
  onselect({ item: value, indices: cursor })
57
55
  }
58
56
  let stateIcons = $derived({ ...defaultStateIcons.action, ...icons })
59
- let filtered = $derived(options.filter((item) => !item[fields.isDeleted]))
60
- let wrapper = new ListProxy(options, value, fields)
57
+ let filtered = $derived(options.filter((item) => !item[fields.deleted]))
58
+ let wrapper = $derived(new ListController(options, value, fields))
59
+ let mapper = new FieldMapper(fields)
61
60
  </script>
62
61
 
63
62
  <rk-tabs
@@ -67,15 +66,19 @@
67
66
  class:justify-end={align === 'right'}
68
67
  tabindex="0"
69
68
  role="listbox"
70
- use:navigator={{ wrapper }}
69
+ use:navigator={{ wrapper, horizontal: true }}
71
70
  onaction={handleNav}
72
71
  onremove={handleRemove}
73
72
  onadd={handleAdd}
74
73
  >
75
- {#each wrapper.nodes as item, index}
76
- {@const Template = getSnippet(extra, item.get('component')) ?? stub}
74
+ {#each filtered as item, index}
75
+ {@const Template = getSnippet(extra, mapper.get('snippet', item), stub)}
77
76
  <rk-tab>
78
- <Template value={item} {mapping} />
77
+ {#if Template}
78
+ <Template value={item} {fields} />
79
+ {:else}
80
+ <Item value={item} {fields} />
81
+ {/if}
79
82
  {#if editable}
80
83
  <Icon
81
84
  name="remove"
package/src/Toggle.svelte CHANGED
@@ -1,8 +1,7 @@
1
1
  <script>
2
- import { FieldMapper, noop } from '@rokkit/core'
2
+ // import { FieldMapper, noop } from '@rokkit/core'
3
3
  import { keyboard } from '@rokkit/actions'
4
4
  import Item from './Item.svelte'
5
- import { defaultMapping } from './constants'
6
5
 
7
6
  /**
8
7
  * @typedef {Object} Props
@@ -17,8 +16,9 @@
17
16
  class: classes = '',
18
17
  value = $bindable(null),
19
18
  options = [false, true],
20
- mapping = defaultMapping,
21
- onchange = noop
19
+ fields,
20
+ label = 'toggle',
21
+ onchange
22
22
  } = $props()
23
23
 
24
24
  const keyMappings = {
@@ -37,7 +37,7 @@
37
37
  }
38
38
 
39
39
  value = options[nextIndex]
40
- onchange(value)
40
+ onchange?.(value)
41
41
  }
42
42
  </script>
43
43
 
@@ -47,8 +47,8 @@
47
47
  onnext={() => toggle()}
48
48
  onprev={() => toggle(-1)}
49
49
  onclick={() => toggle()}
50
- aria-label={mapping.getLabel(value)}
50
+ aria-label={label}
51
51
  >
52
- <Item {value} {mapping} />
52
+ <Item {value} {fields} />
53
53
  </button>
54
54
  </rk-toggle>
package/src/Tree.svelte CHANGED
@@ -1,8 +1,8 @@
1
1
  <script>
2
- import { createEmitter } from '@rokkit/core'
2
+ import { createEmitter, defaultFields } from '@rokkit/core'
3
3
  import { navigator } from '@rokkit/actions'
4
4
  import NestedList from './NestedList.svelte'
5
- import { NestedProxy } from '@rokkit/states'
5
+ import { NestedController } from '@rokkit/states'
6
6
  import { omit, has } from 'ramda'
7
7
  /**
8
8
  * @typedef {Object} Props
@@ -13,6 +13,10 @@
13
13
  * @property {import('./types').NodeStateIcons|Object} [icons]
14
14
  * @property {boolean} [autoCloseSiblings=false]
15
15
  * @property {boolean} [multiselect=false]
16
+ * @property {Function} [header]
17
+ * @property {Function} [footer]
18
+ * @property {Function} [empty]
19
+ * @property {Function} [stub]
16
20
  */
17
21
 
18
22
  /** @type {Props & { [key: string]: any }} */
@@ -24,37 +28,52 @@
24
28
  icons = {},
25
29
  autoCloseSiblings = false,
26
30
  multiselect = false,
27
- keys = null,
28
31
  header,
29
32
  footer,
30
33
  empty,
34
+ stub,
31
35
  ...events
32
36
  } = $props()
33
37
 
34
- let emitter = createEmitter(events, ['select', 'move', 'collapse', 'expand'])
35
- let wrapper = new NestedProxy(items, value, fields, { autoCloseSiblings, multiselect })
38
+ let emitter = createEmitter(events, ['select', 'move', 'toggle'])
39
+ let wrapper = new NestedController(items, value, fields, { autoCloseSiblings, multiselect })
40
+ let snippets = omit(['onselect', 'onmove', 'ontoggle'], events)
41
+ let derivedFields = $derived({ ...defaultFields, ...fields })
36
42
 
37
43
  function handleAction(event) {
38
- const { eventName, data } = event.detail
39
- if (eventName === 'select') value = wrapper.currentNode?.value
40
-
41
- if (has([eventName], emitter)) emitter[eventName](data)
44
+ const { name, data } = event.detail
45
+ if (name === 'select') value = data.value
46
+ if (has([name], emitter)) emitter[name](data)
42
47
  }
43
48
  </script>
44
49
 
45
50
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
46
- <rk-tree tabindex="0" class={classes} use:navigator={{ wrapper }} onaction={handleAction}>
51
+ <rk-tree
52
+ tabindex="0"
53
+ class={classes}
54
+ use:navigator={{ wrapper, nested: true }}
55
+ onaction={handleAction}
56
+ >
47
57
  {#if header}
48
58
  <rk-header>{@render header()}</rk-header>
49
59
  {/if}
50
- {#if wrapper.nodes.length === 0}
60
+ {#if items.length === 0}
51
61
  {#if empty}
52
62
  {@render empty()}
53
63
  {:else}
54
64
  <div class="m-auto p-4 text-center text-gray-500">No data available</div>
55
65
  {/if}
56
66
  {/if}
57
- <NestedList items={wrapper.nodes} {value} {icons} />
67
+ <NestedList
68
+ {items}
69
+ fields={derivedFields}
70
+ {value}
71
+ {icons}
72
+ focusedKey={wrapper.currentKey}
73
+ selectedKeys={wrapper.selectedKeys}
74
+ {stub}
75
+ {snippets}
76
+ />
58
77
  {#if footer}
59
78
  <rk-footer>{@render footer()}</rk-footer>
60
79
  {/if}
@@ -134,7 +134,7 @@
134
134
  >
135
135
  {#each columns as col, index}
136
136
  {@const value = { ...pick(['icon'], col), ...item }}
137
- {@const SvelteComponent = mapping.getComponent(item)}
137
+ {@const SvelteComponent = mapping.get('component', item)}
138
138
  <td>
139
139
  <cell>
140
140
  {#if multiselect && index === 0}
package/src/index.js CHANGED
@@ -25,7 +25,6 @@ export { default as Toggle } from './Toggle.svelte'
25
25
  export { default as Switch } from './Switch.svelte'
26
26
  export { default as List } from './List.svelte'
27
27
  export { default as Accordion } from './Accordion.svelte'
28
- export { default as NestedList } from './NestedList.svelte'
29
28
  export { default as Tree } from './Tree.svelte'
30
29
  export { default as Tabs } from './Tabs.svelte'
31
30
  export { default as Select } from './Select.svelte'
package/src/lib/tree.js CHANGED
@@ -13,7 +13,7 @@ export function addRootNode(items, root = '/', mapping = defaultMapping) {
13
13
  return [
14
14
  {
15
15
  [mapping.fields.text]: root,
16
- [mapping.fields.isOpen]: true,
16
+ [mapping.fields.expanded]: true,
17
17
  [mapping.fields.children]: items
18
18
  }
19
19
  ]
@@ -1,13 +1,12 @@
1
1
  <script>
2
2
  import { getContext } from 'svelte'
3
- import { defaultFields, FieldMapper } from '@rokkit/core'
4
3
 
5
4
  const registry = getContext('registry')
6
5
 
7
6
  let {
8
7
  class: className = '',
9
8
  options = [],
10
- fields = defaultFields,
9
+ fields,
11
10
  navigator = 'tabs',
12
11
  type = 'vertical',
13
12
  category = null,
@@ -19,7 +18,9 @@
19
18
  </script>
20
19
 
21
20
  <section class={className}>
22
- <Template {options} {fields} bind:value={category} {...restProps} />
21
+ {#if Template}
22
+ <Template {options} {fields} bind:value={category} {...restProps} />
23
+ {/if}
23
24
  <field-layout class={type}>
24
25
  {@render children?.()}
25
26
  </field-layout>