@rokkit/ui 1.0.0-next.127 → 1.0.0-next.129
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 +6 -16
- package/src/components/BreadCrumbs.svelte +37 -17
- package/src/components/Button.svelte +11 -5
- package/src/components/Carousel.svelte +11 -6
- package/src/components/Code.svelte +6 -2
- package/src/components/FloatingAction.svelte +24 -21
- package/src/components/FloatingNavigation.svelte +36 -29
- package/src/components/Grid.svelte +128 -0
- package/src/components/ItemContent.svelte +21 -20
- package/src/components/LazyTree.svelte +165 -0
- package/src/components/List.svelte +146 -434
- package/src/components/Menu.svelte +195 -346
- package/src/components/MultiSelect.svelte +238 -390
- package/src/components/PaletteManager.svelte +15 -5
- package/src/components/Pill.svelte +19 -14
- package/src/components/Range.svelte +8 -3
- package/src/components/Rating.svelte +19 -9
- package/src/components/Reveal.svelte +1 -13
- package/src/components/SearchFilter.svelte +11 -3
- package/src/components/Select.svelte +265 -454
- package/src/components/Stepper.svelte +9 -6
- package/src/components/Switch.svelte +11 -11
- package/src/components/Table.svelte +0 -1
- package/src/components/Tabs.svelte +96 -172
- package/src/components/Timeline.svelte +5 -5
- package/src/components/Toggle.svelte +55 -119
- package/src/components/Toolbar.svelte +24 -23
- package/src/components/Tree.svelte +115 -584
- package/src/components/UploadFileStatus.svelte +83 -0
- package/src/components/UploadProgress.svelte +131 -0
- package/src/components/UploadTarget.svelte +124 -0
- package/src/components/index.ts +5 -0
- package/src/index.ts +6 -1
- package/src/types/button.ts +3 -0
- package/src/types/code.ts +4 -4
- package/src/types/floating-action.ts +13 -8
- package/src/types/floating-navigation.ts +14 -2
- package/src/types/index.ts +5 -3
- package/src/types/list.ts +10 -6
- package/src/types/menu.ts +38 -138
- package/src/types/palette.ts +17 -0
- package/src/types/select.ts +33 -63
- package/src/types/switch.ts +9 -5
- package/src/types/table.ts +6 -6
- package/src/types/tabs.ts +13 -34
- package/src/types/timeline.ts +5 -3
- package/src/types/toggle.ts +15 -56
- package/src/types/toolbar.ts +1 -1
- package/src/types/tree.ts +9 -18
- package/src/types/upload-file-status.ts +45 -0
- package/src/types/upload-progress.ts +111 -0
- package/src/types/upload-target.ts +68 -0
- package/src/utils/upload.js +128 -0
- package/src/types/item-proxy.ts +0 -358
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/ui",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.129",
|
|
4
4
|
"description": "Data driven UI components for Rokkit applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"svelte": "./src/index.ts",
|
|
@@ -25,8 +25,6 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
28
|
-
"test": "vitest run",
|
|
29
|
-
"test:watch": "vitest",
|
|
30
28
|
"build": "echo 'No build step needed for source-only package'"
|
|
31
29
|
},
|
|
32
30
|
"keywords": [
|
|
@@ -37,26 +35,18 @@
|
|
|
37
35
|
"dropdown"
|
|
38
36
|
],
|
|
39
37
|
"dependencies": {
|
|
40
|
-
"@rokkit/core": "1.0.0-next.
|
|
41
|
-
"@rokkit/data": "1.0.0-next.
|
|
42
|
-
"@rokkit/states": "1.0.0-next.
|
|
43
|
-
"@rokkit/actions": "1.0.0-next.
|
|
38
|
+
"@rokkit/core": "1.0.0-next.129",
|
|
39
|
+
"@rokkit/data": "1.0.0-next.129",
|
|
40
|
+
"@rokkit/states": "1.0.0-next.129",
|
|
41
|
+
"@rokkit/actions": "1.0.0-next.129"
|
|
44
42
|
},
|
|
45
43
|
"peerDependencies": {
|
|
46
44
|
"shiki": "^3.23.0",
|
|
47
45
|
"svelte": "^5.0.0"
|
|
48
46
|
},
|
|
49
47
|
"devDependencies": {
|
|
50
|
-
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
51
|
-
"@testing-library/jest-dom": "^6.9.1",
|
|
52
|
-
"@testing-library/svelte": "^5.3.1",
|
|
53
|
-
"@testing-library/user-event": "^14.6.1",
|
|
54
|
-
"@vitest/browser": "^4.0.18",
|
|
55
|
-
"playwright": "^1.58.2",
|
|
56
48
|
"svelte": "^5.53.5",
|
|
57
49
|
"svelte-check": "^4.4.3",
|
|
58
|
-
"typescript": "^5.9.3"
|
|
59
|
-
"vite": "^7.3.1",
|
|
60
|
-
"vitest": "^4.0.18"
|
|
50
|
+
"typescript": "^5.9.3"
|
|
61
51
|
}
|
|
62
52
|
}
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
|
-
import {
|
|
3
|
+
import { ProxyItem, messages } from '@rokkit/states'
|
|
4
|
+
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
5
|
+
|
|
6
|
+
interface BreadCrumbsIcons {
|
|
7
|
+
separator?: string
|
|
8
|
+
}
|
|
4
9
|
|
|
5
10
|
interface BreadCrumbsProps {
|
|
6
11
|
/** Array of breadcrumb items */
|
|
7
12
|
items?: unknown[]
|
|
8
13
|
/** Custom field mappings */
|
|
9
|
-
fields?:
|
|
10
|
-
/**
|
|
11
|
-
|
|
14
|
+
fields?: Record<string, string>
|
|
15
|
+
/** Custom icons */
|
|
16
|
+
icons?: BreadCrumbsIcons
|
|
12
17
|
/** Callback when a breadcrumb is clicked */
|
|
13
18
|
onclick?: (value: unknown, item: unknown) => void
|
|
14
19
|
/** Custom snippet for rendering each crumb */
|
|
15
|
-
crumb?: Snippet<[
|
|
20
|
+
crumb?: Snippet<[ProxyItem, boolean]>
|
|
16
21
|
/** Additional CSS class */
|
|
17
22
|
class?: string
|
|
18
23
|
}
|
|
@@ -20,29 +25,32 @@
|
|
|
20
25
|
const {
|
|
21
26
|
items = [],
|
|
22
27
|
fields,
|
|
23
|
-
|
|
28
|
+
label = messages.current.breadcrumbs.label,
|
|
29
|
+
icons: userIcons = {} as BreadCrumbsIcons,
|
|
24
30
|
onclick,
|
|
25
31
|
crumb,
|
|
26
32
|
class: className = ''
|
|
27
|
-
}: BreadCrumbsProps = $props()
|
|
33
|
+
}: BreadCrumbsProps & { label?: string } = $props()
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
const icons = $derived({ separator: DEFAULT_STATE_ICONS.navigate.right, ...userIcons })
|
|
36
|
+
|
|
37
|
+
function createProxy(item: unknown): ProxyItem {
|
|
38
|
+
return new ProxyItem(item, fields)
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
function handleClick(proxy:
|
|
34
|
-
onclick?.(proxy.
|
|
41
|
+
function handleClick(proxy: ProxyItem) {
|
|
42
|
+
onclick?.(proxy.value, proxy.original)
|
|
35
43
|
}
|
|
36
44
|
</script>
|
|
37
45
|
|
|
38
|
-
{#snippet defaultCrumb(proxy:
|
|
39
|
-
{#if proxy.icon}
|
|
40
|
-
<span data-breadcrumb-icon class={proxy.icon} aria-hidden="true"></span>
|
|
46
|
+
{#snippet defaultCrumb(proxy: ProxyItem, _isLast: boolean)}
|
|
47
|
+
{#if proxy.get('icon')}
|
|
48
|
+
<span data-breadcrumb-icon class={proxy.get('icon')} aria-hidden="true"></span>
|
|
41
49
|
{/if}
|
|
42
|
-
<span data-breadcrumb-label>{proxy.
|
|
50
|
+
<span data-breadcrumb-label>{proxy.label}</span>
|
|
43
51
|
{/snippet}
|
|
44
52
|
|
|
45
|
-
<nav data-breadcrumbs class={className || undefined} aria-label=
|
|
53
|
+
<nav data-breadcrumbs class={className || undefined} aria-label={label}>
|
|
46
54
|
<ol data-breadcrumb-list>
|
|
47
55
|
{#each items as item, index (index)}
|
|
48
56
|
{@const proxy = createProxy(item)}
|
|
@@ -50,7 +58,7 @@
|
|
|
50
58
|
|
|
51
59
|
{#if index > 0}
|
|
52
60
|
<li data-breadcrumb-separator aria-hidden="true">
|
|
53
|
-
<span class={separator}></span>
|
|
61
|
+
<span class={icons.separator}></span>
|
|
54
62
|
</li>
|
|
55
63
|
{/if}
|
|
56
64
|
|
|
@@ -63,6 +71,18 @@
|
|
|
63
71
|
{@render defaultCrumb(proxy, isLast)}
|
|
64
72
|
{/if}
|
|
65
73
|
</span>
|
|
74
|
+
{:else if proxy.get('href')}
|
|
75
|
+
<a
|
|
76
|
+
href={proxy.get('href')}
|
|
77
|
+
data-breadcrumb-link
|
|
78
|
+
onclick={() => handleClick(proxy)}
|
|
79
|
+
>
|
|
80
|
+
{#if crumb}
|
|
81
|
+
{@render crumb(proxy, isLast)}
|
|
82
|
+
{:else}
|
|
83
|
+
{@render defaultCrumb(proxy, isLast)}
|
|
84
|
+
{/if}
|
|
85
|
+
</a>
|
|
66
86
|
{:else}
|
|
67
87
|
<button
|
|
68
88
|
type="button"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ButtonProps } from '../types/button.js'
|
|
3
|
-
import {
|
|
3
|
+
import { ProxyItem } from '@rokkit/states'
|
|
4
4
|
import ItemContent from './ItemContent.svelte'
|
|
5
5
|
|
|
6
6
|
let {
|
|
@@ -12,23 +12,25 @@
|
|
|
12
12
|
icon,
|
|
13
13
|
iconRight,
|
|
14
14
|
href,
|
|
15
|
-
target,
|
|
15
|
+
target = '_self',
|
|
16
|
+
title,
|
|
16
17
|
disabled = false,
|
|
17
18
|
loading = false,
|
|
18
19
|
onclick,
|
|
19
20
|
class: className = '',
|
|
20
|
-
children
|
|
21
|
+
children,
|
|
22
|
+
...rest
|
|
21
23
|
}: ButtonProps = $props()
|
|
22
24
|
|
|
23
25
|
const isIconOnly = $derived(Boolean(icon) && !label && !children)
|
|
24
26
|
const isDisabled = $derived(disabled || loading)
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
|
-
* Create
|
|
29
|
+
* Create a ProxyItem for default content rendering.
|
|
28
30
|
* Constructs a minimal item from button props.
|
|
29
31
|
*/
|
|
30
32
|
const proxy = $derived(
|
|
31
|
-
new
|
|
33
|
+
new ProxyItem({ text: label, icon, iconRight })
|
|
32
34
|
)
|
|
33
35
|
</script>
|
|
34
36
|
|
|
@@ -46,6 +48,7 @@
|
|
|
46
48
|
<a
|
|
47
49
|
{href}
|
|
48
50
|
{target}
|
|
51
|
+
{title}
|
|
49
52
|
data-button
|
|
50
53
|
data-variant={variant}
|
|
51
54
|
data-style={style}
|
|
@@ -55,6 +58,7 @@
|
|
|
55
58
|
class={className || undefined}
|
|
56
59
|
aria-label={label}
|
|
57
60
|
aria-busy={loading || undefined}
|
|
61
|
+
{...rest}
|
|
58
62
|
>
|
|
59
63
|
{#if children}
|
|
60
64
|
{@render children()}
|
|
@@ -65,6 +69,7 @@
|
|
|
65
69
|
{:else}
|
|
66
70
|
<button
|
|
67
71
|
{type}
|
|
72
|
+
{title}
|
|
68
73
|
data-button
|
|
69
74
|
data-variant={variant}
|
|
70
75
|
data-style={style}
|
|
@@ -77,6 +82,7 @@
|
|
|
77
82
|
aria-label={label}
|
|
78
83
|
aria-busy={loading || undefined}
|
|
79
84
|
{onclick}
|
|
85
|
+
{...rest}
|
|
80
86
|
>
|
|
81
87
|
{#if children}
|
|
82
88
|
{@render children()}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
3
|
import { swipeable, keyboard } from '@rokkit/actions'
|
|
4
|
+
import { messages } from '@rokkit/states'
|
|
4
5
|
|
|
5
6
|
interface CarouselProps {
|
|
6
7
|
/** Number of slides (required when using children snippet) */
|
|
@@ -36,10 +37,13 @@
|
|
|
36
37
|
showDots = true,
|
|
37
38
|
showArrows = true,
|
|
38
39
|
transition = 'slide',
|
|
40
|
+
labels: userLabels = {},
|
|
39
41
|
class: className = '',
|
|
40
42
|
slide,
|
|
41
43
|
children
|
|
42
|
-
}: CarouselProps = $props()
|
|
44
|
+
}: CarouselProps & { labels?: Record<string, string> } = $props()
|
|
45
|
+
|
|
46
|
+
const labels = $derived({ ...messages.current.carousel, ...userLabels })
|
|
43
47
|
|
|
44
48
|
let hovered = $state(false)
|
|
45
49
|
|
|
@@ -85,13 +89,14 @@
|
|
|
85
89
|
</script>
|
|
86
90
|
|
|
87
91
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
92
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
88
93
|
<div
|
|
89
94
|
data-carousel
|
|
90
95
|
data-carousel-transition={transition}
|
|
91
96
|
class={className || undefined}
|
|
92
|
-
role="
|
|
97
|
+
role="application"
|
|
93
98
|
aria-roledescription="carousel"
|
|
94
|
-
aria-label=
|
|
99
|
+
aria-label={labels.label}
|
|
95
100
|
tabindex="0"
|
|
96
101
|
use:swipeable={{ horizontal: true, vertical: false }}
|
|
97
102
|
use:keyboard={keyMap}
|
|
@@ -133,7 +138,7 @@
|
|
|
133
138
|
<button
|
|
134
139
|
data-carousel-prev
|
|
135
140
|
type="button"
|
|
136
|
-
aria-label=
|
|
141
|
+
aria-label={labels.prev}
|
|
137
142
|
onclick={prev}
|
|
138
143
|
disabled={!loop && current === 0}
|
|
139
144
|
>
|
|
@@ -143,7 +148,7 @@
|
|
|
143
148
|
<button
|
|
144
149
|
data-carousel-next
|
|
145
150
|
type="button"
|
|
146
|
-
aria-label=
|
|
151
|
+
aria-label={labels.next}
|
|
147
152
|
onclick={next}
|
|
148
153
|
disabled={!loop && current === count - 1}
|
|
149
154
|
>
|
|
@@ -152,7 +157,7 @@
|
|
|
152
157
|
{/if}
|
|
153
158
|
|
|
154
159
|
{#if showDots && count > 1}
|
|
155
|
-
<div data-carousel-dots role="tablist" aria-label=
|
|
160
|
+
<div data-carousel-dots role="tablist" aria-label={labels.slides}>
|
|
156
161
|
{#each Array(count) as _, index (index)}
|
|
157
162
|
<button
|
|
158
163
|
data-carousel-dot
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { CodeProps, CodeStateIcons } from '../types/index.js'
|
|
3
3
|
import { defaultCodeStateIcons } from '../types/code.js'
|
|
4
4
|
import { highlightCode } from '../utils/shiki.js'
|
|
5
|
+
import { messages } from '@rokkit/states'
|
|
5
6
|
|
|
6
7
|
const {
|
|
7
8
|
code,
|
|
@@ -9,9 +10,12 @@
|
|
|
9
10
|
theme = 'dark',
|
|
10
11
|
showLineNumbers = false,
|
|
11
12
|
showCopyButton = true,
|
|
13
|
+
labels: userLabels = {},
|
|
12
14
|
icons: userIcons,
|
|
13
15
|
class: className = ''
|
|
14
|
-
}: CodeProps = $props()
|
|
16
|
+
}: CodeProps & { labels?: Record<string, string> } = $props()
|
|
17
|
+
|
|
18
|
+
const labels = $derived({ ...messages.current.code, ...userLabels })
|
|
15
19
|
|
|
16
20
|
// Merge icons with defaults
|
|
17
21
|
const icons = $derived<CodeStateIcons>({ ...defaultCodeStateIcons, ...userIcons })
|
|
@@ -55,7 +59,7 @@
|
|
|
55
59
|
type="button"
|
|
56
60
|
class="copy-button"
|
|
57
61
|
onclick={copyToClipboard}
|
|
58
|
-
aria-label={copied ?
|
|
62
|
+
aria-label={copied ? labels.copied : labels.copy}
|
|
59
63
|
>
|
|
60
64
|
{#if copied}
|
|
61
65
|
<span class="copy-icon {icons.copysuccess}"></span>
|
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
FloatingActionProps,
|
|
4
4
|
FloatingActionItem,
|
|
5
5
|
FloatingActionItemSnippet,
|
|
6
|
-
FloatingActionItemHandlers
|
|
6
|
+
FloatingActionItemHandlers,
|
|
7
|
+
FloatingActionIcons
|
|
7
8
|
} from '../types/floating-action.js'
|
|
8
9
|
import { getSnippet } from '../types/floating-action.js'
|
|
9
|
-
import {
|
|
10
|
+
import { ProxyItem } from '@rokkit/states'
|
|
11
|
+
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
10
12
|
|
|
11
13
|
let {
|
|
12
14
|
items = [],
|
|
13
15
|
fields: userFields,
|
|
14
|
-
|
|
15
|
-
closeIcon = 'i-lucide:x',
|
|
16
|
+
icons: userIcons = {} as FloatingActionIcons,
|
|
16
17
|
label = 'Actions',
|
|
17
18
|
size = 'md',
|
|
18
19
|
position = 'bottom-right',
|
|
@@ -30,11 +31,13 @@
|
|
|
30
31
|
...snippets
|
|
31
32
|
}: FloatingActionProps & { [key: string]: FloatingActionItemSnippet | unknown } = $props()
|
|
32
33
|
|
|
34
|
+
const icons = $derived({ add: DEFAULT_STATE_ICONS.action.add, close: DEFAULT_STATE_ICONS.action.close, ...userIcons })
|
|
35
|
+
|
|
33
36
|
/**
|
|
34
|
-
* Create
|
|
37
|
+
* Create a ProxyItem for the given item
|
|
35
38
|
*/
|
|
36
|
-
function createProxy(item: FloatingActionItem):
|
|
37
|
-
return new
|
|
39
|
+
function createProxy(item: FloatingActionItem): ProxyItem {
|
|
40
|
+
return new ProxyItem(item, userFields)
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
let fabRef = $state<HTMLDivElement | null>(null)
|
|
@@ -82,9 +85,9 @@
|
|
|
82
85
|
/**
|
|
83
86
|
* Handle item selection
|
|
84
87
|
*/
|
|
85
|
-
function handleItemClick(item: { proxy:
|
|
88
|
+
function handleItemClick(item: { proxy: ProxyItem; original: FloatingActionItem }) {
|
|
86
89
|
if (item.proxy.disabled) return
|
|
87
|
-
onselect?.(item.proxy.
|
|
90
|
+
onselect?.(item.proxy.value, item.original)
|
|
88
91
|
close()
|
|
89
92
|
// Return focus to trigger
|
|
90
93
|
const trigger = fabRef?.querySelector('[data-fab-trigger]') as HTMLElement | undefined
|
|
@@ -162,7 +165,7 @@
|
|
|
162
165
|
*/
|
|
163
166
|
function handleItemKeyDown(
|
|
164
167
|
event: KeyboardEvent,
|
|
165
|
-
item: { proxy:
|
|
168
|
+
item: { proxy: ProxyItem; original: FloatingActionItem }
|
|
166
169
|
) {
|
|
167
170
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
168
171
|
event.preventDefault()
|
|
@@ -190,7 +193,7 @@
|
|
|
190
193
|
* Create handlers object for custom snippets
|
|
191
194
|
*/
|
|
192
195
|
function createHandlers(item: {
|
|
193
|
-
proxy:
|
|
196
|
+
proxy: ProxyItem
|
|
194
197
|
original: FloatingActionItem
|
|
195
198
|
}): FloatingActionItemHandlers {
|
|
196
199
|
return {
|
|
@@ -202,8 +205,8 @@
|
|
|
202
205
|
/**
|
|
203
206
|
* Resolve which snippet to use for an item
|
|
204
207
|
*/
|
|
205
|
-
function resolveItemSnippet(proxy:
|
|
206
|
-
const snippetName = proxy.
|
|
208
|
+
function resolveItemSnippet(proxy: ProxyItem): FloatingActionItemSnippet | null {
|
|
209
|
+
const snippetName = proxy.get('snippet')
|
|
207
210
|
if (snippetName) {
|
|
208
211
|
const namedSnippet = getSnippet(snippets, snippetName)
|
|
209
212
|
if (namedSnippet) {
|
|
@@ -240,7 +243,7 @@
|
|
|
240
243
|
</script>
|
|
241
244
|
|
|
242
245
|
{#snippet defaultItem(
|
|
243
|
-
proxy:
|
|
246
|
+
proxy: ProxyItem,
|
|
244
247
|
handlers: FloatingActionItemHandlers,
|
|
245
248
|
index: number,
|
|
246
249
|
total: number
|
|
@@ -251,22 +254,22 @@
|
|
|
251
254
|
data-fab-index={index}
|
|
252
255
|
data-disabled={proxy.disabled || undefined}
|
|
253
256
|
disabled={proxy.disabled || disabled}
|
|
254
|
-
aria-label={proxy.label
|
|
257
|
+
aria-label={proxy.label}
|
|
255
258
|
style="--fab-index: {index}; --fab-total: {total}; --fab-delay: {getItemDelay(index)}"
|
|
256
259
|
onclick={handlers.onclick}
|
|
257
260
|
onkeydown={handlers.onkeydown}
|
|
258
261
|
>
|
|
259
|
-
{#if proxy.icon}
|
|
260
|
-
<span data-fab-item-icon class={proxy.icon} aria-hidden="true"></span>
|
|
262
|
+
{#if proxy.get('icon')}
|
|
263
|
+
<span data-fab-item-icon class={proxy.get('icon')} aria-hidden="true"></span>
|
|
261
264
|
{/if}
|
|
262
|
-
{#if proxy.
|
|
263
|
-
<span data-fab-item-label>{proxy.
|
|
265
|
+
{#if proxy.label}
|
|
266
|
+
<span data-fab-item-label>{proxy.label}</span>
|
|
264
267
|
{/if}
|
|
265
268
|
</button>
|
|
266
269
|
{/snippet}
|
|
267
270
|
|
|
268
271
|
{#snippet renderItem(
|
|
269
|
-
item: { proxy:
|
|
272
|
+
item: { proxy: ProxyItem; original: FloatingActionItem },
|
|
270
273
|
index: number,
|
|
271
274
|
total: number
|
|
272
275
|
)}
|
|
@@ -326,6 +329,6 @@
|
|
|
326
329
|
onclick={toggle}
|
|
327
330
|
onkeydown={handleTriggerKeyDown}
|
|
328
331
|
>
|
|
329
|
-
<span data-fab-icon class={open ?
|
|
332
|
+
<span data-fab-icon class={open ? icons.close : icons.add} aria-hidden="true"></span>
|
|
330
333
|
</button>
|
|
331
334
|
</div>
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { FloatingNavigationProps } from '../types/floating-navigation.js'
|
|
3
|
-
import {
|
|
2
|
+
import type { FloatingNavigationProps, FloatingNavigationIcons } from '../types/floating-navigation.js'
|
|
3
|
+
import { ProxyItem, messages } from '@rokkit/states'
|
|
4
|
+
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
items = [],
|
|
7
8
|
fields: userFields,
|
|
9
|
+
icons: userIcons = {} as FloatingNavigationIcons,
|
|
8
10
|
value = $bindable(),
|
|
9
11
|
position = 'right',
|
|
10
12
|
pinned = $bindable(false),
|
|
11
13
|
observe = true,
|
|
12
14
|
observerOptions = { rootMargin: '-20% 0px -70% 0px', threshold: 0 },
|
|
13
15
|
size = 'md',
|
|
14
|
-
label =
|
|
16
|
+
label = messages.current.floatingNav.label,
|
|
17
|
+
labels: userLabels = {},
|
|
15
18
|
onselect,
|
|
16
19
|
onpinchange,
|
|
17
20
|
item: itemSnippet,
|
|
18
21
|
class: className = ''
|
|
19
|
-
}: FloatingNavigationProps = $props()
|
|
22
|
+
}: FloatingNavigationProps & { labels?: Record<string, string> } = $props()
|
|
23
|
+
|
|
24
|
+
const labels = $derived({ ...messages.current.floatingNav, ...userLabels })
|
|
25
|
+
|
|
26
|
+
const icons = $derived({ pin: DEFAULT_STATE_ICONS.action.pin, unpin: DEFAULT_STATE_ICONS.action.unpin, ...userIcons })
|
|
20
27
|
|
|
21
28
|
let navRef = $state<HTMLElement | null>(null)
|
|
22
29
|
let expanded = $state(false)
|
|
@@ -26,13 +33,13 @@
|
|
|
26
33
|
|
|
27
34
|
const itemProxies = $derived(
|
|
28
35
|
items.map((item) => ({
|
|
29
|
-
proxy: new
|
|
36
|
+
proxy: new ProxyItem(item, userFields),
|
|
30
37
|
original: item
|
|
31
38
|
}))
|
|
32
39
|
)
|
|
33
40
|
|
|
34
41
|
const activeIndex = $derived(
|
|
35
|
-
itemProxies.findIndex((item) => item.proxy.
|
|
42
|
+
itemProxies.findIndex((item) => item.proxy.value === value)
|
|
36
43
|
)
|
|
37
44
|
|
|
38
45
|
function togglePin() {
|
|
@@ -49,13 +56,13 @@
|
|
|
49
56
|
if (!pinned) expanded = false
|
|
50
57
|
}
|
|
51
58
|
|
|
52
|
-
function handleItemClick(item: { proxy:
|
|
53
|
-
value = item.proxy.
|
|
54
|
-
onselect?.(item.proxy.
|
|
59
|
+
function handleItemClick(item: { proxy: ProxyItem; original: Record<string, unknown> }) {
|
|
60
|
+
value = item.proxy.value
|
|
61
|
+
onselect?.(item.proxy.value, item.original)
|
|
55
62
|
|
|
56
63
|
// Smooth scroll to target section
|
|
57
|
-
const href = item.proxy.
|
|
58
|
-
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.
|
|
64
|
+
const href = item.proxy.get('href') !== undefined ? String(item.original[userFields?.href ?? 'href'] ?? '') : ''
|
|
65
|
+
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
59
66
|
const el = document.getElementById(targetId)
|
|
60
67
|
el?.scrollIntoView({ behavior: 'smooth' })
|
|
61
68
|
}
|
|
@@ -116,24 +123,24 @@
|
|
|
116
123
|
for (const entry of entries) {
|
|
117
124
|
if (entry.isIntersecting) {
|
|
118
125
|
const match = itemProxies.find((item) => {
|
|
119
|
-
const href = item.proxy.
|
|
126
|
+
const href = item.proxy.get('href') !== undefined
|
|
120
127
|
? String(item.original[userFields?.href ?? 'href'] ?? '')
|
|
121
128
|
: ''
|
|
122
|
-
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.
|
|
129
|
+
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
123
130
|
return targetId === entry.target.id
|
|
124
131
|
})
|
|
125
132
|
if (match) {
|
|
126
|
-
value = match.proxy.
|
|
133
|
+
value = match.proxy.value
|
|
127
134
|
}
|
|
128
135
|
}
|
|
129
136
|
}
|
|
130
137
|
}, observerOptions)
|
|
131
138
|
|
|
132
139
|
for (const item of itemProxies) {
|
|
133
|
-
const href = item.proxy.
|
|
140
|
+
const href = item.proxy.get('href') !== undefined
|
|
134
141
|
? String(item.original[userFields?.href ?? 'href'] ?? '')
|
|
135
142
|
: ''
|
|
136
|
-
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.
|
|
143
|
+
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
137
144
|
const el = document.getElementById(targetId)
|
|
138
145
|
if (el) observer.observe(el)
|
|
139
146
|
}
|
|
@@ -164,21 +171,21 @@
|
|
|
164
171
|
type="button"
|
|
165
172
|
data-floating-nav-pin
|
|
166
173
|
aria-pressed={pinned}
|
|
167
|
-
aria-label={pinned ?
|
|
174
|
+
aria-label={pinned ? labels.unpin : labels.pin}
|
|
168
175
|
onclick={togglePin}
|
|
169
176
|
>
|
|
170
|
-
<span class={pinned ?
|
|
177
|
+
<span data-floating-nav-pin-icon class={pinned ? icons.unpin : icons.pin} aria-hidden="true"></span>
|
|
171
178
|
</button>
|
|
172
179
|
</div>
|
|
173
180
|
|
|
174
181
|
<div data-floating-nav-items>
|
|
175
|
-
{#each itemProxies as item, index (item.proxy.
|
|
176
|
-
{@const isActive = item.proxy.
|
|
177
|
-
{@const isLink = item.proxy.
|
|
182
|
+
{#each itemProxies as item, index (item.proxy.value ?? index)}
|
|
183
|
+
{@const isActive = item.proxy.value === value}
|
|
184
|
+
{@const isLink = item.proxy.get('href') !== undefined}
|
|
178
185
|
{#if itemSnippet}
|
|
179
186
|
{@render itemSnippet(item.original, {
|
|
180
|
-
text: item.proxy.
|
|
181
|
-
icon: item.proxy.icon,
|
|
187
|
+
text: item.proxy.label,
|
|
188
|
+
icon: item.proxy.get('icon'),
|
|
182
189
|
active: isActive
|
|
183
190
|
})}
|
|
184
191
|
{:else if isLink}
|
|
@@ -194,10 +201,10 @@
|
|
|
194
201
|
handleItemClick(item)
|
|
195
202
|
}}
|
|
196
203
|
>
|
|
197
|
-
{#if item.proxy.icon}
|
|
198
|
-
<span data-floating-nav-icon class={item.proxy.icon} aria-hidden="true"></span>
|
|
204
|
+
{#if item.proxy.get('icon')}
|
|
205
|
+
<span data-floating-nav-icon class={item.proxy.get('icon')} aria-hidden="true"></span>
|
|
199
206
|
{/if}
|
|
200
|
-
<span data-floating-nav-label>{item.proxy.
|
|
207
|
+
<span data-floating-nav-label>{item.proxy.label}</span>
|
|
201
208
|
</a>
|
|
202
209
|
{:else}
|
|
203
210
|
<button
|
|
@@ -209,10 +216,10 @@
|
|
|
209
216
|
style="--fn-index: {index}; --fn-total: {itemProxies.length}"
|
|
210
217
|
onclick={() => handleItemClick(item)}
|
|
211
218
|
>
|
|
212
|
-
{#if item.proxy.icon}
|
|
213
|
-
<span data-floating-nav-icon class={item.proxy.icon} aria-hidden="true"></span>
|
|
219
|
+
{#if item.proxy.get('icon')}
|
|
220
|
+
<span data-floating-nav-icon class={item.proxy.get('icon')} aria-hidden="true"></span>
|
|
214
221
|
{/if}
|
|
215
|
-
<span data-floating-nav-label>{item.proxy.
|
|
222
|
+
<span data-floating-nav-label>{item.proxy.label}</span>
|
|
216
223
|
</button>
|
|
217
224
|
{/if}
|
|
218
225
|
{/each}
|