@rokkit/ui 1.0.0-next.100 → 1.0.0-next.106

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 (49) hide show
  1. package/dist/constants.d.ts +2 -0
  2. package/dist/index.d.ts +30 -0
  3. package/dist/lib/index.d.ts +1 -0
  4. package/dist/lib/tree.d.ts +9 -0
  5. package/dist/types.d.ts +5 -0
  6. package/package.json +16 -40
  7. package/src/Accordion.svelte +78 -0
  8. package/src/BreadCrumbs.svelte +33 -0
  9. package/src/Calendar.svelte +93 -0
  10. package/src/CheckBox.svelte +56 -0
  11. package/src/Connector.svelte +39 -0
  12. package/src/Icon.svelte +71 -0
  13. package/src/Item.svelte +24 -0
  14. package/src/Link.svelte +21 -0
  15. package/src/List.svelte +67 -0
  16. package/src/Message.svelte +11 -0
  17. package/src/NestedList.svelte +68 -0
  18. package/src/Node.svelte +64 -0
  19. package/src/Overlay.svelte +21 -0
  20. package/src/Pill.svelte +41 -0
  21. package/src/ProgressBar.svelte +21 -0
  22. package/src/RadioGroup.svelte +51 -0
  23. package/src/Range.svelte +45 -0
  24. package/src/RangeMinMax.svelte +124 -0
  25. package/src/RangeSlider.svelte +79 -0
  26. package/src/RangeTick.svelte +28 -0
  27. package/src/Rating.svelte +95 -0
  28. package/src/ResponsiveGrid.svelte +88 -0
  29. package/src/Scrollable.svelte +7 -0
  30. package/src/Separator.svelte +1 -0
  31. package/src/SlidingColumns.svelte +52 -0
  32. package/src/Summary.svelte +26 -0
  33. package/src/Switch.svelte +86 -0
  34. package/src/TableCell.svelte +51 -0
  35. package/src/TableHeaderCell.svelte +54 -0
  36. package/src/Tabs.svelte +88 -0
  37. package/src/Toggle.svelte +54 -0
  38. package/src/ToggleThemeMode.svelte +19 -0
  39. package/src/Tree.svelte +53 -0
  40. package/src/TreeTable.svelte +170 -0
  41. package/src/ValidationReport.svelte +23 -0
  42. package/src/constants.js +4 -0
  43. package/src/index.js +34 -8
  44. package/src/lib/index.js +1 -0
  45. package/src/lib/tree.js +22 -0
  46. package/src/types.js +9 -0
  47. package/LICENSE +0 -21
  48. package/README.md +0 -3
  49. package/src/wrappers.js +0 -2
@@ -0,0 +1,2 @@
1
+ export const defaultMapping: FieldMapper;
2
+ import { FieldMapper } from '@rokkit/core';
@@ -0,0 +1,30 @@
1
+ export * from "./types.js";
2
+ export { default as Icon } from "./Icon.svelte";
3
+ export { default as Item } from "./Item.svelte";
4
+ export { default as Pill } from "./Pill.svelte";
5
+ export { default as ProgressBar } from "./ProgressBar.svelte";
6
+ export { default as Separator } from "./Separator.svelte";
7
+ export { default as Connector } from "./Connector.svelte";
8
+ export { default as RangeTick } from "./RangeTick.svelte";
9
+ export { default as RangeSlider } from "./RangeSlider.svelte";
10
+ export { default as Node } from "./Node.svelte";
11
+ export { default as Summary } from "./Summary.svelte";
12
+ export { default as BreadCrumbs } from "./BreadCrumbs.svelte";
13
+ export { default as Rating } from "./Rating.svelte";
14
+ export { default as RangeMinMax } from "./RangeMinMax.svelte";
15
+ export { default as Range } from "./Range.svelte";
16
+ export { default as RadioGroup } from "./RadioGroup.svelte";
17
+ export { default as CheckBox } from "./CheckBox.svelte";
18
+ export { default as Calendar } from "./Calendar.svelte";
19
+ export { default as ValidationReport } from "./ValidationReport.svelte";
20
+ export { default as ResponsiveGrid } from "./ResponsiveGrid.svelte";
21
+ export { default as Toggle } from "./Toggle.svelte";
22
+ export { default as Switch } from "./Switch.svelte";
23
+ export { default as List } from "./List.svelte";
24
+ export { default as Accordion } from "./Accordion.svelte";
25
+ export { default as NestedList } from "./NestedList.svelte";
26
+ export { default as Tree } from "./Tree.svelte";
27
+ export { default as ToggleThemeMode } from "./ToggleThemeMode.svelte";
28
+ export { default as Overlay } from "./Overlay.svelte";
29
+ export { default as Message } from "./Message.svelte";
30
+ export { default as SlidingColumns } from "./SlidingColumns.svelte";
@@ -0,0 +1 @@
1
+ export { addRootNode } from "./tree";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Adds a root node to the items array converting it into a nested tree with one root node
3
+ *
4
+ * @param {Array<Object>} items
5
+ * @param {string} root
6
+ * @param {import('@rokkit/core').FieldMapping} fields
7
+ * @returns
8
+ */
9
+ export function addRootNode(items: Array<Object>, root?: string, mapping?: import("@rokkit/core").FieldMapper): Object[];
@@ -0,0 +1,5 @@
1
+ export type NodeStateIcons = {
2
+ opened: string;
3
+ closed: string;
4
+ };
5
+ export type ConnectionType = "empty" | "last" | "child" | "sibling";
package/package.json CHANGED
@@ -1,63 +1,39 @@
1
1
  {
2
2
  "name": "@rokkit/ui",
3
- "version": "1.0.0-next.100",
4
- "description": "All UI components",
3
+ "version": "1.0.0-next.106",
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",
7
- "main": "index.js",
8
7
  "module": "src/index.js",
9
- "types": "dist/index.d.ts",
10
8
  "type": "module",
11
9
  "publishConfig": {
12
10
  "access": "public"
13
11
  },
14
- "devDependencies": {
15
- "@sveltejs/vite-plugin-svelte": "^3.1.2",
16
- "@testing-library/svelte": "^5.2.1",
17
- "@vitest/coverage-v8": "^2.1.1",
18
- "@vitest/ui": "~2.1.1",
19
- "jsdom": "^25.0.0",
20
- "svelte": "^4.2.19",
21
- "typescript": "^5.6.2",
22
- "vite": "^5.4.6",
23
- "vitest": "~2.1.1",
24
- "shared-config": "1.0.0-next.100",
25
- "validators": "1.0.0-next.100"
12
+ "scripts": {
13
+ "prepublishOnly": "tsc --project tsconfig.build.json",
14
+ "clean": "rm -rf dist",
15
+ "build": "bun clean && bun prepublishOnly"
26
16
  },
27
17
  "files": [
28
18
  "src/**/*.js",
29
19
  "src/**/*.svelte",
30
- "!src/mocks",
31
- "!src/**/*.spec.js"
20
+ "dist/**/*.d.ts"
32
21
  ],
33
22
  "exports": {
34
23
  "./src": "./src",
35
- "./wrappers": "./src/wrappers/index.js",
36
24
  "./package.json": "./package.json",
25
+ "./utils": "./src/lib/index.js",
37
26
  ".": {
38
27
  "types": "./dist/index.d.ts",
39
- "import": "./src/index.js",
40
- "svelte": "./src/index.js"
28
+ "import": "./src/index.js"
41
29
  }
42
30
  },
43
31
  "dependencies": {
44
- "ramda": "^0.30.1",
45
- "@rokkit/actions": "1.0.0-next.100",
46
- "@rokkit/atoms": "1.0.0-next.100",
47
- "@rokkit/core": "1.0.0-next.100",
48
- "@rokkit/molecules": "1.0.0-next.100",
49
- "@rokkit/layout": "1.0.0-next.100",
50
- "@rokkit/organisms": "1.0.0-next.100"
51
- },
52
- "scripts": {
53
- "format": "prettier --write .",
54
- "lint": "eslint --fix .",
55
- "test:ct": "playwright test -c playwright-ct.config.ts",
56
- "test:ci": "vitest run",
57
- "test:ui": "vitest --ui",
58
- "test": "vitest",
59
- "coverage": "vitest run --coverage",
60
- "latest": "pnpm upgrade --latest && pnpm test:ci",
61
- "release": "pnpm publish --access public"
32
+ "@rokkit/actions": "latest",
33
+ "@rokkit/core": "latest",
34
+ "@rokkit/states": "latest",
35
+ "d3-scale": "^4.0.2",
36
+ "date-fns": "^4.1.0",
37
+ "ramda": "^0.30.1"
62
38
  }
63
- }
39
+ }
@@ -0,0 +1,78 @@
1
+ <script>
2
+ import { equals } from 'ramda'
3
+ import { createEmitter, noop, getKeyFromPath } from '@rokkit/core'
4
+ import { DataWrapper } from '@rokkit/states'
5
+ import { navigator } from '@rokkit/actions'
6
+ import { defaultMapping } from './constants'
7
+ import Summary from './Summary.svelte'
8
+
9
+ /**
10
+ * @typedef {Object} Props
11
+ * @property {string} [class]
12
+ * @property {any} [items]
13
+ * @property {import('@rokkit/core').FieldMapper} [mapping]
14
+ * @property {boolean} [autoCloseSiblings]
15
+ * @property {boolean} [multiselect]
16
+ * @property {any} [value]
17
+ */
18
+
19
+ /** @type {Props} */
20
+ let {
21
+ class: classes = '',
22
+ items = $bindable([]),
23
+ mapping = defaultMapping,
24
+ autoCloseSiblings = false,
25
+ multiselect = false,
26
+ value = $bindable(null),
27
+ ...events
28
+ } = $props()
29
+
30
+ let emitter = $derived(
31
+ createEmitter(events, ['collapse', 'change', 'expand', 'click', 'select', 'move'])
32
+ )
33
+ let wrapper = new DataWrapper(items, mapping, value, { events, multiselect, autoCloseSiblings })
34
+ </script>
35
+
36
+ {#snippet listItems(items, wrapper, hierarchy = [], onchange = noop)}
37
+ {@const mapping = wrapper.mapping}
38
+ {#each items as item, index}
39
+ {@const Template = mapping.getComponent(item)}
40
+ {@const path = getKeyFromPath([...hierarchy, index])}
41
+ {@const props = mapping.getAttribute(item, 'props') || {}}
42
+ <rk-list-item
43
+ role="option"
44
+ data-path={path}
45
+ aria-selected={wrapper.selected.has(path)}
46
+ aria-current={equals(wrapper.currentNode, item)}
47
+ >
48
+ <Template bind:value={items[index]} {mapping} onchange={events.change} {...props} />
49
+ </rk-list-item>
50
+ {/each}
51
+ {/snippet}
52
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
53
+ <rk-accordion
54
+ class={classes}
55
+ tabindex="0"
56
+ use:navigator={{ wrapper }}
57
+ onactivate={() => (value = wrapper.value)}
58
+ >
59
+ {#each wrapper.data as item, index}
60
+ {@const hasItems = mapping.hasChildren(item)}
61
+ {@const id = 'id-' + index}
62
+
63
+ <div
64
+ {id}
65
+ class="flex flex-col"
66
+ class:is-expanded={mapping.isExpanded(item)}
67
+ class:is-selected={equals(item, value)}
68
+ data-path={index}
69
+ >
70
+ <Summary {mapping} bind:value={items[index]} expanded={mapping.isExpanded(item)} />
71
+ {#if hasItems && mapping.isExpanded(item)}
72
+ <rk-list role="listbox" tabindex="-1">
73
+ {@render listItems(item[mapping.fields.children], wrapper, [index], events.change)}
74
+ </rk-list>
75
+ {/if}
76
+ </div>
77
+ {/each}
78
+ </rk-accordion>
@@ -0,0 +1,33 @@
1
+ <script>
2
+ import { defaultMapping } from './constants'
3
+
4
+ /**
5
+ * @typedef {Object} Props
6
+ * @property {string} [class]
7
+ * @property {any} [items]
8
+ * @property {string} [separator]
9
+ * @property {any} [fields]
10
+ * @property {any} [using]
11
+ */
12
+
13
+ /** @type {Props} */
14
+ let { class: classes = '', items = [], separator = '/', mapping = defaultMapping } = $props()
15
+ </script>
16
+
17
+ <rk-crumbs class={classes}>
18
+ {#each items as item, index}
19
+ {@const Component = mapping.getComponent(item)}
20
+ {#if index > 0}
21
+ <span>
22
+ {#if separator.length === 1}
23
+ {separator}
24
+ {:else}
25
+ <icon class={separator}></icon>
26
+ {/if}
27
+ </span>
28
+ {/if}
29
+ <rk-crumb class:is-selected={index === items.length - 1}>
30
+ <Component value={item} {mapping} />
31
+ </rk-crumb>
32
+ {/each}
33
+ </rk-crumbs>
@@ -0,0 +1,93 @@
1
+ <script>
2
+ import { format, isSameDay, addMonths } from 'date-fns'
3
+ import { weekdays, getCalendarDays } from '@rokkit/core'
4
+
5
+ /**
6
+ * @typedef {Object} Props
7
+ * @property {any} [value]
8
+ * @property {any} [holidays]
9
+ * @property {boolean} [fixed]
10
+ */
11
+
12
+ /** @type {Props} */
13
+ let { value = $bindable(new Date()), holidays = [], fixed = true } = $props()
14
+
15
+ function handleChange(event) {
16
+ value = new Date(event.target.value, value.getMonth(), value.getDate())
17
+ }
18
+ function nextMonth() {
19
+ value = addMonths(value, 1)
20
+ }
21
+ function previousMonth() {
22
+ value = addMonths(value, -1)
23
+ }
24
+
25
+ let year = $derived(value.getFullYear())
26
+ let days = $derived(getCalendarDays(value, holidays, fixed))
27
+ </script>
28
+
29
+ <calendar class="mx-auto flex select-none flex-col items-center">
30
+ <month-year class="flex h-10 w-full flex-row items-center">
31
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
32
+ <square
33
+ class="cursor-pointer select-none"
34
+ onclick={previousMonth}
35
+ role="option"
36
+ aria-selected={false}
37
+ tabindex="0"
38
+ >
39
+ <icon class="chevron-left"></icon>
40
+ </square>
41
+ <span class="gap-1px flex flex-grow items-center justify-center">
42
+ <p>{format(value, 'MMMM')}</p>
43
+ <input
44
+ type="number"
45
+ value={year}
46
+ class="flex w-14 flex-grow-0 border-none bg-transparent"
47
+ onchange={handleChange}
48
+ />
49
+ </span>
50
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
51
+ <square
52
+ class="cursor-pointer select-none"
53
+ onclick={nextMonth}
54
+ role="option"
55
+ aria-selected={false}
56
+ tabindex="0"
57
+ >
58
+ <icon class="chevron-right"></icon>
59
+ </square>
60
+ </month-year>
61
+ <cal-body class="flex w-full cursor-pointer flex-col p-1">
62
+ <days-of-week class="grid grid-cols-7">
63
+ {#each weekdays as day, index}
64
+ <p class:weekend={index % 6 === 0}>
65
+ {day.slice(0, 1)}
66
+ </p>
67
+ {/each}
68
+ </days-of-week>
69
+ <days-of-month class="grid grid-cols-7 grid-rows-5">
70
+ {#each days as { day, offset, date, weekend }}
71
+ {@const start = offset > 0 ? offset : 'auto'}
72
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
73
+ <day-of-month
74
+ class:weekend
75
+ style:grid-column-start={start}
76
+ onclick={() => (value = date)}
77
+ role="option"
78
+ aria-selected={isSameDay(date, value)}
79
+ tabindex="0"
80
+ >
81
+ {day}
82
+ </day-of-month>
83
+ {/each}
84
+ </days-of-month>
85
+ </cal-body>
86
+ </calendar>
87
+
88
+ <style>
89
+ days-of-week p,
90
+ day-of-month {
91
+ @apply flex items-center justify-center;
92
+ }
93
+ </style>
@@ -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,39 @@
1
+ <script>
2
+ /**
3
+ * @typedef {Object} Props
4
+ * @property {import('./types.js').ConnectionType} [type]
5
+ * @property {boolean} [rtl=false]
6
+ */
7
+
8
+ /** @type {Props} */
9
+ let { type = $bindable('empty'), rtl = false } = $props()
10
+ let validatedType = $derived(['last', 'child', 'sibling'].includes(type) ? type : 'empty')
11
+ </script>
12
+
13
+ <span
14
+ class="grid h-full w-4 min-w-4 grid-cols-2 grid-rows-2"
15
+ class:line-empty={validatedType === 'empty'}
16
+ class:line-child={validatedType === 'child'}
17
+ class:line-sibling={validatedType === 'sibling'}
18
+ class:line-last={validatedType === 'last'}
19
+ >
20
+ {#if validatedType === 'last'}
21
+ {#if rtl}
22
+ <i class="border-b border-r"></i>
23
+ {:else}
24
+ <i class="border-r"></i>
25
+ <i class="border-b"></i>
26
+ {/if}
27
+ {:else if validatedType === 'child'}
28
+ {#if rtl}
29
+ <i class="row-span-2 grid grid-rows-2 border-r">
30
+ <i class="border-b"></i>
31
+ </i>
32
+ {:else}
33
+ <i class="col-span-1 row-span-2 border-r"></i>
34
+ <i class="border-b"></i>
35
+ {/if}
36
+ {:else if validatedType === 'sibling'}
37
+ <i class="row-span-2 border-r"></i>
38
+ {/if}
39
+ </span>
@@ -0,0 +1,71 @@
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 {string} [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
+ class: classes = '',
23
+ name,
24
+ state = null,
25
+ size = 'base',
26
+ role = 'img',
27
+ label = null,
28
+ disabled = false,
29
+ tabindex = $bindable(0),
30
+ checked = $bindable(null),
31
+ ...events
32
+ } = $props()
33
+
34
+ let emitter = $derived(createEmitter(events, ['click', 'change', 'mouseenter', 'mouseleave']))
35
+ function handleClick(e) {
36
+ e.preventDefault()
37
+
38
+ if (!disabled) {
39
+ if (role === 'checkbox' || role === 'option') {
40
+ checked = !checked
41
+ emitter?.change(checked)
42
+ }
43
+ emitter?.click()
44
+ }
45
+ }
46
+
47
+ let validatedTabindex = $derived(role === 'img' || disabled ? -1 : tabindex)
48
+ let ariaChecked = $derived(
49
+ ['checkbox', 'option'].includes(role) ? (checked !== null ? checked : false) : null
50
+ )
51
+ </script>
52
+
53
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
54
+ <rk-icon
55
+ class={classes}
56
+ class:small={size === 'small'}
57
+ class:medium={size === 'medium'}
58
+ class:large={size === 'large'}
59
+ class:disabled
60
+ {role}
61
+ aria-label={label ?? name}
62
+ aria-checked={ariaChecked}
63
+ onclick={handleClick}
64
+ onkeydown={(e) => e.key === 'Enter' && e.currentTarget.click()}
65
+ data-state={state}
66
+ tabindex={validatedTabindex}
67
+ onmouseenter={emitter.mouseenter}
68
+ onnmouseleave={emitter.nmouseleave}
69
+ >
70
+ <i class={name} aria-hidden="true"></i>
71
+ </rk-icon>
@@ -0,0 +1,24 @@
1
+ <script>
2
+ import Icon from './Icon.svelte'
3
+ import { FieldMapper } from '@rokkit/core'
4
+
5
+ /**
6
+ * @typedef {Object} Props
7
+ * @property {string|Object} [value]
8
+ * @property {FieldMapper} [mapping]
9
+ */
10
+
11
+ /** @type {Props} */
12
+ let { value, mapping = new FieldMapper() } = $props()
13
+ let content = $derived(mapping.getText(value))
14
+ let ariaLabel = $derived(mapping.getLabel(value) ?? content)
15
+ </script>
16
+
17
+ {#if mapping.hasIcon(value)}
18
+ <Icon name={mapping.getIcon(value)} label={ariaLabel} />
19
+ {:else if mapping.hasImage(value)}
20
+ <img src={mapping.getImage(value)} alt={ariaLabel} />
21
+ {/if}
22
+ {#if content}
23
+ <p>{content}</p>
24
+ {/if}
@@ -0,0 +1,21 @@
1
+ <script>
2
+ import { FieldMapper } from '@rokkit/core'
3
+ import Item from './Item.svelte'
4
+ /**
5
+ * @typedef {Object} Props
6
+ * @property {string} [class]
7
+ * @property {string|Object} [value]
8
+ * @property {FieldMapper} [mapping]
9
+ * @property {string} [href]
10
+ * @property {Object} [rest]
11
+ */
12
+
13
+ /** @type {Props} */
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 })
17
+ </script>
18
+
19
+ <a href={url} class={classes} {...props}>
20
+ <Item {value} {mapping} />
21
+ </a>
@@ -0,0 +1,67 @@
1
+ <script>
2
+ import { createEmitter, noop, getKeyFromPath } from '@rokkit/core'
3
+ import { equals } from 'ramda'
4
+ import { defaultMapping } from './constants'
5
+ // import { listItems } from './snippets.svelte'
6
+ import { onMount } from 'svelte'
7
+ import { DataWrapper } from '@rokkit/states'
8
+ import { navigator } from '@rokkit/actions'
9
+
10
+ /**
11
+ * @typedef {Object} Props
12
+ * @property {string} [class]
13
+ * @property {string} [name]
14
+ * @property {any} [items]
15
+ * @property {import('@rokkit/core').FieldMapper} [mapping]
16
+ * @property {any} [value]
17
+ * @property {number} [tabindex]
18
+ * @property {any} [hierarchy]
19
+ * @property {import('svelte').Snippet} [children]
20
+ */
21
+
22
+ /** @type {Props} */
23
+ let {
24
+ class: classes = '',
25
+ name = 'list',
26
+ items = $bindable([]),
27
+ value = $bindable(null),
28
+ mapping = defaultMapping,
29
+ tabindex = 0,
30
+ hierarchy = [],
31
+ children,
32
+ ...events
33
+ } = $props()
34
+
35
+ let emitter = createEmitter(events, ['select', 'change', 'move'])
36
+ let wrapper = new DataWrapper(items, mapping, value, { events: emitter })
37
+ </script>
38
+
39
+ {#snippet listItems(items, wrapper, hierarchy = [], onchange = noop)}
40
+ {@const mapping = wrapper.mapping}
41
+ {#each items as item, index}
42
+ {@const Template = mapping.getComponent(item)}
43
+ {@const path = getKeyFromPath([...hierarchy, index])}
44
+ {@const props = mapping.getAttribute(item, 'props') || {}}
45
+ <rk-list-item
46
+ role="option"
47
+ data-path={path}
48
+ aria-selected={wrapper.selected.has(path)}
49
+ aria-current={equals(wrapper.currentNode, item)}
50
+ >
51
+ <Template bind:value={items[index]} {mapping} {onchange} {...props} />
52
+ </rk-list-item>
53
+ {/each}
54
+ {/snippet}
55
+
56
+ <rk-list
57
+ class={classes}
58
+ role="listbox"
59
+ aria-label={name}
60
+ use:navigator={{ wrapper }}
61
+ {tabindex}
62
+ onactivate={() => (value = wrapper.value)}
63
+ >
64
+ {@render children?.()}
65
+ {@render listItems(wrapper.data, wrapper, hierarchy, emitter.change)}
66
+ <!-- <ListItems bind:items {mapping} {hierarchy} /> -->
67
+ </rk-list>
@@ -0,0 +1,11 @@
1
+ <script>
2
+ let { text = null, type = 'error', children } = $props()
3
+ </script>
4
+
5
+ <rk-message class={type}>
6
+ {#if children}
7
+ {@render children()}
8
+ {:else}
9
+ {text}
10
+ {/if}
11
+ </rk-message>