mithril-materialized 1.4.2 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +112 -351
  2. package/dist/autocomplete.d.ts +12 -4
  3. package/dist/button.d.ts +10 -10
  4. package/dist/carousel.d.ts +26 -7
  5. package/dist/chip.d.ts +2 -2
  6. package/dist/code-block.d.ts +2 -3
  7. package/dist/collapsible.d.ts +12 -7
  8. package/dist/collection.d.ts +8 -8
  9. package/dist/dropdown.d.ts +5 -5
  10. package/dist/floating-action-button.d.ts +9 -5
  11. package/dist/icon.d.ts +2 -2
  12. package/dist/index.css +7968 -2
  13. package/dist/index.d.ts +1 -2
  14. package/dist/index.esm.js +4211 -2
  15. package/dist/index.js +4265 -2
  16. package/dist/index.umd.js +4269 -2
  17. package/dist/input-options.d.ts +3 -1
  18. package/dist/input.d.ts +17 -12
  19. package/dist/label.d.ts +4 -5
  20. package/dist/material-box.d.ts +22 -4
  21. package/dist/material-icon.d.ts +14 -0
  22. package/dist/modal.d.ts +23 -7
  23. package/dist/option.d.ts +12 -11
  24. package/dist/pagination.d.ts +5 -9
  25. package/dist/parallax.d.ts +8 -6
  26. package/dist/pickers.d.ts +129 -5
  27. package/dist/radio.d.ts +10 -6
  28. package/dist/search-select.d.ts +2 -2
  29. package/dist/select.d.ts +5 -5
  30. package/dist/switch.d.ts +4 -5
  31. package/dist/tabs.d.ts +18 -5
  32. package/dist/utils.d.ts +17 -0
  33. package/package.json +35 -10
  34. package/sass/components/_badges.scss +59 -0
  35. package/sass/components/_buttons.scss +327 -0
  36. package/sass/components/_cards.scss +197 -0
  37. package/sass/components/_carousel.scss +92 -0
  38. package/sass/components/_chips.scss +92 -0
  39. package/sass/components/_collapsible.scss +94 -0
  40. package/sass/components/_color-classes.scss +34 -0
  41. package/sass/components/_color-variables.scss +371 -0
  42. package/sass/components/_datepicker.scss +251 -0
  43. package/sass/components/_dropdown.scss +90 -0
  44. package/sass/components/_global.scss +775 -0
  45. package/sass/components/_grid.scss +160 -0
  46. package/sass/components/_icons-material-design.scss +5 -0
  47. package/sass/components/_materialbox.scss +43 -0
  48. package/sass/components/_modal.scss +97 -0
  49. package/sass/components/_navbar.scss +211 -0
  50. package/sass/components/_normalize.scss +447 -0
  51. package/sass/components/_preloader.scss +336 -0
  52. package/sass/components/_pulse.scss +34 -0
  53. package/sass/components/_sidenav.scss +213 -0
  54. package/sass/components/_slider.scss +94 -0
  55. package/sass/components/_table_of_contents.scss +36 -0
  56. package/sass/components/_tabs.scss +102 -0
  57. package/sass/components/_tapTarget.scss +105 -0
  58. package/sass/components/_timepicker.scss +170 -0
  59. package/sass/components/_toast.scss +61 -0
  60. package/sass/components/_tooltip.scss +32 -0
  61. package/sass/components/_transitions.scss +13 -0
  62. package/sass/components/_typography.scss +61 -0
  63. package/sass/components/_variables.scss +352 -0
  64. package/sass/components/_waves.scss +114 -0
  65. package/sass/components/forms/_checkboxes.scss +203 -0
  66. package/sass/components/forms/_file-input.scss +50 -0
  67. package/sass/components/forms/_form-groups.scss +28 -0
  68. package/sass/components/forms/_forms.scss +24 -0
  69. package/sass/components/forms/_input-fields.scss +361 -0
  70. package/sass/components/forms/_radio-buttons.scss +118 -0
  71. package/sass/components/forms/_range.scss +164 -0
  72. package/sass/components/forms/_select.scss +193 -0
  73. package/sass/components/forms/_switches.scss +92 -0
  74. package/sass/materialize.scss +41 -0
  75. package/dist/index.css.map +0 -1
  76. package/dist/index.esm.js.map +0 -1
  77. package/dist/index.js.map +0 -1
  78. package/dist/index.modern.js +0 -2
  79. package/dist/index.modern.js.map +0 -1
  80. package/dist/index.umd.js.map +0 -1
  81. package/dist/map-editor.d.ts +0 -63
  82. package/dist/timeline.d.ts +0 -24
package/dist/index.umd.js CHANGED
@@ -1,2 +1,4269 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("mithril")):"function"==typeof define&&define.amd?define(["exports","mithril"],t):t((e||self).mithrilMaterialized={},e.mithril)}(this,function(e,t){function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=/*#__PURE__*/a(t);function i(){return i=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var a=arguments[t];for(var n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n])}return e},i.apply(this,arguments)}function o(e,t){if(null==e)return{};var a,n,i={},o=Object.keys(e);for(n=0;n<o.length;n++)t.indexOf(a=o[n])>=0||(i[a]=e[a]);return i}var l,r=function(){return"idxxxxxxxx".replace(/[x]/g,function(){return(16*Math.random()|0).toString(16)})},d=function(e){return!isNaN(parseFloat(e))&&isFinite(e)},c=function(e,t,a){return void 0===t&&(t=2),void 0===a&&(a="0"),String(e).padStart(t,a)},u=["label","id","isMandatory","isActive","className"],s={view:function(e){return n.default("span.mandatory",e.attrs,"*")}},f=function(){return{view:function(e){var t=e.attrs,a=t.label,l=t.id,r=t.isMandatory,d=t.isActive,c=t.className,f=o(t,u);return a?n.default("label",i({},f,{className:[c,d?"active":""].filter(Boolean).join(" ").trim(),for:l}),[n.default.trust(a),r?n.default(s):void 0]):void 0}}},p=function(){return{view:function(e){var t=e.attrs,a=t.helperText,i=t.dataError,o=t.dataSuccess;return a||i||o?n.default("span.helper-text.left",{className:t.className,"data-error":i,"data-success":o},a?n.default.trust(a):""):void 0}}},m=["label","helperText","initialValue","onchange","newRow","className","style","iconName","isMandatory"],v=["iconName"],h=function(){return{view:function(e){var t=e.attrs,a=t.iconName,i=o(t,v);return n.default("i.material-icons",i,a)}}},g=["modalId","tooltip","tooltipPostion","iconName","iconClass","label","className","attr"],b=function(e,t,a){return void 0===a&&(a=""),function(){return{view:function(l){var r=l.attrs,d=r.modalId,c=r.tooltip,u=r.tooltipPostion,s=r.iconName,f=r.iconClass,p=r.label,m=r.className,v=r.attr,b=o(r,g),y=[d?"modal-trigger":"",c?"tooltipped":"",t,m].filter(Boolean).join(" ").trim();return n.default(e,i({},b,v,{className:y,href:d?"#"+d:void 0,"data-position":c?u||"top":void 0,"data-tooltip":c||void 0,type:a}),s?n.default(h,{iconName:s,className:f||"left"}):void 0,p||void 0)}}}},y=b("a","waves-effect waves-light btn","button"),w=b("a","waves-effect waves-light btn-large","button"),x=b("a","waves-effect waves-light btn-small","button"),k=b("a","waves-effect waves-teal btn-flat","button"),N=b("button","btn-floating btn-large waves-effect waves-light","button"),I=b("button","btn waves-effect waves-light","submit"),C=["href","src","alt"],A=function(){return{view:function(e){var t=e.attrs,a=t.href,l=t.src,r=t.alt,d=o(t,C);return n.default("a.carousel-item",i({},d,{href:a}),n.default("img",{src:l,alt:r}))}}},T=["newRow","code","language","className"],D=function(){return{view:function(e){var t=e.attrs,a=t.header,i=t.body,o=t.iconName;return n.default(t.active?"li.active":"li",[a||o?n.default(".collapsible-header",[o?n.default("i.material-icons",o):void 0,a?"string"==typeof a?n.default("span",a):a:void 0]):void 0,i?n.default(".collapsible-body",i):void 0])}}},S=["header","items","mode"],O=["title","active","href"],V=["items","header"],R=["items","header","mode"];e.CollectionMode=void 0,(l=e.CollectionMode||(e.CollectionMode={}))[l.BASIC=0]="BASIC",l[l.LINKS=1]="LINKS",l[l.AVATAR=2]="AVATAR";var B=function(e){return e&&/https?:\/\//.test(e)},L=function(){return{view:function(e){var t=e.attrs,a=t.href,i=t.iconName,o=void 0===i?"send":i,l=t.onclick,r=t.style,d={href:a,style:void 0===r?{cursor:"pointer"}:r,className:"secondary-content",onclick:l?function(){return l(t)}:void 0};return B(a)||!a?n.default("a[target=_]",d,n.default(h,{iconName:o})):n.default(n.default.route.Link,d,n.default(h,{iconName:o}))}}},j=function(e){return void 0===e&&(e=""),/\./.test(e)},E=function(){return{view:function(t){var a=t.attrs,i=a.item,o=i.title,l=i.content,r=void 0===l?"":l,d=i.active,c=i.iconName,u=i.avatar,s=i.className,f=i.onclick;return a.mode===e.CollectionMode.AVATAR?n.default("li.collection-item.avatar",{className:d?"active":"",onclick:f?function(){return f(i)}:void 0},[j(u)?n.default("img.circle",{src:u}):n.default("i.material-icons.circle",{className:s},u),n.default("span.title",o),n.default("p",n.default.trust(r)),n.default(L,i)]):n.default("li.collection-item",{className:d?"active":""},c?n.default("div",[o,n.default(L,i)]):o)}}},F=function(){return{view:function(t){var a=t.attrs,i=a.header,l=a.items,r=a.mode,d=void 0===r?e.CollectionMode.BASIC:r,c=o(a,S),u=l.map(function(e){return n.default(E,{key:e.id,item:e,mode:d})});return i?n.default("ul.collection.with-header",c,[n.default("li.collection-header",n.default("h4",i)),u]):n.default("ul.collection",c,u)}}},P=function(){return{view:function(e){var t=e.attrs.item,a=t.title,l=t.active,r=t.href,d=i({},o(t,O),{className:"collection-item "+(l?"active":""),href:r});return B(r)||!r?n.default("a[target=_]",d,a):n.default(n.default.route.Link,d,a)}}},K=function(){return{view:function(e){var t=e.attrs,a=t.items,i=t.header,l=o(t,V);return i?n.default(".collection.with-header",l,[n.default(".collection-header",n.default("h4",i)),a.map(function(e){return n.default(P,{key:e.id,item:e})})]):n.default(".collection",l,a.map(function(e){return n.default(P,{key:e.id,item:e})}))}}},_=function(){return{view:function(t){var a=t.attrs,l=a.items,r=a.header,d=a.mode,c=void 0===d?e.CollectionMode.BASIC:d,u=o(a,R);return r||l&&l.length>0?c===e.CollectionMode.LINKS?n.default(K,i({header:r,items:l},u)):n.default(F,i({header:r,items:l,mode:c},u)):void 0}}},z=["key","label","onchange","disabled","items","iconName","helperText","style","className"],H=["className","iconName","iconClass","position","style","buttons"],U=["className","helperText","iconName","id","initialValue","isMandatory","label","onchange","onkeydown","onkeypress","onkeyup","onblur","style"],q=["className","dataError","dataSuccess","helperText","iconName","id","initialValue","isMandatory","label","maxLength","newRow","onchange","onkeydown","onkeypress","onkeyup","onblur","style","validate"],W=function(){var e={id:r()};return{view:function(t){var a=t.attrs,l=a.className,r=void 0===l?"col s12":l,d=a.helperText,c=a.iconName,u=a.id,s=void 0===u?e.id:u,m=a.initialValue,v=a.isMandatory,h=a.label,g=a.onchange,b=a.onkeydown,y=a.onkeypress,w=a.onkeyup,x=a.onblur,k=a.style,N=o(a,U);return n.default(".input-field",{className:r,style:k},[c?n.default("i.material-icons.prefix",c):"",n.default("textarea.materialize-textarea",i({},N,{id:s,tabindex:0,oncreate:function(e){var t=e.dom;M.textareaAutoResize(t),a.maxLength&&M.CharacterCounter.init(t)},onchange:g?function(e){var t=e.target;g(t&&"string"==typeof t.value?t.value:"")}:void 0,value:m,onkeyup:w?function(e){w(e,e.target.value)}:void 0,onkeydown:b?function(e){b(e,e.target.value)}:void 0,onkeypress:y?function(e){y(e,e.target.value)}:void 0,onblur:x})),n.default(f,{label:h,id:s,isMandatory:v,isActive:m||a.placeholder}),n.default(p,{helperText:d})])}}},Y=function(e,t){return void 0===t&&(t=""),function(){var a={id:r()},l=function(t){var a=t.value;return!a||"number"!==e&&"range"!==e?a:+a},d=function(e,t){e.setCustomValidity("boolean"==typeof t?t?"":"Custom validation failed":t)};return{view:function(r){var c=r.attrs,u=c.className,s=void 0===u?"col s12":u,m=c.dataError,v=c.dataSuccess,h=c.helperText,g=c.iconName,b=c.id,y=void 0===b?a.id:b,w=c.initialValue,x=c.isMandatory,k=c.label,N=c.maxLength,I=c.newRow,C=c.onchange,A=c.onkeydown,T=c.onkeypress,D=c.onkeyup,S=c.onblur,O=c.style,V=c.validate,R=o(c,q),B=[I?"clear":"",t,s].filter(Boolean).join(" ").trim();return n.default(".input-field",{className:B,style:O},[g?n.default("i.material-icons.prefix",g):void 0,n.default("input.validate",i({},R,{type:e,tabindex:0,id:y,oncreate:function(t){var a,n=t.dom;(a=c.autofocus)&&("boolean"==typeof a?a:a())&&n.focus(),N&&M.CharacterCounter.init(n),"range"===e&&M.Range.init(n)},onkeyup:D?function(e){D(e,l(e.target))}:void 0,onkeydown:A?function(e){A(e,l(e.target))}:void 0,onkeypress:T?function(e){T(e,l(e.target))}:void 0,onblur:S,onupdate:V?function(e){var t=e.dom;d(t,V(l(t),t))}:void 0,onchange:function(e){var t=e.target;if(t){var a=l(t);C&&C(a),V&&d(t,V(a,t))}},value:w})),n.default(f,{label:k,id:y,isMandatory:x,isActive:!(void 0===w&&!c.placeholder&&"number"!==e&&"color"!==e&&"range"!==e)}),n.default(p,{helperText:h,dataError:m,dataSuccess:v})])}}}},$=Y("text"),X=Y("password"),G=Y("number"),J=Y("url"),Q=Y("color"),Z=Y("range",".range-field"),ee=Y("email"),te=function(){return{view:function(e){var t=e.attrs,a=t.className,i=t.onchange,o=t.label,l=t.description;return n.default("div",{className:void 0===a?"col s12":a,style:t.style},n.default("label",[n.default("input[type=checkbox][tabindex=0]",{checked:t.checked,disabled:t.disabled,onclick:i?function(e){e.target&&void 0!==e.target.checked&&i(e.target.checked)}:void 0}),o?"string"==typeof o?n.default("span",o):o:void 0]),l&&n.default(p,{className:"input-checkbox-desc",helperText:l}))}}},ae=function(){return{view:function(e){var t=e.attrs,a=t.title;return n.default("li",{className:t.active?"active":t.disabled?"disabled":"waves-effect"},"number"==typeof a?n.default(n.default.route.Link,{href:t.href},a):a)}}},ne=["label","helperText","initialValue","newRow","className","iconName","isMandatory","onchange","disabled"],ie=["label","helperText","initialValue","newRow","className","iconName","isMandatory","onchange","disabled"],oe=function(){return{view:function(e){var t=e.attrs,a=t.id,i=t.label,o=t.onchange,l=t.className;return n.default("div",{className:void 0===l?"col s12":l},n.default("label",[n.default("input[type=radio][tabindex=0]",{name:t.groupId,disabled:t.disabled,checked:t.checked,onclick:o?function(){return o(a)}:void 0}),n.default("span",n.default.trust(i))]))}}},le=["label","left","right","disabled","newRow","onchange","checked","isMandatory","className"],re=function(){return{view:function(e){var t=e.attrs,a=t.id,i=t.title,o=t.datetime,l=t.active,r=t.content,d=t.iconName,c=t.timeFormatter,u=t.onSelect;return n.default("li",{id:a,className:l?"active":void 0,onclick:u?function(){return u({id:a,title:i,datetime:o,active:l,content:r})}:void 0,style:u?"cursor: pointer;":void 0},[n.default(".mm_time",{datetime:o},[n.default("span",(0,t.dateFormatter)(o)),n.default("span",c(o))]),d?n.default(".mm_icon",n.default("i.material-icons",d)):void 0,n.default(".mm_label",[i?"string"==typeof i?n.default("h5",i):i:void 0,r?"string"==typeof r?n.default("p",r):r:void 0])])}}};e.AnchorItem=P,e.Autocomplete=function(){var e={id:r()};return{view:function(t){var a=t.attrs,l=a.id||e.id,r=a.label,d=a.helperText,c=a.initialValue,u=a.onchange,s=a.newRow,v=a.className,h=void 0===v?"col s12":v,g=a.style,b=a.iconName,y=a.isMandatory,w=o(a,m);return n.default(".input-field"+(s?".clear":""),{className:s?h+" clear":h,style:g},[b?n.default("i.material-icons.prefix",b):"",n.default("input",i({},w,{className:"autocomplete",type:"text",tabindex:0,id:l,oncreate:function(e){M.Autocomplete.init(e.dom,a)},onchange:u?function(e){e.target&&e.target.value&&u(e.target.value)}:void 0,value:c})),n.default(f,{label:r,id:l,isMandatory:y,isActive:c}),n.default(p,{helperText:d})])}}},e.Button=y,e.ButtonFactory=b,e.Carousel=function(){return{view:function(e){var t=e.attrs,a=t.items;return a&&a.length>0?n.default(".carousel",{oncreate:function(e){M.Carousel.init(e.dom,t)}},a.map(function(e){return n.default(A,e)})):void 0}}},e.CarouselItem=A,e.Chips=function(){var e={chipsData:[],selectedChip:null,focused:!1,inputValue:"",inputId:r(),autocompleteItems:[],selectedAutocompleteIndex:-1,showAutocomplete:!1},t=null,a=function(){var a;if(null!=(a=t)&&null!=(a=a.attrs.autocompleteOptions)&&a.data){var n=t.attrs.autocompleteOptions,i=n.data,o=n.minLength,l=void 0===o?1:o,r=e.inputValue.toLowerCase();if(r.length<l)return e.autocompleteItems=[],void(e.showAutocomplete=!1);var d=function(e){return Array.isArray(e)?e.map(function(e){return"string"==typeof e?{tag:e}:e}):Object.entries(e).map(function(e){var t=e[0];return{tag:t,value:e[1]||t}})}(i),c=d.filter(function(e){return e.tag.toLowerCase().includes(r)});e.autocompleteItems=c.slice(0,t.attrs.autocompleteOptions.limit||Infinity),e.showAutocomplete=e.autocompleteItems.length>0,e.selectedAutocompleteIndex=-1}else e.autocompleteItems=[]},i=function(t){o({tag:t.tag,image:t.image,alt:t.alt}),e.inputValue="",e.showAutocomplete=!1,e.selectedAutocompleteIndex=-1},o=function(a){if(t){var n=t.attrs,i=n.limit,o=void 0===i?Infinity:i,l=n.onChipAdd,r=n.onchange;!function(e,t){return!(!e.tag||""===e.tag.trim()||t.some(function(t){return t.tag===e.tag}))}(a,e.chipsData)||e.chipsData.length>=o||(e.chipsData=[].concat(e.chipsData,[a]),e.inputValue="",l&&l(a),r&&r(e.chipsData))}},l=function(a){if(t){var n=t.attrs,i=n.onChipDelete,o=n.onchange,l=e.chipsData[a];e.chipsData=e.chipsData.filter(function(e,t){return t!==a}),e.selectedChip=null,i&&i(l),o&&o(e.chipsData)}},d=function(a){if(t){var n=t.attrs.onChipSelect;e.selectedChip=a,n&&e.chipsData[a]&&n(e.chipsData[a])}},c=function(a){var r=a.target;if(e.showAutocomplete){if("ArrowDown"===a.key){var c;a.preventDefault(),e.selectedAutocompleteIndex=Math.min(e.selectedAutocompleteIndex+1,e.autocompleteItems.length-1);var u=null==(c=t)?void 0:c.dom.querySelector(".autocomplete-item.selected");return u&&u.scrollIntoView({block:"nearest"}),void n.default.redraw()}if("ArrowUp"===a.key){var s;a.preventDefault(),e.selectedAutocompleteIndex=Math.max(e.selectedAutocompleteIndex-1,-1);var f=null==(s=t)?void 0:s.dom.querySelector(".autocomplete-item.selected");return f&&f.scrollIntoView({block:"nearest"}),void n.default.redraw()}if("Enter"===a.key&&e.selectedAutocompleteIndex>=0)return a.preventDefault(),void i(e.autocompleteItems[e.selectedAutocompleteIndex])}"Enter"===a.key&&r.value.trim()?(a.preventDefault(),o({tag:r.value.trim()})):"Backspace"===a.key&&!r.value&&e.chipsData.length>0?(a.preventDefault(),l(e.chipsData.length-1)):"ArrowLeft"===a.key&&!r.value&&e.chipsData.length&&(a.preventDefault(),d(e.chipsData.length-1))};return{oninit:function(t){e.chipsData=t.attrs.data||[]},oncreate:function(e){t=e},onremove:function(){t=null},view:function(o){var r=o.attrs,u=r.isMandatory,s=void 0===u?r.required:u,m=r.className,v=r.label,h=r.helperText,g=r.placeholder,b=r.secondaryPlaceholder;return n.default(".input-field",{id:r.id,className:void 0===m?"col s12":m},[n.default(".chips.chips-initial",{class:"chips-container "+(e.focused?"focused":"")+" "+(g?"chips-placeholder":"")},[e.chipsData.map(function(a,i){return n.default(".chip",{key:a.tag+"-"+i,tabindex:0,class:e.selectedChip===i?"selected":"",onkeydown:function(a){return function(a,n){if("Backspace"===a.key||"Delete"===a.key){a.preventDefault(),l(n);var i=Math.max(n-1,0);e.chipsData.length&&d(i)}else if("ArrowLeft"===a.key&&n>0)d(n-1);else if("ArrowRight"===a.key)if(n<e.chipsData.length-1)d(n+1);else{var o,r=null==(o=t)?void 0:o.dom.querySelector(".chips-input");r&&r.focus()}}(a,i)}},[a.image&&n.default("img",{src:a.image,alt:a.alt||a.tag}),a.tag,n.default("i.material-icons.close",{onclick:function(e){e.stopPropagation(),l(i)}},"close")])}),n.default("input.chips-input.input",{id:e.inputId,title:"label",value:e.inputValue,placeholder:!e.chipsData.length&&g?g:e.chipsData.length&&b?b:"",oninput:function(t){e.inputValue=t.target.value,a()},onfocus:function(){e.focused=!0,e.selectedChip=null,a()},onblur:function(){e.focused=!1,setTimeout(function(){e.showAutocomplete=!1,e.selectedChip=null,n.default.redraw()},150)},onkeydown:c}),e.showAutocomplete&&n.default("ul.autocomplete-content.dropdown-content",{style:{display:"block",opacity:1,transform:"scaleX(1) scaleY(1)",position:"absolute",width:"100%",left:0,top:"100%",maxHeight:"200px",overflow:"auto",zIndex:1e3,backgroundColor:"#fff",boxShadow:"0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2)"}},e.autocompleteItems.map(function(t,a){return n.default("li.autocomplete-item",{key:t.tag,class:e.selectedAutocompleteIndex===a?"selected":"",style:{padding:"12px 16px",cursor:"pointer",backgroundColor:e.selectedAutocompleteIndex===a?"#eee":"transparent"},onmousedown:function(e){e.preventDefault(),i(t)},onmouseover:function(){e.selectedAutocompleteIndex=a}},[t.image&&n.default("img.autocomplete-item-image",{src:t.image,alt:t.alt||t.tag,style:{width:"24px",height:"24px",marginRight:"8px",verticalAlign:"middle"}}),n.default("span.autocomplete-item-text",t.tag)])}))]),v&&n.default(f,{label:v,id:e.inputId,isMandatory:s,isActive:!!(e.focused||e.chipsData.length||g)}),h&&n.default(p,{helperText:h})])}}},e.CodeBlock=function(){return{view:function(e){var t=e.attrs,a=t.newRow,l=t.code,r=t.language,d=t.className,c=o(t,T),u=r||"lang-TypeScript",s=u.replace("lang-",""),f=l instanceof Array?l.join("\n"):l,p=[a?"clear":"",u,d].filter(Boolean).join(" ").trim();return n.default("pre.codeblock"+(a?".clear":""),t,[n.default("div",n.default("label",s)),n.default("code",i({},c,{className:p}),f)])}}},e.Collapsible=function(){return{oncreate:function(e){M.Collapsible.init(e.dom,e.attrs)},view:function(e){var t=e.attrs,a=t.items;return a&&a.length>0?n.default("ul.collapsible",{class:t.class||t.className,style:t.style,id:t.id},a.map(function(e){return n.default(D,e)})):void 0}}},e.CollapsibleItem=D,e.Collection=_,e.ColorInput=Q,e.DatePicker=function(){var e={id:r()};return{view:function(t){var a=t.attrs,l=a.label,r=a.helperText,d=a.initialValue,c=a.newRow,u=a.className,s=void 0===u?"col s12":u,m=a.iconName,v=a.isMandatory,h=a.onchange,g=a.disabled,b=o(a,ne),y=e.id,w=h?function(){return e.dp&&h(e.dp.date)}:void 0,x=[c?"clear":"",s].filter(Boolean).join(" ").trim();return n.default(".input-field",{className:x,onremove:function(){return e.dp&&e.dp.destroy()}},[m?n.default("i.material-icons.prefix",m):"",n.default("input",i({},b,{type:"text",tabindex:0,className:"datepicker",id:y,disabled:g,oncreate:function(t){e.dp=M.Datepicker.init(t.dom,i({format:"yyyy/mm/dd",showClearBtn:!0,setDefaultDate:!0,defaultDate:d?new Date(d):new Date},b,{onClose:w}))}})),n.default(f,{label:l,id:y,isMandatory:v,isActive:!!d}),n.default(p,{helperText:r})])}}},e.Dropdown=function(){var e={};return{oninit:function(t){var a=t.attrs,n=a.id,i=void 0===n?r():n,o=a.initialValue,l=a.checkedId;e.id=i,e.initialValue=o||l},view:function(t){var a=t.attrs,i=a.key,l=a.label,r=a.onchange,d=a.disabled,c=void 0!==d&&d,u=a.items,s=a.iconName,f=a.helperText,m=a.style,v=a.className,h=void 0===v?"col s12":v,g=o(a,z),b=e.id,y=e.initialValue,w=y?u.filter(function(e){return e.id?e.id===y:e.label===y}).shift():void 0,x=w?w.label:l||"Select";return n.default(".input-field",{className:h,key:i,style:m},[s?n.default("i.material-icons.prefix",s):void 0,n.default(p,{helperText:f}),n.default("a.dropdown-trigger.btn.truncate[href=#]",{"data-target":b,disabled:c,className:"col s12",style:m||(s?"margin: 0.2em 0 0 3em;":void 0),oncreate:function(e){M.Dropdown.init(e.dom,g)}},x),n.default("ul.dropdown-content",{id:b},u.map(function(t){return n.default("li[tabindex=-1]",{className:t.divider?"divider":""},t.divider?void 0:n.default("a",{onclick:r?function(){e.initialValue=t.id||t.label,r(e.initialValue)}:void 0},[t.iconName?n.default("i.material-icons",t.iconName):void 0,t.label]))}))])}}},e.EmailInput=ee,e.FileInput=function(){var e,t=!1;return{view:function(a){var i=a.attrs,o=i.multiple,l=i.disabled,r=i.initialValue,d=i.placeholder,c=i.onchange,u=i.className,s=void 0===u?"col s12":u,f=i.accept,p=i.label,m=void 0===p?"File":p,v=f?f instanceof Array?f.join(", "):f:void 0;return n.default(".file-field.input-field",{className:i.class||s},[n.default(".btn",[n.default("span",m),n.default("input[type=file]",{title:m,accept:v,multiple:o,disabled:l,onchange:c?function(e){var a=e.target;a&&a.files&&c&&(t=!0,c(a.files))}:void 0})]),n.default(".file-path-wrapper",n.default("input.file-path.validate[type=text]",{placeholder:d,oncreate:function(t){e=t.dom,r&&(e.value=r)}})),(t||r)&&n.default("a.waves-effect.waves-teal.btn-flat",{style:{float:"right",position:"relative",top:"-3rem",padding:0},onclick:function(){t=!1,e.value="",c&&c({})}},n.default("i.material-icons","clear"))])}}},e.FlatButton=k,e.FloatingActionButton=function(){return{view:function(e){var t=e.attrs,a=t.className,i=t.iconName,l=t.iconClass,r=void 0===l?"large":l,d=t.position,c=t.style,u=void 0===c?"left"===d||"inline-left"===d?"position: absolute; display: inline-block; left: 24px;":"right"===d||"inline-right"===d?"position: absolute; display: inline-block; right: 24px;":void 0:c,s=t.buttons,f=o(t,H),p=n.default(".fixed-action-btn",{style:u,oncreate:function(e){return M.FloatingActionButton.init(e.dom,f)}},[n.default("a.btn-floating.btn-large",{className:a},n.default("i.material-icons",{classNames:r},i)),s?n.default("ul",s.map(function(e){return n.default("li",n.default("a.btn-floating",{className:e.className,onclick:function(t){return e.onClick&&e.onClick(t)}},n.default("i.material-icons",{className:e.iconClass},e.iconName)))})):void 0]);return"inline-right"===d||"inline-left"===d?n.default("div",{style:"position: relative; height: 70px;"},p):p}}},e.HelperText=p,e.Icon=h,e.InputCheckbox=te,e.Label=f,e.LargeButton=w,e.ListItem=E,e.Mandatory=s,e.MapEditor=function(){var t=function(e){return a.curKey=a.id=e},a={elementId:r(),id:"",curKey:"",kvc:function(e,t,a){var i=a.keyClass,o=void 0===i?".col.s4":i,l=a.valueClass,r=void 0===l?".col.s8":l,d=t instanceof Array?t.join(", "):"boolean"==typeof t?n.default(te,{label:" ",checked:t,disabled:!0,className:"checkbox-in-collection"}):t.toString();return{title:n.default(".row",{style:"margin-bottom: 0"},[n.default(o,n.default("b",e)),n.default(r,d)])}}},i=function(){a.id="",a.curKey=""};return{oninit:function(e){var t=e.attrs,n=t.keyValueConverter,i=t.id;n&&(a.kvc=n),i&&(a.elementId=i)},view:function(o){var l=o.attrs,r=l.className,d=void 0===r?"col s12":r,c=l.disabled,u=l.disallowArrays,s=l.header,p=l.iconName,m=l.iconNameKey,v=void 0===m?p?"label":void 0:m,h=l.isMandatory,g=l.label,b=l.labelKey,y=void 0===b?"Key":b,w=l.labelValue,x=void 0===w?"Value":w,N=l.properties,I=l.onchange,C=l.falsy,M=void 0===C?["false"]:C,A=l.truthy,T=void 0===A?["true"]:A,D=function(){return I?I(N):void 0},S=function(e,n){return Object.keys(e).map(function(t){return{key:t,value:e[t]}}).map(function(e){return function(e,n){var i=n.onclick;return n.id=n.id||e,n.active=e===a.curKey,n.onclick=i?function(){return t(e)&&i(n)}:function(){return t(e)},n}(e.key,a.kvc(e.key,e.value,{keyClass:n.keyClass,valueClass:n.valueClass}))})}(N,{keyClass:l.keyClass,valueClass:l.valueClass}),O=a.curKey,V=N[O],R="boolean"==typeof V||"number"==typeof V?V:V?V instanceof Array?"["+V.join(", ")+"]":V:"",B=a.elementId;return[n.default(".map-editor",n.default(".input-field",{className:d,style:"min-height: 1.5em;"},[p?n.default("i.material-icons.prefix",p):"",n.default(f,{label:g,isMandatory:h,isActive:S.length>0}),n.default(_,{id:B,items:S,mode:e.CollectionMode.LINKS,header:s})])),c?void 0:[n.default($,{label:y,iconName:v,className:"col s5",initialValue:O,onchange:function(e){a.curKey=e,a.id&&(delete N[a.id],N[e]=V,a.id=e),D()}}),"string"==typeof R?n.default(W,{label:x,initialValue:R,className:"col s7",onchange:function(e){var t=function(e,t,a){return t.indexOf(e)>=0||!(a.indexOf(e)>=0)&&void 0}(e,T,M),a=void 0===t&&/^\s*\d+\s*$/i.test(e)?+e:void 0;N[O]="boolean"==typeof t?t:"number"==typeof a?a:function(e,t){if(void 0===t&&(t=!1),t)return e;if(e){var a=/\s*\[(.*)\]\s*/gi.exec(e);if(a&&2===a.length)return a[1].split(",").map(function(e){return e.trim()}).map(function(e){return/^\d+$/g.test(e)?+e:e})}}(e,u)||e,D()}}):"number"==typeof R?n.default(G,{label:x,initialValue:R,className:"col s7",onchange:function(e){N[O]=e,D()}}):n.default(te,{label:x,checked:R,className:"input-field col s7",onchange:function(e){N[O]=e,D()}}),n.default(".col.s12.right-align",[n.default(k,{iconName:"add",onclick:i}),n.default(k,{iconName:"delete",disabled:!O,onclick:function(){delete N[O],i(),D()}})])]]}}},e.MaterialBox=function(){return{oncreate:function(e){M.Materialbox.init(e.dom,e.attrs)},view:function(e){return n.default("img.materialboxed",e.attrs)}}},e.ModalPanel=function(){return{oncreate:function(e){var t=e.attrs,a=t.onCreate,n=M.Modal.init(e.dom,t.options);a&&a(n)},view:function(e){var t=e.attrs,a=t.id,o=t.title,l=t.description,r=t.buttons,d=t.richContent,c=[t.className,t.fixedFooter?"modal-fixed-footer":"",t.bottomSheet?"bottom-sheet":""].filter(Boolean).join(" ").trim();return n.default(".modal",{id:a,className:c},[n.default(".modal-content",[n.default("h4",o),d&&"string"==typeof l?n.default.trust(l||""):"string"==typeof l?n.default("p",l):l]),r?n.default(".modal-footer",r.map(function(e){return n.default(k,i({},e,{className:"modal-close"}))})):void 0])}}},e.NumberInput=G,e.Options=function(){var e={},t=function(t){return e.checkedIds.indexOf(t)>=0};return{oninit:function(t){var a=t.attrs,n=a.checkedId,i=n||a.initialValue;e.checkedId=n,e.checkedIds=i?i instanceof Array?[].concat(i):[i]:[]},view:function(a){var i=a.attrs,o=i.label,l=i.id,r=i.options,d=i.checkedId,c=i.description,u=i.className,s=void 0===u?"col s12":u,m=i.style,v=i.disabled,h=i.checkboxClass,g=i.newRow,b=i.isMandatory,y=i.onchange;d&&e.checkedId!==d&&(e.checkedId=d,e.checkedIds=d instanceof Array?d:[d]);var w=y?function(t,a){var n=e.checkedIds.filter(function(e){return e!==t});a&&n.push(t),e.checkedIds=n,y(n)}:void 0,x=[g?"clear":"",s].filter(Boolean).join(" ").trim();return n.default("div",{className:x,style:m},[n.default("div",{className:"input-field options"},n.default(f,{id:l,label:o,isMandatory:b})),n.default(p,{helperText:c})].concat(r.map(function(e){return n.default(te,{disabled:v||e.disabled,label:e.label,onchange:w?function(t){return w(e.id,t)}:void 0,className:e.className||h,checked:t(e.id),description:e.description})})))}}},e.Pagination=function(){var e={pagIndex:0};return{view:function(t){var a=t.attrs,o=a.items,l=a.curPage,r=void 0===l?1:l,d=a.size,c=void 0===d?Math.min(9,o.length):d,u=e.pagIndex,s=u*c,f=s+c,p=u>0,m=f<o.length,v=[{title:n.default("a",{onclick:function(){return p&&e.pagIndex--}},n.default("i.material-icons","chevron_left")),disabled:!p}].concat(o.filter(function(e,t){return s<=t&&t<f}),[{title:n.default("a",{onclick:function(){return m&&e.pagIndex++}},n.default("i.material-icons","chevron_right")),disabled:!m}]);return n.default("ul.pagination",v.map(function(e,t){return n.default(ae,i({title:s+t},e,{active:s+t===r}))}))}}},e.Parallax=function(){return{oncreate:function(e){M.Parallax.init(e.dom,e.attrs)},view:function(e){var t=e.attrs.src;return t?n.default(".parallax-container",n.default(".parallax",n.default("img",{src:t}))):void 0}}},e.PasswordInput=X,e.RadioButton=oe,e.RadioButtons=function(){var e={groupId:r()};return{oninit:function(t){var a=t.attrs,n=a.checkedId,i=a.initialValue;e.oldCheckedId=n,e.checkedId=n||i},view:function(t){var a=t.attrs,o=a.id,l=a.checkedId,r=a.newRow,d=a.className,c=void 0===d?"col s12":d,u=a.label,s=void 0===u?"":u,p=a.disabled,m=a.description,v=a.options,h=a.isMandatory,g=a.checkboxClass,b=a.onchange;e.oldCheckedId!==l&&(e.oldCheckedId=e.checkedId=l);var y=e.groupId,w=e.checkedId,x=function(t){e.checkedId=t,b&&b(t)};return r&&(c+=" clear"),n.default("div",{id:o,className:c},[n.default("div",{className:"input-field options"},n.default(f,{id:o,label:s,isMandatory:h})),m?n.default("p.helper-text",n.default.trust(m)):""].concat(v.map(function(e){return n.default(oe,i({},e,{onchange:x,groupId:y,disabled:p,className:g,checked:e.id===w}))})))}}},e.RangeInput=Z,e.RoundIconButton=N,e.SearchSelect=function(){var e={isOpen:!1,selectedOptions:[],searchTerm:"",options:[],inputRef:null,dropdownRef:null,focusedIndex:-1,onchange:null},t=function(t){var a=t.target;e.inputRef&&e.inputRef.contains(a)?(e.isOpen=!e.isOpen,n.default.redraw()):e.dropdownRef&&!e.dropdownRef.contains(a)&&(e.isOpen=!1,n.default.redraw())},a=function(t){if(e.isOpen){var a=e.options.filter(function(t){return(t.label||t.id.toString()).toLowerCase().includes((e.searchTerm||"").toLowerCase())&&!e.selectedOptions.some(function(e){return e.id===t.id})});switch(t.key){case"ArrowDown":t.preventDefault(),e.focusedIndex=Math.min(e.focusedIndex+1,a.length-1),n.default.redraw();break;case"ArrowUp":t.preventDefault(),e.focusedIndex=Math.max(e.focusedIndex-1,-1),n.default.redraw();break;case"Enter":t.preventDefault(),e.focusedIndex>=0&&e.focusedIndex<a.length&&i(a[e.focusedIndex]);break;case"Escape":t.preventDefault(),e.isOpen=!1,e.focusedIndex=-1,n.default.redraw()}}},i=function(t){t.disabled||(e.selectedOptions=e.selectedOptions.some(function(e){return e.id===t.id})?e.selectedOptions.filter(function(e){return e.id!==t.id}):[].concat(e.selectedOptions,[t]),e.searchTerm="",e.focusedIndex=-1,e.onchange&&e.onchange(e.selectedOptions.map(function(e){return e.id})),n.default.redraw())};return{oninit:function(t){var a=t.attrs,n=a.options,i=void 0===n?[]:n,o=a.initialValue,l=void 0===o?[]:o,r=a.onchange;e.options=i,e.selectedOptions=i.filter(function(e){return l.includes(e.id)}),e.onchange=r},oncreate:function(){document.addEventListener("click",t),document.addEventListener("keydown",a)},onremove:function(){document.removeEventListener("click",t),document.removeEventListener("keydown",a)},view:function(t){var a=t.attrs,o=a.oncreateNewOption,l=a.className,r=a.placeholder,d=a.searchPlaceholder,c=void 0===d?"Search options...":d,u=a.noOptionsFound,s=void 0===u?"No options found":u,f=a.label,p=a.maxHeight,m=void 0===p?"25rem":p,v=e.options.filter(function(t){return(t.label||t.id.toString()).toLowerCase().includes((e.searchTerm||"").toLowerCase())&&!e.selectedOptions.some(function(e){return e.id===t.id})}),h=o&&e.searchTerm&&!v.some(function(t){return(t.label||t.id.toString()).toLowerCase()===e.searchTerm.toLowerCase()});return n.default(".multi-select-dropdown.input-field",{className:l},[n.default("label",{class:r||e.selectedOptions.length>0?"active":""},f),n.default(".dropdown-trigger",{oncreate:function(t){e.inputRef=t.dom},style:{borderBottom:"2px solid #d1d5db",display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer"}},[n.default(".selected-options",{style:{display:"flex",flexWrap:"wrap",minHeight:"50px",paddingTop:"12px"}},0===e.selectedOptions.length?[n.default("span",r)]:e.selectedOptions.map(function(t){return n.default(".chip",[t.label||t.id.toString(),n.default("button",{onclick:function(a){a.stopPropagation(),function(t){e.selectedOptions=e.selectedOptions.filter(function(e){return e.id!==t.id}),e.onchange&&e.onchange(e.selectedOptions.map(function(e){return e.id})),n.default.redraw()}(t)},style:{marginLeft:"0.25rem",background:"none",border:"none",cursor:"pointer"}},"×")])})),n.default("svg.caret",{class:"caret",height:"24",viewBox:"0 0 24 24",width:"24",xmlns:"http://www.w3.org/2000/svg"},[n.default("path",{d:"M7 10l5 5 5-5z"}),n.default("path",{d:"M0 0h24v24H0z",fill:"none"})])]),e.isOpen&&n.default(".dropdown-menu",{oncreate:function(t){e.dropdownRef=t.dom},onremove:function(){e.dropdownRef=null},style:{position:"absolute",width:"98%",marginTop:"0.4rem",zIndex:1e3}},[n.default("ul.dropdown-content.select-dropdown",{style:{maxHeight:m,opacity:1,display:"block",width:"100%"}},[n.default("li",{class:"search-wrapper",style:{padding:"0 16px",position:"relative"}},[n.default("input",{type:"text",placeholder:c,value:e.searchTerm||"",oninput:function(t){e.searchTerm=t.target.value,e.focusedIndex=-1,n.default.redraw()},style:{width:"100%",outline:"none",fontSize:"0.875rem"}})])].concat(0!==v.length||h?[]:[n.default("li",{style:{padding:"0.5rem",textAlign:"center",color:"#9ca3af"}},s)],h?[n.default("li",{onclick:function(){try{return Promise.resolve(o(e.searchTerm)).then(function(e){i(e)})}catch(e){return Promise.reject(e)}},style:{display:"flex",alignItems:"center",cursor:"pointer",background:e.focusedIndex===v.length?"#f3f4f6":""}},[n.default("span",'+ "'+e.searchTerm+'"')])]:[],v.map(function(t,a){return n.default("li",{onclick:function(){return i(t)},class:t.disabled?"disabled":void 0,style:{display:"flex",alignItems:"center",cursor:t.disabled?"not-allowed":"pointer",background:e.focusedIndex===a?"#f3f4f6":""}},n.default("span",[n.default("input",{type:"checkbox",checked:e.selectedOptions.some(function(e){return e.id===t.id}),style:{marginRight:"0.5rem"}}),t.label||t.id.toString()]))})))])])}}},e.SecondaryContent=L,e.Select=function(){var e={},t=function(e){return e.map(function(e){return e.id}).join("")},a=function(e,t,a){return void 0===a&&(a=!1),a||(t instanceof Array&&(e||"number"==typeof e)?t.indexOf(e)>=0:t===e)};return{oninit:function(a){var n=a.attrs,i=n.checkedId,o=n.initialValue;e.ids=t(n.options);var l=i||o;e.checkedId=i instanceof Array?[].concat(i):i,e.initialValue=null!=l?l instanceof Array?l.filter(function(e){return null!=e}):[l]:[]},view:function(i){var o=i.attrs,l=o.id,r=o.newRow,c=o.className,u=void 0===c?"col s12":c,s=o.checkedId,m=o.key,v=o.options,h=o.multiple,g=o.label,b=o.helperText,y=o.placeholder,w=void 0===y?"":y,x=o.isMandatory,k=o.iconName,N=o.disabled,I=o.classes,C=void 0===I?"":I,A=o.dropdownOptions,T=o.onchange;e.checkedId!==s&&(e.initialValue=s?s instanceof Array?s:[s]:void 0);var D=e.initialValue,S=T?h?function(){var t=e.instance&&e.instance.getSelectedValues(),a=t?t.length>0&&d(t[0])?t.map(function(e){return+e}):t.filter(function(e){return null!==e||void 0!==e}):void 0;e.initialValue=a||[],T(e.initialValue)}:function(t){if(t&&t.currentTarget){var a=t.currentTarget,n=d(a.value)?+a.value:a.value;e.initialValue=void 0!==typeof n?[n]:[]}e.initialValue&&T(e.initialValue)}:void 0;r&&(u+=" clear");var O=!v.some(function(e){return a(e.id,D)}),V=v.reduce(function(e,t){return t.group&&e.indexOf(t.group)<0&&e.push(t.group),e},[]);return n.default(".input-field.select-space",{className:u,key:m,oncreate:h?function(t){return e.wrapper=t.dom}:void 0},[k&&n.default("i.material-icons.prefix",k),n.default("select",{id:l,title:g,disabled:N,multiple:h,oncreate:function(t){e.instance=M.FormSelect.init(t.dom,{classes:C,dropdownOptions:A})},onupdate:function(a){var n=a.dom;if(h){var i=k?1:0;!e.inputEl&&e.wrapper&&e.wrapper.childNodes&&e.wrapper.childNodes.length>0&&e.wrapper.childNodes[i].childNodes&&e.wrapper.childNodes[i].childNodes[0]&&(e.inputEl=e.wrapper.childNodes[i].childNodes[0]),e.inputEl&&e.inputEl.value&&e.inputEl.value.startsWith(w+", ")&&(e.inputEl.value=e.inputEl.value.replace(w+", ",""))}var o=t(v),l=s&&e.checkedId!==s.toString();e.ids!==o&&(e.ids=o,l=!0),(e.checkedId instanceof Array&&s instanceof Array?e.checkedId.join()!==s.join():e.checkedId!==s)&&(e.checkedId=s,l=!0),l&&(e.instance=M.FormSelect.init(n,{classes:C,dropdownOptions:A}))},onchange:S},n.default("option",{value:"",disabled:!0,selected:!!O||void 0},w),0===V.length?v.map(function(e,t){var i;return n.default("option",{value:e.id,title:e.title||void 0,disabled:e.disabled?"true":void 0,"data-icon":e.img||void 0,selected:a(e.id,D,0===t&&O&&!w)},null==(i=e.label)?void 0:i.replace("&amp;","&"))}):V.map(function(e){return n.default("optgroup",{label:e},v.filter(function(t){return t.group===e}).map(function(e,t){var i;return n.default("option",{value:e.id,title:e.title||void 0,disabled:e.disabled?"true":void 0,"data-icon":e.img||void 0,selected:a(e.id,D,0===t&&O&&!w)},null==(i=e.label)?void 0:i.replace("&amp;","&"))}))})),n.default(f,{label:g,isMandatory:x}),b&&n.default(p,{helperText:b})])}}},e.SmallButton=x,e.SubmitButton=I,e.Switch=function(){var e={id:r()};return{view:function(t){var a=t.attrs,i=a.id||e.id,l=a.label,r=a.left,d=a.right,c=a.disabled,u=a.newRow,s=a.onchange,p=a.checked,m=a.isMandatory,v=a.className,h=void 0===v?"col s12":v,g=o(a,le),b=["input-field",u?"clear":"",h].filter(Boolean).join(" ").trim();return n.default("div",{className:b},[l?n.default(f,{label:l||"",id:i,isMandatory:m,className:"active"}):void 0,n.default(".switch",g,n.default("label",[r||"Off",n.default("input[type=checkbox]",{id:i,disabled:c,checked:p,onclick:s?function(e){e.target&&void 0!==e.target.checked&&s(e.target.checked)}:void 0}),n.default("span.lever"),d||"On"]))])}}},e.Tabs=function(){var e={},t=function(e,t){return t||e.replace(/ /g,"").toLowerCase()};return{view:function(a){var i=a.attrs,o=i.tabWidth,l=i.selectedTabId,r=i.tabs,d=i.className,c=i.style,u=i.duration,s=i.onShow,f=i.swipeable,p=i.responsiveThreshold,m=r.filter(function(e){return e.active}).shift(),v=l||(m?t(m.title,m.id):""),h=["fill"===o?"tabs-fixed-width":"",d].filter(Boolean).join(" ").trim();return n.default(".row",[n.default(".col.s12",n.default("ul.tabs",{className:h,style:c,oncreate:function(t){e.instance=M.Tabs.init(t.dom,{duration:u,onShow:s,responsiveThreshold:p,swipeable:f})},onupdate:function(){if(v){var e=document.getElementById("tab_"+v);e&&e.click()}},onremove:function(){return e.instance.destroy()}},r.map(function(e){var a=e.className,i=e.title,l=e.id,d=e.active,c=e.disabled,u=e.target,s=e.href,f=["fixed"===o?"col s"+Math.floor(12/r.length):"",a].filter(Boolean).join(" ").trim(),p=t(i,l);return n.default("li.tab",{className:f,disabled:c},n.default("a",{id:"tab_"+p,className:d?"active":"",target:u,href:s||"#"+p},i))}))),r.filter(function(e){return void 0===e.href}).map(function(e){var a=e.vnode,i=e.contentClass;return n.default(".col.s12",{id:t(e.title,e.id),className:i},a)})])}}},e.TextArea=W,e.TextInput=$,e.TimePicker=function(){var e={id:r()};return{view:function(t){var a=t.attrs,l=a.label,r=a.helperText,d=a.initialValue,c=a.newRow,u=a.className,s=void 0===u?"col s12":u,m=a.iconName,v=a.isMandatory,h=a.onchange,g=a.disabled,b=o(a,ie),y=e.id,w=new Date,x=h?function(){return e.tp&&h(e.tp.time||d||w.getHours()+":"+w.getMinutes())}:void 0,k=["input-field","timepicker",c?"clear":"",s].filter(Boolean).join(" ").trim();return n.default("div",{className:k,onremove:function(){return e.tp&&e.tp.destroy()}},[m?n.default("i.material-icons.prefix",m):"",n.default("input",i({},b,{type:"text",tabindex:0,id:y,disabled:g,value:d,oncreate:function(t){e.tp=M.Timepicker.init(t.dom,i({twelveHour:!1,showClearBtn:!0,defaultTime:d},b,{onCloseEnd:x}))}})),n.default(f,{label:l,id:y,isMandatory:v,isActive:d}),n.default(p,{helperText:r})])}}},e.Timeline=function(){var e=function(e){return e.getUTCDate()+"/"+(e.getUTCMonth()+1)+"/"+e.getUTCFullYear()},t=function(e){return c(e.getUTCHours())+":"+c(e.getUTCMinutes())};return{view:function(a){var o=a.attrs,l=o.onSelect,r=o.timeFormatter,d=void 0===r?t:r,c=o.dateFormatter,u=void 0===c?e:c;return n.default("ul.mm_timeline",o.items.map(function(e){return n.default(re,i({onSelect:l,dateFormatter:u,timeFormatter:d},e))}))}}},e.UrlInput=J,e.isNumeric=d,e.padLeft=c,e.uniqueId=r,e.uuid4=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}});
2
- //# sourceMappingURL=index.umd.js.map
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('mithril')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'mithril'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["mithril-materialized"] = {}, global.m));
5
+ })(this, (function (exports, m) { 'use strict';
6
+
7
+ /******************************************************************************
8
+ Copyright (c) Microsoft Corporation.
9
+
10
+ Permission to use, copy, modify, and/or distribute this software for any
11
+ purpose with or without fee is hereby granted.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
14
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
16
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
17
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
18
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19
+ PERFORMANCE OF THIS SOFTWARE.
20
+ ***************************************************************************** */
21
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
22
+
23
+
24
+ function __rest(s, e) {
25
+ var t = {};
26
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
27
+ t[p] = s[p];
28
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
29
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
30
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
31
+ t[p[i]] = s[p[i]];
32
+ }
33
+ return t;
34
+ }
35
+
36
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
37
+ var e = new Error(message);
38
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
39
+ };
40
+
41
+ /**
42
+ * Create a unique ID
43
+ * @see https://stackoverflow.com/a/2117523/319711
44
+ *
45
+ * @returns id followed by 8 hexadecimal characters.
46
+ */
47
+ const uniqueId = () => {
48
+ // tslint:disable-next-line:no-bitwise
49
+ return 'idxxxxxxxx'.replace(/[x]/g, () => ((Math.random() * 16) | 0).toString(16));
50
+ };
51
+ /**
52
+ * Create a GUID
53
+ * @see https://stackoverflow.com/a/2117523/319711
54
+ *
55
+ * @returns RFC4122 version 4 compliant GUID
56
+ */
57
+ const uuid4 = () => {
58
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
59
+ // tslint:disable-next-line:no-bitwise
60
+ const r = (Math.random() * 16) | 0;
61
+ // tslint:disable-next-line:no-bitwise
62
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
63
+ return v.toString(16);
64
+ });
65
+ };
66
+ /** Check if a string or number is numeric. @see https://stackoverflow.com/a/9716488/319711 */
67
+ const isNumeric = (n) => !isNaN(parseFloat(n)) && isFinite(n);
68
+ /**
69
+ * Pad left, default width 2 with a '0'
70
+ *
71
+ * @see http://stackoverflow.com/a/10073788/319711
72
+ * @param {(string | number)} n
73
+ * @param {number} [width=2]
74
+ * @param {string} [z='0']
75
+ * @returns
76
+ */
77
+ const padLeft = (n, width = 2, z = '0') => String(n).padStart(width, z);
78
+ // Keep only essential dropdown positioning styles
79
+ const getDropdownStyles = (inputRef, overlap = false, options, isDropDown = false) => {
80
+ if (!inputRef) {
81
+ return {
82
+ display: 'block',
83
+ opacity: 1,
84
+ position: 'absolute',
85
+ top: overlap ? 0 : '100%',
86
+ left: '0',
87
+ zIndex: 1000,
88
+ width: '100%',
89
+ };
90
+ }
91
+ const rect = inputRef.getBoundingClientRect();
92
+ const viewportHeight = window.innerHeight;
93
+ // Calculate dropdown height based on options
94
+ let estimatedHeight = 200; // Default fallback
95
+ const itemHeight = 52; // Standard height for dropdown items
96
+ if (options) {
97
+ const groupHeaderHeight = 52; // Height for group headers
98
+ // Count groups and total options
99
+ const groups = new Set();
100
+ let totalOptions = 0;
101
+ options
102
+ .filter((o) => !o.divider)
103
+ .forEach((option) => {
104
+ totalOptions++;
105
+ if (option.group) {
106
+ groups.add(option.group);
107
+ }
108
+ });
109
+ // Calculate total height: options + group headers + padding
110
+ estimatedHeight = totalOptions * itemHeight + groups.size * groupHeaderHeight;
111
+ }
112
+ const spaceBelow = viewportHeight - rect.bottom;
113
+ const spaceAbove = rect.top;
114
+ // If there's not enough space below and more space above, position dropdown above
115
+ const shouldPositionAbove = spaceBelow < estimatedHeight && spaceAbove > spaceBelow;
116
+ // Calculate available space and whether scrolling is needed
117
+ const availableSpace = shouldPositionAbove ? spaceAbove : spaceBelow;
118
+ // When positioning above, we need to consider the actual space from viewport top to input
119
+ let effectiveAvailableSpace = availableSpace;
120
+ if (shouldPositionAbove) {
121
+ effectiveAvailableSpace = rect.top - 10; // Space from viewport top to input, minus margin
122
+ }
123
+ const needsScrolling = estimatedHeight > effectiveAvailableSpace;
124
+ // Calculate the actual height the dropdown will take
125
+ const actualHeight = needsScrolling ? effectiveAvailableSpace : estimatedHeight;
126
+ // Calculate positioning when dropdown should appear above
127
+ let topOffset;
128
+ if (shouldPositionAbove) {
129
+ // Calculate how much space we actually have from top of viewport to top of input
130
+ const availableSpaceFromViewportTop = rect.top;
131
+ // If dropdown fits comfortably above input, use normal positioning
132
+ if (actualHeight <= availableSpaceFromViewportTop) {
133
+ topOffset = 12 - actualHeight + (isDropDown ? itemHeight : 0); // Bottom of dropdown aligns with top of input
134
+ }
135
+ else {
136
+ // If dropdown is too tall, position it at the very top of viewport
137
+ // This makes the dropdown use all available space from viewport top to input top
138
+ topOffset = -availableSpaceFromViewportTop + 5; // 5px margin from viewport top
139
+ }
140
+ }
141
+ else {
142
+ topOffset = overlap ? 0 : '100%';
143
+ }
144
+ const styles = {
145
+ display: 'block',
146
+ opacity: 1,
147
+ position: 'absolute',
148
+ top: typeof topOffset === 'number' ? `${topOffset}px` : topOffset,
149
+ left: '0',
150
+ zIndex: 1000,
151
+ width: `${rect.width}px`,
152
+ };
153
+ // Only add scrolling constraints when necessary
154
+ if (needsScrolling) {
155
+ styles.maxHeight = `${actualHeight}px`;
156
+ styles.overflowY = 'auto';
157
+ }
158
+ return styles;
159
+ };
160
+ /**
161
+ * Generate a range of numbers from a to and including b, i.e. [a, b]
162
+ * @example: console.log(range(5, 10)); // [5, 6, 7, 8, 9, 10]
163
+ */
164
+ const range = (a, b) => Array.from({ length: b - a + 1 }, (_, i) => a + i);
165
+
166
+ // import './styles/input.css';
167
+ const Mandatory = { view: ({ attrs }) => m('span.mandatory', attrs, '*') };
168
+ /** Simple label element, used for most components. */
169
+ const Label = () => {
170
+ return {
171
+ view: (_a) => {
172
+ var _b = _a.attrs, { label, id, isMandatory, isActive, className } = _b, params = __rest(_b, ["label", "id", "isMandatory", "isActive", "className"]);
173
+ return label
174
+ ? m('label', Object.assign(Object.assign({}, params), { className: [className, isActive ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined, for: id }), [m.trust(label), isMandatory ? m(Mandatory) : undefined])
175
+ : undefined;
176
+ },
177
+ };
178
+ };
179
+ /** Create a helper text, often used for displaying a small help text. May be replaced by the validation message. */
180
+ const HelperText = () => {
181
+ return {
182
+ view: ({ attrs: { helperText, dataError, dataSuccess, className } }) => {
183
+ return helperText || dataError || dataSuccess
184
+ ? m('span.helper-text.left', { className, 'data-error': dataError, 'data-success': dataSuccess }, dataError ? m.trust(dataError) : dataSuccess ? m.trust(dataSuccess) : helperText ? m.trust(helperText) : '')
185
+ : undefined;
186
+ },
187
+ };
188
+ };
189
+
190
+ /** Component to auto complete your text input - Pure Mithril implementation */
191
+ const Autocomplete = () => {
192
+ const state = {
193
+ id: uniqueId(),
194
+ isActive: false,
195
+ inputValue: '',
196
+ isOpen: false,
197
+ suggestions: [],
198
+ selectedIndex: -1,
199
+ inputElement: null,
200
+ };
201
+ const filterSuggestions = (input, data, limit, minLength) => {
202
+ if (!input || input.length < minLength) {
203
+ return [];
204
+ }
205
+ const filtered = Object.entries(data || {})
206
+ .filter(([key]) => key.toLowerCase().includes(input.toLowerCase()))
207
+ .map(([key, value]) => ({ key, value }))
208
+ .slice(0, limit);
209
+ return filtered;
210
+ };
211
+ const selectSuggestion = (suggestion, attrs) => {
212
+ state.inputValue = suggestion.key;
213
+ state.isOpen = false;
214
+ state.selectedIndex = -1;
215
+ if (attrs.onchange) {
216
+ attrs.onchange(suggestion.key);
217
+ }
218
+ if (attrs.onAutocomplete) {
219
+ attrs.onAutocomplete(suggestion.key);
220
+ }
221
+ // Force redraw to update label state
222
+ m.redraw();
223
+ };
224
+ const handleKeydown = (e, attrs) => {
225
+ if (!state.isOpen)
226
+ return;
227
+ switch (e.key) {
228
+ case 'ArrowDown':
229
+ e.preventDefault();
230
+ state.selectedIndex = Math.min(state.selectedIndex + 1, state.suggestions.length - 1);
231
+ break;
232
+ case 'ArrowUp':
233
+ e.preventDefault();
234
+ state.selectedIndex = Math.max(state.selectedIndex - 1, -1);
235
+ break;
236
+ case 'Enter':
237
+ e.preventDefault();
238
+ if (state.selectedIndex >= 0 && state.suggestions[state.selectedIndex]) {
239
+ state.isOpen = false;
240
+ selectSuggestion(state.suggestions[state.selectedIndex], attrs);
241
+ }
242
+ break;
243
+ case 'Escape':
244
+ e.preventDefault();
245
+ state.isOpen = false;
246
+ state.selectedIndex = -1;
247
+ break;
248
+ }
249
+ };
250
+ const closeDropdown = (e) => {
251
+ const target = e.target;
252
+ const autocompleteWrapper = target.closest('.autocomplete-wrapper');
253
+ const dropdownContent = target.closest('.autocomplete-content');
254
+ // Close if clicking outside both the input wrapper and dropdown content
255
+ if (!autocompleteWrapper && !dropdownContent) {
256
+ state.isOpen = false;
257
+ state.selectedIndex = -1;
258
+ }
259
+ };
260
+ const getDropdownStyles = () => {
261
+ if (!state.inputElement) {
262
+ return {
263
+ display: 'block',
264
+ width: '100%',
265
+ height: `${state.suggestions.length * 50}px`,
266
+ transformOrigin: '0px 0px',
267
+ opacity: state.isOpen ? 1 : 0,
268
+ transform: 'scaleX(1) scaleY(1)',
269
+ };
270
+ }
271
+ const rect = state.inputElement.getBoundingClientRect();
272
+ const inputWidth = rect.width;
273
+ return {
274
+ display: 'block',
275
+ width: `${inputWidth}px`,
276
+ height: `${state.suggestions.length * 50}px`,
277
+ transformOrigin: '0px 0px',
278
+ opacity: state.isOpen ? 1 : 0,
279
+ transform: 'scaleX(1) scaleY(1)',
280
+ position: 'absolute',
281
+ top: '100%',
282
+ left: '0',
283
+ zIndex: 1000,
284
+ };
285
+ };
286
+ return {
287
+ oninit: ({ attrs }) => {
288
+ state.inputValue = attrs.initialValue || '';
289
+ document.addEventListener('click', closeDropdown);
290
+ },
291
+ onremove: () => {
292
+ document.removeEventListener('click', closeDropdown);
293
+ },
294
+ view: ({ attrs }) => {
295
+ const id = attrs.id || state.id;
296
+ const { label, helperText, onchange, newRow, className = 'col s12', style, iconName, isMandatory, data = {}, limit = Infinity, minLength = 1 } = attrs, params = __rest(attrs, ["label", "helperText", "onchange", "newRow", "className", "style", "iconName", "isMandatory", "data", "limit", "minLength"]);
297
+ const cn = newRow ? className + ' clear' : className;
298
+ // Update suggestions when input changes
299
+ state.suggestions = filterSuggestions(state.inputValue, data, limit, minLength);
300
+ // Check if there's a perfect match (exact key match, case-insensitive)
301
+ const hasExactMatch = state.inputValue.length >= minLength &&
302
+ Object.keys(data).some((key) => key.toLowerCase() === state.inputValue.toLowerCase());
303
+ // Only open dropdown if there are suggestions and no perfect match
304
+ state.isOpen = state.suggestions.length > 0 && state.inputValue.length >= minLength && !hasExactMatch;
305
+ const replacer = new RegExp(`(${state.inputValue})`, 'i');
306
+ return m('.input-field.autocomplete-wrapper', {
307
+ className: cn,
308
+ style,
309
+ }, [
310
+ iconName ? m('i.material-icons.prefix', iconName) : '',
311
+ m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value: state.inputValue, oncreate: (vnode) => {
312
+ state.inputElement = vnode.dom;
313
+ }, oninput: (e) => {
314
+ const target = e.target;
315
+ state.inputValue = target.value;
316
+ state.selectedIndex = -1;
317
+ if (onchange) {
318
+ onchange(target.value);
319
+ }
320
+ }, onkeydown: (e) => {
321
+ handleKeydown(e, attrs);
322
+ // Call original onkeydown if provided
323
+ if (attrs.onkeydown) {
324
+ attrs.onkeydown(e, state.inputValue);
325
+ }
326
+ }, onfocus: () => {
327
+ state.isActive = true;
328
+ if (state.inputValue.length >= minLength) {
329
+ // Check for perfect match on focus too
330
+ const hasExactMatch = Object.keys(data).some((key) => key.toLowerCase() === state.inputValue.toLowerCase());
331
+ state.isOpen = state.suggestions.length > 0 && !hasExactMatch;
332
+ }
333
+ }, onblur: (e) => {
334
+ state.isActive = false;
335
+ // Delay closing to allow clicks on suggestions
336
+ setTimeout(() => {
337
+ if (!e.relatedTarget || !e.relatedTarget.closest('.autocomplete-content')) {
338
+ state.isOpen = false;
339
+ state.selectedIndex = -1;
340
+ m.redraw();
341
+ }
342
+ }, 150);
343
+ } })),
344
+ // Autocomplete dropdown
345
+ state.isOpen &&
346
+ m('ul.autocomplete-content.dropdown-content', {
347
+ style: getDropdownStyles(),
348
+ }, state.suggestions.map((suggestion, index) => m('li', {
349
+ key: suggestion.key,
350
+ class: state.selectedIndex === index ? 'active' : '',
351
+ onclick: (e) => {
352
+ e.preventDefault();
353
+ e.stopPropagation();
354
+ selectSuggestion(suggestion, attrs);
355
+ },
356
+ onmouseover: () => {
357
+ state.selectedIndex = index;
358
+ m.redraw();
359
+ },
360
+ }, [
361
+ // Check if value contains image URL or icon
362
+ suggestion.value && suggestion.value.includes('http')
363
+ ? m('img', {
364
+ src: suggestion.value,
365
+ class: 'right circle',
366
+ onerror: (e) => {
367
+ // Hide image if it fails to load
368
+ e.target.style.display = 'none';
369
+ },
370
+ })
371
+ : suggestion.value && suggestion.value.startsWith('icon:')
372
+ ? m('i.material-icons', {
373
+ style: {
374
+ fontSize: '24px',
375
+ color: 'var(--md-grey-600)',
376
+ },
377
+ }, suggestion.value.replace('icon:', ''))
378
+ : null,
379
+ m('span', suggestion.key
380
+ ? m.trust(suggestion.key.replace(replacer, (i) => `<span class="highlight">${i}</span>`))
381
+ : ''),
382
+ ]))),
383
+ m(Label, {
384
+ label,
385
+ id,
386
+ isMandatory,
387
+ isActive: state.isActive || state.inputValue.length > 0 || !!attrs.placeholder || !!attrs.initialValue,
388
+ }),
389
+ m(HelperText, { helperText }),
390
+ ]);
391
+ },
392
+ };
393
+ };
394
+
395
+ /**
396
+ * A simple material icon, defined by its icon name.
397
+ *
398
+ * @example m(Icon, { className: 'small' }, 'create') renders a small 'create' icon
399
+ * @example m(Icon, { className: 'prefix' }, iconName) renders the icon as a prefix
400
+ */
401
+ const Icon = () => ({
402
+ view: (_a) => {
403
+ var _b = _a.attrs, { iconName } = _b, passThrough = __rest(_b, ["iconName"]);
404
+ return m('i.material-icons', passThrough, iconName);
405
+ },
406
+ });
407
+
408
+ /**
409
+ * A factory to create new buttons.
410
+ *
411
+ * @example FlatButton = ButtonFactory('a.waves-effect.waves-teal.btn-flat');
412
+ */
413
+ const ButtonFactory = (element, defaultClassNames, type = '') => {
414
+ return () => {
415
+ return {
416
+ view: ({ attrs }) => {
417
+ const { modalId, tooltip, tooltipPostion, iconName, iconClass, label, className, attr } = attrs, params = __rest(attrs, ["modalId", "tooltip", "tooltipPostion", "iconName", "iconClass", "label", "className", "attr"]);
418
+ const cn = [modalId ? 'modal-trigger' : '', tooltip ? 'tooltipped' : '', defaultClassNames, className]
419
+ .filter(Boolean)
420
+ .join(' ')
421
+ .trim();
422
+ return m(element, Object.assign(Object.assign(Object.assign({}, params), attr), { className: cn, href: modalId ? `#${modalId}` : undefined, 'data-position': tooltip ? tooltipPostion || 'top' : undefined, 'data-tooltip': tooltip || undefined, type }),
423
+ // `${dca}${modalId ? `.modal-trigger[href=#${modalId}]` : ''}${
424
+ // tooltip ? `.tooltipped[data-position=${tooltipPostion || 'top'}][data-tooltip=${tooltip}]` : ''
425
+ // }${toAttributeString(attr)}`, {}
426
+ iconName ? m(Icon, { iconName, className: iconClass || 'left' }) : undefined, label ? label : undefined);
427
+ },
428
+ };
429
+ };
430
+ };
431
+ const Button = ButtonFactory('a', 'waves-effect waves-light btn', 'button');
432
+ const LargeButton = ButtonFactory('a', 'waves-effect waves-light btn-large', 'button');
433
+ const SmallButton = ButtonFactory('a', 'waves-effect waves-light btn-small', 'button');
434
+ const FlatButton = ButtonFactory('a', 'waves-effect waves-teal btn-flat', 'button');
435
+ const RoundIconButton = ButtonFactory('button', 'btn-floating btn-large waves-effect waves-light', 'button');
436
+ const SubmitButton = ButtonFactory('button', 'btn waves-effect waves-light', 'submit');
437
+
438
+ /**
439
+ * Materialize CSS Carousel component with dynamic positioning
440
+ * Port of the original MaterializeCSS carousel logic
441
+ */
442
+ const Carousel = () => {
443
+ // Default options based on original Materialize CSS
444
+ const defaults = {
445
+ duration: 200, // ms
446
+ dist: -100, // zoom scale
447
+ shift: 0, // spacing for center image
448
+ padding: 0, // Padding between non center items
449
+ numVisible: 5, // Number of visible items in carousel
450
+ fullWidth: false, // Change to full width styles
451
+ indicators: false, // Toggle indicators
452
+ noWrap: false, // Don't wrap around and cycle through items
453
+ };
454
+ const state = {
455
+ // Carousel state
456
+ hasMultipleSlides: false,
457
+ showIndicators: false,
458
+ noWrap: false,
459
+ pressed: false,
460
+ dragged: false,
461
+ verticalDragged: false,
462
+ offset: 0,
463
+ target: 0,
464
+ center: 0,
465
+ // Touch/drag state
466
+ reference: 0,
467
+ referenceY: 0,
468
+ velocity: 0,
469
+ amplitude: 0,
470
+ frame: 0,
471
+ timestamp: 0,
472
+ // Item measurements
473
+ itemWidth: 0,
474
+ itemHeight: 0,
475
+ dim: 1, // Make sure dim is non zero for divisions
476
+ // Animation
477
+ ticker: null,
478
+ scrollingTimeout: null};
479
+ // Utility functions
480
+ const xpos = (e) => {
481
+ // Touch event
482
+ if ('targetTouches' in e && e.targetTouches && e.targetTouches.length >= 1) {
483
+ return e.targetTouches[0].clientX;
484
+ }
485
+ // Mouse event
486
+ return e.clientX;
487
+ };
488
+ const ypos = (e) => {
489
+ // Touch event
490
+ if ('targetTouches' in e && e.targetTouches && e.targetTouches.length >= 1) {
491
+ return e.targetTouches[0].clientY;
492
+ }
493
+ // Mouse event
494
+ return e.clientY;
495
+ };
496
+ const wrap = (x, count) => {
497
+ return x >= count ? x % count : x < 0 ? wrap(count + (x % count), count) : x;
498
+ };
499
+ const track = () => {
500
+ const now = Date.now();
501
+ const elapsed = now - state.timestamp;
502
+ state.timestamp = now;
503
+ const delta = state.offset - state.frame;
504
+ state.frame = state.offset;
505
+ const v = (1000 * delta) / (1 + elapsed);
506
+ state.velocity = 0.8 * v + 0.2 * state.velocity;
507
+ };
508
+ const autoScroll = () => {
509
+ if (state.amplitude) {
510
+ const elapsed = Date.now() - state.timestamp;
511
+ const delta = state.amplitude * Math.exp(-elapsed / defaults.duration);
512
+ if (delta > 2 || delta < -2) {
513
+ scroll(state.target - delta);
514
+ requestAnimationFrame(autoScroll);
515
+ }
516
+ else {
517
+ scroll(state.target);
518
+ }
519
+ }
520
+ };
521
+ const updateItemStyle = (el, opacity, zIndex, transform) => {
522
+ el.style.transform = transform;
523
+ el.style.zIndex = zIndex.toString();
524
+ el.style.opacity = opacity.toString();
525
+ el.style.visibility = 'visible';
526
+ };
527
+ const scroll = (x, attrs) => {
528
+ const carouselEl = document.querySelector('.carousel');
529
+ if (!carouselEl)
530
+ return;
531
+ // Track scrolling state
532
+ if (!carouselEl.classList.contains('scrolling')) {
533
+ carouselEl.classList.add('scrolling');
534
+ }
535
+ if (state.scrollingTimeout != null) {
536
+ window.clearTimeout(state.scrollingTimeout);
537
+ }
538
+ state.scrollingTimeout = window.setTimeout(() => {
539
+ carouselEl.classList.remove('scrolling');
540
+ }, defaults.duration);
541
+ // Start actual scroll
542
+ const items = Array.from(carouselEl.querySelectorAll('.carousel-item'));
543
+ const count = items.length;
544
+ if (count === 0)
545
+ return;
546
+ const lastCenter = state.center;
547
+ const numVisibleOffset = 1 / defaults.numVisible;
548
+ state.offset = typeof x === 'number' ? x : state.offset;
549
+ state.center = Math.floor((state.offset + state.dim / 2) / state.dim);
550
+ const delta = state.offset - state.center * state.dim;
551
+ const dir = delta < 0 ? 1 : -1;
552
+ const tween = (-dir * delta * 2) / state.dim;
553
+ const half = count >> 1;
554
+ let alignment;
555
+ let centerTweenedOpacity;
556
+ if (defaults.fullWidth) {
557
+ alignment = 'translateX(0)';
558
+ centerTweenedOpacity = 1;
559
+ }
560
+ else {
561
+ alignment = `translateX(${(carouselEl.clientWidth - state.itemWidth) / 2}px) `;
562
+ alignment += `translateY(${(carouselEl.clientHeight - state.itemHeight) / 2}px)`;
563
+ centerTweenedOpacity = 1 - numVisibleOffset * tween;
564
+ }
565
+ // Set indicator active
566
+ if (state.showIndicators) {
567
+ const diff = state.center % count;
568
+ const indicators = carouselEl.querySelectorAll('.indicator-item');
569
+ indicators.forEach((indicator, index) => {
570
+ indicator.classList.toggle('active', index === diff);
571
+ });
572
+ }
573
+ // Center item
574
+ if (!state.noWrap || (state.center >= 0 && state.center < count)) {
575
+ const el = items[wrap(state.center, count)];
576
+ // Add active class to center item
577
+ items.forEach((item) => item.classList.remove('active'));
578
+ el.classList.add('active');
579
+ const transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * defaults.shift * tween}px) translateZ(${defaults.dist * tween}px)`;
580
+ updateItemStyle(el, centerTweenedOpacity, 0, transformString);
581
+ }
582
+ // Side items
583
+ for (let i = 1; i <= half; ++i) {
584
+ let zTranslation;
585
+ let tweenedOpacity;
586
+ // Right side
587
+ if (defaults.fullWidth) {
588
+ zTranslation = defaults.dist;
589
+ tweenedOpacity = i === half && delta < 0 ? 1 - tween : 1;
590
+ }
591
+ else {
592
+ zTranslation = defaults.dist * (i * 2 + tween * dir);
593
+ tweenedOpacity = 1 - numVisibleOffset * (i * 2 + tween * dir);
594
+ }
595
+ if (!state.noWrap || state.center + i < count) {
596
+ const el = items[wrap(state.center + i, count)];
597
+ const transformString = `${alignment} translateX(${defaults.shift + (state.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
598
+ updateItemStyle(el, tweenedOpacity, -i, transformString);
599
+ }
600
+ // Left side
601
+ if (defaults.fullWidth) {
602
+ zTranslation = defaults.dist;
603
+ tweenedOpacity = i === half && delta > 0 ? 1 - tween : 1;
604
+ }
605
+ else {
606
+ zTranslation = defaults.dist * (i * 2 - tween * dir);
607
+ tweenedOpacity = 1 - numVisibleOffset * (i * 2 - tween * dir);
608
+ }
609
+ if (!state.noWrap || state.center - i >= 0) {
610
+ const el = items[wrap(state.center - i, count)];
611
+ const transformString = `${alignment} translateX(${-defaults.shift + (-state.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
612
+ updateItemStyle(el, tweenedOpacity, -i, transformString);
613
+ }
614
+ }
615
+ // onCycleTo callback
616
+ if (lastCenter !== state.center && attrs && attrs.onCycleTo) {
617
+ const currItem = items[wrap(state.center, count)];
618
+ if (currItem) {
619
+ const itemIndex = Array.from(items).indexOf(currItem);
620
+ attrs.onCycleTo(attrs.items[itemIndex], itemIndex, state.dragged);
621
+ }
622
+ }
623
+ };
624
+ const cycleTo = (n, callback, _attrs) => {
625
+ const items = document.querySelectorAll('.carousel-item');
626
+ const count = items.length;
627
+ if (count === 0)
628
+ return;
629
+ let diff = (state.center % count) - n;
630
+ // Account for wraparound
631
+ if (!state.noWrap) {
632
+ if (diff < 0) {
633
+ if (Math.abs(diff + count) < Math.abs(diff)) {
634
+ diff += count;
635
+ }
636
+ }
637
+ else if (diff > 0) {
638
+ if (Math.abs(diff - count) < diff) {
639
+ diff -= count;
640
+ }
641
+ }
642
+ }
643
+ state.target = state.dim * Math.round(state.offset / state.dim);
644
+ if (diff < 0) {
645
+ state.target += state.dim * Math.abs(diff);
646
+ }
647
+ else if (diff > 0) {
648
+ state.target -= state.dim * diff;
649
+ }
650
+ // Scroll
651
+ if (state.offset !== state.target) {
652
+ state.amplitude = state.target - state.offset;
653
+ state.timestamp = Date.now();
654
+ requestAnimationFrame(autoScroll);
655
+ }
656
+ };
657
+ // Event handlers
658
+ const handleCarouselTap = (e) => {
659
+ // Fixes firefox draggable image bug
660
+ if (e.type === 'mousedown' && e.target.tagName === 'IMG') {
661
+ e.preventDefault();
662
+ }
663
+ state.pressed = true;
664
+ state.dragged = false;
665
+ state.verticalDragged = false;
666
+ state.reference = xpos(e);
667
+ state.referenceY = ypos(e);
668
+ state.velocity = state.amplitude = 0;
669
+ state.frame = state.offset;
670
+ state.timestamp = Date.now();
671
+ if (state.ticker)
672
+ clearInterval(state.ticker);
673
+ state.ticker = setInterval(track, 100);
674
+ };
675
+ const handleCarouselDrag = (e, attrs) => {
676
+ if (state.pressed) {
677
+ const x = xpos(e);
678
+ const y = ypos(e);
679
+ const delta = state.reference - x;
680
+ const deltaY = Math.abs(state.referenceY - y);
681
+ if (deltaY < 30 && !state.verticalDragged) {
682
+ if (delta > 2 || delta < -2) {
683
+ state.dragged = true;
684
+ state.reference = x;
685
+ scroll(state.offset + delta, attrs);
686
+ }
687
+ }
688
+ else if (state.dragged) {
689
+ e.preventDefault();
690
+ e.stopPropagation();
691
+ return false;
692
+ }
693
+ else {
694
+ state.verticalDragged = true;
695
+ }
696
+ }
697
+ if (state.dragged) {
698
+ e.preventDefault();
699
+ e.stopPropagation();
700
+ return false;
701
+ }
702
+ return true;
703
+ };
704
+ const handleCarouselRelease = (e, _attrs) => {
705
+ if (state.pressed) {
706
+ state.pressed = false;
707
+ }
708
+ else {
709
+ return;
710
+ }
711
+ if (state.ticker)
712
+ clearInterval(state.ticker);
713
+ state.target = state.offset;
714
+ if (state.velocity > 10 || state.velocity < -10) {
715
+ state.amplitude = 0.9 * state.velocity;
716
+ state.target = state.offset + state.amplitude;
717
+ }
718
+ state.target = Math.round(state.target / state.dim) * state.dim;
719
+ // No wrap of items
720
+ if (state.noWrap) {
721
+ const items = document.querySelectorAll('.carousel-item');
722
+ if (state.target >= state.dim * (items.length - 1)) {
723
+ state.target = state.dim * (items.length - 1);
724
+ }
725
+ else if (state.target < 0) {
726
+ state.target = 0;
727
+ }
728
+ }
729
+ state.amplitude = state.target - state.offset;
730
+ state.timestamp = Date.now();
731
+ requestAnimationFrame(autoScroll);
732
+ if (state.dragged) {
733
+ e.preventDefault();
734
+ e.stopPropagation();
735
+ }
736
+ return false;
737
+ };
738
+ const handleCarouselClick = (e, attrs) => {
739
+ if (state.dragged) {
740
+ e.preventDefault();
741
+ e.stopPropagation();
742
+ return false;
743
+ }
744
+ else if (!defaults.fullWidth) {
745
+ const target = e.target.closest('.carousel-item');
746
+ if (target) {
747
+ const items = Array.from(document.querySelectorAll('.carousel-item'));
748
+ const clickedIndex = items.indexOf(target);
749
+ const diff = wrap(state.center, items.length) - clickedIndex;
750
+ if (diff !== 0) {
751
+ e.preventDefault();
752
+ e.stopPropagation();
753
+ }
754
+ cycleTo(clickedIndex);
755
+ }
756
+ }
757
+ return true;
758
+ };
759
+ const handleIndicatorClick = (e, attrs) => {
760
+ e.stopPropagation();
761
+ const indicator = e.target.closest('.indicator-item');
762
+ if (indicator) {
763
+ const indicators = Array.from(document.querySelectorAll('.indicator-item'));
764
+ const index = indicators.indexOf(indicator);
765
+ cycleTo(index);
766
+ }
767
+ };
768
+ return {
769
+ view: ({ attrs }) => {
770
+ const { items, indicators = false } = attrs;
771
+ if (!items || items.length === 0)
772
+ return undefined;
773
+ // Merge options
774
+ Object.assign(defaults, attrs);
775
+ const supportTouch = typeof window.ontouchstart !== 'undefined';
776
+ return m('.carousel', {
777
+ oncreate: ({ attrs, dom }) => {
778
+ const carouselEl = dom;
779
+ const items = carouselEl.querySelectorAll('.carousel-item');
780
+ state.hasMultipleSlides = items.length > 1;
781
+ state.showIndicators = defaults.indicators && state.hasMultipleSlides;
782
+ state.noWrap = defaults.noWrap || !state.hasMultipleSlides;
783
+ if (items.length > 0) {
784
+ const firstItem = items[0];
785
+ state.itemWidth = firstItem.offsetWidth;
786
+ state.itemHeight = firstItem.offsetHeight;
787
+ state.dim = state.itemWidth * 2 + defaults.padding || 1;
788
+ }
789
+ // Cap numVisible at count
790
+ defaults.numVisible = Math.min(items.length, defaults.numVisible);
791
+ // Initial scroll
792
+ scroll(state.offset, attrs);
793
+ },
794
+ onmousedown: (e) => handleCarouselTap(e),
795
+ onmousemove: (e) => handleCarouselDrag(e, attrs),
796
+ onmouseup: (e) => handleCarouselRelease(e),
797
+ onmouseleave: (e) => handleCarouselRelease(e),
798
+ onclick: (e) => handleCarouselClick(e),
799
+ ontouchstart: supportTouch ? (e) => handleCarouselTap(e) : undefined,
800
+ ontouchmove: supportTouch ? (e) => handleCarouselDrag(e, attrs) : undefined,
801
+ ontouchend: supportTouch ? (e) => handleCarouselRelease(e) : undefined,
802
+ }, [
803
+ // Carousel items
804
+ ...items.map((item) => m('a.carousel-item', {
805
+ // key: index,
806
+ href: item.href,
807
+ style: 'visibility: hidden;', // Initially hidden, will be shown by scroll
808
+ }, m('img', { src: item.src, alt: item.alt }))),
809
+ // Indicators
810
+ indicators &&
811
+ items.length > 1 &&
812
+ m('ul.indicators', items.map((_, index) => m('li.indicator-item', {
813
+ key: `indicator-${index}`,
814
+ className: index === 0 ? 'active' : '',
815
+ onclick: (e) => handleIndicatorClick(e),
816
+ }))),
817
+ ]);
818
+ },
819
+ };
820
+ };
821
+
822
+ const iconPaths = {
823
+ caret: [
824
+ 'M7 10l5 5 5-5z', // arrow
825
+ 'M0 0h24v24H0z', // background
826
+ ],
827
+ close: [
828
+ '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',
829
+ 'M0 0h24v24H0z',
830
+ ],
831
+ };
832
+ const MaterialIcon = () => {
833
+ return {
834
+ view: ({ attrs }) => {
835
+ var _a;
836
+ const { name, direction = 'down', style } = attrs, props = __rest(attrs, ["name", "direction", "style"]);
837
+ const rotationMap = {
838
+ down: 0,
839
+ up: 180,
840
+ left: -90,
841
+ right: 90,
842
+ };
843
+ const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
844
+ const transform = rotation ? `rotate(${rotation}deg)` : undefined;
845
+ return m('svg', Object.assign(Object.assign({}, props), { style: Object.assign({ transform }, style), height: '1lh', width: '24', viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' }), iconPaths[name].map((d) => m('path', {
846
+ d,
847
+ fill: d.includes('M0 0h24v24H0z') ? 'none' : 'currentColor',
848
+ })));
849
+ },
850
+ };
851
+ };
852
+
853
+ const Chips = () => {
854
+ const state = {
855
+ chipsData: [],
856
+ selectedChip: null,
857
+ focused: false,
858
+ inputValue: '',
859
+ inputId: uniqueId(),
860
+ autocompleteItems: [],
861
+ selectedAutocompleteIndex: -1,
862
+ showAutocomplete: false,
863
+ };
864
+ let currentVnode = null;
865
+ const processAutocompleteData = (data) => {
866
+ if (Array.isArray(data)) {
867
+ return data.map((item) => {
868
+ if (typeof item === 'string') {
869
+ return { tag: item };
870
+ }
871
+ return item;
872
+ });
873
+ }
874
+ return Object.entries(data).map(([text, value]) => ({
875
+ tag: text,
876
+ value: value || text,
877
+ }));
878
+ };
879
+ const updateAutocomplete = () => {
880
+ var _a;
881
+ if (!((_a = currentVnode === null || currentVnode === void 0 ? void 0 : currentVnode.attrs.autocompleteOptions) === null || _a === void 0 ? void 0 : _a.data)) {
882
+ state.autocompleteItems = [];
883
+ return;
884
+ }
885
+ const { data, minLength = 1 } = currentVnode.attrs.autocompleteOptions;
886
+ const input = state.inputValue.toLowerCase();
887
+ if (input.length < minLength) {
888
+ state.autocompleteItems = [];
889
+ state.showAutocomplete = false;
890
+ return;
891
+ }
892
+ const allOptions = processAutocompleteData(data);
893
+ const filtered = allOptions.filter((option) => option.tag.toLowerCase().includes(input));
894
+ const limit = currentVnode.attrs.autocompleteOptions.limit || Infinity;
895
+ state.autocompleteItems = filtered.slice(0, limit);
896
+ state.showAutocomplete = state.autocompleteItems.length > 0;
897
+ state.selectedAutocompleteIndex = -1;
898
+ };
899
+ const selectAutocompleteItem = (item) => {
900
+ addChip({
901
+ tag: item.tag,
902
+ image: item.image,
903
+ alt: item.alt, // Preserve alt text when converting to chip
904
+ });
905
+ state.inputValue = '';
906
+ state.showAutocomplete = false;
907
+ state.selectedAutocompleteIndex = -1;
908
+ };
909
+ const isValid = (chip, currentChips) => {
910
+ if (!chip.tag || chip.tag.trim() === '')
911
+ return false;
912
+ return !currentChips.some((c) => c.tag === chip.tag);
913
+ };
914
+ const addChip = (chip) => {
915
+ if (!currentVnode)
916
+ return;
917
+ const { limit = Infinity, onChipAdd, onchange } = currentVnode.attrs;
918
+ if (!isValid(chip, state.chipsData) || state.chipsData.length >= limit) {
919
+ return;
920
+ }
921
+ state.chipsData = [...state.chipsData, chip];
922
+ state.inputValue = '';
923
+ if (onChipAdd)
924
+ onChipAdd(chip);
925
+ if (onchange)
926
+ onchange(state.chipsData);
927
+ };
928
+ const deleteChip = (index) => {
929
+ if (!currentVnode)
930
+ return;
931
+ const { onChipDelete, onchange } = currentVnode.attrs;
932
+ const chip = state.chipsData[index];
933
+ state.chipsData = state.chipsData.filter((_, i) => i !== index);
934
+ state.selectedChip = null;
935
+ if (onChipDelete)
936
+ onChipDelete(chip);
937
+ if (onchange)
938
+ onchange(state.chipsData);
939
+ };
940
+ const selectChip = (index) => {
941
+ if (!currentVnode)
942
+ return;
943
+ const { onChipSelect } = currentVnode.attrs;
944
+ state.selectedChip = index;
945
+ if (onChipSelect && state.chipsData[index]) {
946
+ onChipSelect(state.chipsData[index]);
947
+ }
948
+ };
949
+ const handleKeydown = (e) => {
950
+ const target = e.target;
951
+ if (state.showAutocomplete) {
952
+ if (e.key === 'ArrowDown') {
953
+ e.preventDefault();
954
+ state.selectedAutocompleteIndex = Math.min(state.selectedAutocompleteIndex + 1, state.autocompleteItems.length - 1);
955
+ const selectedItem = currentVnode === null || currentVnode === void 0 ? void 0 : currentVnode.dom.querySelector('.autocomplete-item.selected');
956
+ if (selectedItem) {
957
+ selectedItem.scrollIntoView({ block: 'nearest' });
958
+ }
959
+ return;
960
+ }
961
+ if (e.key === 'ArrowUp') {
962
+ e.preventDefault();
963
+ state.selectedAutocompleteIndex = Math.max(state.selectedAutocompleteIndex - 1, -1);
964
+ const selectedItem = currentVnode === null || currentVnode === void 0 ? void 0 : currentVnode.dom.querySelector('.autocomplete-item.selected');
965
+ if (selectedItem) {
966
+ selectedItem.scrollIntoView({ block: 'nearest' });
967
+ }
968
+ return;
969
+ }
970
+ if (e.key === 'Enter' && state.selectedAutocompleteIndex >= 0) {
971
+ e.preventDefault();
972
+ selectAutocompleteItem(state.autocompleteItems[state.selectedAutocompleteIndex]);
973
+ return;
974
+ }
975
+ }
976
+ if (e.key === 'Enter' && target.value.trim()) {
977
+ e.preventDefault();
978
+ addChip({ tag: target.value.trim() });
979
+ }
980
+ else if (e.key === 'Backspace' && !target.value && state.chipsData.length > 0) {
981
+ e.preventDefault();
982
+ // Delete the last chip immediately when backspace is pressed in an empty input
983
+ deleteChip(state.chipsData.length - 1);
984
+ }
985
+ else if (e.key === 'ArrowLeft' && !target.value && state.chipsData.length) {
986
+ e.preventDefault();
987
+ selectChip(state.chipsData.length - 1);
988
+ }
989
+ };
990
+ const handleChipKeydown = (e, index) => {
991
+ if (e.key === 'Backspace' || e.key === 'Delete') {
992
+ e.preventDefault();
993
+ deleteChip(index);
994
+ const newIndex = Math.max(index - 1, 0);
995
+ if (state.chipsData.length)
996
+ selectChip(newIndex);
997
+ }
998
+ else if (e.key === 'ArrowLeft' && index > 0) {
999
+ selectChip(index - 1);
1000
+ }
1001
+ else if (e.key === 'ArrowRight') {
1002
+ if (index < state.chipsData.length - 1) {
1003
+ selectChip(index + 1);
1004
+ }
1005
+ else {
1006
+ const input = currentVnode === null || currentVnode === void 0 ? void 0 : currentVnode.dom.querySelector('.chips-input');
1007
+ if (input)
1008
+ input.focus();
1009
+ }
1010
+ }
1011
+ };
1012
+ return {
1013
+ oninit: ({ attrs }) => {
1014
+ state.chipsData = attrs.data || [];
1015
+ },
1016
+ oncreate: (vnode) => {
1017
+ currentVnode = vnode;
1018
+ },
1019
+ onremove: () => {
1020
+ currentVnode = null;
1021
+ },
1022
+ view: ({ attrs }) => {
1023
+ const { id, required, isMandatory = required, className = 'col s12', label, helperText, placeholder, secondaryPlaceholder, } = attrs;
1024
+ const getPlaceholder = () => {
1025
+ if (!state.chipsData.length && placeholder) {
1026
+ return placeholder;
1027
+ }
1028
+ if (state.chipsData.length && secondaryPlaceholder) {
1029
+ return secondaryPlaceholder;
1030
+ }
1031
+ return '';
1032
+ };
1033
+ return m('.input-field', { id, className }, [
1034
+ m('.chips.chips-initial', {
1035
+ class: `chips-container${state.focused ? ' focused' : ''}${placeholder ? ' chips-placeholder' : ''}`,
1036
+ }, [
1037
+ // Chips
1038
+ state.chipsData.map((chip, index) => m('.chip', {
1039
+ key: `${chip.tag}-${index}`,
1040
+ tabindex: 0,
1041
+ class: state.selectedChip === index ? 'selected' : undefined,
1042
+ onkeydown: (e) => handleChipKeydown(e, index),
1043
+ }, [
1044
+ chip.image &&
1045
+ m('img', {
1046
+ src: chip.image,
1047
+ alt: chip.alt || chip.tag,
1048
+ }),
1049
+ chip.tag,
1050
+ m(MaterialIcon, {
1051
+ name: 'close',
1052
+ className: 'close',
1053
+ onclick: (e) => {
1054
+ e.stopPropagation();
1055
+ deleteChip(index);
1056
+ },
1057
+ }),
1058
+ ])),
1059
+ // Input
1060
+ m('input[type=text].chips-input.input.browser-default', {
1061
+ id: state.inputId,
1062
+ title: 'label',
1063
+ value: state.inputValue,
1064
+ placeholder: getPlaceholder(),
1065
+ oninput: (e) => {
1066
+ state.inputValue = e.target.value;
1067
+ updateAutocomplete();
1068
+ },
1069
+ onfocus: () => {
1070
+ state.focused = true;
1071
+ state.selectedChip = null;
1072
+ updateAutocomplete();
1073
+ },
1074
+ onblur: () => {
1075
+ state.focused = false;
1076
+ setTimeout(() => {
1077
+ state.showAutocomplete = false;
1078
+ state.selectedChip = null;
1079
+ }, 150);
1080
+ },
1081
+ onkeydown: handleKeydown,
1082
+ }),
1083
+ state.showAutocomplete &&
1084
+ m('ul.autocomplete-content.dropdown-content', {
1085
+ style: {
1086
+ display: 'block',
1087
+ opacity: 1,
1088
+ transform: 'scaleX(1) scaleY(1)',
1089
+ position: 'absolute',
1090
+ width: '100%',
1091
+ left: 0,
1092
+ top: '100%',
1093
+ maxHeight: '200px',
1094
+ overflow: 'auto',
1095
+ zIndex: 1000,
1096
+ backgroundColor: '#fff',
1097
+ boxShadow: '0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2)',
1098
+ },
1099
+ }, state.autocompleteItems.map((item, index) => m('li.autocomplete-item', {
1100
+ key: item.tag,
1101
+ class: state.selectedAutocompleteIndex === index ? 'selected' : '',
1102
+ style: {
1103
+ padding: '12px 16px',
1104
+ cursor: 'pointer',
1105
+ backgroundColor: state.selectedAutocompleteIndex === index ? '#eee' : 'transparent',
1106
+ },
1107
+ onmousedown: (e) => {
1108
+ e.preventDefault();
1109
+ selectAutocompleteItem(item);
1110
+ },
1111
+ onmouseover: () => {
1112
+ state.selectedAutocompleteIndex = index;
1113
+ },
1114
+ }, [
1115
+ item.image &&
1116
+ m('img.autocomplete-item-image', {
1117
+ src: item.image,
1118
+ alt: item.alt || item.tag,
1119
+ style: {
1120
+ width: '24px',
1121
+ height: '24px',
1122
+ marginRight: '8px',
1123
+ verticalAlign: 'middle',
1124
+ },
1125
+ }),
1126
+ m('span.autocomplete-item-text', item.tag),
1127
+ ]))),
1128
+ ]),
1129
+ label &&
1130
+ m(Label, {
1131
+ label,
1132
+ id: state.inputId,
1133
+ isMandatory,
1134
+ isActive: state.focused || state.chipsData.length || placeholder ? true : false,
1135
+ }),
1136
+ helperText && m(HelperText, { helperText }),
1137
+ ]);
1138
+ },
1139
+ };
1140
+ };
1141
+
1142
+ /** A simple code block without syntax high-lighting */
1143
+ const CodeBlock = () => ({
1144
+ view: ({ attrs }) => {
1145
+ const { newRow, code, language, className } = attrs, params = __rest(attrs, ["newRow", "code", "language", "className"]);
1146
+ const lang = language || 'lang-TypeScript';
1147
+ const label = lang.replace('lang-', '');
1148
+ const cb = code instanceof Array ? code.join('\n') : code;
1149
+ const cn = [newRow ? 'clear' : '', lang, className].filter(Boolean).join(' ').trim();
1150
+ return m(`pre.codeblock${newRow ? '.clear' : ''}`, attrs, [
1151
+ m('div', m('label', label)),
1152
+ m('code', Object.assign(Object.assign({}, params), { className: cn }), cb),
1153
+ ]);
1154
+ },
1155
+ });
1156
+
1157
+ const CollapsibleItem = () => {
1158
+ return {
1159
+ view: ({ attrs: { header, body, iconName, isActive, onToggle } }) => {
1160
+ return m('li', { className: isActive ? 'active' : '' }, [
1161
+ header || iconName
1162
+ ? m('.collapsible-header', {
1163
+ onclick: onToggle,
1164
+ style: {
1165
+ cursor: 'pointer',
1166
+ padding: '1rem',
1167
+ backgroundColor: '#fff',
1168
+ borderBottom: '1px solid #ddd',
1169
+ display: 'flex',
1170
+ alignItems: 'center',
1171
+ transition: 'background-color 0.2s ease',
1172
+ },
1173
+ onmouseover: (e) => {
1174
+ e.target.style.backgroundColor = '#f5f5f5';
1175
+ },
1176
+ onmouseleave: (e) => {
1177
+ e.target.style.backgroundColor = '#fff';
1178
+ },
1179
+ }, [
1180
+ iconName ? m('i.material-icons', { style: { marginRight: '1rem' } }, iconName) : undefined,
1181
+ header ? (typeof header === 'string' ? m('span', header) : header) : undefined,
1182
+ ])
1183
+ : undefined,
1184
+ m('.collapsible-body', {
1185
+ style: {
1186
+ display: isActive ? 'block' : 'none',
1187
+ transition: 'display 0.3s ease',
1188
+ },
1189
+ }, [
1190
+ m('.collapsible-body-content', {
1191
+ style: { padding: '2rem' },
1192
+ }, body ? (typeof body === 'string' ? m('div', { innerHTML: body }) : body) : undefined),
1193
+ ]),
1194
+ ]);
1195
+ },
1196
+ };
1197
+ };
1198
+ /**
1199
+ * Creates a collabsible or accordion component with pure CSS/Mithril implementation.
1200
+ * No MaterializeCSS JavaScript dependencies.
1201
+ */
1202
+ const Collapsible = () => {
1203
+ const state = {
1204
+ activeItems: new Set(),
1205
+ };
1206
+ return {
1207
+ oninit: ({ attrs }) => {
1208
+ // Initialize active items from the items array
1209
+ attrs.items.forEach((item, index) => {
1210
+ if (item.active) {
1211
+ state.activeItems.add(index);
1212
+ }
1213
+ });
1214
+ },
1215
+ view: ({ attrs }) => {
1216
+ const { items, accordion = true, class: c, className, style, id } = attrs;
1217
+ const toggleItem = (index) => {
1218
+ if (accordion) {
1219
+ // Accordion mode: only one item can be active
1220
+ if (state.activeItems.has(index)) {
1221
+ state.activeItems.clear();
1222
+ }
1223
+ else {
1224
+ state.activeItems.clear();
1225
+ state.activeItems.add(index);
1226
+ }
1227
+ }
1228
+ else {
1229
+ // Expandable mode: multiple items can be active
1230
+ if (state.activeItems.has(index)) {
1231
+ state.activeItems.delete(index);
1232
+ }
1233
+ else {
1234
+ state.activeItems.add(index);
1235
+ }
1236
+ }
1237
+ };
1238
+ return items && items.length > 0
1239
+ ? m('ul.collapsible', {
1240
+ class: c || className,
1241
+ style: Object.assign({ border: '1px solid #ddd', borderRadius: '2px', margin: '0.5rem 0 1rem 0' }, style),
1242
+ id,
1243
+ }, items.map((item, index) => m(CollapsibleItem, Object.assign(Object.assign({}, item), { key: index, isActive: state.activeItems.has(index), onToggle: () => toggleItem(index) }))))
1244
+ : undefined;
1245
+ },
1246
+ };
1247
+ };
1248
+
1249
+ exports.CollectionMode = void 0;
1250
+ (function (CollectionMode) {
1251
+ CollectionMode[CollectionMode["BASIC"] = 0] = "BASIC";
1252
+ CollectionMode[CollectionMode["LINKS"] = 1] = "LINKS";
1253
+ CollectionMode[CollectionMode["AVATAR"] = 2] = "AVATAR";
1254
+ })(exports.CollectionMode || (exports.CollectionMode = {}));
1255
+ const isNonLocalRoute = (url) => url && /https?:\/\//.test(url);
1256
+ const SecondaryContent = () => {
1257
+ return {
1258
+ view: ({ attrs }) => {
1259
+ const { href, iconName = 'send', onclick, style = { cursor: 'pointer' } } = attrs;
1260
+ const props = {
1261
+ href,
1262
+ style,
1263
+ className: 'secondary-content',
1264
+ onclick: onclick ? () => onclick(attrs) : undefined,
1265
+ };
1266
+ return isNonLocalRoute(href) || !href
1267
+ ? m('a[target=_]', props, m(Icon, { iconName }))
1268
+ : m(m.route.Link, props, m(Icon, { iconName }));
1269
+ },
1270
+ };
1271
+ };
1272
+ const avatarIsImage = (avatar = '') => /\./.test(avatar);
1273
+ const ListItem = () => {
1274
+ return {
1275
+ view: ({ attrs: { item, mode } }) => {
1276
+ const { title, content = '', active, iconName, avatar, className, onclick } = item;
1277
+ return mode === exports.CollectionMode.AVATAR
1278
+ ? m('li.collection-item.avatar', {
1279
+ className: active ? 'active' : '',
1280
+ onclick: onclick ? () => onclick(item) : undefined,
1281
+ }, [
1282
+ avatarIsImage(avatar)
1283
+ ? m('img.circle', { src: avatar })
1284
+ : m('i.material-icons.circle', { className }, avatar),
1285
+ m('span.title', title),
1286
+ m('p', m.trust(content)),
1287
+ m(SecondaryContent, item),
1288
+ ])
1289
+ : m('li.collection-item', {
1290
+ className: active ? 'active' : '',
1291
+ }, iconName ? m('div', [title, m(SecondaryContent, item)]) : title);
1292
+ },
1293
+ };
1294
+ };
1295
+ const BasicCollection = () => {
1296
+ return {
1297
+ view: (_a) => {
1298
+ var _b = _a.attrs, { header, items, mode = exports.CollectionMode.BASIC } = _b, params = __rest(_b, ["header", "items", "mode"]);
1299
+ const collectionItems = items.map((item) => m(ListItem, { key: item.id, item, mode }));
1300
+ return header
1301
+ ? m('ul.collection.with-header', params, [m('li.collection-header', m('h4', header)), collectionItems])
1302
+ : m('ul.collection', params, collectionItems);
1303
+ },
1304
+ };
1305
+ };
1306
+ const AnchorItem = () => {
1307
+ return {
1308
+ view: ({ attrs: { item } }) => {
1309
+ const { title, active, href } = item, params = __rest(item, ["title", "active", "href"]);
1310
+ const props = Object.assign(Object.assign({}, params), { className: `collection-item ${active ? 'active' : ''}`, href });
1311
+ return isNonLocalRoute(href) || !href
1312
+ ? m('a[target=_]', props, title)
1313
+ : m(m.route.Link, props, title);
1314
+ },
1315
+ };
1316
+ };
1317
+ const LinksCollection = () => {
1318
+ return {
1319
+ view: (_a) => {
1320
+ var _b = _a.attrs, { items, header } = _b, params = __rest(_b, ["items", "header"]);
1321
+ return header
1322
+ ? m('.collection.with-header', params, [
1323
+ m('.collection-header', m('h4', header)),
1324
+ items.map((item) => m(AnchorItem, { key: item.id, item })),
1325
+ ])
1326
+ : m('.collection', params, items.map((item) => m(AnchorItem, { key: item.id, item })));
1327
+ },
1328
+ };
1329
+ };
1330
+ /**
1331
+ * Creates a Collection of items, optionally containing links, headers, secondary content or avatars.
1332
+ * @see https://materializecss.com/collections.html
1333
+ */
1334
+ const Collection = () => {
1335
+ return {
1336
+ view: (_a) => {
1337
+ var _b = _a.attrs, { items, header, mode = exports.CollectionMode.BASIC } = _b, params = __rest(_b, ["items", "header", "mode"]);
1338
+ return header || (items && items.length > 0)
1339
+ ? mode === exports.CollectionMode.LINKS
1340
+ ? m(LinksCollection, Object.assign({ header, items }, params))
1341
+ : m(BasicCollection, Object.assign({ header, items, mode }, params))
1342
+ : undefined;
1343
+ },
1344
+ };
1345
+ };
1346
+
1347
+ /** Pure TypeScript Dropdown component - no Materialize dependencies */
1348
+ const Dropdown = () => {
1349
+ const state = {
1350
+ isOpen: false,
1351
+ initialValue: undefined,
1352
+ id: '',
1353
+ focusedIndex: -1,
1354
+ inputRef: null,
1355
+ dropdownRef: null,
1356
+ };
1357
+ const handleKeyDown = (e, items, onchange) => {
1358
+ const availableItems = items.filter((item) => !item.divider && !item.disabled);
1359
+ switch (e.key) {
1360
+ case 'ArrowDown':
1361
+ e.preventDefault();
1362
+ if (!state.isOpen) {
1363
+ state.isOpen = true;
1364
+ state.focusedIndex = 0;
1365
+ }
1366
+ else {
1367
+ state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
1368
+ }
1369
+ break;
1370
+ case 'ArrowUp':
1371
+ e.preventDefault();
1372
+ if (state.isOpen) {
1373
+ state.focusedIndex = Math.max(state.focusedIndex - 1, 0);
1374
+ }
1375
+ break;
1376
+ case 'Enter':
1377
+ case ' ':
1378
+ e.preventDefault();
1379
+ if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
1380
+ const selectedItem = availableItems[state.focusedIndex];
1381
+ const value = (selectedItem.id || selectedItem.label);
1382
+ state.initialValue = value;
1383
+ state.isOpen = false;
1384
+ state.focusedIndex = -1;
1385
+ if (onchange)
1386
+ onchange(value);
1387
+ }
1388
+ else if (!state.isOpen) {
1389
+ state.isOpen = true;
1390
+ state.focusedIndex = 0;
1391
+ }
1392
+ break;
1393
+ case 'Escape':
1394
+ e.preventDefault();
1395
+ state.isOpen = false;
1396
+ state.focusedIndex = -1;
1397
+ break;
1398
+ }
1399
+ };
1400
+ return {
1401
+ oninit: ({ attrs: { id = uniqueId(), initialValue, checkedId } }) => {
1402
+ state.id = id;
1403
+ state.initialValue = initialValue || checkedId;
1404
+ // Mithril will handle click events through the component structure
1405
+ },
1406
+ view: ({ attrs: { key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12' }, }) => {
1407
+ const { initialValue } = state;
1408
+ const selectedItem = initialValue
1409
+ ? items.filter((i) => (i.id ? i.id === initialValue : i.label === initialValue)).shift()
1410
+ : undefined;
1411
+ const title = selectedItem ? selectedItem.label : label || 'Select';
1412
+ const availableItems = items.filter((item) => !item.divider && !item.disabled);
1413
+ return m('.dropdown-wrapper.input-field', { className, key, style }, [
1414
+ iconName ? m('i.material-icons.prefix', iconName) : undefined,
1415
+ m(HelperText, { helperText }),
1416
+ m('.select-wrapper', {
1417
+ onclick: disabled
1418
+ ? undefined
1419
+ : () => {
1420
+ state.isOpen = !state.isOpen;
1421
+ state.focusedIndex = state.isOpen ? 0 : -1;
1422
+ },
1423
+ onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
1424
+ tabindex: disabled ? -1 : 0,
1425
+ 'aria-expanded': state.isOpen ? 'true' : 'false',
1426
+ 'aria-haspopup': 'listbox',
1427
+ role: 'combobox',
1428
+ }, [
1429
+ m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
1430
+ id: state.id,
1431
+ value: title,
1432
+ oncreate: ({ dom }) => {
1433
+ state.inputRef = dom;
1434
+ },
1435
+ onclick: (e) => {
1436
+ e.preventDefault();
1437
+ e.stopPropagation();
1438
+ if (!disabled) {
1439
+ state.isOpen = !state.isOpen;
1440
+ state.focusedIndex = state.isOpen ? 0 : -1;
1441
+ }
1442
+ },
1443
+ }),
1444
+ // Dropdown Menu using Select component's positioning logic
1445
+ state.isOpen &&
1446
+ m('ul.dropdown-content.select-dropdown', {
1447
+ tabindex: 0,
1448
+ role: 'listbox',
1449
+ 'aria-labelledby': state.id,
1450
+ oncreate: ({ dom }) => {
1451
+ state.dropdownRef = dom;
1452
+ },
1453
+ onremove: () => {
1454
+ state.dropdownRef = null;
1455
+ },
1456
+ style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
1457
+ // Convert dropdown items to format expected by getDropdownStyles
1458
+ group: undefined }))), true),
1459
+ }, items.map((item, index) => {
1460
+ if (item.divider) {
1461
+ return m('li.divider', {
1462
+ key: `divider-${index}`,
1463
+ });
1464
+ }
1465
+ const itemIndex = availableItems.indexOf(item);
1466
+ const isFocused = itemIndex === state.focusedIndex;
1467
+ return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
1468
+ item.disabled ? 'disabled' : '',
1469
+ isFocused ? 'focused' : '',
1470
+ (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
1471
+ ]
1472
+ .filter(Boolean)
1473
+ .join(' ') }, (item.disabled
1474
+ ? {}
1475
+ : {
1476
+ onclick: (e) => {
1477
+ e.stopPropagation();
1478
+ const value = (item.id || item.label);
1479
+ state.initialValue = value;
1480
+ state.isOpen = false;
1481
+ state.focusedIndex = -1;
1482
+ if (onchange)
1483
+ onchange(value);
1484
+ },
1485
+ })), m('span', {
1486
+ style: {
1487
+ display: 'flex',
1488
+ alignItems: 'center',
1489
+ padding: '14px 16px',
1490
+ },
1491
+ }, [
1492
+ item.iconName
1493
+ ? m('i.material-icons', {
1494
+ style: { marginRight: '32px' },
1495
+ }, item.iconName)
1496
+ : undefined,
1497
+ item.label,
1498
+ ]));
1499
+ })),
1500
+ m(MaterialIcon, {
1501
+ name: 'caret',
1502
+ direction: 'down',
1503
+ }),
1504
+ ]),
1505
+ ]);
1506
+ },
1507
+ };
1508
+ };
1509
+
1510
+ /**
1511
+ * Floating Action Button
1512
+ */
1513
+ const FloatingActionButton = () => {
1514
+ const state = {
1515
+ isOpen: false,
1516
+ };
1517
+ const handleClickOutside = (e) => {
1518
+ const target = e.target;
1519
+ if (!target.closest('.fixed-action-btn')) {
1520
+ state.isOpen = false;
1521
+ }
1522
+ };
1523
+ return {
1524
+ oncreate: () => {
1525
+ document.addEventListener('click', handleClickOutside);
1526
+ },
1527
+ onremove: () => {
1528
+ document.removeEventListener('click', handleClickOutside);
1529
+ },
1530
+ view: ({ attrs: { className, iconName, iconClass, position, style = position === 'left' || position === 'inline-left'
1531
+ ? 'position: absolute; display: inline-block; left: 24px;'
1532
+ : position === 'right' || position === 'inline-right'
1533
+ ? 'position: absolute; display: inline-block; right: 24px;'
1534
+ : undefined, buttons, direction = 'top', hoverEnabled = true, }, }) => {
1535
+ const fabClasses = [
1536
+ 'fixed-action-btn',
1537
+ direction ? `direction-${direction}` : '',
1538
+ state.isOpen ? 'active' : '',
1539
+ // hoverEnabled ? 'hover-enabled' : '',
1540
+ ]
1541
+ .filter(Boolean)
1542
+ .join(' ');
1543
+ return m('div', {
1544
+ style: position === 'inline-right' || position === 'inline-left' ? 'position: relative; height: 70px;' : undefined,
1545
+ }, m(`.${fabClasses}`, {
1546
+ style,
1547
+ onclick: (e) => {
1548
+ e.stopPropagation();
1549
+ if (buttons && buttons.length > 0) {
1550
+ state.isOpen = !state.isOpen;
1551
+ }
1552
+ },
1553
+ onmouseover: hoverEnabled
1554
+ ? () => {
1555
+ if (buttons && buttons.length > 0) {
1556
+ state.isOpen = true;
1557
+ }
1558
+ }
1559
+ : undefined,
1560
+ onmouseleave: hoverEnabled
1561
+ ? () => {
1562
+ state.isOpen = false;
1563
+ }
1564
+ : undefined,
1565
+ }, [
1566
+ m('a.btn-floating.btn-large', {
1567
+ className,
1568
+ }, m('i.material-icons', { className: iconClass }, iconName)),
1569
+ buttons &&
1570
+ buttons.length > 0 &&
1571
+ m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
1572
+ style: {
1573
+ opacity: state.isOpen ? '1' : '0',
1574
+ transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
1575
+ transition: `all 0.3s ease ${index * 40}ms`,
1576
+ },
1577
+ onclick: (e) => {
1578
+ e.stopPropagation();
1579
+ if (button.onClick)
1580
+ button.onClick(e);
1581
+ },
1582
+ }, m('i.material-icons', { className: button.iconClass }, button.iconName))))),
1583
+ ]));
1584
+ },
1585
+ };
1586
+ };
1587
+
1588
+ /** Character counter component that tracks text length against maxLength */
1589
+ const CharacterCounter = () => {
1590
+ return {
1591
+ view: ({ attrs }) => {
1592
+ const { currentLength, maxLength, show } = attrs;
1593
+ if (!show)
1594
+ return null;
1595
+ const isOverLimit = currentLength > maxLength;
1596
+ return m('span.character-counter', {
1597
+ style: {
1598
+ color: isOverLimit ? '#F44336' : '#9e9e9e',
1599
+ fontSize: '12px',
1600
+ display: 'block',
1601
+ textAlign: 'right',
1602
+ marginTop: '8px',
1603
+ },
1604
+ }, `${currentLength}/${maxLength}`);
1605
+ },
1606
+ };
1607
+ };
1608
+ /** Create a TextArea */
1609
+ const TextArea = () => {
1610
+ const state = {
1611
+ id: uniqueId(),
1612
+ currentLength: 0,
1613
+ hasInteracted: false,
1614
+ height: undefined,
1615
+ active: false,
1616
+ textarea: undefined,
1617
+ };
1618
+ const updateHeight = (textarea) => {
1619
+ textarea.style.height = 'auto';
1620
+ const newHeight = textarea.scrollHeight + 'px';
1621
+ state.height = textarea.value.length === 0 ? undefined : newHeight;
1622
+ };
1623
+ return {
1624
+ onremove: () => {
1625
+ },
1626
+ view: ({ attrs }) => {
1627
+ var _a;
1628
+ const { className = 'col s12', helperText, iconName, id = state.id, initialValue, placeholder, isMandatory, label, maxLength, oninput, onchange, onkeydown, onkeypress, onkeyup, onblur, style } = attrs, params = __rest(attrs, ["className", "helperText", "iconName", "id", "initialValue", "placeholder", "isMandatory", "label", "maxLength", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "onblur", "style"]);
1629
+ // const attributes = toAttrs(params);
1630
+ return m('.input-field', { className, style }, [
1631
+ iconName ? m('i.material-icons.prefix', iconName) : '',
1632
+ m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, style: {
1633
+ height: state.height,
1634
+ }, oncreate: ({ dom }) => {
1635
+ const textarea = (state.textarea = dom);
1636
+ // Set initial value and height if provided
1637
+ if (initialValue !== undefined) {
1638
+ textarea.value = String(initialValue);
1639
+ updateHeight(textarea);
1640
+ // } else {
1641
+ // updateHeight(textarea);
1642
+ }
1643
+ // Update character count state for counter component
1644
+ if (maxLength) {
1645
+ state.currentLength = textarea.value.length; // Initial count
1646
+ m.redraw();
1647
+ }
1648
+ }, onupdate: ({ dom }) => {
1649
+ const textarea = dom;
1650
+ if (state.height)
1651
+ textarea.style.height = state.height;
1652
+ }, onfocus: () => {
1653
+ state.active = true;
1654
+ }, oninput: (e) => {
1655
+ state.active = true;
1656
+ state.hasInteracted = false;
1657
+ const target = e.target;
1658
+ // Update height for auto-resize
1659
+ updateHeight(target);
1660
+ // Update character count
1661
+ if (maxLength) {
1662
+ state.currentLength = target.value.length;
1663
+ state.hasInteracted = target.value.length > 0;
1664
+ }
1665
+ // Call onchange handler
1666
+ if (oninput) {
1667
+ oninput(target.value);
1668
+ }
1669
+ }, onblur: (e) => {
1670
+ state.active = false;
1671
+ // const target = e.target as HTMLTextAreaElement;
1672
+ state.hasInteracted = true;
1673
+ // Call original onblur if provided
1674
+ if (onblur) {
1675
+ onblur(e);
1676
+ }
1677
+ if (onchange && state.textarea) {
1678
+ onchange(state.textarea.value);
1679
+ }
1680
+ }, value: initialValue, onkeyup: onkeyup
1681
+ ? (ev) => {
1682
+ onkeyup(ev, ev.target.value);
1683
+ }
1684
+ : undefined, onkeydown: onkeydown
1685
+ ? (ev) => {
1686
+ onkeydown(ev, ev.target.value);
1687
+ }
1688
+ : undefined, onkeypress: onkeypress
1689
+ ? (ev) => {
1690
+ onkeypress(ev, ev.target.value);
1691
+ }
1692
+ : undefined })),
1693
+ m(Label, {
1694
+ label,
1695
+ id,
1696
+ isMandatory,
1697
+ isActive: ((_a = state.textarea) === null || _a === void 0 ? void 0 : _a.value) || initialValue || placeholder || state.active,
1698
+ }),
1699
+ m(HelperText, {
1700
+ helperText,
1701
+ dataError: state.hasInteracted && attrs.dataError ? attrs.dataError : undefined,
1702
+ dataSuccess: state.hasInteracted && attrs.dataSuccess ? attrs.dataSuccess : undefined,
1703
+ }),
1704
+ maxLength
1705
+ ? m(CharacterCounter, {
1706
+ currentLength: state.currentLength,
1707
+ maxLength,
1708
+ show: state.currentLength > 0,
1709
+ })
1710
+ : undefined,
1711
+ ]);
1712
+ },
1713
+ };
1714
+ };
1715
+ /** Default component for all kinds of input fields. */
1716
+ const InputField = (type, defaultClass = '') => () => {
1717
+ const state = {
1718
+ id: uniqueId(),
1719
+ currentLength: 0,
1720
+ hasInteracted: false,
1721
+ isValid: true,
1722
+ active: false,
1723
+ inputElement: null,
1724
+ };
1725
+ // let labelManager: { updateLabelState: () => void; cleanup: () => void } | null = null;
1726
+ // let lengthUpdateHandler: (() => void) | null = null;
1727
+ // let inputElement: HTMLInputElement | null = null;
1728
+ const getValue = (target) => {
1729
+ const val = target.value;
1730
+ return (val ? (type === 'number' || type === 'range' ? +val : val) : val);
1731
+ };
1732
+ const setValidity = (target, validationResult) => {
1733
+ if (typeof validationResult === 'boolean') {
1734
+ target.setCustomValidity(validationResult ? '' : 'Custom validation failed');
1735
+ }
1736
+ else {
1737
+ target.setCustomValidity(validationResult);
1738
+ }
1739
+ };
1740
+ const focus = ({ autofocus }) => autofocus ? (typeof autofocus === 'boolean' ? autofocus : autofocus()) : false;
1741
+ const lengthUpdateHandler = () => {
1742
+ var _a;
1743
+ const length = (_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value.length;
1744
+ if (length) {
1745
+ state.currentLength = length;
1746
+ state.hasInteracted = length > 0;
1747
+ }
1748
+ };
1749
+ return {
1750
+ view: ({ attrs }) => {
1751
+ var _a;
1752
+ const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id, initialValue, placeholder, isMandatory, label, maxLength, newRow, oninput, onchange, onkeydown, onkeypress, onkeyup, style, validate } = attrs, params = __rest(attrs, ["className", "dataError", "dataSuccess", "helperText", "iconName", "id", "initialValue", "placeholder", "isMandatory", "label", "maxLength", "newRow", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "style", "validate"]);
1753
+ // const attributes = toAttrs(params);
1754
+ const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim();
1755
+ return m('.input-field', { className: cn, style }, [
1756
+ iconName ? m('i.material-icons.prefix', iconName) : undefined,
1757
+ m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
1758
+ // attributes,
1759
+ oncreate: ({ dom }) => {
1760
+ const input = (state.inputElement = dom);
1761
+ if (focus(attrs)) {
1762
+ input.focus();
1763
+ }
1764
+ // Set initial value if provided
1765
+ if (initialValue !== undefined) {
1766
+ input.value = String(initialValue);
1767
+ }
1768
+ // Update character count state for counter component
1769
+ if (maxLength) {
1770
+ state.currentLength = input.value.length; // Initial count
1771
+ }
1772
+ // Range input functionality
1773
+ if (type === 'range') {
1774
+ const updateThumb = () => {
1775
+ const value = input.value;
1776
+ const min = input.min || '0';
1777
+ const max = input.max || '100';
1778
+ const percentage = ((parseFloat(value) - parseFloat(min)) / (parseFloat(max) - parseFloat(min))) * 100;
1779
+ input.style.setProperty('--range-progress', `${percentage}%`);
1780
+ };
1781
+ input.addEventListener('input', updateThumb);
1782
+ updateThumb(); // Initial position
1783
+ }
1784
+ }, onkeyup: onkeyup
1785
+ ? (ev) => {
1786
+ onkeyup(ev, getValue(ev.target));
1787
+ }
1788
+ : undefined, onkeydown: onkeydown
1789
+ ? (ev) => {
1790
+ onkeydown(ev, getValue(ev.target));
1791
+ }
1792
+ : undefined, onkeypress: onkeypress
1793
+ ? (ev) => {
1794
+ onkeypress(ev, getValue(ev.target));
1795
+ }
1796
+ : undefined, onupdate: validate
1797
+ ? ({ dom }) => {
1798
+ const target = dom;
1799
+ setValidity(target, validate(getValue(target), target));
1800
+ }
1801
+ : undefined, oninput: (e) => {
1802
+ state.active = true;
1803
+ const target = e.target;
1804
+ // Handle original oninput logic
1805
+ const value = getValue(target);
1806
+ if (oninput) {
1807
+ oninput(value);
1808
+ }
1809
+ if (maxLength) {
1810
+ lengthUpdateHandler();
1811
+ }
1812
+ // Don't validate on input, only clear error states if user is typing
1813
+ if (validate && target.classList.contains('invalid') && target.value.length > 0) {
1814
+ const validationResult = validate(value, target);
1815
+ if (typeof validationResult === 'boolean' && validationResult) {
1816
+ target.classList.remove('invalid');
1817
+ target.classList.add('valid');
1818
+ state.isValid = true;
1819
+ }
1820
+ else if (typeof validationResult === 'string' && validationResult === '') {
1821
+ target.classList.remove('invalid');
1822
+ target.classList.add('valid');
1823
+ state.isValid = true;
1824
+ }
1825
+ }
1826
+ }, onfocus: () => {
1827
+ state.active = true;
1828
+ }, onblur: (e) => {
1829
+ state.active = false;
1830
+ const target = e.target;
1831
+ state.hasInteracted = true;
1832
+ if (target && validate) {
1833
+ const value = getValue(target);
1834
+ // Only validate if user has entered some text
1835
+ if (value && String(value).length > 0) {
1836
+ const validationResult = validate(value, target);
1837
+ state.isValid = typeof validationResult === 'boolean' ? validationResult : false;
1838
+ setValidity(target, validationResult);
1839
+ // Update visual validation state
1840
+ if (typeof validationResult === 'boolean') {
1841
+ if (validationResult) {
1842
+ target.classList.remove('invalid');
1843
+ target.classList.add('valid');
1844
+ }
1845
+ else {
1846
+ target.classList.remove('valid');
1847
+ target.classList.add('invalid');
1848
+ }
1849
+ }
1850
+ else if (typeof validationResult === 'string') {
1851
+ target.classList.remove('valid');
1852
+ target.classList.add('invalid');
1853
+ state.isValid = false;
1854
+ }
1855
+ }
1856
+ else {
1857
+ // Clear validation state if no text
1858
+ target.classList.remove('valid', 'invalid');
1859
+ state.isValid = true;
1860
+ }
1861
+ }
1862
+ // Also call the original onblur handler if provided
1863
+ if (attrs.onblur) {
1864
+ attrs.onblur(e);
1865
+ }
1866
+ if (onchange && state.inputElement) {
1867
+ onchange(getValue(state.inputElement));
1868
+ }
1869
+ }, value: initialValue })),
1870
+ m(Label, {
1871
+ label,
1872
+ id,
1873
+ isMandatory,
1874
+ isActive: state.active ||
1875
+ ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) ||
1876
+ initialValue ||
1877
+ placeholder ||
1878
+ type === 'number' ||
1879
+ type === 'color' ||
1880
+ type === 'range'
1881
+ ? true
1882
+ : false,
1883
+ }),
1884
+ m(HelperText, {
1885
+ helperText,
1886
+ dataError: state.hasInteracted && !state.isValid ? dataError : undefined,
1887
+ dataSuccess: state.hasInteracted && state.isValid ? dataSuccess : undefined,
1888
+ }),
1889
+ maxLength
1890
+ ? m(CharacterCounter, {
1891
+ currentLength: state.currentLength,
1892
+ maxLength,
1893
+ show: state.currentLength > 0,
1894
+ })
1895
+ : undefined,
1896
+ ]);
1897
+ },
1898
+ };
1899
+ };
1900
+ /** Component for entering some text */
1901
+ const TextInput = InputField('text');
1902
+ /** Component for entering a password */
1903
+ const PasswordInput = InputField('password');
1904
+ /** Component for entering a number */
1905
+ const NumberInput = InputField('number');
1906
+ /** Component for entering a URL */
1907
+ const UrlInput = InputField('url');
1908
+ /** Component for entering a color */
1909
+ const ColorInput = InputField('color');
1910
+ /** Component for entering a range */
1911
+ const RangeInput = InputField('range', '.range-field');
1912
+ /** Component for entering an email */
1913
+ const EmailInput = InputField('email');
1914
+ /** Component for uploading a file */
1915
+ const FileInput = () => {
1916
+ let canClear = false;
1917
+ let i;
1918
+ return {
1919
+ view: ({ attrs }) => {
1920
+ const { multiple, disabled, initialValue, placeholder, onchange, className = 'col s12', accept: acceptedFiles, label = 'File', } = attrs;
1921
+ const accept = acceptedFiles
1922
+ ? acceptedFiles instanceof Array
1923
+ ? acceptedFiles.join(', ')
1924
+ : acceptedFiles
1925
+ : undefined;
1926
+ return m('.file-field.input-field', {
1927
+ className: attrs.class || className,
1928
+ }, [
1929
+ m('.btn', [
1930
+ m('span', label),
1931
+ m('input[type=file]', {
1932
+ title: label,
1933
+ accept,
1934
+ multiple,
1935
+ disabled,
1936
+ onchange: onchange
1937
+ ? (e) => {
1938
+ const i = e.target;
1939
+ if (i && i.files && onchange) {
1940
+ canClear = true;
1941
+ onchange(i.files);
1942
+ }
1943
+ }
1944
+ : undefined,
1945
+ }),
1946
+ ]),
1947
+ m('.file-path-wrapper', m('input.file-path.validate[type=text]', {
1948
+ placeholder,
1949
+ oncreate: ({ dom }) => {
1950
+ i = dom;
1951
+ if (initialValue)
1952
+ i.value = initialValue;
1953
+ },
1954
+ })),
1955
+ (canClear || initialValue) &&
1956
+ m('a.waves-effect.waves-teal.btn-flat', {
1957
+ style: {
1958
+ float: 'right',
1959
+ position: 'relative',
1960
+ top: '-3rem',
1961
+ padding: 0,
1962
+ },
1963
+ onclick: () => {
1964
+ canClear = false;
1965
+ i.value = '';
1966
+ onchange && onchange({});
1967
+ },
1968
+ }, m(MaterialIcon, {
1969
+ name: 'close',
1970
+ className: 'close',
1971
+ })),
1972
+ ]);
1973
+ },
1974
+ };
1975
+ };
1976
+
1977
+ /**
1978
+ * Pure TypeScript MaterialBox - creates an image lightbox that fills the screen when clicked
1979
+ * No MaterializeCSS dependencies
1980
+ */
1981
+ const MaterialBox = () => {
1982
+ const state = {
1983
+ isOpen: false,
1984
+ originalImage: null,
1985
+ overlay: null,
1986
+ overlayImage: null,
1987
+ };
1988
+ const openBox = (img, attrs) => {
1989
+ if (state.isOpen)
1990
+ return;
1991
+ state.isOpen = true;
1992
+ state.originalImage = img;
1993
+ if (attrs.onOpenStart)
1994
+ attrs.onOpenStart();
1995
+ // Create overlay
1996
+ const overlay = document.createElement('div');
1997
+ overlay.className = 'materialbox-overlay';
1998
+ overlay.style.cssText = `
1999
+ position: fixed;
2000
+ top: 0;
2001
+ left: 0;
2002
+ right: 0;
2003
+ bottom: 0;
2004
+ background-color: rgba(0, 0, 0, 0.85);
2005
+ z-index: 1000;
2006
+ opacity: 0;
2007
+ transition: opacity ${attrs.inDuration || 275}ms ease;
2008
+ cursor: zoom-out;
2009
+ `;
2010
+ // Create enlarged image
2011
+ const enlargedImg = document.createElement('img');
2012
+ enlargedImg.src = img.src;
2013
+ enlargedImg.alt = img.alt || '';
2014
+ enlargedImg.className = 'materialbox-image';
2015
+ // Get original image dimensions and position
2016
+ const imgRect = img.getBoundingClientRect();
2017
+ const windowWidth = window.innerWidth;
2018
+ const windowHeight = window.innerHeight;
2019
+ // Calculate final size maintaining aspect ratio
2020
+ const aspectRatio = img.naturalWidth / img.naturalHeight;
2021
+ const maxWidth = windowWidth * 0.9;
2022
+ const maxHeight = windowHeight * 0.9;
2023
+ let finalWidth = maxWidth;
2024
+ let finalHeight = maxWidth / aspectRatio;
2025
+ if (finalHeight > maxHeight) {
2026
+ finalHeight = maxHeight;
2027
+ finalWidth = maxHeight * aspectRatio;
2028
+ }
2029
+ // Set initial position and size (same as original image)
2030
+ enlargedImg.style.cssText = `
2031
+ position: fixed;
2032
+ top: ${imgRect.top}px;
2033
+ left: ${imgRect.left}px;
2034
+ width: ${imgRect.width}px;
2035
+ height: ${imgRect.height}px;
2036
+ transition: all ${attrs.inDuration || 275}ms ease;
2037
+ cursor: zoom-out;
2038
+ max-width: none;
2039
+ z-index: 1001;
2040
+ `;
2041
+ // Add caption if provided
2042
+ let caption = null;
2043
+ if (attrs.caption) {
2044
+ caption = document.createElement('div');
2045
+ caption.className = 'materialbox-caption';
2046
+ caption.textContent = attrs.caption;
2047
+ caption.style.cssText = `
2048
+ position: fixed;
2049
+ bottom: 20px;
2050
+ left: 50%;
2051
+ transform: translateX(-50%);
2052
+ color: white;
2053
+ font-size: 16px;
2054
+ text-align: center;
2055
+ opacity: 0;
2056
+ transition: opacity ${attrs.inDuration || 275}ms ease ${attrs.inDuration || 275}ms;
2057
+ z-index: 1002;
2058
+ pointer-events: none;
2059
+ `;
2060
+ }
2061
+ // Add to DOM
2062
+ document.body.appendChild(overlay);
2063
+ document.body.appendChild(enlargedImg);
2064
+ if (caption)
2065
+ document.body.appendChild(caption);
2066
+ // Store references
2067
+ state.overlay = overlay;
2068
+ state.overlayImage = enlargedImg;
2069
+ // Prevent body scrolling
2070
+ document.body.style.overflow = 'hidden';
2071
+ // Trigger animations
2072
+ requestAnimationFrame(() => {
2073
+ overlay.style.opacity = '1';
2074
+ enlargedImg.style.top = `${(windowHeight - finalHeight) / 2}px`;
2075
+ enlargedImg.style.left = `${(windowWidth - finalWidth) / 2}px`;
2076
+ enlargedImg.style.width = `${finalWidth}px`;
2077
+ enlargedImg.style.height = `${finalHeight}px`;
2078
+ if (caption) {
2079
+ caption.style.opacity = '1';
2080
+ }
2081
+ });
2082
+ // Add close handlers
2083
+ const closeHandler = () => closeBox(attrs);
2084
+ overlay.addEventListener('click', closeHandler);
2085
+ enlargedImg.addEventListener('click', closeHandler);
2086
+ document.addEventListener('keydown', (e) => {
2087
+ if (e.key === 'Escape')
2088
+ closeHandler();
2089
+ });
2090
+ // Call onOpenEnd after animation
2091
+ setTimeout(() => {
2092
+ if (attrs.onOpenEnd)
2093
+ attrs.onOpenEnd();
2094
+ }, attrs.inDuration || 275);
2095
+ };
2096
+ const closeBox = (attrs) => {
2097
+ if (!state.isOpen || !state.originalImage || !state.overlay || !state.overlayImage)
2098
+ return;
2099
+ if (attrs.onCloseStart)
2100
+ attrs.onCloseStart();
2101
+ const originalRect = state.originalImage.getBoundingClientRect();
2102
+ // Animate back to original position
2103
+ state.overlay.style.opacity = '0';
2104
+ state.overlayImage.style.top = `${originalRect.top}px`;
2105
+ state.overlayImage.style.left = `${originalRect.left}px`;
2106
+ state.overlayImage.style.width = `${originalRect.width}px`;
2107
+ state.overlayImage.style.height = `${originalRect.height}px`;
2108
+ // Hide caption
2109
+ const caption = document.querySelector('.materialbox-caption');
2110
+ if (caption) {
2111
+ caption.style.opacity = '0';
2112
+ }
2113
+ // Clean up after animation
2114
+ setTimeout(() => {
2115
+ if (state.overlay) {
2116
+ document.body.removeChild(state.overlay);
2117
+ state.overlay = null;
2118
+ }
2119
+ if (state.overlayImage) {
2120
+ document.body.removeChild(state.overlayImage);
2121
+ state.overlayImage = null;
2122
+ }
2123
+ if (caption) {
2124
+ document.body.removeChild(caption);
2125
+ }
2126
+ // Restore body scroll
2127
+ document.body.style.overflow = '';
2128
+ state.isOpen = false;
2129
+ state.originalImage = null;
2130
+ if (attrs.onCloseEnd)
2131
+ attrs.onCloseEnd();
2132
+ }, attrs.outDuration || 200);
2133
+ };
2134
+ return {
2135
+ onremove: () => {
2136
+ // Clean up if component is removed while open
2137
+ if (state.isOpen) {
2138
+ if (state.overlay)
2139
+ document.body.removeChild(state.overlay);
2140
+ if (state.overlayImage)
2141
+ document.body.removeChild(state.overlayImage);
2142
+ const caption = document.querySelector('.materialbox-caption');
2143
+ if (caption)
2144
+ document.body.removeChild(caption);
2145
+ document.body.style.overflow = '';
2146
+ }
2147
+ },
2148
+ view: ({ attrs }) => {
2149
+ const { src, alt, width, height, caption, className, style } = attrs, otherAttrs = __rest(attrs, ["src", "alt", "width", "height", "caption", "className", "style"]);
2150
+ return m('img.materialboxed', Object.assign(Object.assign({}, otherAttrs), { src, alt: alt || '', width,
2151
+ height, className: ['materialboxed', className].filter(Boolean).join(' '), style: Object.assign({ cursor: 'zoom-in', transition: 'opacity 200ms ease' }, style), onclick: (e) => {
2152
+ e.preventDefault();
2153
+ openBox(e.target, attrs);
2154
+ } }));
2155
+ },
2156
+ };
2157
+ };
2158
+
2159
+ /**
2160
+ * CSS-only Modal Panel component - no JavaScript dependencies
2161
+ * Uses modern CSS techniques for modal functionality
2162
+ */
2163
+ const ModalPanel = () => {
2164
+ const state = {
2165
+ isOpen: false,
2166
+ id: '',
2167
+ };
2168
+ let keydownHandler = null;
2169
+ const closeModal = (attrs) => {
2170
+ state.isOpen = false;
2171
+ if (attrs.onToggle)
2172
+ attrs.onToggle(false);
2173
+ if (attrs.onClose)
2174
+ attrs.onClose();
2175
+ // Remove keyboard listener
2176
+ if (keydownHandler) {
2177
+ document.removeEventListener('keydown', keydownHandler);
2178
+ keydownHandler = null;
2179
+ }
2180
+ // Restore body scroll
2181
+ document.body.style.overflow = '';
2182
+ };
2183
+ const openModal = (attrs) => {
2184
+ state.isOpen = true;
2185
+ if (attrs.onToggle)
2186
+ attrs.onToggle(true);
2187
+ // Add keyboard listener
2188
+ keydownHandler = (e) => {
2189
+ if (e.key === 'Escape' && attrs.closeOnEsc !== false && state.isOpen) {
2190
+ closeModal(attrs);
2191
+ }
2192
+ };
2193
+ document.addEventListener('keydown', keydownHandler);
2194
+ // Prevent body scroll
2195
+ document.body.style.overflow = 'hidden';
2196
+ };
2197
+ return {
2198
+ oninit: ({ attrs }) => {
2199
+ state.id = attrs.id;
2200
+ if (attrs.isOpen) {
2201
+ openModal(attrs);
2202
+ }
2203
+ },
2204
+ onremove: () => {
2205
+ // Cleanup on component removal
2206
+ if (keydownHandler) {
2207
+ document.removeEventListener('keydown', keydownHandler);
2208
+ keydownHandler = null;
2209
+ }
2210
+ document.body.style.overflow = '';
2211
+ },
2212
+ view: ({ attrs }) => {
2213
+ // Sync external isOpen prop with internal state - do this in view for immediate response
2214
+ if (attrs.isOpen !== undefined && attrs.isOpen !== state.isOpen) {
2215
+ if (attrs.isOpen) {
2216
+ openModal(attrs);
2217
+ }
2218
+ else {
2219
+ closeModal(attrs);
2220
+ }
2221
+ }
2222
+ const { id, title, description, fixedFooter, bottomSheet, buttons, richContent, className, showCloseButton = true, closeOnBackdropClick = true, } = attrs;
2223
+ const modalClasses = [
2224
+ 'modal',
2225
+ className || '',
2226
+ fixedFooter ? 'modal-fixed-footer' : '',
2227
+ bottomSheet ? 'bottom-sheet' : '',
2228
+ state.isOpen ? 'active' : '',
2229
+ ]
2230
+ .filter(Boolean)
2231
+ .join(' ')
2232
+ .trim();
2233
+ const overlayClasses = ['modal-overlay', state.isOpen ? 'active' : ''].filter(Boolean).join(' ').trim();
2234
+ return m('div', { className: 'modal-container' }, [
2235
+ // Modal overlay
2236
+ m('div', {
2237
+ className: overlayClasses,
2238
+ onclick: closeOnBackdropClick ? () => closeModal(attrs) : undefined,
2239
+ style: {
2240
+ display: state.isOpen ? 'block' : 'none',
2241
+ position: 'fixed',
2242
+ top: '0',
2243
+ left: '0',
2244
+ width: '100%',
2245
+ height: '100%',
2246
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
2247
+ zIndex: '1002',
2248
+ },
2249
+ }),
2250
+ // Modal content
2251
+ m('div', {
2252
+ id,
2253
+ className: modalClasses,
2254
+ 'aria-hidden': state.isOpen ? 'false' : 'true',
2255
+ role: 'dialog',
2256
+ 'aria-labelledby': `${id}-title`,
2257
+ 'aria-describedby': description ? `${id}-desc` : undefined,
2258
+ style: {
2259
+ display: state.isOpen ? 'block' : 'none',
2260
+ position: 'fixed',
2261
+ top: '50%',
2262
+ left: '50%',
2263
+ transform: 'translate(-50%, -50%)',
2264
+ backgroundColor: '#fff',
2265
+ borderRadius: '4px',
2266
+ maxWidth: '75%',
2267
+ maxHeight: '85%',
2268
+ overflow: 'auto',
2269
+ zIndex: '1003',
2270
+ padding: '0',
2271
+ boxShadow: '0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12), 0 11px 15px -7px rgba(0,0,0,0.20)',
2272
+ },
2273
+ onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
2274
+ }, [
2275
+ // Close button
2276
+ showCloseButton &&
2277
+ m('button', {
2278
+ className: 'modal-close btn-flat',
2279
+ style: {
2280
+ position: 'absolute',
2281
+ top: '8px',
2282
+ right: '8px',
2283
+ padding: '8px',
2284
+ minWidth: 'auto',
2285
+ lineHeight: 1,
2286
+ },
2287
+ onclick: () => closeModal(attrs),
2288
+ 'aria-label': 'Close modal',
2289
+ }, '×'),
2290
+ // Modal content
2291
+ m('.modal-content', {
2292
+ style: { padding: '24px', paddingTop: showCloseButton ? '48px' : '24px' },
2293
+ }, [
2294
+ m('h4', { id: `${id}-title`, style: { margin: '0 0 20px 0' } }, title),
2295
+ description &&
2296
+ m('div', Object.assign({ id: `${id}-desc` }, (richContent && typeof description === 'string' ? { innerHTML: description } : {})), richContent && typeof description === 'string' ? undefined : description),
2297
+ ]),
2298
+ // Modal footer with buttons
2299
+ buttons &&
2300
+ buttons.length > 0 &&
2301
+ m('.modal-footer', {
2302
+ style: {
2303
+ padding: '4px 6px',
2304
+ borderTop: '1px solid rgba(160,160,160,0.2)',
2305
+ textAlign: 'right',
2306
+ },
2307
+ }, buttons.map((buttonProps) => m(FlatButton, Object.assign(Object.assign({}, buttonProps), { className: `modal-close ${buttonProps.className || ''}`, onclick: (e) => {
2308
+ if (buttonProps.onclick)
2309
+ buttonProps.onclick(e);
2310
+ closeModal(attrs);
2311
+ } })))),
2312
+ ]),
2313
+ ]);
2314
+ },
2315
+ };
2316
+ };
2317
+
2318
+ /** Component to show a check box */
2319
+ const InputCheckbox = () => {
2320
+ return {
2321
+ view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
2322
+ const checkboxId = inputId || uniqueId();
2323
+ return m(`p`, { className, style }, m('label', { for: checkboxId }, [
2324
+ m('input[type=checkbox][tabindex=0]', {
2325
+ id: checkboxId,
2326
+ checked,
2327
+ disabled,
2328
+ onclick: onchange
2329
+ ? (e) => {
2330
+ if (e.target && typeof e.target.checked !== 'undefined') {
2331
+ onchange(e.target.checked);
2332
+ }
2333
+ }
2334
+ : undefined,
2335
+ }),
2336
+ label ? (typeof label === 'string' ? m('span', label) : label) : undefined,
2337
+ ]), description && m(HelperText, { className: 'input-checkbox-desc', helperText: description }));
2338
+ },
2339
+ };
2340
+ };
2341
+ /** A list of checkboxes */
2342
+ const Options = () => {
2343
+ const state = {};
2344
+ const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
2345
+ const selectAll = (options, callback) => {
2346
+ const allIds = options.map((option) => option.id);
2347
+ state.checkedIds = [...allIds];
2348
+ if (callback)
2349
+ callback(allIds);
2350
+ };
2351
+ const selectNone = (callback) => {
2352
+ state.checkedIds = [];
2353
+ if (callback)
2354
+ callback([]);
2355
+ };
2356
+ return {
2357
+ oninit: ({ attrs: { initialValue, checkedId, id } }) => {
2358
+ const iv = checkedId || initialValue;
2359
+ state.checkedId = checkedId;
2360
+ state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
2361
+ state.componentId = id || uniqueId();
2362
+ },
2363
+ view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
2364
+ const onchange = callback
2365
+ ? (propId, checked) => {
2366
+ const checkedIds = state.checkedIds.filter((i) => i !== propId);
2367
+ if (checked) {
2368
+ checkedIds.push(propId);
2369
+ }
2370
+ state.checkedIds = checkedIds;
2371
+ callback(checkedIds);
2372
+ }
2373
+ : undefined;
2374
+ const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
2375
+ const optionsContent = layout === 'horizontal'
2376
+ ? m('div.grid-container', options.map((option) => m(InputCheckbox, {
2377
+ disabled: disabled || option.disabled,
2378
+ label: option.label,
2379
+ onchange: onchange ? (v) => onchange(option.id, v) : undefined,
2380
+ className: option.className || checkboxClass,
2381
+ checked: isChecked(option.id),
2382
+ description: option.description,
2383
+ inputId: `${state.componentId}-${option.id}`,
2384
+ })))
2385
+ : options.map((option) => m(InputCheckbox, {
2386
+ disabled: disabled || option.disabled,
2387
+ label: option.label,
2388
+ onchange: onchange ? (v) => onchange(option.id, v) : undefined,
2389
+ className: option.className || checkboxClass,
2390
+ checked: isChecked(option.id),
2391
+ description: option.description,
2392
+ inputId: `${state.componentId}-${option.id}`,
2393
+ }));
2394
+ return m('div', { id: state.componentId, className: cn, style }, [
2395
+ label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
2396
+ showSelectAll &&
2397
+ m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
2398
+ m('a', {
2399
+ href: '#',
2400
+ onclick: (e) => {
2401
+ e.preventDefault();
2402
+ selectAll(options, callback);
2403
+ },
2404
+ style: 'margin-right: 15px;',
2405
+ }, 'Select All'),
2406
+ m('a', {
2407
+ href: '#',
2408
+ onclick: (e) => {
2409
+ e.preventDefault();
2410
+ selectNone(callback);
2411
+ },
2412
+ }, 'Select None'),
2413
+ ]),
2414
+ description && m(HelperText, { helperText: description }),
2415
+ m('form', { action: '#' }, optionsContent),
2416
+ ]);
2417
+ },
2418
+ };
2419
+ };
2420
+
2421
+ const PaginationItem = () => ({
2422
+ view: ({ attrs: { title, href, active, disabled } }) => m('li', { className: active ? 'active' : disabled ? 'disabled' : 'waves-effect' }, typeof title === 'number' ? m(m.route.Link, { href }, title) : title),
2423
+ });
2424
+ const Pagination = () => {
2425
+ const state = {
2426
+ pagIndex: 0,
2427
+ };
2428
+ return {
2429
+ view: ({ attrs: { items, curPage = 1, size = Math.min(9, items.length) } }) => {
2430
+ const { pagIndex } = state;
2431
+ const startPage = pagIndex * size;
2432
+ const endPage = startPage + size;
2433
+ const canGoBack = pagIndex > 0;
2434
+ const canGoForward = endPage < items.length;
2435
+ const displayedItems = [
2436
+ {
2437
+ title: m('a', {
2438
+ onclick: () => canGoBack && state.pagIndex--,
2439
+ }, m('i.material-icons', 'chevron_left')),
2440
+ disabled: !canGoBack,
2441
+ },
2442
+ ...items.filter((_, i) => startPage <= i && i < endPage),
2443
+ {
2444
+ title: m('a', {
2445
+ onclick: () => canGoForward && state.pagIndex++,
2446
+ }, m('i.material-icons', 'chevron_right')),
2447
+ disabled: !canGoForward,
2448
+ },
2449
+ ];
2450
+ return m('ul.pagination', displayedItems.map((item, i) => m(PaginationItem, Object.assign(Object.assign({ title: startPage + i }, item), { active: startPage + i === curPage }))));
2451
+ },
2452
+ };
2453
+ };
2454
+
2455
+ /**
2456
+ * MaterializeCSS Parallax component with dynamic positioning
2457
+ * Port of the original MaterializeCSS parallax logic
2458
+ */
2459
+ const Parallax = () => {
2460
+ let containerEl = null;
2461
+ let imgEl = null;
2462
+ let scrollThrottle = null;
2463
+ let lastScrollTop = -1;
2464
+ // MaterializeCSS parallax logic - exact port from original source
2465
+ const updateParallax = () => {
2466
+ var _a;
2467
+ if (!containerEl || !imgEl)
2468
+ return;
2469
+ const containerHeight = containerEl.offsetHeight > 0 ? containerEl.offsetHeight : ((_a = containerEl.parentElement) === null || _a === void 0 ? void 0 : _a.offsetHeight) || 1;
2470
+ const imgHeight = imgEl.offsetHeight;
2471
+ const parallaxDist = imgHeight - containerHeight;
2472
+ const bottom = containerEl.offsetTop + containerHeight;
2473
+ const top = containerEl.offsetTop;
2474
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
2475
+ const windowHeight = window.innerHeight;
2476
+ const windowBottom = scrollTop + windowHeight;
2477
+ const percentScrolled = (windowBottom - top) / (containerHeight + windowHeight);
2478
+ // MaterializeCSS formula: start at negative parallaxDist/2, move toward positive parallaxDist/2
2479
+ const parallax = Math.round(parallaxDist * percentScrolled - parallaxDist / 2);
2480
+ // Only update if we're in the viewport and scroll position changed
2481
+ if (bottom > scrollTop && top < windowBottom && scrollTop !== lastScrollTop) {
2482
+ // Match MaterializeCSS transform format: translate3d(-50%, Ypx, 0px) with opacity
2483
+ imgEl.style.transform = `translate3d(-50%, ${parallax}px, 0px)`;
2484
+ imgEl.style.opacity = '1';
2485
+ lastScrollTop = scrollTop;
2486
+ }
2487
+ };
2488
+ const handleScroll = () => {
2489
+ if (scrollThrottle)
2490
+ return;
2491
+ scrollThrottle = requestAnimationFrame(() => {
2492
+ updateParallax();
2493
+ scrollThrottle = null;
2494
+ });
2495
+ };
2496
+ const handleResize = () => {
2497
+ updateParallax();
2498
+ };
2499
+ const setupParallax = (containerElement, responsiveThreshold) => {
2500
+ containerEl = containerElement;
2501
+ imgEl = containerElement.querySelector('.parallax img');
2502
+ if (!imgEl)
2503
+ return;
2504
+ // Check if we should enable parallax based on screen size
2505
+ const shouldEnableParallax = window.innerWidth >= responsiveThreshold;
2506
+ if (shouldEnableParallax) {
2507
+ // Set initial MaterializeCSS styles on the image
2508
+ imgEl.style.transform = 'translate3d(-50%, 0px, 0px)';
2509
+ imgEl.style.opacity = '1';
2510
+ // Wait for image to load before calculating parallax
2511
+ if (imgEl.complete) {
2512
+ updateParallax();
2513
+ }
2514
+ else {
2515
+ imgEl.onload = () => updateParallax();
2516
+ }
2517
+ // Add event listeners
2518
+ window.addEventListener('scroll', handleScroll, { passive: true });
2519
+ window.addEventListener('resize', handleResize, { passive: true });
2520
+ // Store cleanup function
2521
+ containerEl._parallaxCleanup = () => {
2522
+ window.removeEventListener('scroll', handleScroll);
2523
+ window.removeEventListener('resize', handleResize);
2524
+ if (scrollThrottle) {
2525
+ cancelAnimationFrame(scrollThrottle);
2526
+ scrollThrottle = null;
2527
+ }
2528
+ };
2529
+ }
2530
+ };
2531
+ const cleanup = () => {
2532
+ if (containerEl && containerEl._parallaxCleanup) {
2533
+ containerEl._parallaxCleanup();
2534
+ }
2535
+ containerEl = null;
2536
+ imgEl = null;
2537
+ if (scrollThrottle) {
2538
+ cancelAnimationFrame(scrollThrottle);
2539
+ scrollThrottle = null;
2540
+ }
2541
+ lastScrollTop = -1;
2542
+ };
2543
+ return {
2544
+ oncreate: ({ dom, attrs }) => {
2545
+ const { responsiveThreshold = 768 } = attrs;
2546
+ setupParallax(dom, responsiveThreshold);
2547
+ },
2548
+ onremove: () => {
2549
+ cleanup();
2550
+ },
2551
+ view: ({ attrs }) => {
2552
+ const { src, alt = '' } = attrs;
2553
+ if (!src)
2554
+ return undefined;
2555
+ return m('.parallax-container', [
2556
+ m('.parallax', [
2557
+ m('img', {
2558
+ src,
2559
+ alt,
2560
+ onerror: (e) => {
2561
+ console.warn('Parallax image failed to load:', src);
2562
+ const img = e.target;
2563
+ img.style.backgroundColor = '#ddd';
2564
+ img.alt = 'Image failed to load';
2565
+ },
2566
+ }),
2567
+ ]),
2568
+ ]);
2569
+ },
2570
+ };
2571
+ };
2572
+
2573
+ const defaultI18n = {
2574
+ cancel: 'Cancel',
2575
+ clear: 'Clear',
2576
+ done: 'Ok',
2577
+ previousMonth: '\u2039',
2578
+ nextMonth: '\u203a',
2579
+ months: [
2580
+ 'January',
2581
+ 'February',
2582
+ 'March',
2583
+ 'April',
2584
+ 'May',
2585
+ 'June',
2586
+ 'July',
2587
+ 'August',
2588
+ 'September',
2589
+ 'October',
2590
+ 'November',
2591
+ 'December',
2592
+ ],
2593
+ monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
2594
+ weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
2595
+ weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
2596
+ weekdaysAbbrev: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
2597
+ };
2598
+ // Utility functions based on Materialize CSS implementation
2599
+ const isDate = (obj) => {
2600
+ return /Date/.test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
2601
+ };
2602
+ const isWeekend = (date) => {
2603
+ const day = date.getDay();
2604
+ return day === 0 || day === 6;
2605
+ };
2606
+ const setToStartOfDay = (date) => {
2607
+ if (isDate(date))
2608
+ date.setHours(0, 0, 0, 0);
2609
+ };
2610
+ const getDaysInMonth = (year, month) => {
2611
+ return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
2612
+ };
2613
+ const isLeapYear = (year) => {
2614
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
2615
+ };
2616
+ const compareDates = (a, b) => {
2617
+ return a.getTime() === b.getTime();
2618
+ };
2619
+ /**
2620
+ * Enhanced DatePicker component based on Materialize CSS datepicker
2621
+ */
2622
+ const DatePicker = () => {
2623
+ let state;
2624
+ const mergeOptions = (attrs) => {
2625
+ // Handle HTML attributes
2626
+ let yearRange = 10;
2627
+ if (attrs.yearrange) {
2628
+ const parts = attrs.yearrange.split(',');
2629
+ if (parts.length === 2) {
2630
+ yearRange = [parseInt(parts[0], 10), parseInt(parts[1], 10)];
2631
+ }
2632
+ }
2633
+ else if (attrs.yearRange) {
2634
+ yearRange = attrs.yearRange;
2635
+ }
2636
+ // Handle format - priority: format attribute > displayFormat > default
2637
+ let finalFormat = 'mmm dd, yyyy';
2638
+ if (attrs.format) {
2639
+ finalFormat = attrs.format;
2640
+ }
2641
+ else if (attrs.displayFormat) {
2642
+ finalFormat = attrs.displayFormat;
2643
+ }
2644
+ const merged = Object.assign({ autoClose: false, format: finalFormat, parse: null, defaultDate: null, setDefaultDate: false, disableWeekends: false, disableDayFn: null, firstDay: 0, minDate: null, maxDate: null, yearRange, showClearBtn: false, i18n: defaultI18n, onSelect: null, onOpen: null, onClose: null }, attrs);
2645
+ // Merge i18n properly
2646
+ merged.i18n = Object.assign(Object.assign({}, defaultI18n), attrs.i18n);
2647
+ return merged;
2648
+ };
2649
+ const toString = (date, format) => {
2650
+ if (!date || !isDate(date)) {
2651
+ return '';
2652
+ }
2653
+ // Split format into tokens - match longer patterns first
2654
+ const formatTokens = /(dddd|ddd|dd|d|mmmm|mmm|mm|m|yyyy|yy)/g;
2655
+ let result = format;
2656
+ // Replace all format tokens with actual values
2657
+ result = result.replace(formatTokens, (match) => {
2658
+ if (state.formats[match]) {
2659
+ return String(state.formats[match]());
2660
+ }
2661
+ return match;
2662
+ });
2663
+ return result;
2664
+ };
2665
+ const setDate = (date, preventOnSelect = false, options) => {
2666
+ if (!date) {
2667
+ state.date = null;
2668
+ return;
2669
+ }
2670
+ if (typeof date === 'string') {
2671
+ date = new Date(Date.parse(date));
2672
+ }
2673
+ if (!isDate(date)) {
2674
+ return;
2675
+ }
2676
+ const min = options.minDate;
2677
+ const max = options.maxDate;
2678
+ if (isDate(min) && date < min) {
2679
+ date = min;
2680
+ }
2681
+ else if (isDate(max) && date > max) {
2682
+ date = max;
2683
+ }
2684
+ state.date = new Date(date.getTime());
2685
+ setToStartOfDay(state.date);
2686
+ gotoDate(state.date);
2687
+ if (!preventOnSelect && options.onSelect) {
2688
+ options.onSelect(state.date);
2689
+ }
2690
+ };
2691
+ const gotoDate = (date) => {
2692
+ if (!isDate(date)) {
2693
+ return;
2694
+ }
2695
+ state.calendars = [
2696
+ {
2697
+ month: date.getMonth(),
2698
+ year: date.getFullYear(),
2699
+ },
2700
+ ];
2701
+ };
2702
+ const nextMonth = () => {
2703
+ state.calendars[0].month++;
2704
+ adjustCalendars();
2705
+ };
2706
+ const prevMonth = () => {
2707
+ state.calendars[0].month--;
2708
+ adjustCalendars();
2709
+ };
2710
+ const adjustCalendars = () => {
2711
+ state.calendars[0] = adjustCalendar(state.calendars[0]);
2712
+ };
2713
+ const adjustCalendar = (calendar) => {
2714
+ if (calendar.month < 0) {
2715
+ calendar.year -= Math.ceil(Math.abs(calendar.month) / 12);
2716
+ calendar.month += 12;
2717
+ }
2718
+ if (calendar.month > 11) {
2719
+ calendar.year += Math.floor(Math.abs(calendar.month) / 12);
2720
+ calendar.month -= 12;
2721
+ }
2722
+ return calendar;
2723
+ };
2724
+ const renderDay = (opts, options) => {
2725
+ const arr = [];
2726
+ let ariaSelected = 'false';
2727
+ if (opts.isEmpty) {
2728
+ {
2729
+ return m('td.is-empty');
2730
+ }
2731
+ }
2732
+ if (opts.isDisabled) {
2733
+ arr.push('is-disabled');
2734
+ }
2735
+ if (opts.isToday) {
2736
+ arr.push('is-today');
2737
+ }
2738
+ if (opts.isSelected) {
2739
+ arr.push('is-selected');
2740
+ ariaSelected = 'true';
2741
+ }
2742
+ return m('td', {
2743
+ 'data-day': opts.day,
2744
+ class: arr.join(' '),
2745
+ 'aria-selected': ariaSelected,
2746
+ }, [
2747
+ m('button.datepicker-day-button', {
2748
+ type: 'button',
2749
+ 'data-year': opts.year,
2750
+ 'data-month': opts.month,
2751
+ 'data-day': opts.day,
2752
+ onclick: (e) => {
2753
+ const target = e.target;
2754
+ if (!opts.isDisabled) {
2755
+ const year = parseInt(target.getAttribute('data-year') || '0', 10);
2756
+ const month = parseInt(target.getAttribute('data-month') || '0', 10);
2757
+ const day = parseInt(target.getAttribute('data-day') || '0', 10);
2758
+ const selectedDate = new Date(year, month, day);
2759
+ setDate(selectedDate, false, options);
2760
+ if (options.autoClose) {
2761
+ state.isOpen = false;
2762
+ }
2763
+ }
2764
+ },
2765
+ }, opts.day),
2766
+ ]);
2767
+ };
2768
+ const renderCalendar = (year, month, options, randId) => {
2769
+ const now = new Date();
2770
+ const days = getDaysInMonth(year, month);
2771
+ let before = new Date(year, month, 1).getDay();
2772
+ const data = [];
2773
+ let row = [];
2774
+ setToStartOfDay(now);
2775
+ if (options.firstDay > 0) {
2776
+ before -= options.firstDay;
2777
+ if (before < 0) {
2778
+ before += 7;
2779
+ }
2780
+ }
2781
+ const previousMonth = month === 0 ? 11 : month - 1;
2782
+ const nextMonth = month === 11 ? 0 : month + 1;
2783
+ const yearOfPreviousMonth = month === 0 ? year - 1 : year;
2784
+ const yearOfNextMonth = month === 11 ? year + 1 : year;
2785
+ const daysInPreviousMonth = getDaysInMonth(yearOfPreviousMonth, previousMonth);
2786
+ let cells = days + before;
2787
+ let after = cells;
2788
+ while (after > 7) {
2789
+ after -= 7;
2790
+ }
2791
+ cells += 7 - after;
2792
+ for (let i = 0, r = 0; i < cells; i++) {
2793
+ const day = new Date(year, month, 1 + (i - before));
2794
+ const isSelected = isDate(state.date) ? compareDates(day, state.date) : false;
2795
+ const isToday = compareDates(day, now);
2796
+ const isEmpty = i < before || i >= days + before;
2797
+ let dayNumber = 1 + (i - before);
2798
+ let monthNumber = month;
2799
+ let yearNumber = year;
2800
+ if (isEmpty) {
2801
+ if (i < before) {
2802
+ dayNumber = daysInPreviousMonth + dayNumber;
2803
+ monthNumber = previousMonth;
2804
+ yearNumber = yearOfPreviousMonth;
2805
+ }
2806
+ else {
2807
+ dayNumber = dayNumber - days;
2808
+ monthNumber = nextMonth;
2809
+ yearNumber = yearOfNextMonth;
2810
+ }
2811
+ }
2812
+ const isDisabled = (options.minDate && day < options.minDate) ||
2813
+ (options.maxDate && day > options.maxDate) ||
2814
+ (options.disableWeekends && isWeekend(day)) ||
2815
+ (options.disableDayFn && options.disableDayFn(day));
2816
+ const dayConfig = {
2817
+ day: dayNumber,
2818
+ month: monthNumber,
2819
+ year: yearNumber,
2820
+ isSelected: isSelected,
2821
+ isToday: isToday,
2822
+ isDisabled: isDisabled,
2823
+ isEmpty: isEmpty};
2824
+ row.push(renderDay(dayConfig, options));
2825
+ if (++r === 7) {
2826
+ data.push(m('tr.datepicker-row', row));
2827
+ row = [];
2828
+ r = 0;
2829
+ }
2830
+ }
2831
+ const weekdayHeaders = [];
2832
+ for (let i = 0; i < 7; i++) {
2833
+ let day = i + options.firstDay;
2834
+ while (day >= 7) {
2835
+ day -= 7;
2836
+ }
2837
+ weekdayHeaders.push(m('th', { scope: 'col' }, [
2838
+ m('abbr', { title: options.i18n.weekdays[day] }, options.i18n.weekdaysAbbrev[day]),
2839
+ ]));
2840
+ }
2841
+ return m('.datepicker-table-wrapper', [
2842
+ m('table.datepicker-table', {
2843
+ cellpadding: '0',
2844
+ cellspacing: '0',
2845
+ role: 'grid',
2846
+ 'aria-labelledby': 'datepicker-controls',
2847
+ }, [m('thead', [m('tr', weekdayHeaders)]), m('tbody', data)]),
2848
+ ]);
2849
+ };
2850
+ const renderDateDisplay = (options) => {
2851
+ const displayDate = isDate(state.date) ? state.date : new Date();
2852
+ const day = options.i18n.weekdaysShort[displayDate.getDay()];
2853
+ const month = options.i18n.monthsShort[displayDate.getMonth()];
2854
+ const date = displayDate.getDate();
2855
+ return m('.datepicker-date-display', [
2856
+ m('span.year-text', displayDate.getFullYear()),
2857
+ m('span.date-text', `${day}, ${month} ${date}`),
2858
+ ]);
2859
+ };
2860
+ const renderControls = (options, randId) => {
2861
+ const calendar = state.calendars[0];
2862
+ const year = calendar.year;
2863
+ const month = calendar.month;
2864
+ // Month options
2865
+ const monthOptions = [];
2866
+ for (let i = 0; i < 12; i++) {
2867
+ monthOptions.push(m('option', {
2868
+ value: i,
2869
+ selected: i === month ? 'selected' : undefined,
2870
+ }, options.i18n.months[i]));
2871
+ }
2872
+ // Year options
2873
+ const yearOptions = [];
2874
+ let yearStart, yearEnd;
2875
+ if (Array.isArray(options.yearRange)) {
2876
+ yearStart = options.yearRange[0];
2877
+ yearEnd = options.yearRange[1];
2878
+ }
2879
+ else {
2880
+ yearStart = year - options.yearRange;
2881
+ yearEnd = year + options.yearRange;
2882
+ }
2883
+ for (let i = yearStart; i <= yearEnd; i++) {
2884
+ yearOptions.push(m('option', {
2885
+ value: i,
2886
+ selected: i === year ? 'selected' : undefined,
2887
+ }, i));
2888
+ }
2889
+ return m('.datepicker-controls', {
2890
+ id: randId,
2891
+ role: 'heading',
2892
+ 'aria-live': 'assertive',
2893
+ }, [
2894
+ m('button.month-prev', {
2895
+ type: 'button',
2896
+ onclick: (e) => {
2897
+ e.preventDefault();
2898
+ prevMonth();
2899
+ },
2900
+ }, m('svg', { fill: '#000000', height: '24', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg' }, [
2901
+ m('path', { d: 'M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z' }),
2902
+ m('path', { d: 'M0-.5h24v24H0z', fill: 'none' }),
2903
+ ])),
2904
+ m('.selects-container', [
2905
+ // Month select wrapper
2906
+ m('.select-wrapper.select-month', [
2907
+ m('input.select-dropdown.dropdown-trigger', {
2908
+ type: 'text',
2909
+ readonly: true,
2910
+ value: options.i18n.months[month],
2911
+ onclick: (e) => {
2912
+ e.preventDefault();
2913
+ state.monthDropdownOpen = !state.monthDropdownOpen;
2914
+ state.yearDropdownOpen = false; // Close year dropdown
2915
+ },
2916
+ }),
2917
+ // Custom dropdown menu
2918
+ state.monthDropdownOpen &&
2919
+ m('.dropdown-content', options.i18n.months.map((monthName, index) => m('.dropdown-item', {
2920
+ key: index,
2921
+ class: index === month ? 'selected' : '',
2922
+ onclick: (e) => {
2923
+ e.stopPropagation();
2924
+ gotoMonth(index);
2925
+ state.monthDropdownOpen = false;
2926
+ },
2927
+ }, monthName))),
2928
+ ]),
2929
+ // Year select wrapper
2930
+ m('.select-wrapper.select-year', [
2931
+ m('input.select-dropdown.dropdown-trigger', {
2932
+ type: 'text',
2933
+ readonly: true,
2934
+ value: year.toString(),
2935
+ onclick: (e) => {
2936
+ e.preventDefault();
2937
+ state.yearDropdownOpen = !state.yearDropdownOpen;
2938
+ state.monthDropdownOpen = false; // Close month dropdown
2939
+ },
2940
+ }),
2941
+ // Custom dropdown menu
2942
+ state.yearDropdownOpen &&
2943
+ m('.dropdown-content', range(yearStart, yearEnd).map((i) => m('.dropdown-item', {
2944
+ key: i,
2945
+ class: i === year ? 'selected' : '',
2946
+ onclick: (e) => {
2947
+ e.stopPropagation();
2948
+ gotoYear(i);
2949
+ state.yearDropdownOpen = false;
2950
+ },
2951
+ }, i))),
2952
+ ]),
2953
+ ]),
2954
+ m('button.month-next', {
2955
+ type: 'button',
2956
+ onclick: (e) => {
2957
+ e.preventDefault();
2958
+ nextMonth();
2959
+ },
2960
+ }, m('svg', { fill: '#000000', height: '24', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg' }, [
2961
+ m('path', { d: 'M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z' }),
2962
+ m('path', { d: 'M0-.25h24v24H0z', fill: 'none' }),
2963
+ ])),
2964
+ ]);
2965
+ };
2966
+ const gotoMonth = (month) => {
2967
+ if (!isNaN(month)) {
2968
+ state.calendars[0].month = month;
2969
+ adjustCalendars();
2970
+ }
2971
+ };
2972
+ const gotoYear = (year) => {
2973
+ if (!isNaN(year)) {
2974
+ state.calendars[0].year = year;
2975
+ adjustCalendars();
2976
+ }
2977
+ };
2978
+ const handleDocumentClick = (e) => {
2979
+ const target = e.target;
2980
+ if (!target.closest('.select-wrapper') && !target.closest('.dropdown-content')) {
2981
+ state.monthDropdownOpen = false;
2982
+ state.yearDropdownOpen = false;
2983
+ }
2984
+ };
2985
+ return {
2986
+ oninit: (vnode) => {
2987
+ const attrs = vnode.attrs;
2988
+ const options = mergeOptions(attrs);
2989
+ state = {
2990
+ id: uniqueId(),
2991
+ isOpen: false,
2992
+ date: null,
2993
+ calendars: [{ month: 0, year: 0 }],
2994
+ monthDropdownOpen: false,
2995
+ yearDropdownOpen: false,
2996
+ formats: {
2997
+ d: () => { var _a; return ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDate()) || 0; },
2998
+ dd: () => {
2999
+ var _a;
3000
+ const d = ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDate()) || 0;
3001
+ return (d < 10 ? '0' : '') + d;
3002
+ },
3003
+ ddd: () => { var _a; return options.i18n.weekdaysShort[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDay()) || 0]; },
3004
+ dddd: () => { var _a; return options.i18n.weekdays[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDay()) || 0]; },
3005
+ m: () => { var _a; return (((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0) + 1; },
3006
+ mm: () => {
3007
+ var _a;
3008
+ const m = (((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0) + 1;
3009
+ return (m < 10 ? '0' : '') + m;
3010
+ },
3011
+ mmm: () => { var _a; return options.i18n.monthsShort[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0]; },
3012
+ mmmm: () => { var _a; return options.i18n.months[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0]; },
3013
+ yy: () => { var _a; return ('' + (((_a = state.date) === null || _a === void 0 ? void 0 : _a.getFullYear()) || 0)).slice(2); },
3014
+ yyyy: () => { var _a; return ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getFullYear()) || 0; },
3015
+ },
3016
+ };
3017
+ // Initialize date
3018
+ let defaultDate = attrs.defaultDate;
3019
+ if (!defaultDate && attrs.initialValue) {
3020
+ defaultDate = new Date(attrs.initialValue);
3021
+ }
3022
+ if (isDate(defaultDate)) {
3023
+ // Always set the date if we have initialValue or defaultDate
3024
+ setDate(defaultDate, true, options);
3025
+ }
3026
+ else {
3027
+ gotoDate(new Date());
3028
+ }
3029
+ // Add document click listener to close dropdowns
3030
+ document.addEventListener('click', handleDocumentClick);
3031
+ },
3032
+ onremove: () => {
3033
+ // Clean up event listener
3034
+ document.removeEventListener('click', handleDocumentClick);
3035
+ },
3036
+ view: (vnode) => {
3037
+ const attrs = vnode.attrs;
3038
+ const options = mergeOptions(attrs);
3039
+ const { id = state.id, label, dateLabel, placeholder, disabled, required, className, style, helperText, iconName, newRow, } = attrs;
3040
+ // Use dateLabel if label is not provided (backward compatibility)
3041
+ const finalLabel = label || dateLabel;
3042
+ const finalClassName = newRow ? `${className || ''} clear` : className;
3043
+ return m('.input-field', {
3044
+ className: finalClassName,
3045
+ style,
3046
+ }, [
3047
+ // Icon prefix
3048
+ iconName && m('i.material-icons.prefix', iconName),
3049
+ // Main date input
3050
+ m('input.datepicker', {
3051
+ id,
3052
+ type: 'text',
3053
+ value: toString(state.date, options.format),
3054
+ placeholder,
3055
+ disabled,
3056
+ required,
3057
+ readonly: true,
3058
+ format: attrs.format,
3059
+ yearrange: attrs.yearrange,
3060
+ tabindex: '0',
3061
+ onclick: () => {
3062
+ if (!disabled) {
3063
+ state.isOpen = true;
3064
+ if (options.onOpen)
3065
+ options.onOpen();
3066
+ }
3067
+ },
3068
+ }),
3069
+ // Label
3070
+ finalLabel &&
3071
+ m('label', {
3072
+ for: id,
3073
+ class: state.date || placeholder ? 'active' : '',
3074
+ }, finalLabel),
3075
+ // Helper text
3076
+ helperText && m('span.helper-text', helperText),
3077
+ // Modal datepicker
3078
+ state.isOpen && [
3079
+ m('.modal.datepicker-modal.open', {
3080
+ id: `modal-${state.id}`,
3081
+ tabindex: 0,
3082
+ style: {
3083
+ zIndex: 1003,
3084
+ display: 'block',
3085
+ opacity: 1,
3086
+ top: '10%',
3087
+ transform: 'scaleX(1) scaleY(1)',
3088
+ },
3089
+ }, [
3090
+ m('.modal-content.datepicker-container', {
3091
+ onclick: (e) => {
3092
+ // Close dropdowns when clicking anywhere in the modal content
3093
+ const target = e.target;
3094
+ if (!target.closest('.select-wrapper') && !target.closest('.dropdown-content')) {
3095
+ state.monthDropdownOpen = false;
3096
+ state.yearDropdownOpen = false;
3097
+ }
3098
+ },
3099
+ }, [
3100
+ renderDateDisplay(options),
3101
+ m('.datepicker-calendar-container', [
3102
+ m('.datepicker-calendar', [
3103
+ renderControls(options, `datepicker-title-${Math.random().toString(36).slice(2)}`),
3104
+ renderCalendar(state.calendars[0].year, state.calendars[0].month, options),
3105
+ ]),
3106
+ m('.datepicker-footer', [
3107
+ options.showClearBtn &&
3108
+ m('button.btn-flat.datepicker-clear.waves-effect', {
3109
+ type: 'button',
3110
+ style: '',
3111
+ onclick: () => {
3112
+ setDate(null, false, options);
3113
+ state.isOpen = false;
3114
+ },
3115
+ }, options.i18n.clear),
3116
+ m('.confirmation-btns', [
3117
+ m('button.btn-flat.datepicker-cancel.waves-effect', {
3118
+ type: 'button',
3119
+ onclick: () => {
3120
+ state.isOpen = false;
3121
+ if (options.onClose)
3122
+ options.onClose();
3123
+ },
3124
+ }, options.i18n.cancel),
3125
+ m('button.btn-flat.datepicker-done.waves-effect', {
3126
+ type: 'button',
3127
+ onclick: () => {
3128
+ if (attrs.onchange && state.date) {
3129
+ attrs.onchange(state.date.toISOString().split('T')[0]);
3130
+ }
3131
+ state.isOpen = false;
3132
+ if (options.onClose)
3133
+ options.onClose();
3134
+ },
3135
+ }, options.i18n.done),
3136
+ ]),
3137
+ ]),
3138
+ ]),
3139
+ ]),
3140
+ ]),
3141
+ // Modal overlay
3142
+ m('.modal-overlay', {
3143
+ style: {
3144
+ zIndex: 1002,
3145
+ display: 'block',
3146
+ opacity: 0.5,
3147
+ },
3148
+ onclick: () => {
3149
+ state.isOpen = false;
3150
+ if (options.onClose)
3151
+ options.onClose();
3152
+ },
3153
+ }),
3154
+ ],
3155
+ ]);
3156
+ },
3157
+ };
3158
+ };
3159
+ /**
3160
+ * Enhanced TimePicker component with i18n support and improved functionality.
3161
+ *
3162
+ * Usage:
3163
+ * - Use `initialValue` to set the current/initial time value (24h format: "HH:MM")
3164
+ * - Use `defaultTime` only if you need a fallback when the field is cleared
3165
+ * - The component accepts and outputs 24-hour format strings ("HH:MM")
3166
+ * - Display format (12h/24h) is controlled by the `twelveHour` property
3167
+ */
3168
+ const TimePicker = () => {
3169
+ const state = {
3170
+ id: uniqueId(),
3171
+ isOpen: false,
3172
+ hours: 12,
3173
+ minutes: 0,
3174
+ ampm: 'AM',
3175
+ use12Hour: false,
3176
+ time: ''};
3177
+ const parseTime = (timeString) => {
3178
+ if (!timeString)
3179
+ return { hours: 12, minutes: 0, ampm: 'AM' };
3180
+ const [time, ampm] = timeString.split(' ');
3181
+ const [hoursStr, minutesStr] = time.split(':');
3182
+ let hours = parseInt(hoursStr, 10) || 0;
3183
+ const minutes = parseInt(minutesStr, 10) || 0;
3184
+ if (ampm) {
3185
+ // 12-hour format
3186
+ if (ampm.toUpperCase() === 'PM' && hours !== 12)
3187
+ hours += 12;
3188
+ if (ampm.toUpperCase() === 'AM' && hours === 12)
3189
+ hours = 0;
3190
+ return { hours, minutes, ampm: ampm.toUpperCase() };
3191
+ }
3192
+ else {
3193
+ // 24-hour format
3194
+ const displayAmpm = hours >= 12 ? 'PM' : 'AM';
3195
+ return { hours, minutes, ampm: displayAmpm };
3196
+ }
3197
+ };
3198
+ const formatTime = (hours, minutes, use12Hour) => {
3199
+ if (use12Hour) {
3200
+ const displayHours = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
3201
+ const ampm = hours >= 12 ? 'PM' : 'AM';
3202
+ return `${displayHours}:${minutes.toString().padStart(2, '0')} ${ampm}`;
3203
+ }
3204
+ else {
3205
+ return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
3206
+ }
3207
+ };
3208
+ const setTime = (timeString, attrs) => {
3209
+ const parsed = parseTime(timeString);
3210
+ state.hours = parsed.hours;
3211
+ state.minutes = parsed.minutes;
3212
+ state.ampm = parsed.ampm;
3213
+ state.time = timeString;
3214
+ if (attrs.onchange) {
3215
+ // Always output 24-hour format for consistency
3216
+ const output24h = `${state.hours.toString().padStart(2, '0')}:${state.minutes.toString().padStart(2, '0')}`;
3217
+ attrs.onchange(output24h);
3218
+ }
3219
+ if (attrs.oninput) {
3220
+ attrs.oninput(timeString);
3221
+ }
3222
+ if (attrs.onSelect) {
3223
+ attrs.onSelect(state.hours, state.minutes);
3224
+ }
3225
+ };
3226
+ return {
3227
+ oninit: (vnode) => {
3228
+ const { initialValue, defaultTime, twelveHour = false } = vnode.attrs;
3229
+ state.use12Hour = twelveHour;
3230
+ const timeValue = initialValue || defaultTime || '';
3231
+ if (timeValue) {
3232
+ const parsed = parseTime(timeValue);
3233
+ state.hours = parsed.hours;
3234
+ state.minutes = parsed.minutes;
3235
+ state.ampm = parsed.ampm;
3236
+ state.time = formatTime(state.hours, state.minutes, state.use12Hour);
3237
+ }
3238
+ },
3239
+ view: (vnode) => {
3240
+ const attrs = vnode.attrs;
3241
+ const { id = state.id, label = 'Time', placeholder = 'Select time', disabled, readonly, required, className, style, helperText, iconName = 'access_time', newRow, twelveHour = false, timeLabel = 'Time', nowLabel = 'Now', clearLabel = 'Clear', closeLabel = 'Close', amLabel = 'AM', pmLabel = 'PM', showClearBtn = false, showNowBtn = false, useModal = true, } = attrs;
3242
+ state.use12Hour = twelveHour;
3243
+ const finalClassName = newRow ? `${className || ''} clear` : className;
3244
+ const displayValue = state.time ? formatTime(state.hours, state.minutes, state.use12Hour) : '';
3245
+ return m('.input-field', {
3246
+ className: finalClassName,
3247
+ style,
3248
+ }, [
3249
+ // Icon prefix
3250
+ iconName && m('i.material-icons.prefix', iconName),
3251
+ // Time input field
3252
+ m('input.timepicker', {
3253
+ id,
3254
+ type: 'text',
3255
+ value: displayValue,
3256
+ placeholder,
3257
+ disabled,
3258
+ readonly,
3259
+ required,
3260
+ onclick: () => {
3261
+ if (!disabled && !readonly && useModal) {
3262
+ state.isOpen = true;
3263
+ if (attrs.onOpen)
3264
+ attrs.onOpen();
3265
+ }
3266
+ },
3267
+ }),
3268
+ // Label
3269
+ label &&
3270
+ m('label', {
3271
+ for: id,
3272
+ class: displayValue || placeholder ? 'active' : '',
3273
+ }, label),
3274
+ // Helper text
3275
+ helperText && m('span.helper-text', helperText),
3276
+ // Time picker modal (simplified version)
3277
+ useModal &&
3278
+ state.isOpen &&
3279
+ m('.timepicker-modal', {
3280
+ style: {
3281
+ position: 'fixed',
3282
+ top: '50%',
3283
+ left: '50%',
3284
+ transform: 'translate(-50%, -50%)',
3285
+ backgroundColor: 'white',
3286
+ padding: '20px',
3287
+ borderRadius: '4px',
3288
+ boxShadow: '0 4px 20px rgba(0,0,0,0.3)',
3289
+ zIndex: 1000,
3290
+ },
3291
+ }, [
3292
+ m('h4', timeLabel),
3293
+ m('.time-inputs', {
3294
+ style: { display: 'flex', alignItems: 'center', gap: '10px', margin: '20px 0' },
3295
+ }, [
3296
+ // Hours input
3297
+ m('input', {
3298
+ type: 'number',
3299
+ min: state.use12Hour ? 1 : 0,
3300
+ max: state.use12Hour ? 12 : 23,
3301
+ value: state.use12Hour
3302
+ ? state.hours === 0
3303
+ ? 12
3304
+ : state.hours > 12
3305
+ ? state.hours - 12
3306
+ : state.hours
3307
+ : state.hours,
3308
+ style: { width: '60px', textAlign: 'center', padding: '8px' },
3309
+ onchange: (e) => {
3310
+ const target = e.target;
3311
+ let hours = parseInt(target.value) || 0;
3312
+ if (state.use12Hour) {
3313
+ if (state.ampm === 'PM' && hours !== 12)
3314
+ hours += 12;
3315
+ if (state.ampm === 'AM' && hours === 12)
3316
+ hours = 0;
3317
+ }
3318
+ state.hours = hours;
3319
+ state.time = formatTime(state.hours, state.minutes, state.use12Hour);
3320
+ },
3321
+ }),
3322
+ m('span', ':'),
3323
+ // Minutes input
3324
+ m('input', {
3325
+ type: 'number',
3326
+ min: 0,
3327
+ max: 59,
3328
+ value: state.minutes,
3329
+ style: { width: '60px', textAlign: 'center', padding: '8px' },
3330
+ onchange: (e) => {
3331
+ const target = e.target;
3332
+ state.minutes = parseInt(target.value) || 0;
3333
+ state.time = formatTime(state.hours, state.minutes, state.use12Hour);
3334
+ },
3335
+ }),
3336
+ // AM/PM toggle for 12-hour format
3337
+ state.use12Hour &&
3338
+ m('select', {
3339
+ value: state.ampm,
3340
+ style: { padding: '8px' },
3341
+ onchange: (e) => {
3342
+ const target = e.target;
3343
+ const oldAmpm = state.ampm;
3344
+ state.ampm = target.value;
3345
+ // Adjust hours when switching AM/PM
3346
+ if (oldAmpm !== state.ampm) {
3347
+ if (state.ampm === 'PM' && state.hours < 12) {
3348
+ state.hours += 12;
3349
+ }
3350
+ else if (state.ampm === 'AM' && state.hours >= 12) {
3351
+ state.hours -= 12;
3352
+ }
3353
+ }
3354
+ state.time = formatTime(state.hours, state.minutes, state.use12Hour);
3355
+ },
3356
+ }, [m('option', { value: 'AM' }, amLabel), m('option', { value: 'PM' }, pmLabel)]),
3357
+ ]),
3358
+ // Action buttons
3359
+ m('.timepicker-actions', {
3360
+ style: { display: 'flex', justifyContent: 'flex-end', gap: '10px' },
3361
+ }, [
3362
+ showClearBtn &&
3363
+ m('button.btn-flat', {
3364
+ onclick: () => {
3365
+ setTime('', attrs);
3366
+ state.isOpen = false;
3367
+ },
3368
+ }, clearLabel),
3369
+ showNowBtn &&
3370
+ m('button.btn-flat', {
3371
+ onclick: () => {
3372
+ const now = new Date();
3373
+ state.hours = now.getHours();
3374
+ state.minutes = now.getMinutes();
3375
+ state.ampm = state.hours >= 12 ? 'PM' : 'AM';
3376
+ state.time = formatTime(state.hours, state.minutes, state.use12Hour);
3377
+ },
3378
+ }, nowLabel),
3379
+ m('button.btn-flat', {
3380
+ onclick: () => {
3381
+ state.isOpen = false;
3382
+ if (attrs.onClose)
3383
+ attrs.onClose();
3384
+ },
3385
+ }, closeLabel),
3386
+ m('button.btn-flat', {
3387
+ onclick: () => {
3388
+ setTime(state.time, attrs);
3389
+ state.isOpen = false;
3390
+ if (attrs.onClose)
3391
+ attrs.onClose();
3392
+ },
3393
+ }, 'OK'),
3394
+ ]),
3395
+ ]),
3396
+ // Modal backdrop
3397
+ useModal &&
3398
+ state.isOpen &&
3399
+ m('.modal-backdrop', {
3400
+ style: {
3401
+ position: 'fixed',
3402
+ top: 0,
3403
+ left: 0,
3404
+ width: '100%',
3405
+ height: '100%',
3406
+ backgroundColor: 'rgba(0,0,0,0.5)',
3407
+ zIndex: 999,
3408
+ },
3409
+ onclick: () => {
3410
+ state.isOpen = false;
3411
+ if (attrs.onClose)
3412
+ attrs.onClose();
3413
+ },
3414
+ }),
3415
+ ]);
3416
+ },
3417
+ };
3418
+ };
3419
+
3420
+ const RadioButton = () => ({
3421
+ view: ({ attrs: { id, groupId, label, onchange, className = 'col s12', checked, disabled, inputId } }) => {
3422
+ const radioId = inputId || `${groupId}-${id}`;
3423
+ return m('p', { className }, m('label', { for: radioId }, [
3424
+ m('input[type=radio][tabindex=0]', {
3425
+ id: radioId,
3426
+ name: groupId,
3427
+ disabled,
3428
+ checked,
3429
+ onclick: onchange ? () => onchange(id) : undefined,
3430
+ }),
3431
+ m('span', m.trust(label)),
3432
+ ]));
3433
+ },
3434
+ });
3435
+ /** Component to show a list of radio buttons, from which you can choose one. */
3436
+ // export const RadioButtons: FactoryComponent<IRadioButtons<T>> = () => {
3437
+ const RadioButtons = () => {
3438
+ const state = { groupId: uniqueId() };
3439
+ return {
3440
+ oninit: ({ attrs: { checkedId, initialValue, id } }) => {
3441
+ state.oldCheckedId = checkedId;
3442
+ state.checkedId = checkedId || initialValue;
3443
+ state.componentId = id || uniqueId();
3444
+ },
3445
+ view: ({ attrs: { checkedId: cid, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange: callback, }, }) => {
3446
+ if (state.oldCheckedId !== cid) {
3447
+ state.oldCheckedId = state.checkedId = cid;
3448
+ }
3449
+ const { groupId, checkedId, componentId } = state;
3450
+ const onchange = (propId) => {
3451
+ state.checkedId = propId;
3452
+ if (callback) {
3453
+ callback(propId);
3454
+ }
3455
+ };
3456
+ const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
3457
+ const optionsContent = layout === 'horizontal'
3458
+ ? m('div.grid-container', options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
3459
+ groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` }))))
3460
+ : options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
3461
+ groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` })));
3462
+ return m('div', { id: componentId, className: cn }, [
3463
+ label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
3464
+ description && m('p.helper-text', m.trust(description)),
3465
+ m('form', { action: '#' }, optionsContent),
3466
+ ]);
3467
+ },
3468
+ };
3469
+ };
3470
+
3471
+ /** Select component */
3472
+ const Select = () => {
3473
+ const state = {
3474
+ id: '',
3475
+ isOpen: false,
3476
+ selectedIds: [],
3477
+ focusedIndex: -1,
3478
+ inputRef: null,
3479
+ dropdownRef: null,
3480
+ };
3481
+ const isSelected = (id, selectedIds) => {
3482
+ return selectedIds.some((selectedId) => selectedId === id);
3483
+ };
3484
+ const toggleOption = (id, multiple, attrs) => {
3485
+ if (multiple) {
3486
+ const newIds = state.selectedIds.includes(id)
3487
+ ? // isSelected(id, state.selectedIds)
3488
+ state.selectedIds.filter((selectedId) => selectedId !== id)
3489
+ : [...state.selectedIds, id];
3490
+ state.selectedIds = newIds;
3491
+ attrs.onchange(newIds);
3492
+ console.log(newIds);
3493
+ // Keep dropdown open for multiple select
3494
+ }
3495
+ else {
3496
+ state.selectedIds = [id];
3497
+ // Close dropdown for single select
3498
+ state.isOpen = false;
3499
+ attrs.onchange([id]);
3500
+ }
3501
+ };
3502
+ const handleKeyDown = (e, attrs) => {
3503
+ const { options } = attrs;
3504
+ const selectableOptions = options.filter((opt) => !opt.disabled);
3505
+ switch (e.key) {
3506
+ case 'ArrowDown':
3507
+ e.preventDefault();
3508
+ if (!state.isOpen) {
3509
+ state.isOpen = true;
3510
+ state.focusedIndex = 0;
3511
+ }
3512
+ else {
3513
+ const currentSelectableIndex = selectableOptions.findIndex((opt) => opt === options[state.focusedIndex]);
3514
+ const nextSelectableIndex = Math.min(currentSelectableIndex + 1, selectableOptions.length - 1);
3515
+ const nextOption = selectableOptions[nextSelectableIndex];
3516
+ state.focusedIndex = options.findIndex((opt) => opt === nextOption);
3517
+ }
3518
+ break;
3519
+ case 'ArrowUp':
3520
+ e.preventDefault();
3521
+ if (state.isOpen) {
3522
+ const currentSelectableIndex = selectableOptions.findIndex((opt) => opt === options[state.focusedIndex]);
3523
+ const prevSelectableIndex = Math.max(currentSelectableIndex - 1, 0);
3524
+ const prevOption = selectableOptions[prevSelectableIndex];
3525
+ state.focusedIndex = options.findIndex((opt) => opt === prevOption);
3526
+ }
3527
+ break;
3528
+ case 'Enter':
3529
+ case ' ':
3530
+ e.preventDefault();
3531
+ if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < options.length) {
3532
+ const option = options[state.focusedIndex];
3533
+ if (option && !option.disabled) {
3534
+ toggleOption(option.id, attrs.multiple || false, attrs);
3535
+ }
3536
+ }
3537
+ else if (!state.isOpen) {
3538
+ state.isOpen = true;
3539
+ state.focusedIndex = 0;
3540
+ }
3541
+ break;
3542
+ case 'Escape':
3543
+ e.preventDefault();
3544
+ state.isOpen = false;
3545
+ state.focusedIndex = -1;
3546
+ break;
3547
+ }
3548
+ };
3549
+ const closeDropdown = (e) => {
3550
+ const target = e.target;
3551
+ if (!target.closest('.select-wrapper-container')) {
3552
+ state.isOpen = false;
3553
+ m.redraw();
3554
+ }
3555
+ };
3556
+ const renderGroupedOptions = (options, multiple, attrs) => {
3557
+ const groupedOptions = {};
3558
+ const ungroupedOptions = [];
3559
+ // Group options by their group property
3560
+ options.forEach((option) => {
3561
+ if (option.group) {
3562
+ if (!groupedOptions[option.group]) {
3563
+ groupedOptions[option.group] = [];
3564
+ }
3565
+ groupedOptions[option.group].push(option);
3566
+ }
3567
+ else {
3568
+ ungroupedOptions.push(option);
3569
+ }
3570
+ });
3571
+ const renderElements = [];
3572
+ // Render ungrouped options first
3573
+ ungroupedOptions.forEach((option) => {
3574
+ renderElements.push(m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === options.indexOf(option) ? 'focused' : '' }, (option.disabled
3575
+ ? {}
3576
+ : {
3577
+ onclick: (e) => {
3578
+ e.stopPropagation();
3579
+ toggleOption(option.id, multiple, attrs);
3580
+ },
3581
+ })), m('span', multiple
3582
+ ? m('label', { for: option.id }, m('input', {
3583
+ id: option.id,
3584
+ type: 'checkbox',
3585
+ checked: state.selectedIds.includes(option.id),
3586
+ disabled: option.disabled ? true : undefined,
3587
+ onclick: (e) => {
3588
+ e.stopPropagation();
3589
+ },
3590
+ }), m('span', option.label))
3591
+ : option.label)));
3592
+ });
3593
+ // Render grouped options
3594
+ Object.keys(groupedOptions).forEach((groupName) => {
3595
+ // Add group header
3596
+ renderElements.push(m('li.optgroup', { tabindex: 0 }, m('span', groupName)));
3597
+ // Add group options
3598
+ groupedOptions[groupName].forEach((option) => {
3599
+ renderElements.push(m('li', Object.assign({ class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, state.selectedIds) ? ' selected' : ''}${state.focusedIndex === options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
3600
+ ? {}
3601
+ : {
3602
+ onclick: (e) => {
3603
+ e.stopPropagation();
3604
+ toggleOption(option.id, multiple, attrs);
3605
+ },
3606
+ })), m('span', multiple
3607
+ ? m('label', { for: option.id }, m('input', {
3608
+ id: option.id,
3609
+ type: 'checkbox',
3610
+ checked: state.selectedIds.includes(option.id),
3611
+ disabled: option.disabled ? true : undefined,
3612
+ onclick: (e) => {
3613
+ e.stopPropagation();
3614
+ },
3615
+ }), m('span', option.label))
3616
+ : option.label)));
3617
+ });
3618
+ });
3619
+ return renderElements;
3620
+ };
3621
+ return {
3622
+ oninit: ({ attrs }) => {
3623
+ const { checkedId, initialValue, id } = attrs;
3624
+ state.id = id || uniqueId();
3625
+ const iv = checkedId || initialValue;
3626
+ if (iv !== null && typeof iv !== 'undefined') {
3627
+ if (iv instanceof Array) {
3628
+ state.selectedIds = [...iv];
3629
+ }
3630
+ else {
3631
+ state.selectedIds = [iv];
3632
+ }
3633
+ }
3634
+ // Add global click listener to close dropdown
3635
+ document.addEventListener('click', closeDropdown);
3636
+ },
3637
+ onremove: () => {
3638
+ // Cleanup global listener
3639
+ document.removeEventListener('click', closeDropdown);
3640
+ },
3641
+ view: ({ attrs }) => {
3642
+ // Sync external checkedId prop with internal state - do this in view for immediate response
3643
+ const { checkedId } = attrs;
3644
+ if (checkedId !== undefined) {
3645
+ const newIds = checkedId instanceof Array ? checkedId : [checkedId];
3646
+ if (JSON.stringify(newIds) !== JSON.stringify(state.selectedIds)) {
3647
+ state.selectedIds = newIds;
3648
+ }
3649
+ }
3650
+ const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, disabled, style, } = attrs;
3651
+ const finalClassName = newRow ? `${className} clear` : className;
3652
+ const selectedOptions = options.filter((opt) => isSelected(opt.id, state.selectedIds));
3653
+ return m('.input-field.select-space', {
3654
+ className: finalClassName,
3655
+ key,
3656
+ style,
3657
+ }, [
3658
+ // Icon prefix
3659
+ iconName && m('i.material-icons.prefix', iconName),
3660
+ m('.select-wrapper', {
3661
+ onclick: disabled
3662
+ ? undefined
3663
+ : () => {
3664
+ state.isOpen = !state.isOpen;
3665
+ },
3666
+ onkeydown: disabled ? undefined : (e) => handleKeyDown(e, attrs),
3667
+ tabindex: disabled ? -1 : 0,
3668
+ 'aria-expanded': state.isOpen ? 'true' : 'false',
3669
+ 'aria-haspopup': 'listbox',
3670
+ role: 'combobox',
3671
+ }, [
3672
+ m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
3673
+ id: state.id,
3674
+ value: selectedOptions.length > 0 ? selectedOptions.map((o) => o.label || o.id).join(', ') : placeholder,
3675
+ oncreate: ({ dom }) => {
3676
+ state.inputRef = dom;
3677
+ },
3678
+ onclick: (e) => {
3679
+ e.preventDefault();
3680
+ e.stopPropagation();
3681
+ state.isOpen = !state.isOpen;
3682
+ },
3683
+ }),
3684
+ // Dropdown Menu
3685
+ state.isOpen &&
3686
+ m('ul.dropdown-content.select-dropdown', {
3687
+ tabindex: 0,
3688
+ oncreate: ({ dom }) => {
3689
+ state.dropdownRef = dom;
3690
+ },
3691
+ onremove: () => {
3692
+ state.dropdownRef = null;
3693
+ },
3694
+ style: getDropdownStyles(state.inputRef, true, options),
3695
+ }, [
3696
+ placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
3697
+ ...renderGroupedOptions(options, multiple, attrs),
3698
+ ]),
3699
+ m(MaterialIcon, {
3700
+ name: 'caret',
3701
+ direction: 'down',
3702
+ }),
3703
+ ]),
3704
+ // Label
3705
+ label &&
3706
+ m(Label, {
3707
+ id: state.id,
3708
+ label,
3709
+ isMandatory,
3710
+ }),
3711
+ // Helper text
3712
+ helperText && m(HelperText, { helperText }),
3713
+ ]);
3714
+ },
3715
+ };
3716
+ };
3717
+
3718
+ /** Component to display a switch with two values. */
3719
+ const Switch = () => {
3720
+ const state = { id: uniqueId(), checked: false };
3721
+ return {
3722
+ oninit: ({ attrs: { checked } }) => {
3723
+ state.checked = checked || false;
3724
+ },
3725
+ view: ({ attrs }) => {
3726
+ const id = attrs.id || state.id;
3727
+ const { label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
3728
+ const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
3729
+ return m('div', {
3730
+ className: cn,
3731
+ onclick: (e) => {
3732
+ state.checked = !state.checked;
3733
+ onchange && onchange(state.checked);
3734
+ e.preventDefault();
3735
+ },
3736
+ }, [
3737
+ label && m(Label, { label: label || '', id, isMandatory, className: 'active' }),
3738
+ m('.switch', params, m('label', {
3739
+ style: { cursor: 'pointer' },
3740
+ }, [
3741
+ m('span', left || 'Off'),
3742
+ m('input[type=checkbox]', {
3743
+ id,
3744
+ disabled,
3745
+ checked: state.checked,
3746
+ }),
3747
+ m('span.lever'),
3748
+ m('span', right || 'On'),
3749
+ ])),
3750
+ ]);
3751
+ },
3752
+ };
3753
+ };
3754
+
3755
+ /** CSS-only Tabs component - no MaterializeCSS dependencies */
3756
+ const Tabs = () => {
3757
+ const toAnchored = () => {
3758
+ let activeTabFound = false;
3759
+ return (tab) => {
3760
+ const active = activeTabFound ? false : tab.active;
3761
+ if (active)
3762
+ activeTabFound = true;
3763
+ const tabId = createId(tab.title, tab.id);
3764
+ return Object.assign(Object.assign({}, tab), { active, tabId, anchorId: `anchor-${tabId}` });
3765
+ };
3766
+ };
3767
+ const state = {
3768
+ activeTabId: '',
3769
+ isDragging: false,
3770
+ startX: 0,
3771
+ translateX: 0,
3772
+ indicatorStyle: {
3773
+ left: '0px',
3774
+ width: '0px',
3775
+ }};
3776
+ const createId = (title, id) => (id ? id : title.replace(/ /g, '').toLowerCase());
3777
+ const updateIndicator = () => {
3778
+ const tabElement = document.getElementById(state.activeTabId);
3779
+ if (tabElement) {
3780
+ const tabsContainer = tabElement.closest('.tabs');
3781
+ if (tabsContainer) {
3782
+ const containerRect = tabsContainer.getBoundingClientRect();
3783
+ const tabRect = tabElement.getBoundingClientRect();
3784
+ const newLeft = `${tabRect.left - containerRect.left}px`;
3785
+ const newWidth = `${tabRect.width}px`;
3786
+ // Only update if values actually changed - NO m.redraw()!
3787
+ if (state.indicatorStyle.left !== newLeft || state.indicatorStyle.width !== newWidth) {
3788
+ state.indicatorStyle = {
3789
+ left: newLeft,
3790
+ width: newWidth,
3791
+ };
3792
+ }
3793
+ }
3794
+ }
3795
+ };
3796
+ const handleTabClick = (tabId, tabElement, attrs) => {
3797
+ if (state.activeTabId === tabId)
3798
+ return;
3799
+ state.activeTabId = tabId;
3800
+ // Call onShow callback if provided
3801
+ if (attrs.onShow) {
3802
+ attrs.onShow(tabElement);
3803
+ }
3804
+ // Call onTabChange callback if provided
3805
+ if (attrs.onTabChange) {
3806
+ attrs.onTabChange(tabId);
3807
+ }
3808
+ };
3809
+ // Touch/swipe support for mobile
3810
+ const handleTouchStart = (e) => {
3811
+ if (!e.touches || e.touches.length === 0)
3812
+ return;
3813
+ state.isDragging = true;
3814
+ state.startX = e.touches[0].clientX;
3815
+ };
3816
+ const handleTouchEnd = (e, attrs) => {
3817
+ if (!state.isDragging || !e.changedTouches || e.changedTouches.length === 0)
3818
+ return;
3819
+ const endX = e.changedTouches[0].clientX;
3820
+ const deltaX = endX - state.startX;
3821
+ const threshold = 50; // Minimum swipe distance
3822
+ if (Math.abs(deltaX) > threshold) {
3823
+ const currentIndex = attrs.tabs.findIndex((tab) => createId(tab.title, tab.id) === state.activeTabId);
3824
+ if (deltaX > 0 && currentIndex > 0) {
3825
+ // Swipe right - go to previous tab
3826
+ const prevTab = attrs.tabs[currentIndex - 1];
3827
+ if (!prevTab.disabled && !prevTab.href) {
3828
+ const newTabId = createId(prevTab.title, prevTab.id);
3829
+ state.activeTabId = newTabId;
3830
+ if (attrs.onTabChange) {
3831
+ attrs.onTabChange(newTabId);
3832
+ }
3833
+ }
3834
+ }
3835
+ else if (deltaX < 0 && currentIndex < attrs.tabs.length - 1) {
3836
+ // Swipe left - go to next tab
3837
+ const nextTab = attrs.tabs[currentIndex + 1];
3838
+ if (!nextTab.disabled && !nextTab.href) {
3839
+ const newTabId = createId(nextTab.title, nextTab.id);
3840
+ state.activeTabId = newTabId;
3841
+ if (attrs.onTabChange) {
3842
+ attrs.onTabChange(newTabId);
3843
+ }
3844
+ }
3845
+ }
3846
+ }
3847
+ state.isDragging = false;
3848
+ state.translateX = 0;
3849
+ // m.redraw();
3850
+ };
3851
+ /** Initialize active tab - selectedTabId takes precedence, next active property or first available tab */
3852
+ const setActiveTabId = (anchoredTabs, selectedTabId) => {
3853
+ const selectedTab = selectedTabId ? anchoredTabs.find((a) => a.tabId === selectedTabId) : undefined;
3854
+ if (selectedTab) {
3855
+ state.activeTabId = selectedTab.tabId;
3856
+ selectedTab.active = true;
3857
+ return selectedTab;
3858
+ }
3859
+ const activeTab = anchoredTabs.find((t) => t.active);
3860
+ if (activeTab) {
3861
+ // Active tab property takes precedence over selectedTabId
3862
+ state.activeTabId = activeTab.tabId;
3863
+ return activeTab;
3864
+ }
3865
+ // Default to first non-disabled tab
3866
+ const firstAvailableTab = anchoredTabs.find((t) => !t.disabled && !t.href);
3867
+ if (firstAvailableTab) {
3868
+ state.activeTabId = firstAvailableTab.tabId;
3869
+ firstAvailableTab.active = true;
3870
+ return firstAvailableTab;
3871
+ }
3872
+ return undefined;
3873
+ };
3874
+ return {
3875
+ oninit: ({ attrs }) => {
3876
+ const anchoredTabs = attrs.tabs.map(toAnchored());
3877
+ setActiveTabId(anchoredTabs, attrs.selectedTabId);
3878
+ },
3879
+ oncreate: () => {
3880
+ updateIndicator();
3881
+ m.redraw();
3882
+ },
3883
+ view: ({ attrs }) => {
3884
+ const { tabWidth, tabs, className, style, swipeable = false } = attrs;
3885
+ const cn = [tabWidth === 'fill' ? 'tabs-fixed-width' : '', className].filter(Boolean).join(' ').trim();
3886
+ const anchoredTabs = tabs.map(toAnchored());
3887
+ const activeTab = setActiveTabId(anchoredTabs, attrs.selectedTabId);
3888
+ updateIndicator();
3889
+ return m('.row', [
3890
+ // Tab headers
3891
+ m('.col.s12', m('ul.tabs', {
3892
+ className: cn,
3893
+ style,
3894
+ }, [
3895
+ ...anchoredTabs.map((tab) => {
3896
+ const { className: tabClassName, title, anchorId, tabId, disabled, target, href } = tab;
3897
+ const cn = ['tab', tabWidth === 'fixed' ? `col s${Math.floor(12 / tabs.length)}` : '', tabClassName]
3898
+ .filter(Boolean)
3899
+ .join(' ')
3900
+ .trim();
3901
+ return m('li', {
3902
+ key: tabId,
3903
+ id: tabId,
3904
+ className: cn,
3905
+ disabled,
3906
+ }, m('a', {
3907
+ id: anchorId,
3908
+ className: tab.active ? 'active' : undefined,
3909
+ target,
3910
+ href: href || `#${anchorId}`,
3911
+ onclick: disabled || href
3912
+ ? undefined
3913
+ : (e) => {
3914
+ e.preventDefault();
3915
+ handleTabClick(tabId, e.target, attrs);
3916
+ },
3917
+ style: disabled ? { opacity: '0.6', cursor: 'not-allowed' } : undefined,
3918
+ }, title));
3919
+ }),
3920
+ // Add the indicator element
3921
+ m('li.indicator', {
3922
+ key: 'indicator',
3923
+ style: {
3924
+ display: state.activeTabId ? 'block' : 'none',
3925
+ left: state.indicatorStyle.left,
3926
+ width: state.indicatorStyle.width,
3927
+ transition: 'left 0.35s ease, width 0.35s ease',
3928
+ },
3929
+ }),
3930
+ ]), activeTab &&
3931
+ m('.col.s12', {
3932
+ ontouchstart: swipeable ? handleTouchStart : undefined,
3933
+ ontouchend: swipeable ? (e) => handleTouchEnd(e, attrs) : undefined,
3934
+ style: swipeable ? { touchAction: 'pan-y' } : undefined,
3935
+ }, m('.tab-content', {
3936
+ className: activeTab.contentClass,
3937
+ }, activeTab.vnode))),
3938
+ ]);
3939
+ },
3940
+ };
3941
+ };
3942
+
3943
+ /**
3944
+ * Mithril Factory Component for Multi-Select Dropdown with search
3945
+ */
3946
+ const SearchSelect = () => {
3947
+ // (): <T extends string | number>(): Component<SearchSelectAttrs<T>, SearchSelectState<T>> => {
3948
+ // State initialization
3949
+ const state = {
3950
+ isOpen: false,
3951
+ selectedOptions: [], //options.filter((o) => iv.includes(o.id)),
3952
+ searchTerm: '',
3953
+ options: [],
3954
+ inputRef: null,
3955
+ dropdownRef: null,
3956
+ focusedIndex: -1,
3957
+ onchange: null,
3958
+ };
3959
+ const componentId = uniqueId();
3960
+ const searchInputId = `${componentId}-search`;
3961
+ // Handle click outside
3962
+ const handleClickOutside = (e) => {
3963
+ const target = e.target;
3964
+ if (state.dropdownRef && state.dropdownRef.contains(target)) {
3965
+ // Click inside dropdown, do nothing
3966
+ return;
3967
+ }
3968
+ if (state.inputRef && state.inputRef.contains(target)) {
3969
+ // Click on trigger handled by onclick event
3970
+ return;
3971
+ }
3972
+ else {
3973
+ // Click outside, close dropdown
3974
+ state.isOpen = false;
3975
+ m.redraw();
3976
+ }
3977
+ };
3978
+ // Handle keyboard navigation
3979
+ const handleKeyDown = (e, filteredOptions, showAddNew) => {
3980
+ if (!state.isOpen)
3981
+ return;
3982
+ const totalOptions = filteredOptions.length + (showAddNew ? 1 : 0);
3983
+ switch (e.key) {
3984
+ case 'ArrowDown':
3985
+ e.preventDefault();
3986
+ state.focusedIndex = Math.min(state.focusedIndex + 1, totalOptions - 1);
3987
+ break;
3988
+ case 'ArrowUp':
3989
+ e.preventDefault();
3990
+ state.focusedIndex = Math.max(state.focusedIndex - 1, -1);
3991
+ break;
3992
+ case 'Enter':
3993
+ e.preventDefault();
3994
+ if (state.focusedIndex >= 0) {
3995
+ if (showAddNew && state.focusedIndex === filteredOptions.length) {
3996
+ // Handle add new option
3997
+ return 'addNew';
3998
+ }
3999
+ else if (state.focusedIndex < filteredOptions.length) {
4000
+ toggleOption(filteredOptions[state.focusedIndex]);
4001
+ }
4002
+ }
4003
+ break;
4004
+ case 'Escape':
4005
+ e.preventDefault();
4006
+ state.isOpen = false;
4007
+ state.focusedIndex = -1;
4008
+ break;
4009
+ }
4010
+ return null;
4011
+ };
4012
+ // Toggle option selection
4013
+ const toggleOption = (option) => {
4014
+ if (option.disabled)
4015
+ return;
4016
+ state.selectedOptions = state.selectedOptions.some((item) => item.id === option.id)
4017
+ ? state.selectedOptions.filter((item) => item.id !== option.id)
4018
+ : [...state.selectedOptions, option];
4019
+ state.searchTerm = '';
4020
+ state.focusedIndex = -1;
4021
+ state.onchange && state.onchange(state.selectedOptions.map((o) => o.id));
4022
+ };
4023
+ // Remove a selected option
4024
+ const removeOption = (option) => {
4025
+ state.selectedOptions = state.selectedOptions.filter((item) => item.id !== option.id);
4026
+ state.onchange && state.onchange(state.selectedOptions.map((o) => o.id));
4027
+ };
4028
+ return {
4029
+ oninit: ({ attrs: { options = [], initialValue = [], onchange } }) => {
4030
+ state.options = options;
4031
+ state.selectedOptions = options.filter((o) => initialValue.includes(o.id));
4032
+ state.onchange = onchange;
4033
+ },
4034
+ oncreate() {
4035
+ document.addEventListener('click', handleClickOutside);
4036
+ },
4037
+ onremove() {
4038
+ document.removeEventListener('click', handleClickOutside);
4039
+ },
4040
+ view({ attrs: {
4041
+ // onchange,
4042
+ oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label,
4043
+ // maxHeight = '25rem',
4044
+ }, }) {
4045
+ // Safely filter options
4046
+ const filteredOptions = state.options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
4047
+ !state.selectedOptions.some((selected) => selected.id === option.id));
4048
+ // Check if we should show the "add new option" element
4049
+ const showAddNew = oncreateNewOption &&
4050
+ state.searchTerm &&
4051
+ !filteredOptions.some((o) => (o.label || o.id.toString()).toLowerCase() === state.searchTerm.toLowerCase());
4052
+ // Render the dropdown
4053
+ return m('.input-field.multi-select-dropdown', { className }, [
4054
+ m('.chips.chips-initial.chips-container', {
4055
+ oncreate: ({ dom }) => {
4056
+ state.inputRef = dom;
4057
+ },
4058
+ onclick: (e) => {
4059
+ // console.log('SearchSelect clicked', state.isOpen, e); // Debug log
4060
+ e.preventDefault();
4061
+ e.stopPropagation();
4062
+ state.isOpen = !state.isOpen;
4063
+ // console.log('SearchSelect state changed to', state.isOpen); // Debug log
4064
+ },
4065
+ style: {
4066
+ display: 'flex',
4067
+ alignItems: 'end',
4068
+ flexWrap: 'wrap',
4069
+ cursor: 'pointer',
4070
+ position: 'relative',
4071
+ },
4072
+ }, [
4073
+ // TODO FIXME Add to existing input
4074
+ // Hidden input for label association and accessibility
4075
+ m('input', {
4076
+ type: 'text',
4077
+ id: componentId,
4078
+ value: state.selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
4079
+ readonly: true,
4080
+ style: { position: 'absolute', left: '-9999px', opacity: 0 },
4081
+ }),
4082
+ // Selected Options (chips)
4083
+ ...state.selectedOptions.map((option) => m('.chip', [
4084
+ option.label || option.id.toString(),
4085
+ m(MaterialIcon, {
4086
+ name: 'close',
4087
+ className: 'close',
4088
+ onclick: (e) => {
4089
+ e.stopPropagation();
4090
+ removeOption(option);
4091
+ },
4092
+ }),
4093
+ ])),
4094
+ // Placeholder when no options selected
4095
+ state.selectedOptions.length === 0 &&
4096
+ placeholder &&
4097
+ m('span.placeholder', {
4098
+ style: {
4099
+ color: '#9e9e9e',
4100
+ flexGrow: 1,
4101
+ padding: '8px 0',
4102
+ },
4103
+ }, placeholder),
4104
+ // Spacer to push caret to the right
4105
+ m('span.spacer', { style: { flexGrow: 1 } }),
4106
+ m(MaterialIcon, {
4107
+ name: 'caret',
4108
+ direction: state.isOpen ? 'up' : 'down',
4109
+ style: { marginLeft: 'auto', cursor: 'pointer' },
4110
+ }),
4111
+ ]),
4112
+ // Label
4113
+ label &&
4114
+ m('label', {
4115
+ for: componentId,
4116
+ class: placeholder || state.selectedOptions.length > 0 ? 'active' : '',
4117
+ }, label),
4118
+ // Dropdown Menu
4119
+ state.isOpen &&
4120
+ m('ul.dropdown-content.select-dropdown', {
4121
+ oncreate: ({ dom }) => {
4122
+ state.dropdownRef = dom;
4123
+ },
4124
+ onremove: () => {
4125
+ state.dropdownRef = null;
4126
+ },
4127
+ style: getDropdownStyles(state.inputRef),
4128
+ }, [
4129
+ m('li', // Search Input
4130
+ {
4131
+ class: 'search-wrapper',
4132
+ style: { padding: '0 16px', position: 'relative' },
4133
+ }, [
4134
+ m('input', {
4135
+ type: 'text',
4136
+ id: searchInputId,
4137
+ placeholder: searchPlaceholder,
4138
+ value: state.searchTerm || '',
4139
+ oncreate: ({ dom }) => {
4140
+ // Auto-focus the search input when dropdown opens
4141
+ dom.focus();
4142
+ },
4143
+ oninput: (e) => {
4144
+ state.searchTerm = e.target.value;
4145
+ state.focusedIndex = -1; // Reset focus when typing
4146
+ },
4147
+ onkeydown: async (e) => {
4148
+ const result = handleKeyDown(e, filteredOptions, !!showAddNew);
4149
+ if (result === 'addNew' && oncreateNewOption) {
4150
+ const option = await oncreateNewOption(state.searchTerm);
4151
+ toggleOption(option);
4152
+ }
4153
+ },
4154
+ style: {
4155
+ width: '100%',
4156
+ outline: 'none',
4157
+ fontSize: '0.875rem',
4158
+ border: 'none',
4159
+ padding: '8px 0',
4160
+ borderBottom: '1px solid #9e9e9e',
4161
+ },
4162
+ }),
4163
+ ]),
4164
+ // No options found message or list of options
4165
+ ...(filteredOptions.length === 0 && !showAddNew
4166
+ ? [
4167
+ m('li',
4168
+ // {
4169
+ // style: getNoOptionsStyles(),
4170
+ // },
4171
+ noOptionsFound),
4172
+ ]
4173
+ : []),
4174
+ // Add new option item
4175
+ ...(showAddNew
4176
+ ? [
4177
+ m('li', {
4178
+ onclick: async () => {
4179
+ const option = await oncreateNewOption(state.searchTerm);
4180
+ toggleOption(option);
4181
+ },
4182
+ class: state.focusedIndex === filteredOptions.length ? 'active' : '',
4183
+ onmouseover: () => {
4184
+ state.focusedIndex = filteredOptions.length;
4185
+ },
4186
+ }, [m('span', `+ "${state.searchTerm}"`)]),
4187
+ ]
4188
+ : []),
4189
+ // List of filtered options
4190
+ ...filteredOptions.map((option, index) => m('li', {
4191
+ onclick: (e) => {
4192
+ e.preventDefault();
4193
+ e.stopPropagation();
4194
+ toggleOption(option);
4195
+ },
4196
+ class: `${option.disabled ? 'disabled' : ''} ${state.focusedIndex === index ? 'active' : ''}`.trim(),
4197
+ onmouseover: () => {
4198
+ if (!option.disabled) {
4199
+ state.focusedIndex = index;
4200
+ }
4201
+ },
4202
+ }, m('span', [
4203
+ m('input', {
4204
+ type: 'checkbox',
4205
+ checked: state.selectedOptions.some((selected) => selected.id === option.id),
4206
+ }),
4207
+ option.label || option.id.toString(),
4208
+ ]))),
4209
+ ]),
4210
+ ]);
4211
+ },
4212
+ };
4213
+ };
4214
+
4215
+ exports.AnchorItem = AnchorItem;
4216
+ exports.Autocomplete = Autocomplete;
4217
+ exports.Button = Button;
4218
+ exports.ButtonFactory = ButtonFactory;
4219
+ exports.Carousel = Carousel;
4220
+ exports.CharacterCounter = CharacterCounter;
4221
+ exports.Chips = Chips;
4222
+ exports.CodeBlock = CodeBlock;
4223
+ exports.Collapsible = Collapsible;
4224
+ exports.CollapsibleItem = CollapsibleItem;
4225
+ exports.Collection = Collection;
4226
+ exports.ColorInput = ColorInput;
4227
+ exports.DatePicker = DatePicker;
4228
+ exports.Dropdown = Dropdown;
4229
+ exports.EmailInput = EmailInput;
4230
+ exports.FileInput = FileInput;
4231
+ exports.FlatButton = FlatButton;
4232
+ exports.FloatingActionButton = FloatingActionButton;
4233
+ exports.HelperText = HelperText;
4234
+ exports.Icon = Icon;
4235
+ exports.InputCheckbox = InputCheckbox;
4236
+ exports.Label = Label;
4237
+ exports.LargeButton = LargeButton;
4238
+ exports.ListItem = ListItem;
4239
+ exports.Mandatory = Mandatory;
4240
+ exports.MaterialBox = MaterialBox;
4241
+ exports.ModalPanel = ModalPanel;
4242
+ exports.NumberInput = NumberInput;
4243
+ exports.Options = Options;
4244
+ exports.Pagination = Pagination;
4245
+ exports.Parallax = Parallax;
4246
+ exports.PasswordInput = PasswordInput;
4247
+ exports.RadioButton = RadioButton;
4248
+ exports.RadioButtons = RadioButtons;
4249
+ exports.RangeInput = RangeInput;
4250
+ exports.RoundIconButton = RoundIconButton;
4251
+ exports.SearchSelect = SearchSelect;
4252
+ exports.SecondaryContent = SecondaryContent;
4253
+ exports.Select = Select;
4254
+ exports.SmallButton = SmallButton;
4255
+ exports.SubmitButton = SubmitButton;
4256
+ exports.Switch = Switch;
4257
+ exports.Tabs = Tabs;
4258
+ exports.TextArea = TextArea;
4259
+ exports.TextInput = TextInput;
4260
+ exports.TimePicker = TimePicker;
4261
+ exports.UrlInput = UrlInput;
4262
+ exports.getDropdownStyles = getDropdownStyles;
4263
+ exports.isNumeric = isNumeric;
4264
+ exports.padLeft = padLeft;
4265
+ exports.range = range;
4266
+ exports.uniqueId = uniqueId;
4267
+ exports.uuid4 = uuid4;
4268
+
4269
+ }));