@rokkit/ui 1.0.0-next.133 → 1.0.0-next.134
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/README.md +106 -166
- package/package.json +7 -5
- package/src/components/BreadCrumbs.svelte +2 -10
- package/src/components/Button.svelte +1 -3
- package/src/components/ButtonGroup.svelte +2 -11
- package/src/components/Card.svelte +1 -8
- package/src/components/Carousel.svelte +1 -5
- package/src/components/FloatingAction.svelte +5 -1
- package/src/components/FloatingNavigation.svelte +25 -17
- package/src/components/LazyTree.svelte +69 -62
- package/src/components/List.svelte +10 -7
- package/src/components/Menu.svelte +9 -8
- package/src/components/MultiSelect.svelte +14 -3
- package/src/components/PaletteManager.svelte +2 -6
- package/src/components/ProgressBar.svelte +7 -7
- package/src/components/Range.svelte +20 -18
- package/src/components/Rating.svelte +6 -2
- package/src/components/SearchFilter.svelte +2 -2
- package/src/components/Select.svelte +19 -10
- package/src/components/Stepper.svelte +4 -2
- package/src/components/Table.svelte +17 -24
- package/src/components/Tabs.svelte +21 -9
- package/src/components/Tree.svelte +53 -53
- package/src/components/UploadFileStatus.svelte +25 -6
- package/src/components/UploadProgress.svelte +2 -6
- package/src/components/UploadTarget.svelte +1 -1
- package/src/utils/palette.ts +37 -5
- package/src/utils/upload.js +4 -1
package/README.md
CHANGED
|
@@ -1,221 +1,161 @@
|
|
|
1
1
|
# @rokkit/ui
|
|
2
2
|
|
|
3
|
-
Data
|
|
3
|
+
Data-driven UI components for Svelte 5 applications.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
+
npm install @rokkit/ui
|
|
9
|
+
# or
|
|
8
10
|
bun add @rokkit/ui
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Requires `svelte ^5.0.0` as a peer dependency.
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
`@rokkit/ui` provides 38 components covering forms, dropdowns, lists, navigation, data display, layout, and file upload. Components follow a data-first model: they adapt to your data structures via field mapping rather than requiring data to be reshaped. All components are unstyled by default — they expose `data-*` attribute hooks for theming via `@rokkit/themes`. Keyboard navigation and ARIA accessibility are built in.
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
## Usage
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
- Custom field mapping for any data structure
|
|
19
|
-
- Icons, descriptions, and disabled states
|
|
20
|
-
- Size variants (sm, md, lg)
|
|
21
|
-
- Keyboard navigation
|
|
22
|
-
- Full accessibility (ARIA)
|
|
21
|
+
### List
|
|
23
22
|
|
|
24
23
|
```svelte
|
|
25
24
|
<script>
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
const options = [
|
|
29
|
-
{ text: 'Copy', icon: 'i-solar:copy-bold', value: 'copy' },
|
|
30
|
-
{ text: 'Paste', icon: 'i-solar:clipboard-bold', value: 'paste' },
|
|
31
|
-
{ text: 'Delete', icon: 'i-solar:trash-bold', value: 'delete', disabled: true }
|
|
32
|
-
]
|
|
25
|
+
import { List } from '@rokkit/ui'
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
27
|
+
const items = ['Apple', 'Banana', 'Cherry']
|
|
28
|
+
let selected = $state(null)
|
|
37
29
|
</script>
|
|
38
30
|
|
|
39
|
-
<
|
|
31
|
+
<List {items} bind:value={selected} onselect={(val) => console.log(val)} />
|
|
40
32
|
```
|
|
41
33
|
|
|
42
|
-
|
|
34
|
+
### Select with field mapping
|
|
35
|
+
|
|
36
|
+
When your data keys differ from the expected defaults, use the `fields` prop:
|
|
43
37
|
|
|
44
38
|
```svelte
|
|
45
39
|
<script>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{ text: 'Export as SVG', value: 'svg' }
|
|
52
|
-
]
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
text: 'Data',
|
|
56
|
-
children: [
|
|
57
|
-
{ text: 'Export as CSV', value: 'csv' },
|
|
58
|
-
{ text: 'Export as JSON', value: 'json' }
|
|
59
|
-
]
|
|
60
|
-
}
|
|
40
|
+
import { Select } from '@rokkit/ui'
|
|
41
|
+
|
|
42
|
+
const countries = [
|
|
43
|
+
{ name: 'United States', code: 'us' },
|
|
44
|
+
{ name: 'Germany', code: 'de' }
|
|
61
45
|
]
|
|
46
|
+
let chosen = $state(null)
|
|
62
47
|
</script>
|
|
63
48
|
|
|
64
|
-
<
|
|
49
|
+
<Select options={countries} fields={{ label: 'name', value: 'code' }} bind:value={chosen} />
|
|
65
50
|
```
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Use the `fields` prop to map your data structure to Menu's expected fields:
|
|
52
|
+
### Tabs with bind:value
|
|
70
53
|
|
|
71
54
|
```svelte
|
|
72
55
|
<script>
|
|
73
|
-
|
|
74
|
-
{ name: 'Option A', id: 'a' },
|
|
75
|
-
{ name: 'Option B', id: 'b' }
|
|
76
|
-
]
|
|
56
|
+
import { Tabs } from '@rokkit/ui'
|
|
77
57
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
value: '
|
|
81
|
-
|
|
58
|
+
const tabs = [
|
|
59
|
+
{ label: 'Overview', value: 'overview' },
|
|
60
|
+
{ label: 'Settings', value: 'settings' }
|
|
61
|
+
]
|
|
62
|
+
let activeTab = $state('overview')
|
|
82
63
|
</script>
|
|
83
64
|
|
|
84
|
-
<
|
|
65
|
+
<Tabs items={tabs} bind:value={activeTab}>
|
|
66
|
+
{#snippet tabPanel(tab)}
|
|
67
|
+
<div>Content for {tab.label}</div>
|
|
68
|
+
{/snippet}
|
|
69
|
+
</Tabs>
|
|
85
70
|
```
|
|
86
71
|
|
|
87
|
-
###
|
|
72
|
+
### Button variants
|
|
73
|
+
|
|
74
|
+
```svelte
|
|
75
|
+
<script>
|
|
76
|
+
import { Button } from '@rokkit/ui'
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<Button variant="primary" onclick={() => save()}>Save</Button>
|
|
80
|
+
<Button variant="default" onclick={() => cancel()}>Cancel</Button>
|
|
81
|
+
<Button href="/docs">Documentation</Button>
|
|
82
|
+
```
|
|
88
83
|
|
|
89
|
-
|
|
84
|
+
### Menu
|
|
90
85
|
|
|
91
86
|
```svelte
|
|
92
87
|
<script>
|
|
93
88
|
import { Menu } from '@rokkit/ui'
|
|
94
89
|
|
|
95
|
-
const
|
|
96
|
-
{ text: '
|
|
97
|
-
{ text: '
|
|
98
|
-
{ text: '
|
|
90
|
+
const items = [
|
|
91
|
+
{ text: 'Copy', value: 'copy' },
|
|
92
|
+
{ text: 'Paste', value: 'paste' },
|
|
93
|
+
{ text: 'Delete', value: 'delete', disabled: true }
|
|
99
94
|
]
|
|
100
95
|
</script>
|
|
101
96
|
|
|
102
|
-
<Menu {
|
|
103
|
-
|
|
104
|
-
<button onclick={handlers.onclick} onkeydown={handlers.onkeydown}>
|
|
105
|
-
🎯 {menuItem.text}
|
|
106
|
-
</button>
|
|
107
|
-
{/snippet}
|
|
97
|
+
<Menu options={items} label="Actions" onselect={(value) => handleAction(value)} />
|
|
98
|
+
```
|
|
108
99
|
|
|
109
|
-
|
|
110
|
-
<div class="custom-header">
|
|
111
|
-
📁 {group.text}
|
|
112
|
-
</div>
|
|
113
|
-
{/snippet}
|
|
100
|
+
## Components
|
|
114
101
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
102
|
+
| Category | Components |
|
|
103
|
+
| ----------------- | -------------------------------------------------------------- |
|
|
104
|
+
| Form / Input | Button, ButtonGroup, Toggle, Switch, Range, SearchFilter, Tabs |
|
|
105
|
+
| Dropdown | Menu, Select, MultiSelect, Toolbar, ToolbarGroup |
|
|
106
|
+
| List / Navigation | List, Tree, LazyTree, BreadCrumbs |
|
|
107
|
+
| Layout | Card, Grid, Carousel, ProgressBar, Timeline |
|
|
108
|
+
| Data Display | Table, Rating, Pill, Connector, Stepper |
|
|
109
|
+
| Visual | Reveal, Tilt, Shine, Code, ItemContent |
|
|
110
|
+
| Advanced | PaletteManager, FloatingAction, FloatingNavigation |
|
|
111
|
+
| Upload | UploadTarget, UploadFileStatus, UploadProgress |
|
|
120
112
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
113
|
+
## API
|
|
114
|
+
|
|
115
|
+
### Standard props
|
|
116
|
+
|
|
117
|
+
Most components share a consistent interface:
|
|
118
|
+
|
|
119
|
+
| Prop | Description |
|
|
120
|
+
| ----------------------- | ----------------------------------------------------------------------- |
|
|
121
|
+
| `items` / `options` | Array of data items |
|
|
122
|
+
| `value` | Bindable selected value |
|
|
123
|
+
| `fields` | Field mapping — maps component-expected keys to your data's actual keys |
|
|
124
|
+
| `onchange` / `onselect` | Selection callback |
|
|
125
|
+
|
|
126
|
+
### Field mapping
|
|
127
|
+
|
|
128
|
+
The `fields` prop lets any data structure work with any component without reshaping:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
// Your data has 'name' and 'id' — map them to what the component expects
|
|
132
|
+
const fields = { label: 'name', value: 'id' }
|
|
127
133
|
```
|
|
128
134
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
- `onclick: () => void` - Call to trigger item selection
|
|
140
|
-
- `onkeydown: (event) => void` - Forward keyboard events for accessibility
|
|
141
|
-
|
|
142
|
-
### Props
|
|
143
|
-
|
|
144
|
-
| Prop | Type | Default | Description |
|
|
145
|
-
| ------------ | ----------------------- | -------- | ---------------------------------- |
|
|
146
|
-
| `options` | `MenuItem[]` | `[]` | Array of menu items or groups |
|
|
147
|
-
| `fields` | `MenuFields` | `{}` | Field mapping configuration |
|
|
148
|
-
| `label` | `string` | `'Menu'` | Button label text |
|
|
149
|
-
| `icon` | `string` | - | Button icon class |
|
|
150
|
-
| `showArrow` | `boolean` | `true` | Show dropdown arrow indicator |
|
|
151
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size variant |
|
|
152
|
-
| `align` | `'left' \| 'right'` | `'left'` | Dropdown alignment |
|
|
153
|
-
| `disabled` | `boolean` | `false` | Disable the menu |
|
|
154
|
-
| `onselect` | `(value, item) => void` | - | Selection callback |
|
|
155
|
-
| `item` | `Snippet` | - | Custom snippet for rendering items |
|
|
156
|
-
| `groupLabel` | `Snippet` | - | Custom snippet for group headers |
|
|
157
|
-
| `class` | `string` | `''` | Additional CSS classes |
|
|
158
|
-
|
|
159
|
-
### CSS Custom Properties
|
|
160
|
-
|
|
161
|
-
```css
|
|
162
|
-
/* Trigger button */
|
|
163
|
-
--menu-trigger-bg
|
|
164
|
-
--menu-trigger-bg-hover
|
|
165
|
-
--menu-trigger-bg-active
|
|
166
|
-
--menu-trigger-border
|
|
167
|
-
--menu-trigger-border-hover
|
|
168
|
-
--menu-trigger-border-active
|
|
169
|
-
--menu-trigger-text
|
|
170
|
-
--menu-trigger-text-hover
|
|
171
|
-
--menu-focus-ring
|
|
172
|
-
|
|
173
|
-
/* Dropdown */
|
|
174
|
-
--menu-dropdown-bg
|
|
175
|
-
--menu-dropdown-border
|
|
176
|
-
--menu-dropdown-shadow
|
|
177
|
-
|
|
178
|
-
/* Items */
|
|
179
|
-
--menu-item-text
|
|
180
|
-
--menu-item-text-hover
|
|
181
|
-
--menu-item-bg-hover
|
|
182
|
-
--menu-item-bg-focus
|
|
183
|
-
--menu-item-icon
|
|
184
|
-
--menu-item-icon-hover
|
|
185
|
-
--menu-item-description
|
|
186
|
-
|
|
187
|
-
/* Groups */
|
|
188
|
-
--menu-group-label-color
|
|
189
|
-
--menu-divider-color
|
|
135
|
+
### Snippet customization
|
|
136
|
+
|
|
137
|
+
Components accept Svelte 5 snippets for rendering overrides. This lets you customize presentation without forking the component:
|
|
138
|
+
|
|
139
|
+
```svelte
|
|
140
|
+
<List {items}>
|
|
141
|
+
{#snippet itemContent(item)}
|
|
142
|
+
<span class="tag">{item.label}</span>
|
|
143
|
+
{/snippet}
|
|
144
|
+
</List>
|
|
190
145
|
```
|
|
191
146
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
| `icon` | `'icon'` | Icon class field |
|
|
199
|
-
| `description` | `'description'` | Secondary text field |
|
|
200
|
-
| `disabled` | `'disabled'` | Disabled state field |
|
|
201
|
-
| `children` | `'children'` | Children array for groups |
|
|
202
|
-
| `snippet` | `'snippet'` | Custom snippet name field |
|
|
203
|
-
|
|
204
|
-
## Types
|
|
205
|
-
|
|
206
|
-
All types are exported from the package:
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
import type {
|
|
210
|
-
MenuProps,
|
|
211
|
-
MenuFields,
|
|
212
|
-
MenuItem,
|
|
213
|
-
MenuItemSnippet,
|
|
214
|
-
MenuGroupLabelSnippet,
|
|
215
|
-
MenuItemHandlers
|
|
216
|
-
} from '@rokkit/ui'
|
|
147
|
+
## Exports
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
import { List, Select, Menu, Button /* ... */ } from '@rokkit/ui'
|
|
151
|
+
import type { ListProps, SelectFields } from '@rokkit/ui/types'
|
|
152
|
+
import { generatePalette } from '@rokkit/ui/utils/palette'
|
|
217
153
|
```
|
|
218
154
|
|
|
219
|
-
##
|
|
155
|
+
## Theming
|
|
156
|
+
|
|
157
|
+
Components use `data-*` attribute selectors for styling (e.g., `[data-list-item]`, `[data-button]`). Apply styles via `@rokkit/themes` or write your own CSS targeting these hooks.
|
|
158
|
+
|
|
159
|
+
---
|
|
220
160
|
|
|
221
|
-
|
|
161
|
+
Part of [Rokkit](https://github.com/jerrythomas/rokkit) — a Svelte 5 component library and design system.
|
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.134",
|
|
4
4
|
"description": "Data driven UI components for Rokkit applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"svelte": "./src/index.ts",
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"src",
|
|
25
|
+
"README.md",
|
|
25
26
|
"LICENSE"
|
|
26
27
|
],
|
|
27
28
|
"scripts": {
|
|
28
29
|
"prepublishOnly": "cp ../../LICENSE .",
|
|
30
|
+
"postpublish": "rm -f LICENSE",
|
|
29
31
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
30
32
|
"build": "echo 'No build step needed for source-only package'"
|
|
31
33
|
},
|
|
@@ -37,10 +39,10 @@
|
|
|
37
39
|
"dropdown"
|
|
38
40
|
],
|
|
39
41
|
"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.
|
|
42
|
+
"@rokkit/core": "1.0.0-next.134",
|
|
43
|
+
"@rokkit/data": "1.0.0-next.134",
|
|
44
|
+
"@rokkit/states": "1.0.0-next.134",
|
|
45
|
+
"@rokkit/actions": "1.0.0-next.134"
|
|
44
46
|
},
|
|
45
47
|
"peerDependencies": {
|
|
46
48
|
"shiki": "^3.23.0",
|
|
@@ -72,11 +72,7 @@
|
|
|
72
72
|
{/if}
|
|
73
73
|
</span>
|
|
74
74
|
{:else if proxy.get('href')}
|
|
75
|
-
<a
|
|
76
|
-
href={proxy.get('href')}
|
|
77
|
-
data-breadcrumb-link
|
|
78
|
-
onclick={() => handleClick(proxy)}
|
|
79
|
-
>
|
|
75
|
+
<a href={proxy.get('href')} data-breadcrumb-link onclick={() => handleClick(proxy)}>
|
|
80
76
|
{#if crumb}
|
|
81
77
|
{@render crumb(proxy, isLast)}
|
|
82
78
|
{:else}
|
|
@@ -84,11 +80,7 @@
|
|
|
84
80
|
{/if}
|
|
85
81
|
</a>
|
|
86
82
|
{:else}
|
|
87
|
-
<button
|
|
88
|
-
type="button"
|
|
89
|
-
data-breadcrumb-link
|
|
90
|
-
onclick={() => handleClick(proxy)}
|
|
91
|
-
>
|
|
83
|
+
<button type="button" data-breadcrumb-link onclick={() => handleClick(proxy)}>
|
|
92
84
|
{#if crumb}
|
|
93
85
|
{@render crumb(proxy, isLast)}
|
|
94
86
|
{:else}
|
|
@@ -29,9 +29,7 @@
|
|
|
29
29
|
* Create a ProxyItem for default content rendering.
|
|
30
30
|
* Constructs a minimal item from button props.
|
|
31
31
|
*/
|
|
32
|
-
const proxy = $derived(
|
|
33
|
-
new ProxyItem({ text: label, icon, iconRight })
|
|
34
|
-
)
|
|
32
|
+
const proxy = $derived(new ProxyItem({ label, icon, iconRight }))
|
|
35
33
|
</script>
|
|
36
34
|
|
|
37
35
|
{#snippet defaultContent()}
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ButtonGroupProps } from '../types/button.js'
|
|
3
3
|
|
|
4
|
-
const {
|
|
5
|
-
size = 'md',
|
|
6
|
-
class: className = '',
|
|
7
|
-
children
|
|
8
|
-
}: ButtonGroupProps = $props()
|
|
4
|
+
const { size = 'md', class: className = '', children }: ButtonGroupProps = $props()
|
|
9
5
|
</script>
|
|
10
6
|
|
|
11
|
-
<div
|
|
12
|
-
data-button-group
|
|
13
|
-
data-size={size}
|
|
14
|
-
class={className || undefined}
|
|
15
|
-
role="group"
|
|
16
|
-
>
|
|
7
|
+
<div data-button-group data-size={size} class={className || undefined} role="group">
|
|
17
8
|
{@render children?.()}
|
|
18
9
|
</div>
|
|
@@ -16,14 +16,7 @@
|
|
|
16
16
|
children?: Snippet
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const {
|
|
20
|
-
href,
|
|
21
|
-
onclick,
|
|
22
|
-
class: className = '',
|
|
23
|
-
header,
|
|
24
|
-
footer,
|
|
25
|
-
children
|
|
26
|
-
}: CardProps = $props()
|
|
19
|
+
const { href, onclick, class: className = '', header, footer, children }: CardProps = $props()
|
|
27
20
|
</script>
|
|
28
21
|
|
|
29
22
|
{#snippet cardContent()}
|
|
@@ -110,11 +110,7 @@
|
|
|
110
110
|
onmouseleave={() => (hovered = false)}
|
|
111
111
|
>
|
|
112
112
|
<div data-carousel-viewport>
|
|
113
|
-
<div
|
|
114
|
-
data-carousel-track
|
|
115
|
-
style:--carousel-current={current}
|
|
116
|
-
style:--carousel-count={count}
|
|
117
|
-
>
|
|
113
|
+
<div data-carousel-track style:--carousel-current={current} style:--carousel-count={count}>
|
|
118
114
|
{#if slide}
|
|
119
115
|
{#each Array(count) as _, index (index)}
|
|
120
116
|
<div
|
|
@@ -31,7 +31,11 @@
|
|
|
31
31
|
...snippets
|
|
32
32
|
}: FloatingActionProps & { [key: string]: FloatingActionItemSnippet | unknown } = $props()
|
|
33
33
|
|
|
34
|
-
const icons = $derived({
|
|
34
|
+
const icons = $derived({
|
|
35
|
+
add: DEFAULT_STATE_ICONS.action.add,
|
|
36
|
+
close: DEFAULT_STATE_ICONS.action.close,
|
|
37
|
+
...userIcons
|
|
38
|
+
})
|
|
35
39
|
|
|
36
40
|
/**
|
|
37
41
|
* Create a ProxyItem for the given item
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
FloatingNavigationProps,
|
|
4
|
+
FloatingNavigationIcons
|
|
5
|
+
} from '../types/floating-navigation.js'
|
|
3
6
|
import { ProxyItem, messages } from '@rokkit/states'
|
|
4
7
|
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
5
8
|
|
|
@@ -23,7 +26,11 @@
|
|
|
23
26
|
|
|
24
27
|
const labels = $derived({ ...messages.current.floatingNav, ...userLabels })
|
|
25
28
|
|
|
26
|
-
const icons = $derived({
|
|
29
|
+
const icons = $derived({
|
|
30
|
+
pin: DEFAULT_STATE_ICONS.action.pin,
|
|
31
|
+
unpin: DEFAULT_STATE_ICONS.action.unpin,
|
|
32
|
+
...userIcons
|
|
33
|
+
})
|
|
27
34
|
|
|
28
35
|
let navRef = $state<HTMLElement | null>(null)
|
|
29
36
|
let expanded = $state(false)
|
|
@@ -38,9 +45,7 @@
|
|
|
38
45
|
}))
|
|
39
46
|
)
|
|
40
47
|
|
|
41
|
-
const activeIndex = $derived(
|
|
42
|
-
itemProxies.findIndex((item) => item.proxy.value === value)
|
|
43
|
-
)
|
|
48
|
+
const activeIndex = $derived(itemProxies.findIndex((item) => item.proxy.value === value))
|
|
44
49
|
|
|
45
50
|
function togglePin() {
|
|
46
51
|
pinned = !pinned
|
|
@@ -61,7 +66,10 @@
|
|
|
61
66
|
onselect?.(item.proxy.value, item.original)
|
|
62
67
|
|
|
63
68
|
// Smooth scroll to target section
|
|
64
|
-
const href =
|
|
69
|
+
const href =
|
|
70
|
+
item.proxy.get('href') !== undefined
|
|
71
|
+
? String(item.original[userFields?.href ?? 'href'] ?? '')
|
|
72
|
+
: ''
|
|
65
73
|
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
66
74
|
const el = document.getElementById(targetId)
|
|
67
75
|
el?.scrollIntoView({ behavior: 'smooth' })
|
|
@@ -123,9 +131,10 @@
|
|
|
123
131
|
for (const entry of entries) {
|
|
124
132
|
if (entry.isIntersecting) {
|
|
125
133
|
const match = itemProxies.find((item) => {
|
|
126
|
-
const href =
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
const href =
|
|
135
|
+
item.proxy.get('href') !== undefined
|
|
136
|
+
? String(item.original[userFields?.href ?? 'href'] ?? '')
|
|
137
|
+
: ''
|
|
129
138
|
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
130
139
|
return targetId === entry.target.id
|
|
131
140
|
})
|
|
@@ -137,9 +146,10 @@
|
|
|
137
146
|
}, observerOptions)
|
|
138
147
|
|
|
139
148
|
for (const item of itemProxies) {
|
|
140
|
-
const href =
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
const href =
|
|
150
|
+
item.proxy.get('href') !== undefined
|
|
151
|
+
? String(item.original[userFields?.href ?? 'href'] ?? '')
|
|
152
|
+
: ''
|
|
143
153
|
const targetId = href.startsWith('#') ? href.slice(1) : String(item.proxy.value)
|
|
144
154
|
const el = document.getElementById(targetId)
|
|
145
155
|
if (el) observer.observe(el)
|
|
@@ -174,7 +184,8 @@
|
|
|
174
184
|
aria-label={pinned ? labels.unpin : labels.pin}
|
|
175
185
|
onclick={togglePin}
|
|
176
186
|
>
|
|
177
|
-
<span data-floating-nav-pin-icon class={pinned ? icons.unpin : icons.pin} aria-hidden="true"
|
|
187
|
+
<span data-floating-nav-pin-icon class={pinned ? icons.unpin : icons.pin} aria-hidden="true"
|
|
188
|
+
></span>
|
|
178
189
|
</button>
|
|
179
190
|
</div>
|
|
180
191
|
|
|
@@ -225,10 +236,7 @@
|
|
|
225
236
|
{/each}
|
|
226
237
|
|
|
227
238
|
{#if activeIndex >= 0}
|
|
228
|
-
<span
|
|
229
|
-
data-floating-nav-indicator
|
|
230
|
-
style="--fn-active-index: {activeIndex}"
|
|
231
|
-
aria-hidden="true"
|
|
239
|
+
<span data-floating-nav-indicator style="--fn-active-index: {activeIndex}" aria-hidden="true"
|
|
232
240
|
></span>
|
|
233
241
|
{/if}
|
|
234
242
|
</div>
|