@streamscloud/kit 0.65.0 → 0.66.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/dist/core/actions/horizontal-wheel-scroll.d.ts +6 -2
- package/dist/core/actions/horizontal-wheel-scroll.js +3 -1
- package/dist/ui/breadcrumb/breadcrumb-localization.d.ts +1 -0
- package/dist/ui/breadcrumb/breadcrumb-localization.js +7 -0
- package/dist/ui/breadcrumb/cmp.breadcrumb.svelte +151 -13
- package/dist/ui/breadcrumb/cmp.breadcrumb.svelte.d.ts +7 -1
- package/dist/ui/dialog/cmp.dialog-container.svelte +4 -2
- package/dist/ui/seek-bar/cmp.seek-bar.svelte +4 -4
- package/dist/ui/tree/cmp.tree-node-view.svelte +181 -80
- package/dist/ui/tree/cmp.tree-node-view.svelte.d.ts +4 -1
- package/dist/ui/tree/cmp.tree.svelte +8 -7
- package/dist/ui/tree/cmp.tree.svelte.d.ts +6 -6
- package/dist/ui/tree/index.d.ts +1 -2
- package/dist/ui/tree/tree-drop-target.svelte.d.ts +13 -0
- package/dist/ui/tree/tree-drop-target.svelte.js +30 -0
- package/dist/ui/tree/tree-keyboard.svelte.d.ts +1 -0
- package/dist/ui/tree/tree-keyboard.svelte.js +7 -0
- package/dist/ui/tree/tree-localization.d.ts +3 -0
- package/dist/ui/tree/tree-localization.js +12 -0
- package/dist/ui/tree/tree-node.svelte.d.ts +51 -31
- package/dist/ui/tree/tree-node.svelte.js +103 -123
- package/package.json +1 -1
- package/dist/ui/tree/tree-node-drag-config.svelte.d.ts +0 -16
- package/dist/ui/tree/tree-node-drag-config.svelte.js +0 -4
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Action } from 'svelte/action';
|
|
2
|
-
|
|
2
|
+
type HorizontalWheelScrollParams = {
|
|
3
3
|
isolateScroll: boolean;
|
|
4
|
+
/** `false` lets the wheel through to the page once the element can't scroll further that way. @default true */
|
|
5
|
+
isolateAtEdges?: boolean;
|
|
4
6
|
speed?: number;
|
|
5
|
-
}
|
|
7
|
+
};
|
|
8
|
+
export declare const horizontalWheelScroll: Action<HTMLElement, HorizontalWheelScrollParams | undefined>;
|
|
9
|
+
export {};
|
|
@@ -5,8 +5,10 @@ export const horizontalWheelScroll = (node, param = { isolateScroll: true, speed
|
|
|
5
5
|
return;
|
|
6
6
|
}
|
|
7
7
|
const delta = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY;
|
|
8
|
+
const maxScrollLeft = node.scrollWidth - node.clientWidth;
|
|
9
|
+
const atEdge = (delta < 0 && node.scrollLeft <= 0) || (delta > 0 && node.scrollLeft >= maxScrollLeft - 1);
|
|
8
10
|
node.scrollBy({ left: delta * (param.speed ?? 1) });
|
|
9
|
-
if (param.isolateScroll) {
|
|
11
|
+
if (param.isolateScroll && (param.isolateAtEdges !== false || !atEdge)) {
|
|
10
12
|
event.preventDefault();
|
|
11
13
|
event.stopPropagation();
|
|
12
14
|
}
|
|
@@ -3,10 +3,17 @@ const loc = {
|
|
|
3
3
|
navLabel: {
|
|
4
4
|
en: 'Breadcrumb',
|
|
5
5
|
no: 'Brødsmuler'
|
|
6
|
+
},
|
|
7
|
+
hiddenLevels: {
|
|
8
|
+
en: 'Show hidden levels',
|
|
9
|
+
no: 'Vis skjulte nivåer'
|
|
6
10
|
}
|
|
7
11
|
};
|
|
8
12
|
export class BreadcrumbLocalization {
|
|
9
13
|
get navLabel() {
|
|
10
14
|
return loc.navLabel[AppLocale.current];
|
|
11
15
|
}
|
|
16
|
+
get hiddenLevels() {
|
|
17
|
+
return loc.hiddenLevels[AppLocale.current];
|
|
18
|
+
}
|
|
12
19
|
}
|
|
@@ -1,19 +1,95 @@
|
|
|
1
|
-
<script lang="ts">import {
|
|
2
|
-
|
|
1
|
+
<script lang="ts">import { horizontalWheelScroll } from '../../core/actions';
|
|
2
|
+
import { Popover } from '../popover';
|
|
3
|
+
import { BreadcrumbLocalization } from './breadcrumb-localization';
|
|
4
|
+
import { tick } from 'svelte';
|
|
5
|
+
let { items, separator = '/', underline = 'hover', overflow = 'wrap', maxItems } = $props();
|
|
3
6
|
const localization = new BreadcrumbLocalization();
|
|
7
|
+
let canScrollBack = $state(false);
|
|
8
|
+
let canScrollForward = $state(false);
|
|
9
|
+
let menuOpened = $state(false);
|
|
10
|
+
const tailLength = $derived(maxItems === undefined ? 0 : Math.max(2, maxItems) - 1);
|
|
11
|
+
const collapsed = $derived(maxItems !== undefined && items.length > tailLength + 1);
|
|
12
|
+
const hiddenItems = $derived(collapsed ? items.slice(1, items.length - tailLength) : []);
|
|
13
|
+
const shownItems = $derived(collapsed ? [items[0], ...items.slice(items.length - tailLength)] : items);
|
|
14
|
+
const scrolls = $derived(overflow === 'scroll');
|
|
15
|
+
// The "…" Popover renders inside the track; a mask would hide it.
|
|
16
|
+
const masked = $derived(scrolls && !menuOpened);
|
|
17
|
+
const updateScrollState = (node) => {
|
|
18
|
+
canScrollBack = node.scrollLeft > 0;
|
|
19
|
+
canScrollForward = node.scrollLeft < node.scrollWidth - node.clientWidth - 1;
|
|
20
|
+
};
|
|
21
|
+
const scrollTrack = (node, _items) => {
|
|
22
|
+
const scrollToCurrent = () => {
|
|
23
|
+
if (scrolls) {
|
|
24
|
+
node.scrollLeft = node.scrollWidth;
|
|
25
|
+
}
|
|
26
|
+
updateScrollState(node);
|
|
27
|
+
};
|
|
28
|
+
// Children too: the track's own box is pinned at max-width, so a web-font swap only resizes its content.
|
|
29
|
+
const observer = new ResizeObserver(scrollToCurrent);
|
|
30
|
+
const observeTrackAndCrumbs = () => {
|
|
31
|
+
observer.disconnect();
|
|
32
|
+
observer.observe(node);
|
|
33
|
+
for (const child of node.children) {
|
|
34
|
+
observer.observe(child);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
observeTrackAndCrumbs();
|
|
38
|
+
return {
|
|
39
|
+
update: async () => {
|
|
40
|
+
await tick();
|
|
41
|
+
observeTrackAndCrumbs();
|
|
42
|
+
scrollToCurrent();
|
|
43
|
+
},
|
|
44
|
+
destroy: () => observer.disconnect()
|
|
45
|
+
};
|
|
46
|
+
};
|
|
4
47
|
</script>
|
|
5
48
|
|
|
6
|
-
|
|
7
|
-
{#
|
|
49
|
+
{#snippet crumb(item: BreadcrumbItem, current: boolean, menuItem: boolean)}
|
|
50
|
+
{#if current}
|
|
51
|
+
<span class="breadcrumb__current" aria-current="page">{item.label}</span>
|
|
52
|
+
{:else if item.href}
|
|
53
|
+
<a class="breadcrumb__link" class:breadcrumb__link--menu={menuItem} href={item.href} onclick={(e) => item.on?.click?.(e)}>{item.label}</a>
|
|
54
|
+
{:else if item.on?.click}
|
|
55
|
+
<button type="button" class="breadcrumb__link" class:breadcrumb__link--menu={menuItem} onclick={(e) => item.on?.click?.(e)}>{item.label}</button>
|
|
56
|
+
{:else}
|
|
57
|
+
<span class="breadcrumb__current" class:breadcrumb__current--menu={menuItem}>{item.label}</span>
|
|
58
|
+
{/if}
|
|
59
|
+
{/snippet}
|
|
60
|
+
|
|
61
|
+
<div
|
|
62
|
+
class="breadcrumb breadcrumb--underline-{underline}"
|
|
63
|
+
class:breadcrumb--scroll={scrolls}
|
|
64
|
+
class:breadcrumb--mask-back={masked && canScrollBack && !canScrollForward}
|
|
65
|
+
class:breadcrumb--mask-forward={masked && canScrollForward && !canScrollBack}
|
|
66
|
+
class:breadcrumb--mask-both={masked && canScrollBack && canScrollForward}
|
|
67
|
+
role="navigation"
|
|
68
|
+
aria-label={localization.navLabel}
|
|
69
|
+
use:scrollTrack={shownItems}
|
|
70
|
+
use:horizontalWheelScroll={{ isolateScroll: scrolls, isolateAtEdges: false }}
|
|
71
|
+
onscroll={(e) => updateScrollState(e.currentTarget)}>
|
|
72
|
+
{#each shownItems as item, i (`${i}:${item.label}`)}
|
|
8
73
|
{#if i > 0}<span class="breadcrumb__separator" aria-hidden="true">{separator}</span>{/if}
|
|
9
|
-
{
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
74
|
+
{@render crumb(item, i === shownItems.length - 1, false)}
|
|
75
|
+
{#if i === 0 && collapsed}
|
|
76
|
+
<span class="breadcrumb__separator" aria-hidden="true">{separator}</span>
|
|
77
|
+
<span class="breadcrumb__more" onwheel={(e) => e.stopPropagation()}>
|
|
78
|
+
<Popover
|
|
79
|
+
position="bottom-start"
|
|
80
|
+
fixedPosition
|
|
81
|
+
aria-label={localization.hiddenLevels}
|
|
82
|
+
on={{ opened: () => (menuOpened = true), closed: () => (menuOpened = false) }}>
|
|
83
|
+
{#snippet trigger()}
|
|
84
|
+
<span class="breadcrumb__more-trigger">…</span>
|
|
85
|
+
{/snippet}
|
|
86
|
+
<div class="breadcrumb__menu">
|
|
87
|
+
{#each hiddenItems as hidden, j (`${j}:${hidden.label}`)}
|
|
88
|
+
{@render crumb(hidden, false, true)}
|
|
89
|
+
{/each}
|
|
90
|
+
</div>
|
|
91
|
+
</Popover>
|
|
92
|
+
</span>
|
|
17
93
|
{/if}
|
|
18
94
|
{/each}
|
|
19
95
|
</div>
|
|
@@ -27,7 +103,9 @@ A horizontal trail of crumbs ending at the current page. Each non-last item rend
|
|
|
27
103
|
- plain text `<span>` when neither is set.
|
|
28
104
|
|
|
29
105
|
The last item is always plain text with `aria-current="page"`. `underline` decides whether the crumb
|
|
30
|
-
links show their underline on hover only (default), always, or never.
|
|
106
|
+
links show their underline on hover only (default), always, or never. `overflow="scroll"` keeps the
|
|
107
|
+
trail on one line that scrolls sideways; `maxItems` folds the middle into a "…" crumb that lists the
|
|
108
|
+
hidden levels.
|
|
31
109
|
|
|
32
110
|
### CSS Custom Properties
|
|
33
111
|
| Property | Description | Default |
|
|
@@ -63,7 +141,34 @@ links show their underline on hover only (default), always, or never.
|
|
|
63
141
|
.breadcrumb--underline-never {
|
|
64
142
|
--_breadcrumb--link-text-decoration-hover: none;
|
|
65
143
|
}
|
|
144
|
+
.breadcrumb--scroll {
|
|
145
|
+
--_breadcrumb--mask-size: 1.5rem;
|
|
146
|
+
flex-wrap: nowrap;
|
|
147
|
+
max-width: 100%;
|
|
148
|
+
padding-block: 0.25rem;
|
|
149
|
+
margin-block: -0.25rem;
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
overflow-x: auto;
|
|
152
|
+
scrollbar-width: none;
|
|
153
|
+
}
|
|
154
|
+
.breadcrumb--scroll::-webkit-scrollbar {
|
|
155
|
+
display: none;
|
|
156
|
+
}
|
|
157
|
+
.breadcrumb--mask-back {
|
|
158
|
+
mask-image: linear-gradient(to right, transparent 0%, black var(--_breadcrumb--mask-size), black 100%);
|
|
159
|
+
}
|
|
160
|
+
.breadcrumb--mask-forward {
|
|
161
|
+
mask-image: linear-gradient(to right, black 0%, black calc(100% - var(--_breadcrumb--mask-size)), transparent 100%);
|
|
162
|
+
}
|
|
163
|
+
.breadcrumb--mask-both {
|
|
164
|
+
mask-image: linear-gradient(to right, transparent 0%, black var(--_breadcrumb--mask-size), black calc(100% - var(--_breadcrumb--mask-size)), transparent 100%);
|
|
165
|
+
}
|
|
66
166
|
.breadcrumb__link {
|
|
167
|
+
padding: 0;
|
|
168
|
+
font: inherit;
|
|
169
|
+
background: none;
|
|
170
|
+
border: none;
|
|
171
|
+
cursor: pointer;
|
|
67
172
|
color: var(--_breadcrumb--link-color);
|
|
68
173
|
text-decoration: var(--_breadcrumb--link-text-decoration);
|
|
69
174
|
transition: color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
@@ -77,11 +182,44 @@ links show their underline on hover only (default), always, or never.
|
|
|
77
182
|
outline-offset: 2px;
|
|
78
183
|
border-radius: var(--sc-kit--radius--sm);
|
|
79
184
|
}
|
|
185
|
+
.breadcrumb__link--menu {
|
|
186
|
+
display: block;
|
|
187
|
+
width: 100%;
|
|
188
|
+
padding: var(--sc-kit--space--1) var(--sc-kit--space--3);
|
|
189
|
+
text-align: start;
|
|
190
|
+
border-radius: var(--sc-kit--radius--sm);
|
|
191
|
+
white-space: nowrap;
|
|
192
|
+
}
|
|
193
|
+
.breadcrumb__link--menu:hover {
|
|
194
|
+
background: var(--sc-kit--color--bg--hover);
|
|
195
|
+
}
|
|
80
196
|
.breadcrumb__current {
|
|
81
197
|
color: var(--_breadcrumb--current-color);
|
|
82
198
|
font-weight: var(--sc-kit--font-weight--medium);
|
|
83
199
|
}
|
|
200
|
+
.breadcrumb__current--menu {
|
|
201
|
+
display: block;
|
|
202
|
+
padding: var(--sc-kit--space--1) var(--sc-kit--space--3);
|
|
203
|
+
font-weight: var(--sc-kit--font-weight--regular);
|
|
204
|
+
white-space: nowrap;
|
|
205
|
+
}
|
|
84
206
|
.breadcrumb__separator {
|
|
85
207
|
color: var(--_breadcrumb--color);
|
|
86
208
|
user-select: none;
|
|
209
|
+
}
|
|
210
|
+
.breadcrumb__more {
|
|
211
|
+
display: inline-flex;
|
|
212
|
+
}
|
|
213
|
+
.breadcrumb__more-trigger {
|
|
214
|
+
color: var(--_breadcrumb--link-color);
|
|
215
|
+
transition: color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
216
|
+
}
|
|
217
|
+
.breadcrumb__more-trigger:hover {
|
|
218
|
+
color: var(--_breadcrumb--link-color-hover);
|
|
219
|
+
}
|
|
220
|
+
.breadcrumb__menu {
|
|
221
|
+
display: flex;
|
|
222
|
+
flex-direction: column;
|
|
223
|
+
padding: var(--sc-kit--space--1);
|
|
224
|
+
font-size: var(--_breadcrumb--font-size);
|
|
87
225
|
}</style>
|
|
@@ -5,6 +5,10 @@ type Props = {
|
|
|
5
5
|
separator?: string;
|
|
6
6
|
/** When the crumb links show their underline: only on hover, permanently, or not at all. @default 'hover' */
|
|
7
7
|
underline?: 'hover' | 'always' | 'never';
|
|
8
|
+
/** `'wrap'` breaks the trail onto new lines; `'scroll'` keeps one line that scrolls sideways (wheel included) and starts scrolled to the current crumb. @default 'wrap' */
|
|
9
|
+
overflow?: 'wrap' | 'scroll';
|
|
10
|
+
/** Collapse the middle of a longer trail: keep the first crumb and the last `maxItems - 1`, the hidden ones open from a "…" crumb. Values below 2 count as 2. */
|
|
11
|
+
maxItems?: number;
|
|
8
12
|
};
|
|
9
13
|
/**
|
|
10
14
|
* A horizontal trail of crumbs ending at the current page. Each non-last item renders as:
|
|
@@ -14,7 +18,9 @@ type Props = {
|
|
|
14
18
|
* - plain text `<span>` when neither is set.
|
|
15
19
|
*
|
|
16
20
|
* The last item is always plain text with `aria-current="page"`. `underline` decides whether the crumb
|
|
17
|
-
* links show their underline on hover only (default), always, or never.
|
|
21
|
+
* links show their underline on hover only (default), always, or never. `overflow="scroll"` keeps the
|
|
22
|
+
* trail on one line that scrolls sideways; `maxItems` folds the middle into a "…" crumb that lists the
|
|
23
|
+
* hidden levels.
|
|
18
24
|
*
|
|
19
25
|
* ### CSS Custom Properties
|
|
20
26
|
* | Property | Description | Default |
|
|
@@ -11,8 +11,9 @@ const DialogView = $derived(dialog.view);
|
|
|
11
11
|
// doesn't always prevent dialog close. Using keydown to block ESC earlier.
|
|
12
12
|
// Remove when browser bug is fixed.
|
|
13
13
|
// See: https://issues.chromium.org/issues/41487622
|
|
14
|
+
// On window, not <dialog>: after a stacked dialog unmounts focus is on <body> and Esc natively closes this one.
|
|
14
15
|
const handleKeydown = (event) => {
|
|
15
|
-
if (event.key !==
|
|
16
|
+
if (event.key !== 'Escape' || !isActive) {
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
18
19
|
if (!canDismiss) {
|
|
@@ -62,6 +63,8 @@ const customWidth = $derived(isCustomWidth ? size : null);
|
|
|
62
63
|
const isStretchContent = $derived(position === 'full-screen' || position === 'full-height' || position === 'side-left' || position === 'side-right');
|
|
63
64
|
</script>
|
|
64
65
|
|
|
66
|
+
<svelte:window onkeydown={handleKeydown} />
|
|
67
|
+
|
|
65
68
|
<dialog
|
|
66
69
|
bind:this={dialogElement}
|
|
67
70
|
class="dialog-container"
|
|
@@ -79,7 +82,6 @@ const isStretchContent = $derived(position === 'full-screen' || position === 'fu
|
|
|
79
82
|
class:dialog-container--position-full-height={position === 'full-height'}
|
|
80
83
|
class:dialog-container--position-side-left={position === 'side-left'}
|
|
81
84
|
class:dialog-container--position-side-right={position === 'side-right'}
|
|
82
|
-
onkeydown={handleKeydown}
|
|
83
85
|
oncancel={handleCancel}
|
|
84
86
|
onclick={handleBackdropClick}
|
|
85
87
|
style:--dialog-container--custom-width={customWidth}>
|
|
@@ -52,16 +52,16 @@ const onMouseDown = (e) => {
|
|
|
52
52
|
const handleScrubberKeyDown = (event) => {
|
|
53
53
|
const step = 0.05;
|
|
54
54
|
let newValue;
|
|
55
|
-
if (event.key ===
|
|
55
|
+
if (event.key === 'ArrowLeft') {
|
|
56
56
|
newValue = Math.max(0, value - step);
|
|
57
57
|
}
|
|
58
|
-
else if (event.key ===
|
|
58
|
+
else if (event.key === 'ArrowRight') {
|
|
59
59
|
newValue = Math.min(1, value + step);
|
|
60
60
|
}
|
|
61
|
-
else if (event.key ===
|
|
61
|
+
else if (event.key === 'Home') {
|
|
62
62
|
newValue = 0;
|
|
63
63
|
}
|
|
64
|
-
else if (event.key ===
|
|
64
|
+
else if (event.key === 'End') {
|
|
65
65
|
newValue = 1;
|
|
66
66
|
}
|
|
67
67
|
if (newValue !== undefined) {
|