svelte-p5-components 0.4.1 → 0.5.0
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 +10 -0
- package/dist/ActivityBar.svelte +227 -0
- package/dist/ActivityBar.svelte.d.ts +66 -0
- package/dist/ContextMenu.svelte +439 -0
- package/dist/ContextMenu.svelte.d.ts +80 -0
- package/dist/EntityToggleList.svelte +175 -21
- package/dist/EntityToggleList.svelte.d.ts +7 -0
- package/dist/SidePanel.svelte +272 -0
- package/dist/SidePanel.svelte.d.ts +59 -0
- package/dist/TimelineScrubber.svelte +223 -70
- package/dist/TimelineScrubber.svelte.d.ts +32 -8
- package/dist/TimelineScrubber.test.svelte.js +4 -3
- package/dist/TimelineTrack.svelte +483 -150
- package/dist/TimelineTrack.svelte.d.ts +33 -18
- package/dist/TimelineTrack.test.svelte.js +70 -9
- package/dist/createMediaPlayback.svelte.d.ts +60 -0
- package/dist/createMediaPlayback.svelte.js +169 -0
- package/dist/createMediaPlayback.test.d.ts +1 -0
- package/dist/createMediaPlayback.test.js +176 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,16 @@ Higher-level Svelte 5 components built on [`svelte-p5`](../core). The pieces tha
|
|
|
8
8
|
pnpm add svelte-p5 svelte-p5-components p5
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
To test a change that's merged on `main` but not yet released, install the preview build from [pkg.pr.new](https://pkg.pr.new/):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add \
|
|
15
|
+
https://pkg.pr.new/edw1nzhao/svelte-p5/svelte-p5@main \
|
|
16
|
+
https://pkg.pr.new/edw1nzhao/svelte-p5/svelte-p5-components@main
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Pin to a SHA (`@<commit-sha>`) instead of `@main` for reproducible installs.
|
|
20
|
+
|
|
11
21
|
## `<Sketch>`
|
|
12
22
|
|
|
13
23
|
`<P5Canvas>` plus a `ResizeObserver` on the parent element and automatic `pixelDensity(devicePixelRatio)`. The default you probably want.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One item rendered as an icon button in the rail.
|
|
6
|
+
*/
|
|
7
|
+
export interface ActivityBarItem {
|
|
8
|
+
/** Stable id. Passed back via `onSelect` and used for `activeId`. */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Snippet that renders the icon. Called with no arguments. */
|
|
11
|
+
icon: Snippet;
|
|
12
|
+
/** Accessible label + tooltip title. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Optional numeric badge rendered over the icon. */
|
|
15
|
+
badge?: number;
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
/**
|
|
21
|
+
* Vertical activity-bar rail (VS Code pattern). Renders a stack of
|
|
22
|
+
* square icon buttons wired into a `role="tablist"` group. One item
|
|
23
|
+
* can be "active"; callers own that state via the `activeId` prop.
|
|
24
|
+
*
|
|
25
|
+
* Visual contract: 48×48 square buttons. Active state renders an
|
|
26
|
+
* accent-colored left border + subtle background tint. Hover is a
|
|
27
|
+
* faint background. All class names are author-written (not scoped
|
|
28
|
+
* hashes) so consumers can safely target them via `:global(...)`:
|
|
29
|
+
*
|
|
30
|
+
* .activity-bar
|
|
31
|
+
* .activity-bar__item
|
|
32
|
+
* .activity-bar__item--active
|
|
33
|
+
* .activity-bar__badge
|
|
34
|
+
*
|
|
35
|
+
* Keyboard: Up/Down arrow moves focus between buttons (wraps); Enter
|
|
36
|
+
* or Space activates the focused item.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```svelte
|
|
40
|
+
* <ActivityBar
|
|
41
|
+
* activeId={active}
|
|
42
|
+
* items={[
|
|
43
|
+
* { id: 'viz', label: 'Visualizations', icon: vizIcon },
|
|
44
|
+
* { id: 'filters', label: 'Filters', icon: filtersIcon }
|
|
45
|
+
* ]}
|
|
46
|
+
* onSelect={(id) => (active = active === id ? undefined : id)}
|
|
47
|
+
* />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
interface Props {
|
|
51
|
+
/** The items to render. */
|
|
52
|
+
items: ActivityBarItem[];
|
|
53
|
+
/**
|
|
54
|
+
* The id of the currently-active item. When undefined, no item
|
|
55
|
+
* shows the active treatment. Bindable.
|
|
56
|
+
*/
|
|
57
|
+
activeId?: string;
|
|
58
|
+
/** Click / keyboard-activation callback. */
|
|
59
|
+
onSelect?: (id: string) => void;
|
|
60
|
+
/** Rail width in px. Default: 48. */
|
|
61
|
+
width?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Rail orientation. Only `'vertical'` is supported today; the prop
|
|
64
|
+
* is kept so future work can add horizontal without breaking API.
|
|
65
|
+
*/
|
|
66
|
+
orientation?: 'vertical';
|
|
67
|
+
class?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let {
|
|
71
|
+
items,
|
|
72
|
+
activeId = $bindable<string | undefined>(undefined),
|
|
73
|
+
onSelect,
|
|
74
|
+
width = 48,
|
|
75
|
+
orientation = 'vertical',
|
|
76
|
+
class: className = ''
|
|
77
|
+
}: Props = $props();
|
|
78
|
+
|
|
79
|
+
let rootEl: HTMLDivElement | null = $state(null);
|
|
80
|
+
|
|
81
|
+
function handleItemClick(id: string) {
|
|
82
|
+
onSelect?.(id);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
86
|
+
if (!rootEl) return;
|
|
87
|
+
const buttons = Array.from(rootEl.querySelectorAll<HTMLButtonElement>('.activity-bar__item'));
|
|
88
|
+
if (buttons.length === 0) return;
|
|
89
|
+
|
|
90
|
+
const current = document.activeElement as HTMLElement | null;
|
|
91
|
+
const idx = current ? buttons.indexOf(current as HTMLButtonElement) : -1;
|
|
92
|
+
|
|
93
|
+
if (event.key === 'ArrowDown' || (orientation === 'vertical' && event.key === 'ArrowRight')) {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
const next = idx < 0 ? 0 : (idx + 1) % buttons.length;
|
|
96
|
+
buttons[next]?.focus();
|
|
97
|
+
} else if (
|
|
98
|
+
event.key === 'ArrowUp' ||
|
|
99
|
+
(orientation === 'vertical' && event.key === 'ArrowLeft')
|
|
100
|
+
) {
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
const next = idx < 0 ? buttons.length - 1 : (idx - 1 + buttons.length) % buttons.length;
|
|
103
|
+
buttons[next]?.focus();
|
|
104
|
+
} else if (event.key === 'Enter' || event.key === ' ') {
|
|
105
|
+
if (idx >= 0) {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
const id = items[idx]?.id;
|
|
108
|
+
if (id) onSelect?.(id);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<div
|
|
115
|
+
bind:this={rootEl}
|
|
116
|
+
class="activity-bar {className}"
|
|
117
|
+
role="tablist"
|
|
118
|
+
aria-orientation={orientation}
|
|
119
|
+
style:width="{width}px"
|
|
120
|
+
onkeydown={handleKeydown}
|
|
121
|
+
tabindex="-1"
|
|
122
|
+
>
|
|
123
|
+
{#each items as item (item.id)}
|
|
124
|
+
{@const isActive = item.id === activeId}
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
class="activity-bar__item {isActive ? 'activity-bar__item--active' : ''}"
|
|
128
|
+
role="tab"
|
|
129
|
+
aria-selected={isActive}
|
|
130
|
+
aria-label={item.label}
|
|
131
|
+
title={item.label}
|
|
132
|
+
tabindex={isActive ? 0 : -1}
|
|
133
|
+
onclick={() => handleItemClick(item.id)}
|
|
134
|
+
>
|
|
135
|
+
<span class="activity-bar__icon">
|
|
136
|
+
{@render item.icon()}
|
|
137
|
+
</span>
|
|
138
|
+
{#if item.badge !== undefined && item.badge > 0}
|
|
139
|
+
<span class="activity-bar__badge" aria-hidden="true">{item.badge}</span>
|
|
140
|
+
{/if}
|
|
141
|
+
</button>
|
|
142
|
+
{/each}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<style>
|
|
146
|
+
.activity-bar {
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-direction: column;
|
|
149
|
+
align-items: stretch;
|
|
150
|
+
flex: 0 0 auto;
|
|
151
|
+
height: 100%;
|
|
152
|
+
background: var(--activity-bar-bg, #f3f3f3);
|
|
153
|
+
border-right: 1px solid var(--activity-bar-border, rgba(0, 0, 0, 0.08));
|
|
154
|
+
overflow: hidden;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.activity-bar__item {
|
|
158
|
+
position: relative;
|
|
159
|
+
display: inline-flex;
|
|
160
|
+
align-items: center;
|
|
161
|
+
justify-content: center;
|
|
162
|
+
width: 48px;
|
|
163
|
+
height: 48px;
|
|
164
|
+
padding: 0;
|
|
165
|
+
margin: 0;
|
|
166
|
+
border: none;
|
|
167
|
+
background: transparent;
|
|
168
|
+
color: var(--activity-bar-fg, #555);
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
transition:
|
|
171
|
+
background-color 120ms ease,
|
|
172
|
+
color 120ms ease;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.activity-bar__item:hover {
|
|
176
|
+
background: var(--activity-bar-bg-hover, rgba(0, 0, 0, 0.05));
|
|
177
|
+
color: var(--activity-bar-fg-hover, #111);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.activity-bar__item:focus-visible {
|
|
181
|
+
outline: 2px solid var(--activity-bar-focus, rgba(59, 130, 246, 0.55));
|
|
182
|
+
outline-offset: -2px;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.activity-bar__item--active {
|
|
186
|
+
color: var(--activity-bar-fg-active, #111);
|
|
187
|
+
background: var(--activity-bar-bg-active, rgba(59, 130, 246, 0.08));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.activity-bar__item--active::before {
|
|
191
|
+
content: '';
|
|
192
|
+
position: absolute;
|
|
193
|
+
left: 0;
|
|
194
|
+
top: 0;
|
|
195
|
+
bottom: 0;
|
|
196
|
+
width: 2px;
|
|
197
|
+
background: var(--activity-bar-accent, #3b82f6);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.activity-bar__icon {
|
|
201
|
+
display: inline-flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
justify-content: center;
|
|
204
|
+
width: 22px;
|
|
205
|
+
height: 22px;
|
|
206
|
+
pointer-events: none;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.activity-bar__badge {
|
|
210
|
+
position: absolute;
|
|
211
|
+
top: 6px;
|
|
212
|
+
right: 6px;
|
|
213
|
+
min-width: 16px;
|
|
214
|
+
height: 16px;
|
|
215
|
+
padding: 0 4px;
|
|
216
|
+
border-radius: 8px;
|
|
217
|
+
background: var(--activity-bar-badge-bg, #ef4444);
|
|
218
|
+
color: var(--activity-bar-badge-fg, #fff);
|
|
219
|
+
font:
|
|
220
|
+
10px/16px system-ui,
|
|
221
|
+
-apple-system,
|
|
222
|
+
Segoe UI,
|
|
223
|
+
sans-serif;
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
text-align: center;
|
|
226
|
+
}
|
|
227
|
+
</style>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* One item rendered as an icon button in the rail.
|
|
4
|
+
*/
|
|
5
|
+
export interface ActivityBarItem {
|
|
6
|
+
/** Stable id. Passed back via `onSelect` and used for `activeId`. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Snippet that renders the icon. Called with no arguments. */
|
|
9
|
+
icon: Snippet;
|
|
10
|
+
/** Accessible label + tooltip title. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Optional numeric badge rendered over the icon. */
|
|
13
|
+
badge?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Vertical activity-bar rail (VS Code pattern). Renders a stack of
|
|
17
|
+
* square icon buttons wired into a `role="tablist"` group. One item
|
|
18
|
+
* can be "active"; callers own that state via the `activeId` prop.
|
|
19
|
+
*
|
|
20
|
+
* Visual contract: 48×48 square buttons. Active state renders an
|
|
21
|
+
* accent-colored left border + subtle background tint. Hover is a
|
|
22
|
+
* faint background. All class names are author-written (not scoped
|
|
23
|
+
* hashes) so consumers can safely target them via `:global(...)`:
|
|
24
|
+
*
|
|
25
|
+
* .activity-bar
|
|
26
|
+
* .activity-bar__item
|
|
27
|
+
* .activity-bar__item--active
|
|
28
|
+
* .activity-bar__badge
|
|
29
|
+
*
|
|
30
|
+
* Keyboard: Up/Down arrow moves focus between buttons (wraps); Enter
|
|
31
|
+
* or Space activates the focused item.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```svelte
|
|
35
|
+
* <ActivityBar
|
|
36
|
+
* activeId={active}
|
|
37
|
+
* items={[
|
|
38
|
+
* { id: 'viz', label: 'Visualizations', icon: vizIcon },
|
|
39
|
+
* { id: 'filters', label: 'Filters', icon: filtersIcon }
|
|
40
|
+
* ]}
|
|
41
|
+
* onSelect={(id) => (active = active === id ? undefined : id)}
|
|
42
|
+
* />
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
interface Props {
|
|
46
|
+
/** The items to render. */
|
|
47
|
+
items: ActivityBarItem[];
|
|
48
|
+
/**
|
|
49
|
+
* The id of the currently-active item. When undefined, no item
|
|
50
|
+
* shows the active treatment. Bindable.
|
|
51
|
+
*/
|
|
52
|
+
activeId?: string;
|
|
53
|
+
/** Click / keyboard-activation callback. */
|
|
54
|
+
onSelect?: (id: string) => void;
|
|
55
|
+
/** Rail width in px. Default: 48. */
|
|
56
|
+
width?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Rail orientation. Only `'vertical'` is supported today; the prop
|
|
59
|
+
* is kept so future work can add horizontal without breaking API.
|
|
60
|
+
*/
|
|
61
|
+
orientation?: 'vertical';
|
|
62
|
+
class?: string;
|
|
63
|
+
}
|
|
64
|
+
declare const ActivityBar: import("svelte").Component<Props, {}, "activeId">;
|
|
65
|
+
type ActivityBar = ReturnType<typeof ActivityBar>;
|
|
66
|
+
export default ActivityBar;
|