treeselectjs 0.2.5 → 0.2.8

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 CHANGED
@@ -5,6 +5,8 @@ A multi-select js component with nested options.
5
5
  - Full key support (ArrowUp, ArrowDown, Space, ArrowLeft, ArrowRight, Enter)
6
6
  - Screen sensitive direction
7
7
 
8
+ **Live Demo:** https://dipson88.github.io/treeselectjs/
9
+
8
10
  ![Example img](https://github.com/dipson88/treeselectjs/blob/main/treeselectjs.png?raw=true)
9
11
 
10
12
  ### Getting Started
@@ -24,7 +26,7 @@ and css file with styles
24
26
 
25
27
  Example
26
28
  ```
27
- import Treeselect from 'treeselectjs'
29
+ import Treeselect from '../dist/treeselect-js.js';
28
30
 
29
31
  const options = [
30
32
  {
@@ -73,57 +75,61 @@ const options = [
73
75
  ]
74
76
 
75
77
  const slot = document.createElement('div')
76
- slot.innerHTML='<a class="test" href="">Slot example text</a>'
78
+ slot.innerHTML='<a class="treeselect-demo__slot" href="">Click!</a>'
77
79
 
78
- const domElement = document.querySelector('.treeselect-test')
80
+ const domElement = document.querySelector('.treeselect-demo')
79
81
  const treeselect = new Treeselect({
80
82
  parentHtmlContainer: domElement,
81
83
  value: ['West End', 'Paris', 'Lyon'],
82
84
  options: options,
83
- alwaysOpen: true,
84
- appendToBody: true,
85
- listSlotHtmlComponent: slot,
86
- disabled: false,
87
- emptyText: 'No data text'
85
+ listSlotHtmlComponent: slot
88
86
  })
89
87
 
90
88
  treeselect.srcElement.addEventListener('input', (e) => {
91
- console.log(e.detail)
89
+ console.log('Selected value:', e.detail)
90
+ })
91
+
92
+ slot.addEventListener('click', (e) => {
93
+ e.preventDefault()
94
+ alert('Slot click!')
92
95
  })
93
96
  ```
94
97
 
95
98
  ### Props
96
- Name | Type (default) | Discription
99
+ Name | Type (default) | Description
97
100
  ------------- | ------------- | -------------
98
- parentHtmlContainer | HTMLElement | It sould be a HTML element (div), it will be changed to the list container.
101
+ parentHtmlContainer | HTMLElement (required!) | It should be a HTML element (div), it will be changed to the list container.
99
102
  value | Array[String] ([]) | It is an array with ids.
100
- options | Array[Object] ([]) | It is an array of objects { name: String, value: String, children: [] }, where children are the same array of objects.
103
+ options | Array[Object] ([]) | It is an array of objects { name: String, value: String, children: [] }, where children are the same array of objects. Do not use duplicated values.
101
104
  openLevel | Number (0) | All groups will be opened to this level.
102
105
  appendToBody | Boolean (false) | List will be appended to the body instead of the input container.
103
- alwaysOpen | Boolean (false) | List will be always opened.
106
+ alwaysOpen | Boolean (false) | List will be always opened. You can use it for comfortable style changing. If you what to use it as an opened list, turn `staticList' to `true`.
104
107
  showTags | Boolean (true) | Selected values look like tags. The false value shows results as '{count} elements selected'.
105
108
  clearable | Boolean (true) | Clear icon is available.
106
109
  searchable | Boolean (true) | Search is available.
107
110
  placeholder | String ('Search...') | Placeholder text.
108
- grouped | Boolean (true) | Show groups in the input and group lefs if all group selected.
111
+ grouped | Boolean (true) | Show groups in the input and group leafs if all group selected.
109
112
  listSlotHtmlComponent | HTMLElement (null) | It should be a HTML element, it will be append to the end of the list.
110
113
  disabled | Boolean (false) | List will be disabled.
111
114
  emptyText | String ('No results found...') | A empty list text.
115
+ staticList | Boolean (false) | Add the list as a static DOM element. List doesn't overlap content. This prop will be ignored if you use `appendToBody`.
112
116
 
113
117
  ### Emits
114
- Name | Return Type | Discription
118
+ Name | Return Type | Description
115
119
  ------------- | ------------- | -------------
116
120
  input | Array[String] | Returns selected ids without groups, only leafs.
117
121
 
118
122
  ### Methods
119
- Name | Params | Discription
123
+ Name | Params | Description
120
124
  ------------- | ------------- | -------------
121
125
  updateValue | Array[String] | Update selected values.
122
- mount | None | Helps to remount and update settings.
123
- destroy | None | Deletes elements from the DOM and clears all the data. Call mount() to recreate.
126
+ mount | None | Helps to remount and update settings. Change settings that you need (treeselect.appendToBody = true), then call mount().
127
+ destroy | None | Deletes elements from the DOM. Call mount() to add treeselect to the DOM with previously saved internal data. If you need to recreate treeselect with default params - call **new Treeselect(options)**.
124
128
 
125
129
  ### Notes
126
130
  1) If you want to change the padding of the element you can use CSS selector. I've added **'group'** and **'level'** attributes, but you have to use **!important**.
127
131
  2) If you want to update props, set props to the entity of the class and then call **mount()** method.
128
132
  3) Use **updateValue()** method to update only the value.
129
- 4) If you need to delete List from the DOM when you don't need treeselect anymore - call **destroy()**.
133
+ 4) If you need to delete List from the DOM when you don't need treeselect anymore - call **destroy()**.
134
+ 5) Do not use **duplicated** values for the options. You will see a error with duplicated values.
135
+ 6) **Value** inside the **options** prop should be a **String**.
package/dist/input.js CHANGED
@@ -1 +1 @@
1
- import svg from"./svgIcons.js";class TreeselectInput{#htmlTagsSecton=null;#htmlEditControl=null;#htmlOperators=null;#htmlArrow=null;#openEvent=new CustomEvent("open");#closeEvent=new CustomEvent("close");constructor({value:e,showTags:t,clearable:s,isAlwaysOpened:a,searchable:i,placeholder:n,disabled:r}){this.value=e,this.showTags=t??!0,this.searchable=i??!0,this.placeholder=n??"Search...",this.clearable=s??!0,this.isAlwaysOpened=a??!1,this.disabled=r??!1,this.isOpened=!1,this.searchText="",this.srcElement=this.#createTreeselectInput(),this.#updateDOM()}focus(){this.#htmlEditControl.focus()}blur(){this.isOpened&&this.#updateOpenClose()}updateValue(e){this.value=e,this.#updateTags(),this.#updateEditControl()}removeItem(t){this.value=this.value.filter(e=>e.id!==t),this.#emitInput(),this.#updateTags(),this.#updateEditControl()}clear(){this.value=[],this.searchText="",this.#emitSearch(""),this.#emitInput(),this.#updateTags(),this.#updateEditControl()}openClose(){this.#updateOpenClose()}#updateDOM(){this.#updateTags(),this.#updateEditControl(),this.#updateOperators()}#updateTags(){this.#htmlTagsSecton.innerHTML="",this.showTags?this.#htmlTagsSecton.append(...this.#createTags()):this.#htmlTagsSecton.appendChild(this.#createCountElement())}#updateOperators(){const e=[];this.#htmlOperators.innerHTML="",this.clearable&&e.push(this.#createClearButton()),this.isAlwaysOpened||e.push(this.#createInputArrow(this.isOpened)),e.length&&this.#htmlOperators.append(...e)}#updateArrowDirection(){var e;this.isAlwaysOpened||(e=this.isOpened?svg.arrowUp:svg.arrowDown,this.#htmlArrow.innerHTML=e)}#updateEditControl(){this.value?.length?this.#htmlEditControl.removeAttribute("placeholder"):this.#htmlEditControl.setAttribute("placeholder",this.placeholder),this.searchable?this.srcElement.classList.remove("treeselect-input--unsearchable"):this.srcElement.classList.add("treeselect-input--unsearchable"),this.#htmlEditControl.value=this.searchText}#updateOpenClose(){this.isOpened=!this.isOpened,this.#updateArrowDirection(),this.isOpened?this.#emitOpen():this.#emitClose()}#createTreeselectInput(){const e=document.createElement("div");return e.classList.add("treeselect-input"),e.setAttribute("tabindex","-1"),this.#htmlTagsSecton=this.#createTagsSection(),this.#htmlEditControl=this.#createControl(),this.#htmlOperators=this.#createOperators(),e.addEventListener("mousedown",e=>{e.preventDefault(),this.isOpened||this.#updateOpenClose(),this.focus()}),e.append(this.#htmlTagsSecton,this.#htmlEditControl,this.#htmlOperators),e}#createTagsSection(){const e=document.createElement("div");return e.classList.add("treeselect-input__tags"),e}#createTags(){return this.value.map(t=>{const e=document.createElement("div");e.classList.add("treeselect-input__tags-element"),e.setAttribute("tabindex","-1"),e.setAttribute("tag-id",t.id),e.setAttribute("title",t.name);var s=this.#createTagName(t.name),a=this.#createTagCross();return e.addEventListener("mousedown",e=>{e.preventDefault(),e.stopPropagation(),this.focus(),this.removeItem(t.id)}),e.append(s,a),e})}#createTagName(e){const t=document.createElement("span");return t.classList.add("treeselect-input__tags-name"),t.innerHTML=e,t}#createTagCross(){const e=document.createElement("span");return e.classList.add("treeselect-input__tags-cross"),e.innerHTML=svg.cross,e}#createCountElement(){const e=document.createElement("span");return e.classList.add("treeselect-input__tags-count"),this.value.length?e.innerHTML=1===this.value.length?this.value[0].name:this.value.length+" elements selected":e.innerHTML="",e}#createControl(){const a=document.createElement("input");return a.classList.add("treeselect-input__edit"),this.disabled&&a.setAttribute("tabindex","-1"),a.addEventListener("keydown",e=>{"Backspace"!==e.key||this.searchText.length||!this.value.length||this.showTags||this.clear(),"Backspace"===e.key&&!this.searchText.length&&this.value.length&&this.removeItem(this.value[this.value.length-1].id),"Space"!==e.code||this.searchText&&this.searchable||this.#updateOpenClose()}),a.addEventListener("input",e=>{e.stopPropagation();var t=this.searchText,s=a.value.trim();0===t.length&&0===s.length?a.value="":(this.searchable?(this.#emitSearch(e.target.value),this.isOpened||this.#updateOpenClose()):a.value="",this.searchText=a.value)}),a}#createOperators(){const e=document.createElement("div");return e.classList.add("treeselect-input__operators"),e}#createClearButton(){const e=document.createElement("span");return e.classList.add("treeselect-input__clear"),e.setAttribute("tabindex","-1"),e.innerHTML=svg.clear,e.addEventListener("mousedown",e=>{e.preventDefault(),e.stopPropagation(),this.#htmlEditControl.focus(),(this.searchText.length||this.value.length)&&this.clear()}),e}#createInputArrow(e){return this.#htmlArrow=document.createElement("span"),this.#htmlArrow.classList.add("treeselect-input__arrow"),this.#htmlArrow.innerHTML=e?svg.arrowUp:svg.arrowDown,this.#htmlArrow.addEventListener("mousedown",e=>{e.stopPropagation(),e.preventDefault(),this.focus(),this.#updateOpenClose()}),this.#htmlArrow}#emitInput(){this.srcElement.dispatchEvent(new CustomEvent("input",{detail:this.value}))}#emitSearch(e){this.srcElement.dispatchEvent(new CustomEvent("search",{detail:e}))}#emitOpen(){this.srcElement.dispatchEvent(this.#openEvent)}#emitClose(){this.srcElement.dispatchEvent(this.#closeEvent)}}export default TreeselectInput;
1
+ import svg from"./svgIcons.js";class TreeselectInput{#htmlTagsSection=null;#htmlEditControl=null;#htmlOperators=null;#htmlArrow=null;#openEvent=new CustomEvent("open");#closeEvent=new CustomEvent("close");constructor({value:e,showTags:t,clearable:s,isAlwaysOpened:a,searchable:i,placeholder:n,disabled:r}){this.value=e,this.showTags=t??!0,this.searchable=i??!0,this.placeholder=n??"Search...",this.clearable=s??!0,this.isAlwaysOpened=a??!1,this.disabled=r??!1,this.isOpened=!1,this.searchText="",this.srcElement=this.#createTreeselectInput(),this.#updateDOM()}focus(){this.#htmlEditControl.focus()}blur(){this.isOpened&&this.#updateOpenClose()}updateValue(e){this.value=e,this.#updateTags(),this.#updateEditControl()}removeItem(t){this.value=this.value.filter(e=>e.id!==t),this.#emitInput(),this.#updateTags(),this.#updateEditControl()}clear(){this.value=[],this.searchText="",this.#emitSearch(""),this.#emitInput(),this.#updateTags(),this.#updateEditControl()}openClose(){this.#updateOpenClose()}#updateDOM(){this.#updateTags(),this.#updateEditControl(),this.#updateOperators()}#updateTags(){this.#htmlTagsSection.innerHTML="",this.showTags?this.#htmlTagsSection.append(...this.#createTags()):this.#htmlTagsSection.appendChild(this.#createCountElement())}#updateOperators(){const e=[];this.#htmlOperators.innerHTML="",this.clearable&&e.push(this.#createClearButton()),this.isAlwaysOpened||e.push(this.#createInputArrow(this.isOpened)),e.length&&this.#htmlOperators.append(...e)}#updateArrowDirection(){var e;this.isAlwaysOpened||(e=this.isOpened?svg.arrowUp:svg.arrowDown,this.#htmlArrow.innerHTML=e)}#updateEditControl(){this.value?.length?this.#htmlEditControl.removeAttribute("placeholder"):this.#htmlEditControl.setAttribute("placeholder",this.placeholder),this.searchable?this.srcElement.classList.remove("treeselect-input--unsearchable"):this.srcElement.classList.add("treeselect-input--unsearchable"),this.#htmlEditControl.value=this.searchText}#updateOpenClose(){this.isOpened=!this.isOpened,this.#updateArrowDirection(),this.isOpened?this.#emitOpen():this.#emitClose()}#createTreeselectInput(){const e=document.createElement("div");return e.classList.add("treeselect-input"),e.setAttribute("tabindex","-1"),this.#htmlTagsSection=this.#createTagsSection(),this.#htmlEditControl=this.#createControl(),this.#htmlOperators=this.#createOperators(),e.addEventListener("mousedown",e=>{e.preventDefault(),this.isOpened||this.#updateOpenClose(),this.focus()}),e.append(this.#htmlTagsSection,this.#htmlEditControl,this.#htmlOperators),e}#createTagsSection(){const e=document.createElement("div");return e.classList.add("treeselect-input__tags"),e}#createTags(){return this.value.map(t=>{const e=document.createElement("div");e.classList.add("treeselect-input__tags-element"),e.setAttribute("tabindex","-1"),e.setAttribute("tag-id",t.id),e.setAttribute("title",t.name);var s=this.#createTagName(t.name),a=this.#createTagCross();return e.addEventListener("mousedown",e=>{e.preventDefault(),e.stopPropagation(),this.focus(),this.removeItem(t.id)}),e.append(s,a),e})}#createTagName(e){const t=document.createElement("span");return t.classList.add("treeselect-input__tags-name"),t.innerHTML=e,t}#createTagCross(){const e=document.createElement("span");return e.classList.add("treeselect-input__tags-cross"),e.innerHTML=svg.cross,e}#createCountElement(){const e=document.createElement("span");return e.classList.add("treeselect-input__tags-count"),this.value.length?e.innerHTML=1===this.value.length?this.value[0].name:this.value.length+" elements selected":e.innerHTML="",e}#createControl(){const a=document.createElement("input");return a.classList.add("treeselect-input__edit"),this.disabled&&a.setAttribute("tabindex","-1"),a.addEventListener("keydown",e=>{"Backspace"!==e.key||this.searchText.length||!this.value.length||this.showTags||this.clear(),"Backspace"===e.key&&!this.searchText.length&&this.value.length&&this.removeItem(this.value[this.value.length-1].id),"Space"!==e.code||this.searchText&&this.searchable||this.#updateOpenClose()}),a.addEventListener("input",e=>{e.stopPropagation();var t=this.searchText,s=a.value.trim();0===t.length&&0===s.length?a.value="":(this.searchable?(this.#emitSearch(e.target.value),this.isOpened||this.#updateOpenClose()):a.value="",this.searchText=a.value)}),a}#createOperators(){const e=document.createElement("div");return e.classList.add("treeselect-input__operators"),e}#createClearButton(){const e=document.createElement("span");return e.classList.add("treeselect-input__clear"),e.setAttribute("tabindex","-1"),e.innerHTML=svg.clear,e.addEventListener("mousedown",e=>{e.preventDefault(),e.stopPropagation(),this.#htmlEditControl.focus(),(this.searchText.length||this.value.length)&&this.clear()}),e}#createInputArrow(e){return this.#htmlArrow=document.createElement("span"),this.#htmlArrow.classList.add("treeselect-input__arrow"),this.#htmlArrow.innerHTML=e?svg.arrowUp:svg.arrowDown,this.#htmlArrow.addEventListener("mousedown",e=>{e.stopPropagation(),e.preventDefault(),this.focus(),this.#updateOpenClose()}),this.#htmlArrow}#emitInput(){this.srcElement.dispatchEvent(new CustomEvent("input",{detail:this.value}))}#emitSearch(e){this.srcElement.dispatchEvent(new CustomEvent("search",{detail:e}))}#emitOpen(){this.srcElement.dispatchEvent(this.#openEvent)}#emitClose(){this.srcElement.dispatchEvent(this.#closeEvent)}}export default TreeselectInput;
package/dist/list.css CHANGED
@@ -1,7 +1,7 @@
1
1
  .treeselect-list{width:100%;box-sizing:border-box;border:1px solid #d7dde4;overflow-y:auto;background-color:#fff;max-height:300px}
2
2
  .treeselect-list__group-container{box-sizing:border-box}.treeselect-list__item{display:flex;align-items:center;box-sizing:border-box;cursor:pointer;height:30px}
3
- .treeselect-list__item--focused{background-color:#f0ffff !important}.treeselect-list__item--hidden{display:none}
4
- .treeselect-list__item-icon{display:flex;align-items:center;cursor:pointer;height:20px;width:20px;min-width:20px}
3
+ .treeselect-list__item:focus{outline:0}.treeselect-list__item--focused{background-color:#f0ffff !important}
4
+ .treeselect-list__item--hidden{display:none}.treeselect-list__item-icon{display:flex;align-items:center;cursor:pointer;height:20px;width:20px;min-width:20px}
5
5
  .treeselect-list__item-icon svg{pointer-events:none;width:100%;height:100%;stroke:#c5c7cb}
6
6
  .treeselect-list__item-icon:hover svg{stroke:#838790}.treeselect-list__item-checkbox-container{width:20px;height:20px;min-width:20px;border:1px solid #d7dde4;border-radius:3px;position:relative;background-color:#fff;pointer-events:none;box-sizing:border-box}
7
7
  .treeselect-list__item-checkbox-container svg{position:absolute;height:100%;width:100%}
package/dist/list.js CHANGED
@@ -1 +1 @@
1
- import svg from"./svgIcons.js";const getFlatOptons=(e,i,l=0,c=0)=>e.reduce((e,t)=>{var s=!!t.children?.length;return e.push({id:t.value,name:t.name,childOf:l,isGroup:s,checked:!1,level:c,isClosed:i<=c&&s,hidden:i<c}),s&&(s=getFlatOptons(t.children,i,t.value,c+1),e.push(...s)),e},[]),checkAllChildrenInputs=({id:t,checked:s},i)=>{i.forEach(e=>{e.childOf===t&&(e.checked=s,e.isGroup&&checkAllChildrenInputs(e,i))})},checkAllParentInputs=(t,e)=>{const s=e.find(e=>e.id===t),i=e.filter(e=>e.childOf===s.id);var l=i.every(e=>e.checked),c=i.some(e=>e.isPartialChecked||e.checked)&&!l,r=!l&&!c;l&&(s.checked=!0,s.isPartialChecked=!1),c&&(s.checked=!1,s.isPartialChecked=!0),r&&(s.checked=!1,s.isPartialChecked=!1),s.childOf&&checkAllParentInputs(s.childOf,e)},checkInput=({id:e,isGroup:t,childOf:s,checked:i},l)=>{t&&checkAllChildrenInputs({id:e,checked:i},l),s&&checkAllParentInputs(s,l)},updateValue=(t,s,e)=>{s.forEach(e=>e.checked=!1);const i=s.filter(e=>t.includes(e.id));i.forEach(e=>{e.checked=!0,e.isPartialChecked=!1,checkInput(e,s)}),updateDOM(s,e)},hideShowChildren=(t,{id:s,isClosed:i})=>{const e=t.filter(e=>e.childOf===s);e.forEach(e=>{e.hidden=i,e.isGroup&&!e.isClosed&&hideShowChildren(t,{id:e.id,isClosed:i})})},updateDOM=(l,c)=>{l.forEach(e=>{const t=c.querySelector(`[input-id="${e.id}"]`),s=getListItemByCheckbox(t);if(t.checked=e.checked,e.checked?s.classList.add("treeselect-list__item--checked"):s.classList.remove("treeselect-list__item--checked"),e.isPartialChecked?s.classList.add("treeselect-list__item--partial-checked"):s.classList.remove("treeselect-list__item--partial-checked"),e.isGroup){const i=s.querySelector(".treeselect-list__item-icon");e.isClosed?(s.classList.add("treeselect-list__item--closed"),i.innerHTML=svg.arrowRight):(s.classList.remove("treeselect-list__item--closed"),i.innerHTML=svg.arrowDown)}e.hidden?s.classList.add("treeselect-list__item--hidden"):s.classList.remove("treeselect-list__item--hidden"),updateLeftPaddingItems(e,s,l),updateCheckboxClasses(e,t)});var e=l.some(e=>!e.hidden);const t=c.querySelector(".treeselect-list__empty");e?t.classList.add("treeselect-list__empty--hidden"):t.classList.remove("treeselect-list__empty--hidden")},updateLeftPaddingItems=(t,e,s)=>{0===t.level?(s=s.some(e=>e.isGroup&&e.level===t.level),s=!t.isGroup&&s?"20px":"5px",e.style.paddingLeft=t.isGroup?"0":s):e.style.paddingLeft=t.isGroup?20*t.level+"px":20*t.level+20+"px",e.setAttribute("level",t.level),e.setAttribute("group",t.isGroup)},updateCheckboxClasses=(e,t)=>{const s=t.parentNode,i=s.querySelector(".treeselect-list__item-checkbox-icon");e.checked?i.innerHTML=svg.check:e.isPartialChecked?i.innerHTML=svg.partialCheck:i.innerHTML=""},getAllFlattedChildren=(s,i)=>i.reduce((e,t)=>(t.childOf===s&&(e.push(t),t.isGroup&&e.push(...getAllFlattedChildren(t.id,i))),e),[]),getAllFlattendParents=(s,i)=>i.reduce((e,t)=>(t.id===s&&(e.push(t),t.childOf&&e.push(...getAllFlattendParents(t.childOf,i))),e),[]),getGroupedValues=e=>{const{onlyGroupsIds:t,allItems:s}=e.reduce((e,t)=>(t.checked&&(t.isGroup&&e.onlyGroupsIds.push(t.id),e.allItems.push(t)),e),{onlyGroupsIds:[],allItems:[]});return s.filter(e=>!t.includes(e.childOf))},getCheckedValues=e=>e.filter(e=>e.checked&&!e.isGroup),getListItemByCheckbox=e=>{return e.parentNode.parentNode};class TreeselectList{#lastFocusedItem=null;#isMouseActionsAvailable=!0;constructor({options:e,value:t,openLevel:s,listSlotHtmlComponent:i,emptyText:l}){this.options=e,this.value=t,this.searchText="",this.openLevel=s??0,this.listSlotHtmlComponent=i,this.emptyText=l??"No results found...",this.flattedOptions=getFlatOptons(this.options,this.openLevel),this.flattedOptionsBeforeSearch=this.flattedOptions,this.selectedNodes={ids:[],groupedIds:[]},this.srcElement=this.#createList(),this.updateValue(this.value)}updateValue(e){updateValue(e,this.flattedOptions,this.srcElement),this.#updateSelectedNodes()}updateSearchValue(i){var e=""===this.searchText&&""!==i;if(this.searchText=i,e&&(this.flattedOptionsBeforeSearch=JSON.parse(JSON.stringify(this.flattedOptions))),""===this.searchText)return this.flattedOptions=this.flattedOptionsBeforeSearch.map(t=>{const e=this.flattedOptions.find(e=>e.id===t.id);return e.isClosed=t.isClosed,e.hidden=t.hidden,e}),this.flattedOptionsBeforeSearch=[],updateDOM(this.flattedOptions,this.srcElement),void this.focusFirstListElement();const s=this.flattedOptions.reduce((e,t)=>{var s;return t.name.toLowerCase().includes(i.toLowerCase())&&(e.push(t),t.isGroup&&(s=getAllFlattedChildren(t.id,this.flattedOptions),e.push(...s)),t.childOf&&(s=getAllFlattendParents(t.childOf,this.flattedOptions),e.push(...s))),e},[]);this.flattedOptions.forEach(t=>{s.some(e=>e.id===t.id)?(t.isGroup&&(t.isClosed=!1,hideShowChildren(this.flattedOptions,t)),t.hidden=!1):t.hidden=!0}),updateDOM(this.flattedOptions,this.srcElement),this.focusFirstListElement()}callKeyAction(e){this.#isMouseActionsAvailable=!1;const t=this.srcElement.querySelector(".treeselect-list__item--focused");if("Enter"===e&&t&&t.dispatchEvent(new Event("mousedown")),"ArrowLeft"===e||"ArrowRight"===e){if(!t)return;const c=t.querySelector(".treeselect-list__item-checkbox"),r=c.getAttribute("input-id");var s=this.flattedOptions.find(e=>e.id===r);const o=t.querySelector(".treeselect-list__item-icon");"ArrowLeft"!==e||s.isClosed||o.dispatchEvent(new Event("mousedown")),"ArrowRight"===e&&s.isClosed&&o.dispatchEvent(new Event("mousedown"))}if("ArrowDown"===e||"ArrowUp"===e){const d=Array.from(this.srcElement.querySelectorAll(".treeselect-list__item-checkbox")).filter(e=>"none"!==window.getComputedStyle(getListItemByCheckbox(e)).display);if(d.length)if(t){s=d.findIndex(e=>getListItemByCheckbox(e).classList.contains("treeselect-list__item--focused"));const n=getListItemByCheckbox(d[s]);n.classList.remove("treeselect-list__item--focused");var s="ArrowDown"===e?s+1:s-1,i="ArrowDown"===e?0:d.length-1,i=d[s]??d[i],s=!d[s];const a=getListItemByCheckbox(i);a.classList.add("treeselect-list__item--focused");var i=this.srcElement.getBoundingClientRect(),l=a.getBoundingClientRect();s&&"ArrowDown"===e?this.srcElement.scroll(0,0):s&&"ArrowUp"===e?this.srcElement.scroll(0,this.srcElement.scrollHeight):i.y+i.height<l.y+l.height?this.srcElement.scroll(0,this.srcElement.scrollTop+l.height):i.y>l.y&&this.srcElement.scroll(0,this.srcElement.scrollTop-l.height)}else{const h=getListItemByCheckbox(d[0]);h.classList.add("treeselect-list__item--focused")}}}focusFirstListElement(){var e="treeselect-list__item--focused";const t=this.srcElement.querySelector("."+e);var s=Array.from(this.srcElement.querySelectorAll(".treeselect-list__item-checkbox")).filter(e=>"none"!==window.getComputedStyle(getListItemByCheckbox(e)).display);if(s.length){t&&t.classList.remove(e);const i=getListItemByCheckbox(s[0]);i.classList.add(e)}}#createList(){const e=[],t=document.createElement("div");t.classList.add("treeselect-list");var s=this.#getListHTML(this.options);if(e.push(...s),this.listSlotHtmlComponent){const i=document.createElement("div");i.classList.add("treeselect-list__slot"),i.appendChild(this.listSlotHtmlComponent),e.push(i)}s=this.#createEmptyList();return e.push(s),t.addEventListener("mouseout",e=>{e.stopPropagation(),this.#lastFocusedItem&&this.#isMouseActionsAvailable&&this.#lastFocusedItem.classList.add("treeselect-list__item--focused")}),t.addEventListener("mousemove",()=>{this.#isMouseActionsAvailable=!0}),t.append(...e),t}#getListHTML(e){return e.reduce((e,t)=>{if(t.children?.length){const i=this.#createGroupContainer(t);var s=this.#getListHTML(t.children);return i.append(...s),e.push(i),e}s=this.#createGroupItem(t,!1);return e.push(s),e},[])}#createGroupContainer(e){const t=document.createElement("div");t.setAttribute("group-container-id",e.value),t.classList.add("treeselect-list__group-container");e=this.#createGroupItem(e,!0);return t.appendChild(e),t}#createGroupItem(s,e){const t=document.createElement("div");t.setAttribute("tabindex","-1"),t.setAttribute("title",s.name),t.classList.add("treeselect-list__item"),e&&(e=this.#createArrow(),t.appendChild(e)),t.addEventListener("mouseover",()=>{this.#isMouseActionsAvailable&&this.#groupMouseAction(!0,t)},!0),t.addEventListener("mouseout",()=>{this.#isMouseActionsAvailable&&(this.#groupMouseAction(!1,t),this.#lastFocusedItem=t)},!0),t.addEventListener("mousedown",e=>{e.stopPropagation();const t=e.target.querySelector(".treeselect-list__item-checkbox");t.checked=!t.checked,this.#checkboxClickEvent(t,s)});var e=this.#createCheckbox(s),i=this.#createCheckboxLabel(s);return t.append(e,i),t}#createArrow(){const e=document.createElement("span");return e.setAttribute("tabindex","-1"),e.classList.add("treeselect-list__item-icon"),e.innerHTML=svg.arrowDown,e.addEventListener("mousedown",e=>{e.stopPropagation(),this.#arrowClickEvent(e)}),e}#createCheckbox(e){const t=document.createElement("div"),s=(t.classList.add("treeselect-list__item-checkbox-container"),document.createElement("span")),i=(s.classList.add("treeselect-list__item-checkbox-icon"),s.innerHTML="",document.createElement("input"));return i.setAttribute("tabindex","-1"),i.setAttribute("type","checkbox"),i.setAttribute("input-id",e.value),i.classList.add("treeselect-list__item-checkbox"),t.append(s,i),t}#createCheckboxLabel(e){const t=document.createElement("label");return t.innerHTML=e.name,t.classList.add("treeselect-list__item-label"),t}#createEmptyList(){const e=document.createElement("div"),t=(e.classList.add("treeselect-list__empty"),e.setAttribute("title",this.emptyText),document.createElement("span")),s=(t.classList.add("treeselect-list__empty-icon"),t.innerHTML=svg.attention,document.createElement("span"));return s.classList.add("treeselect-list__empty-text"),s.innerHTML=this.emptyText,e.append(t,s),e}#checkboxClickEvent(e,t){const s=this.flattedOptions.find(e=>e.id===t.value);s.checked=e.checked,s.isPartialChecked=!1,checkInput(s,this.flattedOptions),updateDOM(this.flattedOptions,this.srcElement),this.#emitInput()}#arrowClickEvent(e){const t=e.target.parentNode.querySelector("[input-id]"),s=t.getAttribute("input-id"),i=this.flattedOptions.find(e=>e.id===s);i.isClosed=!i.isClosed,hideShowChildren(this.flattedOptions,i),updateDOM(this.flattedOptions,this.srcElement),this.#emitArrrowClick()}#groupMouseAction(e,t){const s="treeselect-list__item--focused";if(e){const i=Array.from(this.srcElement.querySelectorAll("."+s));i.length&&i.forEach(e=>e.classList.remove(s)),t.classList.add(s)}else t.classList.remove(s)}#updateSelectedNodes(){this.selectedNodes={ids:getCheckedValues(this.flattedOptions),groupedIds:getGroupedValues(this.flattedOptions)}}#emitArrrowClick(){this.srcElement.dispatchEvent(new CustomEvent("arrow-click"))}#emitInput(){this.#updateSelectedNodes(),this.srcElement.dispatchEvent(new CustomEvent("input",{detail:this.selectedNodes}))}}export default TreeselectList;
1
+ import svg from"./svgIcons.js";const getFlatOptions=(e,i,l=0,c=0)=>e.reduce((e,t)=>{var s=!!t.children?.length;return e.push({id:t.value,name:t.name,childOf:l,isGroup:s,checked:!1,level:c,isClosed:i<=c&&s,hidden:i<c}),s&&(s=getFlatOptions(t.children,i,t.value,c+1),e.push(...s)),e},[]),checkAllChildrenInputs=({id:t,checked:s},i)=>{i.forEach(e=>{e.childOf===t&&(e.checked=s,e.isPartialChecked=!1,e.isGroup&&checkAllChildrenInputs(e,i))})},checkAllParentInputs=(t,e)=>{const s=e.find(e=>e.id===t),i=e.filter(e=>e.childOf===s.id);var l=i.every(e=>e.checked),c=i.some(e=>e.isPartialChecked||e.checked)&&!l,r=!l&&!c;l&&(s.checked=!0,s.isPartialChecked=!1),c&&(s.checked=!1,s.isPartialChecked=!0),r&&(s.checked=!1,s.isPartialChecked=!1),s.childOf&&checkAllParentInputs(s.childOf,e)},checkInput=({id:e,isGroup:t,childOf:s,checked:i},l)=>{t&&checkAllChildrenInputs({id:e,checked:i},l),s&&checkAllParentInputs(s,l)},updateValue=(t,s,e)=>{s.forEach(e=>{e.checked=!1,e.isPartialChecked=!1});const i=s.filter(e=>t.includes(e.id));i.forEach(e=>{e.checked=!0,e.isPartialChecked=!1,checkInput(e,s)}),updateDOM(s,e)},hideShowChildren=(t,{id:s,isClosed:i})=>{const e=t.filter(e=>e.childOf===s);e.forEach(e=>{e.hidden=i,e.isGroup&&!e.isClosed&&hideShowChildren(t,{id:e.id,isClosed:i})})},updateDOM=(l,c)=>{l.forEach(e=>{const t=c.querySelector(`[input-id="${e.id}"]`),s=getListItemByCheckbox(t);if(t.checked=e.checked,e.checked?s.classList.add("treeselect-list__item--checked"):s.classList.remove("treeselect-list__item--checked"),e.isPartialChecked?s.classList.add("treeselect-list__item--partial-checked"):s.classList.remove("treeselect-list__item--partial-checked"),e.isGroup){const i=s.querySelector(".treeselect-list__item-icon");e.isClosed?(s.classList.add("treeselect-list__item--closed"),i.innerHTML=svg.arrowRight):(s.classList.remove("treeselect-list__item--closed"),i.innerHTML=svg.arrowDown)}e.hidden?s.classList.add("treeselect-list__item--hidden"):s.classList.remove("treeselect-list__item--hidden"),updateLeftPaddingItems(e,s,l),updateCheckboxClasses(e,t)});var e=l.some(e=>!e.hidden);const t=c.querySelector(".treeselect-list__empty");e?t.classList.add("treeselect-list__empty--hidden"):t.classList.remove("treeselect-list__empty--hidden")},updateLeftPaddingItems=(t,e,s)=>{0===t.level?(s=s.some(e=>e.isGroup&&e.level===t.level),s=!t.isGroup&&s?"20px":"5px",e.style.paddingLeft=t.isGroup?"0":s):e.style.paddingLeft=t.isGroup?20*t.level+"px":20*t.level+20+"px",e.setAttribute("level",t.level),e.setAttribute("group",t.isGroup)},updateCheckboxClasses=(e,t)=>{const s=t.parentNode,i=s.querySelector(".treeselect-list__item-checkbox-icon");e.checked?i.innerHTML=svg.check:e.isPartialChecked?i.innerHTML=svg.partialCheck:i.innerHTML=""},getAllFlattedChildren=(s,i)=>i.reduce((e,t)=>(t.childOf===s&&(e.push(t),t.isGroup&&e.push(...getAllFlattedChildren(t.id,i))),e),[]),getAllFlattenParents=(s,i)=>i.reduce((e,t)=>(t.id===s&&(e.push(t),t.childOf&&e.push(...getAllFlattenParents(t.childOf,i))),e),[]),getGroupedValues=e=>{const{onlyGroupsIds:t,allItems:s}=e.reduce((e,t)=>(t.checked&&(t.isGroup&&e.onlyGroupsIds.push(t.id),e.allItems.push(t)),e),{onlyGroupsIds:[],allItems:[]});return s.filter(e=>!t.includes(e.childOf))},getCheckedValues=e=>e.filter(e=>e.checked&&!e.isGroup),getListItemByCheckbox=e=>{return e.parentNode.parentNode},validateOptions=e=>{const t=e.reduce((e,t)=>(e.allItems.includes(t.id)&&e.duplications.push(t.id),e.allItems.push(t.id),e),{duplications:[],allItems:[]})["duplications"];t.length&&console.error(`Validation: You have duplicated values: ${t.join(", ")}! You should use unique values.`)};class TreeselectList{#lastFocusedItem=null;#isMouseActionsAvailable=!0;constructor({options:e,value:t,openLevel:s,listSlotHtmlComponent:i,emptyText:l}){this.options=e,this.value=t,this.searchText="",this.openLevel=s??0,this.listSlotHtmlComponent=i,this.emptyText=l??"No results found...",this.flattedOptions=getFlatOptions(this.options,this.openLevel),this.flattedOptionsBeforeSearch=this.flattedOptions,this.selectedNodes={ids:[],groupedIds:[]},this.srcElement=this.#createList(),this.updateValue(this.value),validateOptions(this.flattedOptions)}updateValue(e){updateValue(e,this.flattedOptions,this.srcElement),this.#updateSelectedNodes()}updateSearchValue(i){if(i!==this.searchText){var e=""===this.searchText&&""!==i;if(this.searchText=i,e&&(this.flattedOptionsBeforeSearch=JSON.parse(JSON.stringify(this.flattedOptions))),""===this.searchText)return this.flattedOptions=this.flattedOptionsBeforeSearch.map(t=>{const e=this.flattedOptions.find(e=>e.id===t.id);return e.isClosed=t.isClosed,e.hidden=t.hidden,e}),this.flattedOptionsBeforeSearch=[],updateDOM(this.flattedOptions,this.srcElement),void this.focusFirstListElement();const s=this.flattedOptions.reduce((e,t)=>{var s;return t.name.toLowerCase().includes(i.toLowerCase())&&(e.push(t),t.isGroup&&(s=getAllFlattedChildren(t.id,this.flattedOptions),e.push(...s)),t.childOf&&(s=getAllFlattenParents(t.childOf,this.flattedOptions),e.push(...s))),e},[]);this.flattedOptions.forEach(t=>{s.some(e=>e.id===t.id)?(t.isGroup&&(t.isClosed=!1,hideShowChildren(this.flattedOptions,t)),t.hidden=!1):t.hidden=!0}),updateDOM(this.flattedOptions,this.srcElement),this.focusFirstListElement()}}callKeyAction(e){this.#isMouseActionsAvailable=!1;const t=this.srcElement.querySelector(".treeselect-list__item--focused");if("Enter"===e&&t&&t.dispatchEvent(new Event("mousedown")),"ArrowLeft"===e||"ArrowRight"===e){if(!t)return;const c=t.querySelector(".treeselect-list__item-checkbox"),r=c.getAttribute("input-id");var s=this.flattedOptions.find(e=>e.id===r);const o=t.querySelector(".treeselect-list__item-icon");"ArrowLeft"!==e||s.isClosed||o.dispatchEvent(new Event("mousedown")),"ArrowRight"===e&&s.isClosed&&o.dispatchEvent(new Event("mousedown"))}if("ArrowDown"===e||"ArrowUp"===e){const d=Array.from(this.srcElement.querySelectorAll(".treeselect-list__item-checkbox")).filter(e=>"none"!==window.getComputedStyle(getListItemByCheckbox(e)).display);if(d.length)if(t){s=d.findIndex(e=>getListItemByCheckbox(e).classList.contains("treeselect-list__item--focused"));const n=getListItemByCheckbox(d[s]);n.classList.remove("treeselect-list__item--focused");var s="ArrowDown"===e?s+1:s-1,i="ArrowDown"===e?0:d.length-1,i=d[s]??d[i],s=!d[s];const a=getListItemByCheckbox(i);a.classList.add("treeselect-list__item--focused");var i=this.srcElement.getBoundingClientRect(),l=a.getBoundingClientRect();s&&"ArrowDown"===e?this.srcElement.scroll(0,0):s&&"ArrowUp"===e?this.srcElement.scroll(0,this.srcElement.scrollHeight):i.y+i.height<l.y+l.height?this.srcElement.scroll(0,this.srcElement.scrollTop+l.height):i.y>l.y&&this.srcElement.scroll(0,this.srcElement.scrollTop-l.height)}else{const h=getListItemByCheckbox(d[0]);h.classList.add("treeselect-list__item--focused")}}}focusFirstListElement(){var e="treeselect-list__item--focused";const t=this.srcElement.querySelector("."+e);var s=Array.from(this.srcElement.querySelectorAll(".treeselect-list__item-checkbox")).filter(e=>"none"!==window.getComputedStyle(getListItemByCheckbox(e)).display);if(s.length){t&&t.classList.remove(e);const i=getListItemByCheckbox(s[0]);i.classList.add(e)}}#createList(){const e=[],t=document.createElement("div");t.classList.add("treeselect-list");var s=this.#getListHTML(this.options);if(e.push(...s),this.listSlotHtmlComponent){const i=document.createElement("div");i.classList.add("treeselect-list__slot"),i.appendChild(this.listSlotHtmlComponent),e.push(i)}s=this.#createEmptyList();return e.push(s),t.addEventListener("mouseout",e=>{e.stopPropagation(),this.#lastFocusedItem&&this.#isMouseActionsAvailable&&this.#lastFocusedItem.classList.add("treeselect-list__item--focused")}),t.addEventListener("mousemove",()=>{this.#isMouseActionsAvailable=!0}),t.append(...e),t}#getListHTML(e){return e.reduce((e,t)=>{if(t.children?.length){const i=this.#createGroupContainer(t);var s=this.#getListHTML(t.children);return i.append(...s),e.push(i),e}s=this.#createGroupItem(t,!1);return e.push(s),e},[])}#createGroupContainer(e){const t=document.createElement("div");t.setAttribute("group-container-id",e.value),t.classList.add("treeselect-list__group-container");e=this.#createGroupItem(e,!0);return t.appendChild(e),t}#createGroupItem(s,e){const t=document.createElement("div");t.setAttribute("tabindex","-1"),t.setAttribute("title",s.name),t.classList.add("treeselect-list__item"),e&&(e=this.#createArrow(),t.appendChild(e)),t.addEventListener("mouseover",()=>{this.#isMouseActionsAvailable&&this.#groupMouseAction(!0,t)},!0),t.addEventListener("mouseout",()=>{this.#isMouseActionsAvailable&&(this.#groupMouseAction(!1,t),this.#lastFocusedItem=t)},!0),t.addEventListener("mousedown",e=>{e.stopPropagation();const t=e.target.querySelector(".treeselect-list__item-checkbox");t.checked=!t.checked,this.#checkboxClickEvent(t,s)});var e=this.#createCheckbox(s),i=this.#createCheckboxLabel(s);return t.append(e,i),t}#createArrow(){const e=document.createElement("span");return e.setAttribute("tabindex","-1"),e.classList.add("treeselect-list__item-icon"),e.innerHTML=svg.arrowDown,e.addEventListener("mousedown",e=>{e.stopPropagation(),this.#arrowClickEvent(e)}),e}#createCheckbox(e){const t=document.createElement("div"),s=(t.classList.add("treeselect-list__item-checkbox-container"),document.createElement("span")),i=(s.classList.add("treeselect-list__item-checkbox-icon"),s.innerHTML="",document.createElement("input"));return i.setAttribute("tabindex","-1"),i.setAttribute("type","checkbox"),i.setAttribute("input-id",e.value),i.classList.add("treeselect-list__item-checkbox"),t.append(s,i),t}#createCheckboxLabel(e){const t=document.createElement("label");return t.innerHTML=e.name,t.classList.add("treeselect-list__item-label"),t}#createEmptyList(){const e=document.createElement("div"),t=(e.classList.add("treeselect-list__empty"),e.setAttribute("title",this.emptyText),document.createElement("span")),s=(t.classList.add("treeselect-list__empty-icon"),t.innerHTML=svg.attention,document.createElement("span"));return s.classList.add("treeselect-list__empty-text"),s.innerHTML=this.emptyText,e.append(t,s),e}#checkboxClickEvent(e,t){const s=this.flattedOptions.find(e=>e.id===t.value);s.checked=e.checked,s.isPartialChecked=!1,checkInput(s,this.flattedOptions),updateDOM(this.flattedOptions,this.srcElement),this.#emitInput()}#arrowClickEvent(e){const t=e.target.parentNode.querySelector("[input-id]"),s=t.getAttribute("input-id"),i=this.flattedOptions.find(e=>e.id===s);i.isClosed=!i.isClosed,hideShowChildren(this.flattedOptions,i),updateDOM(this.flattedOptions,this.srcElement),this.#emitArrowClick()}#groupMouseAction(e,t){const s="treeselect-list__item--focused";if(e){const i=Array.from(this.srcElement.querySelectorAll("."+s));i.length&&i.forEach(e=>e.classList.remove(s)),t.classList.add(s)}else t.classList.remove(s)}#updateSelectedNodes(){this.selectedNodes={ids:getCheckedValues(this.flattedOptions),groupedIds:getGroupedValues(this.flattedOptions)}}#emitArrowClick(){this.srcElement.dispatchEvent(new CustomEvent("arrow-click"))}#emitInput(){this.#updateSelectedNodes(),this.srcElement.dispatchEvent(new CustomEvent("input",{detail:this.selectedNodes}))}}export default TreeselectList;
@@ -1,8 +1,8 @@
1
1
  @import './input.css';@import './list.css';.treeselect{width:100%;position:relative;box-sizing:border-box}
2
- .treeselect--disabled{pointer-events:none}.treeselect-list{position:absolute;left:0;border-radius:4px;box-sizing:border-box;z-index:1050}
3
- .treeselect .treeselect-list{position:absolute}.treeselect-input--focused{border-color:#101010}
4
- .treeselect-input--opened.treeselect-input--top{border-top-color:transparent;border-top-left-radius:0;border-top-right-radius:0}
2
+ .treeselect--disabled{pointer-events:none}.treeselect-list{position:absolute;left:0;border-radius:4px;box-sizing:border-box;z-index:1000}
3
+ .treeselect .treeselect-list{position:absolute}.treeselect .treeselect-list--static{position:static}
4
+ .treeselect-input--focused{border-color:#101010}.treeselect-input--opened.treeselect-input--top{border-top-color:transparent;border-top-left-radius:0;border-top-right-radius:0}
5
5
  .treeselect-input--opened.treeselect-input--bottom{border-bottom-color:transparent;border-bottom-left-radius:0;border-bottom-right-radius:0}
6
6
  .treeselect-list--focused{border-color:#101010}.treeselect-list--top,.treeselect-list--top-to-body{border-bottom-color:#d7dde4;border-bottom-left-radius:0;border-bottom-right-radius:0}
7
- .treeselect-list--bottom,.treeselect-list--bottom-to-body{border-top-color:gainsboro;border-top-left-radius:0;border-top-right-radius:0}
7
+ .treeselect-list--bottom,.treeselect-list--bottom-to-body{border-top-color:#d7dde4;border-top-left-radius:0;border-top-right-radius:0}
8
8
  .treeselect-list--top{left:0;bottom:100%}.treeselect-list--bottom{left:0;top:100%}
@@ -1 +1 @@
1
- import TreeselectInput from"./input.js";import TreeselectList from"./list.js";class Treeselect{#htmlContainer=null;#treeselectList=null;#treeselectInput=null;#transform={top:null,bottom:null};#treeselectInitPosition=null;#containerResizer=null;#containerWidth=0;#scrollEvent=null;#focusEvent=null;#blurEvent=null;constructor({parentHtmlContainer:e,value:t,options:s,openLevel:i,appendToBody:n,alwaysOpen:l,showTags:r,clearable:o,searchable:c,placeholder:a,grouped:d,listSlotHtmlComponent:h,disabled:u,emptyText:p}){this.parentHtmlContainer=e,this.value=t??[],this.options=s??[],this.openLevel=i??0,this.appendToBody=n??!0,this.alwaysOpen=l&&!u,this.showTags=r??!0,this.clearable=o??!0,this.searchable=c??!0,this.placeholder=a??"Search...",this.grouped=d??!0,this.listSlotHtmlComponent=h??null,this.disabled=u??!1,this.emptyText=p??"No results found...",this.srcElement=null,this.mount()}mount(){this.srcElement&&(this.#closeList(),this.srcElement.innerHTML="",this.srcElement=null,this.#removeOutsideListeners()),this.srcElement=this.#createTreeselect(),this.#scrollEvent=this.scrollWindowHandler.bind(this),this.#focusEvent=this.focusWindowHandler.bind(this),this.#blurEvent=this.blurWindowHandler.bind(this),this.alwaysOpen&&this.#treeselectInput.openClose(),this.disabled&&this.srcElement.classList.add("treeselect--disabled")}updateValue(e){const t=this.#treeselectList;t.updateValue(e);var{groupedIds:e,ids:s}=t.selectedNodes,e=this.grouped?e:s;this.#treeselectInput.updateValue(e)}destroy(){this.srcElement&&(this.#closeList(),this.srcElement.innerHTML="",this.srcElement=null,this.#removeOutsideListeners())}#createTreeselect(){const t=this.parentHtmlContainer,s=(t.classList.add("treeselect"),new TreeselectList({options:this.options,value:this.value,openLevel:this.openLevel,listSlotHtmlComponent:this.listSlotHtmlComponent,emptyText:this.emptyText}));var{groupedIds:e,ids:i}=s.selectedNodes;const n=new TreeselectInput({value:this.grouped?e:i,showTags:this.showTags,clearable:this.clearable,isAlwaysOpened:this.alwaysOpen,searchable:this.searchable,placeholder:this.placeholder,disabled:this.disabled});return this.appendToBody&&(this.#containerResizer=new ResizeObserver(()=>{var e=this.srcElement.getBoundingClientRect()["width"];this.#containerWidth=e,this.updateListPosition(t,s.srcElement,!0)})),n.srcElement.addEventListener("input",e=>{e=e.detail.map(({id:e})=>e);this.value=e,s.updateValue(e),this.#emitInput()}),n.srcElement.addEventListener("open",()=>this.#openList()),n.srcElement.addEventListener("keydown",e=>s.callKeyAction(e.key)),n.srcElement.addEventListener("search",e=>{s.updateSearchValue(e.detail),this.updateListPosition(t,s.srcElement,!0)}),n.srcElement.addEventListener("focus",()=>{this.#updateFocusClasses(!0),document.addEventListener("mousedown",this.#focusEvent,!0),document.addEventListener("focus",this.#focusEvent,!0),window.addEventListener("blur",this.#blurEvent)},!0),this.alwaysOpen||n.srcElement.addEventListener("close",()=>{this.#closeList()}),s.srcElement.addEventListener("mouseup",()=>{n.focus()},!0),s.srcElement.addEventListener("input",e=>{const{groupedIds:t,ids:s}=e.detail;e=this.grouped?t:s;n.updateValue(e),this.value=s.map(({id:e})=>e),n.focus(),this.#emitInput()}),s.srcElement.addEventListener("arrow-click",()=>{n.focus(),this.updateListPosition(t,s.srcElement,!0)}),this.#htmlContainer=t,this.#treeselectList=s,this.#treeselectInput=n,t.append(n.srcElement),t}#openList(){window.addEventListener("scroll",this.#scrollEvent,!0),this.appendToBody?(document.body.appendChild(this.#treeselectList.srcElement),this.#containerResizer.observe(this.#htmlContainer)):this.#htmlContainer.appendChild(this.#treeselectList.srcElement),this.updateListPosition(this.#htmlContainer,this.#treeselectList.srcElement,!1),this.#updateOpenCloseClasses(!0),this.#treeselectList.focusFirstListElement()}#closeList(){window.removeEventListener("scroll",this.#scrollEvent,!0),(this.appendToBody?document.body:this.#htmlContainer).contains(this.#treeselectList.srcElement)&&(this.appendToBody?(document.body.removeChild(this.#treeselectList.srcElement),this.#containerResizer?.disconnect()):this.#htmlContainer.removeChild(this.#treeselectList.srcElement),this.#updateOpenCloseClasses(!1))}#updateDirectionClasses(e,t){var s=t?"treeselect-list--top-to-body":"treeselect-list--top",t=t?"treeselect-list--bottom-to-body":"treeselect-list--bottom";e?(this.#treeselectList.srcElement.classList.add(s),this.#treeselectList.srcElement.classList.remove(t),this.#treeselectInput.srcElement.classList.add("treeselect-input--top"),this.#treeselectInput.srcElement.classList.remove("treeselect-input--bottom")):(this.#treeselectList.srcElement.classList.remove(s),this.#treeselectList.srcElement.classList.add(t),this.#treeselectInput.srcElement.classList.remove("treeselect-input--top"),this.#treeselectInput.srcElement.classList.add("treeselect-input--bottom"))}#updateFocusClasses(e){e?(this.#treeselectInput.srcElement.classList.add("treeselect-input--focused"),this.#treeselectList.srcElement.classList.add("treeselect-list--focused")):(this.#treeselectInput.srcElement.classList.remove("treeselect-input--focused"),this.#treeselectList.srcElement.classList.remove("treeselect-list--focused"))}#updateOpenCloseClasses(e){e?this.#treeselectInput.srcElement.classList.add("treeselect-input--opened"):this.#treeselectInput.srcElement.classList.remove("treeselect-input--opened")}#removeOutsideListeners(){window.removeEventListener("scroll",this.#scrollEvent,!0),document.removeEventListener("click",this.#focusEvent,!0),document.removeEventListener("focus",this.#focusEvent,!0),window.removeEventListener("blur",this.#blurEvent)}scrollWindowHandler(){this.updateListPosition(this.#htmlContainer,this.#treeselectList.srcElement,!0)}focusWindowHandler(e){this.#htmlContainer.contains(e.target)||this.#treeselectList.srcElement.contains(e.target)||(this.#treeselectInput.blur(),this.#removeOutsideListeners(),this.#updateFocusClasses(!1))}blurWindowHandler(){this.#treeselectInput.blur(),this.#removeOutsideListeners(),this.#updateFocusClasses(!1)}updateListPosition(e,t,s){var i=e.getBoundingClientRect().y,n=window.innerHeight-e.getBoundingClientRect().y,l=t.clientHeight,n=n<i&&window.innerHeight-i<l+45,i=n?"top":"buttom",r=t.getAttribute("direction");if(this.#htmlContainer.setAttribute("direction",i),!this.appendToBody)return r===i?void 0:void this.#updateDirectionClasses(n,!1);if(!this.#treeselectInitPosition||s){t.style.transform=null;const{x:d,y:o}=t.getBoundingClientRect(),{x:c,y:a}=e.getBoundingClientRect();this.#treeselectInitPosition={containerX:c,containerY:a,listX:d,listY:o}}const{listY:o,containerX:c,containerY:a}=this.#treeselectInitPosition;i=e.clientHeight;r&&!s||(this.#transform.top=`translateY(${a-o-l}px)`,this.#transform.bottom=`translateY(${a+i-o}px)`),t.style.transform=n?this.#transform.top:this.#transform.bottom,this.#updateDirectionClasses(n,!0),t.style.width=this.#containerWidth+"px",t.style.left=c+"px"}#emitInput(){this.srcElement.dispatchEvent(new CustomEvent("input",{detail:this.value}))}}export default Treeselect;
1
+ import TreeselectInput from"./input.js";import TreeselectList from"./list.js";const validateProps=({parentHtmlContainer:e,staticList:t,appendToBody:s})=>{e||console.error("Validation: parentHtmlContainer prop is required!"),t&&s&&console.error("Validation: You should set staticList to false if you use appendToBody!")};class Treeselect{#htmlContainer=null;#treeselectList=null;#treeselectInput=null;#containerResizer=null;#scrollEvent=null;#resizeEvent=null;#focusEvent=null;#blurEvent=null;#isListOpened=!1;constructor({parentHtmlContainer:e,value:t,options:s,openLevel:i,appendToBody:n,alwaysOpen:l,showTags:r,clearable:o,searchable:a,placeholder:c,grouped:d,listSlotHtmlComponent:h,disabled:p,emptyText:u,staticList:m}){validateProps({parentHtmlContainer:e,staticList:m,appendToBody:n}),this.parentHtmlContainer=e,this.value=t??[],this.options=s??[],this.openLevel=i??0,this.appendToBody=n??!0,this.alwaysOpen=l&&!p,this.showTags=r??!0,this.clearable=o??!0,this.searchable=a??!0,this.placeholder=c??"Search...",this.grouped=d??!0,this.listSlotHtmlComponent=h??null,this.disabled=p??!1,this.emptyText=u??"No results found...",this.staticList=m&&!this.appendToBody,this.srcElement=null,this.mount()}mount(){this.destroy(),this.srcElement=this.#createTreeselect(),this.#scrollEvent=this.scrollWindowHandler.bind(this),this.#resizeEvent=this.scrollWindowHandler.bind(this),this.#focusEvent=this.focusWindowHandler.bind(this),this.#blurEvent=this.blurWindowHandler.bind(this),this.alwaysOpen&&this.#treeselectInput.openClose(),this.disabled&&this.srcElement.classList.add("treeselect--disabled")}updateValue(e){const t=this.#treeselectList;t.updateValue(e);var{groupedIds:e,ids:s}=t.selectedNodes,e=this.grouped?e:s;this.#treeselectInput.updateValue(e)}destroy(){this.srcElement&&(this.#closeList(),this.srcElement.innerHTML="",this.srcElement=null,this.#removeOutsideListeners(!0))}#createTreeselect(){const e=this.parentHtmlContainer,t=(e.classList.add("treeselect"),new TreeselectList({options:this.options,value:this.value,openLevel:this.openLevel,listSlotHtmlComponent:this.listSlotHtmlComponent,emptyText:this.emptyText}));var{groupedIds:s,ids:i}=t.selectedNodes;const n=new TreeselectInput({value:this.grouped?s:i,showTags:this.showTags,clearable:this.clearable,isAlwaysOpened:this.alwaysOpen,searchable:this.searchable,placeholder:this.placeholder,disabled:this.disabled});return this.appendToBody&&(this.#containerResizer=new ResizeObserver(()=>{this.updateListPosition()})),n.srcElement.addEventListener("input",e=>{e=e.detail.map(({id:e})=>e);this.value=e,t.updateValue(e),this.#emitInput()}),n.srcElement.addEventListener("open",()=>this.#openList()),n.srcElement.addEventListener("keydown",e=>{this.#isListOpened&&t.callKeyAction(e.key)}),n.srcElement.addEventListener("search",e=>{t.updateSearchValue(e.detail),this.updateListPosition()}),n.srcElement.addEventListener("focus",()=>{this.#updateFocusClasses(!0),document.addEventListener("mousedown",this.#focusEvent,!0),document.addEventListener("focus",this.#focusEvent,!0),window.addEventListener("blur",this.#blurEvent)},!0),this.alwaysOpen||n.srcElement.addEventListener("close",()=>{this.#closeList()}),t.srcElement.addEventListener("mouseup",()=>{n.focus()},!0),t.srcElement.addEventListener("input",e=>{const{groupedIds:t,ids:s}=e.detail;e=this.grouped?t:s;n.updateValue(e),this.value=s.map(({id:e})=>e),n.focus(),this.#emitInput()}),t.srcElement.addEventListener("arrow-click",()=>{n.focus(),this.updateListPosition()}),this.#htmlContainer=e,this.#treeselectList=t,this.#treeselectInput=n,e.append(n.srcElement),e}#openList(){this.#isListOpened=!0,window.addEventListener("scroll",this.#scrollEvent,!0),window.addEventListener("resize",this.#resizeEvent),this.appendToBody?(document.body.appendChild(this.#treeselectList.srcElement),this.#containerResizer.observe(this.#htmlContainer)):this.#htmlContainer.appendChild(this.#treeselectList.srcElement),this.updateListPosition(),this.#updateOpenCloseClasses(!0),this.#treeselectList.focusFirstListElement()}#closeList(){this.#isListOpened=!1,window.removeEventListener("scroll",this.#scrollEvent,!0),window.removeEventListener("resize",this.#resizeEvent),(this.appendToBody?document.body:this.#htmlContainer).contains(this.#treeselectList.srcElement)&&(this.appendToBody?(document.body.removeChild(this.#treeselectList.srcElement),this.#containerResizer?.disconnect()):this.#htmlContainer.removeChild(this.#treeselectList.srcElement),this.#updateOpenCloseClasses(!1))}#updateDirectionClasses(e,t){var s=t?"treeselect-list--top-to-body":"treeselect-list--top",t=t?"treeselect-list--bottom-to-body":"treeselect-list--bottom";e?(this.#treeselectList.srcElement.classList.add(s),this.#treeselectList.srcElement.classList.remove(t),this.#treeselectInput.srcElement.classList.add("treeselect-input--top"),this.#treeselectInput.srcElement.classList.remove("treeselect-input--bottom")):(this.#treeselectList.srcElement.classList.remove(s),this.#treeselectList.srcElement.classList.add(t),this.#treeselectInput.srcElement.classList.remove("treeselect-input--top"),this.#treeselectInput.srcElement.classList.add("treeselect-input--bottom"))}#updateFocusClasses(e){e?(this.#treeselectInput.srcElement.classList.add("treeselect-input--focused"),this.#treeselectList.srcElement.classList.add("treeselect-list--focused")):(this.#treeselectInput.srcElement.classList.remove("treeselect-input--focused"),this.#treeselectList.srcElement.classList.remove("treeselect-list--focused"))}#updateOpenCloseClasses(e){e?this.#treeselectInput.srcElement.classList.add("treeselect-input--opened"):this.#treeselectInput.srcElement.classList.remove("treeselect-input--opened"),this.staticList?this.#treeselectList.srcElement.classList.add("treeselect-list--static"):this.#treeselectList.srcElement.classList.remove("treeselect-list--static")}#removeOutsideListeners(e){this.alwaysOpen&&!e||(window.removeEventListener("scroll",this.#scrollEvent,!0),window.removeEventListener("resize",this.#resizeEvent)),document.removeEventListener("click",this.#focusEvent,!0),document.removeEventListener("focus",this.#focusEvent,!0),window.removeEventListener("blur",this.#blurEvent)}scrollWindowHandler(){this.updateListPosition()}focusWindowHandler(e){this.#htmlContainer.contains(e.target)||this.#treeselectList.srcElement.contains(e.target)||(this.#treeselectInput.blur(),this.#removeOutsideListeners(),this.#updateFocusClasses(!1))}blurWindowHandler(){this.#treeselectInput.blur(),this.#removeOutsideListeners(),this.#updateFocusClasses(!1)}updateListPosition(){const e=this.#treeselectList.srcElement,t=(e.style.transform=null,this.#htmlContainer);var{y:s,height:i}=e.getBoundingClientRect(),{x:n,y:l,height:r,width:o}=t.getBoundingClientRect(),a=window.innerHeight-l-r,a=a<l&&i<=l&&a<i,i=(this.appendToBody&&(e.style.transform=a?`translateY(${l-s-i}px)`:`translateY(${l+r-s}px)`,e.style.width=o+"px",e.style.left=n+window.scrollX+"px"),a?"top":"bottom");e.getAttribute("direction")!==i&&(e.setAttribute("direction",i),this.#updateDirectionClasses(a,this.appendToBody))}#emitInput(){this.srcElement.dispatchEvent(new CustomEvent("input",{detail:this.value}))}}export default Treeselect;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "treeselectjs",
3
- "version": "0.2.5",
3
+ "version": "0.2.8",
4
4
  "description": "Treeselect JS",
5
5
  "main": "dist/treeselect-js.js",
6
6
  "repository": {
package/src/input.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import svg from './svgIcons.js'
2
2
 
3
3
  class TreeselectInput {
4
- #htmlTagsSecton = null
4
+ #htmlTagsSection = null
5
5
  #htmlEditControl = null
6
6
  #htmlOperators = null
7
7
  #htmlArrow = null
@@ -77,12 +77,12 @@ class TreeselectInput {
77
77
  }
78
78
 
79
79
  #updateTags () {
80
- this.#htmlTagsSecton.innerHTML = ''
80
+ this.#htmlTagsSection.innerHTML = ''
81
81
 
82
82
  if (this.showTags) {
83
- this.#htmlTagsSecton.append(...this.#createTags())
83
+ this.#htmlTagsSection.append(...this.#createTags())
84
84
  } else {
85
- this.#htmlTagsSecton.appendChild(this.#createCountElement())
85
+ this.#htmlTagsSection.appendChild(this.#createCountElement())
86
86
  }
87
87
  }
88
88
 
@@ -142,7 +142,7 @@ class TreeselectInput {
142
142
  container.classList.add('treeselect-input')
143
143
  container.setAttribute('tabindex', '-1')
144
144
 
145
- this.#htmlTagsSecton = this.#createTagsSection()
145
+ this.#htmlTagsSection = this.#createTagsSection()
146
146
  this.#htmlEditControl = this.#createControl()
147
147
  this.#htmlOperators = this.#createOperators()
148
148
 
@@ -156,7 +156,7 @@ class TreeselectInput {
156
156
  this.focus()
157
157
  })
158
158
 
159
- container.append(this.#htmlTagsSecton, this.#htmlEditControl, this.#htmlOperators)
159
+ container.append(this.#htmlTagsSection, this.#htmlEditControl, this.#htmlOperators)
160
160
 
161
161
  return container
162
162
  }
package/src/list.css CHANGED
@@ -19,6 +19,10 @@
19
19
  height: 30px;
20
20
  }
21
21
 
22
+ .treeselect-list__item:focus {
23
+ outline: none;
24
+ }
25
+
22
26
  .treeselect-list__item--focused {
23
27
  background-color: #f0ffff!important;
24
28
  }
package/src/list.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import svg from './svgIcons.js'
2
2
 
3
- const getFlatOptons = (options, openLevel, groupId = 0, level = 0) => {
3
+ const getFlatOptions = (options, openLevel, groupId = 0, level = 0) => {
4
4
  return options.reduce((acc, curr) => {
5
5
  const isGroup = !!curr.children?.length
6
6
  const isClosed = level >= openLevel && isGroup
@@ -8,7 +8,7 @@ const getFlatOptons = (options, openLevel, groupId = 0, level = 0) => {
8
8
  acc.push({ id: curr.value, name: curr.name, childOf: groupId, isGroup, checked: false, level, isClosed, hidden })
9
9
 
10
10
  if (isGroup) {
11
- const children = getFlatOptons(curr.children, openLevel, curr.value, level + 1)
11
+ const children = getFlatOptions(curr.children, openLevel, curr.value, level + 1)
12
12
  acc.push(...children)
13
13
  }
14
14
 
@@ -20,6 +20,7 @@ const checkAllChildrenInputs = ({ id, checked }, flatOptions) => {
20
20
  flatOptions.forEach(option => {
21
21
  if (option.childOf === id) {
22
22
  option.checked = checked
23
+ option.isPartialChecked = false
23
24
 
24
25
  if (option.isGroup) {
25
26
  checkAllChildrenInputs(option, flatOptions)
@@ -67,7 +68,10 @@ const checkInput = ({ id, isGroup, childOf, checked }, flatOptions) => {
67
68
  }
68
69
 
69
70
  const updateValue = (newValue, flatOptions, srcElement) => {
70
- flatOptions.forEach(option => option.checked = false)
71
+ flatOptions.forEach(option => {
72
+ option.checked = false
73
+ option.isPartialChecked = false
74
+ })
71
75
  const toCheck = flatOptions.filter(option => newValue.includes(option.id))
72
76
  toCheck.forEach(option => {
73
77
  option.checked = true
@@ -187,13 +191,13 @@ const getAllFlattedChildren = (childOf, flattedOption) => {
187
191
  }, [])
188
192
  }
189
193
 
190
- const getAllFlattendParents = (childOf, flatOptions) => {
194
+ const getAllFlattenParents = (childOf, flatOptions) => {
191
195
  return flatOptions.reduce((acc, curr) => {
192
196
  if (curr.id === childOf) {
193
197
  acc.push(curr)
194
198
 
195
199
  if (curr.childOf) {
196
- acc.push(...getAllFlattendParents(curr.childOf, flatOptions))
200
+ acc.push(...getAllFlattenParents(curr.childOf, flatOptions))
197
201
  }
198
202
  }
199
203
 
@@ -233,6 +237,26 @@ const getListItemByCheckbox = (checkbox) => {
233
237
  return listItem
234
238
  }
235
239
 
240
+ const validateOptions = (flattedOption) => {
241
+ const { duplications } = flattedOption.reduce((acc, curr) => {
242
+ if (acc.allItems.includes(curr.id)) {
243
+ acc.duplications.push(curr.id)
244
+ }
245
+
246
+ acc.allItems.push(curr.id)
247
+
248
+ return acc
249
+ }, {
250
+ duplications: [],
251
+ allItems: []
252
+ })
253
+
254
+
255
+ if (duplications.length) {
256
+ console.error(`Validation: You have duplicated values: ${duplications.join(', ')}! You should use unique values.`)
257
+ }
258
+ }
259
+
236
260
  class TreeselectList {
237
261
  #lastFocusedItem = null
238
262
  #isMouseActionsAvailable = true
@@ -251,12 +275,13 @@ class TreeselectList {
251
275
  this.listSlotHtmlComponent = listSlotHtmlComponent
252
276
  this.emptyText = emptyText ?? 'No results found...'
253
277
 
254
- this.flattedOptions = getFlatOptons(this.options, this.openLevel)
278
+ this.flattedOptions = getFlatOptions(this.options, this.openLevel)
255
279
  this.flattedOptionsBeforeSearch = this.flattedOptions
256
280
  this.selectedNodes = { ids: [], groupedIds: [] }
257
281
  this.srcElement = this.#createList()
258
282
 
259
283
  this.updateValue(this.value)
284
+ validateOptions(this.flattedOptions)
260
285
  }
261
286
 
262
287
  // Public methods
@@ -266,6 +291,10 @@ class TreeselectList {
266
291
  }
267
292
 
268
293
  updateSearchValue (searchText) {
294
+ if (searchText === this.searchText) {
295
+ return
296
+ }
297
+
269
298
  const isStartOfSearching = this.searchText === '' && searchText !== ''
270
299
  this.searchText = searchText
271
300
 
@@ -291,9 +320,9 @@ class TreeselectList {
291
320
  }
292
321
 
293
322
  const allOptions = this.flattedOptions.reduce((acc, curr) => {
294
- const isSerched = curr.name.toLowerCase().includes(searchText.toLowerCase())
323
+ const isSearched = curr.name.toLowerCase().includes(searchText.toLowerCase())
295
324
 
296
- if (isSerched) {
325
+ if (isSearched) {
297
326
  acc.push(curr)
298
327
 
299
328
  if (curr.isGroup) {
@@ -302,7 +331,7 @@ class TreeselectList {
302
331
  }
303
332
 
304
333
  if (curr.childOf) {
305
- const flattedParents = getAllFlattendParents(curr.childOf, this.flattedOptions)
334
+ const flattedParents = getAllFlattenParents(curr.childOf, this.flattedOptions)
306
335
  acc.push(...flattedParents)
307
336
  }
308
337
  }
@@ -408,8 +437,8 @@ class TreeselectList {
408
437
  }
409
438
 
410
439
  focusFirstListElement () {
411
- const focusedCalss = 'treeselect-list__item--focused'
412
- const itemFocused = this.srcElement.querySelector(`.${focusedCalss}`)
440
+ const focusedClass = 'treeselect-list__item--focused'
441
+ const itemFocused = this.srcElement.querySelector(`.${focusedClass}`)
413
442
  const allCheckboxes = Array.from(this.srcElement.querySelectorAll('.treeselect-list__item-checkbox'))
414
443
  .filter(checkbox => window.getComputedStyle(getListItemByCheckbox(checkbox)).display !== 'none')
415
444
 
@@ -418,11 +447,11 @@ class TreeselectList {
418
447
  }
419
448
 
420
449
  if (itemFocused) {
421
- itemFocused.classList.remove(focusedCalss)
450
+ itemFocused.classList.remove(focusedClass)
422
451
  }
423
452
 
424
453
  const firstItem = getListItemByCheckbox(allCheckboxes[0])
425
- firstItem.classList.add(focusedCalss)
454
+ firstItem.classList.add(focusedClass)
426
455
  }
427
456
 
428
457
  // Private methods
@@ -604,7 +633,7 @@ class TreeselectList {
604
633
  hideShowChildren(this.flattedOptions, flattedOption)
605
634
  updateDOM(this.flattedOptions, this.srcElement)
606
635
 
607
- this.#emitArrrowClick()
636
+ this.#emitArrowClick()
608
637
  }
609
638
 
610
639
  #groupMouseAction (isMouseOver, itemElement) {
@@ -631,7 +660,7 @@ class TreeselectList {
631
660
  }
632
661
 
633
662
  // Emits
634
- #emitArrrowClick () {
663
+ #emitArrowClick () {
635
664
  this.srcElement.dispatchEvent(new CustomEvent('arrow-click'))
636
665
  }
637
666
 
@@ -16,13 +16,17 @@
16
16
  left: 0;
17
17
  border-radius: 4px;
18
18
  box-sizing: border-box;
19
- z-index: 1050;
19
+ z-index: 1000;
20
20
  }
21
21
 
22
22
  .treeselect .treeselect-list {
23
23
  position: absolute;
24
24
  }
25
25
 
26
+ .treeselect .treeselect-list--static {
27
+ position: static;
28
+ }
29
+
26
30
  .treeselect-input--focused {
27
31
  border-color: #101010;
28
32
  }
@@ -52,7 +56,7 @@
52
56
 
53
57
  .treeselect-list--bottom,
54
58
  .treeselect-list--bottom-to-body {
55
- border-top-color: gainsboro;
59
+ border-top-color: #d7dde4;
56
60
  border-top-left-radius: 0;
57
61
  border-top-right-radius: 0;
58
62
  }
@@ -1,6 +1,17 @@
1
1
  import TreeselectInput from "./input.js"
2
2
  import TreeselectList from "./list.js"
3
3
 
4
+
5
+ const validateProps = ({ parentHtmlContainer, staticList, appendToBody }) => {
6
+ if (!parentHtmlContainer) {
7
+ console.error('Validation: parentHtmlContainer prop is required!')
8
+ }
9
+
10
+ if (staticList && appendToBody) {
11
+ console.error('Validation: You should set staticList to false if you use appendToBody!')
12
+ }
13
+ }
14
+
4
15
  class Treeselect {
5
16
  // Components
6
17
  #htmlContainer = null
@@ -8,16 +19,17 @@ class Treeselect {
8
19
  #treeselectInput = null
9
20
 
10
21
  // Resize props
11
- #transform = { top: null, bottom: null }
12
- #treeselectInitPosition = null
13
22
  #containerResizer = null
14
- #containerWidth = 0
15
23
 
16
24
  // Outside listeners
17
25
  #scrollEvent = null
26
+ #resizeEvent = null
18
27
  #focusEvent = null
19
28
  #blurEvent = null
20
29
 
30
+ // State
31
+ #isListOpened = false
32
+
21
33
  constructor ({
22
34
  parentHtmlContainer,
23
35
  value,
@@ -32,8 +44,15 @@ class Treeselect {
32
44
  grouped,
33
45
  listSlotHtmlComponent,
34
46
  disabled,
35
- emptyText
47
+ emptyText,
48
+ staticList
36
49
  }) {
50
+ validateProps({
51
+ parentHtmlContainer,
52
+ staticList,
53
+ appendToBody
54
+ })
55
+
37
56
  this.parentHtmlContainer = parentHtmlContainer
38
57
  this.value = value ?? []
39
58
  this.options = options ?? []
@@ -48,6 +67,7 @@ class Treeselect {
48
67
  this.listSlotHtmlComponent = listSlotHtmlComponent ?? null
49
68
  this.disabled = disabled ?? false
50
69
  this.emptyText = emptyText ?? 'No results found...'
70
+ this.staticList = staticList && !this.appendToBody
51
71
 
52
72
  this.srcElement = null
53
73
 
@@ -56,16 +76,12 @@ class Treeselect {
56
76
 
57
77
  // Public methods
58
78
  mount () {
59
- if (this.srcElement) {
60
- this.#closeList()
61
- this.srcElement.innerHTML = ''
62
- this.srcElement = null
63
- this.#removeOutsideListeners()
64
- }
79
+ this.destroy()
65
80
 
66
81
  this.srcElement = this.#createTreeselect()
67
82
 
68
83
  this.#scrollEvent = this.scrollWindowHandler.bind(this)
84
+ this.#resizeEvent = this.scrollWindowHandler.bind(this)
69
85
  this.#focusEvent = this.focusWindowHandler.bind(this)
70
86
  this.#blurEvent = this.blurWindowHandler.bind(this)
71
87
 
@@ -91,7 +107,7 @@ class Treeselect {
91
107
  this.#closeList()
92
108
  this.srcElement.innerHTML = ''
93
109
  this.srcElement = null
94
- this.#removeOutsideListeners()
110
+ this.#removeOutsideListeners(true)
95
111
  }
96
112
  }
97
113
 
@@ -120,9 +136,7 @@ class Treeselect {
120
136
 
121
137
  if (this.appendToBody) {
122
138
  this.#containerResizer = new ResizeObserver(() => {
123
- const { width } = this.srcElement.getBoundingClientRect()
124
- this.#containerWidth = width
125
- this.updateListPosition(container, list.srcElement, true)
139
+ this.updateListPosition()
126
140
  })
127
141
  }
128
142
 
@@ -134,10 +148,14 @@ class Treeselect {
134
148
  this.#emitInput()
135
149
  })
136
150
  input.srcElement.addEventListener('open', () => this.#openList())
137
- input.srcElement.addEventListener('keydown', (e) => list.callKeyAction(e.key))
151
+ input.srcElement.addEventListener('keydown', (e) => {
152
+ if (this.#isListOpened) {
153
+ list.callKeyAction(e.key)
154
+ }
155
+ })
138
156
  input.srcElement.addEventListener('search', (e) => {
139
157
  list.updateSearchValue(e.detail)
140
- this.updateListPosition(container, list.srcElement, true)
158
+ this.updateListPosition()
141
159
  })
142
160
  input.srcElement.addEventListener('focus', () => {
143
161
  this.#updateFocusClasses(true)
@@ -166,7 +184,7 @@ class Treeselect {
166
184
  })
167
185
  list.srcElement.addEventListener('arrow-click', () => {
168
186
  input.focus()
169
- this.updateListPosition(container, list.srcElement, true)
187
+ this.updateListPosition()
170
188
  })
171
189
 
172
190
  this.#htmlContainer = container
@@ -179,7 +197,10 @@ class Treeselect {
179
197
  }
180
198
 
181
199
  #openList () {
200
+ this.#isListOpened = true
201
+
182
202
  window.addEventListener('scroll', this.#scrollEvent, true)
203
+ window.addEventListener('resize', this.#resizeEvent)
183
204
 
184
205
  if (this.appendToBody) {
185
206
  document.body.appendChild(this.#treeselectList.srcElement)
@@ -188,13 +209,16 @@ class Treeselect {
188
209
  this.#htmlContainer.appendChild(this.#treeselectList.srcElement)
189
210
  }
190
211
 
191
- this.updateListPosition(this.#htmlContainer, this.#treeselectList.srcElement, false)
212
+ this.updateListPosition()
192
213
  this.#updateOpenCloseClasses(true)
193
214
  this.#treeselectList.focusFirstListElement()
194
215
  }
195
216
 
196
217
  #closeList () {
218
+ this.#isListOpened = false
219
+
197
220
  window.removeEventListener('scroll', this.#scrollEvent, true)
221
+ window.removeEventListener('resize', this.#resizeEvent)
198
222
  const isElementExist = this.appendToBody
199
223
  ? document.body.contains(this.#treeselectList.srcElement)
200
224
  : this.#htmlContainer.contains(this.#treeselectList.srcElement)
@@ -246,10 +270,19 @@ class Treeselect {
246
270
  } else {
247
271
  this.#treeselectInput.srcElement.classList.remove('treeselect-input--opened')
248
272
  }
273
+
274
+ if (this.staticList) {
275
+ this.#treeselectList.srcElement.classList.add('treeselect-list--static')
276
+ } else {
277
+ this.#treeselectList.srcElement.classList.remove('treeselect-list--static')
278
+ }
249
279
  }
250
280
 
251
- #removeOutsideListeners () {
252
- window.removeEventListener('scroll', this.#scrollEvent, true)
281
+ #removeOutsideListeners (isDestroy) {
282
+ if (!this.alwaysOpen || isDestroy) {
283
+ window.removeEventListener('scroll', this.#scrollEvent, true)
284
+ window.removeEventListener('resize', this.#resizeEvent)
285
+ }
253
286
 
254
287
  document.removeEventListener('click', this.#focusEvent, true)
255
288
  document.removeEventListener('focus', this.#focusEvent, true)
@@ -258,7 +291,7 @@ class Treeselect {
258
291
 
259
292
  // Outside Listeners
260
293
  scrollWindowHandler () {
261
- this.updateListPosition(this.#htmlContainer, this.#treeselectList.srcElement, true)
294
+ this.updateListPosition()
262
295
  }
263
296
 
264
297
  focusWindowHandler (e) {
@@ -277,56 +310,36 @@ class Treeselect {
277
310
  this.#updateFocusClasses(false)
278
311
  }
279
312
 
280
- // Update direction of the list. Support appendToBody and standart mode with absolute
281
- updateListPosition (container, list, isNeedForceUpdate) {
282
- const spaceTop = container.getBoundingClientRect().y
283
- const spaceBottom = window.innerHeight - container.getBoundingClientRect().y
284
- const listHeight = list.clientHeight
285
- const spaceDelta = 45
286
- const isTopDirection = spaceTop > spaceBottom && window.innerHeight - spaceTop < listHeight + spaceDelta
287
- const attributeToAdd = isTopDirection ? 'top' : 'buttom'
288
- const currentAttr = list.getAttribute('direction')
289
-
290
- this.#htmlContainer.setAttribute('direction', attributeToAdd)
291
-
292
- // Standart class handler handler with absolute position
293
- if (!this.appendToBody) {
294
- const isNoNeedToUpdate = currentAttr === attributeToAdd
295
-
296
- if (isNoNeedToUpdate) {
297
- return
298
- }
299
-
300
- this.#updateDirectionClasses(isTopDirection, false)
301
-
302
- return
303
- }
304
-
305
- // Append to body handler
306
- if (!this.#treeselectInitPosition || isNeedForceUpdate) {
307
- list.style.transform = null
313
+ // Update direction of the list. Support appendToBody and standard mode with absolute
314
+ updateListPosition () {
315
+ const list = this.#treeselectList.srcElement
316
+ // We need to reset position
317
+ list.style.transform = null
318
+ const container = this.#htmlContainer
308
319
 
309
- const { x: listX, y: listY } = list.getBoundingClientRect()
310
- const { x: containerX, y: containerY } = container.getBoundingClientRect()
320
+ const { y: listY, height: listHeight } = list.getBoundingClientRect()
321
+ const { x: containerX, y: containerY, height: containerHeight, width: containerWidth } = container.getBoundingClientRect()
322
+ const windowHeight = window.innerHeight
311
323
 
312
- this.#treeselectInitPosition = { containerX, containerY, listX, listY }
324
+ const spaceTop = containerY
325
+ const spaceBottom = windowHeight - containerY - containerHeight
326
+ const isTopDirection = spaceTop > spaceBottom && spaceTop >= listHeight && spaceBottom < listHeight
327
+
328
+ if (this.appendToBody) {
329
+ list.style.transform = isTopDirection
330
+ ? `translateY(${containerY - listY - listHeight}px)`
331
+ : `translateY(${containerY + containerHeight - listY}px)`
332
+ list.style.width = `${containerWidth}px`
333
+ list.style.left = `${containerX + window.scrollX}px`
313
334
  }
314
335
 
315
- const { listX, listY, containerX, containerY } = this.#treeselectInitPosition
316
- const containerHeight = container.clientHeight
317
-
318
- // TODO you should use css max-height
319
- // list.style.maxHeight = `${window.innerHeight - containerHeight}px`
336
+ const attributeToAdd = isTopDirection ? 'top' : 'bottom'
337
+ const currentAttr = list.getAttribute('direction')
320
338
 
321
- if (!currentAttr || isNeedForceUpdate) {
322
- this.#transform.top = `translateY(${containerY - listY - listHeight}px)`
323
- this.#transform.bottom = `translateY(${containerY + containerHeight - listY}px)`
339
+ if (currentAttr !== attributeToAdd) {
340
+ list.setAttribute('direction', attributeToAdd)
341
+ this.#updateDirectionClasses(isTopDirection, this.appendToBody)
324
342
  }
325
-
326
- list.style.transform = isTopDirection ? this.#transform.top : this.#transform.bottom
327
- this.#updateDirectionClasses(isTopDirection, true)
328
- list.style.width = `${this.#containerWidth}px`
329
- list.style.left = `${containerX}px`
330
343
  }
331
344
 
332
345
  // Emits