mithril-materialized 3.11.0 → 3.13.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
@@ -1635,7 +1635,7 @@ const avatarIsImage = (avatar = '') => /\./.test(avatar);
1635
1635
  const ListItem = () => {
1636
1636
  return {
1637
1637
  view: ({ attrs: { item, mode } }) => {
1638
- const { title, content = '', active, iconName, avatar, className, onclick } = item;
1638
+ const { title, content, active, iconName, avatar, className, onclick } = item;
1639
1639
  return mode === CollectionMode.AVATAR
1640
1640
  ? m('li.collection-item.avatar', {
1641
1641
  className: active ? 'active' : '',
@@ -1645,12 +1645,21 @@ const ListItem = () => {
1645
1645
  ? m('img.circle', { src: avatar })
1646
1646
  : m('i.material-icons.circle', { className }, avatar),
1647
1647
  m('span.title', title),
1648
- m('p', m.trust(content)),
1649
- m(SecondaryContent, item),
1650
- ])
1648
+ content ? (typeof content === 'string' ? m('p', m.trust(content)) : m('p', content)) : undefined,
1649
+ iconName ? m(SecondaryContent, item) : undefined,
1650
+ ].filter(Boolean))
1651
1651
  : m('li.collection-item', {
1652
1652
  className: active ? 'active' : '',
1653
- }, iconName ? m('div', [title, m(SecondaryContent, item)]) : title);
1653
+ onclick: onclick ? () => onclick(item) : undefined,
1654
+ }, content
1655
+ ? m('div', [
1656
+ m('div', title),
1657
+ typeof content === 'string' ? m('p.secondary-text', content) : content,
1658
+ iconName ? m(SecondaryContent, item) : undefined,
1659
+ ].filter(Boolean))
1660
+ : iconName
1661
+ ? m('div', [title, m(SecondaryContent, item)])
1662
+ : title);
1654
1663
  },
1655
1664
  };
1656
1665
  };
@@ -10751,12 +10760,12 @@ const Rating = () => {
10751
10760
  const RatingItem = () => {
10752
10761
  return {
10753
10762
  view: ({ attrs }) => {
10754
- const { index, displayValue, step, icons, allowHalfSteps, disabled, onclick, onmouseover } = attrs;
10763
+ const { index, displayValue, step, icons, allowHalfSteps, disabled, showTooltip, tooltipLabel, onclick, onmouseover, } = attrs;
10755
10764
  const itemValue = (index + 1) * step;
10756
10765
  // Calculate fill state based on displayValue vs itemValue
10757
10766
  const diff = displayValue - itemValue;
10758
10767
  const fillState = diff >= 0 ? 'full' : allowHalfSteps && diff >= -step / 2 ? 'half' : 'empty';
10759
- return m('.rating__item', {
10768
+ return m('.rating__item.no-select', {
10760
10769
  className: [
10761
10770
  fillState === 'full' ? 'rating__item--filled' : '',
10762
10771
  fillState === 'half' ? 'rating__item--half' : '',
@@ -10777,6 +10786,8 @@ const Rating = () => {
10777
10786
  clipPath: fillState === 'half' ? 'inset(0 50% 0 0)' : undefined,
10778
10787
  },
10779
10788
  }, typeof icons.filled === 'string' ? icons.filled : m(icons.filled)),
10789
+ // Tooltip
10790
+ showTooltip && tooltipLabel && m('.rating__tooltip', tooltipLabel),
10780
10791
  ]);
10781
10792
  },
10782
10793
  };
@@ -10848,6 +10859,7 @@ const Rating = () => {
10848
10859
  },
10849
10860
  // Array.from({ length: itemCount }, (_, i) => renderRatingItem(attrs, i))
10850
10861
  [...Array(itemCount)].map((_, i) => {
10862
+ var _a;
10851
10863
  const itemValue = (i + 1) * step;
10852
10864
  return m(RatingItem, {
10853
10865
  key: `rating-item-${i}`,
@@ -10857,6 +10869,8 @@ const Rating = () => {
10857
10869
  icons: Object.assign(Object.assign({}, DEFAULT_ICONS), attrs.icon),
10858
10870
  allowHalfSteps: attrs.allowHalfSteps,
10859
10871
  disabled: attrs.disabled,
10872
+ showTooltip: attrs.showTooltips,
10873
+ tooltipLabel: (_a = attrs.tooltipLabels) === null || _a === void 0 ? void 0 : _a[i],
10860
10874
  onclick: () => handleItemClick(attrs, itemValue),
10861
10875
  onmouseover: () => handleItemHover(attrs, itemValue),
10862
10876
  });
@@ -10872,6 +10886,237 @@ const Rating = () => {
10872
10886
  };
10873
10887
  };
10874
10888
 
10889
+ /** Create a LikertScale component */
10890
+ const LikertScale = () => {
10891
+ const state = {
10892
+ id: uniqueId(),
10893
+ groupId: uniqueId(),
10894
+ internalValue: undefined,
10895
+ isFocused: false,
10896
+ };
10897
+ const isControlled = (attrs) => typeof attrs.value !== 'undefined' && typeof attrs.onchange === 'function';
10898
+ const getCurrentValue = (attrs) => {
10899
+ var _a, _b;
10900
+ const controlled = isControlled(attrs);
10901
+ const isNonInteractive = attrs.readonly || attrs.disabled;
10902
+ if (controlled) {
10903
+ return attrs.value;
10904
+ }
10905
+ // Non-interactive components: prefer defaultValue, fallback to value
10906
+ if (isNonInteractive) {
10907
+ return (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : attrs.value;
10908
+ }
10909
+ // Interactive uncontrolled: use internal state (user can change it)
10910
+ return (_b = state.internalValue) !== null && _b !== void 0 ? _b : attrs.defaultValue;
10911
+ };
10912
+ const getLabelText = (value, min, max, getLabelFn) => {
10913
+ if (getLabelFn && value !== undefined) {
10914
+ return getLabelFn(value, min, max);
10915
+ }
10916
+ if (value === undefined) {
10917
+ return `No selection, please choose a value between ${min} and ${max}`;
10918
+ }
10919
+ return `Selected ${value} out of ${min} to ${max}`;
10920
+ };
10921
+ const getSizeClass = (size = 'medium') => {
10922
+ switch (size) {
10923
+ case 'small':
10924
+ return 'likert-scale--small';
10925
+ case 'large':
10926
+ return 'likert-scale--large';
10927
+ default:
10928
+ return 'likert-scale--medium';
10929
+ }
10930
+ };
10931
+ const getDensityClass = (density = 'standard') => {
10932
+ switch (density) {
10933
+ case 'compact':
10934
+ return 'likert-scale--compact';
10935
+ case 'comfortable':
10936
+ return 'likert-scale--comfortable';
10937
+ default:
10938
+ return 'likert-scale--standard';
10939
+ }
10940
+ };
10941
+ const getLayoutClass = (layout = 'responsive') => {
10942
+ switch (layout) {
10943
+ case 'horizontal':
10944
+ return 'likert-scale--horizontal';
10945
+ case 'vertical':
10946
+ return 'likert-scale--vertical';
10947
+ default:
10948
+ return 'likert-scale--responsive';
10949
+ }
10950
+ };
10951
+ const handleChange = (attrs, newValue) => {
10952
+ var _a;
10953
+ if (attrs.readonly || attrs.disabled)
10954
+ return;
10955
+ if (!isControlled(attrs)) {
10956
+ state.internalValue = newValue;
10957
+ }
10958
+ (_a = attrs.onchange) === null || _a === void 0 ? void 0 : _a.call(attrs, newValue);
10959
+ };
10960
+ const handleKeyDown = (attrs, e) => {
10961
+ if (attrs.readonly || attrs.disabled)
10962
+ return;
10963
+ const min = attrs.min || 1;
10964
+ const max = attrs.max || 5;
10965
+ const step = attrs.step || 1;
10966
+ const currentValue = getCurrentValue(attrs);
10967
+ let newValue = currentValue;
10968
+ switch (e.key) {
10969
+ case 'ArrowRight':
10970
+ case 'ArrowUp':
10971
+ e.preventDefault();
10972
+ newValue = currentValue !== undefined ? Math.min(max, currentValue + step) : min;
10973
+ break;
10974
+ case 'ArrowLeft':
10975
+ case 'ArrowDown':
10976
+ e.preventDefault();
10977
+ newValue = currentValue !== undefined ? Math.max(min, currentValue - step) : min;
10978
+ break;
10979
+ case 'Home':
10980
+ e.preventDefault();
10981
+ newValue = min;
10982
+ break;
10983
+ case 'End':
10984
+ e.preventDefault();
10985
+ newValue = max;
10986
+ break;
10987
+ default:
10988
+ return;
10989
+ }
10990
+ if (newValue !== currentValue) {
10991
+ handleChange(attrs, newValue);
10992
+ }
10993
+ };
10994
+ const LikertScaleItem = () => {
10995
+ return {
10996
+ view: ({ attrs }) => {
10997
+ const { value, currentValue, showNumber, showTooltip, tooltipLabel, groupId, name, disabled, readonly, onchange, } = attrs;
10998
+ const radioId = `${groupId}-${value}`;
10999
+ const isChecked = currentValue === value;
11000
+ return m('.likert-scale__item.no-select', {
11001
+ className: [
11002
+ isChecked ? 'likert-scale__item--checked' : '',
11003
+ disabled ? 'likert-scale__item--disabled' : '',
11004
+ readonly ? 'likert-scale__item--readonly' : '',
11005
+ ]
11006
+ .filter(Boolean)
11007
+ .join(' '),
11008
+ }, [
11009
+ // Number label (optional)
11010
+ showNumber && m('.likert-scale__number', value),
11011
+ // Radio button input
11012
+ m('input[type=radio].likert-scale__input', {
11013
+ id: radioId,
11014
+ name: name || groupId,
11015
+ value: value,
11016
+ checked: isChecked,
11017
+ disabled: disabled || readonly,
11018
+ onchange: () => onchange(value),
11019
+ }),
11020
+ // Label for radio button
11021
+ m('label.likert-scale__label', {
11022
+ for: radioId,
11023
+ }),
11024
+ // Tooltip (optional)
11025
+ showTooltip && tooltipLabel && m('.likert-scale__tooltip', tooltipLabel),
11026
+ ]);
11027
+ },
11028
+ };
11029
+ };
11030
+ return {
11031
+ oninit: ({ attrs }) => {
11032
+ const controlled = isControlled(attrs);
11033
+ const isNonInteractive = attrs.readonly || attrs.disabled;
11034
+ // Warn developer for improper controlled usage
11035
+ if (attrs.value !== undefined && !controlled && !isNonInteractive) {
11036
+ console.warn(`LikertScale component received 'value' prop without 'onchange' handler. ` +
11037
+ `Use 'defaultValue' for uncontrolled components or add 'onchange' for controlled components.`);
11038
+ }
11039
+ if (!controlled) {
11040
+ state.internalValue = attrs.defaultValue;
11041
+ }
11042
+ },
11043
+ view: ({ attrs }) => {
11044
+ const { min = 1, max = 5, step = 1, size = 'medium', density = 'standard', layout = 'responsive', className = '', style = {}, readonly = false, disabled = false, id = state.id, name, label, description, isMandatory, startLabel, middleLabel, endLabel, showNumbers = false, showTooltips = false, tooltipLabels, alignLabels = false } = attrs, ariaAttrs = __rest(attrs, ["min", "max", "step", "size", "density", "layout", "className", "style", "readonly", "disabled", "id", "name", "label", "description", "isMandatory", "startLabel", "middleLabel", "endLabel", "showNumbers", "showTooltips", "tooltipLabels", "alignLabels"]);
11045
+ const currentValue = getCurrentValue(attrs);
11046
+ const itemCount = Math.floor((max - min) / step) + 1;
11047
+ // Generate scale values
11048
+ const scaleValues = Array.from({ length: itemCount }, (_, i) => min + i * step);
11049
+ return m('.likert-scale', {
11050
+ className: [
11051
+ 'likert-scale',
11052
+ getSizeClass(size),
11053
+ getDensityClass(density),
11054
+ getLayoutClass(layout),
11055
+ readonly ? 'likert-scale--readonly' : '',
11056
+ disabled ? 'likert-scale--disabled' : '',
11057
+ state.isFocused ? 'likert-scale--focused' : '',
11058
+ alignLabels ? 'likert-scale--aligned' : '',
11059
+ className,
11060
+ ]
11061
+ .filter(Boolean)
11062
+ .join(' '),
11063
+ style,
11064
+ id,
11065
+ role: 'radiogroup',
11066
+ 'aria-label': ariaAttrs['aria-label'] || attrs.ariaLabel || label || `Rating scale from ${min} to ${max}`,
11067
+ 'aria-labelledby': ariaAttrs['aria-labelledby'],
11068
+ 'aria-readonly': readonly,
11069
+ 'aria-disabled': disabled,
11070
+ onkeydown: (e) => handleKeyDown(attrs, e),
11071
+ onfocus: () => {
11072
+ state.isFocused = true;
11073
+ },
11074
+ onblur: () => {
11075
+ state.isFocused = false;
11076
+ },
11077
+ tabindex: readonly || disabled ? -1 : 0,
11078
+ }, [
11079
+ // Label section (only text label, not the description)
11080
+ label &&
11081
+ m('.likert-scale__question-label', [
11082
+ m('span', label + (isMandatory ? ' *' : '')),
11083
+ description && m('.likert-scale__description', m.trust(description)),
11084
+ ]),
11085
+ // Scale section container
11086
+ m('.likert-scale__scale-container', [
11087
+ // Scale items with numbers
11088
+ m('.likert-scale__scale', scaleValues.map((value) => m(LikertScaleItem, {
11089
+ key: `likert-item-${value}`,
11090
+ value,
11091
+ currentValue,
11092
+ showNumber: showNumbers,
11093
+ showTooltip: showTooltips,
11094
+ tooltipLabel: tooltipLabels === null || tooltipLabels === void 0 ? void 0 : tooltipLabels[value - min],
11095
+ groupId: state.groupId,
11096
+ name,
11097
+ disabled,
11098
+ readonly,
11099
+ onchange: (v) => handleChange(attrs, v),
11100
+ }))),
11101
+ // Scale anchors
11102
+ (startLabel || middleLabel || endLabel) &&
11103
+ m('.likert-scale__anchors', [
11104
+ startLabel && m('.likert-scale__anchor.likert-scale__anchor--start', startLabel),
11105
+ middleLabel && m('.likert-scale__anchor.likert-scale__anchor--middle', middleLabel),
11106
+ endLabel && m('.likert-scale__anchor.likert-scale__anchor--end', endLabel),
11107
+ ]),
11108
+ ]),
11109
+ // Screen reader text
11110
+ m('.likert-scale__sr-only', {
11111
+ className: 'likert-scale__sr-only',
11112
+ 'aria-live': 'polite',
11113
+ 'aria-atomic': 'true',
11114
+ }, getLabelText(currentValue, min, max, attrs.getLabelText)),
11115
+ ]);
11116
+ },
11117
+ };
11118
+ };
11119
+
10875
11120
  /**
10876
11121
  * ToggleButton component.
10877
11122
  *
@@ -11189,4 +11434,4 @@ const isValidationError = (result) => !isValidationSuccess(result);
11189
11434
  // ============================================================================
11190
11435
  // All types are already exported via individual export declarations above
11191
11436
 
11192
- 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 };
11437
+ 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, LikertScale, 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
@@ -1637,7 +1637,7 @@ const avatarIsImage = (avatar = '') => /\./.test(avatar);
1637
1637
  const ListItem = () => {
1638
1638
  return {
1639
1639
  view: ({ attrs: { item, mode } }) => {
1640
- const { title, content = '', active, iconName, avatar, className, onclick } = item;
1640
+ const { title, content, active, iconName, avatar, className, onclick } = item;
1641
1641
  return mode === exports.CollectionMode.AVATAR
1642
1642
  ? m('li.collection-item.avatar', {
1643
1643
  className: active ? 'active' : '',
@@ -1647,12 +1647,21 @@ const ListItem = () => {
1647
1647
  ? m('img.circle', { src: avatar })
1648
1648
  : m('i.material-icons.circle', { className }, avatar),
1649
1649
  m('span.title', title),
1650
- m('p', m.trust(content)),
1651
- m(SecondaryContent, item),
1652
- ])
1650
+ content ? (typeof content === 'string' ? m('p', m.trust(content)) : m('p', content)) : undefined,
1651
+ iconName ? m(SecondaryContent, item) : undefined,
1652
+ ].filter(Boolean))
1653
1653
  : m('li.collection-item', {
1654
1654
  className: active ? 'active' : '',
1655
- }, iconName ? m('div', [title, m(SecondaryContent, item)]) : title);
1655
+ onclick: onclick ? () => onclick(item) : undefined,
1656
+ }, content
1657
+ ? m('div', [
1658
+ m('div', title),
1659
+ typeof content === 'string' ? m('p.secondary-text', content) : content,
1660
+ iconName ? m(SecondaryContent, item) : undefined,
1661
+ ].filter(Boolean))
1662
+ : iconName
1663
+ ? m('div', [title, m(SecondaryContent, item)])
1664
+ : title);
1656
1665
  },
1657
1666
  };
1658
1667
  };
@@ -10753,12 +10762,12 @@ const Rating = () => {
10753
10762
  const RatingItem = () => {
10754
10763
  return {
10755
10764
  view: ({ attrs }) => {
10756
- const { index, displayValue, step, icons, allowHalfSteps, disabled, onclick, onmouseover } = attrs;
10765
+ const { index, displayValue, step, icons, allowHalfSteps, disabled, showTooltip, tooltipLabel, onclick, onmouseover, } = attrs;
10757
10766
  const itemValue = (index + 1) * step;
10758
10767
  // Calculate fill state based on displayValue vs itemValue
10759
10768
  const diff = displayValue - itemValue;
10760
10769
  const fillState = diff >= 0 ? 'full' : allowHalfSteps && diff >= -step / 2 ? 'half' : 'empty';
10761
- return m('.rating__item', {
10770
+ return m('.rating__item.no-select', {
10762
10771
  className: [
10763
10772
  fillState === 'full' ? 'rating__item--filled' : '',
10764
10773
  fillState === 'half' ? 'rating__item--half' : '',
@@ -10779,6 +10788,8 @@ const Rating = () => {
10779
10788
  clipPath: fillState === 'half' ? 'inset(0 50% 0 0)' : undefined,
10780
10789
  },
10781
10790
  }, typeof icons.filled === 'string' ? icons.filled : m(icons.filled)),
10791
+ // Tooltip
10792
+ showTooltip && tooltipLabel && m('.rating__tooltip', tooltipLabel),
10782
10793
  ]);
10783
10794
  },
10784
10795
  };
@@ -10850,6 +10861,7 @@ const Rating = () => {
10850
10861
  },
10851
10862
  // Array.from({ length: itemCount }, (_, i) => renderRatingItem(attrs, i))
10852
10863
  [...Array(itemCount)].map((_, i) => {
10864
+ var _a;
10853
10865
  const itemValue = (i + 1) * step;
10854
10866
  return m(RatingItem, {
10855
10867
  key: `rating-item-${i}`,
@@ -10859,6 +10871,8 @@ const Rating = () => {
10859
10871
  icons: Object.assign(Object.assign({}, DEFAULT_ICONS), attrs.icon),
10860
10872
  allowHalfSteps: attrs.allowHalfSteps,
10861
10873
  disabled: attrs.disabled,
10874
+ showTooltip: attrs.showTooltips,
10875
+ tooltipLabel: (_a = attrs.tooltipLabels) === null || _a === void 0 ? void 0 : _a[i],
10862
10876
  onclick: () => handleItemClick(attrs, itemValue),
10863
10877
  onmouseover: () => handleItemHover(attrs, itemValue),
10864
10878
  });
@@ -10874,6 +10888,237 @@ const Rating = () => {
10874
10888
  };
10875
10889
  };
10876
10890
 
10891
+ /** Create a LikertScale component */
10892
+ const LikertScale = () => {
10893
+ const state = {
10894
+ id: uniqueId(),
10895
+ groupId: uniqueId(),
10896
+ internalValue: undefined,
10897
+ isFocused: false,
10898
+ };
10899
+ const isControlled = (attrs) => typeof attrs.value !== 'undefined' && typeof attrs.onchange === 'function';
10900
+ const getCurrentValue = (attrs) => {
10901
+ var _a, _b;
10902
+ const controlled = isControlled(attrs);
10903
+ const isNonInteractive = attrs.readonly || attrs.disabled;
10904
+ if (controlled) {
10905
+ return attrs.value;
10906
+ }
10907
+ // Non-interactive components: prefer defaultValue, fallback to value
10908
+ if (isNonInteractive) {
10909
+ return (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : attrs.value;
10910
+ }
10911
+ // Interactive uncontrolled: use internal state (user can change it)
10912
+ return (_b = state.internalValue) !== null && _b !== void 0 ? _b : attrs.defaultValue;
10913
+ };
10914
+ const getLabelText = (value, min, max, getLabelFn) => {
10915
+ if (getLabelFn && value !== undefined) {
10916
+ return getLabelFn(value, min, max);
10917
+ }
10918
+ if (value === undefined) {
10919
+ return `No selection, please choose a value between ${min} and ${max}`;
10920
+ }
10921
+ return `Selected ${value} out of ${min} to ${max}`;
10922
+ };
10923
+ const getSizeClass = (size = 'medium') => {
10924
+ switch (size) {
10925
+ case 'small':
10926
+ return 'likert-scale--small';
10927
+ case 'large':
10928
+ return 'likert-scale--large';
10929
+ default:
10930
+ return 'likert-scale--medium';
10931
+ }
10932
+ };
10933
+ const getDensityClass = (density = 'standard') => {
10934
+ switch (density) {
10935
+ case 'compact':
10936
+ return 'likert-scale--compact';
10937
+ case 'comfortable':
10938
+ return 'likert-scale--comfortable';
10939
+ default:
10940
+ return 'likert-scale--standard';
10941
+ }
10942
+ };
10943
+ const getLayoutClass = (layout = 'responsive') => {
10944
+ switch (layout) {
10945
+ case 'horizontal':
10946
+ return 'likert-scale--horizontal';
10947
+ case 'vertical':
10948
+ return 'likert-scale--vertical';
10949
+ default:
10950
+ return 'likert-scale--responsive';
10951
+ }
10952
+ };
10953
+ const handleChange = (attrs, newValue) => {
10954
+ var _a;
10955
+ if (attrs.readonly || attrs.disabled)
10956
+ return;
10957
+ if (!isControlled(attrs)) {
10958
+ state.internalValue = newValue;
10959
+ }
10960
+ (_a = attrs.onchange) === null || _a === void 0 ? void 0 : _a.call(attrs, newValue);
10961
+ };
10962
+ const handleKeyDown = (attrs, e) => {
10963
+ if (attrs.readonly || attrs.disabled)
10964
+ return;
10965
+ const min = attrs.min || 1;
10966
+ const max = attrs.max || 5;
10967
+ const step = attrs.step || 1;
10968
+ const currentValue = getCurrentValue(attrs);
10969
+ let newValue = currentValue;
10970
+ switch (e.key) {
10971
+ case 'ArrowRight':
10972
+ case 'ArrowUp':
10973
+ e.preventDefault();
10974
+ newValue = currentValue !== undefined ? Math.min(max, currentValue + step) : min;
10975
+ break;
10976
+ case 'ArrowLeft':
10977
+ case 'ArrowDown':
10978
+ e.preventDefault();
10979
+ newValue = currentValue !== undefined ? Math.max(min, currentValue - step) : min;
10980
+ break;
10981
+ case 'Home':
10982
+ e.preventDefault();
10983
+ newValue = min;
10984
+ break;
10985
+ case 'End':
10986
+ e.preventDefault();
10987
+ newValue = max;
10988
+ break;
10989
+ default:
10990
+ return;
10991
+ }
10992
+ if (newValue !== currentValue) {
10993
+ handleChange(attrs, newValue);
10994
+ }
10995
+ };
10996
+ const LikertScaleItem = () => {
10997
+ return {
10998
+ view: ({ attrs }) => {
10999
+ const { value, currentValue, showNumber, showTooltip, tooltipLabel, groupId, name, disabled, readonly, onchange, } = attrs;
11000
+ const radioId = `${groupId}-${value}`;
11001
+ const isChecked = currentValue === value;
11002
+ return m('.likert-scale__item.no-select', {
11003
+ className: [
11004
+ isChecked ? 'likert-scale__item--checked' : '',
11005
+ disabled ? 'likert-scale__item--disabled' : '',
11006
+ readonly ? 'likert-scale__item--readonly' : '',
11007
+ ]
11008
+ .filter(Boolean)
11009
+ .join(' '),
11010
+ }, [
11011
+ // Number label (optional)
11012
+ showNumber && m('.likert-scale__number', value),
11013
+ // Radio button input
11014
+ m('input[type=radio].likert-scale__input', {
11015
+ id: radioId,
11016
+ name: name || groupId,
11017
+ value: value,
11018
+ checked: isChecked,
11019
+ disabled: disabled || readonly,
11020
+ onchange: () => onchange(value),
11021
+ }),
11022
+ // Label for radio button
11023
+ m('label.likert-scale__label', {
11024
+ for: radioId,
11025
+ }),
11026
+ // Tooltip (optional)
11027
+ showTooltip && tooltipLabel && m('.likert-scale__tooltip', tooltipLabel),
11028
+ ]);
11029
+ },
11030
+ };
11031
+ };
11032
+ return {
11033
+ oninit: ({ attrs }) => {
11034
+ const controlled = isControlled(attrs);
11035
+ const isNonInteractive = attrs.readonly || attrs.disabled;
11036
+ // Warn developer for improper controlled usage
11037
+ if (attrs.value !== undefined && !controlled && !isNonInteractive) {
11038
+ console.warn(`LikertScale component received 'value' prop without 'onchange' handler. ` +
11039
+ `Use 'defaultValue' for uncontrolled components or add 'onchange' for controlled components.`);
11040
+ }
11041
+ if (!controlled) {
11042
+ state.internalValue = attrs.defaultValue;
11043
+ }
11044
+ },
11045
+ view: ({ attrs }) => {
11046
+ const { min = 1, max = 5, step = 1, size = 'medium', density = 'standard', layout = 'responsive', className = '', style = {}, readonly = false, disabled = false, id = state.id, name, label, description, isMandatory, startLabel, middleLabel, endLabel, showNumbers = false, showTooltips = false, tooltipLabels, alignLabels = false } = attrs, ariaAttrs = __rest(attrs, ["min", "max", "step", "size", "density", "layout", "className", "style", "readonly", "disabled", "id", "name", "label", "description", "isMandatory", "startLabel", "middleLabel", "endLabel", "showNumbers", "showTooltips", "tooltipLabels", "alignLabels"]);
11047
+ const currentValue = getCurrentValue(attrs);
11048
+ const itemCount = Math.floor((max - min) / step) + 1;
11049
+ // Generate scale values
11050
+ const scaleValues = Array.from({ length: itemCount }, (_, i) => min + i * step);
11051
+ return m('.likert-scale', {
11052
+ className: [
11053
+ 'likert-scale',
11054
+ getSizeClass(size),
11055
+ getDensityClass(density),
11056
+ getLayoutClass(layout),
11057
+ readonly ? 'likert-scale--readonly' : '',
11058
+ disabled ? 'likert-scale--disabled' : '',
11059
+ state.isFocused ? 'likert-scale--focused' : '',
11060
+ alignLabels ? 'likert-scale--aligned' : '',
11061
+ className,
11062
+ ]
11063
+ .filter(Boolean)
11064
+ .join(' '),
11065
+ style,
11066
+ id,
11067
+ role: 'radiogroup',
11068
+ 'aria-label': ariaAttrs['aria-label'] || attrs.ariaLabel || label || `Rating scale from ${min} to ${max}`,
11069
+ 'aria-labelledby': ariaAttrs['aria-labelledby'],
11070
+ 'aria-readonly': readonly,
11071
+ 'aria-disabled': disabled,
11072
+ onkeydown: (e) => handleKeyDown(attrs, e),
11073
+ onfocus: () => {
11074
+ state.isFocused = true;
11075
+ },
11076
+ onblur: () => {
11077
+ state.isFocused = false;
11078
+ },
11079
+ tabindex: readonly || disabled ? -1 : 0,
11080
+ }, [
11081
+ // Label section (only text label, not the description)
11082
+ label &&
11083
+ m('.likert-scale__question-label', [
11084
+ m('span', label + (isMandatory ? ' *' : '')),
11085
+ description && m('.likert-scale__description', m.trust(description)),
11086
+ ]),
11087
+ // Scale section container
11088
+ m('.likert-scale__scale-container', [
11089
+ // Scale items with numbers
11090
+ m('.likert-scale__scale', scaleValues.map((value) => m(LikertScaleItem, {
11091
+ key: `likert-item-${value}`,
11092
+ value,
11093
+ currentValue,
11094
+ showNumber: showNumbers,
11095
+ showTooltip: showTooltips,
11096
+ tooltipLabel: tooltipLabels === null || tooltipLabels === void 0 ? void 0 : tooltipLabels[value - min],
11097
+ groupId: state.groupId,
11098
+ name,
11099
+ disabled,
11100
+ readonly,
11101
+ onchange: (v) => handleChange(attrs, v),
11102
+ }))),
11103
+ // Scale anchors
11104
+ (startLabel || middleLabel || endLabel) &&
11105
+ m('.likert-scale__anchors', [
11106
+ startLabel && m('.likert-scale__anchor.likert-scale__anchor--start', startLabel),
11107
+ middleLabel && m('.likert-scale__anchor.likert-scale__anchor--middle', middleLabel),
11108
+ endLabel && m('.likert-scale__anchor.likert-scale__anchor--end', endLabel),
11109
+ ]),
11110
+ ]),
11111
+ // Screen reader text
11112
+ m('.likert-scale__sr-only', {
11113
+ className: 'likert-scale__sr-only',
11114
+ 'aria-live': 'polite',
11115
+ 'aria-atomic': 'true',
11116
+ }, getLabelText(currentValue, min, max, attrs.getLabelText)),
11117
+ ]);
11118
+ },
11119
+ };
11120
+ };
11121
+
10877
11122
  /**
10878
11123
  * ToggleButton component.
10879
11124
  *
@@ -11225,6 +11470,7 @@ exports.ImageList = ImageList;
11225
11470
  exports.InputCheckbox = InputCheckbox;
11226
11471
  exports.Label = Label;
11227
11472
  exports.LargeButton = LargeButton;
11473
+ exports.LikertScale = LikertScale;
11228
11474
  exports.LinearProgress = LinearProgress;
11229
11475
  exports.ListItem = ListItem;
11230
11476
  exports.Mandatory = Mandatory;