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