@urbicon-ui/blocks 6.19.2 → 6.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/components/Calendar/Calendar.svelte +8 -0
  2. package/dist/components/Calendar/CalendarGrid.svelte +19 -77
  3. package/dist/components/Calendar/calendar.context.d.ts +2 -0
  4. package/dist/components/Calendar/calendar.variants.d.ts +62 -62
  5. package/dist/i18n/index.d.ts +372 -2
  6. package/dist/icons/IconProvider.svelte +3 -0
  7. package/dist/mint/styles.css +1 -1
  8. package/dist/primitives/Accordion/Accordion.svelte +3 -0
  9. package/dist/primitives/Alert/alert.variants.d.ts +7 -7
  10. package/dist/primitives/Avatar/Avatar.svelte +74 -48
  11. package/dist/primitives/Avatar/avatar.variants.d.ts +32 -6
  12. package/dist/primitives/Avatar/avatar.variants.js +43 -18
  13. package/dist/primitives/Avatar/index.d.ts +6 -1
  14. package/dist/primitives/Checkbox/Checkbox.svelte +7 -4
  15. package/dist/primitives/Collapsible/Collapsible.svelte +3 -0
  16. package/dist/primitives/ConfirmDialog/index.d.ts +12 -0
  17. package/dist/primitives/Dialog/Dialog.svelte +2 -0
  18. package/dist/primitives/Drawer/Drawer.svelte +3 -1
  19. package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +21 -21
  20. package/dist/primitives/Menu/MenuSubmenu.svelte +1 -0
  21. package/dist/primitives/Select/Select.svelte +1 -1
  22. package/dist/primitives/Spinner/spinner.variants.d.ts +15 -15
  23. package/dist/primitives/Stepper/stepper.variants.d.ts +10 -10
  24. package/dist/primitives/Tab/Tab.svelte +3 -0
  25. package/dist/primitives/Toast/toast.variants.d.ts +8 -8
  26. package/dist/primitives/Toggle/Toggle.svelte +7 -4
  27. package/dist/primitives/Tooltip/tooltip.variants.d.ts +2 -2
  28. package/dist/style/interaction.css +1 -1
  29. package/dist/system/attachments/ContextIsolation.svelte +2 -0
  30. package/package.json +3 -3
@@ -373,7 +373,192 @@ declare const blocksTranslations: {
373
373
  };
374
374
  };
375
375
  };
376
- export declare const blocksI18n: import("@urbicon-ui/i18n").PackageI18n<import("@urbicon-ui/i18n").Translations>;
376
+ export declare const blocksI18n: import("@urbicon-ui/i18n").PackageI18n<{
377
+ readonly accessibility: {
378
+ readonly avatar: "Avatar";
379
+ readonly clearInput: "Clear input";
380
+ readonly clearSearch: "Clear search";
381
+ readonly clearSelection: "Clear selection";
382
+ readonly closeDialog: "Close dialog";
383
+ readonly closeDrawer: "Close drawer";
384
+ readonly dismiss: "Dismiss";
385
+ readonly fileUpload: "File upload";
386
+ readonly loading: "Loading";
387
+ readonly maximum: "Maximum";
388
+ readonly minimum: "Minimum";
389
+ readonly pagination: "Page navigation";
390
+ readonly progress: "Progress";
391
+ readonly slider: "Slider";
392
+ readonly toggle: "Toggle";
393
+ readonly toggleOptions: "Toggle options";
394
+ readonly removableBadge: "Removable badge";
395
+ readonly removeBadge: "Remove badge";
396
+ readonly removeFile: "Remove {{name}}";
397
+ };
398
+ readonly button: {
399
+ readonly close: "Close";
400
+ readonly apply: "Apply";
401
+ readonly cancel: "Cancel";
402
+ readonly confirm: "Confirm";
403
+ };
404
+ readonly menu: {
405
+ readonly placeholder: "Select an option...";
406
+ };
407
+ readonly common: {
408
+ readonly loading: "Loading...";
409
+ };
410
+ readonly languages: {
411
+ readonly de: "Deutsch";
412
+ readonly en: "English";
413
+ readonly es: "Español";
414
+ readonly fr: "Français";
415
+ readonly it: "Italiano";
416
+ readonly nl: "Nederlands";
417
+ };
418
+ readonly localeSwitcher: {
419
+ readonly ariaLabel: "Language selection";
420
+ readonly placeholder: "Select language...";
421
+ };
422
+ readonly pagination: {
423
+ readonly next: "Next";
424
+ readonly first: "First";
425
+ readonly last: "Last";
426
+ readonly page: "Page";
427
+ readonly previous: "Previous";
428
+ readonly pageInfo: "{{label}} {{current}} of {{total}}";
429
+ readonly rangeInfo: "{{start}}–{{end}} of {{total}}";
430
+ };
431
+ readonly calendar: {
432
+ readonly recurring: "Recurring event";
433
+ readonly previousMonth: "Previous month";
434
+ readonly nextMonth: "Next month";
435
+ readonly previousWeek: "Previous week";
436
+ readonly nextWeek: "Next week";
437
+ readonly previousDay: "Previous day";
438
+ readonly nextDay: "Next day";
439
+ readonly previousYear: "Previous year";
440
+ readonly nextYear: "Next year";
441
+ readonly today: "Today";
442
+ readonly calendarGrid: "Calendar";
443
+ readonly weekdays: "Weekdays";
444
+ readonly weekNumber: "Week number";
445
+ readonly events: "Events";
446
+ readonly noEvents: "No events";
447
+ readonly showMore: "{{count}} more";
448
+ readonly legend: "Legend";
449
+ readonly yearView: "Year overview";
450
+ readonly weekView: "Week view";
451
+ readonly viewMonth: "Month";
452
+ readonly viewYear: "Year";
453
+ readonly viewWeek: "Week";
454
+ readonly viewDay: "Day";
455
+ readonly multiDayLabel: "Day {{current}} of {{total}}";
456
+ readonly agendaView: "Agenda view";
457
+ readonly viewAgenda: "Agenda";
458
+ readonly viewSwitcher: "View mode";
459
+ };
460
+ readonly planner: {
461
+ readonly previousWeek: "Previous week";
462
+ readonly nextWeek: "Next week";
463
+ readonly previousMonth: "Previous month";
464
+ readonly nextMonth: "Next month";
465
+ readonly previousRange: "Previous range";
466
+ readonly nextRange: "Next range";
467
+ readonly today: "Today";
468
+ readonly grid: "Planner";
469
+ readonly itemCount: "{{count}} items";
470
+ };
471
+ readonly commandPalette: {
472
+ readonly noResults: "No results found.";
473
+ readonly search: "Search...";
474
+ readonly hints: {
475
+ readonly navigate: "Navigate";
476
+ readonly select: "Select";
477
+ readonly close: "Close";
478
+ };
479
+ };
480
+ readonly compositionBar: {
481
+ readonly total: "Total";
482
+ readonly summary: "Composition: {{total}} ({{count}} shares) — {{parts}}";
483
+ readonly share: "Share";
484
+ readonly value: "Value";
485
+ readonly percent: "Percent";
486
+ readonly remaining: "remaining";
487
+ };
488
+ readonly guide: {
489
+ readonly next: "Next";
490
+ readonly previous: "Back";
491
+ readonly skip: "Skip tour";
492
+ readonly done: "Done";
493
+ readonly step: "Step {{current}} of {{total}}";
494
+ readonly tour: "Guided tour";
495
+ readonly close: "Close";
496
+ readonly openHelp: "Help";
497
+ readonly backToList: "All topics";
498
+ readonly info: "More information";
499
+ readonly infoAbout: "More information about {{label}}";
500
+ readonly dismiss: "Dismiss hint";
501
+ readonly startTour: "Start the guided tour";
502
+ readonly actionRequired: "Complete the highlighted action to continue";
503
+ readonly filterPlaceholder: "Filter topics…";
504
+ readonly noResults: "No matching topics";
505
+ };
506
+ readonly journeyTimeline: {
507
+ readonly label: "Journey";
508
+ readonly status: {
509
+ readonly complete: "Completed";
510
+ readonly active: "In progress";
511
+ readonly pending: "Pending";
512
+ readonly attention: "Needs attention";
513
+ readonly blocked: "Blocked";
514
+ readonly skipped: "Skipped";
515
+ };
516
+ };
517
+ readonly datepicker: {
518
+ readonly placeholder: "Select a date...";
519
+ readonly rangePlaceholder: "Select a date range...";
520
+ readonly openCalendar: "Open calendar";
521
+ readonly invalidDate: "Invalid date";
522
+ readonly outOfRange: "Date is outside the allowed range";
523
+ readonly invalidRange: "Invalid date range";
524
+ };
525
+ readonly fileUpload: {
526
+ readonly exists: "File already added";
527
+ readonly invalidType: "File type {{type}} is not allowed";
528
+ readonly tooLarge: "File exceeds {{size}} limit";
529
+ readonly tooMany: "Maximum {{count}} files allowed";
530
+ readonly tooSmall: "File must be at least {{size}}";
531
+ };
532
+ readonly sankey: {
533
+ readonly summary: "Sankey diagram: {{nodes}} nodes, {{links}} links — {{flows}}";
534
+ readonly source: "Source";
535
+ readonly target: "Target";
536
+ readonly value: "Value";
537
+ };
538
+ readonly slider: {
539
+ readonly rangeStatus: {
540
+ readonly insideRecommended: "In recommended range";
541
+ readonly insideValid: "In valid range";
542
+ readonly insideValidOnly: "Outside recommended range, but valid";
543
+ readonly outsideValid: "Outside valid range";
544
+ };
545
+ };
546
+ readonly themeSwitcher: {
547
+ readonly lightMode: "Light mode";
548
+ readonly darkMode: "Dark mode";
549
+ readonly systemTheme: "System theme";
550
+ };
551
+ readonly chart: {
552
+ readonly category: "Category";
553
+ readonly series: "Series {{index}}";
554
+ readonly segment: "Segment";
555
+ readonly value: "Value";
556
+ readonly share: "Share";
557
+ };
558
+ readonly stepper: {
559
+ readonly optional: "Optional";
560
+ };
561
+ }>;
377
562
  /**
378
563
  * Context-scoped translation hook for the blocks package. Call during component
379
564
  * initialisation, then use the returned `t` (conventionally aliased `bt`):
@@ -386,7 +571,192 @@ export declare const blocksI18n: import("@urbicon-ui/i18n").PackageI18n<import("
386
571
  * Resolves against the nearest `<I18nProvider>`'s locale (or the base locale
387
572
  * when none is mounted) and re-renders reactively on locale change.
388
573
  */
389
- export declare const useBlocksI18n: any;
574
+ export declare const useBlocksI18n: () => import("@urbicon-ui/i18n").TypedTranslationFunction<{
575
+ readonly accessibility: {
576
+ readonly avatar: "Avatar";
577
+ readonly clearInput: "Clear input";
578
+ readonly clearSearch: "Clear search";
579
+ readonly clearSelection: "Clear selection";
580
+ readonly closeDialog: "Close dialog";
581
+ readonly closeDrawer: "Close drawer";
582
+ readonly dismiss: "Dismiss";
583
+ readonly fileUpload: "File upload";
584
+ readonly loading: "Loading";
585
+ readonly maximum: "Maximum";
586
+ readonly minimum: "Minimum";
587
+ readonly pagination: "Page navigation";
588
+ readonly progress: "Progress";
589
+ readonly slider: "Slider";
590
+ readonly toggle: "Toggle";
591
+ readonly toggleOptions: "Toggle options";
592
+ readonly removableBadge: "Removable badge";
593
+ readonly removeBadge: "Remove badge";
594
+ readonly removeFile: "Remove {{name}}";
595
+ };
596
+ readonly button: {
597
+ readonly close: "Close";
598
+ readonly apply: "Apply";
599
+ readonly cancel: "Cancel";
600
+ readonly confirm: "Confirm";
601
+ };
602
+ readonly menu: {
603
+ readonly placeholder: "Select an option...";
604
+ };
605
+ readonly common: {
606
+ readonly loading: "Loading...";
607
+ };
608
+ readonly languages: {
609
+ readonly de: "Deutsch";
610
+ readonly en: "English";
611
+ readonly es: "Español";
612
+ readonly fr: "Français";
613
+ readonly it: "Italiano";
614
+ readonly nl: "Nederlands";
615
+ };
616
+ readonly localeSwitcher: {
617
+ readonly ariaLabel: "Language selection";
618
+ readonly placeholder: "Select language...";
619
+ };
620
+ readonly pagination: {
621
+ readonly next: "Next";
622
+ readonly first: "First";
623
+ readonly last: "Last";
624
+ readonly page: "Page";
625
+ readonly previous: "Previous";
626
+ readonly pageInfo: "{{label}} {{current}} of {{total}}";
627
+ readonly rangeInfo: "{{start}}–{{end}} of {{total}}";
628
+ };
629
+ readonly calendar: {
630
+ readonly recurring: "Recurring event";
631
+ readonly previousMonth: "Previous month";
632
+ readonly nextMonth: "Next month";
633
+ readonly previousWeek: "Previous week";
634
+ readonly nextWeek: "Next week";
635
+ readonly previousDay: "Previous day";
636
+ readonly nextDay: "Next day";
637
+ readonly previousYear: "Previous year";
638
+ readonly nextYear: "Next year";
639
+ readonly today: "Today";
640
+ readonly calendarGrid: "Calendar";
641
+ readonly weekdays: "Weekdays";
642
+ readonly weekNumber: "Week number";
643
+ readonly events: "Events";
644
+ readonly noEvents: "No events";
645
+ readonly showMore: "{{count}} more";
646
+ readonly legend: "Legend";
647
+ readonly yearView: "Year overview";
648
+ readonly weekView: "Week view";
649
+ readonly viewMonth: "Month";
650
+ readonly viewYear: "Year";
651
+ readonly viewWeek: "Week";
652
+ readonly viewDay: "Day";
653
+ readonly multiDayLabel: "Day {{current}} of {{total}}";
654
+ readonly agendaView: "Agenda view";
655
+ readonly viewAgenda: "Agenda";
656
+ readonly viewSwitcher: "View mode";
657
+ };
658
+ readonly planner: {
659
+ readonly previousWeek: "Previous week";
660
+ readonly nextWeek: "Next week";
661
+ readonly previousMonth: "Previous month";
662
+ readonly nextMonth: "Next month";
663
+ readonly previousRange: "Previous range";
664
+ readonly nextRange: "Next range";
665
+ readonly today: "Today";
666
+ readonly grid: "Planner";
667
+ readonly itemCount: "{{count}} items";
668
+ };
669
+ readonly commandPalette: {
670
+ readonly noResults: "No results found.";
671
+ readonly search: "Search...";
672
+ readonly hints: {
673
+ readonly navigate: "Navigate";
674
+ readonly select: "Select";
675
+ readonly close: "Close";
676
+ };
677
+ };
678
+ readonly compositionBar: {
679
+ readonly total: "Total";
680
+ readonly summary: "Composition: {{total}} ({{count}} shares) — {{parts}}";
681
+ readonly share: "Share";
682
+ readonly value: "Value";
683
+ readonly percent: "Percent";
684
+ readonly remaining: "remaining";
685
+ };
686
+ readonly guide: {
687
+ readonly next: "Next";
688
+ readonly previous: "Back";
689
+ readonly skip: "Skip tour";
690
+ readonly done: "Done";
691
+ readonly step: "Step {{current}} of {{total}}";
692
+ readonly tour: "Guided tour";
693
+ readonly close: "Close";
694
+ readonly openHelp: "Help";
695
+ readonly backToList: "All topics";
696
+ readonly info: "More information";
697
+ readonly infoAbout: "More information about {{label}}";
698
+ readonly dismiss: "Dismiss hint";
699
+ readonly startTour: "Start the guided tour";
700
+ readonly actionRequired: "Complete the highlighted action to continue";
701
+ readonly filterPlaceholder: "Filter topics…";
702
+ readonly noResults: "No matching topics";
703
+ };
704
+ readonly journeyTimeline: {
705
+ readonly label: "Journey";
706
+ readonly status: {
707
+ readonly complete: "Completed";
708
+ readonly active: "In progress";
709
+ readonly pending: "Pending";
710
+ readonly attention: "Needs attention";
711
+ readonly blocked: "Blocked";
712
+ readonly skipped: "Skipped";
713
+ };
714
+ };
715
+ readonly datepicker: {
716
+ readonly placeholder: "Select a date...";
717
+ readonly rangePlaceholder: "Select a date range...";
718
+ readonly openCalendar: "Open calendar";
719
+ readonly invalidDate: "Invalid date";
720
+ readonly outOfRange: "Date is outside the allowed range";
721
+ readonly invalidRange: "Invalid date range";
722
+ };
723
+ readonly fileUpload: {
724
+ readonly exists: "File already added";
725
+ readonly invalidType: "File type {{type}} is not allowed";
726
+ readonly tooLarge: "File exceeds {{size}} limit";
727
+ readonly tooMany: "Maximum {{count}} files allowed";
728
+ readonly tooSmall: "File must be at least {{size}}";
729
+ };
730
+ readonly sankey: {
731
+ readonly summary: "Sankey diagram: {{nodes}} nodes, {{links}} links — {{flows}}";
732
+ readonly source: "Source";
733
+ readonly target: "Target";
734
+ readonly value: "Value";
735
+ };
736
+ readonly slider: {
737
+ readonly rangeStatus: {
738
+ readonly insideRecommended: "In recommended range";
739
+ readonly insideValid: "In valid range";
740
+ readonly insideValidOnly: "Outside recommended range, but valid";
741
+ readonly outsideValid: "Outside valid range";
742
+ };
743
+ };
744
+ readonly themeSwitcher: {
745
+ readonly lightMode: "Light mode";
746
+ readonly darkMode: "Dark mode";
747
+ readonly systemTheme: "System theme";
748
+ };
749
+ readonly chart: {
750
+ readonly category: "Category";
751
+ readonly series: "Series {{index}}";
752
+ readonly segment: "Segment";
753
+ readonly value: "Value";
754
+ readonly share: "Share";
755
+ };
756
+ readonly stepper: {
757
+ readonly optional: "Optional";
758
+ };
759
+ }>;
390
760
  export declare const hasBlocksTranslation: (key: string) => boolean, getBlocksLocales: () => import("@urbicon-ui/i18n").Locale[];
391
761
  export type BlocksTranslationKey = keyof typeof enTranslations;
392
762
  export { blocksTranslations };
@@ -4,6 +4,9 @@
4
4
 
5
5
  let { icons, children }: { icons: Partial<IconSet>; children: Snippet } = $props();
6
6
 
7
+ // Provider config is applied once at init — icon overrides are not meant to
8
+ // swap reactively after mount.
9
+ // svelte-ignore state_referenced_locally
7
10
  setIcons(icons);
8
11
  </script>
9
12
 
@@ -177,7 +177,7 @@
177
177
 
178
178
  /* === HIGH CONTRAST MODE === */
179
179
 
180
- @media (prefers-contrast: high) {
180
+ @media (prefers-contrast: more) {
181
181
  .blocks-mint-glow {
182
182
  box-shadow: 0 0 0 2px currentColor;
183
183
  }
@@ -36,6 +36,9 @@
36
36
  return Array.isArray(v) ? v : [v];
37
37
  }
38
38
 
39
+ // Uncontrolled seed: capture only the initial `defaultValue`; later changes
40
+ // must not clobber user interaction.
41
+ // svelte-ignore state_referenced_locally
39
42
  let internalValue = $state<string[]>(normalise(defaultValue));
40
43
 
41
44
  const openItems = $derived(value !== undefined ? normalise(value) : internalValue);
@@ -41,7 +41,7 @@ export declare const alertVariants: (props?: import("../../utils/variants.js").T
41
41
  };
42
42
  }> | undefined) => {
43
43
  base: (props?: ({
44
- intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
44
+ intent?: "info" | "primary" | "success" | "warning" | "danger" | "neutral" | undefined;
45
45
  variant?: "inline" | "filled" | "soft" | undefined;
46
46
  size?: "sm" | "md" | "lg" | undefined;
47
47
  } & {
@@ -49,7 +49,7 @@ export declare const alertVariants: (props?: import("../../utils/variants.js").T
49
49
  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;
50
50
  }) | undefined) => string;
51
51
  icon: (props?: ({
52
- intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
52
+ intent?: "info" | "primary" | "success" | "warning" | "danger" | "neutral" | undefined;
53
53
  variant?: "inline" | "filled" | "soft" | undefined;
54
54
  size?: "sm" | "md" | "lg" | undefined;
55
55
  } & {
@@ -57,7 +57,7 @@ export declare const alertVariants: (props?: import("../../utils/variants.js").T
57
57
  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;
58
58
  }) | undefined) => string;
59
59
  content: (props?: ({
60
- intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
60
+ intent?: "info" | "primary" | "success" | "warning" | "danger" | "neutral" | undefined;
61
61
  variant?: "inline" | "filled" | "soft" | undefined;
62
62
  size?: "sm" | "md" | "lg" | undefined;
63
63
  } & {
@@ -65,7 +65,7 @@ export declare const alertVariants: (props?: import("../../utils/variants.js").T
65
65
  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;
66
66
  }) | undefined) => string;
67
67
  title: (props?: ({
68
- intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
68
+ intent?: "info" | "primary" | "success" | "warning" | "danger" | "neutral" | undefined;
69
69
  variant?: "inline" | "filled" | "soft" | undefined;
70
70
  size?: "sm" | "md" | "lg" | undefined;
71
71
  } & {
@@ -73,7 +73,7 @@ export declare const alertVariants: (props?: import("../../utils/variants.js").T
73
73
  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;
74
74
  }) | undefined) => string;
75
75
  description: (props?: ({
76
- intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
76
+ intent?: "info" | "primary" | "success" | "warning" | "danger" | "neutral" | undefined;
77
77
  variant?: "inline" | "filled" | "soft" | undefined;
78
78
  size?: "sm" | "md" | "lg" | undefined;
79
79
  } & {
@@ -81,7 +81,7 @@ export declare const alertVariants: (props?: import("../../utils/variants.js").T
81
81
  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;
82
82
  }) | undefined) => string;
83
83
  actions: (props?: ({
84
- intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
84
+ intent?: "info" | "primary" | "success" | "warning" | "danger" | "neutral" | undefined;
85
85
  variant?: "inline" | "filled" | "soft" | undefined;
86
86
  size?: "sm" | "md" | "lg" | undefined;
87
87
  } & {
@@ -89,7 +89,7 @@ export declare const alertVariants: (props?: import("../../utils/variants.js").T
89
89
  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;
90
90
  }) | undefined) => string;
91
91
  dismissButton: (props?: ({
92
- intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
92
+ intent?: "info" | "primary" | "success" | "warning" | "danger" | "neutral" | undefined;
93
93
  variant?: "inline" | "filled" | "soft" | undefined;
94
94
  size?: "sm" | "md" | "lg" | undefined;
95
95
  } & {
@@ -17,6 +17,7 @@
17
17
  intent = 'neutral',
18
18
  status,
19
19
  statusPosition = 'bottom-right',
20
+ pulse = false,
20
21
  ring = false,
21
22
  ringIntent = 'primary',
22
23
  ringColor,
@@ -38,7 +39,6 @@
38
39
 
39
40
  let avatarElement = $state<HTMLElement>();
40
41
  let imageError = $state(false);
41
- let isHovered = $state(false);
42
42
 
43
43
  const isInteractive = $derived(clickable || interactive || !!onclick);
44
44
 
@@ -91,6 +91,7 @@
91
91
  intent: randomColor ? undefined : intent,
92
92
  status,
93
93
  statusPosition,
94
+ pulse,
94
95
  ring,
95
96
  ringIntent: ringColor ? undefined : ringIntent,
96
97
  interactive: isInteractive
@@ -102,31 +103,20 @@
102
103
  resolveSlotClasses(blocksConfig, 'Avatar', preset, variantProps, slotClassesProp)
103
104
  );
104
105
 
105
- const dynamicStyles = $derived.by(() => {
106
- const styles: Record<string, string> = {};
107
-
108
- if (randomColor && randomBg) {
109
- // Inline style property names must be kebab-case, not JS camelCase.
110
- // Previously `styles.backgroundColor` serialised as-is, so browsers
111
- // ignored it and the avatar fell back to surface-interactive + white
112
- // text (1.15:1 contrast).
113
- styles['background-color'] = randomBg;
114
- styles.color = 'white';
115
- }
116
-
117
- if (ringColor) {
118
- styles['border-color'] = ringColor;
119
- }
120
-
121
- return styles;
122
- });
123
-
124
- const styleString = $derived(
125
- Object.entries(dynamicStyles)
126
- .map(([key, value]) => `${key}: ${value}`)
127
- .join('; ')
106
+ // `randomColor` paints the visible fill, so it targets the `frame` slot.
107
+ // Inline property names stay kebab-case — a previous camelCase
108
+ // `backgroundColor` was silently ignored by browsers, dropping the avatar back
109
+ // to surface-interactive + white text.
110
+ const frameStyle = $derived(
111
+ randomColor && randomBg ? `background-color: ${randomBg}; color: white` : undefined
128
112
  );
129
113
 
114
+ // `ringColor` recolours the ring on the outer `base`. The ring is a
115
+ // Tailwind `ring-2` box-shadow, so it reads `--tw-ring-color` — setting
116
+ // `border-color` (the previous approach) was a no-op, as the avatar has no
117
+ // border. Only meaningful together with `ring`.
118
+ const baseStyle = $derived(ringColor ? `--tw-ring-color: ${ringColor}` : undefined);
119
+
130
120
  $effect(() => {
131
121
  if (avatarElement && mint && mint !== 'none' && isInteractive) {
132
122
  return mintRegistry.apply(avatarElement, mint);
@@ -134,12 +124,10 @@
134
124
  });
135
125
 
136
126
  function handleMouseEnter() {
137
- isHovered = true;
138
127
  onHover?.(true);
139
128
  }
140
129
 
141
130
  function handleMouseLeave() {
142
- isHovered = false;
143
131
  onHover?.(false);
144
132
  }
145
133
 
@@ -165,12 +153,13 @@
165
153
  <div
166
154
  bind:this={avatarElement}
167
155
  class={[
156
+ 'blocks-avatar',
168
157
  `blocks-intent-${intent}`,
169
158
  unstyled
170
159
  ? [slotClasses?.base, className].filter(Boolean).join(' ')
171
160
  : styles.base({ class: [slotClasses?.base, className] })
172
161
  ]}
173
- style={styleString}
162
+ style={baseStyle}
174
163
  role={isInteractive ? 'button' : undefined}
175
164
  tabindex={isInteractive ? 0 : undefined}
176
165
  aria-label={isInteractive ? alt || name || bt('accessibility.avatar') : undefined}
@@ -180,24 +169,31 @@
180
169
  onkeydown={handleKeydown}
181
170
  {...restProps}
182
171
  >
183
- {#if src && !imageError}
184
- <img
185
- {src}
186
- alt={alt || name || bt('accessibility.avatar')}
187
- class={unstyled ? (slotClasses?.image ?? '') : styles.image({ class: slotClasses?.image })}
188
- onerror={handleImageError}
189
- />
190
- {:else if children}
191
- {@render children()}
192
- {:else}
193
- <span
194
- class={unstyled
195
- ? (slotClasses?.fallback ?? '')
196
- : styles.fallback({ class: slotClasses?.fallback })}
197
- >
198
- {getInitials(name)}
199
- </span>
200
- {/if}
172
+ <!-- `frame` clips the image/initials to the avatar shape; the status dot
173
+ below is a sibling so it renders outside the clip and stays whole. -->
174
+ <div
175
+ class={unstyled ? (slotClasses?.frame ?? '') : styles.frame({ class: slotClasses?.frame })}
176
+ style={frameStyle}
177
+ >
178
+ {#if src && !imageError}
179
+ <img
180
+ {src}
181
+ alt={alt || name || bt('accessibility.avatar')}
182
+ class={unstyled ? (slotClasses?.image ?? '') : styles.image({ class: slotClasses?.image })}
183
+ onerror={handleImageError}
184
+ />
185
+ {:else if children}
186
+ {@render children()}
187
+ {:else}
188
+ <span
189
+ class={unstyled
190
+ ? (slotClasses?.fallback ?? '')
191
+ : styles.fallback({ class: slotClasses?.fallback })}
192
+ >
193
+ {getInitials(name)}
194
+ </span>
195
+ {/if}
196
+ </div>
201
197
 
202
198
  {#if status}
203
199
  <span
@@ -229,14 +225,44 @@
229
225
  }
230
226
  }
231
227
 
228
+ /* AVT-3: opt-in "live" pulse on the status dot. A radar ring radiates via an
229
+ animated box-shadow — no extra DOM — coloured to match the status through
230
+ the per-status `--blocks-avatar-pulse-color` set in avatar.variants.ts.
231
+ The dot now sits outside the frame's overflow-hidden clip, so the ring is
232
+ never cut off. */
233
+ :global(.blocks-avatar-status-pulse) {
234
+ animation: avatar-status-pulse 1.6s var(--blocks-ease-confident) infinite;
235
+ }
236
+
237
+ /* The ring must reach its brightest while it is already clear of the dot —
238
+ a shadow at spread 0 hides behind the opaque dot, so a plain from/to fade
239
+ only ever shows the faint, expanded tail. The mid stop lights it up at a
240
+ visible radius; it then keeps growing and fades to nothing. */
241
+ @keyframes avatar-status-pulse {
242
+ 0% {
243
+ box-shadow: 0 0 0 0 color-mix(in oklab, var(--blocks-avatar-pulse-color) 0%, transparent);
244
+ }
245
+ 35% {
246
+ box-shadow: 0 0 0 0.22rem
247
+ color-mix(in oklab, var(--blocks-avatar-pulse-color) 60%, transparent);
248
+ }
249
+ 100% {
250
+ box-shadow: 0 0 0 0.6rem color-mix(in oklab, var(--blocks-avatar-pulse-color) 0%, transparent);
251
+ }
252
+ }
253
+
232
254
  @media (prefers-reduced-motion: reduce) {
233
- :global(.blocks-mint-pulse) {
255
+ :global(.blocks-mint-pulse),
256
+ :global(.blocks-avatar-status-pulse) {
234
257
  animation: none;
235
258
  }
236
259
  }
237
260
 
238
- @media (prefers-contrast: high) {
239
- :global([class*='blocks-avatar']) {
261
+ /* High-contrast delineation for the avatar disc. Targets the stable
262
+ `blocks-avatar` root marker exactly — not `[class*='blocks-avatar']`, which
263
+ would also catch the `blocks-avatar-status-pulse` dot and ring it. */
264
+ @media (prefers-contrast: more) {
265
+ :global(.blocks-avatar) {
240
266
  outline: 2px solid currentColor;
241
267
  outline-offset: -2px;
242
268
  }