treeselectjs 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Dmitry Zhuravkov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Treeselect JS component
2
+
3
+ A multi-select js component with nested options.
4
+
5
+ - Full key support (ArrowUp, ArrowDown, Space, ArrowLeft, ArrowRight, Enter)
6
+ - Screen sensitive direction
7
+
8
+ ![Example img](treeselectjs.png)
9
+
10
+ ### Getting Started
11
+ It is a js module.
12
+
13
+ ```bash
14
+ npm install --save treeselectjs
15
+ ```
16
+ You should import treeselectjs
17
+ ```
18
+ import Treeselect from 'treeselectjs'
19
+ ```
20
+ and css file with styles
21
+ ```
22
+ @import 'treeselectjs/dist/treeselect-js.css'
23
+ ```
24
+
25
+ Example
26
+ ```
27
+ import Treeselect from 'treeselectjs'
28
+
29
+ const options = [
30
+ {
31
+ name: 'England',
32
+ value: 'England',
33
+ children: [
34
+ {
35
+ name: 'London',
36
+ value: 'London',
37
+ children: [
38
+ {
39
+ name: 'Chelsea',
40
+ value: 'Chelsea',
41
+ children: []
42
+ },
43
+ {
44
+ name: 'West End',
45
+ value: 'West End',
46
+ children: []
47
+ }
48
+ ]
49
+ },
50
+ {
51
+ name: 'Brighton',
52
+ value: 'Brighton',
53
+ children: []
54
+ }
55
+ ]
56
+ },
57
+ {
58
+ name: 'France',
59
+ value: 'France',
60
+ children: [
61
+ {
62
+ name: 'Paris',
63
+ value: 'Paris',
64
+ children: []
65
+ },
66
+ {
67
+ name: 'Lyon',
68
+ value: 'Lyon',
69
+ children: []
70
+ }
71
+ ]
72
+ }
73
+ ]
74
+
75
+ const slot = document.createElement('div')
76
+ slot.innerHTML='<a class="test" href="">Slot example text</a>'
77
+
78
+ const domElement = document.querySelector('.treeselect-test')
79
+ const treeselect = new Treeselect({
80
+ parentHtmlContainer: domElement,
81
+ value: ['West End', 'Paris', 'Lyon'],
82
+ options: options,
83
+ alwaysOpen: true,
84
+ appendToBody: true,
85
+ listSlotHtmlComponent: slot,
86
+ disabled: false,
87
+ emptyText: 'No data text'
88
+ })
89
+
90
+ treeselect.srcElement.addEventListener('input', (e) => {
91
+ console.log(e.detail)
92
+ })
93
+ ```
94
+
95
+ ### Props
96
+ Name | Type (default) | Discription
97
+ ------------- | ------------- | -------------
98
+ parentHtmlContainer | HTMLElement | It sould be a HTML element (div), it will be changed to the list container.
99
+ 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.
101
+ openLevel | Number (0) | All groups will be opened to this level.
102
+ appendToBody | Boolean (false) | List will be appended to the body instead of the input container.
103
+ alwaysOpen | Boolean (false) | List will be always opened.
104
+ showTags | Boolean (true) | Selected values look like tags. The false value shows results as '{count} elements selected'.
105
+ clearable | Boolean (true) | Clear icon is available.
106
+ searchable | Boolean (true) | Search is available.
107
+ placeholder | String ('Search...') | Placeholder text.
108
+ grouped | Boolean (true) | Show groups in the input and group lefs if all group selected.
109
+ listSlotHtmlComponent | HTMLElement (null) | It should be a HTML element, it will be append to the end of the list.
110
+ disabled | Boolean (false) | List will be disabled.
111
+ emptyText | String ('No results found...') | A empty list text.
112
+
113
+ ### Emits
114
+ Name | Return Type | Discription
115
+ ------------- | ------------- | -------------
116
+ input | Array[String] | Returns selected ids without groups, only leafs.
117
+
118
+ ### Methods
119
+ Name | Params | Discription
120
+ ------------- | ------------- | -------------
121
+ updateValue | Array[String] | Update selected values.
122
+ mount | None | Helps to remount and update settings.
package/dist/input.css ADDED
@@ -0,0 +1,14 @@
1
+ .treeselect-input{width:100%;box-sizing:border-box;border:1px solid #d7dde4;border-radius:4px;display:flex;align-items:center;flex-wrap:wrap;padding:2px 4px;padding-right:40px;position:relative;min-height:37px;background-color:#fff;cursor:text}
2
+ .treeselect-input--unsearchable{cursor:default}.treeselect-input--unsearchable .treeselect-input__edit{z-index:-1;caret-color:transparent;cursor:default}
3
+ .treeselect-input__tags{display:inline-flex;align-items:center;flex-wrap:wrap;gap:4px;max-width:100%;box-sizing:border-box}
4
+ .treeselect-input__tags-element{display:inline-flex;align-items:center;background-color:#d7dde4;cursor:pointer;padding:2px 5px;border-radius:2px;font-size:14px;max-width:100%;box-sizing:border-box}
5
+ .treeselect-input__tags-element:hover{background-color:#c5c7cb}.treeselect-input__tags-element:hover .treeselect-input__tags-cross svg{stroke:#eb4c42}
6
+ .treeselect-input__tags-name{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
7
+ .treeselect-input__tags-cross{display:flex;margin-left:2px}.treeselect-input__tags-cross svg{width:12px;height:12px}
8
+ .treeselect-input__tags-count{font-size:14px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
9
+ .treeselect-input__edit{flex:1;min-width:30px;border:0;font-size:14px}.treeselect-input__edit:focus{outline:0}
10
+ .treeselect-input__operators{display:flex;max-width:40px;position:absolute;right:2px}
11
+ .treeselect-input__clear{display:flex;cursor:pointer}.treeselect-input__clear svg{stroke:#c5c7cb;width:17px;min-width:17px;height:20px}
12
+ .treeselect-input__clear:hover svg{stroke:#838790}.treeselect-input__arrow{display:flex;cursor:pointer}
13
+ .treeselect-input__arrow svg{stroke:#c5c7cb;width:20px;min-width:20px;height:20px}
14
+ .treeselect-input__arrow:hover svg{stroke:#838790}
package/dist/input.js ADDED
@@ -0,0 +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;
package/dist/list.css ADDED
@@ -0,0 +1,17 @@
1
+ .treeselect-list{width:100%;box-sizing:border-box;border:1px solid #d7dde4;overflow-y:auto;background-color:#fff;max-height:300px}
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}
5
+ .treeselect-list__item-icon svg{pointer-events:none;width:100%;height:100%;stroke:#c5c7cb}
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
+ .treeselect-list__item-checkbox-container svg{position:absolute;height:100%;width:100%}
8
+ .treeselect-list__item-checkbox{margin:0;width:0;height:0;pointer-events:none;position:absolute;z-index:-1}
9
+ .treeselect-list__item-checkbox-icon{position:absolute;height:100%;width:100%;left:0;top:0}
10
+ .treeselect-list__item-label{width:100%;overflow:hidden;text-overflow:ellipsis;word-break:keep-all;white-space:nowrap;font-size:14px;padding-left:5px;pointer-events:none}
11
+ .treeselect-list__empty{display:flex;align-items:center;height:30px;padding-left:4px}
12
+ .treeselect-list__empty--hidden{display:none}.treeselect-list__empty-icon{display:flex;align-items:center}
13
+ .treeselect-list__empty-text{font-size:14px;padding-left:5px;overflow:hidden;text-overflow:ellipsis;word-break:keep-all;white-space:nowrap}
14
+ .treeselect-list__slot{position:sticky;box-sizing:border-box;width:100%;max-width:100%;bottom:0;background-color:#fff}
15
+ .treeselect-list__item .treeselect-list__item-checkbox-container svg{stroke:transparent}
16
+ .treeselect-list__item--checked .treeselect-list__item-checkbox-container svg,.treeselect-list__item--partial-checked .treeselect-list__item-checkbox-container svg{stroke:#fff}
17
+ .treeselect-list__item--checked .treeselect-list__item-checkbox-container,.treeselect-list__item--partial-checked .treeselect-list__item-checkbox-container{background-color:#52c67e}
package/dist/list.js ADDED
@@ -0,0 +1 @@
1
+ import svg from"./svgIcons.js";const getFlatOptons=(e,i,c=0,l=0)=>e.reduce((e,t)=>{var s=!!t.children?.length;return e.push({id:t.value,name:t.name,childOf:c,isGroup:s,checked:!1,level:l,isClosed:i<=l&&s,hidden:i<l}),s&&(s=getFlatOptons(t.children,i,t.value,l+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 c=i.every(e=>e.checked),l=i.some(e=>e.isPartialChecked||e.checked)&&!c,r=!c&&!l;c&&(s.checked=!0,s.isPartialChecked=!1),l&&(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},c)=>{t&&checkAllChildrenInputs({id:e,checked:i},c),s&&checkAllParentInputs(s,c)},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=(e,c)=>{e.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"),e.childOf||e.isGroup?s.style.paddingLeft=e.isGroup?40*e.level+"px":60*e.level+"px":s.style.paddingLeft="20px",updateCheckboxClasses(e,t)});e=e.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")},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:c}){this.options=e,this.value=t,this.searchText="",this.openLevel=s??0,this.listSlotHtmlComponent=i,this.emptyText=c??"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("click")),"ArrowLeft"===e||"ArrowRight"===e){if(!t)return;const l=t.querySelector(".treeselect-list__item-checkbox"),r=l.getAttribute("input-id");var s=this.flattedOptions.find(e=>e.id===r);const n=t.querySelector(".treeselect-list__item-icon");"ArrowLeft"!==e||s.isClosed||n.dispatchEvent(new Event("click")),"ArrowRight"===e&&s.isClosed&&n.dispatchEvent(new Event("click"))}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 o=getListItemByCheckbox(d[s]);o.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(),c=a.getBoundingClientRect();s&&"ArrowDown"===e?this.srcElement.scroll(0,0):s&&"ArrowUp"===e?this.srcElement.scroll(0,this.srcElement.scrollHeight):i.y+i.height<c.y+c.height?this.srcElement.scroll(0,this.srcElement.scrollTop+c.height):i.y>c.y&&this.srcElement.scroll(0,this.srcElement.scrollTop-c.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("click",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("click",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;
@@ -0,0 +1 @@
1
+ export default{arrowUp:'<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 25" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 15l-6-6-6 6"/></svg>',arrowDown:'<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 25" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>',arrowRight:'<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 25" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>',attention:'<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 25" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',clear:'<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 25" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>',cross:'<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 25" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>',check:'<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 25" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>',partialCheck:'<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 25" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>'};
@@ -0,0 +1,8 @@
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}
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}
5
+ .treeselect-input--opened.treeselect-input--bottom{border-bottom-color:transparent;border-bottom-left-radius:0;border-bottom-right-radius:0}
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}
8
+ .treeselect-list--top{left:0;bottom:100%}.treeselect-list--bottom{left:0;top:100%}
@@ -0,0 +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;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,this.srcElement=null,this.scrollEvent=null,this.focusEvent=null,this.blurEvent=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)}#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("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.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,!1)}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:o,y:c}=t.getBoundingClientRect(),{x:a,y:d}=e.getBoundingClientRect();this.#treeselectInitPosition={containerX:a,containerY:d,listX:o,listY:c}}const{listX:o,listY:c,containerX:a,containerY:d}=this.#treeselectInitPosition;i=e.clientHeight;r&&!s||(this.#transform.top=`translate(${a-o}px, ${d-c-l}px)`,this.#transform.bottom=`translate(${a-o}px, ${d+i-c}px)`),t.style.transform=n?this.#transform.top:this.#transform.bottom,this.#updateDirectionClasses(n,!0),t.style.width=this.#containerWidth+"px"}#emitInput(){this.srcElement.dispatchEvent(new CustomEvent("input",{detail:this.value}))}}export default Treeselect;
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "treeselectjs",
3
+ "version": "0.2.0",
4
+ "description": "Treeselect JS",
5
+ "main": "dist/treeselect-js.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/dipson88/treeselectjs.git"
12
+ },
13
+ "keywords": [
14
+ "treeselect",
15
+ "select",
16
+ "js"
17
+ ],
18
+ "author": "Dzmitry Zhuraukou",
19
+ "license": "ISC",
20
+ "bugs": {
21
+ "url": "https://github.com/dipson88/treeselectjs/issues"
22
+ },
23
+ "homepage": "https://github.com/dipson88/treeselectjs#readme",
24
+ "devDependencies": {
25
+ "gulp": "^4.0.2",
26
+ "gulp-uglify": "^3.0.2",
27
+ "gulp-uglifycss": "^1.1.0"
28
+ },
29
+ "files": ["dist", "src"]
30
+ }
package/src/input.css ADDED
@@ -0,0 +1,127 @@
1
+ .treeselect-input {
2
+ width: 100%;
3
+ box-sizing: border-box;
4
+ border: 1px solid #d7dde4;
5
+ border-radius: 4px;
6
+ display: flex;
7
+ align-items: center;
8
+ flex-wrap: wrap;
9
+ padding: 2px 4px;
10
+ padding-right: 40px;
11
+ position: relative;
12
+ min-height: 37px;
13
+ background-color: #ffffff;
14
+ cursor: text;
15
+ }
16
+
17
+ .treeselect-input--unsearchable {
18
+ cursor: default;
19
+ }
20
+
21
+ .treeselect-input--unsearchable .treeselect-input__edit {
22
+ z-index: -1;
23
+ caret-color: transparent;
24
+ cursor: default;
25
+ }
26
+
27
+ .treeselect-input__tags {
28
+ display: inline-flex;
29
+ align-items: center;
30
+ flex-wrap: wrap;
31
+ gap: 4px;
32
+ max-width: 100%;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ .treeselect-input__tags-element {
37
+ display: inline-flex;
38
+ align-items: center;
39
+ background-color: #d7dde4;
40
+ cursor: pointer;
41
+ padding: 2px 5px;
42
+ border-radius: 2px;
43
+ font-size: 14px;
44
+ max-width: 100%;
45
+ box-sizing: border-box;
46
+ }
47
+
48
+ .treeselect-input__tags-element:hover {
49
+ background-color: #c5c7cb;
50
+ }
51
+
52
+ .treeselect-input__tags-element:hover .treeselect-input__tags-cross svg {
53
+ stroke: #eb4c42;
54
+ }
55
+
56
+ .treeselect-input__tags-name {
57
+ overflow: hidden;
58
+ white-space: nowrap;
59
+ text-overflow: ellipsis;
60
+ }
61
+
62
+ .treeselect-input__tags-cross {
63
+ display: flex;
64
+ margin-left: 2px;
65
+ }
66
+
67
+ .treeselect-input__tags-cross svg {
68
+ width: 12px;
69
+ height: 12px;
70
+ }
71
+
72
+ .treeselect-input__tags-count {
73
+ font-size: 14px;
74
+ overflow: hidden;
75
+ white-space: nowrap;
76
+ text-overflow: ellipsis;
77
+ }
78
+
79
+ .treeselect-input__edit {
80
+ flex: 1;
81
+ min-width: 30px;
82
+ border: none;
83
+ font-size: 14px;
84
+ }
85
+
86
+ .treeselect-input__edit:focus {
87
+ outline: none;
88
+ }
89
+
90
+ .treeselect-input__operators {
91
+ display: flex;
92
+ max-width: 40px;
93
+ position: absolute;
94
+ right: 2px;
95
+ }
96
+
97
+ .treeselect-input__clear {
98
+ display: flex;
99
+ cursor: pointer;
100
+ }
101
+
102
+ .treeselect-input__clear svg {
103
+ stroke: #c5c7cb;
104
+ width: 17px;
105
+ min-width: 17px;
106
+ height: 20px;
107
+ }
108
+
109
+ .treeselect-input__clear:hover svg {
110
+ stroke: #838790;
111
+ }
112
+
113
+ .treeselect-input__arrow {
114
+ display: flex;
115
+ cursor: pointer;
116
+ }
117
+
118
+ .treeselect-input__arrow svg {
119
+ stroke: #c5c7cb;
120
+ width: 20px;
121
+ min-width: 20px;
122
+ height: 20px;
123
+ }
124
+
125
+ .treeselect-input__arrow:hover svg {
126
+ stroke: #838790;
127
+ }