@rokkit/ui 1.0.0-next.137 → 1.0.0-next.139
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/package.json +1 -1
- package/src/components/Carousel.svelte +0 -1
- package/src/components/FloatingAction.svelte +66 -37
- package/src/components/FloatingNavigation.svelte +78 -68
- package/src/components/LazyTree.svelte +1 -2
- package/src/components/List.svelte +0 -1
- package/src/components/Menu.svelte +0 -1
- package/src/components/MultiSelect.svelte +30 -43
- package/src/components/Range.svelte +52 -62
- package/src/components/Rating.svelte +26 -16
- package/src/components/SearchFilter.svelte +1 -1
- package/src/components/Select.svelte +72 -52
- package/src/components/Switch.svelte +10 -14
- package/src/components/Table.svelte +10 -13
- package/src/components/Tabs.svelte +0 -2
- package/src/components/Toolbar.svelte +9 -13
- package/src/components/Tree.svelte +0 -1
- package/src/components/UploadTarget.svelte +13 -15
- package/src/utils/palette.ts +38 -55
- package/src/utils/shiki.ts +16 -24
- package/src/utils/upload.js +52 -38
package/package.json
CHANGED
|
@@ -98,18 +98,24 @@
|
|
|
98
98
|
trigger?.focus()
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Move DOM focus to the menu item at the given index
|
|
103
|
+
*/
|
|
104
|
+
function focusDomItem(index: number) {
|
|
105
|
+
const menu = fabRef?.querySelector('[data-fab-menu]')
|
|
106
|
+
if (!menu) return
|
|
107
|
+
const menuItems = menu.querySelectorAll('[data-fab-item]:not([data-disabled])')
|
|
108
|
+
const menuItem = menuItems[index] as HTMLElement | undefined
|
|
109
|
+
menuItem?.focus()
|
|
110
|
+
}
|
|
111
|
+
|
|
101
112
|
/**
|
|
102
113
|
* Focus an item by index
|
|
103
114
|
*/
|
|
104
115
|
function focusItem(index: number) {
|
|
105
116
|
if (index < 0 || index >= flatItems.length) return
|
|
106
117
|
focusedIndex = index
|
|
107
|
-
|
|
108
|
-
if (menu) {
|
|
109
|
-
const menuItems = menu.querySelectorAll('[data-fab-item]:not([data-disabled])')
|
|
110
|
-
const menuItem = menuItems[index] as HTMLElement | undefined
|
|
111
|
-
menuItem?.focus()
|
|
112
|
-
}
|
|
118
|
+
focusDomItem(index)
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
/**
|
|
@@ -125,42 +131,65 @@
|
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Close menu and return focus to trigger
|
|
136
|
+
*/
|
|
137
|
+
function closeAndFocusTrigger() {
|
|
138
|
+
close()
|
|
139
|
+
const trigger = fabRef?.querySelector('[data-fab-trigger]') as HTMLElement | undefined
|
|
140
|
+
trigger?.focus()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Activate the currently focused item
|
|
145
|
+
*/
|
|
146
|
+
function activateFocusedItem() {
|
|
147
|
+
if (focusedIndex >= 0 && focusedIndex < flatItems.length) {
|
|
148
|
+
handleItemClick(flatItems[focusedIndex])
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function wrapNext(current: number, last: number): number {
|
|
153
|
+
return current < last ? current + 1 : 0
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function wrapPrev(current: number, last: number): number {
|
|
157
|
+
return current > 0 ? current - 1 : last
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function nextFabIndex(key: string, current: number, last: number): number {
|
|
161
|
+
if (key === 'ArrowDown') return wrapNext(current, last)
|
|
162
|
+
if (key === 'ArrowUp') return wrapPrev(current, last)
|
|
163
|
+
if (key === 'Home') return 0
|
|
164
|
+
if (key === 'End') return last
|
|
165
|
+
return -1
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handleFabNavMove(event: KeyboardEvent): boolean {
|
|
169
|
+
const last = flatItems.length - 1
|
|
170
|
+
const next = nextFabIndex(event.key, focusedIndex, last)
|
|
171
|
+
if (next === -1) return false
|
|
172
|
+
focusItem(next)
|
|
173
|
+
return true
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isActivateKey(key: string): boolean {
|
|
177
|
+
return key === 'Enter' || key === ' '
|
|
178
|
+
}
|
|
179
|
+
|
|
128
180
|
/**
|
|
129
181
|
* Handle keyboard navigation when menu is open
|
|
130
182
|
*/
|
|
131
183
|
function handleKeyDown(event: KeyboardEvent) {
|
|
132
184
|
if (!open) return
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
case 'ArrowDown':
|
|
142
|
-
event.preventDefault()
|
|
143
|
-
focusItem(focusedIndex < flatItems.length - 1 ? focusedIndex + 1 : 0)
|
|
144
|
-
break
|
|
145
|
-
case 'ArrowUp':
|
|
146
|
-
event.preventDefault()
|
|
147
|
-
focusItem(focusedIndex > 0 ? focusedIndex - 1 : flatItems.length - 1)
|
|
148
|
-
break
|
|
149
|
-
case 'Home':
|
|
150
|
-
event.preventDefault()
|
|
151
|
-
focusItem(0)
|
|
152
|
-
break
|
|
153
|
-
case 'End':
|
|
154
|
-
event.preventDefault()
|
|
155
|
-
focusItem(flatItems.length - 1)
|
|
156
|
-
break
|
|
157
|
-
case 'Enter':
|
|
158
|
-
case ' ':
|
|
159
|
-
event.preventDefault()
|
|
160
|
-
if (focusedIndex >= 0 && focusedIndex < flatItems.length) {
|
|
161
|
-
handleItemClick(flatItems[focusedIndex])
|
|
162
|
-
}
|
|
163
|
-
break
|
|
185
|
+
if (event.key === 'Escape') {
|
|
186
|
+
event.preventDefault()
|
|
187
|
+
closeAndFocusTrigger()
|
|
188
|
+
} else if (isActivateKey(event.key)) {
|
|
189
|
+
event.preventDefault()
|
|
190
|
+
activateFocusedItem()
|
|
191
|
+
} else if (handleFabNavMove(event)) {
|
|
192
|
+
event.preventDefault()
|
|
164
193
|
}
|
|
165
194
|
}
|
|
166
195
|
|
|
@@ -61,65 +61,95 @@
|
|
|
61
61
|
if (!pinned) expanded = false
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function getItemHref(item: { proxy: ProxyItem; original: Record<string, unknown> }): string {
|
|
65
|
+
if (item.proxy.get('href') === undefined) return ''
|
|
66
|
+
return String(item.original[userFields?.href ?? 'href'] ?? '')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveTargetId(item: { proxy: ProxyItem; original: Record<string, unknown> }): string {
|
|
70
|
+
const href = getItemHref(item)
|
|
71
|
+
return href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
function handleItemClick(item: { proxy: ProxyItem; original: Record<string, unknown> }) {
|
|
65
75
|
value = item.proxy.value
|
|
66
76
|
onselect?.(item.proxy.value, item.original)
|
|
67
|
-
|
|
68
|
-
// Smooth scroll to target section
|
|
69
|
-
const href =
|
|
70
|
-
item.proxy.get('href') !== undefined
|
|
71
|
-
? String(item.original[userFields?.href ?? 'href'] ?? '')
|
|
72
|
-
: ''
|
|
73
|
-
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
74
|
-
const el = document.getElementById(targetId)
|
|
77
|
+
const el = document.getElementById(resolveTargetId(item))
|
|
75
78
|
el?.scrollIntoView({ behavior: 'smooth' })
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
function activateFocusedItem() {
|
|
82
|
+
if (focusedIndex >= 0 && focusedIndex < itemProxies.length) {
|
|
83
|
+
handleItemClick(itemProxies[focusedIndex])
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const nextKey = $derived(isVertical ? 'ArrowDown' : 'ArrowRight')
|
|
88
|
+
const prevKey = $derived(isVertical ? 'ArrowUp' : 'ArrowLeft')
|
|
89
|
+
|
|
90
|
+
function wrapNext(current: number, last: number): number {
|
|
91
|
+
return current < last ? current + 1 : 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function wrapPrev(current: number, last: number): number {
|
|
95
|
+
return current > 0 ? current - 1 : last
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getNextNavIndex(key: string, current: number, last: number): number {
|
|
99
|
+
if (key === nextKey) return wrapNext(current, last)
|
|
100
|
+
if (key === prevKey) return wrapPrev(current, last)
|
|
101
|
+
if (key === 'Home') return 0
|
|
102
|
+
if (key === 'End') return last
|
|
103
|
+
return -1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function handleNavMove(event: KeyboardEvent): boolean {
|
|
107
|
+
const last = itemProxies.length - 1
|
|
108
|
+
const next = getNextNavIndex(event.key, focusedIndex, last)
|
|
109
|
+
if (next === -1) return false
|
|
110
|
+
focusItem(next)
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isActivateKey(key: string): boolean {
|
|
115
|
+
return key === 'Enter' || key === ' '
|
|
116
|
+
}
|
|
117
|
+
|
|
78
118
|
function handleKeyDown(event: KeyboardEvent) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
case prevKey:
|
|
88
|
-
event.preventDefault()
|
|
89
|
-
focusItem(focusedIndex > 0 ? focusedIndex - 1 : itemProxies.length - 1)
|
|
90
|
-
break
|
|
91
|
-
case 'Home':
|
|
92
|
-
event.preventDefault()
|
|
93
|
-
focusItem(0)
|
|
94
|
-
break
|
|
95
|
-
case 'End':
|
|
96
|
-
event.preventDefault()
|
|
97
|
-
focusItem(itemProxies.length - 1)
|
|
98
|
-
break
|
|
99
|
-
case 'Enter':
|
|
100
|
-
case ' ':
|
|
101
|
-
event.preventDefault()
|
|
102
|
-
if (focusedIndex >= 0 && focusedIndex < itemProxies.length) {
|
|
103
|
-
handleItemClick(itemProxies[focusedIndex])
|
|
104
|
-
}
|
|
105
|
-
break
|
|
106
|
-
case 'Escape':
|
|
107
|
-
if (!pinned) {
|
|
108
|
-
event.preventDefault()
|
|
109
|
-
expanded = false
|
|
110
|
-
}
|
|
111
|
-
break
|
|
119
|
+
if (isActivateKey(event.key)) {
|
|
120
|
+
event.preventDefault()
|
|
121
|
+
activateFocusedItem()
|
|
122
|
+
} else if (event.key === 'Escape' && !pinned) {
|
|
123
|
+
event.preventDefault()
|
|
124
|
+
expanded = false
|
|
125
|
+
} else if (handleNavMove(event)) {
|
|
126
|
+
event.preventDefault()
|
|
112
127
|
}
|
|
113
128
|
}
|
|
114
129
|
|
|
130
|
+
function focusDomItem(index: number) {
|
|
131
|
+
const itemsContainer = navRef?.querySelector('[data-floating-nav-items]')
|
|
132
|
+
if (!itemsContainer) return
|
|
133
|
+
const navItems = itemsContainer.querySelectorAll('[data-floating-nav-item]')
|
|
134
|
+
const item = navItems[index] as HTMLElement | undefined
|
|
135
|
+
item?.focus()
|
|
136
|
+
}
|
|
137
|
+
|
|
115
138
|
function focusItem(index: number) {
|
|
116
139
|
if (index < 0 || index >= itemProxies.length) return
|
|
117
140
|
focusedIndex = index
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
focusDomItem(index)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function matchesEntryId(item: { proxy: ProxyItem; original: Record<string, unknown> }, id: string): boolean {
|
|
145
|
+
return resolveTargetId(item) === id
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function handleIntersection(entries: IntersectionObserverEntry[]) {
|
|
149
|
+
for (const entry of entries) {
|
|
150
|
+
if (!entry.isIntersecting) continue
|
|
151
|
+
const match = itemProxies.find((item) => matchesEntryId(item, entry.target.id))
|
|
152
|
+
if (match) value = match.proxy.value
|
|
123
153
|
}
|
|
124
154
|
}
|
|
125
155
|
|
|
@@ -127,30 +157,10 @@
|
|
|
127
157
|
$effect(() => {
|
|
128
158
|
if (!observe || itemProxies.length === 0) return
|
|
129
159
|
|
|
130
|
-
const observer = new IntersectionObserver(
|
|
131
|
-
for (const entry of entries) {
|
|
132
|
-
if (entry.isIntersecting) {
|
|
133
|
-
const match = itemProxies.find((item) => {
|
|
134
|
-
const href =
|
|
135
|
-
item.proxy.get('href') !== undefined
|
|
136
|
-
? String(item.original[userFields?.href ?? 'href'] ?? '')
|
|
137
|
-
: ''
|
|
138
|
-
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
139
|
-
return targetId === entry.target.id
|
|
140
|
-
})
|
|
141
|
-
if (match) {
|
|
142
|
-
value = match.proxy.value
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}, observerOptions)
|
|
160
|
+
const observer = new IntersectionObserver(handleIntersection, observerOptions)
|
|
147
161
|
|
|
148
162
|
for (const item of itemProxies) {
|
|
149
|
-
const
|
|
150
|
-
item.proxy.get('href') !== undefined
|
|
151
|
-
? String(item.original[userFields?.href ?? 'href'] ?? '')
|
|
152
|
-
: ''
|
|
153
|
-
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
163
|
+
const targetId = resolveTargetId(item)
|
|
154
164
|
const el = document.getElementById(targetId)
|
|
155
165
|
if (el) observer.observe(el)
|
|
156
166
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
f,
|
|
58
58
|
key,
|
|
59
59
|
level,
|
|
60
|
-
onlazyload ?
|
|
60
|
+
onlazyload ? (_value, rawItem) => onlazyload(rawItem) : null
|
|
61
61
|
)
|
|
62
62
|
})
|
|
63
63
|
)
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
})
|
|
78
78
|
</script>
|
|
79
79
|
|
|
80
|
-
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
81
80
|
<div
|
|
82
81
|
bind:this={treeRef}
|
|
83
82
|
data-tree
|
|
@@ -214,7 +214,6 @@
|
|
|
214
214
|
</button>
|
|
215
215
|
|
|
216
216
|
{#if isOpen}
|
|
217
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
218
217
|
<div bind:this={dropdownRef} data-menu-dropdown role="menu" aria-orientation="vertical">
|
|
219
218
|
{#each wrapper.flatView as node (node.key)}
|
|
220
219
|
{@const proxy = node.proxy}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
// @ts-nocheck
|
|
28
28
|
import type { ProxyItem } from '@rokkit/states'
|
|
29
29
|
import { Wrapper, ProxyTree } from '@rokkit/states'
|
|
30
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
30
31
|
import { Navigator, Trigger } from '@rokkit/actions'
|
|
31
32
|
import { DEFAULT_STATE_ICONS, resolveSnippet, ITEM_SNIPPET, GROUP_SNIPPET } from '@rokkit/core'
|
|
32
33
|
import ItemContent from './ItemContent.svelte'
|
|
@@ -133,65 +134,52 @@
|
|
|
133
134
|
return (value ?? []).some((v) => v === extractedValue)
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
function
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
let newValues: unknown[]
|
|
141
|
-
let newItems: unknown[]
|
|
142
|
-
|
|
143
|
-
if (alreadySelected) {
|
|
144
|
-
newValues = currentValues.filter((v) => v !== extractedValue)
|
|
145
|
-
// Rebuild selected items from remaining values
|
|
146
|
-
newItems = []
|
|
147
|
-
for (const [, proxy] of wrapper.lookup) {
|
|
148
|
-
if (!proxy.hasChildren && newValues.some((v) => v === proxy.value)) {
|
|
149
|
-
newItems.push(proxy.original)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
} else {
|
|
153
|
-
newValues = [...currentValues, extractedValue]
|
|
154
|
-
// Rebuild selected items from lookup to include all values
|
|
155
|
-
newItems = []
|
|
156
|
-
for (const [, proxy] of wrapper.lookup) {
|
|
157
|
-
if (!proxy.hasChildren && newValues.some((v) => v === proxy.value)) {
|
|
158
|
-
newItems.push(proxy.original)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
137
|
+
function buildSelectedItems(newValues: unknown[]): unknown[] {
|
|
138
|
+
const result: unknown[] = []
|
|
139
|
+
for (const [, proxy] of wrapper.lookup) {
|
|
140
|
+
if (isLeafWithValue(proxy, newValues)) result.push(proxy.original)
|
|
161
141
|
}
|
|
142
|
+
return result
|
|
143
|
+
}
|
|
162
144
|
|
|
145
|
+
function applySelection(newValues: unknown[]) {
|
|
146
|
+
const newItems = buildSelectedItems(newValues)
|
|
163
147
|
value = newValues
|
|
164
148
|
selected = newItems
|
|
165
149
|
onchange?.(newValues, newItems)
|
|
166
150
|
}
|
|
167
151
|
|
|
168
|
-
function
|
|
152
|
+
function toggleItemSelection(extractedValue: unknown) {
|
|
169
153
|
const currentValues = value ?? []
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
154
|
+
const alreadySelected = currentValues.some((v) => v === extractedValue)
|
|
155
|
+
const newValues = alreadySelected
|
|
156
|
+
? currentValues.filter((v) => v !== extractedValue)
|
|
157
|
+
: [...currentValues, extractedValue]
|
|
158
|
+
applySelection(newValues)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function removeTag(extractedValue: unknown) {
|
|
162
|
+
const newValues = (value ?? []).filter((v) => v !== extractedValue)
|
|
163
|
+
applySelection(newValues)
|
|
180
164
|
}
|
|
181
165
|
|
|
182
166
|
// ─── Selected items for tags display ──────────────────────────────────────
|
|
183
167
|
|
|
184
|
-
|
|
168
|
+
function isLeafWithValue(proxy: ProxyItem, vals: unknown[]): boolean {
|
|
169
|
+
return !proxy.hasChildren && vals.some((v) => v === proxy.value)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function computeSelectedProxies(): ProxyItem[] {
|
|
185
173
|
const vals = value ?? []
|
|
186
174
|
if (vals.length === 0) return []
|
|
187
175
|
const result: ProxyItem[] = []
|
|
188
176
|
for (const [, proxy] of wrapper.lookup) {
|
|
189
|
-
if (
|
|
190
|
-
result.push(proxy)
|
|
191
|
-
}
|
|
177
|
+
if (isLeafWithValue(proxy, vals)) result.push(proxy)
|
|
192
178
|
}
|
|
193
179
|
return result
|
|
194
|
-
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const selectedProxies = $derived.by(computeSelectedProxies)
|
|
195
183
|
|
|
196
184
|
// ─── Trigger action ───────────────────────────────────────────────────────
|
|
197
185
|
|
|
@@ -236,7 +224,7 @@
|
|
|
236
224
|
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
237
225
|
|
|
238
226
|
const groupDividers = $derived.by(() => {
|
|
239
|
-
const set = new
|
|
227
|
+
const set = new SvelteSet<string>()
|
|
240
228
|
let foundFirst = false
|
|
241
229
|
for (const node of wrapper.flatView) {
|
|
242
230
|
if (node.hasChildren) {
|
|
@@ -318,7 +306,6 @@
|
|
|
318
306
|
</button>
|
|
319
307
|
|
|
320
308
|
{#if isOpen}
|
|
321
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
322
309
|
<div
|
|
323
310
|
bind:this={dropdownRef}
|
|
324
311
|
data-select-dropdown
|
|
@@ -81,22 +81,18 @@
|
|
|
81
81
|
slidingUpper = true
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
function panUpperBy(dx: number) {
|
|
85
|
+
const currentPx = inverseLerp(rangeMode ? upper : value, min, max) * trackWidth
|
|
86
|
+
const minPx = rangeMode ? inverseLerp(lower, min, max) * trackWidth : 0
|
|
87
|
+
const newPx = clamp(currentPx + dx, minPx, trackWidth)
|
|
88
|
+
const snapped = snapToStep(clamp(pixelToValue(newPx), rangeMode ? lower : min, max))
|
|
89
|
+
if (rangeMode) upper = snapped
|
|
90
|
+
else value = snapped
|
|
91
|
+
}
|
|
92
|
+
|
|
84
93
|
function handleUpperPanMove(event: CustomEvent) {
|
|
85
94
|
if (disabled || trackWidth === 0) return
|
|
86
|
-
|
|
87
|
-
const currentPx = inverseLerp(rangeMode ? upper : value, min, max) * trackWidth
|
|
88
|
-
const newPx = clamp(
|
|
89
|
-
currentPx + dx,
|
|
90
|
-
rangeMode ? inverseLerp(lower, min, max) * trackWidth : 0,
|
|
91
|
-
trackWidth
|
|
92
|
-
)
|
|
93
|
-
const raw = pixelToValue(newPx)
|
|
94
|
-
const snapped = snapToStep(clamp(raw, rangeMode ? lower : min, max))
|
|
95
|
-
if (rangeMode) {
|
|
96
|
-
upper = snapped
|
|
97
|
-
} else {
|
|
98
|
-
value = snapped
|
|
99
|
-
}
|
|
95
|
+
panUpperBy(event.detail.dx as number)
|
|
100
96
|
fireChange()
|
|
101
97
|
}
|
|
102
98
|
|
|
@@ -111,41 +107,37 @@
|
|
|
111
107
|
fireChange()
|
|
112
108
|
}
|
|
113
109
|
|
|
114
|
-
function
|
|
115
|
-
if (disabled) return
|
|
110
|
+
function nudgeUpper(delta: number) {
|
|
116
111
|
const increment = step > 0 ? step : (max - min) / 10
|
|
112
|
+
if (rangeMode) upper = clamp(snapToStep(upper + delta * increment), lower, max)
|
|
113
|
+
else value = clamp(snapToStep(value + delta * increment), min, max)
|
|
114
|
+
}
|
|
117
115
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
} else if (event.key === 'End') {
|
|
116
|
+
function jumpUpper(toEnd: boolean) {
|
|
117
|
+
if (rangeMode) upper = toEnd ? max : lower
|
|
118
|
+
else value = toEnd ? max : min
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isIncreaseKey(key: string): boolean {
|
|
122
|
+
return key === 'ArrowRight' || key === 'ArrowUp'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isDecreaseKey(key: string): boolean {
|
|
126
|
+
return key === 'ArrowLeft' || key === 'ArrowDown'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function applyUpperKey(key: string): boolean {
|
|
130
|
+
if (isIncreaseKey(key)) { nudgeUpper(1); return true }
|
|
131
|
+
if (isDecreaseKey(key)) { nudgeUpper(-1); return true }
|
|
132
|
+
if (key === 'Home') { jumpUpper(false); return true }
|
|
133
|
+
if (key === 'End') { jumpUpper(true); return true }
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function handleUpperKeyDown(event: KeyboardEvent) {
|
|
138
|
+
if (disabled) return
|
|
139
|
+
if (applyUpperKey(event.key)) {
|
|
143
140
|
event.preventDefault()
|
|
144
|
-
if (rangeMode) {
|
|
145
|
-
upper = max
|
|
146
|
-
} else {
|
|
147
|
-
value = max
|
|
148
|
-
}
|
|
149
141
|
fireChange()
|
|
150
142
|
}
|
|
151
143
|
}
|
|
@@ -175,25 +167,23 @@
|
|
|
175
167
|
fireChange()
|
|
176
168
|
}
|
|
177
169
|
|
|
178
|
-
function
|
|
179
|
-
if (disabled) return
|
|
170
|
+
function nudgeLower(delta: number) {
|
|
180
171
|
const increment = step > 0 ? step : (max - min) / 10
|
|
172
|
+
lower = clamp(snapToStep(lower + delta * increment), min, upper)
|
|
173
|
+
}
|
|
181
174
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
fireChange()
|
|
194
|
-
} else if (event.key === 'End') {
|
|
175
|
+
function applyLowerKey(key: string): boolean {
|
|
176
|
+
if (isIncreaseKey(key)) { nudgeLower(1); return true }
|
|
177
|
+
if (isDecreaseKey(key)) { nudgeLower(-1); return true }
|
|
178
|
+
if (key === 'Home') { lower = min; return true }
|
|
179
|
+
if (key === 'End') { lower = upper; return true }
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function handleLowerKeyDown(event: KeyboardEvent) {
|
|
184
|
+
if (disabled) return
|
|
185
|
+
if (applyLowerKey(event.key)) {
|
|
195
186
|
event.preventDefault()
|
|
196
|
-
lower = upper
|
|
197
187
|
fireChange()
|
|
198
188
|
}
|
|
199
189
|
}
|
|
@@ -45,31 +45,41 @@
|
|
|
45
45
|
onchange?.(newValue)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function setRating(newValue: number) {
|
|
49
|
+
value = newValue
|
|
50
|
+
onchange?.(newValue)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function tryDigitKey(key: string): boolean {
|
|
54
|
+
const digit = parseInt(key, 10)
|
|
55
|
+
if (isNaN(digit) || digit < 0 || digit > max) return false
|
|
56
|
+
setRating(digit)
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isIncreaseKey(key: string): boolean {
|
|
61
|
+
return key === 'ArrowRight' || key === 'ArrowUp'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isDecreaseKey(key: string): boolean {
|
|
65
|
+
return key === 'ArrowLeft' || key === 'ArrowDown'
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
function handleKeyDown(event: KeyboardEvent) {
|
|
49
69
|
if (disabled) return
|
|
50
70
|
|
|
51
|
-
if (event.key
|
|
71
|
+
if (isIncreaseKey(event.key)) {
|
|
72
|
+
event.preventDefault()
|
|
73
|
+
setRating(Math.min(value + 1, max))
|
|
74
|
+
} else if (isDecreaseKey(event.key)) {
|
|
52
75
|
event.preventDefault()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
onchange?.(newValue)
|
|
56
|
-
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
|
|
76
|
+
setRating(Math.max(value - 1, 0))
|
|
77
|
+
} else if (tryDigitKey(event.key)) {
|
|
57
78
|
event.preventDefault()
|
|
58
|
-
const newValue = Math.max(value - 1, 0)
|
|
59
|
-
value = newValue
|
|
60
|
-
onchange?.(newValue)
|
|
61
|
-
} else {
|
|
62
|
-
const digit = parseInt(event.key, 10)
|
|
63
|
-
if (!isNaN(digit) && digit >= 0 && digit <= max) {
|
|
64
|
-
event.preventDefault()
|
|
65
|
-
value = digit
|
|
66
|
-
onchange?.(digit)
|
|
67
|
-
}
|
|
68
79
|
}
|
|
69
80
|
}
|
|
70
81
|
</script>
|
|
71
82
|
|
|
72
|
-
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
73
83
|
<div
|
|
74
84
|
data-rating
|
|
75
85
|
data-rating-disabled={disabled || undefined}
|