@urbicon-ui/blocks 6.3.6 → 6.3.8

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.
@@ -339,175 +339,7 @@ declare const blocksTranslations: {
339
339
  };
340
340
  };
341
341
  };
342
- export declare const blocksI18n: import("@urbicon-ui/i18n").PackageI18n<{
343
- readonly accessibility: {
344
- readonly avatar: "Avatar";
345
- readonly clearInput: "Clear input";
346
- readonly clearSearch: "Clear search";
347
- readonly clearSelection: "Clear selection";
348
- readonly closeDialog: "Close dialog";
349
- readonly closeDrawer: "Close drawer";
350
- readonly dismiss: "Dismiss";
351
- readonly fileUpload: "File upload";
352
- readonly loading: "Loading";
353
- readonly maximum: "Maximum";
354
- readonly minimum: "Minimum";
355
- readonly pagination: "Page navigation";
356
- readonly progress: "Progress";
357
- readonly slider: "Slider";
358
- readonly toggle: "Toggle";
359
- readonly toggleOptions: "Toggle options";
360
- readonly removableBadge: "Removable badge";
361
- readonly removeBadge: "Remove badge";
362
- readonly removeFile: "Remove {{name}}";
363
- };
364
- readonly button: {
365
- readonly close: "Close";
366
- readonly apply: "Apply";
367
- readonly cancel: "Cancel";
368
- readonly confirm: "Confirm";
369
- };
370
- readonly menu: {
371
- readonly placeholder: "Select an option...";
372
- };
373
- readonly common: {
374
- readonly loading: "Loading...";
375
- };
376
- readonly languages: {
377
- readonly de: "Deutsch";
378
- readonly en: "English";
379
- readonly es: "Español";
380
- readonly fr: "Français";
381
- readonly it: "Italiano";
382
- readonly nl: "Nederlands";
383
- };
384
- readonly localeSwitcher: {
385
- readonly ariaLabel: "Language selection";
386
- readonly placeholder: "Select language...";
387
- };
388
- readonly pagination: {
389
- readonly next: "Next";
390
- readonly first: "First";
391
- readonly last: "Last";
392
- readonly page: "Page";
393
- readonly previous: "Previous";
394
- };
395
- readonly calendar: {
396
- readonly recurring: "Recurring event";
397
- readonly previousMonth: "Previous month";
398
- readonly nextMonth: "Next month";
399
- readonly previousWeek: "Previous week";
400
- readonly nextWeek: "Next week";
401
- readonly previousDay: "Previous day";
402
- readonly nextDay: "Next day";
403
- readonly previousYear: "Previous year";
404
- readonly nextYear: "Next year";
405
- readonly today: "Today";
406
- readonly calendarGrid: "Calendar";
407
- readonly weekdays: "Weekdays";
408
- readonly weekNumber: "Week number";
409
- readonly events: "Events";
410
- readonly noEvents: "No events";
411
- readonly showMore: "{{count}} more";
412
- readonly legend: "Legend";
413
- readonly yearView: "Year overview";
414
- readonly weekView: "Week view";
415
- readonly dayView: "Day view";
416
- readonly monthView: "Month view";
417
- readonly viewMonth: "Month";
418
- readonly viewYear: "Year";
419
- readonly viewWeek: "Week";
420
- readonly viewDay: "Day";
421
- readonly multiDayLabel: "Day {{current}} of {{total}}";
422
- readonly agendaView: "Agenda view";
423
- readonly viewAgenda: "Agenda";
424
- readonly viewSwitcher: "View mode";
425
- };
426
- readonly planner: {
427
- readonly previousWeek: "Previous week";
428
- readonly nextWeek: "Next week";
429
- readonly previousMonth: "Previous month";
430
- readonly nextMonth: "Next month";
431
- readonly previousRange: "Previous range";
432
- readonly nextRange: "Next range";
433
- readonly today: "Today";
434
- readonly grid: "Planner";
435
- readonly weekNumber: "Week number";
436
- readonly itemCount: "{{count}} items";
437
- };
438
- readonly commandPalette: {
439
- readonly noResults: "No results found.";
440
- readonly search: "Search...";
441
- readonly hints: {
442
- readonly navigate: "Navigate";
443
- readonly select: "Select";
444
- readonly close: "Close";
445
- };
446
- };
447
- readonly compositionBar: {
448
- readonly total: "Total";
449
- readonly summary: "Composition: {{total}} ({{count}} shares) — {{parts}}";
450
- readonly share: "Share";
451
- readonly value: "Value";
452
- readonly percent: "Percent";
453
- readonly remaining: "remaining";
454
- };
455
- readonly guide: {
456
- readonly next: "Next";
457
- readonly previous: "Back";
458
- readonly skip: "Skip tour";
459
- readonly done: "Done";
460
- readonly step: "Step {{current}} of {{total}}";
461
- readonly tour: "Guided tour";
462
- readonly close: "Close";
463
- readonly openHelp: "Help";
464
- readonly backToList: "All topics";
465
- readonly info: "More information";
466
- readonly infoAbout: "More information about {{label}}";
467
- readonly dismiss: "Dismiss hint";
468
- readonly startTour: "Start the guided tour";
469
- readonly actionRequired: "Complete the highlighted action to continue";
470
- };
471
- readonly datepicker: {
472
- readonly placeholder: "Select a date...";
473
- readonly rangePlaceholder: "Select a date range...";
474
- readonly clear: "Clear selection";
475
- readonly openCalendar: "Open calendar";
476
- readonly invalidDate: "Invalid date";
477
- readonly outOfRange: "Date is outside the allowed range";
478
- readonly invalidRange: "Invalid date range";
479
- };
480
- readonly fileUpload: {
481
- readonly exists: "File already added";
482
- readonly invalidType: "File type {{type}} is not allowed";
483
- readonly tooLarge: "File exceeds {{size}} limit";
484
- readonly tooMany: "Maximum {{count}} files allowed";
485
- readonly tooSmall: "File must be at least {{size}}";
486
- };
487
- readonly sankey: {
488
- readonly summary: "Sankey diagram: {{nodes}} nodes, {{links}} links — {{flows}}";
489
- readonly source: "Source";
490
- readonly target: "Target";
491
- readonly value: "Value";
492
- };
493
- readonly slider: {
494
- readonly rangeStatus: {
495
- readonly insideRecommended: "In recommended range";
496
- readonly insideValid: "In valid range";
497
- readonly insideValidOnly: "Outside recommended range, but valid";
498
- readonly outsideValid: "Outside valid range";
499
- };
500
- };
501
- readonly themeSwitcher: {
502
- readonly lightMode: "Light mode";
503
- readonly darkMode: "Dark mode";
504
- readonly systemTheme: "System theme";
505
- };
506
- readonly time: {
507
- readonly ago: "{{value}} {{unit}} ago";
508
- readonly now: "now";
509
- };
510
- }>;
342
+ export declare const blocksI18n: import("@urbicon-ui/i18n").PackageI18n<import("@urbicon-ui/i18n").Translations>;
511
343
  /**
512
344
  * Context-scoped translation hook for the blocks package. Call during component
513
345
  * initialisation, then use the returned `t` (conventionally aliased `bt`):
@@ -520,175 +352,7 @@ export declare const blocksI18n: import("@urbicon-ui/i18n").PackageI18n<{
520
352
  * Resolves against the nearest `<I18nProvider>`'s locale (or the base locale
521
353
  * when none is mounted) and re-renders reactively on locale change.
522
354
  */
523
- export declare const useBlocksI18n: () => import("@urbicon-ui/i18n").TypedTranslationFunction<{
524
- readonly accessibility: {
525
- readonly avatar: "Avatar";
526
- readonly clearInput: "Clear input";
527
- readonly clearSearch: "Clear search";
528
- readonly clearSelection: "Clear selection";
529
- readonly closeDialog: "Close dialog";
530
- readonly closeDrawer: "Close drawer";
531
- readonly dismiss: "Dismiss";
532
- readonly fileUpload: "File upload";
533
- readonly loading: "Loading";
534
- readonly maximum: "Maximum";
535
- readonly minimum: "Minimum";
536
- readonly pagination: "Page navigation";
537
- readonly progress: "Progress";
538
- readonly slider: "Slider";
539
- readonly toggle: "Toggle";
540
- readonly toggleOptions: "Toggle options";
541
- readonly removableBadge: "Removable badge";
542
- readonly removeBadge: "Remove badge";
543
- readonly removeFile: "Remove {{name}}";
544
- };
545
- readonly button: {
546
- readonly close: "Close";
547
- readonly apply: "Apply";
548
- readonly cancel: "Cancel";
549
- readonly confirm: "Confirm";
550
- };
551
- readonly menu: {
552
- readonly placeholder: "Select an option...";
553
- };
554
- readonly common: {
555
- readonly loading: "Loading...";
556
- };
557
- readonly languages: {
558
- readonly de: "Deutsch";
559
- readonly en: "English";
560
- readonly es: "Español";
561
- readonly fr: "Français";
562
- readonly it: "Italiano";
563
- readonly nl: "Nederlands";
564
- };
565
- readonly localeSwitcher: {
566
- readonly ariaLabel: "Language selection";
567
- readonly placeholder: "Select language...";
568
- };
569
- readonly pagination: {
570
- readonly next: "Next";
571
- readonly first: "First";
572
- readonly last: "Last";
573
- readonly page: "Page";
574
- readonly previous: "Previous";
575
- };
576
- readonly calendar: {
577
- readonly recurring: "Recurring event";
578
- readonly previousMonth: "Previous month";
579
- readonly nextMonth: "Next month";
580
- readonly previousWeek: "Previous week";
581
- readonly nextWeek: "Next week";
582
- readonly previousDay: "Previous day";
583
- readonly nextDay: "Next day";
584
- readonly previousYear: "Previous year";
585
- readonly nextYear: "Next year";
586
- readonly today: "Today";
587
- readonly calendarGrid: "Calendar";
588
- readonly weekdays: "Weekdays";
589
- readonly weekNumber: "Week number";
590
- readonly events: "Events";
591
- readonly noEvents: "No events";
592
- readonly showMore: "{{count}} more";
593
- readonly legend: "Legend";
594
- readonly yearView: "Year overview";
595
- readonly weekView: "Week view";
596
- readonly dayView: "Day view";
597
- readonly monthView: "Month view";
598
- readonly viewMonth: "Month";
599
- readonly viewYear: "Year";
600
- readonly viewWeek: "Week";
601
- readonly viewDay: "Day";
602
- readonly multiDayLabel: "Day {{current}} of {{total}}";
603
- readonly agendaView: "Agenda view";
604
- readonly viewAgenda: "Agenda";
605
- readonly viewSwitcher: "View mode";
606
- };
607
- readonly planner: {
608
- readonly previousWeek: "Previous week";
609
- readonly nextWeek: "Next week";
610
- readonly previousMonth: "Previous month";
611
- readonly nextMonth: "Next month";
612
- readonly previousRange: "Previous range";
613
- readonly nextRange: "Next range";
614
- readonly today: "Today";
615
- readonly grid: "Planner";
616
- readonly weekNumber: "Week number";
617
- readonly itemCount: "{{count}} items";
618
- };
619
- readonly commandPalette: {
620
- readonly noResults: "No results found.";
621
- readonly search: "Search...";
622
- readonly hints: {
623
- readonly navigate: "Navigate";
624
- readonly select: "Select";
625
- readonly close: "Close";
626
- };
627
- };
628
- readonly compositionBar: {
629
- readonly total: "Total";
630
- readonly summary: "Composition: {{total}} ({{count}} shares) — {{parts}}";
631
- readonly share: "Share";
632
- readonly value: "Value";
633
- readonly percent: "Percent";
634
- readonly remaining: "remaining";
635
- };
636
- readonly guide: {
637
- readonly next: "Next";
638
- readonly previous: "Back";
639
- readonly skip: "Skip tour";
640
- readonly done: "Done";
641
- readonly step: "Step {{current}} of {{total}}";
642
- readonly tour: "Guided tour";
643
- readonly close: "Close";
644
- readonly openHelp: "Help";
645
- readonly backToList: "All topics";
646
- readonly info: "More information";
647
- readonly infoAbout: "More information about {{label}}";
648
- readonly dismiss: "Dismiss hint";
649
- readonly startTour: "Start the guided tour";
650
- readonly actionRequired: "Complete the highlighted action to continue";
651
- };
652
- readonly datepicker: {
653
- readonly placeholder: "Select a date...";
654
- readonly rangePlaceholder: "Select a date range...";
655
- readonly clear: "Clear selection";
656
- readonly openCalendar: "Open calendar";
657
- readonly invalidDate: "Invalid date";
658
- readonly outOfRange: "Date is outside the allowed range";
659
- readonly invalidRange: "Invalid date range";
660
- };
661
- readonly fileUpload: {
662
- readonly exists: "File already added";
663
- readonly invalidType: "File type {{type}} is not allowed";
664
- readonly tooLarge: "File exceeds {{size}} limit";
665
- readonly tooMany: "Maximum {{count}} files allowed";
666
- readonly tooSmall: "File must be at least {{size}}";
667
- };
668
- readonly sankey: {
669
- readonly summary: "Sankey diagram: {{nodes}} nodes, {{links}} links — {{flows}}";
670
- readonly source: "Source";
671
- readonly target: "Target";
672
- readonly value: "Value";
673
- };
674
- readonly slider: {
675
- readonly rangeStatus: {
676
- readonly insideRecommended: "In recommended range";
677
- readonly insideValid: "In valid range";
678
- readonly insideValidOnly: "Outside recommended range, but valid";
679
- readonly outsideValid: "Outside valid range";
680
- };
681
- };
682
- readonly themeSwitcher: {
683
- readonly lightMode: "Light mode";
684
- readonly darkMode: "Dark mode";
685
- readonly systemTheme: "System theme";
686
- };
687
- readonly time: {
688
- readonly ago: "{{value}} {{unit}} ago";
689
- readonly now: "now";
690
- };
691
- }>;
355
+ export declare const useBlocksI18n: any;
692
356
  export declare const hasBlocksTranslation: (key: string) => boolean, getBlocksLocales: () => import("@urbicon-ui/i18n").Locale[];
693
357
  export type BlocksTranslationKey = keyof typeof enTranslations;
694
358
  export { blocksTranslations };
@@ -1,12 +1,18 @@
1
1
  <script lang="ts">
2
+ import { tick } from 'svelte';
2
3
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
3
4
  import { breadcrumbVariants, type BreadcrumbVariants } from './breadcrumb.variants';
4
- import type { BreadcrumbProps } from './index';
5
+ import type { BreadcrumbProps, BreadcrumbItem } from './index';
5
6
 
6
7
  let {
7
8
  items,
8
9
  size = 'md',
10
+ wrap = true,
9
11
  separator,
12
+ maxItems,
13
+ itemsBeforeCollapse = 1,
14
+ itemsAfterCollapse = 1,
15
+ expandLabel = 'Show all breadcrumb items',
10
16
  'aria-label': ariaLabel = 'Breadcrumb',
11
17
  class: className = '',
12
18
  unstyled: unstyledProp = false,
@@ -18,16 +24,69 @@
18
24
  const blocksConfig = getBlocksConfig();
19
25
  const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
20
26
 
21
- const variantProps: BreadcrumbVariants = $derived({ size });
27
+ const variantProps: BreadcrumbVariants = $derived({ size, wrap });
22
28
 
23
29
  const styles = $derived(breadcrumbVariants(variantProps));
24
30
 
25
31
  const slotClasses = $derived(
26
32
  resolveSlotClasses(blocksConfig, 'Breadcrumb', preset, variantProps, slotClassesProp)
27
33
  );
34
+
35
+ // Long trails collapse their middle into a single "…" affordance. Clicking
36
+ // it expands the full trail for the rest of the component's life — the
37
+ // established expandable-breadcrumb pattern. `before`/`after` clamp to sane
38
+ // bounds and the current page (last item) is always kept visible.
39
+ let expanded = $state(false);
40
+ let navEl = $state<HTMLElement>();
41
+
42
+ const before = $derived(Math.max(0, itemsBeforeCollapse));
43
+ const after = $derived(Math.max(1, itemsAfterCollapse));
44
+
45
+ type BreadcrumbEntry =
46
+ | { kind: 'item'; item: BreadcrumbItem; current: boolean }
47
+ | { kind: 'ellipsis' };
48
+
49
+ const entries = $derived.by((): BreadcrumbEntry[] => {
50
+ const last = items.length - 1;
51
+ const collapse =
52
+ maxItems != null &&
53
+ maxItems > 0 &&
54
+ !expanded &&
55
+ items.length > maxItems &&
56
+ // Only worth it when "…" actually replaces ≥2 items — folding a single
57
+ // hidden item saves no space and just adds an interaction.
58
+ items.length - before - after >= 2;
59
+
60
+ if (!collapse) {
61
+ return items.map((item, i) => ({ kind: 'item' as const, item, current: i === last }));
62
+ }
63
+
64
+ const result: BreadcrumbEntry[] = [];
65
+ for (let i = 0; i < before; i++) {
66
+ result.push({ kind: 'item', item: items[i], current: false });
67
+ }
68
+ result.push({ kind: 'ellipsis' });
69
+ for (let i = items.length - after; i < items.length; i++) {
70
+ result.push({ kind: 'item', item: items[i], current: i === last });
71
+ }
72
+ return result;
73
+ });
74
+
75
+ function expand() {
76
+ expanded = true;
77
+ // Keep keyboard focus inside the trail: land on the first item that was
78
+ // hidden (now revealed) rather than dropping to the top of the page when
79
+ // the "…" button unmounts. Head items are links 0…before-1, so the first
80
+ // revealed item is link index `before`.
81
+ const target = before;
82
+ tick().then(() => {
83
+ navEl?.querySelectorAll<HTMLAnchorElement>('a')[target]?.focus();
84
+ });
85
+ }
28
86
  </script>
29
87
 
30
88
  <nav
89
+ bind:this={navEl}
31
90
  class={unstyled
32
91
  ? [slotClasses?.nav, className].filter(Boolean).join(' ')
33
92
  : styles.nav({ class: [slotClasses?.nav, className] })}
@@ -35,26 +94,39 @@
35
94
  {...restProps}
36
95
  >
37
96
  <ol class={unstyled ? (slotClasses?.list ?? '') : styles.list({ class: slotClasses?.list })}>
38
- {#each items as item, i (`${item.label}-${i}`)}
97
+ {#each entries as entry, i (entry.kind === 'ellipsis' ? 'ellipsis' : `${entry.item.label}-${i}`)}
39
98
  <li class={unstyled ? (slotClasses?.item ?? '') : styles.item({ class: slotClasses?.item })}>
40
- {#if i === items.length - 1}
99
+ {#if entry.kind === 'ellipsis'}
100
+ <button
101
+ type="button"
102
+ class={unstyled
103
+ ? (slotClasses?.ellipsis ?? '')
104
+ : styles.ellipsis({ class: slotClasses?.ellipsis })}
105
+ aria-label={expandLabel}
106
+ onclick={expand}
107
+ >
108
+
109
+ </button>
110
+ {:else if entry.current}
41
111
  <span
42
112
  class={unstyled
43
113
  ? (slotClasses?.currentPage ?? '')
44
114
  : styles.currentPage({ class: slotClasses?.currentPage })}
45
- aria-current="page">{item.label}</span
115
+ aria-current="page">{entry.item.label}</span
46
116
  >
47
117
  {:else}
48
118
  <!-- eslint-disable svelte/no-navigation-without-resolve -- BreadcrumbItem.href is opaque to the library -->
49
119
  <a
50
- href={item.href}
120
+ href={entry.item.href}
51
121
  class={unstyled ? (slotClasses?.link ?? '') : styles.link({ class: slotClasses?.link })}
52
- aria-label={item['aria-label']}
53
- onclick={item.onclick}
122
+ aria-label={entry.item['aria-label']}
123
+ onclick={entry.item.onclick}
54
124
  >
55
- {item.label}
125
+ {entry.item.label}
56
126
  </a>
57
127
  <!-- eslint-enable svelte/no-navigation-without-resolve -->
128
+ {/if}
129
+ {#if i < entries.length - 1}
58
130
  <span
59
131
  class={unstyled
60
132
  ? (slotClasses?.separator ?? '')
@@ -14,39 +14,62 @@ export declare const breadcrumbVariants: (props?: import("../../utils/variants.j
14
14
  separator: string;
15
15
  };
16
16
  };
17
+ wrap: {
18
+ true: {
19
+ list: string;
20
+ };
21
+ false: {
22
+ nav: string;
23
+ list: string;
24
+ currentPage: string;
25
+ };
26
+ };
17
27
  }> | undefined) => {
18
28
  nav: (props?: ({
19
29
  size?: "sm" | "md" | "lg" | undefined;
30
+ wrap?: boolean | undefined;
20
31
  } & {
21
32
  class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
22
33
  className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
23
34
  }) | undefined) => string;
24
35
  list: (props?: ({
25
36
  size?: "sm" | "md" | "lg" | undefined;
37
+ wrap?: boolean | undefined;
26
38
  } & {
27
39
  class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
28
40
  className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
29
41
  }) | undefined) => string;
30
42
  item: (props?: ({
31
43
  size?: "sm" | "md" | "lg" | undefined;
44
+ wrap?: boolean | undefined;
32
45
  } & {
33
46
  class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
34
47
  className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
35
48
  }) | undefined) => string;
36
49
  link: (props?: ({
37
50
  size?: "sm" | "md" | "lg" | undefined;
51
+ wrap?: boolean | undefined;
38
52
  } & {
39
53
  class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
40
54
  className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
41
55
  }) | undefined) => string;
42
56
  currentPage: (props?: ({
43
57
  size?: "sm" | "md" | "lg" | undefined;
58
+ wrap?: boolean | undefined;
59
+ } & {
60
+ class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
61
+ className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
62
+ }) | undefined) => string;
63
+ ellipsis: (props?: ({
64
+ size?: "sm" | "md" | "lg" | undefined;
65
+ wrap?: boolean | undefined;
44
66
  } & {
45
67
  class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
46
68
  className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
47
69
  }) | undefined) => string;
48
70
  separator: (props?: ({
49
71
  size?: "sm" | "md" | "lg" | undefined;
72
+ wrap?: boolean | undefined;
50
73
  } & {
51
74
  class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
52
75
  className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
@@ -2,7 +2,7 @@ import { tv } from '../../utils/variants.js';
2
2
  export const breadcrumbVariants = tv({
3
3
  slots: {
4
4
  nav: 'flex items-center',
5
- list: 'flex items-center flex-wrap',
5
+ list: 'flex items-center',
6
6
  item: 'inline-flex items-center',
7
7
  link: [
8
8
  'text-text-secondary hover:text-text-primary',
@@ -11,6 +11,14 @@ export const breadcrumbVariants = tv({
11
11
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:rounded-sm'
12
12
  ],
13
13
  currentPage: 'font-medium text-text-primary truncate max-w-48',
14
+ // Collapse affordance: the "…" button shown in place of the folded middle
15
+ // items. Styled as a quiet link; expands the full trail on click.
16
+ ellipsis: [
17
+ 'inline-flex items-center justify-center rounded-sm px-1',
18
+ 'text-text-tertiary hover:text-text-primary cursor-pointer',
19
+ 'transition-colors duration-[var(--blocks-duration-fast)]',
20
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50'
21
+ ],
14
22
  separator: 'mx-2 text-text-tertiary select-none'
15
23
  },
16
24
  variants: {
@@ -27,9 +35,29 @@ export const breadcrumbVariants = tv({
27
35
  list: 'text-base gap-1',
28
36
  separator: 'mx-2'
29
37
  }
38
+ },
39
+ // Overflow strategy when the trail runs out of horizontal room.
40
+ // `wrap` (default): the trail wraps to multiple lines — the standard
41
+ // free-flowing breadcrumb. `false`: the trail stays on one line and the
42
+ // current page (always the last item) truncates while the ancestor links
43
+ // hold their width — for tight single-line bars (sticky headers, toolbars)
44
+ // where the page name is the part worth shortening. Pair with `maxItems`
45
+ // to fold the middle into a "…" before the current page has to truncate.
46
+ wrap: {
47
+ true: {
48
+ list: 'flex-wrap'
49
+ },
50
+ false: {
51
+ nav: 'min-w-0',
52
+ list: 'flex-nowrap min-w-0 [&>li:not(:last-child)]:shrink-0 [&>li:last-child]:min-w-0 [&>li:last-child]:shrink',
53
+ // Let the current page consume the available width and truncate, rather
54
+ // than capping at the wrap-mode max-width.
55
+ currentPage: 'max-w-none'
56
+ }
30
57
  }
31
58
  },
32
59
  defaultVariants: {
33
- size: 'md'
60
+ size: 'md',
61
+ wrap: true
34
62
  }
35
63
  });
@@ -41,14 +41,39 @@ export interface BreadcrumbItem {
41
41
  * {#snippet separator()}<ChevronRight size={14} />{/snippet}
42
42
  * </Breadcrumb>
43
43
  * ```
44
+ *
45
+ * @example Collapse a long trail — middle items fold into an expandable "…"
46
+ * ```svelte
47
+ * <Breadcrumb items={deepTrail} maxItems={4} />
48
+ * ```
44
49
  */
45
50
  export interface BreadcrumbProps extends BreadcrumbVariants, Omit<HTMLAttributes<HTMLElement>, 'children'> {
46
51
  /** Ordered breadcrumb items (last item is current page) */
47
52
  items: BreadcrumbItem[];
48
53
  /** Size @default 'md' */
49
54
  size?: 'sm' | 'md' | 'lg';
55
+ /**
56
+ * Let the trail wrap onto multiple lines (`true`, default) or keep it on a
57
+ * single line where the current page truncates and the ancestor links hold
58
+ * their width (`false`). Use `false` for tight single-line bars such as a
59
+ * sticky header or toolbar. @default true
60
+ */
61
+ wrap?: boolean;
50
62
  /** Custom separator snippet (default: "/") */
51
63
  separator?: Snippet;
64
+ /**
65
+ * Collapse the trail when it has more than this many items: the middle items
66
+ * fold into a single "…" button that expands the full trail on click. The
67
+ * first `itemsBeforeCollapse` and last `itemsAfterCollapse` items stay
68
+ * visible (the current page is always kept). Omit to never collapse.
69
+ */
70
+ maxItems?: number;
71
+ /** Leading items kept visible when collapsed. @default 1 */
72
+ itemsBeforeCollapse?: number;
73
+ /** Trailing items kept visible when collapsed; the current page is always included. @default 1 */
74
+ itemsAfterCollapse?: number;
75
+ /** Accessible label for the "…" button that expands a collapsed trail. @default 'Show all breadcrumb items' */
76
+ expandLabel?: string;
52
77
  /** Accessible label for the nav element @default 'Breadcrumb' */
53
78
  'aria-label'?: string;
54
79
  /** Custom CSS class */