@urbicon-ui/blocks 6.3.3 → 6.3.7
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/components/CommandPalette/commandPalette.variants.js +3 -1
- package/dist/i18n/index.d.ts +4 -336
- package/dist/primitives/Breadcrumb/Breadcrumb.svelte +79 -8
- package/dist/primitives/Breadcrumb/breadcrumb.variants.d.ts +6 -0
- package/dist/primitives/Breadcrumb/breadcrumb.variants.js +8 -0
- package/dist/primitives/Breadcrumb/index.d.ts +18 -0
- package/dist/primitives/Combobox/Combobox.svelte +49 -8
- package/dist/primitives/Combobox/combobox.variants.d.ts +9 -0
- package/dist/primitives/Combobox/combobox.variants.js +23 -8
- package/dist/primitives/Input/input.variants.js +6 -2
- package/dist/primitives/Menu/Menu.svelte +43 -12
- package/dist/primitives/Popover/Popover.svelte +14 -12
- package/dist/primitives/SegmentGroup/SegmentGroup.svelte +67 -0
- package/dist/primitives/SegmentGroup/index.d.ts +11 -1
- package/dist/primitives/SegmentGroup/segmentgroup.variants.js +15 -1
- package/dist/primitives/Select/Select.svelte +63 -42
- package/dist/primitives/Select/select.variants.js +6 -2
- package/dist/primitives/Textarea/textarea.variants.js +3 -1
- 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/dist/utils/floating.js +30 -2
- package/dist/utils/use-floating-listbox.svelte.js +17 -9
- package/package.json +9 -9
|
@@ -8,7 +8,9 @@ export const commandPaletteVariants = tv({
|
|
|
8
8
|
],
|
|
9
9
|
inputWrapper: ['flex items-center gap-3 border-b border-border-hairline px-4'],
|
|
10
10
|
input: [
|
|
11
|
-
|
|
11
|
+
// `pointer-coarse:text-base` floors the search field to 16px on touch
|
|
12
|
+
// devices — below 16px iOS Safari auto-zooms on focus and never restores.
|
|
13
|
+
'w-full border-0 bg-transparent py-3 text-sm pointer-coarse:text-base',
|
|
12
14
|
'text-text-primary placeholder:text-text-quaternary focus:outline-none'
|
|
13
15
|
],
|
|
14
16
|
inputIcon: 'h-4 w-4 shrink-0 text-text-tertiary',
|
package/dist/i18n/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ declare const blocksTranslations: {
|
|
|
17
17
|
readonly progress: "Progress";
|
|
18
18
|
readonly slider: "Slider";
|
|
19
19
|
readonly toggle: "Toggle";
|
|
20
|
+
readonly toggleOptions: "Toggle options";
|
|
20
21
|
readonly removableBadge: "Removable badge";
|
|
21
22
|
readonly removeBadge: "Remove badge";
|
|
22
23
|
readonly removeFile: "Remove {{name}}";
|
|
@@ -185,6 +186,7 @@ declare const blocksTranslations: {
|
|
|
185
186
|
readonly progress: "Fortschritt";
|
|
186
187
|
readonly slider: "Schieberegler";
|
|
187
188
|
readonly toggle: "Umschalter";
|
|
189
|
+
readonly toggleOptions: "Optionen umschalten";
|
|
188
190
|
readonly removableBadge: "Entfernbarer Badge";
|
|
189
191
|
readonly removeBadge: "Badge entfernen";
|
|
190
192
|
readonly removeFile: "{{name}} entfernen";
|
|
@@ -337,174 +339,7 @@ declare const blocksTranslations: {
|
|
|
337
339
|
};
|
|
338
340
|
};
|
|
339
341
|
};
|
|
340
|
-
export declare const blocksI18n: import("@urbicon-ui/i18n").PackageI18n<
|
|
341
|
-
readonly accessibility: {
|
|
342
|
-
readonly avatar: "Avatar";
|
|
343
|
-
readonly clearInput: "Clear input";
|
|
344
|
-
readonly clearSearch: "Clear search";
|
|
345
|
-
readonly clearSelection: "Clear selection";
|
|
346
|
-
readonly closeDialog: "Close dialog";
|
|
347
|
-
readonly closeDrawer: "Close drawer";
|
|
348
|
-
readonly dismiss: "Dismiss";
|
|
349
|
-
readonly fileUpload: "File upload";
|
|
350
|
-
readonly loading: "Loading";
|
|
351
|
-
readonly maximum: "Maximum";
|
|
352
|
-
readonly minimum: "Minimum";
|
|
353
|
-
readonly pagination: "Page navigation";
|
|
354
|
-
readonly progress: "Progress";
|
|
355
|
-
readonly slider: "Slider";
|
|
356
|
-
readonly toggle: "Toggle";
|
|
357
|
-
readonly removableBadge: "Removable badge";
|
|
358
|
-
readonly removeBadge: "Remove badge";
|
|
359
|
-
readonly removeFile: "Remove {{name}}";
|
|
360
|
-
};
|
|
361
|
-
readonly button: {
|
|
362
|
-
readonly close: "Close";
|
|
363
|
-
readonly apply: "Apply";
|
|
364
|
-
readonly cancel: "Cancel";
|
|
365
|
-
readonly confirm: "Confirm";
|
|
366
|
-
};
|
|
367
|
-
readonly menu: {
|
|
368
|
-
readonly placeholder: "Select an option...";
|
|
369
|
-
};
|
|
370
|
-
readonly common: {
|
|
371
|
-
readonly loading: "Loading...";
|
|
372
|
-
};
|
|
373
|
-
readonly languages: {
|
|
374
|
-
readonly de: "Deutsch";
|
|
375
|
-
readonly en: "English";
|
|
376
|
-
readonly es: "Español";
|
|
377
|
-
readonly fr: "Français";
|
|
378
|
-
readonly it: "Italiano";
|
|
379
|
-
readonly nl: "Nederlands";
|
|
380
|
-
};
|
|
381
|
-
readonly localeSwitcher: {
|
|
382
|
-
readonly ariaLabel: "Language selection";
|
|
383
|
-
readonly placeholder: "Select language...";
|
|
384
|
-
};
|
|
385
|
-
readonly pagination: {
|
|
386
|
-
readonly next: "Next";
|
|
387
|
-
readonly first: "First";
|
|
388
|
-
readonly last: "Last";
|
|
389
|
-
readonly page: "Page";
|
|
390
|
-
readonly previous: "Previous";
|
|
391
|
-
};
|
|
392
|
-
readonly calendar: {
|
|
393
|
-
readonly recurring: "Recurring event";
|
|
394
|
-
readonly previousMonth: "Previous month";
|
|
395
|
-
readonly nextMonth: "Next month";
|
|
396
|
-
readonly previousWeek: "Previous week";
|
|
397
|
-
readonly nextWeek: "Next week";
|
|
398
|
-
readonly previousDay: "Previous day";
|
|
399
|
-
readonly nextDay: "Next day";
|
|
400
|
-
readonly previousYear: "Previous year";
|
|
401
|
-
readonly nextYear: "Next year";
|
|
402
|
-
readonly today: "Today";
|
|
403
|
-
readonly calendarGrid: "Calendar";
|
|
404
|
-
readonly weekdays: "Weekdays";
|
|
405
|
-
readonly weekNumber: "Week number";
|
|
406
|
-
readonly events: "Events";
|
|
407
|
-
readonly noEvents: "No events";
|
|
408
|
-
readonly showMore: "{{count}} more";
|
|
409
|
-
readonly legend: "Legend";
|
|
410
|
-
readonly yearView: "Year overview";
|
|
411
|
-
readonly weekView: "Week view";
|
|
412
|
-
readonly dayView: "Day view";
|
|
413
|
-
readonly monthView: "Month view";
|
|
414
|
-
readonly viewMonth: "Month";
|
|
415
|
-
readonly viewYear: "Year";
|
|
416
|
-
readonly viewWeek: "Week";
|
|
417
|
-
readonly viewDay: "Day";
|
|
418
|
-
readonly multiDayLabel: "Day {{current}} of {{total}}";
|
|
419
|
-
readonly agendaView: "Agenda view";
|
|
420
|
-
readonly viewAgenda: "Agenda";
|
|
421
|
-
readonly viewSwitcher: "View mode";
|
|
422
|
-
};
|
|
423
|
-
readonly planner: {
|
|
424
|
-
readonly previousWeek: "Previous week";
|
|
425
|
-
readonly nextWeek: "Next week";
|
|
426
|
-
readonly previousMonth: "Previous month";
|
|
427
|
-
readonly nextMonth: "Next month";
|
|
428
|
-
readonly previousRange: "Previous range";
|
|
429
|
-
readonly nextRange: "Next range";
|
|
430
|
-
readonly today: "Today";
|
|
431
|
-
readonly grid: "Planner";
|
|
432
|
-
readonly weekNumber: "Week number";
|
|
433
|
-
readonly itemCount: "{{count}} items";
|
|
434
|
-
};
|
|
435
|
-
readonly commandPalette: {
|
|
436
|
-
readonly noResults: "No results found.";
|
|
437
|
-
readonly search: "Search...";
|
|
438
|
-
readonly hints: {
|
|
439
|
-
readonly navigate: "Navigate";
|
|
440
|
-
readonly select: "Select";
|
|
441
|
-
readonly close: "Close";
|
|
442
|
-
};
|
|
443
|
-
};
|
|
444
|
-
readonly compositionBar: {
|
|
445
|
-
readonly total: "Total";
|
|
446
|
-
readonly summary: "Composition: {{total}} ({{count}} shares) — {{parts}}";
|
|
447
|
-
readonly share: "Share";
|
|
448
|
-
readonly value: "Value";
|
|
449
|
-
readonly percent: "Percent";
|
|
450
|
-
readonly remaining: "remaining";
|
|
451
|
-
};
|
|
452
|
-
readonly guide: {
|
|
453
|
-
readonly next: "Next";
|
|
454
|
-
readonly previous: "Back";
|
|
455
|
-
readonly skip: "Skip tour";
|
|
456
|
-
readonly done: "Done";
|
|
457
|
-
readonly step: "Step {{current}} of {{total}}";
|
|
458
|
-
readonly tour: "Guided tour";
|
|
459
|
-
readonly close: "Close";
|
|
460
|
-
readonly openHelp: "Help";
|
|
461
|
-
readonly backToList: "All topics";
|
|
462
|
-
readonly info: "More information";
|
|
463
|
-
readonly infoAbout: "More information about {{label}}";
|
|
464
|
-
readonly dismiss: "Dismiss hint";
|
|
465
|
-
readonly startTour: "Start the guided tour";
|
|
466
|
-
readonly actionRequired: "Complete the highlighted action to continue";
|
|
467
|
-
};
|
|
468
|
-
readonly datepicker: {
|
|
469
|
-
readonly placeholder: "Select a date...";
|
|
470
|
-
readonly rangePlaceholder: "Select a date range...";
|
|
471
|
-
readonly clear: "Clear selection";
|
|
472
|
-
readonly openCalendar: "Open calendar";
|
|
473
|
-
readonly invalidDate: "Invalid date";
|
|
474
|
-
readonly outOfRange: "Date is outside the allowed range";
|
|
475
|
-
readonly invalidRange: "Invalid date range";
|
|
476
|
-
};
|
|
477
|
-
readonly fileUpload: {
|
|
478
|
-
readonly exists: "File already added";
|
|
479
|
-
readonly invalidType: "File type {{type}} is not allowed";
|
|
480
|
-
readonly tooLarge: "File exceeds {{size}} limit";
|
|
481
|
-
readonly tooMany: "Maximum {{count}} files allowed";
|
|
482
|
-
readonly tooSmall: "File must be at least {{size}}";
|
|
483
|
-
};
|
|
484
|
-
readonly sankey: {
|
|
485
|
-
readonly summary: "Sankey diagram: {{nodes}} nodes, {{links}} links — {{flows}}";
|
|
486
|
-
readonly source: "Source";
|
|
487
|
-
readonly target: "Target";
|
|
488
|
-
readonly value: "Value";
|
|
489
|
-
};
|
|
490
|
-
readonly slider: {
|
|
491
|
-
readonly rangeStatus: {
|
|
492
|
-
readonly insideRecommended: "In recommended range";
|
|
493
|
-
readonly insideValid: "In valid range";
|
|
494
|
-
readonly insideValidOnly: "Outside recommended range, but valid";
|
|
495
|
-
readonly outsideValid: "Outside valid range";
|
|
496
|
-
};
|
|
497
|
-
};
|
|
498
|
-
readonly themeSwitcher: {
|
|
499
|
-
readonly lightMode: "Light mode";
|
|
500
|
-
readonly darkMode: "Dark mode";
|
|
501
|
-
readonly systemTheme: "System theme";
|
|
502
|
-
};
|
|
503
|
-
readonly time: {
|
|
504
|
-
readonly ago: "{{value}} {{unit}} ago";
|
|
505
|
-
readonly now: "now";
|
|
506
|
-
};
|
|
507
|
-
}>;
|
|
342
|
+
export declare const blocksI18n: import("@urbicon-ui/i18n").PackageI18n<import("@urbicon-ui/i18n").Translations>;
|
|
508
343
|
/**
|
|
509
344
|
* Context-scoped translation hook for the blocks package. Call during component
|
|
510
345
|
* initialisation, then use the returned `t` (conventionally aliased `bt`):
|
|
@@ -517,174 +352,7 @@ export declare const blocksI18n: import("@urbicon-ui/i18n").PackageI18n<{
|
|
|
517
352
|
* Resolves against the nearest `<I18nProvider>`'s locale (or the base locale
|
|
518
353
|
* when none is mounted) and re-renders reactively on locale change.
|
|
519
354
|
*/
|
|
520
|
-
export declare const useBlocksI18n:
|
|
521
|
-
readonly accessibility: {
|
|
522
|
-
readonly avatar: "Avatar";
|
|
523
|
-
readonly clearInput: "Clear input";
|
|
524
|
-
readonly clearSearch: "Clear search";
|
|
525
|
-
readonly clearSelection: "Clear selection";
|
|
526
|
-
readonly closeDialog: "Close dialog";
|
|
527
|
-
readonly closeDrawer: "Close drawer";
|
|
528
|
-
readonly dismiss: "Dismiss";
|
|
529
|
-
readonly fileUpload: "File upload";
|
|
530
|
-
readonly loading: "Loading";
|
|
531
|
-
readonly maximum: "Maximum";
|
|
532
|
-
readonly minimum: "Minimum";
|
|
533
|
-
readonly pagination: "Page navigation";
|
|
534
|
-
readonly progress: "Progress";
|
|
535
|
-
readonly slider: "Slider";
|
|
536
|
-
readonly toggle: "Toggle";
|
|
537
|
-
readonly removableBadge: "Removable badge";
|
|
538
|
-
readonly removeBadge: "Remove badge";
|
|
539
|
-
readonly removeFile: "Remove {{name}}";
|
|
540
|
-
};
|
|
541
|
-
readonly button: {
|
|
542
|
-
readonly close: "Close";
|
|
543
|
-
readonly apply: "Apply";
|
|
544
|
-
readonly cancel: "Cancel";
|
|
545
|
-
readonly confirm: "Confirm";
|
|
546
|
-
};
|
|
547
|
-
readonly menu: {
|
|
548
|
-
readonly placeholder: "Select an option...";
|
|
549
|
-
};
|
|
550
|
-
readonly common: {
|
|
551
|
-
readonly loading: "Loading...";
|
|
552
|
-
};
|
|
553
|
-
readonly languages: {
|
|
554
|
-
readonly de: "Deutsch";
|
|
555
|
-
readonly en: "English";
|
|
556
|
-
readonly es: "Español";
|
|
557
|
-
readonly fr: "Français";
|
|
558
|
-
readonly it: "Italiano";
|
|
559
|
-
readonly nl: "Nederlands";
|
|
560
|
-
};
|
|
561
|
-
readonly localeSwitcher: {
|
|
562
|
-
readonly ariaLabel: "Language selection";
|
|
563
|
-
readonly placeholder: "Select language...";
|
|
564
|
-
};
|
|
565
|
-
readonly pagination: {
|
|
566
|
-
readonly next: "Next";
|
|
567
|
-
readonly first: "First";
|
|
568
|
-
readonly last: "Last";
|
|
569
|
-
readonly page: "Page";
|
|
570
|
-
readonly previous: "Previous";
|
|
571
|
-
};
|
|
572
|
-
readonly calendar: {
|
|
573
|
-
readonly recurring: "Recurring event";
|
|
574
|
-
readonly previousMonth: "Previous month";
|
|
575
|
-
readonly nextMonth: "Next month";
|
|
576
|
-
readonly previousWeek: "Previous week";
|
|
577
|
-
readonly nextWeek: "Next week";
|
|
578
|
-
readonly previousDay: "Previous day";
|
|
579
|
-
readonly nextDay: "Next day";
|
|
580
|
-
readonly previousYear: "Previous year";
|
|
581
|
-
readonly nextYear: "Next year";
|
|
582
|
-
readonly today: "Today";
|
|
583
|
-
readonly calendarGrid: "Calendar";
|
|
584
|
-
readonly weekdays: "Weekdays";
|
|
585
|
-
readonly weekNumber: "Week number";
|
|
586
|
-
readonly events: "Events";
|
|
587
|
-
readonly noEvents: "No events";
|
|
588
|
-
readonly showMore: "{{count}} more";
|
|
589
|
-
readonly legend: "Legend";
|
|
590
|
-
readonly yearView: "Year overview";
|
|
591
|
-
readonly weekView: "Week view";
|
|
592
|
-
readonly dayView: "Day view";
|
|
593
|
-
readonly monthView: "Month view";
|
|
594
|
-
readonly viewMonth: "Month";
|
|
595
|
-
readonly viewYear: "Year";
|
|
596
|
-
readonly viewWeek: "Week";
|
|
597
|
-
readonly viewDay: "Day";
|
|
598
|
-
readonly multiDayLabel: "Day {{current}} of {{total}}";
|
|
599
|
-
readonly agendaView: "Agenda view";
|
|
600
|
-
readonly viewAgenda: "Agenda";
|
|
601
|
-
readonly viewSwitcher: "View mode";
|
|
602
|
-
};
|
|
603
|
-
readonly planner: {
|
|
604
|
-
readonly previousWeek: "Previous week";
|
|
605
|
-
readonly nextWeek: "Next week";
|
|
606
|
-
readonly previousMonth: "Previous month";
|
|
607
|
-
readonly nextMonth: "Next month";
|
|
608
|
-
readonly previousRange: "Previous range";
|
|
609
|
-
readonly nextRange: "Next range";
|
|
610
|
-
readonly today: "Today";
|
|
611
|
-
readonly grid: "Planner";
|
|
612
|
-
readonly weekNumber: "Week number";
|
|
613
|
-
readonly itemCount: "{{count}} items";
|
|
614
|
-
};
|
|
615
|
-
readonly commandPalette: {
|
|
616
|
-
readonly noResults: "No results found.";
|
|
617
|
-
readonly search: "Search...";
|
|
618
|
-
readonly hints: {
|
|
619
|
-
readonly navigate: "Navigate";
|
|
620
|
-
readonly select: "Select";
|
|
621
|
-
readonly close: "Close";
|
|
622
|
-
};
|
|
623
|
-
};
|
|
624
|
-
readonly compositionBar: {
|
|
625
|
-
readonly total: "Total";
|
|
626
|
-
readonly summary: "Composition: {{total}} ({{count}} shares) — {{parts}}";
|
|
627
|
-
readonly share: "Share";
|
|
628
|
-
readonly value: "Value";
|
|
629
|
-
readonly percent: "Percent";
|
|
630
|
-
readonly remaining: "remaining";
|
|
631
|
-
};
|
|
632
|
-
readonly guide: {
|
|
633
|
-
readonly next: "Next";
|
|
634
|
-
readonly previous: "Back";
|
|
635
|
-
readonly skip: "Skip tour";
|
|
636
|
-
readonly done: "Done";
|
|
637
|
-
readonly step: "Step {{current}} of {{total}}";
|
|
638
|
-
readonly tour: "Guided tour";
|
|
639
|
-
readonly close: "Close";
|
|
640
|
-
readonly openHelp: "Help";
|
|
641
|
-
readonly backToList: "All topics";
|
|
642
|
-
readonly info: "More information";
|
|
643
|
-
readonly infoAbout: "More information about {{label}}";
|
|
644
|
-
readonly dismiss: "Dismiss hint";
|
|
645
|
-
readonly startTour: "Start the guided tour";
|
|
646
|
-
readonly actionRequired: "Complete the highlighted action to continue";
|
|
647
|
-
};
|
|
648
|
-
readonly datepicker: {
|
|
649
|
-
readonly placeholder: "Select a date...";
|
|
650
|
-
readonly rangePlaceholder: "Select a date range...";
|
|
651
|
-
readonly clear: "Clear selection";
|
|
652
|
-
readonly openCalendar: "Open calendar";
|
|
653
|
-
readonly invalidDate: "Invalid date";
|
|
654
|
-
readonly outOfRange: "Date is outside the allowed range";
|
|
655
|
-
readonly invalidRange: "Invalid date range";
|
|
656
|
-
};
|
|
657
|
-
readonly fileUpload: {
|
|
658
|
-
readonly exists: "File already added";
|
|
659
|
-
readonly invalidType: "File type {{type}} is not allowed";
|
|
660
|
-
readonly tooLarge: "File exceeds {{size}} limit";
|
|
661
|
-
readonly tooMany: "Maximum {{count}} files allowed";
|
|
662
|
-
readonly tooSmall: "File must be at least {{size}}";
|
|
663
|
-
};
|
|
664
|
-
readonly sankey: {
|
|
665
|
-
readonly summary: "Sankey diagram: {{nodes}} nodes, {{links}} links — {{flows}}";
|
|
666
|
-
readonly source: "Source";
|
|
667
|
-
readonly target: "Target";
|
|
668
|
-
readonly value: "Value";
|
|
669
|
-
};
|
|
670
|
-
readonly slider: {
|
|
671
|
-
readonly rangeStatus: {
|
|
672
|
-
readonly insideRecommended: "In recommended range";
|
|
673
|
-
readonly insideValid: "In valid range";
|
|
674
|
-
readonly insideValidOnly: "Outside recommended range, but valid";
|
|
675
|
-
readonly outsideValid: "Outside valid range";
|
|
676
|
-
};
|
|
677
|
-
};
|
|
678
|
-
readonly themeSwitcher: {
|
|
679
|
-
readonly lightMode: "Light mode";
|
|
680
|
-
readonly darkMode: "Dark mode";
|
|
681
|
-
readonly systemTheme: "System theme";
|
|
682
|
-
};
|
|
683
|
-
readonly time: {
|
|
684
|
-
readonly ago: "{{value}} {{unit}} ago";
|
|
685
|
-
readonly now: "now";
|
|
686
|
-
};
|
|
687
|
-
}>;
|
|
355
|
+
export declare const useBlocksI18n: any;
|
|
688
356
|
export declare const hasBlocksTranslation: (key: string) => boolean, getBlocksLocales: () => import("@urbicon-ui/i18n").Locale[];
|
|
689
357
|
export type BlocksTranslationKey = keyof typeof enTranslations;
|
|
690
358
|
export { blocksTranslations };
|
|
@@ -1,12 +1,17 @@
|
|
|
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',
|
|
9
10
|
separator,
|
|
11
|
+
maxItems,
|
|
12
|
+
itemsBeforeCollapse = 1,
|
|
13
|
+
itemsAfterCollapse = 1,
|
|
14
|
+
expandLabel = 'Show all breadcrumb items',
|
|
10
15
|
'aria-label': ariaLabel = 'Breadcrumb',
|
|
11
16
|
class: className = '',
|
|
12
17
|
unstyled: unstyledProp = false,
|
|
@@ -25,9 +30,62 @@
|
|
|
25
30
|
const slotClasses = $derived(
|
|
26
31
|
resolveSlotClasses(blocksConfig, 'Breadcrumb', preset, variantProps, slotClassesProp)
|
|
27
32
|
);
|
|
33
|
+
|
|
34
|
+
// Long trails collapse their middle into a single "…" affordance. Clicking
|
|
35
|
+
// it expands the full trail for the rest of the component's life — the
|
|
36
|
+
// established expandable-breadcrumb pattern. `before`/`after` clamp to sane
|
|
37
|
+
// bounds and the current page (last item) is always kept visible.
|
|
38
|
+
let expanded = $state(false);
|
|
39
|
+
let navEl = $state<HTMLElement>();
|
|
40
|
+
|
|
41
|
+
const before = $derived(Math.max(0, itemsBeforeCollapse));
|
|
42
|
+
const after = $derived(Math.max(1, itemsAfterCollapse));
|
|
43
|
+
|
|
44
|
+
type BreadcrumbEntry =
|
|
45
|
+
| { kind: 'item'; item: BreadcrumbItem; current: boolean }
|
|
46
|
+
| { kind: 'ellipsis' };
|
|
47
|
+
|
|
48
|
+
const entries = $derived.by((): BreadcrumbEntry[] => {
|
|
49
|
+
const last = items.length - 1;
|
|
50
|
+
const collapse =
|
|
51
|
+
maxItems != null &&
|
|
52
|
+
maxItems > 0 &&
|
|
53
|
+
!expanded &&
|
|
54
|
+
items.length > maxItems &&
|
|
55
|
+
// Only worth it when "…" actually replaces ≥2 items — folding a single
|
|
56
|
+
// hidden item saves no space and just adds an interaction.
|
|
57
|
+
items.length - before - after >= 2;
|
|
58
|
+
|
|
59
|
+
if (!collapse) {
|
|
60
|
+
return items.map((item, i) => ({ kind: 'item' as const, item, current: i === last }));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const result: BreadcrumbEntry[] = [];
|
|
64
|
+
for (let i = 0; i < before; i++) {
|
|
65
|
+
result.push({ kind: 'item', item: items[i], current: false });
|
|
66
|
+
}
|
|
67
|
+
result.push({ kind: 'ellipsis' });
|
|
68
|
+
for (let i = items.length - after; i < items.length; i++) {
|
|
69
|
+
result.push({ kind: 'item', item: items[i], current: i === last });
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
function expand() {
|
|
75
|
+
expanded = true;
|
|
76
|
+
// Keep keyboard focus inside the trail: land on the first item that was
|
|
77
|
+
// hidden (now revealed) rather than dropping to the top of the page when
|
|
78
|
+
// the "…" button unmounts. Head items are links 0…before-1, so the first
|
|
79
|
+
// revealed item is link index `before`.
|
|
80
|
+
const target = before;
|
|
81
|
+
tick().then(() => {
|
|
82
|
+
navEl?.querySelectorAll<HTMLAnchorElement>('a')[target]?.focus();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
28
85
|
</script>
|
|
29
86
|
|
|
30
87
|
<nav
|
|
88
|
+
bind:this={navEl}
|
|
31
89
|
class={unstyled
|
|
32
90
|
? [slotClasses?.nav, className].filter(Boolean).join(' ')
|
|
33
91
|
: styles.nav({ class: [slotClasses?.nav, className] })}
|
|
@@ -35,26 +93,39 @@
|
|
|
35
93
|
{...restProps}
|
|
36
94
|
>
|
|
37
95
|
<ol class={unstyled ? (slotClasses?.list ?? '') : styles.list({ class: slotClasses?.list })}>
|
|
38
|
-
{#each
|
|
96
|
+
{#each entries as entry, i (entry.kind === 'ellipsis' ? 'ellipsis' : `${entry.item.label}-${i}`)}
|
|
39
97
|
<li class={unstyled ? (slotClasses?.item ?? '') : styles.item({ class: slotClasses?.item })}>
|
|
40
|
-
{#if
|
|
98
|
+
{#if entry.kind === 'ellipsis'}
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
class={unstyled
|
|
102
|
+
? (slotClasses?.ellipsis ?? '')
|
|
103
|
+
: styles.ellipsis({ class: slotClasses?.ellipsis })}
|
|
104
|
+
aria-label={expandLabel}
|
|
105
|
+
onclick={expand}
|
|
106
|
+
>
|
|
107
|
+
…
|
|
108
|
+
</button>
|
|
109
|
+
{:else if entry.current}
|
|
41
110
|
<span
|
|
42
111
|
class={unstyled
|
|
43
112
|
? (slotClasses?.currentPage ?? '')
|
|
44
113
|
: styles.currentPage({ class: slotClasses?.currentPage })}
|
|
45
|
-
aria-current="page">{item.label}</span
|
|
114
|
+
aria-current="page">{entry.item.label}</span
|
|
46
115
|
>
|
|
47
116
|
{:else}
|
|
48
117
|
<!-- eslint-disable svelte/no-navigation-without-resolve -- BreadcrumbItem.href is opaque to the library -->
|
|
49
118
|
<a
|
|
50
|
-
href={item.href}
|
|
119
|
+
href={entry.item.href}
|
|
51
120
|
class={unstyled ? (slotClasses?.link ?? '') : styles.link({ class: slotClasses?.link })}
|
|
52
|
-
aria-label={item['aria-label']}
|
|
53
|
-
onclick={item.onclick}
|
|
121
|
+
aria-label={entry.item['aria-label']}
|
|
122
|
+
onclick={entry.item.onclick}
|
|
54
123
|
>
|
|
55
|
-
{item.label}
|
|
124
|
+
{entry.item.label}
|
|
56
125
|
</a>
|
|
57
126
|
<!-- eslint-enable svelte/no-navigation-without-resolve -->
|
|
127
|
+
{/if}
|
|
128
|
+
{#if i < entries.length - 1}
|
|
58
129
|
<span
|
|
59
130
|
class={unstyled
|
|
60
131
|
? (slotClasses?.separator ?? '')
|
|
@@ -45,6 +45,12 @@ export declare const breadcrumbVariants: (props?: import("../../utils/variants.j
|
|
|
45
45
|
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
46
|
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
47
|
}) | undefined) => string;
|
|
48
|
+
ellipsis: (props?: ({
|
|
49
|
+
size?: "sm" | "md" | "lg" | undefined;
|
|
50
|
+
} & {
|
|
51
|
+
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
|
+
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;
|
|
53
|
+
}) | undefined) => string;
|
|
48
54
|
separator: (props?: ({
|
|
49
55
|
size?: "sm" | "md" | "lg" | undefined;
|
|
50
56
|
} & {
|
|
@@ -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: {
|
|
@@ -41,6 +41,11 @@ 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) */
|
|
@@ -49,6 +54,19 @@ export interface BreadcrumbProps extends BreadcrumbVariants, Omit<HTMLAttributes
|
|
|
49
54
|
size?: 'sm' | 'md' | 'lg';
|
|
50
55
|
/** Custom separator snippet (default: "/") */
|
|
51
56
|
separator?: Snippet;
|
|
57
|
+
/**
|
|
58
|
+
* Collapse the trail when it has more than this many items: the middle items
|
|
59
|
+
* fold into a single "…" button that expands the full trail on click. The
|
|
60
|
+
* first `itemsBeforeCollapse` and last `itemsAfterCollapse` items stay
|
|
61
|
+
* visible (the current page is always kept). Omit to never collapse.
|
|
62
|
+
*/
|
|
63
|
+
maxItems?: number;
|
|
64
|
+
/** Leading items kept visible when collapsed. @default 1 */
|
|
65
|
+
itemsBeforeCollapse?: number;
|
|
66
|
+
/** Trailing items kept visible when collapsed; the current page is always included. @default 1 */
|
|
67
|
+
itemsAfterCollapse?: number;
|
|
68
|
+
/** Accessible label for the "…" button that expands a collapsed trail. @default 'Show all breadcrumb items' */
|
|
69
|
+
expandLabel?: string;
|
|
52
70
|
/** Accessible label for the nav element @default 'Breadcrumb' */
|
|
53
71
|
'aria-label'?: string;
|
|
54
72
|
/** Custom CSS class */
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
|
|
67
67
|
|
|
68
68
|
let inputEl = $state<HTMLInputElement>();
|
|
69
|
+
let wrapperEl = $state<HTMLElement>();
|
|
69
70
|
let listboxEl = $state<HTMLElement>();
|
|
70
71
|
let activeIndex = $state(-1);
|
|
71
72
|
|
|
@@ -135,6 +136,24 @@
|
|
|
135
136
|
if (!disabled) open = true;
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
// Chevron toggle. The input opens on focus but has no way to close itself
|
|
140
|
+
// (re-clicking a focused field is a no-op — the "trigger doesn't close it"
|
|
141
|
+
// report), so the chevron button is the explicit open/close affordance.
|
|
142
|
+
// Focus stays on the input throughout (the chevron's onmousedown calls
|
|
143
|
+
// preventDefault), so closing here doesn't blur-then-reopen via handleFocus.
|
|
144
|
+
function toggleOpen() {
|
|
145
|
+
if (disabled) return;
|
|
146
|
+
if (open) {
|
|
147
|
+
open = false;
|
|
148
|
+
// Restore the selected label if the query was left dangling (mirrors the
|
|
149
|
+
// click-outside path), so the field doesn't read as blank after closing.
|
|
150
|
+
if (!value && selectedOption) query = selectedOption.label;
|
|
151
|
+
} else {
|
|
152
|
+
open = true;
|
|
153
|
+
inputEl?.focus();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
138
157
|
function scrollToActive() {
|
|
139
158
|
tick().then(() => {
|
|
140
159
|
if (!listboxEl) return;
|
|
@@ -212,9 +231,12 @@
|
|
|
212
231
|
}
|
|
213
232
|
}
|
|
214
233
|
|
|
215
|
-
function handleClickOutside(event:
|
|
234
|
+
function handleClickOutside(event: PointerEvent) {
|
|
216
235
|
const target = event.target as Node;
|
|
217
|
-
|
|
236
|
+
// Exclude the whole input wrapper (input + chevron toggle + clear button),
|
|
237
|
+
// so clicking the chevron to close doesn't race this handler into a
|
|
238
|
+
// close-then-reopen. The listbox lives in the top layer (separate subtree).
|
|
239
|
+
if (!wrapperEl?.contains(target) && !listboxEl?.contains(target)) {
|
|
218
240
|
if (!closeOnClickOutside) return;
|
|
219
241
|
open = false;
|
|
220
242
|
if (!value && selectedOption) {
|
|
@@ -226,8 +248,12 @@
|
|
|
226
248
|
|
|
227
249
|
$effect(() => {
|
|
228
250
|
if (open) {
|
|
229
|
-
|
|
230
|
-
|
|
251
|
+
// `pointerdown` (not `mousedown`) for reliable outside-dismiss on touch:
|
|
252
|
+
// iOS fires `pointerdown` immediately on tap, whereas a synthetic
|
|
253
|
+
// `mousedown` can be delayed or dropped when the tapped element is the
|
|
254
|
+
// moving top-layer popover. Mirrors Popover's manual-mode dismiss.
|
|
255
|
+
document.addEventListener('pointerdown', handleClickOutside);
|
|
256
|
+
return () => document.removeEventListener('pointerdown', handleClickOutside);
|
|
231
257
|
}
|
|
232
258
|
});
|
|
233
259
|
|
|
@@ -273,6 +299,7 @@
|
|
|
273
299
|
{/if}
|
|
274
300
|
|
|
275
301
|
<div
|
|
302
|
+
bind:this={wrapperEl}
|
|
276
303
|
class={unstyled
|
|
277
304
|
? (slotClasses?.inputWrapper ?? '')
|
|
278
305
|
: styles.inputWrapper({ class: slotClasses?.inputWrapper })}
|
|
@@ -309,11 +336,25 @@
|
|
|
309
336
|
<CloseIcon class="h-3.5 w-3.5" />
|
|
310
337
|
</button>
|
|
311
338
|
{:else}
|
|
312
|
-
<
|
|
339
|
+
<button
|
|
340
|
+
type="button"
|
|
341
|
+
tabindex={-1}
|
|
342
|
+
{disabled}
|
|
343
|
+
aria-label={bt('accessibility.toggleOptions')}
|
|
344
|
+
aria-controls={listboxId}
|
|
345
|
+
aria-expanded={open}
|
|
346
|
+
onmousedown={(e) => e.preventDefault()}
|
|
347
|
+
onclick={toggleOpen}
|
|
313
348
|
class={unstyled
|
|
314
|
-
? (slotClasses?.
|
|
315
|
-
: styles.
|
|
316
|
-
|
|
349
|
+
? (slotClasses?.chevronButton ?? '')
|
|
350
|
+
: styles.chevronButton({ class: slotClasses?.chevronButton })}
|
|
351
|
+
>
|
|
352
|
+
<ChevronDownIcon
|
|
353
|
+
class={unstyled
|
|
354
|
+
? (slotClasses?.chevron ?? '')
|
|
355
|
+
: styles.chevron({ class: slotClasses?.chevron })}
|
|
356
|
+
/>
|
|
357
|
+
</button>
|
|
317
358
|
{/if}
|
|
318
359
|
|
|
319
360
|
{#if name}
|