mithril-materialized 3.8.0 → 3.10.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.
package/dist/index.esm.js CHANGED
@@ -505,6 +505,106 @@ const Autocomplete = () => {
505
505
  };
506
506
  };
507
507
 
508
+ /**
509
+ * Badge component
510
+ *
511
+ * Displays a badge anchored to a child element. Commonly used for notifications,
512
+ * counts, or status indicators. Supports flexible positioning, colors, and variants.
513
+ *
514
+ * @example
515
+ * ```typescript
516
+ * // Basic notification badge
517
+ * m(Badge, { badgeContent: 5 },
518
+ * m('button.btn', 'Messages')
519
+ * )
520
+ *
521
+ * // Dot badge on avatar
522
+ * m(Badge, {
523
+ * variant: 'dot',
524
+ * color: 'green',
525
+ * overlap: 'circular'
526
+ * },
527
+ * m('img.circle', { src: 'avatar.jpg' })
528
+ * )
529
+ * ```
530
+ */
531
+ const Badge = () => {
532
+ return {
533
+ view: ({ attrs, children }) => {
534
+ const { badgeContent, max, anchorOrigin = { vertical: 'top', horizontal: 'right' }, overlap = 'rectangular', variant = 'standard', color = 'red', colorIntensity, invisible = false, showZero = false, 'aria-label': ariaLabel, badgeClassName = '', className = '' } = attrs, params = __rest(attrs, ["badgeContent", "max", "anchorOrigin", "overlap", "variant", "color", "colorIntensity", "invisible", "showZero", 'aria-label', "badgeClassName", "className"]);
535
+ // === VALIDATION: Single child element ===
536
+ const childArray = Array.isArray(children) ? children : children ? [children] : [];
537
+ if (childArray.length === 0) {
538
+ console.warn('Badge component requires a child element');
539
+ return null;
540
+ }
541
+ if (childArray.length > 1) {
542
+ console.warn('Badge component should only wrap a single child element. Using first child only.');
543
+ }
544
+ const child = childArray[0];
545
+ // === VISIBILITY LOGIC ===
546
+ // Hide badge if:
547
+ // 1. invisible prop is true, OR
548
+ // 2. For standard variant: badgeContent is undefined/null OR (badgeContent is 0 AND !showZero)
549
+ const shouldHideBadge = invisible ||
550
+ (variant === 'standard' &&
551
+ (badgeContent === undefined ||
552
+ badgeContent === null ||
553
+ (badgeContent === 0 && !showZero)));
554
+ // === BADGE CONTENT FORMATTING ===
555
+ // Apply max capping: if badgeContent > max, show "max+"
556
+ const getDisplayContent = () => {
557
+ if (variant === 'dot')
558
+ return '';
559
+ if (typeof badgeContent === 'number' && max !== undefined && badgeContent > max) {
560
+ return `${max}+`;
561
+ }
562
+ return String(badgeContent !== null && badgeContent !== void 0 ? badgeContent : '');
563
+ };
564
+ const displayContent = getDisplayContent();
565
+ // === CSS CLASS ASSEMBLY ===
566
+ // Wrapper classes
567
+ const wrapperClasses = ['badge-wrapper', className].filter(Boolean).join(' ').trim() || undefined;
568
+ // Badge element classes - using m-badge prefix to avoid Materialize conflicts
569
+ const positionClass = `m-badge--${anchorOrigin.vertical}-${anchorOrigin.horizontal}`;
570
+ const badgeClasses = [
571
+ 'm-badge',
572
+ `m-badge--${variant}`,
573
+ positionClass,
574
+ `m-badge--${overlap}`,
575
+ `m-badge--${color}`,
576
+ colorIntensity ? `m-badge--${colorIntensity}` : '',
577
+ shouldHideBadge ? 'm-badge--invisible' : '',
578
+ badgeClassName,
579
+ ]
580
+ .filter(Boolean)
581
+ .join(' ')
582
+ .trim();
583
+ // === ARIA ATTRIBUTES ===
584
+ const badgeAriaLabel = ariaLabel ||
585
+ (variant === 'dot'
586
+ ? 'notification indicator'
587
+ : displayContent
588
+ ? `${displayContent} notifications`
589
+ : 'notification badge');
590
+ // === RENDER ===
591
+ return m('.badge-wrapper', Object.assign(Object.assign({}, params), { className: wrapperClasses }), [
592
+ // Child element
593
+ child,
594
+ // Badge element - only render if not hidden
595
+ !shouldHideBadge
596
+ ? m('span', {
597
+ className: badgeClasses,
598
+ 'aria-label': badgeAriaLabel,
599
+ role: 'status',
600
+ 'aria-live': 'polite',
601
+ }, variant === 'standard' ? displayContent : null)
602
+ : null,
603
+ ]);
604
+ },
605
+ };
606
+ };
607
+
508
608
  /**
509
609
  * A simple material icon, defined by its icon name.
510
610
  *
@@ -10831,6 +10931,182 @@ const ToggleGroup = () => {
10831
10931
  };
10832
10932
  };
10833
10933
 
10934
+ /** Size dimensions in pixels */
10935
+ const SIZE_MAP = {
10936
+ small: 36,
10937
+ medium: 50,
10938
+ large: 64,
10939
+ };
10940
+ /** Stroke width in pixels */
10941
+ const STROKE_WIDTH = 3;
10942
+ /** Create a CircularProgress component */
10943
+ const CircularProgress = () => {
10944
+ const state = {
10945
+ id: uniqueId(),
10946
+ };
10947
+ /**
10948
+ * Calculate SVG stroke properties for determinate progress
10949
+ */
10950
+ const calculateStrokeProperties = (size, value, max) => {
10951
+ const radius = (size - STROKE_WIDTH) / 2;
10952
+ const circumference = 2 * Math.PI * radius;
10953
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100));
10954
+ const strokeDashoffset = circumference - (percentage / 100) * circumference;
10955
+ return {
10956
+ radius,
10957
+ circumference,
10958
+ strokeDashoffset,
10959
+ percentage,
10960
+ };
10961
+ };
10962
+ /**
10963
+ * Get size class name
10964
+ */
10965
+ const getSizeClass = (size = 'medium') => {
10966
+ return `circular-progress--${size}`;
10967
+ };
10968
+ /**
10969
+ * Get color class name
10970
+ */
10971
+ const getColorClass = (color, intensity) => {
10972
+ if (!color)
10973
+ return '';
10974
+ return intensity ? `circular-progress--${color} circular-progress--${intensity}` : `circular-progress--${color}`;
10975
+ };
10976
+ return {
10977
+ view: ({ attrs }) => {
10978
+ const { mode = 'indeterminate', value = 0, max = 100, size = 'medium', color = 'teal', colorIntensity, label, showPercentage = false, className = '', style = {}, id = state.id, 'aria-label': ariaLabel, 'aria-valuemin': ariaValueMin = 0, 'aria-valuemax': ariaValueMax = max, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText } = attrs, params = __rest(attrs, ["mode", "value", "max", "size", "color", "colorIntensity", "label", "showPercentage", "className", "style", "id", 'aria-label', 'aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-valuetext']);
10979
+ const isDeterminate = mode === 'determinate';
10980
+ const sizePixels = SIZE_MAP[size];
10981
+ const { radius, circumference, strokeDashoffset, percentage } = isDeterminate
10982
+ ? calculateStrokeProperties(sizePixels, value, max)
10983
+ : { radius: 0, circumference: 0, strokeDashoffset: 0, percentage: 0 };
10984
+ // Determine label content
10985
+ const labelContent = label !== undefined ? label : showPercentage && isDeterminate ? `${Math.round(percentage)}%` : '';
10986
+ // Build class names
10987
+ const classNames = [
10988
+ 'circular-progress',
10989
+ getSizeClass(size),
10990
+ getColorClass(color, colorIntensity),
10991
+ `circular-progress--${mode}`,
10992
+ className,
10993
+ ]
10994
+ .filter(Boolean)
10995
+ .join(' ');
10996
+ // ARIA attributes
10997
+ const ariaAttrs = isDeterminate
10998
+ ? {
10999
+ 'aria-valuenow': ariaValueNow !== undefined ? ariaValueNow : value,
11000
+ 'aria-valuemin': ariaValueMin,
11001
+ 'aria-valuemax': ariaValueMax,
11002
+ 'aria-valuetext': ariaValueText || `${Math.round(percentage)}%`,
11003
+ }
11004
+ : {
11005
+ 'aria-valuetext': ariaValueText || label || 'Loading',
11006
+ };
11007
+ return m('.circular-progress', Object.assign(Object.assign(Object.assign({}, params), { className: classNames, style: Object.assign({ width: `${sizePixels}px`, height: `${sizePixels}px` }, style), id, role: 'progressbar', 'aria-label': ariaLabel || (isDeterminate ? `Progress: ${Math.round(percentage)}%` : 'Loading') }), ariaAttrs), [
11008
+ // SVG circle
11009
+ m('svg.circular-progress__svg', {
11010
+ viewBox: `0 0 ${sizePixels} ${sizePixels}`,
11011
+ xmlns: 'http://www.w3.org/2000/svg',
11012
+ }, [
11013
+ // Background track circle
11014
+ m('circle.circular-progress__circle.circular-progress__circle--track', {
11015
+ cx: sizePixels / 2,
11016
+ cy: sizePixels / 2,
11017
+ r: radius || (sizePixels - STROKE_WIDTH) / 2,
11018
+ fill: 'none',
11019
+ stroke: 'currentColor',
11020
+ 'stroke-width': STROKE_WIDTH,
11021
+ }),
11022
+ // Progress indicator circle
11023
+ m('circle.circular-progress__circle.circular-progress__circle--indicator', {
11024
+ cx: sizePixels / 2,
11025
+ cy: sizePixels / 2,
11026
+ r: radius || (sizePixels - STROKE_WIDTH) / 2,
11027
+ fill: 'none',
11028
+ stroke: 'currentColor',
11029
+ 'stroke-width': STROKE_WIDTH,
11030
+ 'stroke-dasharray': isDeterminate ? circumference : undefined,
11031
+ 'stroke-dashoffset': isDeterminate ? strokeDashoffset : undefined,
11032
+ 'stroke-linecap': 'round',
11033
+ }),
11034
+ ]),
11035
+ // Label inside circle
11036
+ labelContent &&
11037
+ m('.circular-progress__label', {
11038
+ 'aria-hidden': 'true',
11039
+ }, labelContent),
11040
+ ]);
11041
+ },
11042
+ };
11043
+ };
11044
+
11045
+ /** Create a LinearProgress component */
11046
+ const LinearProgress = () => {
11047
+ const state = {
11048
+ id: uniqueId(),
11049
+ };
11050
+ /**
11051
+ * Get size class name
11052
+ */
11053
+ const getSizeClass = (size = 'medium') => {
11054
+ return `linear-progress__track--${size}`;
11055
+ };
11056
+ /**
11057
+ * Get color class name
11058
+ */
11059
+ const getColorClass = (color, intensity) => {
11060
+ if (!color)
11061
+ return '';
11062
+ return intensity ? `linear-progress--${color} linear-progress--${intensity}` : `linear-progress--${color}`;
11063
+ };
11064
+ return {
11065
+ view: ({ attrs }) => {
11066
+ const { mode = 'indeterminate', value = 0, max = 100, size = 'medium', color = 'teal', colorIntensity, label, showPercentage = false, className = '', style = {}, id = state.id, 'aria-label': ariaLabel, 'aria-valuemin': ariaValueMin = 0, 'aria-valuemax': ariaValueMax = max, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText } = attrs, params = __rest(attrs, ["mode", "value", "max", "size", "color", "colorIntensity", "label", "showPercentage", "className", "style", "id", 'aria-label', 'aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-valuetext']);
11067
+ const isDeterminate = mode === 'determinate';
11068
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100));
11069
+ // Determine label content
11070
+ const labelContent = label !== undefined ? label : showPercentage && isDeterminate ? `${Math.round(percentage)}%` : '';
11071
+ // Build class names
11072
+ const classNames = [
11073
+ 'linear-progress',
11074
+ getColorClass(color, colorIntensity),
11075
+ className,
11076
+ ]
11077
+ .filter(Boolean)
11078
+ .join(' ');
11079
+ // ARIA attributes
11080
+ const ariaAttrs = isDeterminate
11081
+ ? {
11082
+ 'aria-valuenow': ariaValueNow !== undefined ? ariaValueNow : value,
11083
+ 'aria-valuemin': ariaValueMin,
11084
+ 'aria-valuemax': ariaValueMax,
11085
+ 'aria-valuetext': ariaValueText || `${Math.round(percentage)}%`,
11086
+ }
11087
+ : {
11088
+ 'aria-valuetext': ariaValueText || label || 'Loading',
11089
+ };
11090
+ return m('.linear-progress', Object.assign(Object.assign({}, params), { className: classNames, style,
11091
+ id }), [
11092
+ // Progress track container
11093
+ m('.linear-progress__track', Object.assign({ className: `linear-progress__track ${getSizeClass(size)}`, role: 'progressbar', 'aria-label': ariaLabel || (isDeterminate ? `Progress: ${Math.round(percentage)}%` : 'Loading') }, ariaAttrs), [
11094
+ // Progress bar
11095
+ m('.linear-progress__bar', {
11096
+ className: `linear-progress__bar ${isDeterminate ? '' : 'linear-progress__bar--indeterminate'}`,
11097
+ style: isDeterminate ? { width: `${percentage}%` } : undefined,
11098
+ }),
11099
+ ]),
11100
+ // Label at the end (right side)
11101
+ labelContent &&
11102
+ m('.linear-progress__label', {
11103
+ 'aria-hidden': 'true',
11104
+ }, labelContent),
11105
+ ]);
11106
+ },
11107
+ };
11108
+ };
11109
+
10834
11110
  /**
10835
11111
  * @fileoverview Core TypeScript utility types for mithril-materialized library
10836
11112
  * These types improve type safety and developer experience across all components
@@ -10852,4 +11128,4 @@ const isValidationError = (result) => !isValidationSuccess(result);
10852
11128
  // ============================================================================
10853
11129
  // All types are already exported via individual export declarations above
10854
11130
 
10855
- export { AnalogClock, AnchorItem, Autocomplete, Breadcrumb, BreadcrumbManager, Button, ButtonFactory, Carousel, CharacterCounter, Chips, CodeBlock, Collapsible, CollapsibleItem, Collection, CollectionMode, ColorInput, DataTable, DatePicker, DigitalClock, DoubleRangeSlider, Dropdown, EmailInput, FileInput, FileUpload, FlatButton, FloatingActionButton, HelperText, Icon, IconButton, ImageList, InputCheckbox, Label, LargeButton, ListItem, Mandatory, Masonry, MaterialBox, MaterialIcon, ModalPanel, NumberInput, Options, OptionsList, Pagination, PaginationControls, Parallax, PasswordInput, Pushpin, PushpinComponent, RadioButton, RadioButtons, RangeInput, Rating, RoundIconButton, SearchSelect, SecondaryContent, Select, Sidenav, SidenavItem, SidenavManager, SingleRangeSlider, SmallButton, Stepper, SubmitButton, Switch, Tabs, TextArea, TextInput, ThemeManager, ThemeSwitcher, ThemeToggle, TimePicker, TimeRangePicker, Timeline, Toast, ToastComponent, ToggleGroup, Tooltip, TooltipComponent, TreeView, UrlInput, Wizard, addLeadingZero, clearPortal, createBreadcrumb, formatTime, generateHourOptions, generateMinuteOptions, getDropdownStyles, getPortalContainer, initPushpins, initTooltips, isNumeric, isTimeDisabled, isValidationError, isValidationSuccess, padLeft, parseTime, range, releasePortalContainer, renderToPortal, scrollToValue, snapToNearestItem, sortOptions, timeToMinutes, toast, uniqueId, uuid4 };
11131
+ export { AnalogClock, AnchorItem, Autocomplete, Badge, Breadcrumb, BreadcrumbManager, Button, ButtonFactory, Carousel, CharacterCounter, Chips, CircularProgress, CodeBlock, Collapsible, CollapsibleItem, Collection, CollectionMode, ColorInput, DataTable, DatePicker, DigitalClock, DoubleRangeSlider, Dropdown, EmailInput, FileInput, FileUpload, FlatButton, FloatingActionButton, HelperText, Icon, IconButton, ImageList, InputCheckbox, Label, LargeButton, LinearProgress, ListItem, Mandatory, Masonry, MaterialBox, MaterialIcon, ModalPanel, NumberInput, Options, OptionsList, Pagination, PaginationControls, Parallax, PasswordInput, Pushpin, PushpinComponent, RadioButton, RadioButtons, RangeInput, Rating, RoundIconButton, SearchSelect, SecondaryContent, Select, Sidenav, SidenavItem, SidenavManager, SingleRangeSlider, SmallButton, Stepper, SubmitButton, Switch, Tabs, TextArea, TextInput, ThemeManager, ThemeSwitcher, ThemeToggle, TimePicker, TimeRangePicker, Timeline, Toast, ToastComponent, ToggleGroup, Tooltip, TooltipComponent, TreeView, UrlInput, Wizard, addLeadingZero, clearPortal, createBreadcrumb, formatTime, generateHourOptions, generateMinuteOptions, getDropdownStyles, getPortalContainer, initPushpins, initTooltips, isNumeric, isTimeDisabled, isValidationError, isValidationSuccess, padLeft, parseTime, range, releasePortalContainer, renderToPortal, scrollToValue, snapToNearestItem, sortOptions, timeToMinutes, toast, uniqueId, uuid4 };
package/dist/index.js CHANGED
@@ -507,6 +507,106 @@ const Autocomplete = () => {
507
507
  };
508
508
  };
509
509
 
510
+ /**
511
+ * Badge component
512
+ *
513
+ * Displays a badge anchored to a child element. Commonly used for notifications,
514
+ * counts, or status indicators. Supports flexible positioning, colors, and variants.
515
+ *
516
+ * @example
517
+ * ```typescript
518
+ * // Basic notification badge
519
+ * m(Badge, { badgeContent: 5 },
520
+ * m('button.btn', 'Messages')
521
+ * )
522
+ *
523
+ * // Dot badge on avatar
524
+ * m(Badge, {
525
+ * variant: 'dot',
526
+ * color: 'green',
527
+ * overlap: 'circular'
528
+ * },
529
+ * m('img.circle', { src: 'avatar.jpg' })
530
+ * )
531
+ * ```
532
+ */
533
+ const Badge = () => {
534
+ return {
535
+ view: ({ attrs, children }) => {
536
+ const { badgeContent, max, anchorOrigin = { vertical: 'top', horizontal: 'right' }, overlap = 'rectangular', variant = 'standard', color = 'red', colorIntensity, invisible = false, showZero = false, 'aria-label': ariaLabel, badgeClassName = '', className = '' } = attrs, params = __rest(attrs, ["badgeContent", "max", "anchorOrigin", "overlap", "variant", "color", "colorIntensity", "invisible", "showZero", 'aria-label', "badgeClassName", "className"]);
537
+ // === VALIDATION: Single child element ===
538
+ const childArray = Array.isArray(children) ? children : children ? [children] : [];
539
+ if (childArray.length === 0) {
540
+ console.warn('Badge component requires a child element');
541
+ return null;
542
+ }
543
+ if (childArray.length > 1) {
544
+ console.warn('Badge component should only wrap a single child element. Using first child only.');
545
+ }
546
+ const child = childArray[0];
547
+ // === VISIBILITY LOGIC ===
548
+ // Hide badge if:
549
+ // 1. invisible prop is true, OR
550
+ // 2. For standard variant: badgeContent is undefined/null OR (badgeContent is 0 AND !showZero)
551
+ const shouldHideBadge = invisible ||
552
+ (variant === 'standard' &&
553
+ (badgeContent === undefined ||
554
+ badgeContent === null ||
555
+ (badgeContent === 0 && !showZero)));
556
+ // === BADGE CONTENT FORMATTING ===
557
+ // Apply max capping: if badgeContent > max, show "max+"
558
+ const getDisplayContent = () => {
559
+ if (variant === 'dot')
560
+ return '';
561
+ if (typeof badgeContent === 'number' && max !== undefined && badgeContent > max) {
562
+ return `${max}+`;
563
+ }
564
+ return String(badgeContent !== null && badgeContent !== void 0 ? badgeContent : '');
565
+ };
566
+ const displayContent = getDisplayContent();
567
+ // === CSS CLASS ASSEMBLY ===
568
+ // Wrapper classes
569
+ const wrapperClasses = ['badge-wrapper', className].filter(Boolean).join(' ').trim() || undefined;
570
+ // Badge element classes - using m-badge prefix to avoid Materialize conflicts
571
+ const positionClass = `m-badge--${anchorOrigin.vertical}-${anchorOrigin.horizontal}`;
572
+ const badgeClasses = [
573
+ 'm-badge',
574
+ `m-badge--${variant}`,
575
+ positionClass,
576
+ `m-badge--${overlap}`,
577
+ `m-badge--${color}`,
578
+ colorIntensity ? `m-badge--${colorIntensity}` : '',
579
+ shouldHideBadge ? 'm-badge--invisible' : '',
580
+ badgeClassName,
581
+ ]
582
+ .filter(Boolean)
583
+ .join(' ')
584
+ .trim();
585
+ // === ARIA ATTRIBUTES ===
586
+ const badgeAriaLabel = ariaLabel ||
587
+ (variant === 'dot'
588
+ ? 'notification indicator'
589
+ : displayContent
590
+ ? `${displayContent} notifications`
591
+ : 'notification badge');
592
+ // === RENDER ===
593
+ return m('.badge-wrapper', Object.assign(Object.assign({}, params), { className: wrapperClasses }), [
594
+ // Child element
595
+ child,
596
+ // Badge element - only render if not hidden
597
+ !shouldHideBadge
598
+ ? m('span', {
599
+ className: badgeClasses,
600
+ 'aria-label': badgeAriaLabel,
601
+ role: 'status',
602
+ 'aria-live': 'polite',
603
+ }, variant === 'standard' ? displayContent : null)
604
+ : null,
605
+ ]);
606
+ },
607
+ };
608
+ };
609
+
510
610
  /**
511
611
  * A simple material icon, defined by its icon name.
512
612
  *
@@ -10833,6 +10933,182 @@ const ToggleGroup = () => {
10833
10933
  };
10834
10934
  };
10835
10935
 
10936
+ /** Size dimensions in pixels */
10937
+ const SIZE_MAP = {
10938
+ small: 36,
10939
+ medium: 50,
10940
+ large: 64,
10941
+ };
10942
+ /** Stroke width in pixels */
10943
+ const STROKE_WIDTH = 3;
10944
+ /** Create a CircularProgress component */
10945
+ const CircularProgress = () => {
10946
+ const state = {
10947
+ id: uniqueId(),
10948
+ };
10949
+ /**
10950
+ * Calculate SVG stroke properties for determinate progress
10951
+ */
10952
+ const calculateStrokeProperties = (size, value, max) => {
10953
+ const radius = (size - STROKE_WIDTH) / 2;
10954
+ const circumference = 2 * Math.PI * radius;
10955
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100));
10956
+ const strokeDashoffset = circumference - (percentage / 100) * circumference;
10957
+ return {
10958
+ radius,
10959
+ circumference,
10960
+ strokeDashoffset,
10961
+ percentage,
10962
+ };
10963
+ };
10964
+ /**
10965
+ * Get size class name
10966
+ */
10967
+ const getSizeClass = (size = 'medium') => {
10968
+ return `circular-progress--${size}`;
10969
+ };
10970
+ /**
10971
+ * Get color class name
10972
+ */
10973
+ const getColorClass = (color, intensity) => {
10974
+ if (!color)
10975
+ return '';
10976
+ return intensity ? `circular-progress--${color} circular-progress--${intensity}` : `circular-progress--${color}`;
10977
+ };
10978
+ return {
10979
+ view: ({ attrs }) => {
10980
+ const { mode = 'indeterminate', value = 0, max = 100, size = 'medium', color = 'teal', colorIntensity, label, showPercentage = false, className = '', style = {}, id = state.id, 'aria-label': ariaLabel, 'aria-valuemin': ariaValueMin = 0, 'aria-valuemax': ariaValueMax = max, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText } = attrs, params = __rest(attrs, ["mode", "value", "max", "size", "color", "colorIntensity", "label", "showPercentage", "className", "style", "id", 'aria-label', 'aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-valuetext']);
10981
+ const isDeterminate = mode === 'determinate';
10982
+ const sizePixels = SIZE_MAP[size];
10983
+ const { radius, circumference, strokeDashoffset, percentage } = isDeterminate
10984
+ ? calculateStrokeProperties(sizePixels, value, max)
10985
+ : { radius: 0, circumference: 0, strokeDashoffset: 0, percentage: 0 };
10986
+ // Determine label content
10987
+ const labelContent = label !== undefined ? label : showPercentage && isDeterminate ? `${Math.round(percentage)}%` : '';
10988
+ // Build class names
10989
+ const classNames = [
10990
+ 'circular-progress',
10991
+ getSizeClass(size),
10992
+ getColorClass(color, colorIntensity),
10993
+ `circular-progress--${mode}`,
10994
+ className,
10995
+ ]
10996
+ .filter(Boolean)
10997
+ .join(' ');
10998
+ // ARIA attributes
10999
+ const ariaAttrs = isDeterminate
11000
+ ? {
11001
+ 'aria-valuenow': ariaValueNow !== undefined ? ariaValueNow : value,
11002
+ 'aria-valuemin': ariaValueMin,
11003
+ 'aria-valuemax': ariaValueMax,
11004
+ 'aria-valuetext': ariaValueText || `${Math.round(percentage)}%`,
11005
+ }
11006
+ : {
11007
+ 'aria-valuetext': ariaValueText || label || 'Loading',
11008
+ };
11009
+ return m('.circular-progress', Object.assign(Object.assign(Object.assign({}, params), { className: classNames, style: Object.assign({ width: `${sizePixels}px`, height: `${sizePixels}px` }, style), id, role: 'progressbar', 'aria-label': ariaLabel || (isDeterminate ? `Progress: ${Math.round(percentage)}%` : 'Loading') }), ariaAttrs), [
11010
+ // SVG circle
11011
+ m('svg.circular-progress__svg', {
11012
+ viewBox: `0 0 ${sizePixels} ${sizePixels}`,
11013
+ xmlns: 'http://www.w3.org/2000/svg',
11014
+ }, [
11015
+ // Background track circle
11016
+ m('circle.circular-progress__circle.circular-progress__circle--track', {
11017
+ cx: sizePixels / 2,
11018
+ cy: sizePixels / 2,
11019
+ r: radius || (sizePixels - STROKE_WIDTH) / 2,
11020
+ fill: 'none',
11021
+ stroke: 'currentColor',
11022
+ 'stroke-width': STROKE_WIDTH,
11023
+ }),
11024
+ // Progress indicator circle
11025
+ m('circle.circular-progress__circle.circular-progress__circle--indicator', {
11026
+ cx: sizePixels / 2,
11027
+ cy: sizePixels / 2,
11028
+ r: radius || (sizePixels - STROKE_WIDTH) / 2,
11029
+ fill: 'none',
11030
+ stroke: 'currentColor',
11031
+ 'stroke-width': STROKE_WIDTH,
11032
+ 'stroke-dasharray': isDeterminate ? circumference : undefined,
11033
+ 'stroke-dashoffset': isDeterminate ? strokeDashoffset : undefined,
11034
+ 'stroke-linecap': 'round',
11035
+ }),
11036
+ ]),
11037
+ // Label inside circle
11038
+ labelContent &&
11039
+ m('.circular-progress__label', {
11040
+ 'aria-hidden': 'true',
11041
+ }, labelContent),
11042
+ ]);
11043
+ },
11044
+ };
11045
+ };
11046
+
11047
+ /** Create a LinearProgress component */
11048
+ const LinearProgress = () => {
11049
+ const state = {
11050
+ id: uniqueId(),
11051
+ };
11052
+ /**
11053
+ * Get size class name
11054
+ */
11055
+ const getSizeClass = (size = 'medium') => {
11056
+ return `linear-progress__track--${size}`;
11057
+ };
11058
+ /**
11059
+ * Get color class name
11060
+ */
11061
+ const getColorClass = (color, intensity) => {
11062
+ if (!color)
11063
+ return '';
11064
+ return intensity ? `linear-progress--${color} linear-progress--${intensity}` : `linear-progress--${color}`;
11065
+ };
11066
+ return {
11067
+ view: ({ attrs }) => {
11068
+ const { mode = 'indeterminate', value = 0, max = 100, size = 'medium', color = 'teal', colorIntensity, label, showPercentage = false, className = '', style = {}, id = state.id, 'aria-label': ariaLabel, 'aria-valuemin': ariaValueMin = 0, 'aria-valuemax': ariaValueMax = max, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText } = attrs, params = __rest(attrs, ["mode", "value", "max", "size", "color", "colorIntensity", "label", "showPercentage", "className", "style", "id", 'aria-label', 'aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-valuetext']);
11069
+ const isDeterminate = mode === 'determinate';
11070
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100));
11071
+ // Determine label content
11072
+ const labelContent = label !== undefined ? label : showPercentage && isDeterminate ? `${Math.round(percentage)}%` : '';
11073
+ // Build class names
11074
+ const classNames = [
11075
+ 'linear-progress',
11076
+ getColorClass(color, colorIntensity),
11077
+ className,
11078
+ ]
11079
+ .filter(Boolean)
11080
+ .join(' ');
11081
+ // ARIA attributes
11082
+ const ariaAttrs = isDeterminate
11083
+ ? {
11084
+ 'aria-valuenow': ariaValueNow !== undefined ? ariaValueNow : value,
11085
+ 'aria-valuemin': ariaValueMin,
11086
+ 'aria-valuemax': ariaValueMax,
11087
+ 'aria-valuetext': ariaValueText || `${Math.round(percentage)}%`,
11088
+ }
11089
+ : {
11090
+ 'aria-valuetext': ariaValueText || label || 'Loading',
11091
+ };
11092
+ return m('.linear-progress', Object.assign(Object.assign({}, params), { className: classNames, style,
11093
+ id }), [
11094
+ // Progress track container
11095
+ m('.linear-progress__track', Object.assign({ className: `linear-progress__track ${getSizeClass(size)}`, role: 'progressbar', 'aria-label': ariaLabel || (isDeterminate ? `Progress: ${Math.round(percentage)}%` : 'Loading') }, ariaAttrs), [
11096
+ // Progress bar
11097
+ m('.linear-progress__bar', {
11098
+ className: `linear-progress__bar ${isDeterminate ? '' : 'linear-progress__bar--indeterminate'}`,
11099
+ style: isDeterminate ? { width: `${percentage}%` } : undefined,
11100
+ }),
11101
+ ]),
11102
+ // Label at the end (right side)
11103
+ labelContent &&
11104
+ m('.linear-progress__label', {
11105
+ 'aria-hidden': 'true',
11106
+ }, labelContent),
11107
+ ]);
11108
+ },
11109
+ };
11110
+ };
11111
+
10836
11112
  /**
10837
11113
  * @fileoverview Core TypeScript utility types for mithril-materialized library
10838
11114
  * These types improve type safety and developer experience across all components
@@ -10857,6 +11133,7 @@ const isValidationError = (result) => !isValidationSuccess(result);
10857
11133
  exports.AnalogClock = AnalogClock;
10858
11134
  exports.AnchorItem = AnchorItem;
10859
11135
  exports.Autocomplete = Autocomplete;
11136
+ exports.Badge = Badge;
10860
11137
  exports.Breadcrumb = Breadcrumb;
10861
11138
  exports.BreadcrumbManager = BreadcrumbManager;
10862
11139
  exports.Button = Button;
@@ -10864,6 +11141,7 @@ exports.ButtonFactory = ButtonFactory;
10864
11141
  exports.Carousel = Carousel;
10865
11142
  exports.CharacterCounter = CharacterCounter;
10866
11143
  exports.Chips = Chips;
11144
+ exports.CircularProgress = CircularProgress;
10867
11145
  exports.CodeBlock = CodeBlock;
10868
11146
  exports.Collapsible = Collapsible;
10869
11147
  exports.CollapsibleItem = CollapsibleItem;
@@ -10886,6 +11164,7 @@ exports.ImageList = ImageList;
10886
11164
  exports.InputCheckbox = InputCheckbox;
10887
11165
  exports.Label = Label;
10888
11166
  exports.LargeButton = LargeButton;
11167
+ exports.LinearProgress = LinearProgress;
10889
11168
  exports.ListItem = ListItem;
10890
11169
  exports.Mandatory = Mandatory;
10891
11170
  exports.Masonry = Masonry;