@reviewpush/rp-treeselect 0.0.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.
Files changed (64) hide show
  1. package/README.md +172 -0
  2. package/dist/rp-treeselect.cjs.js +3656 -0
  3. package/dist/rp-treeselect.cjs.js.map +1 -0
  4. package/dist/rp-treeselect.cjs.min.js +2 -0
  5. package/dist/rp-treeselect.cjs.min.js.map +1 -0
  6. package/dist/rp-treeselect.css +947 -0
  7. package/dist/rp-treeselect.css.map +1 -0
  8. package/dist/rp-treeselect.min.css +1 -0
  9. package/dist/rp-treeselect.umd.js +4837 -0
  10. package/dist/rp-treeselect.umd.js.map +1 -0
  11. package/dist/rp-treeselect.umd.min.js +2 -0
  12. package/dist/rp-treeselect.umd.min.js.map +1 -0
  13. package/package.json +140 -0
  14. package/src/assets/checkbox-checked-disabled.png +0 -0
  15. package/src/assets/checkbox-checked-disabled@2x.png +0 -0
  16. package/src/assets/checkbox-checked-disabled@3x.png +0 -0
  17. package/src/assets/checkbox-checked.png +0 -0
  18. package/src/assets/checkbox-checked@2x.png +0 -0
  19. package/src/assets/checkbox-checked@3x.png +0 -0
  20. package/src/assets/checkbox-indeterminate-disabled.png +0 -0
  21. package/src/assets/checkbox-indeterminate-disabled@2x.png +0 -0
  22. package/src/assets/checkbox-indeterminate-disabled@3x.png +0 -0
  23. package/src/assets/checkbox-indeterminate.png +0 -0
  24. package/src/assets/checkbox-indeterminate@2x.png +0 -0
  25. package/src/assets/checkbox-indeterminate@3x.png +0 -0
  26. package/src/components/Control.vue +153 -0
  27. package/src/components/HiddenFields.vue +37 -0
  28. package/src/components/Input.vue +295 -0
  29. package/src/components/Menu.vue +313 -0
  30. package/src/components/MenuPortal.vue +179 -0
  31. package/src/components/MultiValue.vue +56 -0
  32. package/src/components/MultiValueItem.vue +45 -0
  33. package/src/components/Option.vue +300 -0
  34. package/src/components/Placeholder.vue +21 -0
  35. package/src/components/SingleValue.vue +34 -0
  36. package/src/components/Tip.vue +32 -0
  37. package/src/components/Treeselect.vue +42 -0
  38. package/src/components/icons/Arrow.vue +11 -0
  39. package/src/components/icons/Delete.vue +11 -0
  40. package/src/constants.js +50 -0
  41. package/src/index.js +14 -0
  42. package/src/mixins/treeselectMixin.js +1949 -0
  43. package/src/style.less +1147 -0
  44. package/src/utils/.eslintrc.js +6 -0
  45. package/src/utils/constant.js +1 -0
  46. package/src/utils/createMap.js +1 -0
  47. package/src/utils/debounce.js +1 -0
  48. package/src/utils/deepExtend.js +25 -0
  49. package/src/utils/find.js +6 -0
  50. package/src/utils/identity.js +1 -0
  51. package/src/utils/includes.js +3 -0
  52. package/src/utils/index.js +38 -0
  53. package/src/utils/isNaN.js +3 -0
  54. package/src/utils/isPromise.js +1 -0
  55. package/src/utils/last.js +1 -0
  56. package/src/utils/noop.js +1 -0
  57. package/src/utils/onLeftClick.js +7 -0
  58. package/src/utils/once.js +1 -0
  59. package/src/utils/quickDiff.js +9 -0
  60. package/src/utils/removeFromArray.js +4 -0
  61. package/src/utils/scrollIntoView.js +15 -0
  62. package/src/utils/setupResizeAndScrollEventListeners.js +34 -0
  63. package/src/utils/warning.js +11 -0
  64. package/src/utils/watchSize.js +67 -0
@@ -0,0 +1,313 @@
1
+ <script>
2
+ import { MENU_BUFFER } from '../constants'
3
+ import { watchSize, setupResizeAndScrollEventListeners } from '../utils'
4
+ import Option from './Option'
5
+ import Tip from './Tip'
6
+
7
+ const directionMap = {
8
+ top: 'top',
9
+ bottom: 'bottom',
10
+ above: 'top',
11
+ below: 'bottom',
12
+ }
13
+
14
+ export default {
15
+ name: 'rp-treeselect--menu',
16
+ inject: [ 'instance' ],
17
+
18
+ computed: {
19
+ menuStyle() {
20
+ const { instance } = this
21
+
22
+ return {
23
+ maxHeight: instance.maxHeight + 'px',
24
+ }
25
+ },
26
+
27
+ menuContainerStyle() {
28
+ const { instance } = this
29
+
30
+ return {
31
+ zIndex: instance.appendToBody ? null : instance.zIndex,
32
+ }
33
+ },
34
+ },
35
+
36
+ watch: {
37
+ 'instance.menu.isOpen'(newValue) {
38
+ if (newValue) {
39
+ // In case `openMenu()` is just called and the menu is not rendered yet.
40
+ this.$nextTick(this.onMenuOpen)
41
+ } else {
42
+ this.onMenuClose()
43
+ }
44
+ },
45
+ },
46
+
47
+ created() {
48
+ this.menuSizeWatcher = null
49
+ this.menuResizeAndScrollEventListeners = null
50
+ },
51
+
52
+ mounted() {
53
+ const { instance } = this
54
+
55
+ if (instance.menu.isOpen) this.$nextTick(this.onMenuOpen)
56
+ },
57
+
58
+ destroyed() {
59
+ this.onMenuClose()
60
+ },
61
+
62
+ methods: {
63
+ renderMenu() {
64
+ const { instance } = this
65
+
66
+ if (!instance.menu.isOpen) return null
67
+
68
+ return (
69
+ <div ref="menu" class="rp-treeselect__menu" onMousedown={instance.handleMouseDown} style={this.menuStyle}>
70
+ {this.renderBeforeList()}
71
+ {instance.async
72
+ ? this.renderAsyncSearchMenuInner()
73
+ : instance.localSearch.active
74
+ ? this.renderLocalSearchMenuInner()
75
+ : this.renderNormalMenuInner()}
76
+ {this.renderAfterList()}
77
+ </div>
78
+ )
79
+ },
80
+
81
+ renderBeforeList() {
82
+ const { instance } = this
83
+ const beforeListRenderer = instance.$scopedSlots['before-list']
84
+
85
+ return beforeListRenderer
86
+ ? beforeListRenderer()
87
+ : null
88
+ },
89
+
90
+ renderAfterList() {
91
+ const { instance } = this
92
+ const afterListRenderer = instance.$scopedSlots['after-list']
93
+
94
+ return afterListRenderer
95
+ ? afterListRenderer()
96
+ : null
97
+ },
98
+
99
+ renderNormalMenuInner() {
100
+ const { instance } = this
101
+
102
+ if (instance.rootOptionsStates.isLoading) {
103
+ return this.renderLoadingOptionsTip()
104
+ } else if (instance.rootOptionsStates.loadingError) {
105
+ return this.renderLoadingRootOptionsErrorTip()
106
+ } else if (instance.rootOptionsStates.isLoaded && instance.forest.normalizedOptions.length === 0) {
107
+ return this.renderNoAvailableOptionsTip()
108
+ } else {
109
+ return this.renderOptionList()
110
+ }
111
+ },
112
+
113
+ renderLocalSearchMenuInner() {
114
+ const { instance } = this
115
+
116
+ if (instance.rootOptionsStates.isLoading) {
117
+ return this.renderLoadingOptionsTip()
118
+ } else if (instance.rootOptionsStates.loadingError) {
119
+ return this.renderLoadingRootOptionsErrorTip()
120
+ } else if (instance.rootOptionsStates.isLoaded && instance.forest.normalizedOptions.length === 0) {
121
+ return this.renderNoAvailableOptionsTip()
122
+ } else if (instance.localSearch.noResults) {
123
+ return this.renderNoResultsTip()
124
+ } else {
125
+ return this.renderOptionList()
126
+ }
127
+ },
128
+
129
+ renderAsyncSearchMenuInner() {
130
+ const { instance } = this
131
+ const entry = instance.getRemoteSearchEntry()
132
+ const shouldShowSearchPromptTip = instance.trigger.searchQuery === '' && !instance.defaultOptions
133
+ const shouldShowNoResultsTip = shouldShowSearchPromptTip
134
+ ? false
135
+ : entry.isLoaded && entry.options.length === 0
136
+
137
+ if (shouldShowSearchPromptTip) {
138
+ return this.renderSearchPromptTip()
139
+ } else if (entry.isLoading) {
140
+ return this.renderLoadingOptionsTip()
141
+ } else if (entry.loadingError) {
142
+ return this.renderAsyncSearchLoadingErrorTip()
143
+ } else if (shouldShowNoResultsTip) {
144
+ return this.renderNoResultsTip()
145
+ } else {
146
+ return this.renderOptionList()
147
+ }
148
+ },
149
+
150
+ renderOptionList() {
151
+ const { instance } = this
152
+
153
+ return (
154
+ <div class="rp-treeselect__list">
155
+ {instance.forest.normalizedOptions.map(rootNode => (
156
+ <Option node={rootNode} key={rootNode.id} />
157
+ ))}
158
+ </div>
159
+ )
160
+ },
161
+
162
+ renderSearchPromptTip() {
163
+ const { instance } = this
164
+
165
+ return (
166
+ <Tip type="search-prompt" icon="warning">{ instance.searchPromptText }</Tip>
167
+ )
168
+ },
169
+
170
+ renderLoadingOptionsTip() {
171
+ const { instance } = this
172
+
173
+ return (
174
+ <Tip type="loading" icon="loader">{ instance.loadingText }</Tip>
175
+ )
176
+ },
177
+
178
+ renderLoadingRootOptionsErrorTip() {
179
+ const { instance } = this
180
+
181
+ return (
182
+ <Tip type="error" icon="error">
183
+ { instance.rootOptionsStates.loadingError }
184
+ <a class="rp-treeselect__retry" onClick={instance.loadRootOptions} title={instance.retryTitle}>
185
+ { instance.retryText }
186
+ </a>
187
+ </Tip>
188
+ )
189
+ },
190
+
191
+ renderAsyncSearchLoadingErrorTip() {
192
+ const { instance } = this
193
+ const entry = instance.getRemoteSearchEntry()
194
+
195
+ // TODO: retryTitle?
196
+
197
+ return (
198
+ <Tip type="error" icon="error">
199
+ { entry.loadingError }
200
+ <a class="rp-treeselect__retry" onClick={instance.handleRemoteSearch} title={instance.retryTitle}>
201
+ { instance.retryText }
202
+ </a>
203
+ </Tip>
204
+ )
205
+ },
206
+
207
+ renderNoAvailableOptionsTip() {
208
+ const { instance } = this
209
+
210
+ return (
211
+ <Tip type="no-options" icon="warning">{ instance.noOptionsText }</Tip>
212
+ )
213
+ },
214
+
215
+ renderNoResultsTip() {
216
+ const { instance } = this
217
+
218
+ return (
219
+ <Tip type="no-results" icon="warning">{ instance.noResultsText }</Tip>
220
+ )
221
+ },
222
+
223
+ onMenuOpen() {
224
+ this.adjustMenuOpenDirection()
225
+ this.setupMenuSizeWatcher()
226
+ this.setupMenuResizeAndScrollEventListeners()
227
+ },
228
+
229
+ onMenuClose() {
230
+ this.removeMenuSizeWatcher()
231
+ this.removeMenuResizeAndScrollEventListeners()
232
+ },
233
+
234
+ adjustMenuOpenDirection() {
235
+ const { instance } = this
236
+ if (!instance.menu.isOpen) return
237
+
238
+ const $menu = instance.getMenu()
239
+ const $control = instance.getControl()
240
+ const menuRect = $menu.getBoundingClientRect()
241
+ const controlRect = $control.getBoundingClientRect()
242
+ const menuHeight = menuRect.height
243
+ const viewportHeight = window.innerHeight
244
+ const spaceAbove = controlRect.top
245
+ const spaceBelow = window.innerHeight - controlRect.bottom
246
+ const isControlInViewport = (
247
+ (controlRect.top >= 0 && controlRect.top <= viewportHeight) ||
248
+ (controlRect.top < 0 && controlRect.bottom > 0)
249
+ )
250
+ const hasEnoughSpaceBelow = spaceBelow > menuHeight + MENU_BUFFER
251
+ const hasEnoughSpaceAbove = spaceAbove > menuHeight + MENU_BUFFER
252
+
253
+ if (!isControlInViewport) {
254
+ instance.closeMenu()
255
+ } else if (instance.openDirection !== 'auto') {
256
+ instance.menu.placement = directionMap[instance.openDirection]
257
+ } else if (hasEnoughSpaceBelow || !hasEnoughSpaceAbove) {
258
+ instance.menu.placement = 'bottom'
259
+ } else {
260
+ instance.menu.placement = 'top'
261
+ }
262
+ },
263
+
264
+ setupMenuSizeWatcher() {
265
+ const { instance } = this
266
+ const $menu = instance.getMenu()
267
+
268
+ // istanbul ignore next
269
+ if (this.menuSizeWatcher) return
270
+
271
+ this.menuSizeWatcher = {
272
+ remove: watchSize($menu, this.adjustMenuOpenDirection),
273
+ }
274
+ },
275
+
276
+ setupMenuResizeAndScrollEventListeners() {
277
+ const { instance } = this
278
+ const $control = instance.getControl()
279
+
280
+ // istanbul ignore next
281
+ if (this.menuResizeAndScrollEventListeners) return
282
+
283
+ this.menuResizeAndScrollEventListeners = {
284
+ remove: setupResizeAndScrollEventListeners($control, this.adjustMenuOpenDirection),
285
+ }
286
+ },
287
+
288
+ removeMenuSizeWatcher() {
289
+ if (!this.menuSizeWatcher) return
290
+
291
+ this.menuSizeWatcher.remove()
292
+ this.menuSizeWatcher = null
293
+ },
294
+
295
+ removeMenuResizeAndScrollEventListeners() {
296
+ if (!this.menuResizeAndScrollEventListeners) return
297
+
298
+ this.menuResizeAndScrollEventListeners.remove()
299
+ this.menuResizeAndScrollEventListeners = null
300
+ },
301
+ },
302
+
303
+ render() {
304
+ return (
305
+ <div ref="menu-container" class="rp-treeselect__menu-container" style={this.menuContainerStyle}>
306
+ <transition name="rp-treeselect__menu--transition">
307
+ {this.renderMenu()}
308
+ </transition>
309
+ </div>
310
+ )
311
+ },
312
+ }
313
+ </script>
@@ -0,0 +1,179 @@
1
+ <script>
2
+ import Vue from 'vue'
3
+ import { watchSize, setupResizeAndScrollEventListeners, find } from '../utils'
4
+ import Menu from './Menu'
5
+
6
+ const PortalTarget = {
7
+ name: 'rp-treeselect--portal-target',
8
+ inject: [ 'instance' ],
9
+
10
+ watch: {
11
+ 'instance.menu.isOpen'(newValue) {
12
+ if (newValue) {
13
+ this.setupHandlers()
14
+ } else {
15
+ this.removeHandlers()
16
+ }
17
+ },
18
+
19
+ 'instance.menu.placement'() {
20
+ this.updateMenuContainerOffset()
21
+ },
22
+ },
23
+
24
+ created() {
25
+ this.controlResizeAndScrollEventListeners = null
26
+ this.controlSizeWatcher = null
27
+ },
28
+
29
+ mounted() {
30
+ const { instance } = this
31
+
32
+ if (instance.menu.isOpen) this.setupHandlers()
33
+ },
34
+
35
+ methods: {
36
+ setupHandlers() {
37
+ this.updateWidth()
38
+ this.updateMenuContainerOffset()
39
+ this.setupControlResizeAndScrollEventListeners()
40
+ this.setupControlSizeWatcher()
41
+ },
42
+
43
+ removeHandlers() {
44
+ this.removeControlResizeAndScrollEventListeners()
45
+ this.removeControlSizeWatcher()
46
+ },
47
+
48
+ setupControlResizeAndScrollEventListeners() {
49
+ const { instance } = this
50
+ const $control = instance.getControl()
51
+
52
+ // istanbul ignore next
53
+ if (this.controlResizeAndScrollEventListeners) return
54
+
55
+ this.controlResizeAndScrollEventListeners = {
56
+ remove: setupResizeAndScrollEventListeners($control, this.updateMenuContainerOffset),
57
+ }
58
+ },
59
+
60
+ setupControlSizeWatcher() {
61
+ const { instance } = this
62
+ const $control = instance.getControl()
63
+
64
+ // istanbul ignore next
65
+ if (this.controlSizeWatcher) return
66
+
67
+ this.controlSizeWatcher = {
68
+ remove: watchSize($control, () => {
69
+ this.updateWidth()
70
+ this.updateMenuContainerOffset()
71
+ }),
72
+ }
73
+ },
74
+
75
+ removeControlResizeAndScrollEventListeners() {
76
+ if (!this.controlResizeAndScrollEventListeners) return
77
+
78
+ this.controlResizeAndScrollEventListeners.remove()
79
+ this.controlResizeAndScrollEventListeners = null
80
+ },
81
+
82
+ removeControlSizeWatcher() {
83
+ if (!this.controlSizeWatcher) return
84
+
85
+ this.controlSizeWatcher.remove()
86
+ this.controlSizeWatcher = null
87
+ },
88
+
89
+ updateWidth() {
90
+ const { instance } = this
91
+ const $portalTarget = this.$el
92
+ const $control = instance.getControl()
93
+ const controlRect = $control.getBoundingClientRect()
94
+
95
+ $portalTarget.style.width = controlRect.width + 'px'
96
+ },
97
+
98
+ updateMenuContainerOffset() {
99
+ const { instance } = this
100
+ const $control = instance.getControl()
101
+ const $portalTarget = this.$el
102
+ const controlRect = $control.getBoundingClientRect()
103
+ const portalTargetRect = $portalTarget.getBoundingClientRect()
104
+ const offsetY = instance.menu.placement === 'bottom' ? controlRect.height : 0
105
+ const left = Math.round(controlRect.left - portalTargetRect.left) + 'px'
106
+ const top = Math.round(controlRect.top - portalTargetRect.top + offsetY) + 'px'
107
+ const menuContainerStyle = this.$refs.menu.$refs['menu-container'].style
108
+ const transformVariations = [ 'transform', 'webkitTransform', 'MozTransform', 'msTransform' ]
109
+ const transform = find(transformVariations, t => t in document.body.style)
110
+
111
+ // IE9 doesn't support `translate3d()`.
112
+ menuContainerStyle[transform] = `translate(${left}, ${top})`
113
+ },
114
+ },
115
+
116
+ render() {
117
+ const { instance } = this
118
+ const portalTargetClass = [ 'rp-treeselect__portal-target', instance.wrapperClass ]
119
+ const portalTargetStyle = { zIndex: instance.zIndex }
120
+
121
+ return (
122
+ <div class={portalTargetClass} style={portalTargetStyle} data-instance-id={instance.getInstanceId()}>
123
+ <Menu ref="menu" />
124
+ </div>
125
+ )
126
+ },
127
+
128
+ destroyed() {
129
+ this.removeHandlers()
130
+ },
131
+ }
132
+
133
+ let placeholder
134
+
135
+ export default {
136
+ name: 'rp-treeselect--menu-portal',
137
+
138
+ created() {
139
+ this.portalTarget = null
140
+ },
141
+
142
+ mounted() {
143
+ this.setup()
144
+ },
145
+
146
+ destroyed() {
147
+ this.teardown()
148
+ },
149
+
150
+ methods: {
151
+ setup() {
152
+ const el = document.createElement('div')
153
+ document.body.appendChild(el)
154
+
155
+ this.portalTarget = new Vue({
156
+ el,
157
+ parent: this,
158
+ ...PortalTarget,
159
+ })
160
+ },
161
+
162
+ teardown() {
163
+ document.body.removeChild(this.portalTarget.$el)
164
+ this.portalTarget.$el.innerHTML = ''
165
+
166
+ this.portalTarget.$destroy()
167
+ this.portalTarget = null
168
+ },
169
+ },
170
+
171
+ render() {
172
+ if (!placeholder) placeholder = (
173
+ <div class="rp-treeselect__menu-placeholder" />
174
+ )
175
+
176
+ return placeholder
177
+ },
178
+ }
179
+ </script>
@@ -0,0 +1,56 @@
1
+ <script>
2
+ import MultiValueItem from './MultiValueItem'
3
+ import Input from './Input'
4
+ import Placeholder from './Placeholder'
5
+
6
+ export default {
7
+ name: 'rp-treeselect--multi-value',
8
+ inject: [ 'instance' ],
9
+
10
+ methods: {
11
+ renderMultiValueItems() {
12
+ const { instance } = this
13
+
14
+ return instance.internalValue
15
+ .slice(0, instance.limit)
16
+ .map(instance.getNode)
17
+ .map(node => (
18
+ <MultiValueItem key={`multi-value-item-${node.id}`} node={node} />
19
+ ))
20
+ },
21
+
22
+ renderExceedLimitTip() {
23
+ const { instance } = this
24
+ const count = instance.internalValue.length - instance.limit
25
+
26
+ if (count <= 0) return null
27
+
28
+ return (
29
+ <div class="rp-treeselect__limit-tip rp-treeselect-helper-zoom-effect-off" key="exceed-limit-tip">
30
+ <span class="rp-treeselect__limit-tip-text">{ instance.limitText(count) }</span>
31
+ </div>
32
+ )
33
+ },
34
+ },
35
+
36
+ render() {
37
+ const { renderValueContainer } = this.$parent
38
+ const transitionGroupProps = {
39
+ props: {
40
+ tag: 'div',
41
+ name: 'rp-treeselect__multi-value-item--transition',
42
+ appear: true,
43
+ },
44
+ }
45
+
46
+ return renderValueContainer(
47
+ <transition-group class="rp-treeselect__multi-value" {...transitionGroupProps}>
48
+ {this.renderMultiValueItems()}
49
+ {this.renderExceedLimitTip()}
50
+ <Placeholder key="placeholder" />
51
+ <Input ref="input" key="input" />
52
+ </transition-group>,
53
+ )
54
+ },
55
+ }
56
+ </script>
@@ -0,0 +1,45 @@
1
+ <script>
2
+ import { onLeftClick } from '../utils'
3
+ import DeleteIcon from './icons/Delete'
4
+
5
+ export default {
6
+ name: 'rp-treeselect--multi-value-item',
7
+ inject: [ 'instance' ],
8
+
9
+ props: {
10
+ node: {
11
+ type: Object,
12
+ required: true,
13
+ },
14
+ },
15
+
16
+ methods: {
17
+ handleMouseDown: onLeftClick(function handleMouseDown() {
18
+ const { instance, node } = this
19
+
20
+ // Deselect this node.
21
+ instance.select(node)
22
+ }),
23
+ },
24
+
25
+ render() {
26
+ const { instance, node } = this
27
+ const itemClass = {
28
+ 'rp-treeselect__multi-value-item': true,
29
+ 'rp-treeselect__multi-value-item-disabled': node.isDisabled,
30
+ 'rp-treeselect__multi-value-item-new': node.isNew,
31
+ }
32
+ const customValueLabelRenderer = instance.$scopedSlots['value-label']
33
+ const labelRenderer = customValueLabelRenderer ? customValueLabelRenderer({ node }) : node.label
34
+
35
+ return (
36
+ <div class="rp-treeselect__multi-value-item-container">
37
+ <div class={itemClass} onMousedown={this.handleMouseDown}>
38
+ <span class="rp-treeselect__multi-value-label">{ labelRenderer }</span>
39
+ <span class="rp-treeselect__icon rp-treeselect__value-remove"><DeleteIcon /></span>
40
+ </div>
41
+ </div>
42
+ )
43
+ },
44
+ }
45
+ </script>