mithril-materialized 3.12.0 → 3.14.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
@@ -618,6 +618,85 @@ const Icon = () => ({
618
618
  },
619
619
  });
620
620
 
621
+ const iconPaths = {
622
+ caret: [
623
+ 'M7 10l5 5 5-5z', // arrow
624
+ 'M0 0h24v24H0z', // background
625
+ ],
626
+ close: [
627
+ 'M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7A1 1 0 0 0 5.7 7.11L10.59 12l-4.89 4.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 0 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z',
628
+ 'M0 0h24v24H0z',
629
+ ],
630
+ chevron: [
631
+ 'M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z', // chevron down
632
+ 'M0 0h24v24H0z', // background
633
+ ],
634
+ chevron_left: [
635
+ 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z', // chevron left
636
+ 'M0 0h24v24H0z', // background
637
+ ],
638
+ chevron_right: [
639
+ 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z', // chevron right
640
+ 'M0 0h24v24H0z', // background
641
+ ],
642
+ menu: [
643
+ 'M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z', // hamburger menu
644
+ 'M0 0h24v24H0z', // background
645
+ ],
646
+ expand: [
647
+ 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z', // plus
648
+ 'M0 0h24v24H0z', // background
649
+ ],
650
+ collapse: [
651
+ 'M19 13H5v-2h14v2z', // minus
652
+ 'M0 0h24v24H0z', // background
653
+ ],
654
+ check: [
655
+ 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z', // checkmark
656
+ 'M0 0h24v24H0z', // background
657
+ ],
658
+ radio_checked: [
659
+ 'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z', // radio button checked
660
+ 'M0 0h24v24H0z', // background
661
+ ],
662
+ radio_unchecked: [
663
+ 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z', // radio button unchecked
664
+ 'M0 0h24v24H0z', // background
665
+ ],
666
+ light_mode: [
667
+ 'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5M2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1m18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1M11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1m0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1M5.99 4.58a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41zm12.4 12.4a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0a.996.996 0 0 0 0-1.41zm1.06-11a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0zM7.05 18.4a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0z',
668
+ 'M0 0h24v24H0z', // background
669
+ ],
670
+ dark_mode: [
671
+ 'M12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.39 5.39 0 0 1-4.4 2.26 5.4 5.4 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z',
672
+ 'M0 0h24v24H0z', // background
673
+ ],
674
+ delete: [
675
+ 'M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z',
676
+ 'M0 0h24v24H0z', // background
677
+ ],
678
+ };
679
+ const MaterialIcon = () => {
680
+ return {
681
+ view: ({ attrs }) => {
682
+ var _a;
683
+ const { name, direction = 'down', style } = attrs, props = __rest(attrs, ["name", "direction", "style"]);
684
+ const rotationMap = {
685
+ down: 0,
686
+ up: 180,
687
+ left: 90,
688
+ right: -90,
689
+ };
690
+ const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
691
+ const transform = rotation ? `rotate(${rotation}deg)` : undefined;
692
+ return m('svg', Object.assign(Object.assign({}, props), { style: Object.assign({ transform }, style), height: '24px', width: '24px', viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' }), iconPaths[name].map((d) => m('path', {
693
+ d,
694
+ fill: d.includes('M0 0h24v24H0z') ? 'none' : 'currentColor',
695
+ })));
696
+ },
697
+ };
698
+ };
699
+
621
700
  /*!
622
701
  * Waves Effect for Mithril Materialized
623
702
  * Based on Waves v0.6.4 by Alfiana E. Sibuea
@@ -723,7 +802,7 @@ WavesEffect.onTouchEnd = (e) => {
723
802
  const ButtonFactory = (element, defaultClassNames, type = '') => {
724
803
  return () => {
725
804
  return {
726
- view: ({ attrs }) => {
805
+ view: ({ attrs, children }) => {
727
806
  const { tooltip, tooltipPosition, tooltipPostion, // Keep for backwards compatibility
728
807
  iconName, iconClass, label, className, variant } = attrs, params = __rest(attrs, ["tooltip", "tooltipPosition", "tooltipPostion", "iconName", "iconClass", "label", "className", "variant"]);
729
808
  // Use variant or fallback to factory type
@@ -739,7 +818,7 @@ const ButtonFactory = (element, defaultClassNames, type = '') => {
739
818
  ontouchstart: WavesEffect.onTouchStart,
740
819
  ontouchend: WavesEffect.onTouchEnd
741
820
  } : {};
742
- return m(element, Object.assign(Object.assign(Object.assign({}, params), wavesHandlers), { className: cn, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined, type: buttonType }), iconName ? m(Icon, { iconName, className: iconClass || 'left' }) : undefined, label ? label : undefined);
821
+ return m(element, Object.assign(Object.assign(Object.assign({}, params), wavesHandlers), { className: cn, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined, type: buttonType }), iconName ? m(Icon, { iconName, className: iconClass || 'left' }) : undefined, label ? label : undefined, children);
743
822
  },
744
823
  };
745
824
  };
@@ -751,6 +830,54 @@ const FlatButton = ButtonFactory('a', 'waves-effect waves-teal btn-flat', 'butto
751
830
  const IconButton = ButtonFactory('button', 'btn-flat btn-icon waves-effect waves-teal', 'button');
752
831
  const RoundIconButton = ButtonFactory('button', 'btn-floating btn-large waves-effect waves-light', 'button');
753
832
  const SubmitButton = ButtonFactory('button', 'btn waves-effect waves-light', 'submit');
833
+ const RaisedIconButton = ButtonFactory('button', 'btn waves-effect waves-light', 'button');
834
+ const ConfirmButton = () => {
835
+ let isConfirming = false;
836
+ let isBlocked = false;
837
+ let timeoutId;
838
+ let blockTimeoutId;
839
+ const reset = () => {
840
+ isConfirming = false;
841
+ isBlocked = false;
842
+ m.redraw();
843
+ };
844
+ const unblock = () => {
845
+ isBlocked = false;
846
+ };
847
+ return {
848
+ onremove: () => {
849
+ window.clearTimeout(timeoutId);
850
+ window.clearTimeout(blockTimeoutId);
851
+ },
852
+ view: ({ attrs }) => {
853
+ const { iconName = 'delete', confirmIconName = 'check', confirmColor = 'red', timeout = 3000, clickDelay = 500, onFirstClick, onclick } = attrs, props = __rest(attrs, ["iconName", "confirmIconName", "confirmColor", "timeout", "clickDelay", "onFirstClick", "onclick"]);
854
+ const handleClick = (e) => {
855
+ e.preventDefault();
856
+ if (isBlocked)
857
+ return;
858
+ if (isConfirming) {
859
+ window.clearTimeout(timeoutId);
860
+ window.clearTimeout(blockTimeoutId); // Clean up safety
861
+ isConfirming = false;
862
+ onclick === null || onclick === void 0 ? void 0 : onclick(e);
863
+ }
864
+ else {
865
+ isConfirming = true;
866
+ isBlocked = true;
867
+ onFirstClick === null || onFirstClick === void 0 ? void 0 : onFirstClick();
868
+ timeoutId = window.setTimeout(reset, timeout);
869
+ blockTimeoutId = window.setTimeout(unblock, clickDelay);
870
+ }
871
+ };
872
+ const cn = isConfirming ? confirmColor : 'red-text';
873
+ const commonProps = Object.assign(Object.assign({}, props), { className: `${props.className || ''} ${cn}`, style: Object.assign(Object.assign(Object.assign({}, props.style), { display: 'flex', alignItems: 'center', justifyContent: 'center' }), (isConfirming ? { padding: '0 8px', width: 'auto', minWidth: 'auto' } : {})), onclick: handleClick });
874
+ if (isConfirming) {
875
+ return m(RaisedIconButton, commonProps, m(MaterialIcon, { name: confirmIconName }));
876
+ }
877
+ return m(IconButton, commonProps, m(MaterialIcon, { name: iconName }));
878
+ },
879
+ };
880
+ };
754
881
 
755
882
  /**
756
883
  * Materialize CSS Carousel component with dynamic positioning
@@ -1142,81 +1269,6 @@ const Carousel = () => {
1142
1269
  };
1143
1270
  };
1144
1271
 
1145
- const iconPaths = {
1146
- caret: [
1147
- 'M7 10l5 5 5-5z', // arrow
1148
- 'M0 0h24v24H0z', // background
1149
- ],
1150
- close: [
1151
- 'M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7A1 1 0 0 0 5.7 7.11L10.59 12l-4.89 4.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 0 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z',
1152
- 'M0 0h24v24H0z',
1153
- ],
1154
- chevron: [
1155
- 'M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z', // chevron down
1156
- 'M0 0h24v24H0z', // background
1157
- ],
1158
- chevron_left: [
1159
- 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z', // chevron left
1160
- 'M0 0h24v24H0z', // background
1161
- ],
1162
- chevron_right: [
1163
- 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z', // chevron right
1164
- 'M0 0h24v24H0z', // background
1165
- ],
1166
- menu: [
1167
- 'M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z', // hamburger menu
1168
- 'M0 0h24v24H0z', // background
1169
- ],
1170
- expand: [
1171
- 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z', // plus
1172
- 'M0 0h24v24H0z', // background
1173
- ],
1174
- collapse: [
1175
- 'M19 13H5v-2h14v2z', // minus
1176
- 'M0 0h24v24H0z', // background
1177
- ],
1178
- check: [
1179
- 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z', // checkmark
1180
- 'M0 0h24v24H0z', // background
1181
- ],
1182
- radio_checked: [
1183
- 'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z', // radio button checked
1184
- 'M0 0h24v24H0z', // background
1185
- ],
1186
- radio_unchecked: [
1187
- 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z', // radio button unchecked
1188
- 'M0 0h24v24H0z', // background
1189
- ],
1190
- light_mode: [
1191
- 'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5M2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1m18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1M11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1m0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1M5.99 4.58a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41zm12.4 12.4a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0a.996.996 0 0 0 0-1.41zm1.06-11a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0zM7.05 18.4a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0z',
1192
- 'M0 0h24v24H0z', // background
1193
- ],
1194
- dark_mode: [
1195
- 'M12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.39 5.39 0 0 1-4.4 2.26 5.4 5.4 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z',
1196
- 'M0 0h24v24H0z', // background
1197
- ],
1198
- };
1199
- const MaterialIcon = () => {
1200
- return {
1201
- view: ({ attrs }) => {
1202
- var _a;
1203
- const { name, direction = 'down', style } = attrs, props = __rest(attrs, ["name", "direction", "style"]);
1204
- const rotationMap = {
1205
- down: 0,
1206
- up: 180,
1207
- left: 90,
1208
- right: -90,
1209
- };
1210
- const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
1211
- const transform = rotation ? `rotate(${rotation}deg)` : undefined;
1212
- return m('svg', Object.assign(Object.assign({}, props), { style: Object.assign({ transform }, style), height: '24px', width: '24px', viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' }), iconPaths[name].map((d) => m('path', {
1213
- d,
1214
- fill: d.includes('M0 0h24v24H0z') ? 'none' : 'currentColor',
1215
- })));
1216
- },
1217
- };
1218
- };
1219
-
1220
1272
  const Chips = () => {
1221
1273
  const state = {
1222
1274
  chipsData: [],
@@ -1635,7 +1687,7 @@ const avatarIsImage = (avatar = '') => /\./.test(avatar);
1635
1687
  const ListItem = () => {
1636
1688
  return {
1637
1689
  view: ({ attrs: { item, mode } }) => {
1638
- const { title, content = '', active, iconName, avatar, className, onclick } = item;
1690
+ const { title, content, active, iconName, avatar, className, onclick } = item;
1639
1691
  return mode === CollectionMode.AVATAR
1640
1692
  ? m('li.collection-item.avatar', {
1641
1693
  className: active ? 'active' : '',
@@ -1645,12 +1697,21 @@ const ListItem = () => {
1645
1697
  ? m('img.circle', { src: avatar })
1646
1698
  : m('i.material-icons.circle', { className }, avatar),
1647
1699
  m('span.title', title),
1648
- m('p', m.trust(content)),
1649
- m(SecondaryContent, item),
1650
- ])
1700
+ content ? (typeof content === 'string' ? m('p', m.trust(content)) : m('p', content)) : undefined,
1701
+ iconName ? m(SecondaryContent, item) : undefined,
1702
+ ].filter(Boolean))
1651
1703
  : m('li.collection-item', {
1652
1704
  className: active ? 'active' : '',
1653
- }, iconName ? m('div', [title, m(SecondaryContent, item)]) : title);
1705
+ onclick: onclick ? () => onclick(item) : undefined,
1706
+ }, content
1707
+ ? m('div', [
1708
+ m('div', title),
1709
+ typeof content === 'string' ? m('p.secondary-text', content) : content,
1710
+ iconName ? m(SecondaryContent, item) : undefined,
1711
+ ].filter(Boolean))
1712
+ : iconName
1713
+ ? m('div', [title, m(SecondaryContent, item)])
1714
+ : title);
1654
1715
  },
1655
1716
  };
1656
1717
  };
@@ -10751,12 +10812,12 @@ const Rating = () => {
10751
10812
  const RatingItem = () => {
10752
10813
  return {
10753
10814
  view: ({ attrs }) => {
10754
- const { index, displayValue, step, icons, allowHalfSteps, disabled, onclick, onmouseover } = attrs;
10815
+ const { index, displayValue, step, icons, allowHalfSteps, disabled, showTooltip, tooltipLabel, onclick, onmouseover, } = attrs;
10755
10816
  const itemValue = (index + 1) * step;
10756
10817
  // Calculate fill state based on displayValue vs itemValue
10757
10818
  const diff = displayValue - itemValue;
10758
10819
  const fillState = diff >= 0 ? 'full' : allowHalfSteps && diff >= -step / 2 ? 'half' : 'empty';
10759
- return m('.rating__item', {
10820
+ return m('.rating__item.no-select', {
10760
10821
  className: [
10761
10822
  fillState === 'full' ? 'rating__item--filled' : '',
10762
10823
  fillState === 'half' ? 'rating__item--half' : '',
@@ -10777,6 +10838,8 @@ const Rating = () => {
10777
10838
  clipPath: fillState === 'half' ? 'inset(0 50% 0 0)' : undefined,
10778
10839
  },
10779
10840
  }, typeof icons.filled === 'string' ? icons.filled : m(icons.filled)),
10841
+ // Tooltip
10842
+ showTooltip && tooltipLabel && m('.rating__tooltip', tooltipLabel),
10780
10843
  ]);
10781
10844
  },
10782
10845
  };
@@ -10848,6 +10911,7 @@ const Rating = () => {
10848
10911
  },
10849
10912
  // Array.from({ length: itemCount }, (_, i) => renderRatingItem(attrs, i))
10850
10913
  [...Array(itemCount)].map((_, i) => {
10914
+ var _a;
10851
10915
  const itemValue = (i + 1) * step;
10852
10916
  return m(RatingItem, {
10853
10917
  key: `rating-item-${i}`,
@@ -10857,6 +10921,8 @@ const Rating = () => {
10857
10921
  icons: Object.assign(Object.assign({}, DEFAULT_ICONS), attrs.icon),
10858
10922
  allowHalfSteps: attrs.allowHalfSteps,
10859
10923
  disabled: attrs.disabled,
10924
+ showTooltip: attrs.showTooltips,
10925
+ tooltipLabel: (_a = attrs.tooltipLabels) === null || _a === void 0 ? void 0 : _a[i],
10860
10926
  onclick: () => handleItemClick(attrs, itemValue),
10861
10927
  onmouseover: () => handleItemHover(attrs, itemValue),
10862
10928
  });
@@ -10872,6 +10938,237 @@ const Rating = () => {
10872
10938
  };
10873
10939
  };
10874
10940
 
10941
+ /** Create a LikertScale component */
10942
+ const LikertScale = () => {
10943
+ const state = {
10944
+ id: uniqueId(),
10945
+ groupId: uniqueId(),
10946
+ internalValue: undefined,
10947
+ isFocused: false,
10948
+ };
10949
+ const isControlled = (attrs) => typeof attrs.value !== 'undefined' && typeof attrs.onchange === 'function';
10950
+ const getCurrentValue = (attrs) => {
10951
+ var _a, _b;
10952
+ const controlled = isControlled(attrs);
10953
+ const isNonInteractive = attrs.readonly || attrs.disabled;
10954
+ if (controlled) {
10955
+ return attrs.value;
10956
+ }
10957
+ // Non-interactive components: prefer defaultValue, fallback to value
10958
+ if (isNonInteractive) {
10959
+ return (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : attrs.value;
10960
+ }
10961
+ // Interactive uncontrolled: use internal state (user can change it)
10962
+ return (_b = state.internalValue) !== null && _b !== void 0 ? _b : attrs.defaultValue;
10963
+ };
10964
+ const getLabelText = (value, min, max, getLabelFn) => {
10965
+ if (getLabelFn && value !== undefined) {
10966
+ return getLabelFn(value, min, max);
10967
+ }
10968
+ if (value === undefined) {
10969
+ return `No selection, please choose a value between ${min} and ${max}`;
10970
+ }
10971
+ return `Selected ${value} out of ${min} to ${max}`;
10972
+ };
10973
+ const getSizeClass = (size = 'medium') => {
10974
+ switch (size) {
10975
+ case 'small':
10976
+ return 'likert-scale--small';
10977
+ case 'large':
10978
+ return 'likert-scale--large';
10979
+ default:
10980
+ return 'likert-scale--medium';
10981
+ }
10982
+ };
10983
+ const getDensityClass = (density = 'standard') => {
10984
+ switch (density) {
10985
+ case 'compact':
10986
+ return 'likert-scale--compact';
10987
+ case 'comfortable':
10988
+ return 'likert-scale--comfortable';
10989
+ default:
10990
+ return 'likert-scale--standard';
10991
+ }
10992
+ };
10993
+ const getLayoutClass = (layout = 'responsive') => {
10994
+ switch (layout) {
10995
+ case 'horizontal':
10996
+ return 'likert-scale--horizontal';
10997
+ case 'vertical':
10998
+ return 'likert-scale--vertical';
10999
+ default:
11000
+ return 'likert-scale--responsive';
11001
+ }
11002
+ };
11003
+ const handleChange = (attrs, newValue) => {
11004
+ var _a;
11005
+ if (attrs.readonly || attrs.disabled)
11006
+ return;
11007
+ if (!isControlled(attrs)) {
11008
+ state.internalValue = newValue;
11009
+ }
11010
+ (_a = attrs.onchange) === null || _a === void 0 ? void 0 : _a.call(attrs, newValue);
11011
+ };
11012
+ const handleKeyDown = (attrs, e) => {
11013
+ if (attrs.readonly || attrs.disabled)
11014
+ return;
11015
+ const min = attrs.min || 1;
11016
+ const max = attrs.max || 5;
11017
+ const step = attrs.step || 1;
11018
+ const currentValue = getCurrentValue(attrs);
11019
+ let newValue = currentValue;
11020
+ switch (e.key) {
11021
+ case 'ArrowRight':
11022
+ case 'ArrowUp':
11023
+ e.preventDefault();
11024
+ newValue = currentValue !== undefined ? Math.min(max, currentValue + step) : min;
11025
+ break;
11026
+ case 'ArrowLeft':
11027
+ case 'ArrowDown':
11028
+ e.preventDefault();
11029
+ newValue = currentValue !== undefined ? Math.max(min, currentValue - step) : min;
11030
+ break;
11031
+ case 'Home':
11032
+ e.preventDefault();
11033
+ newValue = min;
11034
+ break;
11035
+ case 'End':
11036
+ e.preventDefault();
11037
+ newValue = max;
11038
+ break;
11039
+ default:
11040
+ return;
11041
+ }
11042
+ if (newValue !== currentValue) {
11043
+ handleChange(attrs, newValue);
11044
+ }
11045
+ };
11046
+ const LikertScaleItem = () => {
11047
+ return {
11048
+ view: ({ attrs }) => {
11049
+ const { value, currentValue, showNumber, showTooltip, tooltipLabel, groupId, name, disabled, readonly, onchange, } = attrs;
11050
+ const radioId = `${groupId}-${value}`;
11051
+ const isChecked = currentValue === value;
11052
+ return m('.likert-scale__item.no-select', {
11053
+ className: [
11054
+ isChecked ? 'likert-scale__item--checked' : '',
11055
+ disabled ? 'likert-scale__item--disabled' : '',
11056
+ readonly ? 'likert-scale__item--readonly' : '',
11057
+ ]
11058
+ .filter(Boolean)
11059
+ .join(' '),
11060
+ }, [
11061
+ // Number label (optional)
11062
+ showNumber && m('.likert-scale__number', value),
11063
+ // Radio button input
11064
+ m('input[type=radio].likert-scale__input', {
11065
+ id: radioId,
11066
+ name: name || groupId,
11067
+ value: value,
11068
+ checked: isChecked,
11069
+ disabled: disabled || readonly,
11070
+ onchange: () => onchange(value),
11071
+ }),
11072
+ // Label for radio button
11073
+ m('label.likert-scale__label', {
11074
+ for: radioId,
11075
+ }),
11076
+ // Tooltip (optional)
11077
+ showTooltip && tooltipLabel && m('.likert-scale__tooltip', tooltipLabel),
11078
+ ]);
11079
+ },
11080
+ };
11081
+ };
11082
+ return {
11083
+ oninit: ({ attrs }) => {
11084
+ const controlled = isControlled(attrs);
11085
+ const isNonInteractive = attrs.readonly || attrs.disabled;
11086
+ // Warn developer for improper controlled usage
11087
+ if (attrs.value !== undefined && !controlled && !isNonInteractive) {
11088
+ console.warn(`LikertScale component received 'value' prop without 'onchange' handler. ` +
11089
+ `Use 'defaultValue' for uncontrolled components or add 'onchange' for controlled components.`);
11090
+ }
11091
+ if (!controlled) {
11092
+ state.internalValue = attrs.defaultValue;
11093
+ }
11094
+ },
11095
+ view: ({ attrs }) => {
11096
+ 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"]);
11097
+ const currentValue = getCurrentValue(attrs);
11098
+ const itemCount = Math.floor((max - min) / step) + 1;
11099
+ // Generate scale values
11100
+ const scaleValues = Array.from({ length: itemCount }, (_, i) => min + i * step);
11101
+ return m('.likert-scale', {
11102
+ className: [
11103
+ 'likert-scale',
11104
+ getSizeClass(size),
11105
+ getDensityClass(density),
11106
+ getLayoutClass(layout),
11107
+ readonly ? 'likert-scale--readonly' : '',
11108
+ disabled ? 'likert-scale--disabled' : '',
11109
+ state.isFocused ? 'likert-scale--focused' : '',
11110
+ alignLabels ? 'likert-scale--aligned' : '',
11111
+ className,
11112
+ ]
11113
+ .filter(Boolean)
11114
+ .join(' '),
11115
+ style,
11116
+ id,
11117
+ role: 'radiogroup',
11118
+ 'aria-label': ariaAttrs['aria-label'] || attrs.ariaLabel || label || `Rating scale from ${min} to ${max}`,
11119
+ 'aria-labelledby': ariaAttrs['aria-labelledby'],
11120
+ 'aria-readonly': readonly,
11121
+ 'aria-disabled': disabled,
11122
+ onkeydown: (e) => handleKeyDown(attrs, e),
11123
+ onfocus: () => {
11124
+ state.isFocused = true;
11125
+ },
11126
+ onblur: () => {
11127
+ state.isFocused = false;
11128
+ },
11129
+ tabindex: readonly || disabled ? -1 : 0,
11130
+ }, [
11131
+ // Label section (only text label, not the description)
11132
+ label &&
11133
+ m('.likert-scale__question-label', [
11134
+ m('span', label + (isMandatory ? ' *' : '')),
11135
+ description && m('.likert-scale__description', m.trust(description)),
11136
+ ]),
11137
+ // Scale section container
11138
+ m('.likert-scale__scale-container', [
11139
+ // Scale items with numbers
11140
+ m('.likert-scale__scale', scaleValues.map((value) => m(LikertScaleItem, {
11141
+ key: `likert-item-${value}`,
11142
+ value,
11143
+ currentValue,
11144
+ showNumber: showNumbers,
11145
+ showTooltip: showTooltips,
11146
+ tooltipLabel: tooltipLabels === null || tooltipLabels === void 0 ? void 0 : tooltipLabels[value - min],
11147
+ groupId: state.groupId,
11148
+ name,
11149
+ disabled,
11150
+ readonly,
11151
+ onchange: (v) => handleChange(attrs, v),
11152
+ }))),
11153
+ // Scale anchors
11154
+ (startLabel || middleLabel || endLabel) &&
11155
+ m('.likert-scale__anchors', [
11156
+ startLabel && m('.likert-scale__anchor.likert-scale__anchor--start', startLabel),
11157
+ middleLabel && m('.likert-scale__anchor.likert-scale__anchor--middle', middleLabel),
11158
+ endLabel && m('.likert-scale__anchor.likert-scale__anchor--end', endLabel),
11159
+ ]),
11160
+ ]),
11161
+ // Screen reader text
11162
+ m('.likert-scale__sr-only', {
11163
+ className: 'likert-scale__sr-only',
11164
+ 'aria-live': 'polite',
11165
+ 'aria-atomic': 'true',
11166
+ }, getLabelText(currentValue, min, max, attrs.getLabelText)),
11167
+ ]);
11168
+ },
11169
+ };
11170
+ };
11171
+
10875
11172
  /**
10876
11173
  * ToggleButton component.
10877
11174
  *
@@ -11189,4 +11486,4 @@ const isValidationError = (result) => !isValidationSuccess(result);
11189
11486
  // ============================================================================
11190
11487
  // All types are already exported via individual export declarations above
11191
11488
 
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 };
11489
+ export { AnalogClock, AnchorItem, Autocomplete, Badge, Breadcrumb, BreadcrumbManager, Button, ButtonFactory, Carousel, CharacterCounter, Chips, CircularProgress, CodeBlock, Collapsible, CollapsibleItem, Collection, CollectionMode, ColorInput, ConfirmButton, 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 };