@urbicon-ui/blocks 6.3.6 → 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/i18n/index.d.ts +2 -338
- 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 +7 -3
- package/dist/primitives/Combobox/combobox.variants.js +6 -2
- package/dist/primitives/SegmentGroup/index.d.ts +1 -1
- package/dist/primitives/Select/Select.svelte +2 -2
- package/dist/primitives/Select/select.variants.js +6 -2
- package/dist/utils/floating.js +30 -2
- package/dist/utils/use-floating-listbox.svelte.js +17 -9
- package/package.json +3 -3
package/dist/i18n/index.d.ts
CHANGED
|
@@ -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:
|
|
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,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 */
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
function handleClickOutside(event:
|
|
234
|
+
function handleClickOutside(event: PointerEvent) {
|
|
235
235
|
const target = event.target as Node;
|
|
236
236
|
// Exclude the whole input wrapper (input + chevron toggle + clear button),
|
|
237
237
|
// so clicking the chevron to close doesn't race this handler into a
|
|
@@ -248,8 +248,12 @@
|
|
|
248
248
|
|
|
249
249
|
$effect(() => {
|
|
250
250
|
if (open) {
|
|
251
|
-
|
|
252
|
-
|
|
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);
|
|
253
257
|
}
|
|
254
258
|
});
|
|
255
259
|
|
|
@@ -22,12 +22,16 @@ export const comboboxVariants = tv({
|
|
|
22
22
|
// `position`, `width`, `overflow-y` are set inline in Combobox.svelte
|
|
23
23
|
// so the native `popover="manual"` top-layer rendering works
|
|
24
24
|
// correctly and the UA's `overflow: auto` doesn't render a duplicate
|
|
25
|
-
// scrollbar. Width
|
|
25
|
+
// scrollbar. Width AND a keyboard-aware max-height come from Floating
|
|
26
|
+
// UI's `size` middleware: it sets `--blocks-overlay-available-height`
|
|
27
|
+
// to the room left in the visual viewport (e.g. above the iOS keyboard);
|
|
28
|
+
// the `max-h-[min(15rem,…)]` below keeps 15rem as the upper design cap
|
|
29
|
+
// via `min()` and falls back to it whenever the var is unset.
|
|
26
30
|
// tier: contain — floating dropdown panel.
|
|
27
31
|
listbox: [
|
|
28
32
|
'rounded-contain border',
|
|
29
33
|
'bg-surface-elevated border-border-subtle shadow-[var(--blocks-shadow-md)]',
|
|
30
|
-
'max-h-
|
|
34
|
+
'max-h-[min(15rem,var(--blocks-overlay-available-height,100dvh))] p-1 space-y-0.5'
|
|
31
35
|
],
|
|
32
36
|
option: [
|
|
33
37
|
'flex w-full items-center gap-2 rounded-modify px-3 py-2 text-left',
|
|
@@ -18,7 +18,7 @@ export interface SegmentGroupContext {
|
|
|
18
18
|
readonly mint: MintProp;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* @description
|
|
21
|
+
* @description Segment control with an animated sliding indicator for single selection; collapses to a vertical radio-style stack when its row can't fit the available width.
|
|
22
22
|
* Compact mode/view switcher with smooth animation.
|
|
23
23
|
*
|
|
24
24
|
* @tag navigation
|
|
@@ -373,7 +373,7 @@
|
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
-
function handleClickOutside(event:
|
|
376
|
+
function handleClickOutside(event: PointerEvent) {
|
|
377
377
|
const target = event.target as Node;
|
|
378
378
|
if (
|
|
379
379
|
open &&
|
|
@@ -407,7 +407,7 @@
|
|
|
407
407
|
const hasSelection = $derived(multiple ? selectedOptions.length > 0 : selectedOption !== null);
|
|
408
408
|
</script>
|
|
409
409
|
|
|
410
|
-
<svelte:window
|
|
410
|
+
<svelte:window onpointerdown={handleClickOutside} />
|
|
411
411
|
|
|
412
412
|
<div
|
|
413
413
|
class={unstyled
|
|
@@ -26,12 +26,16 @@ export const selectVariants = tv({
|
|
|
26
26
|
// `position`, `width`, `overflow-y` are set inline in Select.svelte
|
|
27
27
|
// so the native `popover="manual"` top-layer rendering works
|
|
28
28
|
// correctly and the UA's `overflow: auto` doesn't render a duplicate
|
|
29
|
-
// scrollbar. Width
|
|
29
|
+
// scrollbar. Width AND a keyboard-aware max-height come from Floating
|
|
30
|
+
// UI's `size` middleware: it sets `--blocks-overlay-available-height`
|
|
31
|
+
// to the room left in the visual viewport (e.g. above the iOS keyboard);
|
|
32
|
+
// the `max-h-[min(15rem,…)]` below keeps 15rem as the upper design cap
|
|
33
|
+
// via `min()` and falls back to it whenever the var is unset.
|
|
30
34
|
// tier: contain — floating dropdown panel.
|
|
31
35
|
listbox: [
|
|
32
36
|
'rounded-contain border',
|
|
33
37
|
'bg-surface-elevated border-border-subtle shadow-[var(--blocks-shadow-md)]',
|
|
34
|
-
'max-h-
|
|
38
|
+
'max-h-[min(15rem,var(--blocks-overlay-available-height,100dvh))] p-1 space-y-0.5'
|
|
35
39
|
],
|
|
36
40
|
option: [
|
|
37
41
|
'flex w-full items-center gap-2 rounded-modify px-3 cursor-pointer select-none',
|
package/dist/utils/floating.js
CHANGED
|
@@ -240,8 +240,21 @@ export function size(options) {
|
|
|
240
240
|
name: 'size',
|
|
241
241
|
fn(state) {
|
|
242
242
|
const overflow = detectOverflow(state);
|
|
243
|
-
const
|
|
244
|
-
const
|
|
243
|
+
const side = getSide(state.placement);
|
|
244
|
+
const vertical = side === 'top' || side === 'bottom';
|
|
245
|
+
// Room in the placement's MAIN axis is measured anchor → viewport edge:
|
|
246
|
+
// `dimension - overflow[side]` cancels the floating element's own
|
|
247
|
+
// dimension, so the value reflects true available room and grows back
|
|
248
|
+
// once space is restored. Subtracting the *clamped* overflow of both
|
|
249
|
+
// edges instead would only ever ratchet down — a one-way "latch" that
|
|
250
|
+
// keeps the panel short after the iOS keyboard closes. The CROSS axis
|
|
251
|
+
// keeps the "what currently fits between both edges" measure.
|
|
252
|
+
const availableWidth = vertical
|
|
253
|
+
? state.rects.floating.width - Math.max(0, overflow.left) - Math.max(0, overflow.right)
|
|
254
|
+
: state.rects.floating.width - overflow[side];
|
|
255
|
+
const availableHeight = vertical
|
|
256
|
+
? state.rects.floating.height - overflow[side]
|
|
257
|
+
: state.rects.floating.height - Math.max(0, overflow.top) - Math.max(0, overflow.bottom);
|
|
245
258
|
options.apply({ ...state, availableWidth, availableHeight });
|
|
246
259
|
const newW = state.elements.floating.offsetWidth;
|
|
247
260
|
const newH = state.elements.floating.offsetHeight;
|
|
@@ -284,6 +297,21 @@ export function autoUpdate(reference, floating, callback) {
|
|
|
284
297
|
}
|
|
285
298
|
window.addEventListener('resize', callback);
|
|
286
299
|
cleanups.push(() => window.removeEventListener('resize', callback));
|
|
300
|
+
// iOS Safari resizes and offsets the *visual* viewport when the on-screen
|
|
301
|
+
// keyboard opens (and during pinch-zoom / overscroll) WITHOUT firing a
|
|
302
|
+
// `window` 'resize' or 'scroll'. A `position: fixed` overlay would otherwise
|
|
303
|
+
// stay pinned to stale coordinates — visibly detaching from its anchor and
|
|
304
|
+
// drifting as the page settles. Tracking the visualViewport keeps anchored
|
|
305
|
+
// overlays glued to their reference on touch devices.
|
|
306
|
+
const vv = window.visualViewport;
|
|
307
|
+
if (vv) {
|
|
308
|
+
vv.addEventListener('resize', callback);
|
|
309
|
+
vv.addEventListener('scroll', callback, { passive: true });
|
|
310
|
+
cleanups.push(() => {
|
|
311
|
+
vv.removeEventListener('resize', callback);
|
|
312
|
+
vv.removeEventListener('scroll', callback);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
287
315
|
return () => {
|
|
288
316
|
for (const fn of cleanups)
|
|
289
317
|
fn();
|
|
@@ -21,6 +21,9 @@ export function useFloatingListbox(opts) {
|
|
|
21
21
|
if (!isOpen) {
|
|
22
22
|
cleanupPosition?.();
|
|
23
23
|
cleanupPosition = undefined;
|
|
24
|
+
// Drop the keyboard-aware height clamp so the next open re-measures from
|
|
25
|
+
// the full design cap instead of inheriting a stale value.
|
|
26
|
+
floating.style.removeProperty('--blocks-overlay-available-height');
|
|
24
27
|
if (usePortal && floating.matches(':popover-open')) {
|
|
25
28
|
try {
|
|
26
29
|
floating.hidePopover();
|
|
@@ -50,15 +53,20 @@ export function useFloatingListbox(opts) {
|
|
|
50
53
|
offset(4),
|
|
51
54
|
flip(),
|
|
52
55
|
shift({ padding: 8 }),
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
floatingSize({
|
|
57
|
+
apply({ availableHeight, rects }) {
|
|
58
|
+
// Cap the panel to the room actually left between the anchor and
|
|
59
|
+
// the (visual) viewport edge — decisive on iOS, where the
|
|
60
|
+
// keyboard shrinks the visualViewport. Exposed as a CSS var that
|
|
61
|
+
// the variant feeds into `max-h-[min(15rem,var(--…))]`, so the
|
|
62
|
+
// 15rem design token stays the upper bound while this tracks the
|
|
63
|
+
// live available height (and recovers when room is restored).
|
|
64
|
+
floating.style.setProperty('--blocks-overlay-available-height', `${Math.max(0, Math.round(availableHeight))}px`);
|
|
65
|
+
if (syncWidth) {
|
|
66
|
+
floating.style.width = `${rects.reference.width}px`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})
|
|
62
70
|
]
|
|
63
71
|
})
|
|
64
72
|
.then(({ x, y }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urbicon-ui/blocks",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.7",
|
|
4
4
|
"description": "Svelte 5 UI component library with Tailwind CSS 4, OKLCH design tokens and zero runtime dependencies",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
"@sveltejs/package": "^2.5.8",
|
|
92
92
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
93
93
|
"@tailwindcss/vite": "^4.3.1",
|
|
94
|
-
"@urbicon-ui/i18n": "6.3.
|
|
95
|
-
"@urbicon-ui/shared-types": "6.3.
|
|
94
|
+
"@urbicon-ui/i18n": "6.3.7",
|
|
95
|
+
"@urbicon-ui/shared-types": "6.3.7",
|
|
96
96
|
"prettier": "^3.8.4",
|
|
97
97
|
"prettier-plugin-svelte": "^4.1.1",
|
|
98
98
|
"prettier-plugin-tailwindcss": "^0.8.0",
|