@urbicon-ui/blocks 6.9.0 → 6.11.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/i18n/index.d.ts +2 -0
- package/dist/primitives/JourneyTimeline/JourneyTimeline.svelte +200 -96
- package/dist/primitives/JourneyTimeline/index.d.ts +93 -37
- package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +225 -42
- package/dist/primitives/JourneyTimeline/journey-timeline.variants.js +240 -69
- package/dist/primitives/Tab/tab.variants.d.ts +8 -8
- package/dist/translations/de.d.ts +1 -0
- package/dist/translations/de.js +1 -0
- package/dist/translations/en.d.ts +1 -0
- package/dist/translations/en.js +1 -0
- package/package.json +3 -3
package/dist/i18n/index.d.ts
CHANGED
|
@@ -137,6 +137,7 @@ declare const blocksTranslations: {
|
|
|
137
137
|
readonly complete: "Completed";
|
|
138
138
|
readonly active: "In progress";
|
|
139
139
|
readonly pending: "Pending";
|
|
140
|
+
readonly attention: "Needs attention";
|
|
140
141
|
readonly blocked: "Blocked";
|
|
141
142
|
readonly skipped: "Skipped";
|
|
142
143
|
};
|
|
@@ -318,6 +319,7 @@ declare const blocksTranslations: {
|
|
|
318
319
|
readonly complete: "Abgeschlossen";
|
|
319
320
|
readonly active: "In Bearbeitung";
|
|
320
321
|
readonly pending: "Ausstehend";
|
|
322
|
+
readonly attention: "Aufmerksamkeit erforderlich";
|
|
321
323
|
readonly blocked: "Blockiert";
|
|
322
324
|
readonly skipped: "Übersprungen";
|
|
323
325
|
};
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useBlocksI18n } from '../..';
|
|
3
3
|
import { getBlocksConfig, resolveSlotClasses } from '../../provider';
|
|
4
|
-
import { resolveIcon } from '../../icons';
|
|
5
|
-
import CheckIconDefault from '../../icons/CheckIcon.svelte';
|
|
6
|
-
import CircleDotIconDefault from '../../icons/CircleDotIcon.svelte';
|
|
7
|
-
import BanIconDefault from '../../icons/BanIcon.svelte';
|
|
8
|
-
import MinusIconDefault from '../../icons/MinusIcon.svelte';
|
|
9
4
|
import {
|
|
10
5
|
journeyTimelineVariants,
|
|
11
6
|
type JourneyTimelineSlots,
|
|
@@ -15,20 +10,18 @@
|
|
|
15
10
|
|
|
16
11
|
const bt = useBlocksI18n();
|
|
17
12
|
|
|
18
|
-
const CheckIcon = resolveIcon('check', CheckIconDefault);
|
|
19
|
-
const CircleDotIcon = resolveIcon('circleDot', CircleDotIconDefault);
|
|
20
|
-
const BanIcon = resolveIcon('ban', BanIconDefault);
|
|
21
|
-
const MinusIcon = resolveIcon('minus', MinusIconDefault);
|
|
22
|
-
|
|
23
13
|
let {
|
|
24
14
|
items,
|
|
25
15
|
orientation = 'vertical',
|
|
26
16
|
size = 'md',
|
|
17
|
+
detail: detailProp,
|
|
27
18
|
focusId = $bindable(),
|
|
28
19
|
defaultFocusId,
|
|
29
|
-
scrollSpy = false,
|
|
30
20
|
onFocusChange,
|
|
31
21
|
node,
|
|
22
|
+
meta,
|
|
23
|
+
marker,
|
|
24
|
+
trailing,
|
|
32
25
|
class: className = '',
|
|
33
26
|
unstyled: unstyledProp = false,
|
|
34
27
|
slotClasses: slotClassesProp = {},
|
|
@@ -39,6 +32,14 @@
|
|
|
39
32
|
const blocksConfig = getBlocksConfig();
|
|
40
33
|
const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
|
|
41
34
|
|
|
35
|
+
// Horizontal always renders the shared panel — inline expansion inside a
|
|
36
|
+
// horizontal rail has no sane geometry. `detail` only chooses for vertical.
|
|
37
|
+
const detailMode = $derived(orientation === 'horizontal' ? 'panel' : (detailProp ?? 'inline'));
|
|
38
|
+
|
|
39
|
+
// The chronicle axis renders as soon as any node carries `meta` (or a rich
|
|
40
|
+
// `meta` snippet is provided).
|
|
41
|
+
const hasMetaRail = $derived(!!meta || items.some((it) => it.meta !== undefined));
|
|
42
|
+
|
|
42
43
|
// --- focus state (controlled via bind:focusId, else internal) -------------
|
|
43
44
|
const focusableItems = $derived(items.filter((it) => it.focusable !== false));
|
|
44
45
|
const isFocusable = (id: string | undefined) =>
|
|
@@ -52,17 +53,17 @@
|
|
|
52
53
|
);
|
|
53
54
|
|
|
54
55
|
let internalFocusId = $state<string | undefined>(undefined);
|
|
55
|
-
// A stale internal focus (the
|
|
56
|
-
// default so a node is always
|
|
57
|
-
// (write-strict) and only surfaced via the dev warning below.
|
|
56
|
+
// A stale internal focus (the focused node was removed) falls back to the
|
|
57
|
+
// default so a node is always in focus. A controlled `focusId` is honoured
|
|
58
|
+
// as-is (write-strict) and only surfaced via the dev warning below.
|
|
58
59
|
const activeFocusId = $derived(
|
|
59
60
|
focusId !== undefined ? focusId : isFocusable(internalFocusId) ? internalFocusId : defaultFocus
|
|
60
61
|
);
|
|
61
62
|
|
|
62
63
|
// The roving-tabindex target: the last keyboard/click-touched node, else the
|
|
63
|
-
//
|
|
64
|
-
// focus without
|
|
65
|
-
// against
|
|
64
|
+
// focused node. Kept distinct from `activeFocusId` so arrow keys can move DOM
|
|
65
|
+
// focus without changing the focused node (that happens on Enter/Space/click).
|
|
66
|
+
// Guarded against removed nodes so the tab stop never lands on nothing.
|
|
66
67
|
let rovingId = $state<string | undefined>(undefined);
|
|
67
68
|
// Every candidate is validated against the live focusable set (not just the
|
|
68
69
|
// first non-undefined one), so a stale roving/active id — or a bad controlled
|
|
@@ -77,7 +78,7 @@
|
|
|
77
78
|
if (!import.meta.env?.DEV || items.length === 0) return;
|
|
78
79
|
if (focusId !== undefined && !isFocusable(focusId)) {
|
|
79
80
|
console.warn(
|
|
80
|
-
`[JourneyTimeline] focusId "${focusId}" matches no focusable node —
|
|
81
|
+
`[JourneyTimeline] focusId "${focusId}" matches no focusable node — no node shows its detail.`
|
|
81
82
|
);
|
|
82
83
|
}
|
|
83
84
|
if (focusId === undefined && defaultFocusId !== undefined && !isFocusable(defaultFocusId)) {
|
|
@@ -87,22 +88,97 @@
|
|
|
87
88
|
}
|
|
88
89
|
if (!node && focusableItems.length > 0) {
|
|
89
90
|
console.warn(
|
|
90
|
-
'[JourneyTimeline] focusable nodes render as
|
|
91
|
+
'[JourneyTimeline] focusable nodes render as buttons but no `node` snippet was provided. Pass a `node` snippet or set focusable:false on nodes that carry no detail.'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (orientation === 'horizontal' && detailProp === 'inline') {
|
|
95
|
+
console.warn(
|
|
96
|
+
'[JourneyTimeline] detail="inline" is ignored for orientation="horizontal" — the horizontal rail always renders the shared panel.'
|
|
91
97
|
);
|
|
92
98
|
}
|
|
93
99
|
});
|
|
94
100
|
|
|
95
|
-
function setFocus(id: string) {
|
|
101
|
+
function setFocus(id: string): boolean {
|
|
96
102
|
const target = items.find((it) => it.id === id);
|
|
97
|
-
if (!target || target.focusable === false || id === activeFocusId) return;
|
|
103
|
+
if (!target || target.focusable === false || id === activeFocusId) return false;
|
|
98
104
|
if (focusId !== undefined) focusId = id;
|
|
99
105
|
else internalFocusId = id;
|
|
100
106
|
onFocusChange?.(id);
|
|
107
|
+
return true;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
function activate(item: JourneyNode) {
|
|
104
111
|
rovingId = item.id;
|
|
105
|
-
setFocus(item.id);
|
|
112
|
+
if (setFocus(item.id)) pinFocusAnchor(item.id);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// --- focus anchor pinning --------------------------------------------------
|
|
116
|
+
// When activating a node below the currently open card, that card's collapse
|
|
117
|
+
// would yank the freshly activated header up the page. While the height
|
|
118
|
+
// transition runs, counter-scroll each frame so the activated header stays
|
|
119
|
+
// visually stationary — the context moves, the focus point doesn't. Real user
|
|
120
|
+
// scroll input cancels the pin immediately. Inline vertical detail only; the
|
|
121
|
+
// panel modes never change geometry.
|
|
122
|
+
// Only one pin may drive the scroll at a time: activating another node while
|
|
123
|
+
// a pin is in flight cancels it first (two loops anchored to different
|
|
124
|
+
// headers would fight over the scroll position frame by frame).
|
|
125
|
+
let cancelActivePin: (() => void) | undefined;
|
|
126
|
+
function pinFocusAnchor(id: string) {
|
|
127
|
+
if (orientation !== 'vertical' || detailMode !== 'inline' || !node || !rootRef) return;
|
|
128
|
+
cancelActivePin?.();
|
|
129
|
+
const anchor = rootRef.querySelector<HTMLElement>(
|
|
130
|
+
`[data-journey-trigger][data-node-id="${id}"]`
|
|
131
|
+
);
|
|
132
|
+
if (!anchor) return;
|
|
133
|
+
|
|
134
|
+
// Nearest scrollable ancestor, else the window.
|
|
135
|
+
let scroller: HTMLElement | null = rootRef.parentElement;
|
|
136
|
+
while (scroller) {
|
|
137
|
+
const style = getComputedStyle(scroller);
|
|
138
|
+
if (/(auto|scroll)/.test(style.overflowY) && scroller.scrollHeight > scroller.clientHeight)
|
|
139
|
+
break;
|
|
140
|
+
scroller = scroller.parentElement;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Pin for as long as the grid-rows transition runs (motion tokens collapse
|
|
144
|
+
// to 1ms under prefers-reduced-motion, so the pin is a single correction).
|
|
145
|
+
const detailEl = rootRef.querySelector<HTMLElement>('[data-journey-detail]');
|
|
146
|
+
const raw = detailEl ? getComputedStyle(detailEl).transitionDuration.split(',')[0].trim() : '';
|
|
147
|
+
const parsed = raw.endsWith('ms')
|
|
148
|
+
? Number.parseFloat(raw)
|
|
149
|
+
: raw.endsWith('s')
|
|
150
|
+
? Number.parseFloat(raw) * 1000
|
|
151
|
+
: Number.NaN;
|
|
152
|
+
const duration = Math.min(Number.isFinite(parsed) ? parsed + 80 : 380, 600);
|
|
153
|
+
|
|
154
|
+
const startTop = anchor.getBoundingClientRect().top;
|
|
155
|
+
const start = performance.now();
|
|
156
|
+
let cancelled = false;
|
|
157
|
+
const cancel = () => {
|
|
158
|
+
cancelled = true;
|
|
159
|
+
};
|
|
160
|
+
cancelActivePin = cancel;
|
|
161
|
+
window.addEventListener('wheel', cancel, { passive: true, capture: true });
|
|
162
|
+
window.addEventListener('touchmove', cancel, { passive: true, capture: true });
|
|
163
|
+
const cleanup = () => {
|
|
164
|
+
window.removeEventListener('wheel', cancel, { capture: true });
|
|
165
|
+
window.removeEventListener('touchmove', cancel, { capture: true });
|
|
166
|
+
if (cancelActivePin === cancel) cancelActivePin = undefined;
|
|
167
|
+
};
|
|
168
|
+
const step = () => {
|
|
169
|
+
if (cancelled || !anchor.isConnected) {
|
|
170
|
+
cleanup();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const drift = anchor.getBoundingClientRect().top - startTop;
|
|
174
|
+
if (drift !== 0) {
|
|
175
|
+
if (scroller) scroller.scrollTop += drift;
|
|
176
|
+
else window.scrollBy(0, drift);
|
|
177
|
+
}
|
|
178
|
+
if (performance.now() - start < duration) requestAnimationFrame(step);
|
|
179
|
+
else cleanup();
|
|
180
|
+
};
|
|
181
|
+
requestAnimationFrame(step);
|
|
106
182
|
}
|
|
107
183
|
|
|
108
184
|
// --- keyboard roving navigation -------------------------------------------
|
|
@@ -161,47 +237,13 @@
|
|
|
161
237
|
}
|
|
162
238
|
}
|
|
163
239
|
|
|
164
|
-
// --- scroll-spy (optional) -------------------------------------------------
|
|
165
|
-
$effect(() => {
|
|
166
|
-
if (!scrollSpy || !rootRef) return;
|
|
167
|
-
// Re-subscribe when the node set changes (reading `items` makes it a dep).
|
|
168
|
-
const currentItems = items;
|
|
169
|
-
const nodeEls = Array.from(rootRef.querySelectorAll<HTMLElement>('[data-journey-node]'));
|
|
170
|
-
if (nodeEls.length === 0) return;
|
|
171
|
-
|
|
172
|
-
const visible = new Set<string>();
|
|
173
|
-
// `observe()` delivers a synthetic initial callback for every target before
|
|
174
|
-
// any scrolling. Seed `visible` from it but do NOT act — otherwise mounting
|
|
175
|
-
// would clobber the resolved default focus (or a controlled focusId) and fire
|
|
176
|
-
// a spurious onFocusChange. Scroll-spy only *follows* real scrolling.
|
|
177
|
-
let seeded = false;
|
|
178
|
-
const observer = new IntersectionObserver(
|
|
179
|
-
(entries) => {
|
|
180
|
-
for (const entry of entries) {
|
|
181
|
-
const id = (entry.target as HTMLElement).dataset.nodeId;
|
|
182
|
-
if (!id) continue;
|
|
183
|
-
if (entry.isIntersecting) visible.add(id);
|
|
184
|
-
else visible.delete(id);
|
|
185
|
-
}
|
|
186
|
-
if (!seeded) {
|
|
187
|
-
seeded = true;
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const topmost = currentItems.find((it) => it.focusable !== false && visible.has(it.id));
|
|
191
|
-
if (topmost) setFocus(topmost.id);
|
|
192
|
-
},
|
|
193
|
-
// A node counts as "current" while it sits in the top 40% of the viewport.
|
|
194
|
-
{ rootMargin: '0px 0px -60% 0px', threshold: 0 }
|
|
195
|
-
);
|
|
196
|
-
for (const el of nodeEls) observer.observe(el);
|
|
197
|
-
return () => observer.disconnect();
|
|
198
|
-
});
|
|
199
|
-
|
|
200
240
|
// --- styling ---------------------------------------------------------------
|
|
201
241
|
type Styles = ReturnType<typeof journeyTimelineVariants>;
|
|
202
242
|
|
|
203
243
|
const containerStyles = $derived(
|
|
204
|
-
unstyled
|
|
244
|
+
unstyled
|
|
245
|
+
? null
|
|
246
|
+
: journeyTimelineVariants({ orientation, size, detail: detailMode, withMeta: hasMetaRail })
|
|
205
247
|
);
|
|
206
248
|
|
|
207
249
|
function nodeStyles(item: JourneyNode, focused: boolean): Styles | null {
|
|
@@ -210,11 +252,14 @@
|
|
|
210
252
|
: journeyTimelineVariants({
|
|
211
253
|
orientation,
|
|
212
254
|
size,
|
|
255
|
+
detail: detailMode,
|
|
256
|
+
withMeta: hasMetaRail,
|
|
213
257
|
status: item.status,
|
|
214
258
|
focused,
|
|
215
259
|
interactive: item.focusable !== false,
|
|
216
260
|
// The connector leaving a completed node reads as "travelled".
|
|
217
|
-
|
|
261
|
+
travelled: item.status === 'complete',
|
|
262
|
+
connectorStyle: item.connector ?? 'solid'
|
|
218
263
|
});
|
|
219
264
|
}
|
|
220
265
|
|
|
@@ -235,7 +280,6 @@
|
|
|
235
280
|
}
|
|
236
281
|
|
|
237
282
|
// --- misc derived ----------------------------------------------------------
|
|
238
|
-
const iconSize = $derived(size === 'sm' ? 14 : size === 'lg' ? 20 : 16);
|
|
239
283
|
const focusedItem = $derived(
|
|
240
284
|
items.find((it) => it.focusable !== false && it.id === activeFocusId)
|
|
241
285
|
);
|
|
@@ -250,21 +294,15 @@
|
|
|
250
294
|
}
|
|
251
295
|
</script>
|
|
252
296
|
|
|
253
|
-
|
|
254
|
-
{#
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
<CircleDotIcon size={iconSize} />
|
|
259
|
-
{:else if item.status === 'blocked'}
|
|
260
|
-
<BanIcon size={iconSize} />
|
|
261
|
-
{:else if item.status === 'skipped'}
|
|
262
|
-
<MinusIcon size={iconSize} />
|
|
297
|
+
{#snippet metaCell(item: JourneyNode, styles: Styles | null)}
|
|
298
|
+
{#if meta}
|
|
299
|
+
{@render meta(item)}
|
|
300
|
+
{:else if item.meta !== undefined}
|
|
301
|
+
<span class={sc(styles, 'meta')}>{item.meta}</span>
|
|
263
302
|
{/if}
|
|
264
303
|
{/snippet}
|
|
265
304
|
|
|
266
|
-
{#snippet
|
|
267
|
-
<span class={sc(styles, 'marker')}>{@render markerGlyph(item)}</span>
|
|
305
|
+
{#snippet labels(item: JourneyNode, styles: Styles | null)}
|
|
268
306
|
<span class={sc(styles, 'labelGroup')}>
|
|
269
307
|
<span class={sc(styles, 'title')}>{item.title}</span>
|
|
270
308
|
{#if item.subtitle}
|
|
@@ -274,6 +312,25 @@
|
|
|
274
312
|
<span class="sr-only">{statusLabel(item.status)}</span>
|
|
275
313
|
{/snippet}
|
|
276
314
|
|
|
315
|
+
{#snippet markerDot(item: JourneyNode, styles: Styles | null)}
|
|
316
|
+
<!-- Decorative either way: the status is announced via the sr-only label,
|
|
317
|
+
so custom glyph content never needs to be readable. -->
|
|
318
|
+
<span class={sc(styles, 'marker')} data-journey-marker="" aria-hidden="true">
|
|
319
|
+
{#if marker}{@render marker(item)}{/if}
|
|
320
|
+
</span>
|
|
321
|
+
{/snippet}
|
|
322
|
+
|
|
323
|
+
{#snippet headerRow(item: JourneyNode, index: number, focused: boolean, styles: Styles | null)}
|
|
324
|
+
<div class={sc(styles, 'header')}>
|
|
325
|
+
{@render trigger(item, index, focused, styles)}
|
|
326
|
+
{#if trailing}
|
|
327
|
+
<div class={sc(styles, 'trailing')} data-journey-trailing="">
|
|
328
|
+
{@render trailing(item)}
|
|
329
|
+
</div>
|
|
330
|
+
{/if}
|
|
331
|
+
</div>
|
|
332
|
+
{/snippet}
|
|
333
|
+
|
|
277
334
|
{#snippet trigger(item: JourneyNode, index: number, focused: boolean, styles: Styles | null)}
|
|
278
335
|
{@const interactive = item.focusable !== false}
|
|
279
336
|
{#if interactive}
|
|
@@ -285,17 +342,19 @@
|
|
|
285
342
|
tabindex={item.id === tabbableId ? 0 : -1}
|
|
286
343
|
aria-expanded={node ? focused : undefined}
|
|
287
344
|
aria-controls={node
|
|
288
|
-
?
|
|
289
|
-
?
|
|
345
|
+
? detailMode === 'panel'
|
|
346
|
+
? // Reference the shared readout only while it actually renders — a
|
|
347
|
+
// bad controlled focusId leaves no panel to point at.
|
|
348
|
+
focusedItem && panelId
|
|
290
349
|
: detailDomId(index)
|
|
291
350
|
: undefined}
|
|
292
351
|
onclick={() => activate(item)}
|
|
293
352
|
>
|
|
294
|
-
{@render
|
|
353
|
+
{@render labels(item, styles)}
|
|
295
354
|
</button>
|
|
296
355
|
{:else}
|
|
297
356
|
<div class={sc(styles, 'trigger')} data-node-id={item.id}>
|
|
298
|
-
{@render
|
|
357
|
+
{@render labels(item, styles)}
|
|
299
358
|
</div>
|
|
300
359
|
{/if}
|
|
301
360
|
{/snippet}
|
|
@@ -304,6 +363,7 @@
|
|
|
304
363
|
bind:this={rootRef}
|
|
305
364
|
class={sc(containerStyles, 'base', className)}
|
|
306
365
|
data-orientation={orientation}
|
|
366
|
+
data-detail={detailMode}
|
|
307
367
|
{...restProps}
|
|
308
368
|
>
|
|
309
369
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
@@ -314,6 +374,7 @@
|
|
|
314
374
|
>
|
|
315
375
|
{#each items as item, index (item.id)}
|
|
316
376
|
{@const focused = item.focusable !== false && item.id === activeFocusId}
|
|
377
|
+
{@const last = index === items.length - 1}
|
|
317
378
|
{@const styles = nodeStyles(item, focused)}
|
|
318
379
|
{#if orientation === 'horizontal'}
|
|
319
380
|
<li
|
|
@@ -322,8 +383,34 @@
|
|
|
322
383
|
data-node-id={item.id}
|
|
323
384
|
aria-current={item.status === 'active' ? 'step' : undefined}
|
|
324
385
|
>
|
|
325
|
-
{
|
|
326
|
-
|
|
386
|
+
{#if hasMetaRail}
|
|
387
|
+
<div class={sc(styles, 'metaColumn')}>
|
|
388
|
+
{#if meta || item.meta !== undefined}
|
|
389
|
+
{@render metaCell(item, styles)}
|
|
390
|
+
{:else}
|
|
391
|
+
<!-- Empty placeholder keeps every station's meta row the same
|
|
392
|
+
height, so markers and titles stay on one baseline. -->
|
|
393
|
+
<span class={sc(styles, 'meta')} aria-hidden="true"> </span>
|
|
394
|
+
{/if}
|
|
395
|
+
</div>
|
|
396
|
+
{/if}
|
|
397
|
+
<!-- DOM order header → spine (visual order flipped via order-*):
|
|
398
|
+
a segment label is announced after the node it departs from. -->
|
|
399
|
+
{@render headerRow(item, index, focused, styles)}
|
|
400
|
+
<div class={sc(styles, 'markerColumn')}>
|
|
401
|
+
{@render markerDot(item, styles)}
|
|
402
|
+
{#if !last}
|
|
403
|
+
<span data-journey-connector="" class={sc(styles, 'connector')} aria-hidden="true"
|
|
404
|
+
></span>
|
|
405
|
+
{#if item.segmentLabel}
|
|
406
|
+
<span class={sc(styles, 'segment')} data-journey-segment="">
|
|
407
|
+
{item.segmentLabel}
|
|
408
|
+
</span>
|
|
409
|
+
<span data-journey-connector="" class={sc(styles, 'connector')} aria-hidden="true"
|
|
410
|
+
></span>
|
|
411
|
+
{/if}
|
|
412
|
+
{/if}
|
|
413
|
+
</div>
|
|
327
414
|
</li>
|
|
328
415
|
{:else}
|
|
329
416
|
<li
|
|
@@ -332,25 +419,41 @@
|
|
|
332
419
|
data-node-id={item.id}
|
|
333
420
|
aria-current={item.status === 'active' ? 'step' : undefined}
|
|
334
421
|
>
|
|
335
|
-
{
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
<span data-journey-connector class={sc(styles, 'connector')} aria-hidden="true"
|
|
339
|
-
></span>
|
|
422
|
+
{#if hasMetaRail}
|
|
423
|
+
<div class={sc(styles, 'metaColumn')}>
|
|
424
|
+
{@render metaCell(item, styles)}
|
|
340
425
|
</div>
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
426
|
+
{/if}
|
|
427
|
+
<div class={sc(styles, 'markerColumn')}>
|
|
428
|
+
{@render markerDot(item, styles)}
|
|
429
|
+
{#if !last}
|
|
430
|
+
<span data-journey-connector="" class={sc(styles, 'connector')} aria-hidden="true"
|
|
431
|
+
></span>
|
|
432
|
+
{/if}
|
|
433
|
+
</div>
|
|
434
|
+
<div class={sc(styles, 'content', last && 'pb-0')}>
|
|
435
|
+
<div class={sc(styles, 'card')}>
|
|
436
|
+
{@render headerRow(item, index, focused, styles)}
|
|
437
|
+
{#if detailMode === 'inline' && node && item.focusable !== false}
|
|
438
|
+
<div
|
|
439
|
+
id={detailDomId(index)}
|
|
440
|
+
role="region"
|
|
441
|
+
aria-label={item.title}
|
|
442
|
+
data-journey-detail=""
|
|
443
|
+
class={sc(styles, 'detail')}
|
|
444
|
+
style="grid-template-rows: {focused ? '1fr' : '0fr'}"
|
|
445
|
+
>
|
|
446
|
+
<div class={sc(styles, 'detailInner')}>
|
|
447
|
+
<div class={sc(styles, 'detailContent')}>
|
|
448
|
+
{#if focused}{@render node(item)}{/if}
|
|
449
|
+
</div>
|
|
352
450
|
</div>
|
|
353
451
|
</div>
|
|
452
|
+
{/if}
|
|
453
|
+
</div>
|
|
454
|
+
{#if item.segmentLabel && !last}
|
|
455
|
+
<div class={sc(styles, 'segment')} data-journey-segment="">
|
|
456
|
+
{item.segmentLabel}
|
|
354
457
|
</div>
|
|
355
458
|
{/if}
|
|
356
459
|
</div>
|
|
@@ -359,11 +462,12 @@
|
|
|
359
462
|
{/each}
|
|
360
463
|
</ol>
|
|
361
464
|
|
|
362
|
-
{#if
|
|
465
|
+
{#if detailMode === 'panel' && node && focusedItem}
|
|
363
466
|
<div
|
|
364
467
|
id={panelId}
|
|
365
468
|
role="region"
|
|
366
469
|
aria-label={focusedItem.title}
|
|
470
|
+
data-journey-panel=""
|
|
367
471
|
class={sc(containerStyles, 'panel')}
|
|
368
472
|
>
|
|
369
473
|
{@render node(focusedItem)}
|