diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7392f2b..63428f6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,14 +5,21 @@ on: pull_request: +env: + RAILS_ENV: test + jobs: build: runs-on: ubuntu-latest - name: Ruby ${{ matrix.ruby }} + name: Ruby ${{ matrix.ruby }} / ${{ matrix.asset_pipeline }} strategy: + fail-fast: false matrix: ruby: - '3.4.4' + asset_pipeline: + - sprockets + - propshaft steps: - uses: actions/checkout@v4 @@ -23,9 +30,19 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true + env: + BUNDLE_GEMFILE: gemfiles/${{ matrix.asset_pipeline }}.gemfile + - name: Build Propshaft assets + if: matrix.asset_pipeline == 'propshaft' + run: bundle exec rails dartsass:build + working-directory: spec/dummy + env: + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.asset_pipeline }}.gemfile - name: Set up Chrome uses: browser-actions/setup-chrome@v1 with: chrome-version: stable - name: Run the default task run: bundle exec rake + env: + BUNDLE_GEMFILE: gemfiles/${{ matrix.asset_pipeline }}.gemfile diff --git a/.gitignore b/.gitignore index fea7040..b91d768 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,15 @@ /coverage/ /doc/ /Gemfile.lock +*gemfile.lock /pkg/ /spec/reports/ +/spec/dummy/app/assets/builds/ tmp/ *.log +node_modules/ +yarn.lock +package-lock.json # Database files *.sqlite3 diff --git a/app/assets/javascripts/active_admin/activeadmin_slimselect.js b/app/assets/javascripts/active_admin/activeadmin_slimselect.js new file mode 100644 index 0000000..85e308d --- /dev/null +++ b/app/assets/javascripts/active_admin/activeadmin_slimselect.js @@ -0,0 +1 @@ +!function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";class e{main;placeholder;values;single;max;value;valueText;valueDelete;valueOut;deselect;deselectPath;arrow;arrowClose;arrowOpen;content;contentOpen;dirAbove;dirBelow;search;searchHighlighter;searching;addable;addablePath;list;optgroup;optgroupLabel;optgroupLabelText;optgroupActions;optgroupSelectAll;optgroupSelectAllBox;optgroupSelectAllCheck;optgroupClosable;option;optionDelete;highlighted;mainOpen;close;selected;error;disabled;hide;constructor(e){e||(e={});let t=(e="",t="")=>`${e} ${t}`.trim();this.main=t("ss-main",e.main),this.placeholder=t("ss-placeholder",e.placeholder),this.values=t("ss-values",e.values),this.single=t("ss-single",e.single),this.max=t("ss-max",e.max),this.value=t("ss-value",e.value),this.valueText=t("ss-value-text",e.valueText),this.valueDelete=t("ss-value-delete",e.valueDelete),this.valueOut=t("ss-value-out",e.valueOut),this.deselect=t("ss-deselect",e.deselect),this.deselectPath=e.deselectPath||"M10,10 L90,90 M10,90 L90,10",this.arrow=t("ss-arrow",e.arrow),this.arrowClose=e.arrowClose||"M10,30 L50,70 L90,30",this.arrowOpen=e.arrowOpen||"M10,70 L50,30 L90,70",this.content=t("ss-content",e.content),this.contentOpen=t("ss-open",e.contentOpen),this.dirAbove=t("ss-dir-above",e.dirAbove),this.dirBelow=t("ss-dir-below",e.dirBelow),this.search=t("ss-search",e.search),this.searchHighlighter=t("ss-search-highlight",e.searchHighlighter),this.searching=t("ss-searching",e.searching),this.addable=t("ss-addable",e.addable),this.addablePath=e.addablePath||"M50,10 L50,90 M10,50 L90,50",this.list=t("ss-list",e.list),this.optgroup=t("ss-optgroup",e.optgroup),this.optgroupLabel=t("ss-optgroup-label",e.optgroupLabel),this.optgroupLabelText=t("ss-optgroup-label-text",e.optgroupLabelText),this.optgroupActions=t("ss-optgroup-actions",e.optgroupActions),this.optgroupSelectAll=t("ss-selectall",e.optgroupSelectAll),this.optgroupSelectAllBox=e.optgroupSelectAllBox||"M60,10 L10,10 L10,90 L90,90 L90,50",this.optgroupSelectAllCheck=e.optgroupSelectAllCheck||"M30,45 L50,70 L90,10",this.optgroupClosable=t("ss-closable",e.optgroupClosable),this.option=t("ss-option",e.option),this.optionDelete=e.optionDelete||"M10,10 L90,90 M10,90 L90,10",this.highlighted=t("ss-highlighted",e.highlighted),this.mainOpen=t("ss-open",e.mainOpen),this.close=t("ss-close",e.close),this.selected=t("ss-selected",e.selected),this.error=t("ss-error",e.error),this.disabled=t("ss-disabled",e.disabled),this.hide=t("ss-hide",e.hide)}getFirst(e){return this[e].split(" ")[0]}}function t(){return Math.random().toString(36).substring(2,10)}function s(e,t){function s(e,s){return s&&e&&e.classList&&e.classList.contains(s)||s&&e&&e.dataset&&e.dataset.id&&e.dataset.id===t?e:null}return s(e,t)||function e(t,i){return t&&t!==document?s(t,i)?t:e(t.parentNode,i):null}(e,t)}function i(e,t=50,s=!1){let i;return function(...n){const a=self,l=s&&!i;clearTimeout(i),i=setTimeout(()=>{i=null,s||e.apply(a,n)},t),l&&e.apply(a,n)}}function n(e,t){return JSON.stringify(e)===JSON.stringify(t)}class a{id;value;text;html;defaultSelected;selected;display;disabled;placeholder;class;style;data;mandatory;constructor(e){this.id=e.id&&""!==e.id?e.id:t(),this.value=void 0===e.value?e.text||"":e.value||"",this.text=e.text||"",this.html=e.html||"",this.defaultSelected=void 0!==e.defaultSelected&&e.defaultSelected,this.selected=void 0!==e.selected&&e.selected,this.display=void 0===e.display||e.display,this.disabled=void 0!==e.disabled&&e.disabled,this.mandatory=void 0!==e.mandatory&&e.mandatory,this.placeholder=void 0!==e.placeholder&&e.placeholder,this.class=e.class||"",this.style=e.style||"",this.data=e.data||{}}}class l{id;label;selectAll;selectAllText;closable;options;constructor(e){if(this.id=e.id&&""!==e.id?e.id:t(),this.label=e.label||"",this.selectAll=void 0!==e.selectAll&&e.selectAll,this.selectAllText=e.selectAllText||"Select All",this.closable=e.closable||"off",this.options=[],e.options)for(const t of e.options)this.options.push(new a(t))}}class o{selectType="single";data=[];selectedOrder=[];constructor(e,t){this.selectType=e,this.setData(t)}validateDataArray(e){if(!Array.isArray(e))return new Error("Data must be an array");for(let t of e)if(t)if(t instanceof l||"label"in t){if(!("label"in t))return new Error("Optgroup must have a label");if("options"in t&&t.options)for(let e of t.options){const t=this.validateOption(e);if(t)return t}}else{if(!(t instanceof a||"text"in t))return new Error("Data object must be a valid optgroup or option");{const e=this.validateOption(t);if(e)return e}}return null}validateOption(e){return"text"in e?null:new Error("Option must have a text")}partialToFullData(e){let t=[];return e.forEach(e=>{if(e){if(e instanceof l||"label"in e){let s=[];"options"in e&&e.options&&e.options.forEach(e=>{s.push(new a(e))}),s.length>0&&t.push(new l(e))}(e instanceof a||"text"in e)&&t.push(new a(e))}}),t}setData(e,t=!1){const s=this.partialToFullData(e);if(t){const e=this.getSelectedOptions(),t=[];e.forEach(e=>{let i=!1;for(const t of s){if(t instanceof a&&t.id===e.id){i=!0;break}if(t instanceof l)for(const s of t.options)if(s.id===e.id){i=!0;break}}i||t.push(e)}),this.data=[...t,...s]}else this.data=s;"single"===this.selectType&&this.setSelectedBy("id",this.getSelected())}getData(){return this.filter(null,!0)}getDataOptions(){return this.filter(null,!1)}addOption(e,t=!1){if(t){let t=[new a(e)];this.setData(t.concat(this.getData()))}else this.setData(this.getData().concat(new a(e)))}setSelectedBy(e,t){let s=null,i=!1;const n=[];for(let o of this.data){if(o instanceof l)for(let a of o.options){s||(s=a);let l=a[e]||"";a.selected=!i&&t.includes(l),a.selected&&(n.push(a),"single"===this.selectType&&(i=!0))}o instanceof a&&(s||(s=o),o.selected=!i&&t.includes(o[e]),o.selected&&(n.push(o),"single"===this.selectType&&(i=!0)))}"single"===this.selectType&&s&&!i&&(s.selected=!0,n.push(s));const o=t.map(t=>n.find(s=>s[e]===t)?.id||"");this.selectedOrder=o}getSelected(){return this.getSelectedOptions().map(e=>e.id)}getSelectedValues(){return this.getSelectedOptions().map(e=>e.value)}getSelectedOptions(){return this.filter(e=>e.selected,!1)}getOptgroupByID(e){for(let t of this.data)if(t instanceof l&&t.id===e)return t;return null}getOptionByID(e){let t=this.filter(t=>t.id===e,!1);return t.length?t[0]:null}getSelectType(){return this.selectType}getFirstOption(){let e=null;for(let t of this.data)if(t instanceof l?e=t.options[0]:t instanceof a&&(e=t),e)break;return e}search(e,t){return""===(e=e.trim())?this.getData():this.filter(s=>t(s,e),!0)}filter(e,t){const s=[];return this.data.forEach(i=>{if(i instanceof l){let n=[];if(i.options.forEach(i=>{(!e||e(i))&&(t?n.push(new a(i)):s.push(new a(i)))}),n.length>0){let e=new l(i);e.options=n,s.push(e)}}i instanceof a&&(!e||e(i))&&s.push(new a(i))}),s}selectedOrderOptions(e){const t=[];return this.selectedOrder.forEach(s=>{const i=e.find(e=>e.id===s);i&&t.push(i)}),e.forEach(e=>{let s=!1;t.forEach(t=>{e.id!==t.id||(s=!0)}),s||t.push(e)}),t}}class h{settings;store;callbacks;lastSelectedOption;closeAnimationTimeout=null;main;content;classes;constructor(e,t,s,i){this.store=s,this.settings=e,this.classes=t,this.callbacks=i,this.lastSelectedOption=null,this.main=this.mainDiv(),this.content=this.contentDiv(),this.updateClassStyles(),this.updateAriaAttributes(),"relative"!==this.settings.contentPosition&&(this.content.main.style.top="-9999px",this.content.main.style.left="-9999px",this.content.main.style.margin="0",this.content.main.style.width="auto"),this.settings.contentLocation&&this.settings.contentLocation.appendChild(this.content.main)}addClasses(e,t){if(!t||""===t.trim())return;const s=t.split(" ").filter(e=>""!==e.trim());for(const t of s)e.classList.add(t.trim())}removeClasses(e,t){if(!t||""===t.trim())return;const s=t.split(" ").filter(e=>""!==e.trim());for(const t of s)e.classList.remove(t.trim())}enable(){this.removeClasses(this.main.main,this.classes.disabled),this.main.main.setAttribute("aria-disabled","false"),this.content.search.input.disabled=!1}disable(){this.addClasses(this.main.main,this.classes.disabled),this.main.main.setAttribute("aria-disabled","true"),this.content.search.input.disabled=!0}open(){this.main.arrow.path.setAttribute("d",this.classes.arrowOpen),this.main.main.setAttribute("aria-expanded","true"),this.closeAnimationTimeout&&(clearTimeout(this.closeAnimationTimeout),this.closeAnimationTimeout=null);const e="up"===this.settings.openPosition?this.classes.dirAbove:this.classes.dirBelow;this.addClasses(this.main.main,e),this.addClasses(this.content.main,e),this.addClasses(this.content.main,this.classes.contentOpen),this.content.search.input.removeAttribute("aria-hidden"),this.moveContent();const t=this.store.getSelectedOptions();if(t.length){const e=t[t.length-1].id,s=this.content.list.querySelector('[data-id="'+e+'"]');s&&this.ensureElementInView(this.content.list,s)}}close(){this.main.main.setAttribute("aria-expanded","false"),this.main.arrow.path.setAttribute("d",this.classes.arrowClose),this.removeClasses(this.content.main,this.classes.contentOpen),this.content.search.input.setAttribute("aria-hidden","true"),this.main.main.removeAttribute("aria-activedescendant");const e=this.getAnimationTiming();this.closeAnimationTimeout=setTimeout(()=>{this.removeClasses(this.main.main,this.classes.dirAbove),this.removeClasses(this.main.main,this.classes.dirBelow),this.removeClasses(this.content.main,this.classes.dirAbove),this.removeClasses(this.content.main,this.classes.dirBelow),this.closeAnimationTimeout=null},e)}getAnimationTiming(){const e=getComputedStyle(this.content.main).getPropertyValue("--ss-animation-timing").trim();if(e){if(e.endsWith("ms"))return parseFloat(e);if(e.endsWith("s"))return 1e3*parseFloat(e)}return 200}updateClassStyles(){if(this.main.main.className="",this.main.main.removeAttribute("style"),this.content.main.className="",this.content.main.removeAttribute("style"),this.addClasses(this.main.main,this.classes.main),this.addClasses(this.content.main,this.classes.content),""!==this.settings.style&&(this.main.main.style.cssText=this.settings.style,this.content.main.style.cssText=this.settings.style),this.settings.class.length)for(const e of this.settings.class)""!==e.trim()&&(this.main.main.classList.add(e.trim()),this.content.main.classList.add(e.trim()));("relative"===this.settings.contentPosition||"fixed"===this.settings.contentPosition)&&this.content.main.classList.add("ss-"+this.settings.contentPosition)}updateAriaAttributes(){const e=this.content.list.id;this.main.main.role="combobox",this.main.main.setAttribute("aria-haspopup","listbox"),this.main.main.setAttribute("aria-controls",e),this.main.main.setAttribute("aria-expanded","false"),this.content.list.setAttribute("role","listbox"),this.content.list.setAttribute("aria-label",this.settings.ariaLabel+" listbox"),this.settings.isMultiple&&this.content.list.setAttribute("aria-multiselectable","true"),this.content.search.input.setAttribute("aria-controls",e)}mainDiv(){const e=document.createElement("div");e.dataset.id=this.settings.id,e.setAttribute("aria-label",this.settings.ariaLabel),e.tabIndex=0,e.onkeydown=e=>{switch(e.key){case"ArrowUp":case"ArrowDown":return this.callbacks.open(),"ArrowDown"===e.key?this.highlight("down"):this.highlight("up"),!1;case"Tab":return this.callbacks.close(),!0;case"Enter":case" ":this.callbacks.open();const t=this.content.list.querySelector("."+this.classes.getFirst("highlighted"));return t&&t.click(),!1;case"Escape":return this.callbacks.close(),!1}return 1===e.key.length&&this.callbacks.open(),!0},e.onclick=e=>{this.settings.disabled||(this.settings.isOpen?this.callbacks.close():this.callbacks.open())};const t=document.createElement("div");this.addClasses(t,this.classes.values),e.appendChild(t);const s=document.createElement("div");this.addClasses(s,this.classes.deselect);const i=this.store?.getSelectedOptions();!this.settings.allowDeselect||this.settings.isMultiple&&i&&i.length<=0?this.addClasses(s,this.classes.hide):this.removeClasses(s,this.classes.hide),s.onclick=e=>{if(e.stopPropagation(),this.settings.disabled)return;let t=!0;const s=this.store.getSelectedOptions();if(this.callbacks.beforeChange&&(t=!0===this.callbacks.beforeChange([],s)),t){if(this.settings.isMultiple)this.callbacks.setSelected([],!1),this.updateDeselectAll();else{const e=this.store.getFirstOption(),t=e?e.id:"";this.callbacks.setSelected(t,!1)}this.settings.closeOnSelect&&this.callbacks.close(),this.callbacks.afterChange&&this.callbacks.afterChange(this.store.getSelectedOptions())}};const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("viewBox","0 0 100 100");const a=document.createElementNS("http://www.w3.org/2000/svg","path");a.setAttribute("d",this.classes.deselectPath),n.appendChild(a),s.appendChild(n),e.appendChild(s);const l=document.createElementNS("http://www.w3.org/2000/svg","svg");this.addClasses(l,this.classes.arrow),l.setAttribute("viewBox","0 0 100 100");const o=document.createElementNS("http://www.w3.org/2000/svg","path");return o.setAttribute("d",this.classes.arrowClose),this.settings.alwaysOpen&&this.addClasses(l,this.classes.hide),l.appendChild(o),e.appendChild(l),{main:e,values:t,deselect:{main:s,svg:n,path:a},arrow:{main:l,path:o}}}mainFocus(e){"click"!==e&&this.main.main.focus({preventScroll:!0})}placeholder(){const e=this.store.filter(e=>e.placeholder,!1);let t=this.settings.placeholderText;e.length&&(""!==e[0].html?t=e[0].html:""!==e[0].text&&(t=e[0].text));const s=document.createElement("div");return this.addClasses(s,this.classes.placeholder),s.innerHTML=t,s}renderValues(){this.settings.isMultiple?(this.renderMultipleValues(),this.updateDeselectAll()):this.renderSingleValue()}renderSingleValue(){const e=this.store.filter(e=>e.selected&&!e.placeholder,!1),t=e.length>0?e[0]:null;if(t){const e=document.createElement("div");this.addClasses(e,this.classes.single),t.html?e.innerHTML=t.html:e.innerText=t.text,this.main.values.innerHTML=e.outerHTML}else this.main.values.innerHTML=this.placeholder().outerHTML;this.settings.allowDeselect&&e.length?this.removeClasses(this.main.deselect.main,this.classes.hide):this.addClasses(this.main.deselect.main,this.classes.hide)}renderMultipleValues(){let e=this.main.values.childNodes,t=this.store.filter(e=>e.selected&&e.display,!1);if(0===t.length)return void(this.main.values.innerHTML=this.placeholder().outerHTML);{const e=this.main.values.querySelector("."+this.classes.getFirst("placeholder"));e&&e.remove()}if(t.length>this.settings.maxValuesShown){const e=document.createElement("div");return this.addClasses(e,this.classes.max),e.textContent=this.settings.maxValuesMessage.replace("{number}",t.length.toString()),void(this.main.values.innerHTML=e.outerHTML)}{const e=this.main.values.querySelector("."+this.classes.getFirst("max"));e&&e.remove()}this.settings.keepOrder&&(t=this.store.selectedOrderOptions(t));let s=[];for(let i=0;ie.id===a,!1).length||s.push(n))}for(const e of s)this.addClasses(e,this.classes.valueOut),setTimeout(()=>{this.main.values.hasChildNodes()&&this.main.values.contains(e)&&this.main.values.removeChild(e)},100);e=this.main.values.childNodes;for(let s=0;s{if(t.preventDefault(),t.stopPropagation(),this.settings.disabled)return;let s=!0;const i=this.store.getSelectedOptions(),n=i.filter(t=>t.selected&&t.id!==e.id,!0);if(!(this.settings.minSelected&&n.length{"Enter"===e.key&&s.click()}}return t}contentDiv(){const e=document.createElement("div");e.dataset.id=this.settings.id;const t=this.searchDiv();e.appendChild(t.main);const s=this.listDiv();return e.appendChild(s),{main:e,search:t,list:s}}moveContent(){"relative"!==this.settings.contentPosition&&"down"!==this.settings.openPosition?"up"!==this.settings.openPosition?"up"===this.putContent()?this.moveContentAbove():this.moveContentBelow():this.moveContentAbove():this.moveContentBelow()}searchDiv(){const e=document.createElement("div"),t=document.createElement("input"),s=document.createElement("div");this.addClasses(e,this.classes.search);const n={main:e,input:t};if(this.settings.showSearch||(this.addClasses(e,this.classes.hide),t.readOnly=!0),t.type="search",t.placeholder=this.settings.searchPlaceholder,t.tabIndex=-1,t.setAttribute("aria-label",this.settings.searchPlaceholder),t.setAttribute("aria-autocomplete","list"),t.setAttribute("autocapitalize","off"),t.setAttribute("autocomplete","off"),t.setAttribute("autocorrect","off"),t.setAttribute("aria-hidden","true"),t.oninput=i(e=>{this.callbacks.search(e.target.value)},100),t.onkeydown=e=>{switch(e.key){case"ArrowUp":case"ArrowDown":return"ArrowDown"===e.key?this.highlight("down"):this.highlight("up"),!1;case"Tab":return this.callbacks.close(),!0;case"Escape":return this.callbacks.close(),!1;case" ":const t=this.content.list.querySelector("."+this.classes.getFirst("highlighted"));return!t||(t.click(),!1);case"Enter":const i=this.content.list.querySelector("."+this.classes.getFirst("highlighted"));return i?(i.click(),!1):!this.callbacks.addable||(s.click(),!1)}return!0},e.appendChild(t),this.callbacks.addable){this.addClasses(s,this.classes.addable);const t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.setAttribute("viewBox","0 0 100 100");const i=document.createElementNS("http://www.w3.org/2000/svg","path");i.setAttribute("d",this.classes.addablePath),t.appendChild(i),s.appendChild(t),s.onclick=e=>{if(e.preventDefault(),e.stopPropagation(),!this.callbacks.addable)return;const t=this.content.search.input.value.trim();if(""===t)return void this.content.search.input.focus();const s=e=>{let t=new a(e);if(this.callbacks.addOption(t),this.settings.isMultiple){let e=this.store.getSelected();e.push(t.id),this.callbacks.setSelected(e,!0)}else this.callbacks.setSelected([t.id],!0);this.callbacks.search(""),this.settings.closeOnSelect&&setTimeout(()=>{this.callbacks.close()},100)},i=this.callbacks.addable(t);!1===i||null==i||(i instanceof Promise?i.then(e=>{"string"==typeof e?s({text:e,value:e}):i instanceof Error?this.renderError(i.message):s(e)}):"string"==typeof i?s({text:i,value:i}):i instanceof Error?this.renderError(i.message):s(i))},e.appendChild(s),n.addable={main:s,svg:t,path:i}}return n}searchFocus(){this.content.search.input.focus({preventScroll:!0})}getOptions(e=!1,t=!1,s=!1){let i="."+this.classes.getFirst("option");return e&&(i+=":not(."+this.classes.getFirst("placeholder")+")"),t&&(i+=":not(."+this.classes.getFirst("disabled")+")"),s&&(i+=":not(."+this.classes.getFirst("hide")+")"),Array.from(this.content.list.querySelectorAll(i))}highlight(e){const t=this.getOptions(!0,!0,!0);if(0===t.length)return;if(1===t.length&&!t[0].classList.contains(this.classes.getFirst("highlighted")))return void this.addClasses(t[0],this.classes.highlighted);let s=!1;for(const e of t)e.classList.contains(this.classes.getFirst("highlighted"))&&(s=!0);if(!s)for(const e of t)if(e.classList.contains(this.classes.getFirst("selected"))){this.addClasses(e,this.classes.highlighted);break}for(let s=0;s=0?s-1:t.length-1];this.addClasses(a,this.classes.highlighted),this.ensureElementInView(this.content.list,a),a.id&&this.main.main.setAttribute("aria-activedescendant",a.id);const l=a.parentElement;if(l&&l.classList.contains(this.classes.getFirst("close"))){const e=l.querySelector("."+this.classes.getFirst("optgroupLabel"));e&&e.click()}return}const i=t["down"===e?0:t.length-1];this.addClasses(i,this.classes.highlighted),i.id&&this.main.main.setAttribute("aria-activedescendant",i.id),this.ensureElementInView(this.content.list,i)}listDiv(){const e=document.createElement("div");this.addClasses(e,this.classes.list);const t=this.settings.id+"-list";return e.id=t,e.dataset.id=t,e}renderError(e){this.content.list.innerHTML="";const t=document.createElement("div");this.addClasses(t,this.classes.error),t.textContent=e,this.content.list.appendChild(t)}renderSearching(){this.content.list.innerHTML="";const e=document.createElement("div");this.addClasses(e,this.classes.searching),e.textContent=this.settings.searchingText,this.content.list.appendChild(e)}renderOptions(e){if(this.content.list.innerHTML="",0===e.length){const e=document.createElement("div");return this.addClasses(e,this.classes.search),this.callbacks.addable?e.innerHTML=this.settings.addableText.replace("{value}",this.content.search.input.value):e.innerHTML=this.settings.searchText,void this.content.list.appendChild(e)}this.settings.allowDeselect&&!this.settings.isMultiple&&(this.store.filter(e=>e.placeholder,!1).length||this.store.addOption(new a({text:"",value:"",selected:!1,placeholder:!0}),!0));const t=document.createDocumentFragment();for(const s of e){if(s instanceof l){const e=document.createElement("div");this.addClasses(e,this.classes.optgroup);const i=document.createElement("div");this.addClasses(i,this.classes.optgroupLabel),e.appendChild(i);const n=document.createElement("div");this.addClasses(n,this.classes.optgroupLabelText),n.textContent=s.label,i.appendChild(n);const l=document.createElement("div");if(this.addClasses(l,this.classes.optgroupActions),i.appendChild(l),this.settings.isMultiple&&s.selectAll){const e=document.createElement("div");this.addClasses(e,this.classes.optgroupSelectAll);let t=!0;for(const e of s.options)if(!e.selected){t=!1;break}t&&this.addClasses(e,this.classes.selected);const i=document.createElement("span");i.textContent=s.selectAllText,e.appendChild(i);const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("viewBox","0 0 100 100"),e.appendChild(n);const o=document.createElementNS("http://www.w3.org/2000/svg","path");o.setAttribute("d",this.classes.optgroupSelectAllBox),n.appendChild(o);const h=document.createElementNS("http://www.w3.org/2000/svg","path");h.setAttribute("d",this.classes.optgroupSelectAllCheck),n.appendChild(h),e.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation();const i=this.store.getSelected();if(t){const e=i.filter(e=>{for(const t of s.options)if(e===t.id)return!1;return!0});return void this.callbacks.setSelected(e,!0)}{let e=s.options.map(e=>e.id).filter(e=>void 0!==e);const t=i.concat(e);for(const e of s.options)e.id&&!this.store.getOptionByID(e.id)&&this.callbacks.addOption(new a(e));return void this.callbacks.setSelected(t,!0)}}),l.appendChild(e)}if("off"!==s.closable){const t=document.createElement("div");this.addClasses(t,this.classes.optgroupClosable);const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("viewBox","0 0 100 100"),this.addClasses(n,this.classes.arrow),t.appendChild(n);const a=document.createElementNS("http://www.w3.org/2000/svg","path");n.appendChild(a),s.options.some(e=>e.selected)||""!==this.content.search.input.value.trim()?(this.addClasses(t,this.classes.mainOpen),a.setAttribute("d",this.classes.arrowOpen)):"open"===s.closable?(this.addClasses(e,this.classes.mainOpen),a.setAttribute("d",this.classes.arrowOpen)):"close"===s.closable&&(this.addClasses(e,this.classes.close),a.setAttribute("d",this.classes.arrowClose)),i.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation(),e.classList.contains(this.classes.getFirst("close"))?(this.removeClasses(e,this.classes.close),this.addClasses(e,this.classes.mainOpen),a.setAttribute("d",this.classes.arrowOpen)):(this.removeClasses(e,this.classes.mainOpen),this.addClasses(e,this.classes.close),a.setAttribute("d",this.classes.arrowClose))}),l.appendChild(t)}e.appendChild(i);for(const i of s.options)e.appendChild(this.option(new a(i))),t.appendChild(e)}s instanceof a&&t.appendChild(this.option(s))}this.content.list.appendChild(t)}option(e){if(e.placeholder){const e=document.createElement("div");return this.addClasses(e,this.classes.option),this.addClasses(e,this.classes.hide),e}const t=document.createElement("div");return t.dataset.id=e.id,t.id=this.settings.id+"-"+e.id,this.addClasses(t,this.classes.option),t.setAttribute("role","option"),e.class&&e.class.split(" ").forEach(e=>{t.classList.add(e)}),e.style&&(t.style.cssText=e.style),this.settings.searchHighlight&&""!==this.content.search.input.value.trim()?t.innerHTML=this.highlightText(""!==e.html?e.html:e.text,this.content.search.input.value,this.classes.searchHighlighter):""!==e.html?t.innerHTML=e.html:t.textContent=e.text,this.settings.showOptionTooltips&&t.textContent&&t.setAttribute("title",t.textContent),e.display||this.addClasses(t,this.classes.hide),e.disabled&&this.addClasses(t,this.classes.disabled),e.selected&&this.settings.hideSelected&&this.addClasses(t,this.classes.hide),e.selected?(this.addClasses(t,this.classes.selected),t.setAttribute("aria-selected","true"),this.main.main.setAttribute("aria-activedescendant",t.id)):(this.removeClasses(t,this.classes.selected),t.setAttribute("aria-selected","false")),t.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const s=this.store.getSelected(),i=t.currentTarget,n=String(i.dataset.id),a=t.ctrlKey||t.metaKey;if(e.disabled||!this.settings.isMultiple&&e.selected&&!this.settings.allowDeselect||e.selected&&e.mandatory||this.settings.isMultiple&&this.settings.maxSelected<=s.length&&!e.selected||this.settings.isMultiple&&this.settings.minSelected>=s.length&&e.selected&&!a)return;let l=!1;const o=this.store.getSelectedOptions();let h=[];if(this.settings.isMultiple){const s=o.some(e=>e.id===n);if(t.shiftKey&&this.lastSelectedOption){const t=this.store.getDataOptions(),s=t.findIndex(e=>e.id===this.lastSelectedOption.id),i=t.findIndex(t=>t.id===e.id);if(s>=0&&i>=0){const e=Math.min(s,i),n=Math.max(s,i),a=t.slice(e,n+1).filter(e=>!o.find(t=>t.id===e.id));h=o.length+a.length<=this.settings.maxSelected?o.concat(a):o}else h=o}else h=s?o.filter(e=>e.id!==n):o.concat(e),this.lastSelectedOption=e}if(this.settings.isMultiple||(h=e.selected?[]:[e]),this.callbacks.beforeChange||(l=!0),this.callbacks.beforeChange&&(l=!1!==this.callbacks.beforeChange(h,o)),l){this.store.getOptionByID(n)||this.callbacks.addOption(e),this.callbacks.setSelected(h.map(e=>e.id),!1);const s=t.ctrlKey||t.metaKey||t.shiftKey;this.settings.closeOnSelect&&!(this.settings.isMultiple&&s)&&this.callbacks.close(),this.callbacks.afterChange&&this.callbacks.afterChange(h)}}),t}destroy(){this.closeAnimationTimeout&&(clearTimeout(this.closeAnimationTimeout),this.closeAnimationTimeout=null),this.main.main.remove(),this.content.main.remove()}highlightText(e,t,s){const i=t.trim();if(""===i)return e;const n=i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),a=document.createElement("div");a.innerHTML=e;const l=e=>{if(e.nodeType===Node.TEXT_NODE){const t=e.textContent||"",i=new RegExp("("+n+")","i");if(i.test(t)){const n=document.createElement("span");t.split(i).forEach((e,t)=>{if(e&&i.test(e)){const t=document.createElement("mark");t.className=s,t.textContent=e,n.appendChild(t)}else e&&n.appendChild(document.createTextNode(e))}),e.parentNode?.replaceChild(n,e)}}else e.nodeType===Node.ELEMENT_NODE&&Array.from(e.childNodes).forEach(e=>l(e))};return Array.from(a.childNodes).forEach(e=>l(e)),a.innerHTML}setContentDirection(e){const t="above"===e,s=t?this.classes.dirAbove:this.classes.dirBelow,i=t?this.classes.dirBelow:this.classes.dirAbove;if(this.removeClasses(this.main.main,i),this.addClasses(this.main.main,s),this.removeClasses(this.content.main,i),this.addClasses(this.content.main,s),t){const e=this.main.main.offsetHeight,t=this.content.main.offsetHeight;this.content.main.style.margin="-"+(e+t-1)+"px 0px 0px 0px"}else this.content.main.style.margin="-1px 0px 0px 0px"}setContentPosition(){if("relative"===this.settings.contentPosition)return;const e=this.main.main.getBoundingClientRect();let t,s;"fixed"===this.settings.contentPosition?(t=e.top+e.height,s=e.left):(t=e.top+window.scrollY+e.height,s=e.left+window.scrollX),this.content.main.style.top=t+"px",this.content.main.style.left=s+"px";const i=this.settings.contentWidth;this.content.main.style.width="",this.content.main.style.minWidth="",this.content.main.style.maxWidth="",i?i.startsWith(">")?this.content.main.style.minWidth=i.slice(1):i.startsWith("<")?this.content.main.style.maxWidth=i.slice(1):this.content.main.style.width=i:this.content.main.style.width=e.width+"px";const n=window.innerWidth-20,a=()=>{const e=this.content.main.getBoundingClientRect().right;if(e<=n)return;const t=e-n,s=parseFloat(this.content.main.style.left)||0;if("fixed"===this.settings.contentPosition){const e=Math.max(20,s-t);this.content.main.style.left=e+"px"}else{const e=Math.max(window.scrollX+20,s-t);this.content.main.style.left=e+"px"}};requestAnimationFrame(()=>{a(),requestAnimationFrame(a)})}moveContentAbove(){this.setContentDirection("above"),this.setContentPosition()}moveContentBelow(){this.setContentDirection("below"),this.setContentPosition()}ensureElementInView(e,t){const s=e.scrollTop+e.offsetTop,i=s+e.clientHeight,n=t.offsetTop,a=n+t.clientHeight;ni&&(e.scrollTop+=a-i)}putContent(){const e=this.main.main.offsetHeight,t=this.main.main.getBoundingClientRect(),s=this.content.main.offsetHeight;return window.innerHeight-(t.top+e)<=s&&t.top>s?"up":"down"}updateDeselectAll(){if(!this.store||!this.settings)return;const e=this.store.getSelectedOptions(),t=e&&e.length>0,s=this.settings.isMultiple,i=this.settings.allowDeselect,n=this.main.deselect.main,a=this.classes.hide;!i||s&&!t?this.addClasses(n,a):this.removeClasses(n,a)}}class r{select;onValueChange;onClassChange;onDisabledChange;onOptionsChange;onLabelClick;listen=!1;observer=null;isUpdating=!1;pendingOptionsChange=null;preventNativeSelect=null;preventNativeSelectMousedown=null;preventNativeSelectFocus=null;constructor(e){this.select=e,this.valueChange=this.valueChange.bind(this),this.select.addEventListener("change",this.valueChange,{passive:!0}),this.observer=new MutationObserver(this.observeCall.bind(this)),this.changeListen(!0)}enable(){this.select.disabled=!1}disable(){this.select.disabled=!0}hideUI(){this.select.tabIndex=-1,this.select.style.position="absolute",this.select.style.width="1px",this.select.style.height="1px",this.select.style.opacity="0",this.select.style.overflow="hidden",this.select.style.pointerEvents="none",this.select.style.margin="0",this.select.style.padding="0",this.select.style.borderWidth="0",this.select.style.clip="rect(0 0 0 0)",this.select.setAttribute("aria-hidden","true"),this.preventNativeSelect||(this.preventNativeSelect=e=>{e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation()},this.preventNativeSelectMousedown=e=>{e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation()},this.preventNativeSelectFocus=e=>{e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation()},this.select.addEventListener("click",this.preventNativeSelect,{capture:!0,passive:!1}),this.select.addEventListener("mousedown",this.preventNativeSelectMousedown,{capture:!0,passive:!1}),this.select.addEventListener("focus",this.preventNativeSelectFocus,{capture:!0,passive:!1}))}showUI(){this.select.removeAttribute("tabindex"),this.select.style.position="",this.select.style.width="",this.select.style.height="",this.select.style.opacity="",this.select.style.overflow="",this.select.style.pointerEvents="",this.select.style.margin="",this.select.style.padding="",this.select.style.borderWidth="",this.select.style.clip="",this.select.removeAttribute("aria-hidden"),this.preventNativeSelect&&(this.select.removeEventListener("click",this.preventNativeSelect,{capture:!0}),this.preventNativeSelect=null),this.preventNativeSelectMousedown&&(this.select.removeEventListener("mousedown",this.preventNativeSelectMousedown,{capture:!0}),this.preventNativeSelectMousedown=null),this.preventNativeSelectFocus&&(this.select.removeEventListener("focus",this.preventNativeSelectFocus,{capture:!0}),this.preventNativeSelectFocus=null)}changeListen(e){this.listen=e,e&&this.observer&&this.observer.observe(this.select,{subtree:!0,childList:!0,attributes:!0}),e||this.observer&&this.observer.disconnect()}valueChange(e){return this.listen&&this.onValueChange&&this.onValueChange(this.getSelectedOptions()),!0}observeCall(e){if(!this.listen)return;let t=!1,s=!1,i=!1,n=!1;for(const a of e){if(a.target===this.select&&("disabled"===a.attributeName&&(s=!0),"class"===a.attributeName&&(t=!0),"childList"===a.type)){for(const e of Array.from(a.addedNodes))if("OPTION"===e.nodeName&&e.value===this.select.value){n=!0;break}i=!0}("OPTGROUP"===a.target.nodeName||"OPTION"===a.target.nodeName)&&(i=!0)}if(t&&this.onClassChange&&this.onClassChange(this.select.className.split(" ")),s&&this.onDisabledChange&&(this.changeListen(!1),this.onDisabledChange(this.select.disabled),this.changeListen(!0)),i&&this.onOptionsChange){if(this.isUpdating){if(this.select.options.length>0){const e=this.getData();e.length>0&&(this.pendingOptionsChange=e)}return void(n&&this.select.dispatchEvent(new Event("change")))}this.changeListen(!1),this.onOptionsChange(this.getData()),this.changeListen(!0)}n&&this.select.dispatchEvent(new Event("change"))}getData(){let e=[];const t=this.select.childNodes;for(const s of t)"OPTGROUP"===s.nodeName&&e.push(this.getDataFromOptgroup(s)),"OPTION"===s.nodeName&&e.push(this.getDataFromOption(s));return e}getDataFromOptgroup(e){let t={id:e.id,label:e.label,selectAll:!!e.dataset&&"true"===e.dataset.selectall,selectAllText:e.dataset?e.dataset.selectalltext:"Select all",closable:e.dataset?e.dataset.closable:"off",options:[]};const s=e.childNodes;for(const e of s)"OPTION"===e.nodeName&&t.options.push(this.getDataFromOption(e));return t}getDataFromOption(e){return{id:e.id,value:e.value,text:e.text,html:e.dataset&&e.dataset.html?e.dataset.html:"",defaultSelected:e.defaultSelected,selected:e.selected,display:"none"!==e.style.display,disabled:e.disabled,mandatory:!!e.dataset&&"true"===e.dataset.mandatory,placeholder:"true"===e.dataset.placeholder,class:e.className,style:e.style.cssText,data:e.dataset}}getSelectedOptions(){let e=[];const t=this.select.childNodes;for(const s of t){if("OPTGROUP"===s.nodeName){const t=s.childNodes;for(const s of t)if("OPTION"===s.nodeName){const t=s;t.selected&&e.push(this.getDataFromOption(t))}}if("OPTION"===s.nodeName){const t=s;t.selected&&e.push(this.getDataFromOption(t))}}return e}getSelectedValues(){return this.getSelectedOptions().map(e=>e.value)}setSelected(e){this.changeListen(!1);const t=this.select.childNodes;for(const s of t){if("OPTGROUP"===s.nodeName){const t=s.childNodes;for(const s of t)if("OPTION"===s.nodeName){const t=s;t.selected=e.includes(t.id)}}if("OPTION"===s.nodeName){const t=s;t.selected=e.includes(t.id)}}this.changeListen(!0)}setSelectedByValue(e){this.changeListen(!1);const t=this.select.childNodes;for(const s of t){if("OPTGROUP"===s.nodeName){const t=s.childNodes;for(const s of t)if("OPTION"===s.nodeName){const t=s;t.selected=e.includes(t.value)}}if("OPTION"===s.nodeName){const t=s;t.selected=e.includes(t.value)}}this.changeListen(!0)}updateSelect(e,t,s){this.changeListen(!1),e&&(this.select.dataset.id=e),t&&(this.select.style.cssText=t),s&&(this.select.className="",s.forEach(e=>{""!==e.trim()&&this.select.classList.add(e.trim())})),this.changeListen(!0)}updateOptions(e){if(e&&0!==e.length){this.isUpdating=!0,this.pendingOptionsChange=null,this.changeListen(!1),this.select.innerHTML="";for(const t of e)t instanceof l&&this.select.appendChild(this.createOptgroup(t)),t instanceof a&&this.select.appendChild(this.createOption(t));if(this.select.dispatchEvent(new Event("change",{bubbles:!0})),this.changeListen(!0),this.isUpdating=!1,null!==this.pendingOptionsChange){const e=this.pendingOptionsChange;e.length>0&&this.onOptionsChange?(this.pendingOptionsChange=null,this.changeListen(!1),this.onOptionsChange(e),this.changeListen(!0)):this.pendingOptionsChange=null}}}createOptgroup(e){const t=document.createElement("optgroup");if(t.id=e.id,t.label=e.label,e.selectAll&&(t.dataset.selectAll="true"),"off"!==e.closable&&(t.dataset.closable=e.closable),e.options)for(const s of e.options)t.appendChild(this.createOption(s));return t}createOption(e){const t=document.createElement("option");return t.id=e.id,t.value=e.value,t.textContent=e.text,""!==e.html&&t.setAttribute("data-html",e.html),t.defaultSelected=e.defaultSelected,t.selected=e.selected,e.disabled&&(t.disabled=!0),e.display||(t.style.display="none"),e.placeholder&&t.setAttribute("data-placeholder","true"),e.mandatory&&t.setAttribute("data-mandatory","true"),e.class&&e.class.split(" ").forEach(e=>{t.classList.add(e)}),e.data&&"object"==typeof e.data&&Object.keys(e.data).forEach(s=>{t.setAttribute("data-"+function(e){const t=e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,e=>"-"+e.toLowerCase());return e[0]===e[0].toUpperCase()?t.substring(1):t}(s),e.data[s])}),t}setupLabelHandlers(){const e=[],t=this.select.id;t&&document.querySelectorAll(`label[for="${t}"]`).forEach(t=>e.push(t));let i=this.select.parentElement;for(;i&&i!==document.body;){if("LABEL"===i.tagName){e.push(i);break}i=i.parentElement}Array.from(new Set(e)).forEach(e=>{if(e.__slimSelectLabelHandler)return;const t=e=>{const t=s(e.target,this.select.dataset.id);e.preventDefault(),!t&&this.onLabelClick&&this.onLabelClick()};e.__slimSelectLabelHandler=t,e.addEventListener("click",t,{capture:!0,passive:!1})})}removeLabelHandlers(){const e=[],t=this.select.id;t&&document.querySelectorAll(`label[for="${t}"]`).forEach(t=>e.push(t));let s=this.select.parentElement;for(;s&&s!==document.body;){if("LABEL"===s.tagName){e.push(s);break}s=s.parentElement}Array.from(new Set(e)).forEach(e=>{const t=e.__slimSelectLabelHandler;t&&(e.removeEventListener("click",t,{capture:!0}),delete e.__slimSelectLabelHandler)})}destroy(){this.changeListen(!1),this.select.removeEventListener("change",this.valueChange),this.preventNativeSelect&&(this.select.removeEventListener("click",this.preventNativeSelect,{capture:!0}),this.preventNativeSelect=null),this.preventNativeSelectMousedown&&(this.select.removeEventListener("mousedown",this.preventNativeSelectMousedown,{capture:!0}),this.preventNativeSelectMousedown=null),this.preventNativeSelectFocus&&(this.select.removeEventListener("focus",this.preventNativeSelectFocus,{capture:!0}),this.preventNativeSelectFocus=null),this.observer&&(this.observer.disconnect(),this.observer=null),this.removeLabelHandlers(),delete this.select.dataset.id,this.showUI()}}class c{id="";style="";class=[];isMultiple=!1;isOpen=!1;isFullOpen=!1;intervalMove=null;disabled;alwaysOpen;showSearch;focusSearch;keepSearch;ariaLabel;searchPlaceholder;searchText;searchingText;searchHighlight;closeOnSelect;contentLocation;contentPosition;contentWidth;openPosition;placeholderText;allowDeselect;hideSelected;keepOrder;showOptionTooltips;minSelected;maxSelected;timeoutDelay;maxValuesShown;maxValuesMessage;addableText;constructor(e){e||(e={}),this.id="ss-"+t(),this.style=e.style||"",this.class=e.class||[],this.disabled=void 0!==e.disabled&&e.disabled,this.alwaysOpen=void 0!==e.alwaysOpen&&e.alwaysOpen,this.showSearch=void 0===e.showSearch||e.showSearch,this.focusSearch=void 0===e.focusSearch||e.focusSearch,this.keepSearch=void 0!==e.keepSearch&&e.keepSearch,this.ariaLabel=e.ariaLabel||"Combobox",this.searchPlaceholder=e.searchPlaceholder||"Search...",this.searchText=e.searchText||"No Results",this.searchingText=e.searchingText||"Searching...",this.searchHighlight=void 0!==e.searchHighlight&&e.searchHighlight,this.closeOnSelect=void 0===e.closeOnSelect||e.closeOnSelect,this.contentLocation=e.contentLocation||document.body,this.contentPosition=e.contentPosition||"absolute",this.contentWidth=e.contentWidth||"",this.openPosition=e.openPosition||"auto",this.placeholderText=void 0!==e.placeholderText?e.placeholderText:"Select Value",this.allowDeselect=void 0!==e.allowDeselect&&e.allowDeselect,this.hideSelected=void 0!==e.hideSelected&&e.hideSelected,this.keepOrder=void 0!==e.keepOrder&&e.keepOrder,this.showOptionTooltips=void 0!==e.showOptionTooltips&&e.showOptionTooltips,this.minSelected=e.minSelected||0,this.maxSelected=e.maxSelected||1e3,this.timeoutDelay=e.timeoutDelay||200,this.maxValuesShown=e.maxValuesShown||20,this.maxValuesMessage=e.maxValuesMessage||"{number} selected",this.addableText=e.addableText||'Press "Enter" to add {value}'}}class d{selectEl;settings;cssClasses;select;store;render;openTimeout=null;closeTimeout=null;events={search:void 0,searchFilter:(e,t)=>-1!==e.text.toLowerCase().indexOf(t.toLowerCase()),addable:void 0,beforeChange:void 0,afterChange:void 0,beforeOpen:void 0,afterOpen:void 0,beforeClose:void 0,afterClose:void 0};constructor(t){if(this.selectEl="string"==typeof t.select?document.querySelector(t.select):t.select,!this.selectEl)return void(t.events&&t.events.error&&t.events.error(new Error("Could not find select element")));if("SELECT"!==this.selectEl.tagName)return void(t.events&&t.events.error&&t.events.error(new Error("Element isnt of type select")));this.selectEl.dataset.ssid&&this.destroy(),this.settings=new c(t.settings),this.cssClasses=new e(t.cssClasses);const s=["beforeOpen","afterOpen","beforeClose","afterClose"];for(const e in t.events)t.events.hasOwnProperty(e)&&(-1!==s.indexOf(e)?this.events[e]=i(t.events[e],100):this.events[e]=t.events[e]);this.settings.disabled=t.settings?.disabled?t.settings.disabled:this.selectEl.disabled,this.settings.isMultiple=this.selectEl.multiple,this.settings.style=this.selectEl.style.cssText,this.settings.class=this.selectEl.className.split(" "),this.select=new r(this.selectEl),this.selectEl.id||(this.selectEl.id=this.settings.id),this.select.updateSelect(this.settings.id,this.settings.style,this.settings.class),this.select.hideUI(),this.select.onValueChange=e=>{this.setSelected(e.map(e=>e.id))},this.select.onClassChange=e=>{this.settings.class=e,this.render.updateClassStyles()},this.select.onDisabledChange=e=>{e?this.disable():this.enable()},this.select.onOptionsChange=e=>{this.setData(e||[])},this.select.onLabelClick=()=>{this.settings.disabled||(this.settings.isOpen?this.close():this.open())};const n=t.data?t.data:this.select.getData();this.store=new o(this.settings.isMultiple?"multiple":"single",n),t.data&&this.select.updateOptions(this.store.getData());const a={open:this.open.bind(this),close:this.close.bind(this),addable:this.events.addable?this.events.addable:void 0,setSelected:this.setSelected.bind(this),addOption:this.addOption.bind(this),search:this.search.bind(this),beforeChange:this.events.beforeChange,afterChange:this.events.afterChange};this.render=new h(this.settings,this.cssClasses,this.store,a),this.render.renderValues(),this.render.renderOptions(this.store.getData());const l=this.selectEl.getAttribute("aria-label"),d=this.selectEl.getAttribute("aria-labelledby");l?this.render.main.main.setAttribute("aria-label",l):d&&this.render.main.main.setAttribute("aria-labelledby",d),this.selectEl.parentNode&&this.selectEl.parentNode.insertBefore(this.render.main.main,this.selectEl.nextSibling),window.addEventListener("resize",this.windowResize,!1),"auto"===this.settings.openPosition&&window.addEventListener("scroll",this.windowScroll,!1),document.addEventListener("visibilitychange",this.windowVisibilityChange),this.settings.disabled&&this.disable(),this.settings.alwaysOpen&&this.open(),this.select.setupLabelHandlers(),this.selectEl.slim=this}enable(){this.settings.disabled=!1,this.select.enable(),this.render.enable()}disable(){this.settings.disabled=!0,this.select.disable(),this.render.disable()}getData(){return this.store.getData()}setData(e){const t=this.store.getSelected(),s=this.store.validateDataArray(e);if(s)return void(this.events.error&&this.events.error(s));this.store.setData(e);const i=this.store.getData();this.select.updateOptions(i),this.render.renderValues(),this.render.renderOptions(i),this.events.afterChange&&!n(t,this.store.getSelected())&&this.events.afterChange(this.store.getSelectedOptions())}getSelected(){let e=this.store.getSelectedOptions();return this.settings.keepOrder&&(e=this.store.selectedOrderOptions(e)),e.map(e=>e.value)}setSelected(e,t=!0){const s=this.store.getSelected(),i=this.store.getDataOptions();e=Array.isArray(e)?e:[e];const a=[];for(const t of e)if(i.find(e=>e.id==t))a.push(t);else for(const e of i.filter(e=>e.value==t))a.push(e.id);this.store.setSelectedBy("id",a);const l=this.store.getData();this.select.updateOptions(l),this.render.renderValues(),""!==this.render.content.search.input.value?this.search(this.render.content.search.input.value):this.render.renderOptions(l),t&&this.events.afterChange&&!n(s,this.store.getSelected())&&this.events.afterChange(this.store.getSelectedOptions())}addOption(e){const t=this.store.getSelected();this.store.getDataOptions().some(t=>t.value===(e.value??e.text))||this.store.addOption(e);const s=this.store.getData();this.select.updateOptions(s),this.render.renderValues(),this.render.renderOptions(s),this.events.afterChange&&!n(t,this.store.getSelected())&&this.events.afterChange(this.store.getSelectedOptions())}open(){this.settings.disabled||this.settings.isOpen||(this.closeTimeout&&(clearTimeout(this.closeTimeout),this.closeTimeout=null),this.events.beforeOpen&&this.events.beforeOpen(),this.render.open(),this.settings.showSearch&&this.settings.focusSearch&&this.render.searchFocus(),this.settings.isOpen=!0,this.openTimeout=setTimeout(()=>{this.events.afterOpen&&this.events.afterOpen(),this.settings.isOpen&&(this.settings.isFullOpen=!0),document.addEventListener("click",this.documentClick)},this.settings.timeoutDelay),"absolute"===this.settings.contentPosition&&(this.settings.intervalMove&&clearInterval(this.settings.intervalMove),this.settings.intervalMove=setInterval(this.render.moveContent.bind(this.render),500)))}close(e=null){!this.settings.isOpen||this.settings.alwaysOpen||(this.openTimeout&&(clearTimeout(this.openTimeout),this.openTimeout=null),this.events.beforeClose&&this.events.beforeClose(),this.render.close(),!this.settings.keepSearch&&""!==this.render.content.search.input.value&&this.search(""),this.render.mainFocus(e),this.settings.isOpen=!1,this.settings.isFullOpen=!1,this.closeTimeout=setTimeout(()=>{this.events.afterClose&&this.events.afterClose(),document.removeEventListener("click",this.documentClick)},this.settings.timeoutDelay),this.settings.intervalMove&&clearInterval(this.settings.intervalMove))}search(e){if(this.render.content.search.input.value!==e&&(this.render.content.search.input.value=e),""===e)return void this.render.renderOptions(this.store.getData());if(!this.events.search){const t=""===e?this.store.getData():this.store.search(e,this.events.searchFilter);return void this.render.renderOptions(t)}this.render.renderSearching();const t=this.events.search(e,this.store.getSelectedOptions());t instanceof Promise?t.then(e=>{this.store.setData(e,!0),this.select.updateOptions(this.store.getData()),this.render.renderOptions(this.store.getData())}).catch(e=>{this.render.renderError("string"==typeof e?e:e.message)}):Array.isArray(t)?(this.store.setData(t,!0),this.select.updateOptions(this.store.getData()),this.render.renderOptions(this.store.getData())):this.render.renderError("Search event must return a promise or an array of data")}destroy(){this.openTimeout&&(clearTimeout(this.openTimeout),this.openTimeout=null),this.closeTimeout&&(clearTimeout(this.closeTimeout),this.closeTimeout=null),this.settings.intervalMove&&(clearInterval(this.settings.intervalMove),this.settings.intervalMove=null),document.removeEventListener("click",this.documentClick),window.removeEventListener("resize",this.windowResize,!1),"auto"===this.settings.openPosition&&window.removeEventListener("scroll",this.windowScroll,!1),document.removeEventListener("visibilitychange",this.windowVisibilityChange),this.store.setData([]),this.render.destroy(),this.select.destroy()}windowResize=i(()=>{!this.settings.isOpen&&!this.settings.isFullOpen||this.render.moveContent()});windowScroll=i(()=>{!this.settings.isOpen&&!this.settings.isFullOpen||this.render.moveContent()});documentClick=e=>{this.settings.isOpen&&e.target&&!s(e.target,this.settings.id)&&this.close(e.type)};windowVisibilityChange=()=>{document.hidden&&this.close()}}function p(){document.querySelectorAll("[data-slimselect-input]").forEach(function(e){if(!e.slim){var t=e.getAttribute("data-opt-remote")||"",s=e.getAttribute("data-opt-text")||"text",i=e.getAttribute("data-opt-value")||"value",n={closeOnSelect:!0,allowDeselect:!0,placeholderText:e.getAttribute("placeholder")||"Select..."},a=e.getAttribute("data-opts");if(a)try{var l=JSON.parse(a);Object.assign(n,l)}catch(e){console.error("SlimSelect: Invalid JSON in data-opts",e)}else Array.from(e.attributes).forEach(function(e){if(e.name.startsWith("data-opt-")&&"data-opt-remote"!==e.name&&"data-opt-text"!==e.name&&"data-opt-value"!==e.name){var t=e.name.substring(9);t=t.replace(/-([a-z])/g,function(e){return e[1].toUpperCase()});var s=e.value;"true"===s?s=!0:"false"===s&&(s=!1),n[t]=s}});var o={select:e,settings:n};t&&(o.events={search:function(e,n){return new Promise(function(a,l){if(0===e.length)return a(n);var o=t+"?q["+s+"_contains]="+encodeURIComponent(e);fetch(o).then(function(e){return e.json()}).then(function(e){var t=e.slice(0,10).map(function(e){return{text:e[s],value:String(e[i])}});a(t)}).catch(function(e){console.error("SlimSelect fetch error:",e),l(e)})})}}),new d(o)}})}document.addEventListener("DOMContentLoaded",p),document.addEventListener("has_many_add:after",p),document.addEventListener("turbolinks:load",p),document.addEventListener("turbo:load",p),"undefined"!=typeof jQuery&&jQuery(document).on("has_many_add:after",function(){document.dispatchEvent(new Event("has_many_add:after"))})}); diff --git a/app/assets/stylesheets/active_admin/activeadmin_slimselect.css b/app/assets/stylesheets/active_admin/activeadmin_slimselect.css new file mode 100644 index 0000000..286fb33 --- /dev/null +++ b/app/assets/stylesheets/active_admin/activeadmin_slimselect.css @@ -0,0 +1,571 @@ +:root { + --ss-primary-color: #5897fb; + --ss-bg-color: #ffffff; + --ss-font-color: #4d4d4d; + --ss-placeholder-color: #8d8d8d; + --ss-disabled-color: #dcdee2; + --ss-border-color: #dcdee2; + --ss-highlight-color: #fffb8c; + --ss-focus-color: var(--ss-primary-color); + --ss-success-color: #00b755; + --ss-error-color: #dc3545; + --ss-main-height: 40px; + --ss-content-height: 300px; + --ss-search-height: 40px; + --ss-option-height: auto; + --ss-spacing-l: 9px; + --ss-spacing-m: 7px; + --ss-spacing-s: 5px; + --ss-animation-timing: 0.2s; + --ss-border-radius: 4px; +} + +@keyframes ss-valueIn { + 0% { + transform: scale(0); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 1; + } +} +@keyframes ss-valueOut { + 0% { + transform: scale(1); + opacity: 1; + } + 100% { + transform: scale(0); + opacity: 0; + } +} +.ss-hide { + display: none !important; +} + +.ss-main { + display: flex; + flex-direction: row; + position: relative; + user-select: none; + color: var(--ss-font-color); + min-height: var(--ss-main-height); + width: 100%; + padding: var(--ss-spacing-s); + cursor: pointer; + border: 1px solid var(--ss-border-color); + border-radius: var(--ss-border-radius); + background-color: var(--ss-bg-color); + outline: 0; + box-sizing: border-box; + transition: background-color var(--ss-animation-timing); + overflow: hidden; +} + +.ss-main:focus { + box-shadow: 0 0 5px var(--ss-focus-color); +} + +.ss-main.ss-disabled { + background-color: var(--ss-disabled-color); + cursor: not-allowed; +} + +.ss-main.ss-disabled .ss-values .ss-disabled { + color: var(--ss-font-color); +} + +.ss-main.ss-disabled .ss-values .ss-value .ss-value-delete { + cursor: not-allowed; +} + +.ss-main.ss-dir-above { + border-top-left-radius: 0px; + border-top-right-radius: 0px; +} + +.ss-main.ss-dir-below { + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; +} + +.ss-main .ss-values { + display: inline-flex; + flex-wrap: wrap; + gap: var(--ss-spacing-m); + flex: 1 1 100%; + min-width: 0; + max-width: 100%; + overflow: hidden; +} + +.ss-main .ss-values .ss-placeholder { + display: block; + width: 100%; + min-width: 0; + padding: var(--ss-spacing-s) var(--ss-spacing-m) var(--ss-spacing-s) var(--ss-spacing-m); + margin: auto 0px auto 0px; + line-height: 1em; + color: var(--ss-placeholder-color); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ss-main .ss-values .ss-max { + display: flex; + user-select: none; + align-items: center; + width: fit-content; + font-size: 12px; + color: var(--ss-bg-color); + line-height: 1; + padding: var(--ss-spacing-s) var(--ss-spacing-m); + background-color: var(--ss-primary-color); + border-radius: var(--ss-border-radius); +} + +.ss-main .ss-values .ss-single { + display: block; + margin: auto 0px auto var(--ss-spacing-s); + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1 1 auto; + max-width: calc(100% - 20px); +} + +.ss-main .ss-values .ss-value { + display: flex; + user-select: none; + align-items: center; + width: fit-content; + max-width: 100%; + background-color: var(--ss-primary-color); + border-radius: var(--ss-border-radius); + animation-name: ss-valueIn; + animation-duration: var(--ss-animation-timing); + animation-timing-function: ease-out; + animation-fill-mode: both; +} + +.ss-main .ss-values .ss-value.ss-value-out { + animation-name: ss-valueOut; + animation-duration: var(--ss-animation-timing); + animation-timing-function: ease-out; +} + +.ss-main .ss-values .ss-value .ss-value-text { + flex: 1; + min-width: 0; + font-size: 12px; + line-height: 1; + color: var(--ss-bg-color); + padding: var(--ss-spacing-s) var(--ss-spacing-m); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ss-main .ss-values .ss-value .ss-value-delete { + display: flex; + align-items: center; + height: var(--ss-spacing-l); + width: var(--ss-spacing-l); + padding: var(--ss-spacing-s) var(--ss-spacing-m); + cursor: pointer; + border-left: solid 1px var(--ss-bg-color); + box-sizing: content-box; +} + +.ss-main .ss-values .ss-value .ss-value-delete svg { + height: var(--ss-spacing-l); + width: var(--ss-spacing-l); +} + +.ss-main .ss-values .ss-value .ss-value-delete svg path { + fill: none; + stroke: var(--ss-bg-color); + stroke-width: 18; + stroke-linecap: round; + stroke-linejoin: round; +} + +.ss-main .ss-deselect { + flex: 0 1 auto; + display: flex; + align-items: center; + justify-content: center; + width: fit-content; + height: auto; + padding: 0 var(--ss-spacing-m) 0 var(--ss-spacing-m); +} + +.ss-main .ss-deselect svg { + width: 8px; + height: 8px; +} + +.ss-main .ss-deselect svg path { + fill: none; + stroke: var(--ss-font-color); + stroke-width: 20; + stroke-linecap: round; + stroke-linejoin: round; +} + +.ss-main .ss-arrow { + flex: 0 1 auto; + display: flex; + align-items: center; + justify-content: flex-end; + width: 12px; + height: 12px; + margin: auto var(--ss-spacing-m) auto var(--ss-spacing-m); +} + +.ss-main .ss-arrow path { + fill: none; + stroke: var(--ss-font-color); + stroke-width: 18; + stroke-linecap: round; + stroke-linejoin: round; + transition-timing-function: ease-out; + transition: var(--ss-animation-timing); +} + +.ss-content { + position: absolute; + display: flex; + height: auto; + flex-direction: column; + width: auto; + max-height: var(--ss-content-height); + box-sizing: border-box; + border: solid 1px var(--ss-border-color); + background-color: var(--ss-bg-color); + transition: transform var(--ss-animation-timing), opacity var(--ss-animation-timing); + opacity: 0; + transform: scaleY(0); + overflow: hidden; + z-index: 10000; +} + +.ss-content.ss-relative { + position: relative; + height: 100%; +} + +.ss-content.ss-fixed { + position: fixed; +} + +.ss-content.ss-dir-above { + transform-origin: center bottom; + flex-direction: column-reverse; + border-top-left-radius: var(--ss-border-radius); + border-top-right-radius: var(--ss-border-radius); +} + +.ss-content.ss-dir-below { + transform-origin: center top; + border-bottom-left-radius: var(--ss-border-radius); + border-bottom-right-radius: var(--ss-border-radius); +} + +.ss-content.ss-open { + opacity: 1; + transform: scaleY(1); +} + +.ss-content .ss-search { + flex: 0 1 auto; + display: flex; + flex-direction: row; + border-bottom: 1px solid var(--ss-border-color); +} + +.ss-content .ss-search input { + display: inline-flex; + font-size: inherit; + line-height: inherit; + flex: 1 1 auto; + width: 100%; + min-width: 0px; + height: var(--ss-search-height); + padding: var(--ss-spacing-m) var(--ss-spacing-l); + margin: 0; + border: none; + background-color: var(--ss-bg-color); + outline: 0; + text-align: left; + box-sizing: border-box; +} + +.ss-content .ss-search input::placeholder { + color: var(--ss-placeholder-color); + vertical-align: middle; +} + +.ss-content .ss-search input:focus { + box-shadow: none; +} + +.ss-content .ss-search .ss-addable { + display: inline-flex; + justify-content: center; + align-items: center; + cursor: pointer; + flex: 0 0 auto; + height: auto; + margin: 0 0 0 var(--ss-spacing-m); + border: 1px solid var(--ss-border-color); + border-radius: var(--ss-border-radius); +} + +.ss-content .ss-search .ss-addable svg { + display: flex; + align-items: center; + justify-content: flex-end; + flex: 0 1 auto; + width: 12px; + height: 12px; + margin: auto var(--ss-spacing-m) auto var(--ss-spacing-m); +} + +.ss-content .ss-search .ss-addable svg path { + fill: none; + stroke: var(--ss-font-color); + stroke-width: 18; + stroke-linecap: round; + stroke-linejoin: round; +} + +.ss-content.ss-dir-above .ss-search { + border-bottom: none; + border-top: 1px solid var(--ss-border-color); +} + +.ss-content .ss-list { + flex: 1 1 auto; + height: auto; + overflow-x: hidden; + overflow-y: auto; +} + +.ss-content .ss-list .ss-error { + color: var(--ss-error-color); + padding: var(--ss-spacing-l); +} + +.ss-content .ss-list .ss-searching { + color: var(--ss-font-color); + padding: var(--ss-spacing-l); +} + +.ss-content .ss-list .ss-optgroup .ss-option { + transition: max-height var(--ss-animation-timing) ease, opacity var(--ss-animation-timing) ease, padding-top var(--ss-animation-timing) ease, padding-bottom var(--ss-animation-timing) ease, margin-top var(--ss-animation-timing) ease, margin-bottom var(--ss-animation-timing) ease; +} + +.ss-content .ss-list .ss-optgroup.ss-open .ss-option { + max-height: 100px; + opacity: 1; + overflow: visible; +} + +.ss-content .ss-list .ss-optgroup.ss-close .ss-option { + padding-top: 0 !important; + padding-bottom: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + max-height: 0; + opacity: 0; + overflow: hidden; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: var(--ss-spacing-m) var(--ss-spacing-l) var(--ss-spacing-m) var(--ss-spacing-l); +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-label-text { + flex: 1 1 auto; + font-weight: bold; + color: var(--ss-font-color); +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label:has(.ss-arrow) { + cursor: pointer; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions { + flex: 0 1 auto; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: var(--ss-spacing-m); +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-selectall { + flex: 0 0 auto; + display: flex; + flex-direction: row; + cursor: pointer; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-selectall:hover { + opacity: 0.5; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-selectall.ss-selected svg path { + stroke: var(--ss-error-color); +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-selectall span { + flex: 0 1 auto; + display: flex; + align-items: center; + justify-content: center; + font-size: 60%; + text-align: center; + padding: 0 var(--ss-spacing-s) 0 0; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-selectall svg { + flex: 0 1 auto; + width: 13px; + height: 13px; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-selectall svg path { + fill: none; + stroke: var(--ss-success-color); + stroke-linecap: round; + stroke-linejoin: round; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-selectall svg:first-child { + stroke-width: 5; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-selectall svg:last-child { + stroke-width: 11; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-closable { + flex: 0 1 auto; + display: flex; + flex-direction: row; + cursor: pointer; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-closable .ss-arrow { + flex: 1 1 auto; + width: 10px; + height: 10px; +} + +.ss-content .ss-list .ss-optgroup .ss-optgroup-label .ss-optgroup-actions .ss-closable .ss-arrow path { + fill: none; + stroke: var(--ss-font-color); + stroke-width: 18; + stroke-linecap: round; + stroke-linejoin: round; + transition-timing-function: ease-out; + transition: var(--ss-animation-timing); +} + +.ss-content .ss-list .ss-optgroup .ss-option { + padding: var(--ss-spacing-s) var(--ss-spacing-s) var(--ss-spacing-s) calc(var(--ss-spacing-l) * 3); +} + +.ss-content .ss-list .ss-option { + display: flex; + align-items: center; + min-height: var(--ss-option-height); + padding: var(--ss-spacing-m) var(--ss-spacing-l) var(--ss-spacing-m) var(--ss-spacing-l); + white-space: normal; + color: var(--ss-font-color); + cursor: pointer; + user-select: none; +} + +.ss-content .ss-list .ss-option:hover:not(.ss-disabled) { + color: var(--ss-bg-color); + background-color: var(--ss-primary-color); + border-left: 5px solid var(--ss-primary-color); + transition: border-left var(--ss-animation-timing) ease; +} + +.ss-content .ss-list .ss-option.ss-highlighted, .ss-content .ss-list .ss-option:not(.ss-disabled).ss-selected { + color: var(--ss-bg-color); + background-color: var(--ss-primary-color); +} + +.ss-content .ss-list .ss-option.ss-disabled { + cursor: not-allowed; + background-color: var(--ss-disabled-color); +} + +.ss-content .ss-list .ss-option.ss-disabled:hover { + color: var(--ss-font-color); +} + +.ss-content .ss-list .ss-option .ss-search-highlight { + display: inline-block; + background-color: var(--ss-highlight-color); +} + +.ss-content .ss-list .ss-search { + display: flex; + flex-direction: row; + align-items: center; + padding: var(--ss-spacing-m) var(--ss-spacing-l) var(--ss-spacing-m) var(--ss-spacing-l); +} +.ss-main { + width: calc(80% - 22px); + min-height: auto; + padding: 8px 10px 7px; + border: 1px solid #c9d0d6; + border-radius: 3px; + background-color: #fff; +} +.ss-main:focus, .ss-main.ss-open { + border: 1px solid #99a2aa; + box-shadow: 0 0 4px #99a2aa; +} +.ss-main .ss-values { + gap: 4px; +} +.ss-main .ss-values .ss-single { + margin: 0; + padding: 0; + line-height: 1.1em; +} +.ss-main .ss-values .ss-placeholder { + padding: 0; + margin: 0; + line-height: 1.1em; +} +.ss-main .ss-values .ss-value { + margin: 0; +} +.ss-main .ss-values .ss-value .ss-value-delete { + font-size: 14px; +} +.ss-main .ss-deselect { + padding: 0 0 0 6px; +} +.ss-main .ss-arrow { + margin: auto 0 auto 6px; +} + +.filter_form .filter_form_field .ss-main { + width: 100%; +} diff --git a/app/javascript/activeadmin_slimselect/index.mjs b/app/javascript/activeadmin_slimselect/index.mjs new file mode 100644 index 0000000..3fb0d57 --- /dev/null +++ b/app/javascript/activeadmin_slimselect/index.mjs @@ -0,0 +1,2 @@ +import './slimselect_input.mjs' +import './slimselect_has_many.mjs' diff --git a/app/javascript/activeadmin_slimselect/slimselect_has_many.mjs b/app/javascript/activeadmin_slimselect/slimselect_has_many.mjs new file mode 100644 index 0000000..ca471fb --- /dev/null +++ b/app/javascript/activeadmin_slimselect/slimselect_has_many.mjs @@ -0,0 +1,12 @@ +// Bridge ActiveAdmin's jQuery-triggered has_many events to native DOM events +// so that activeadmin_slimselect's native event listener can pick them up. +// +// ActiveAdmin fires `has_many_add:after` via jQuery `.trigger()`, but +// slimselect_input.js listens with native `document.addEventListener`. +// jQuery custom events do NOT propagate as native DOM events, so we +// re-dispatch them here. +if (typeof jQuery !== 'undefined') { + jQuery(document).on('has_many_add:after', function () { + document.dispatchEvent(new Event('has_many_add:after')) + }) +} diff --git a/app/javascript/activeadmin_slimselect/slimselect_input.mjs b/app/javascript/activeadmin_slimselect/slimselect_input.mjs new file mode 100644 index 0000000..fc951e1 --- /dev/null +++ b/app/javascript/activeadmin_slimselect/slimselect_input.mjs @@ -0,0 +1,101 @@ +import SlimSelect from 'slim-select' + +// --- functions ------------------------------------------------------------ +function initSlimSelectInputs() { + document.querySelectorAll('[data-slimselect-input]').forEach(function (el) { + // Skip if already initialized + if (el.slim) return + + var remote = el.getAttribute('data-opt-remote') || '' + var fieldText = el.getAttribute('data-opt-text') || 'text' + var fieldValue = el.getAttribute('data-opt-value') || 'value' + + // Build settings object + var settings = { + closeOnSelect: true, + allowDeselect: true, + placeholderText: el.getAttribute('placeholder') || 'Select...' + } + + // Parse data-opts JSON if present (overrides individual data-opt-* attributes) + var optsJson = el.getAttribute('data-opts') + if (optsJson) { + try { + var customOpts = JSON.parse(optsJson) + Object.assign(settings, customOpts) + } catch (e) { + console.error('SlimSelect: Invalid JSON in data-opts', e) + } + } else { + // Parse individual data-opt-* attributes + Array.from(el.attributes).forEach(function (attr) { + if (attr.name.startsWith('data-opt-') && attr.name !== 'data-opt-remote' && attr.name !== 'data-opt-text' && attr.name !== 'data-opt-value') { + var name = attr.name.substring(9) // Remove 'data-opt-' prefix + // Convert kebab-case to camelCase + name = name.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase() }) + // Convert string booleans + var value = attr.value + if (value === 'true') value = true + else if (value === 'false') value = false + settings[name] = value + } + }) + } + + // Build config object + var config = { + select: el, + settings: settings + } + + // Add remote search if configured + if (remote) { + config.events = { + search: function (search, currentData) { + return new Promise(function (resolve, reject) { + if (search.length === 0) { + return resolve(currentData) + } + + var url = remote + '?q[' + fieldText + '_contains]=' + encodeURIComponent(search) + + fetch(url) + .then(function (response) { + return response.json() + }) + .then(function (data) { + var options = data.slice(0, 10).map(function (item) { + return { + text: item[fieldText], + value: String(item[fieldValue]) + } + }) + resolve(options) + }) + .catch(function (error) { + console.error('SlimSelect fetch error:', error) + reject(error) + }) + }) + } + } + } + + // Initialize SlimSelect + new SlimSelect(config) + }) +} + +export { initSlimSelectInputs } + +// --- events --------------------------------------------------------------- +document.addEventListener('DOMContentLoaded', initSlimSelectInputs) + +// ActiveAdmin has_many support +document.addEventListener('has_many_add:after', initSlimSelectInputs) + +// Turbolinks support +document.addEventListener('turbolinks:load', initSlimSelectInputs) + +// Turbo support +document.addEventListener('turbo:load', initSlimSelectInputs) diff --git a/gemfiles/propshaft.gemfile b/gemfiles/propshaft.gemfile new file mode 100644 index 0000000..a304a2e --- /dev/null +++ b/gemfiles/propshaft.gemfile @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec path: ".." + +group :development, :test do + gem "activestorage", "~> 7.0" + gem "capybara", "~> 3.39" + gem "dartsass-rails" + gem "propshaft" + gem "puma", "~> 6.4" + gem "rspec-rails", "~> 6.1" + gem "selenium-webdriver", "~> 4.16" + gem "simplecov", "~> 0.22" + gem "sqlite3", "~> 1.7" +end + +gem "activeadmin-rails", "~> 1.8" +gem "csv" +gem "logger" +gem "ostruct" +gem "rake", "~> 13.0" diff --git a/gemfiles/sprockets.gemfile b/gemfiles/sprockets.gemfile new file mode 100644 index 0000000..6f8a0a4 --- /dev/null +++ b/gemfiles/sprockets.gemfile @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec path: ".." + +group :development, :test do + gem "activestorage", "~> 7.0" + gem "capybara", "~> 3.39" + gem "dartsass-sprockets" + gem "puma", "~> 6.4" + gem "rspec-rails", "~> 6.1" + gem "selenium-webdriver", "~> 4.16" + gem "simplecov", "~> 0.22" + gem "sqlite3", "~> 1.7" +end + +gem "activeadmin-rails", "~> 1.7" +gem "csv" +gem "logger" +gem "ostruct" +gem "rake", "~> 13.0" diff --git a/lib/activeadmin/slimselect/engine.rb b/lib/activeadmin/slimselect/engine.rb index 069e746..9c030a7 100644 --- a/lib/activeadmin/slimselect/engine.rb +++ b/lib/activeadmin/slimselect/engine.rb @@ -7,6 +7,15 @@ module SlimSelect # Rails engine for activeadmin_slimselect class Engine < ::Rails::Engine engine_name "activeadmin_slimselect" + + # Register assets with ActiveAdmin for Propshaft (no Sprockets directive processing). + # With Sprockets, users include assets via //= require and @import directives. + initializer "activeadmin_slimselect.assets" do + unless defined?(Sprockets) + ActiveAdmin.application.register_javascript "active_admin/activeadmin_slimselect.js" + ActiveAdmin.application.register_stylesheet "active_admin/activeadmin_slimselect.css" + end + end end end end diff --git a/package.json b/package.json new file mode 100644 index 0000000..f461c95 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "activeadmin_slimselect", + "version": "0.1.0", + "private": true, + "description": "SlimSelect for ActiveAdmin", + "license": "MIT", + "scripts": { + "build:js": "rollup --config rollup.config.mjs", + "build:css": "sass --no-source-map --load-path=app/assets/stylesheets app/assets/stylesheets/activeadmin/_slimselect_input.scss app/assets/stylesheets/active_admin/activeadmin_slimselect.css", + "build": "npm run build:js && npm run build:css" + }, + "devDependencies": { + "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-terser": "^0.4.0", + "rollup": "^4.0.0", + "sass": "^1.97.3", + "slim-select": "^3.3.0" + } +} diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..57a98cc --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,15 @@ +import resolve from "@rollup/plugin-node-resolve"; +import terser from "@rollup/plugin-terser"; + +export default { + input: "app/javascript/activeadmin_slimselect/index.mjs", + output: { + file: "app/assets/javascripts/active_admin/activeadmin_slimselect.js", + format: "umd", + name: "ActiveAdminSlimSelect" + }, + plugins: [ + resolve(), + terser() + ] +}; diff --git a/spec/dummy/app/assets/stylesheets/active_admin.propshaft.scss b/spec/dummy/app/assets/stylesheets/active_admin.propshaft.scss new file mode 100644 index 0000000..a351e8b --- /dev/null +++ b/spec/dummy/app/assets/stylesheets/active_admin.propshaft.scss @@ -0,0 +1,8 @@ +// Propshaft CSS entry point for dummy app +// This imports ActiveAdmin base styles and slimselect styles +// The gem paths are passed via --load-path in the build command + +@import "active_admin/mixins"; +@import "active_admin/base"; +@import "activeadmin/slimselect/slimselect"; +@import "activeadmin/slimselect_input"; diff --git a/spec/dummy/app/assets/stylesheets/active_admin_print.scss b/spec/dummy/app/assets/stylesheets/active_admin_print.scss new file mode 100644 index 0000000..1200859 --- /dev/null +++ b/spec/dummy/app/assets/stylesheets/active_admin_print.scss @@ -0,0 +1,3 @@ +// Entry point for dartsass:build to compile the ActiveAdmin print stylesheet. +// Named differently to avoid circular resolution with the gem's active_admin/print.scss. +@import "active_admin/print"; diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index a16f6ed..68ae07a 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -14,5 +14,9 @@ class Application < Rails::Application # Don't generate system test files. config.generators.system_tests = nil + + # dartsass-rails compiles SCSS → CSS into app/assets/builds/. + # Ensure the asset pipeline (Propshaft or Sprockets) can find them. + config.assets.paths << Rails.root.join("app/assets/builds") end end diff --git a/spec/dummy/config/initializers/assets.rb b/spec/dummy/config/initializers/assets.rb index 2913ae6..76b24f5 100644 --- a/spec/dummy/config/initializers/assets.rb +++ b/spec/dummy/config/initializers/assets.rb @@ -2,11 +2,14 @@ # Be sure to restart your server when you modify this file. -# Version of your assets, change this if you want to expire all your assets. -Rails.application.config.assets.version = "1.0" +# Sprockets-specific asset configuration (skipped when using Propshaft) +if defined?(Sprockets) + # Version of your assets, change this if you want to expire all your assets. + Rails.application.config.assets.version = "1.0" -# Add additional assets to the asset load path. -Rails.application.config.assets.paths << Rails.root.join("node_modules") + # Add additional assets to the asset load path. + Rails.application.config.assets.paths << Rails.root.join("node_modules") -# Precompile additional assets. -Rails.application.config.assets.precompile += %w[active_admin.js active_admin.css] + # Precompile additional assets. + Rails.application.config.assets.precompile += %w[active_admin.js active_admin.css] +end diff --git a/spec/dummy/config/initializers/dartsass.rb b/spec/dummy/config/initializers/dartsass.rb new file mode 100644 index 0000000..a7c9947 --- /dev/null +++ b/spec/dummy/config/initializers/dartsass.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# DartSass build configuration (Propshaft only). +# ActiveAdmin-Rails 1.8+ uses dartsass-rails so CSS must be pre-compiled via +# `rails dartsass:build`. Under Sprockets, dartsass-sprockets compiles SCSS +# at request time so this config is not needed. +if !defined?(Sprockets) + Rails.application.config.dartsass.builds = { + "active_admin.propshaft.scss" => "active_admin.css", + "active_admin_print.scss" => "active_admin/print.css" + } +end diff --git a/spec/system/slimselect_inputs_spec.rb b/spec/system/slimselect_inputs_spec.rb index c9252ee..b9f9f9b 100644 --- a/spec/system/slimselect_inputs_spec.rb +++ b/spec/system/slimselect_inputs_spec.rb @@ -39,8 +39,11 @@ visit "/admin/posts/#{post.id}/edit" find(slimselect_main).click - # SlimSelect renders dropdown in document.body, so we need to find globally + # SlimSelect renders dropdown in document.body, wait for it to be visible + expect(page).to have_css(".ss-content.ss-open", visible: true) find(".ss-option", text: "John 1").click + # Wait for dropdown to close, confirming selection was registered + expect(page).to have_no_css(".ss-content.ss-open") find('[type="submit"]').click expect(page).to have_content("was successfully updated") expect(post.reload.author).to eq(authors.find { |item| item.name == "John 1" }) @@ -61,8 +64,14 @@ visit "/admin/posts/#{post.id}/edit" find(slimselect_main).click - # SlimSelect renders dropdown in document.body, so we need to find globally + # SlimSelect renders dropdown in document.body, wait for it to be visible + expect(page).to have_css(".ss-content.ss-open", visible: true) find(".ss-option", text: "A tag 1").click + # Wait for selection to be registered (multi-select stays open, check for selected value) + expect(page).to have_css(".ss-value-text", text: "A tag 1") + # Close dropdown by clicking outside + find("body").click + expect(page).to have_no_css(".ss-content.ss-open") scroll_to(find("#post_submit_action")) find('[type="submit"]').click expect(page).to have_content("was successfully updated")