Coverage for fastblocks/adapters/icons/materialicons.py: 0%
184 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-09 00:47 -0700
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-09 00:47 -0700
1"""Material Icons adapter for FastBlocks with multiple themes."""
3from contextlib import suppress
4from typing import Any
5from uuid import UUID
7from acb.config import Settings
8from acb.depends import depends
10from ._base import IconsBase
11from ._utils import (
12 add_accessibility_attributes,
13 build_attr_string,
14 process_animations,
15 process_semantic_colors,
16 process_state_attributes,
17 process_transformations,
18)
21class MaterialIconsSettings(Settings): # type: ignore[misc]
22 """Settings for Material Icons adapter."""
24 # Required ACB 0.19.0+ metadata
25 MODULE_ID: UUID = UUID("01937d86-cf0b-e4bc-0d3e-2c3d4e5f6071") # Static UUID7
26 MODULE_STATUS: str = "stable"
28 # Material Icons configuration
29 version: str = "latest"
30 base_url: str = "https://fonts.googleapis.com"
31 default_theme: str = "filled" # filled, outlined, round, sharp, two-tone
32 default_size: str = "24px"
34 # Available themes
35 enabled_themes: list[str] = ["filled", "outlined", "round", "sharp", "two-tone"]
37 # Icon mapping for common names
38 icon_aliases: dict[str, str] = {
39 "home": "home",
40 "user": "person",
41 "settings": "settings",
42 "search": "search",
43 "menu": "menu",
44 "close": "close",
45 "check": "check",
46 "error": "error",
47 "info": "info",
48 "success": "check_circle",
49 "warning": "warning",
50 "edit": "edit",
51 "delete": "delete",
52 "save": "save",
53 "download": "download",
54 "upload": "upload",
55 "email": "email",
56 "phone": "phone",
57 "location": "location_on",
58 "calendar": "event",
59 "clock": "schedule",
60 "heart": "favorite",
61 "star": "star",
62 "share": "share",
63 "link": "link",
64 "copy": "content_copy",
65 "cut": "content_cut",
66 "paste": "content_paste",
67 "undo": "undo",
68 "redo": "redo",
69 "refresh": "refresh",
70 "logout": "logout",
71 "login": "login",
72 "plus": "add",
73 "minus": "remove",
74 "eye": "visibility",
75 "eye-off": "visibility_off",
76 "lock": "lock",
77 "unlock": "lock_open",
78 "arrow-up": "keyboard_arrow_up",
79 "arrow-down": "keyboard_arrow_down",
80 "arrow-left": "keyboard_arrow_left",
81 "arrow-right": "keyboard_arrow_right",
82 }
84 # Size presets
85 size_presets: dict[str, str] = {
86 "xs": "16px",
87 "sm": "20px",
88 "md": "24px",
89 "lg": "28px",
90 "xl": "32px",
91 "2xl": "40px",
92 "3xl": "48px",
93 "4xl": "56px",
94 "5xl": "64px",
95 }
97 # Color palette
98 material_colors: dict[str, str] = {
99 "red": "#f44336",
100 "pink": "#e91e63",
101 "purple": "#9c27b0",
102 "deep-purple": "#673ab7",
103 "indigo": "#3f51b5",
104 "blue": "#2196f3",
105 "light-blue": "#03a9f4",
106 "cyan": "#00bcd4",
107 "teal": "#009688",
108 "green": "#4caf50",
109 "light-green": "#8bc34a",
110 "lime": "#cddc39",
111 "yellow": "#ffeb3b",
112 "amber": "#ffc107",
113 "orange": "#ff9800",
114 "deep-orange": "#ff5722",
115 "brown": "#795548",
116 "grey": "#9e9e9e",
117 "blue-grey": "#607d8b",
118 }
121class MaterialIconsAdapter(IconsBase):
122 """Material Icons adapter with multiple themes and comprehensive icon set."""
124 # Required ACB 0.19.0+ metadata
125 MODULE_ID: UUID = UUID("01937d86-cf0b-e4bc-0d3e-2c3d4e5f6071") # Static UUID7
126 MODULE_STATUS: str = "stable"
128 def __init__(self) -> None:
129 """Initialize Material Icons adapter."""
130 super().__init__()
131 self.settings: MaterialIconsSettings | None = None
133 # Register with ACB dependency system
134 with suppress(Exception):
135 depends.set(self)
137 def get_stylesheet_links(self) -> list[str]:
138 """Get Material Icons stylesheet links."""
139 if not self.settings:
140 self.settings = MaterialIconsSettings()
142 links = []
144 # Material Icons CSS from Google Fonts
145 for theme in self.settings.enabled_themes:
146 if theme == "filled":
147 # Base Material Icons (filled is default)
148 css_url = f"{self.settings.base_url}/icon?family=Material+Icons"
149 else:
150 # Themed variants
151 theme_name = theme.replace("-", "+").title()
152 css_url = (
153 f"{self.settings.base_url}/icon?family=Material+Icons+{theme_name}"
154 )
156 links.append(f'<link rel="stylesheet" href="{css_url}">')
158 # Custom Material Icons CSS
159 material_css = self._generate_material_css()
160 links.append(f"<style>{material_css}</style>")
162 return links
164 def _generate_material_css(self) -> str:
165 """Generate Material Icons-specific CSS."""
166 if not self.settings:
167 self.settings = MaterialIconsSettings()
169 return f"""
170/* Material Icons Base Styles */
171.material-icons {{
172 font-family: 'Material Icons';
173 font-weight: normal;
174 font-style: normal;
175 font-size: {self.settings.default_size};
176 line-height: 1;
177 letter-spacing: normal;
178 text-transform: none;
179 display: inline-block;
180 white-space: nowrap;
181 word-wrap: normal;
182 direction: ltr;
183 -webkit-font-feature-settings: 'liga';
184 -webkit-font-smoothing: antialiased;
185 vertical-align: -0.125em;
186}}
188/* Theme-specific font families */
189.material-icons-outlined {{
190 font-family: 'Material Icons Outlined';
191}}
193.material-icons-round {{
194 font-family: 'Material Icons Round';
195}}
197.material-icons-sharp {{
198 font-family: 'Material Icons Sharp';
199}}
201.material-icons-two-tone {{
202 font-family: 'Material Icons Two Tone';
203}}
205/* Size variants */
206.material-icons-xs {{ font-size: 16px; }}
207.material-icons-sm {{ font-size: 20px; }}
208.material-icons-md {{ font-size: 24px; }}
209.material-icons-lg {{ font-size: 28px; }}
210.material-icons-xl {{ font-size: 32px; }}
211.material-icons-2xl {{ font-size: 40px; }}
212.material-icons-3xl {{ font-size: 48px; }}
213.material-icons-4xl {{ font-size: 56px; }}
214.material-icons-5xl {{ font-size: 64px; }}
216/* Density variants */
217.material-icons-dense {{
218 font-size: 20px;
219}}
221.material-icons-comfortable {{
222 font-size: 24px;
223}}
225.material-icons-compact {{
226 font-size: 18px;
227}}
229/* Rotation and transformation */
230.material-icons-rotate-90 {{ transform: rotate(90deg); }}
231.material-icons-rotate-180 {{ transform: rotate(180deg); }}
232.material-icons-rotate-270 {{ transform: rotate(270deg); }}
233.material-icons-flip-horizontal {{ transform: scaleX(-1); }}
234.material-icons-flip-vertical {{ transform: scaleY(-1); }}
236/* Animation support */
237.material-icons-spin {{
238 animation: material-spin 2s linear infinite;
239}}
241.material-icons-pulse {{
242 animation: material-pulse 2s ease-in-out infinite alternate;
243}}
245.material-icons-bounce {{
246 animation: material-bounce 1s ease-in-out infinite;
247}}
249.material-icons-shake {{
250 animation: material-shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
251}}
253.material-icons-flip {{
254 animation: material-flip 2s linear infinite;
255}}
257@keyframes material-spin {{
258 0% {{ transform: rotate(0deg); }}
259 100% {{ transform: rotate(360deg); }}
260}}
262@keyframes material-pulse {{
263 from {{ opacity: 1; }}
264 to {{ opacity: 0.25; }}
265}}
267@keyframes material-bounce {{
268 0%, 100% {{ transform: translateY(0); }}
269 50% {{ transform: translateY(-25%); }}
270}}
272@keyframes material-shake {{
273 10%, 90% {{ transform: translate3d(-1px, 0, 0); }}
274 20%, 80% {{ transform: translate3d(2px, 0, 0); }}
275 30%, 50%, 70% {{ transform: translate3d(-4px, 0, 0); }}
276 40%, 60% {{ transform: translate3d(4px, 0, 0); }}
277}}
279@keyframes material-flip {{
280 0% {{ transform: rotateY(0); }}
281 50% {{ transform: rotateY(180deg); }}
282 100% {{ transform: rotateY(360deg); }}
283}}
285/* Material Design color utilities */
286{self._generate_material_color_classes()}
288/* Interactive states */
289.material-icons-interactive {{
290 cursor: pointer;
291 transition: all 0.2s ease;
292 border-radius: 50%;
293 padding: 4px;
294}}
296.material-icons-interactive:hover {{
297 background-color: rgba(0, 0, 0, 0.04);
298 transform: scale(1.1);
299}}
301.material-icons-interactive:active {{
302 background-color: rgba(0, 0, 0, 0.08);
303 transform: scale(0.95);
304}}
306/* States */
307.material-icons-disabled {{
308 opacity: 0.38;
309 cursor: not-allowed;
310}}
312.material-icons-inactive {{
313 opacity: 0.54;
314}}
316.material-icons-loading {{
317 opacity: 0.6;
318}}
320/* Button integration */
321.btn .material-icons {{
322 margin-right: 8px;
323 vertical-align: -0.125em;
324}}
326.btn .material-icons:last-child {{
327 margin-right: 0;
328 margin-left: 8px;
329}}
331.btn .material-icons:only-child {{
332 margin: 0;
333}}
335.btn-sm .material-icons {{
336 font-size: 20px;
337}}
339.btn-lg .material-icons {{
340 font-size: 28px;
341}}
343/* Floating Action Button */
344.fab {{
345 display: inline-flex;
346 align-items: center;
347 justify-content: center;
348 width: 56px;
349 height: 56px;
350 border-radius: 50%;
351 border: none;
352 box-shadow: 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12);
353 cursor: pointer;
354 transition: all 0.3s ease;
355}}
357.fab:hover {{
358 box-shadow: 0 5px 5px -3px rgba(0,0,0,.2), 0 8px 10px 1px rgba(0,0,0,.14), 0 3px 14px 2px rgba(0,0,0,.12);
359 transform: translateY(-2px);
360}}
362.fab-mini {{
363 width: 40px;
364 height: 40px;
365}}
367.fab-extended {{
368 width: auto;
369 height: 48px;
370 border-radius: 24px;
371 padding: 0 16px;
372}}
374/* Badge integration */
375.badge .material-icons {{
376 font-size: 16px;
377 margin-right: 4px;
378 vertical-align: baseline;
379}}
381/* Navigation integration */
382.nav-link .material-icons {{
383 margin-right: 8px;
384 font-size: 20px;
385}}
387/* List integration */
388.list-group-item .material-icons {{
389 margin-right: 16px;
390 color: rgba(0, 0, 0, 0.54);
391}}
393/* Input group integration */
394.input-group-text .material-icons {{
395 color: rgba(0, 0, 0, 0.54);
396}}
398/* Alert integration */
399.alert .material-icons {{
400 margin-right: 8px;
401 font-size: 20px;
402}}
404/* Card integration */
405.card-title .material-icons {{
406 margin-right: 8px;
407}}
409/* Toolbar integration */
410.toolbar .material-icons {{
411 color: rgba(255, 255, 255, 0.87);
412}}
414/* Dark theme support */
415@media (prefers-color-scheme: dark) {{
416 .material-icons-interactive:hover {{
417 background-color: rgba(255, 255, 255, 0.08);
418 }}
420 .material-icons-interactive:active {{
421 background-color: rgba(255, 255, 255, 0.12);
422 }}
424 .list-group-item .material-icons {{
425 color: rgba(255, 255, 255, 0.7);
426 }}
428 .input-group-text .material-icons {{
429 color: rgba(255, 255, 255, 0.7);
430 }}
431}}
433/* Accessibility */
434.material-icons[aria-hidden="false"] {{
435 position: relative;
436}}
438.material-icons[aria-hidden="false"]:focus {{
439 outline: 2px solid #1976d2;
440 outline-offset: 2px;
441}}
442"""
444 def _generate_material_color_classes(self) -> str:
445 """Generate Material Design color classes."""
446 if not self.settings:
447 self.settings = MaterialIconsSettings()
449 css = "/* Material Design Colors */\n"
450 for name, color in self.settings.material_colors.items():
451 css += f".material-icons-{name} {{ color: {color}; }}\n"
453 # Add semantic colors
454 css += """
455.material-icons-primary { color: var(--primary-color, #1976d2); }
456.material-icons-secondary { color: var(--secondary-color, #424242); }
457.material-icons-success { color: var(--success-color, #4caf50); }
458.material-icons-warning { color: var(--warning-color, #ff9800); }
459.material-icons-danger { color: var(--danger-color, #f44336); }
460.material-icons-info { color: var(--info-color, #2196f3); }
461.material-icons-light { color: var(--light-color, #fafafa); }
462.material-icons-dark { color: var(--dark-color, #212121); }
463.material-icons-muted { color: var(--muted-color, #757575); }
464"""
466 return css
468 def get_icon_class(self, icon_name: str, theme: str | None = None) -> str:
469 """Get Material Icons class with theme support."""
470 if not self.settings:
471 self.settings = MaterialIconsSettings()
473 # Use default theme if not specified
474 if not theme:
475 theme = self.settings.default_theme
477 # Validate theme
478 if theme not in self.settings.enabled_themes:
479 theme = self.settings.default_theme
481 # Build class name based on theme
482 if theme == "filled":
483 return "material-icons"
485 return f"material-icons-{theme.replace('_', '-')}"
487 def get_icon_tag(
488 self,
489 icon_name: str,
490 **attributes: Any,
491 ) -> str:
492 """Generate Material Icons tag with full customization."""
493 # Extract theme and size from attributes
494 theme = attributes.pop("theme", None)
495 size = attributes.pop("size", None)
497 if not self.settings:
498 self.settings = MaterialIconsSettings()
500 # Resolve icon aliases
501 resolved_name = icon_name
502 if icon_name in self.settings.icon_aliases:
503 resolved_name = self.settings.icon_aliases[icon_name]
505 # Get base icon class
506 icon_class = self.get_icon_class(icon_name, theme)
508 # Add size class or custom size
509 if size:
510 if size in self.settings.size_presets:
511 icon_class += f" material-icons-{size}"
512 else:
513 attributes["style"] = (
514 f"font-size: {size}; {attributes.get('style', '')}"
515 )
517 # Add custom classes
518 if "class" in attributes:
519 icon_class += f" {attributes.pop('class')}"
521 # Process attributes using shared utilities
522 transform_classes, attributes = process_transformations(
523 attributes, "material-icons"
524 )
525 animation_classes, attributes = process_animations(
526 attributes, ["spin", "pulse", "bounce", "shake", "flip"], "material-icons"
527 )
529 # Extended semantic colors including material design colors
530 semantic_colors = [
531 "primary",
532 "secondary",
533 "success",
534 "warning",
535 "danger",
536 "info",
537 "light",
538 "dark",
539 "muted",
540 *list(self.settings.material_colors.keys()),
541 ]
542 color_class, attributes = process_semantic_colors(
543 attributes, semantic_colors, "material-icons"
544 )
545 state_classes, attributes = process_state_attributes(
546 attributes, "material-icons"
547 )
549 # Handle density (Material Design specific feature)
550 if "density" in attributes:
551 density = attributes.pop("density")
552 if density in ("dense", "comfortable", "compact"):
553 icon_class += f" material-icons-{density}"
555 # Combine all classes
556 icon_class += (
557 transform_classes + animation_classes + color_class + state_classes
558 )
560 # Build attributes and add accessibility
561 attrs = {"class": icon_class} | attributes
562 attrs = add_accessibility_attributes(attrs)
564 # Generate tag
565 attr_string = build_attr_string(attrs)
566 return f"<span {attr_string}>{resolved_name}</span>"
568 def get_fab_tag(
569 self,
570 icon_name: str,
571 variant: str = "regular",
572 theme: str | None = None,
573 **attributes: Any,
574 ) -> str:
575 """Generate Material Design Floating Action Button."""
576 if not self.settings:
577 self.settings = MaterialIconsSettings()
579 # Resolve icon name
580 resolved_name = icon_name
581 if icon_name in self.settings.icon_aliases:
582 resolved_name = self.settings.icon_aliases[icon_name]
584 # Get icon tag
585 icon_tag = self.get_icon_tag(resolved_name, theme=theme, size="md")
587 # Build FAB classes
588 fab_class = "fab"
589 if variant == "mini":
590 fab_class += " fab-mini"
591 elif variant == "extended":
592 fab_class += " fab-extended"
594 # Add custom classes
595 if "class" in attributes:
596 fab_class += f" {attributes.pop('class')}"
598 # Handle extended FAB with text
599 text = attributes.pop("text", "")
600 if variant == "extended" and text:
601 content = f"{icon_tag} {text}"
602 else:
603 content = icon_tag
605 # Build attributes
606 attrs = {"class": fab_class} | attributes
607 attr_string = " ".join(f'{k}="{v}"' for k, v in attrs.items())
609 return f"<button {attr_string}>{content}</button>"
611 def get_available_icons(self) -> dict[str, list[str]]:
612 """Get list of available Material Icons by category."""
613 return {
614 "action": [
615 "home",
616 "search",
617 "settings",
618 "favorite",
619 "star",
620 "bookmark",
621 "help",
622 "info",
623 "check_circle",
624 "done",
625 "thumb_up",
626 "thumb_down",
627 ],
628 "communication": [
629 "email",
630 "phone",
631 "chat",
632 "message",
633 "comment",
634 "forum",
635 "contact_mail",
636 "contact_phone",
637 "textsms",
638 "call",
639 ],
640 "content": [
641 "add",
642 "remove",
643 "clear",
644 "create",
645 "edit",
646 "delete_forever",
647 "content_copy",
648 "content_cut",
649 "content_paste",
650 "save",
651 "undo",
652 "redo",
653 ],
654 "editor": [
655 "format_bold",
656 "format_italic",
657 "format_underlined",
658 "format_color_text",
659 "format_align_left",
660 "format_align_center",
661 "format_align_right",
662 "format_list_bulleted",
663 ],
664 "file": [
665 "folder",
666 "folder_open",
667 "insert_drive_file",
668 "cloud",
669 "cloud_download",
670 "cloud_upload",
671 "attachment",
672 "file_download",
673 "file_upload",
674 ],
675 "hardware": [
676 "computer",
677 "phone_android",
678 "phone_iphone",
679 "tablet",
680 "laptop",
681 "desktop_windows",
682 "keyboard",
683 "mouse",
684 "headset",
685 "speaker",
686 ],
687 "image": [
688 "image",
689 "photo",
690 "photo_camera",
691 "video_camera",
692 "movie",
693 "music_note",
694 "palette",
695 "brush",
696 "color_lens",
697 "gradient",
698 ],
699 "maps": [
700 "location_on",
701 "location_off",
702 "my_location",
703 "navigation",
704 "map",
705 "place",
706 "directions",
707 "directions_car",
708 "directions_walk",
709 "local_taxi",
710 ],
711 "navigation": [
712 "menu",
713 "close",
714 "arrow_back",
715 "arrow_forward",
716 "arrow_upward",
717 "arrow_downward",
718 "chevron_left",
719 "chevron_right",
720 "expand_less",
721 "expand_more",
722 "fullscreen",
723 ],
724 "notification": [
725 "notifications",
726 "notifications_off",
727 "notification_important",
728 "alarm",
729 "alarm_on",
730 "alarm_off",
731 "event",
732 "event_available",
733 "schedule",
734 ],
735 "social": [
736 "person",
737 "people",
738 "group",
739 "public",
740 "school",
741 "domain",
742 "cake",
743 "mood",
744 "mood_bad",
745 "sentiment_satisfied",
746 "party_mode",
747 ],
748 "toggle": [
749 "check_box",
750 "check_box_outline_blank",
751 "radio_button_checked",
752 "radio_button_unchecked",
753 "star",
754 "star_border",
755 "favorite",
756 "favorite_border",
757 "visibility",
758 "visibility_off",
759 ],
760 "av": [
761 "play_arrow",
762 "pause",
763 "stop",
764 "fast_forward",
765 "fast_rewind",
766 "skip_next",
767 "skip_previous",
768 "volume_up",
769 "volume_down",
770 "volume_off",
771 ],
772 }
775# Template filter registration for FastBlocks
776def _register_material_basic_filters(env: Any) -> None:
777 """Register basic Material Icons filters."""
779 @env.filter("material_icon") # type: ignore[misc]
780 def material_icon_filter(
781 icon_name: str,
782 theme: str | None = None,
783 size: str | None = None,
784 **attributes: Any,
785 ) -> str:
786 """Template filter for Material Icons."""
787 icons = depends.get("icons")
788 if isinstance(icons, MaterialIconsAdapter):
789 return icons.get_icon_tag(icon_name, theme=theme, size=size, **attributes)
790 return f"<!-- {icon_name} -->"
792 @env.filter("material_class") # type: ignore[misc]
793 def material_class_filter(icon_name: str, theme: str | None = None) -> str:
794 """Template filter for Material Icons classes."""
795 icons = depends.get("icons")
796 if isinstance(icons, MaterialIconsAdapter):
797 return icons.get_icon_class(icon_name, theme)
798 return "material-icons"
800 @env.global_("materialicons_stylesheet_links") # type: ignore[misc]
801 def materialicons_stylesheet_links() -> str:
802 """Global function for Material Icons stylesheet links."""
803 icons = depends.get("icons")
804 if isinstance(icons, MaterialIconsAdapter):
805 return "\n".join(icons.get_stylesheet_links())
806 return ""
809def _register_material_fab_functions(env: Any) -> None:
810 """Register Material Design FAB functions."""
812 @env.global_("material_fab") # type: ignore[misc]
813 def material_fab(
814 icon_name: str,
815 variant: str = "regular",
816 theme: str | None = None,
817 **attributes: Any,
818 ) -> str:
819 """Generate Material Design Floating Action Button."""
820 icons = depends.get("icons")
821 if isinstance(icons, MaterialIconsAdapter):
822 return icons.get_fab_tag(icon_name, variant, theme, **attributes)
823 return f"<button class='fab'>{icon_name}</button>"
826def _register_material_button_functions(env: Any) -> None:
827 """Register Material Design button functions."""
829 @env.global_("material_button") # type: ignore[misc]
830 def material_button(
831 text: str,
832 icon: str | None = None,
833 theme: str | None = None,
834 icon_position: str = "left",
835 **attributes: Any,
836 ) -> str:
837 """Generate button with Material Icon."""
838 icons = depends.get("icons")
839 if not isinstance(icons, MaterialIconsAdapter):
840 return f"<button>{text}</button>"
842 btn_class = attributes.pop("class", "btn btn-primary")
844 # Build button content
845 content = ""
846 if icon:
847 icon_tag = icons.get_icon_tag(icon, theme=theme, size="sm")
849 if icon_position == "left":
850 content = f"{icon_tag} {text}"
851 elif icon_position == "right":
852 content = f"{text} {icon_tag}"
853 elif icon_position == "only":
854 content = icon_tag
855 else:
856 content = text
857 else:
858 content = text
860 # Build button attributes
861 btn_attrs = {"class": btn_class} | attributes
862 attr_string = " ".join(f'{k}="{v}"' for k, v in btn_attrs.items())
864 return f"<button {attr_string}>{content}</button>"
867def _register_material_chip_functions(env: Any) -> None:
868 """Register Material Design chip functions."""
870 @env.global_("material_chip") # type: ignore[misc]
871 def material_chip(
872 text: str,
873 icon: str | None = None,
874 theme: str | None = None,
875 deletable: bool = False,
876 **attributes: Any,
877 ) -> str:
878 """Generate Material Design chip with icon."""
879 icons = depends.get("icons")
880 if not isinstance(icons, MaterialIconsAdapter):
881 return f"<div class='chip'>{text}</div>"
883 chip_class = attributes.pop("class", "chip")
885 # Build chip content
886 content = ""
887 if icon:
888 icon_tag = icons.get_icon_tag(
889 icon, theme=theme, size="sm", class_="chip-icon"
890 )
891 content += icon_tag
893 content += f"<span class='chip-text'>{text}</span>"
895 if deletable:
896 delete_icon = icons.get_icon_tag(
897 "close", theme=theme, size="sm", class_="chip-delete"
898 )
899 content += delete_icon
901 # Build chip attributes
902 chip_attrs = {"class": chip_class} | attributes
903 attr_string = " ".join(f'{k}="{v}"' for k, v in chip_attrs.items())
905 return f"<div {attr_string}>{content}</div>"
908def register_materialicons_filters(env: Any) -> None:
909 """Register Material Icons filters for Jinja2 templates."""
910 _register_material_basic_filters(env)
911 _register_material_fab_functions(env)
912 _register_material_button_functions(env)
913 _register_material_chip_functions(env)
916# ACB 0.19.0+ compatibility
917__all__ = [
918 "MaterialIconsAdapter",
919 "MaterialIconsSettings",
920 "register_materialicons_filters",
921]