@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,68 @@
1
+ <script>
2
+ import { equals } from 'ramda'
3
+ import { defaultStateIcons, getLineTypes, getKeyFromPath } from '@rokkit/core'
4
+ import { defaultMapping } from './constants'
5
+ import Node from './Node.svelte'
6
+ import NestedList from './NestedList.svelte'
7
+
8
+ /**
9
+ * @typedef {Object} Props
10
+ * @property {string} [class]
11
+ * @property {Array<any>} [items]
12
+ * @property {import('@rokkit/core').FieldMapping} [mapping]
13
+ * @property {import('./types').ConnectionType[]} [types]
14
+ * @property {any} [value]
15
+ * @property {number[]} [hierarchy]
16
+ * @property {import('./types').NodeStateIcons} icons
17
+ */
18
+
19
+ /** @type {Props} */
20
+ let {
21
+ class: classes = '',
22
+ items = $bindable([]),
23
+ wrapper = $bindable(),
24
+ mapping = defaultMapping,
25
+ types = [],
26
+ value = $bindable(null),
27
+ hierarchy = [],
28
+ icons = {}
29
+ } = $props()
30
+
31
+ const stateIcons = $derived({ ...defaultStateIcons.node, ...icons })
32
+ </script>
33
+
34
+ <rk-nested-list class={classes} role="tree">
35
+ {#each items as item, index}
36
+ {@const hasChildren = mapping.hasChildren(item)}
37
+ {@const indexPath = [...hierarchy, index]}
38
+ {@const nodeType = index === items.length - 1 ? 'last' : 'child'}
39
+ {@const connectors = getLineTypes(hasChildren, types, nodeType)}
40
+ {@const selected = wrapper.selected.has(getKeyFromPath(indexPath))}
41
+ {@const expanded = mapping.isExpanded(items[index])}
42
+
43
+ <Node
44
+ value={items[index]}
45
+ {mapping}
46
+ types={connectors}
47
+ path={indexPath}
48
+ {stateIcons}
49
+ {selected}
50
+ {expanded}
51
+ current={equals(wrapper.currentNode, item)}
52
+ >
53
+ {#if expanded}
54
+ <!-- <div role="treeitem" aria-selected={false}> -->
55
+ <NestedList
56
+ items={item[mapping.fields.children]}
57
+ {value}
58
+ {wrapper}
59
+ {mapping}
60
+ icons={stateIcons}
61
+ types={connectors}
62
+ hierarchy={indexPath}
63
+ />
64
+ <!-- </div> -->
65
+ {/if}
66
+ </Node>
67
+ {/each}
68
+ </rk-nested-list>
@@ -0,0 +1,64 @@
1
+ <script>
2
+ import { defaultStateIcons, getKeyFromPath } from '@rokkit/core'
3
+ import { defaultMapping } from './constants'
4
+ import Icon from './Icon.svelte'
5
+ import Connector from './Connector.svelte'
6
+
7
+ /**
8
+ * @typedef {Object} Props
9
+ * @property {any} value
10
+ * @property {import('@rokkit/core').FieldMapper} [mapping]
11
+ * @property {any} [types]
12
+ * @property {import('./types').NodeStateIcons} [stateIcons]
13
+ * @property {boolean} [selected]
14
+ * @property {boolean} [expanded]
15
+ * @property {number[]} [path]
16
+ * @property {import('svelte').Snippet} [children]
17
+ */
18
+
19
+ /** @type {Props} */
20
+ let {
21
+ class: classes = '',
22
+ value = $bindable(),
23
+ mapping = defaultMapping,
24
+ types = [],
25
+ stateIcons = defaultStateIcons.node,
26
+ selected = $bindable(false),
27
+ expanded = false,
28
+ current = false,
29
+ path = [],
30
+ children
31
+ } = $props()
32
+
33
+ let icons = $derived({ ...defaultStateIcons.node, ...stateIcons })
34
+ let stateName = $derived(expanded ? 'opened' : 'closed')
35
+ let state = $derived(
36
+ expanded ? { icon: icons.opened, label: 'collapse' } : { icon: icons.closed, label: 'expand' }
37
+ )
38
+
39
+ const Template = $derived(mapping.getComponent(value))
40
+ </script>
41
+
42
+ <rk-node
43
+ class={classes}
44
+ aria-current={current}
45
+ aria-selected={selected}
46
+ aria-expanded={state.label === 'collapse'}
47
+ role="treeitem"
48
+ data-path={getKeyFromPath(path)}
49
+ data-depth={path.length}
50
+ >
51
+ <div class="flex flex-row items-center">
52
+ {#each types as type}
53
+ {#if type === 'icon'}
54
+ <Icon name={state.icon} label={state.label} state={stateName} class="w-4" size="small" />
55
+ {:else}
56
+ <Connector {type} />
57
+ {/if}
58
+ {/each}
59
+ <rk-item>
60
+ <Template {value} {mapping} />
61
+ </rk-item>
62
+ </div>
63
+ {@render children?.()}
64
+ </rk-node>
@@ -0,0 +1,21 @@
1
+ <script>
2
+ import { dismissable } from '@rokkit/actions'
3
+ let { children } = $props()
4
+ let dismissed = $state(false)
5
+ let ondismiss = () => {
6
+ dismissed = true
7
+ }
8
+ </script>
9
+
10
+ <rk-overlay
11
+ class="absolute left-0 top-0 h-screen w-screen"
12
+ role="presentation"
13
+ class:z-1={!dismissed}
14
+ class:display-none={dismissed}
15
+ aria-hidden="true"
16
+ onclick={ondismiss}
17
+ use:dismissable
18
+ {ondismiss}
19
+ >
20
+ {@render children?.()}
21
+ </rk-overlay>
@@ -0,0 +1,41 @@
1
+ <script>
2
+ import { FieldMapper, noop } from '@rokkit/core'
3
+ import Item from './Item.svelte'
4
+ import Icon from './Icon.svelte'
5
+ import { keyboard } from '@rokkit/actions'
6
+
7
+ /**
8
+ * @type {Object} Props
9
+ * @property {string|Object} [value]
10
+ * @property {FieldMapper} [mapping]
11
+ * @property {boolean} [removable]
12
+ * @property {boolean} [disabled]
13
+ */
14
+
15
+ /** @type {Props} */
16
+ let {
17
+ value,
18
+ class: classes = '',
19
+ mapping = new FieldMapper(),
20
+ removable = false,
21
+ disabled = false,
22
+ onremove = noop
23
+ } = $props()
24
+
25
+ const keyMappings = {
26
+ remove: ['Delete', 'Backspace']
27
+ }
28
+
29
+ function handle(event) {
30
+ if (!disabled) onremove(value)
31
+ }
32
+ </script>
33
+
34
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
35
+ <rk-pill use:keyboard={keyMappings} onremove={handle} tabindex="0" class={classes}>
36
+ <Item {value} {mapping}></Item>
37
+ {#if removable}
38
+ <Icon name="action-close" role="button" aria-label="Remove" {disabled} onclick={handle} small
39
+ ></Icon>
40
+ {/if}
41
+ </rk-pill>
@@ -0,0 +1,21 @@
1
+ <script>
2
+ import { isNil } from 'ramda'
3
+ /**
4
+ * @typedef {Object} Props
5
+ * @property {string} [class]
6
+ * @property {any} [value]
7
+ * @property {any} [max]
8
+ * @property {string} [height]
9
+ * @property {number} [width]
10
+ */
11
+
12
+ /** @type {Props} */
13
+ let { class: classes = '', value = null, max = 100, height = '1.5mm' } = $props()
14
+
15
+ let indeterminate = $derived(isNil(value) || isNil(max))
16
+ let percentage = $derived(indeterminate ? '100%' : `${(value * 100) / max}%`)
17
+ </script>
18
+
19
+ <rk-progress class:indeterminate class={classes} style:height>
20
+ <value-bar class="h-full" style:width={percentage}></value-bar>
21
+ </rk-progress>
@@ -0,0 +1,51 @@
1
+ <script>
2
+ import { equals } from 'ramda'
3
+ import { defaultStateIcons } from '@rokkit/core'
4
+ import { defaultMapping } from './constants'
5
+
6
+ /**
7
+ * @typedef {Object} Props
8
+ * @property {string} [class]
9
+ * @property {any} value
10
+ * @property {any} name
11
+ * @property {any} [id]
12
+ * @property {import('@rokkit/core).FieldMapper} [mapping]
13
+ * @property {any} [options]
14
+ * @property {boolean} [readOnly]
15
+ * @property {boolean} [textAfter]
16
+ * @property {any} [stateIcons]
17
+ */
18
+
19
+ /** @type {Props} */
20
+ let {
21
+ class: className = '',
22
+ value = $bindable(),
23
+ name,
24
+ id = null,
25
+ mapping = $bindable(defaultMapping),
26
+ options = [],
27
+ readOnly = false,
28
+ textAfter = true,
29
+ stateIcons = defaultStateIcons.radio
30
+ } = $props()
31
+
32
+ let flexDirection = $derived(textAfter ? 'flex-row' : 'flex-row-reverse')
33
+ </script>
34
+
35
+ <rk-radio-group
36
+ {id}
37
+ class="flex cursor-pointer select-none flex-col {className}"
38
+ class:disabled={readOnly}
39
+ >
40
+ {#each options as item}
41
+ {@const itemValue = mapping.getValue(item)}
42
+ {@const label = mapping.getText(item)}
43
+ {@const state = equals(itemValue, value) ? 'on' : 'off'}
44
+
45
+ <label class="flex {flexDirection} items-center gap-2">
46
+ <input hidden type="radio" {name} bind:group={value} value={itemValue} {readOnly} />
47
+ <icon class={stateIcons[state]}></icon>
48
+ <p>{label}</p>
49
+ </label>
50
+ {/each}
51
+ </rk-radio-group>
@@ -0,0 +1,45 @@
1
+ <script>
2
+ import RangeMinMax from './RangeMinMax.svelte'
3
+
4
+ /**
5
+ * @typedef {Object} Props
6
+ * @property {any} [name]
7
+ * @property {string} [class]
8
+ * @property {number} [min]
9
+ * @property {number} [max]
10
+ * @property {number} [value]
11
+ * @property {number} [step]
12
+ * @property {number} [ticks]
13
+ * @property {number} [labelSkip]
14
+ */
15
+
16
+ /** @type {Props} */
17
+ let {
18
+ name = null,
19
+ class: classes = '',
20
+ min = 0,
21
+ max = 100,
22
+ value = $bindable(min),
23
+ step = 1,
24
+ ticks = 10,
25
+ labelSkip = 0
26
+ } = $props()
27
+
28
+ let bounds = $state([min, value])
29
+
30
+ $effect(() => {
31
+ value = bounds[1]
32
+ })
33
+ </script>
34
+
35
+ <RangeMinMax
36
+ bind:value={bounds}
37
+ {name}
38
+ {min}
39
+ {max}
40
+ {step}
41
+ {ticks}
42
+ {labelSkip}
43
+ single
44
+ class={classes}
45
+ />
@@ -0,0 +1,124 @@
1
+ <script>
2
+ import { generateTicks } from '@rokkit/core'
3
+ import { scaleLinear } from 'd3-scale'
4
+ import { onMount } from 'svelte'
5
+ import RangeTick from './RangeTick.svelte'
6
+ import RangeSlider from './RangeSlider.svelte'
7
+
8
+ /**
9
+ * @typedef {Object} Props
10
+ * @property {string} [class]
11
+ * @property {any} [name]
12
+ * @property {number} [min]
13
+ * @property {number} [max]
14
+ * @property {[number,number]} [value]
15
+ * @property {boolean} [single]
16
+ * @property {number} [step]
17
+ * @property {number} [ticks]
18
+ * @property {number} [labelSkip]
19
+ */
20
+
21
+ /** @type {Props} */
22
+ let {
23
+ class: classes = '',
24
+ name = null,
25
+ min = 0,
26
+ max = 100,
27
+ value = $bindable([min, min]),
28
+ single = false,
29
+ step = 1,
30
+ ticks = 10,
31
+ labelSkip = 0
32
+ } = $props()
33
+
34
+ let limits = $state([0, 0])
35
+ let lower = $state(min)
36
+ let upper = $state(min)
37
+ let scale = $state()
38
+ let width = $state()
39
+
40
+ let selectedPos = $derived(`${lower}px`)
41
+ let selectedWidth = $derived(`${upper - lower}px`)
42
+
43
+ function updateScale(width, min, max) {
44
+ if (width) {
45
+ limits = [0, width]
46
+ scale = scaleLinear().domain(limits).range([min, max])
47
+ lower = scale.invert(Math.max(value[0], min))
48
+ upper = scale.invert(value[1])
49
+ }
50
+ }
51
+ function handleClick(event) {
52
+ const distance = [Math.abs(event.detail - value[0]), Math.abs(event.detail - value[1])]
53
+ const index = single ? 1 : distance[0] < distance[1] ? 0 : 1
54
+
55
+ value[index] = event.detail
56
+ if (index === 0) {
57
+ lower = scale.invert(event.detail)
58
+ } else {
59
+ upper = scale.invert(event.detail)
60
+ }
61
+ }
62
+
63
+ let tickStep = $derived(Math.max(1, Math.round((max - min) / ticks)))
64
+ let tickItems = $derived(generateTicks(min, max, tickStep, labelSkip + 1))
65
+ let steps = $derived(
66
+ step > 0
67
+ ? Array.from({ length: 1 + (max - min) / step }, (_, i) => Math.min(min + i * step, max))
68
+ : []
69
+ )
70
+ onMount(() => updateScale(width, min, max))
71
+ </script>
72
+
73
+ {#if !Array.isArray(value)}
74
+ <error>Expected value to be an array</error>
75
+ {:else}
76
+ <rk-input-range class={classes}>
77
+ <input {name} type="hidden" bind:value />
78
+ <rk-range-track class="relative grid">
79
+ <rk-range-track-bar class="relative col-start-2 box-border" bind:clientWidth={width}>
80
+ </rk-range-track-bar>
81
+ <rk-selected-bar
82
+ class="absolute col-start-2"
83
+ style:left={selectedPos}
84
+ style:width={selectedWidth}
85
+ ></rk-selected-bar>
86
+ {#if !single}
87
+ <RangeSlider
88
+ bind:cx={lower}
89
+ bind:value={value[0]}
90
+ {steps}
91
+ {scale}
92
+ min={limits[0]}
93
+ max={upper}
94
+ />
95
+ {/if}
96
+ <RangeSlider
97
+ bind:cx={upper}
98
+ bind:value={value[1]}
99
+ {steps}
100
+ {scale}
101
+ min={lower}
102
+ max={limits[1]}
103
+ />
104
+ </rk-range-track>
105
+
106
+ <rk-ticks style:--count={tickItems.length - 1}>
107
+ {#each tickItems as { value, label }}
108
+ <RangeTick {label} {value} on:click={handleClick} />
109
+ {/each}
110
+ </rk-ticks>
111
+ </rk-input-range>
112
+ {/if}
113
+
114
+ <style>
115
+ rk-range-track {
116
+ grid-template-columns: 0.5rem auto 0.5rem;
117
+ }
118
+ rk-ticks {
119
+ display: grid;
120
+ grid-gap: calc((100% - 1rem * (var(--count) + 1)) / var(--count));
121
+ grid-template-columns: repeat(var(--count), 1rem) 1rem;
122
+ grid-template-rows: 7px auto;
123
+ }
124
+ </style>
@@ -0,0 +1,79 @@
1
+ <script>
2
+ import { pannable } from '@rokkit/actions'
3
+
4
+ /**
5
+ * @typedef Props
6
+ * @property {number} min - The minimum value of the thumb.
7
+ * @property {number} max - The maximum value of the thumb.
8
+ * @property {number} value - The current value of the thumb.
9
+ * @property {number} cx - The current position of the thumb.
10
+ * @property {number[]} steps - An array of steps within a range.
11
+ * @property {import('d3-scale').ScaleLinear} scale - Scale mapping the thumb's position to its value.
12
+ */
13
+ /** @type {Props} */
14
+ let { min = 0, max = 100, value = $bindable(), cx = $bindable(), steps = [], scale } = $props()
15
+
16
+ function handlePanMove(event) {
17
+ let x = cx + event.detail.dx
18
+ let limits = [scale.invert(min), scale.invert(max)]
19
+
20
+ cx = Math.min(Math.max(x, limits[0]), limits[1])
21
+ if (steps.length > 0) {
22
+ const result = scale(x)
23
+ let index = 0
24
+ let matched = false
25
+ while (!matched && index < steps.length - 1) {
26
+ if (steps[index] <= result && steps[index + 1] > result) {
27
+ value =
28
+ result - steps[index] > steps[index + 1] - result ? steps[index + 1] : steps[index]
29
+ matched = true
30
+ }
31
+ index++
32
+ }
33
+ } else {
34
+ value = scale(cx)
35
+ }
36
+ }
37
+ function handlePanEnd() {
38
+ sliding = false
39
+ if (steps.length > 0) {
40
+ cx = scale.invert(value)
41
+ }
42
+ }
43
+ function handleKeyDown(event) {
44
+ if (steps.length === 0) {
45
+ const offset = (max - min) / 10
46
+ const step = event.key === 'ArrowLeft' ? -offset : event.key === 'ArrowRight' ? offset : 0
47
+ value = Math.min(Math.max(value + step, min), max)
48
+ } else {
49
+ const index = steps.findIndex((step) => step === value)
50
+ if (event.key === 'ArrowLeft' && index > 0) {
51
+ value = steps[index - 1]
52
+ cx = scale.invert(value)
53
+ } else if (event.key === 'ArrowRight' && index < steps.length - 1) {
54
+ value = steps[index + 1]
55
+ cx = scale.invert(value)
56
+ }
57
+ }
58
+ }
59
+ let sliding = $state(false)
60
+ let position = $derived(`${cx}px`)
61
+ </script>
62
+
63
+ <rk-thumb
64
+ class="absolute -top-1 box-border h-4 w-4 cursor-pointer"
65
+ style:left={position}
66
+ class:sliding
67
+ tabindex="0"
68
+ onfocus={() => (sliding = true)}
69
+ onblur={() => (sliding = false)}
70
+ use:pannable
71
+ onpanmove={handlePanMove}
72
+ onpanstart={() => (sliding = true)}
73
+ onpanend={handlePanEnd}
74
+ onkeydown={handleKeyDown}
75
+ role="slider"
76
+ aria-valuenow={value}
77
+ aria-valuemin={min}
78
+ aria-valuemax={max}
79
+ ></rk-thumb>
@@ -0,0 +1,28 @@
1
+ <script>
2
+ import { noop } from '@rokkit/core'
3
+ /**
4
+ * @typedef {Object} Props
5
+ * @property {string} [label]
6
+ * @property {number} value
7
+ * @property {boolean} [selected]
8
+ * @property {Function} [onclick]
9
+ */
10
+
11
+ /** @type {Props} */
12
+ let { label = null, value, selected = false, onclick = noop } = $props()
13
+ let text = $derived(label ?? value.toFixed(0))
14
+ </script>
15
+
16
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
17
+ <rk-tick
18
+ class="grid cursor-pointer select-none grid-cols-2"
19
+ {onclick}
20
+ role="option"
21
+ aria-selected={selected}
22
+ tabindex="0"
23
+ >
24
+ <rk-tick-bar class="h-5px col-start-2 border-l"></rk-tick-bar>
25
+ <p class="col-span-2 flex justify-center">
26
+ {text}
27
+ </p>
28
+ </rk-tick>
@@ -0,0 +1,95 @@
1
+ <script>
2
+ import { createEmitter, defaultStateIcons } from '@rokkit/core'
3
+ import Icon from './Icon.svelte'
4
+
5
+ /**
6
+ * @typedef {Object} Props
7
+ * @property {any} [id]
8
+ * @property {any} [name]
9
+ * @property {number} [value]
10
+ * @property {number} [max]
11
+ * @property {boolean} [disabled]
12
+ * @property {any} [stateIcons]
13
+ * @property {string} [placeholder]
14
+ * @property {number} [tabindex]
15
+ * @event {CustomEvent} [onchange]
16
+ */
17
+
18
+ /** @type {Props} */
19
+ let {
20
+ id = null,
21
+ name = null,
22
+ value = $bindable(0),
23
+ max = 5,
24
+ disabled = false,
25
+ stateIcons = defaultStateIcons.rating,
26
+ placeholder = 'Rating',
27
+ tabindex = 0,
28
+ ...events
29
+ } = $props()
30
+
31
+ let emitter = $derived(createEmitter(events, ['change']))
32
+ function handleClick(index) {
33
+ if (!disabled) {
34
+ value = value === 1 && index === 0 ? index : index + 1
35
+ emitter.change({ value })
36
+ }
37
+ }
38
+ function handleEnter(index) {
39
+ if (!disabled) {
40
+ hoverIndex = index
41
+ }
42
+ }
43
+ function handleLeave() {
44
+ if (!disabled) {
45
+ hoverIndex = -1
46
+ }
47
+ }
48
+ function handleKeyDown(event) {
49
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
50
+ event.preventDefault()
51
+ value = Math.max(value - 1, 0)
52
+ } else if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
53
+ event.preventDefault()
54
+ value = Math.min(value + 1, max)
55
+ } else {
56
+ var number = parseInt(event.code.replace('Digit', ''), 10)
57
+ if (number >= 0 && number <= 9 && number <= max) {
58
+ event.preventDefault()
59
+ value = number
60
+ }
61
+ }
62
+ }
63
+
64
+ let hoverIndex = $state(-1)
65
+ let stars = $derived([...Array(max).keys()].map((i) => i < value))
66
+ </script>
67
+
68
+ <rk-rating
69
+ {id}
70
+ class="flex cursor-pointer select-none"
71
+ class:disabled
72
+ {tabindex}
73
+ role="radiogroup"
74
+ onkeydown={handleKeyDown}
75
+ >
76
+ {#if name}
77
+ <input {name} hidden type="number" bind:value min={0} {max} readOnly={disabled} />
78
+ {/if}
79
+ {#each stars as selected, index}
80
+ {@const stateIcon = selected ? stateIcons.filled : stateIcons.empty}
81
+ {@const label = [placeholder, index + 1, 'out of', max].join(' ')}
82
+ <Icon
83
+ name={stateIcon}
84
+ {label}
85
+ role="option"
86
+ {disabled}
87
+ checked={index < value}
88
+ class={index <= hoverIndex ? 'hovering' : ''}
89
+ onmouseenter={() => handleEnter(index)}
90
+ onmouseleave={handleLeave}
91
+ onclick={() => handleClick(index)}
92
+ tabindex="-1"
93
+ />
94
+ {/each}
95
+ </rk-rating>