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