datafolder 1.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.
- package/README.md +169 -0
- package/index.js +3 -0
- package/package.json +27 -0
- package/sample.js +37 -0
- package/scripts/airbnb.js +73 -0
- package/scripts/airbnb2.js +74 -0
- package/scripts/airbnb2_queries.js +231 -0
- package/scripts/airbnb_queries.js +264 -0
- package/scripts/downloadFile.js +42 -0
- package/scripts/express.js +46 -0
- package/scripts/fastify.js +79 -0
- package/scripts/flights.js +75 -0
- package/scripts/flights2.js +81 -0
- package/scripts/flights2_queries.js +453 -0
- package/scripts/flights_queries.js +403 -0
- package/src/QyAcl.js +252 -0
- package/src/QyAclSaver.js +14 -0
- package/src/QyAclSaver_Worker.js +29 -0
- package/src/QyAclWriter.js +19 -0
- package/src/QyBatch.js +501 -0
- package/src/QyBinWriter.js +140 -0
- package/src/QyCD.js +61 -0
- package/src/QyCache.js +212 -0
- package/src/QyCompressor.js +60 -0
- package/src/QyCompressor_Worker.js +13 -0
- package/src/QyDB.js +122 -0
- package/src/QyDefaultOptions.js +59 -0
- package/src/QyDir.js +192 -0
- package/src/QyFetch.js +38 -0
- package/src/QyFile.js +124 -0
- package/src/QyFileContentUtils.js +175 -0
- package/src/QyFileLogger.js +58 -0
- package/src/QyIndex.js +237 -0
- package/src/QyIndexPropTree.js +92 -0
- package/src/QyIntersectNode.js +48 -0
- package/src/QyItem.js +105 -0
- package/src/QyJsonSaver.js +20 -0
- package/src/QyKVData.js +289 -0
- package/src/QyKVDataCleaner.js +17 -0
- package/src/QyKVDataCleaner_Worker.js +94 -0
- package/src/QyKVDataLoader.js +65 -0
- package/src/QyKVDataSaver.js +17 -0
- package/src/QyKVDataSaver_Worker.js +81 -0
- package/src/QyLogFileSaver.js +14 -0
- package/src/QyLogFileSaver_Worker.js +26 -0
- package/src/QyLogger.js +76 -0
- package/src/QyMessageWorker.js +41 -0
- package/src/QyMessager.js +84 -0
- package/src/QyMessager_Run.js +13 -0
- package/src/QyMod.js +15 -0
- package/src/QyOpRunner.js +73 -0
- package/src/QyQueue.js +60 -0
- package/src/QySaver.js +83 -0
- package/src/QySnapshotCompactor.js +17 -0
- package/src/QySnapshotCompactor_Worker.js +151 -0
- package/src/QySnapshotSaver.js +17 -0
- package/src/QySnapshotSaver_Worker.js +164 -0
- package/src/QySnapshots.js +354 -0
- package/src/QyStructure.js +50 -0
- package/src/QyTrigger.js +133 -0
- package/src/QyUnionNode.js +105 -0
- package/src/QyUtils.js +250 -0
- package/src/QyWorker.js +33 -0
- package/src/hackernews.js +93 -0
- package/src/rpc/evalCode.js +9 -0
- package/src/rpc/getFileContent.js +16 -0
- package/src/rpc/getSubItems.js +32 -0
- package/src/rpc/queryFiles.js +33 -0
- package/src/rpc/reloadRpc.js +1 -0
- package/src/simulate.js +114 -0
- package/src/simulate2.js +110 -0
- package/src/soc_pokec.js +43 -0
- package/static/bootstrap-5.3.8-dist/css/bootstrap.min.css +6 -0
- package/static/bootstrap-5.3.8-dist/js/bootstrap.bundle.js +6312 -0
- package/static/datafolderui/QyStyledElement.js +30 -0
- package/static/datafolderui/QyUIFolderTree.js +203 -0
- package/static/datafolderui/QyUIUtils.js +42 -0
- package/static/datafolderui/display-tree.js +108 -0
- package/static/datafolderui/explore-tree.js +122 -0
- package/static/datafolderui/folder-tree.js +5 -0
- package/static/datafolderui/hscrpt.js +25 -0
- package/static/datafolderui/index.css +6 -0
- package/static/datafolderui/index.html +20 -0
- package/static/datafolderui/json-view.js +74 -0
- package/static/datafolderui/kv-input.js +66 -0
- package/static/datafolderui/list-input.js +52 -0
- package/static/datafolderui/main-page.js +98 -0
- package/static/datafolderui/num-input.js +27 -0
- package/static/datafolderui/query-editor.js +229 -0
- package/static/datafolderui/querySelectorShadowRoot.js +24 -0
- package/static/datafolderui/resizable-input.js +67 -0
- package/static/favicon.ico +0 -0
- package/static/icons-1.13.1/font/bootstrap-icons.min.css +5 -0
- package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff +0 -0
- package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
- package/static/json-view/dist/jsonview.js +433 -0
- package/static/tree.js/tree.css +106 -0
- package/static/tree.js/tree.js +374 -0
- package/static/w2ui/dist/w2ui.min.css +2 -0
- package/static/w2ui/src/less/icons/icons.json +1 -0
- package/static/w2ui/src/less/icons/preview.html +33 -0
- package/static/w2ui/src/less/icons/readme.md +15 -0
- package/static/w2ui/src/less/icons/svg/box.svg +3 -0
- package/static/w2ui/src/less/icons/svg/check.svg +3 -0
- package/static/w2ui/src/less/icons/svg/colors.svg +3 -0
- package/static/w2ui/src/less/icons/svg/columns.svg +5 -0
- package/static/w2ui/src/less/icons/svg/cross.svg +4 -0
- package/static/w2ui/src/less/icons/svg/drop.svg +6 -0
- package/static/w2ui/src/less/icons/svg/empty.svg +3 -0
- package/static/w2ui/src/less/icons/svg/eye-dropper.svg +4 -0
- package/static/w2ui/src/less/icons/svg/info.svg +8 -0
- package/static/w2ui/src/less/icons/svg/paste.svg +4 -0
- package/static/w2ui/src/less/icons/svg/pencil.svg +5 -0
- package/static/w2ui/src/less/icons/svg/plus.svg +3 -0
- package/static/w2ui/src/less/icons/svg/reload.svg +5 -0
- package/static/w2ui/src/less/icons/svg/search.svg +3 -0
- package/static/w2ui/src/less/icons/svg/settings.svg +8 -0
- package/static/w2ui/src/less/icons/w2ui-font.css +36 -0
- package/static/w2ui/src/less/icons/w2ui-font.woff +0 -0
- package/static/w2ui/src/less/src/buttons.less +184 -0
- package/static/w2ui/src/less/src/common.less +541 -0
- package/static/w2ui/src/less/src/fields.less +302 -0
- package/static/w2ui/src/less/src/form.less +319 -0
- package/static/w2ui/src/less/src/grid.less +1057 -0
- package/static/w2ui/src/less/src/icons.less +37 -0
- package/static/w2ui/src/less/src/layout.less +86 -0
- package/static/w2ui/src/less/src/mixins.less +87 -0
- package/static/w2ui/src/less/src/popup.less +277 -0
- package/static/w2ui/src/less/src/sidebar.less +343 -0
- package/static/w2ui/src/less/src/tabs.less +145 -0
- package/static/w2ui/src/less/src/toolbar.less +219 -0
- package/static/w2ui/src/less/src/tooltip.less +948 -0
- package/static/w2ui/src/less/w2ui.less +196 -0
- package/static/w2ui/src/locale/az-az.json +114 -0
- package/static/w2ui/src/locale/ba-ba.json +114 -0
- package/static/w2ui/src/locale/bg-bg.json +114 -0
- package/static/w2ui/src/locale/ca-es.json +114 -0
- package/static/w2ui/src/locale/da-dk.json +114 -0
- package/static/w2ui/src/locale/de-de.json +114 -0
- package/static/w2ui/src/locale/en-gb.json +17 -0
- package/static/w2ui/src/locale/en-us.json +17 -0
- package/static/w2ui/src/locale/es-ar.json +115 -0
- package/static/w2ui/src/locale/es-cr.json +119 -0
- package/static/w2ui/src/locale/es-es.json +114 -0
- package/static/w2ui/src/locale/es-mx.json +114 -0
- package/static/w2ui/src/locale/fr-fr.json +114 -0
- package/static/w2ui/src/locale/gl-es.json +114 -0
- package/static/w2ui/src/locale/hr-hr.json +114 -0
- package/static/w2ui/src/locale/hu-hu.json +114 -0
- package/static/w2ui/src/locale/id-id.json +114 -0
- package/static/w2ui/src/locale/it-it.json +114 -0
- package/static/w2ui/src/locale/ja-jp.json +114 -0
- package/static/w2ui/src/locale/kk-kz.json +116 -0
- package/static/w2ui/src/locale/ko-kr.json +114 -0
- package/static/w2ui/src/locale/lt-lt.json +114 -0
- package/static/w2ui/src/locale/nl-nl.json +114 -0
- package/static/w2ui/src/locale/no-no.json +114 -0
- package/static/w2ui/src/locale/pl-pl.json +114 -0
- package/static/w2ui/src/locale/pt-br.json +114 -0
- package/static/w2ui/src/locale/ru-ru.json +116 -0
- package/static/w2ui/src/locale/sk-sk.json +114 -0
- package/static/w2ui/src/locale/sl-si.json +116 -0
- package/static/w2ui/src/locale/sr-rs.json +114 -0
- package/static/w2ui/src/locale/tr-tr.json +114 -0
- package/static/w2ui/src/locale/uk-ua.json +114 -0
- package/static/w2ui/src/locale/zh-cn.json +114 -0
- package/static/w2ui/src/locale/zh-tw.json +114 -0
- package/static/w2ui/src/query.js +583 -0
- package/static/w2ui/src/readme.md +11 -0
- package/static/w2ui/src/w2base.js +311 -0
- package/static/w2ui/src/w2compat.js +207 -0
- package/static/w2ui/src/w2field.js +1854 -0
- package/static/w2ui/src/w2form.js +2411 -0
- package/static/w2ui/src/w2grid.js +8916 -0
- package/static/w2ui/src/w2layout.js +1194 -0
- package/static/w2ui/src/w2locale.js +129 -0
- package/static/w2ui/src/w2popup.js +909 -0
- package/static/w2ui/src/w2sidebar.js +1769 -0
- package/static/w2ui/src/w2tabs.js +669 -0
- package/static/w2ui/src/w2toolbar.js +1180 -0
- package/static/w2ui/src/w2tooltip.js +3134 -0
- package/static/w2ui/src/w2utils.js +2457 -0
|
@@ -0,0 +1,3134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Part of w2ui 2.0 library
|
|
3
|
+
* - Dependencies: mQuery, w2utils, w2base
|
|
4
|
+
*
|
|
5
|
+
* TODO:
|
|
6
|
+
* - need help pages
|
|
7
|
+
*
|
|
8
|
+
* 2.0 Changes
|
|
9
|
+
* - multiple tooltips to the same anchor
|
|
10
|
+
* - options.contextMenu
|
|
11
|
+
* - options.prefilter - if true, it will show prefiltered items for w2menu, otherwise all
|
|
12
|
+
* - menu.item.help, menu.item.hotkey, menu.item.extra
|
|
13
|
+
* - options.selected -> for w2menu
|
|
14
|
+
* - options.tooltip => {}
|
|
15
|
+
* - w2menu event onTooltip
|
|
16
|
+
* - added onMouseEnter and onMouseLeave for w2menu
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { w2base } from './w2base.js'
|
|
20
|
+
import { w2utils } from './w2utils.js'
|
|
21
|
+
import { query } from './query.js'
|
|
22
|
+
|
|
23
|
+
class Tooltip {
|
|
24
|
+
// no need to extend w2base, as each individual tooltip extends it
|
|
25
|
+
static active = {} // all defined tooltips
|
|
26
|
+
constructor() {
|
|
27
|
+
this.defaults = {
|
|
28
|
+
name : null, // name for the overlay, otherwise input id is used
|
|
29
|
+
html : '', // text or html
|
|
30
|
+
style : '', // additional style for the overlay
|
|
31
|
+
class : '', // add class for w2ui-tooltip-body
|
|
32
|
+
position : 'top|bottom', // can be left, right, top, bottom
|
|
33
|
+
draggable : false, // if true, then tooltip can be move with mouse
|
|
34
|
+
align : '', // can be: both, both:XX left, right, both, top, bottom
|
|
35
|
+
anchor : null, // element it is attached to, if anchor is body, then it is context menu
|
|
36
|
+
contextMenu : false, // if true, then it is context menu
|
|
37
|
+
anchorClass : '', // add class for anchor when tooltip is shown
|
|
38
|
+
anchorStyle : '', // add style for anchor when tooltip is shown
|
|
39
|
+
autoShow : false, // if autoShow true, then tooltip will show on mouseEnter and hide on mouseLeave
|
|
40
|
+
autoShowOn : null, // when options.autoShow = true, mouse event to show on
|
|
41
|
+
autoHideOn : null, // when options.autoShow = true, mouse event to hide on
|
|
42
|
+
arrowSize : 8, // size of the carret
|
|
43
|
+
screenMargin : 2, // min margin from screen to tooltip
|
|
44
|
+
autoResize : true, // auto resize based on content size and available size
|
|
45
|
+
margin : 1, // distance from the anchor
|
|
46
|
+
offsetX : 0, // delta for left coordinate
|
|
47
|
+
offsetY : 0, // delta for top coordinate
|
|
48
|
+
maxWidth : null, // max width
|
|
49
|
+
maxHeight : null, // max height
|
|
50
|
+
watchScroll : null, // attach to onScroll event // TODO:
|
|
51
|
+
watchResize : null, // attach to onResize event // TODO:
|
|
52
|
+
hideOn : null, // events when to hide tooltip, ['click', 'change', 'key', 'focus', 'blur'],
|
|
53
|
+
onThen : null, // called when displayed
|
|
54
|
+
onShow : null, // callBack when shown
|
|
55
|
+
onHide : null, // callBack when hidden
|
|
56
|
+
onUpdate : null, // callback when tooltip gets updated
|
|
57
|
+
onMove : null // callback when tooltip is moved
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static observeRemove = new MutationObserver((mutations) => {
|
|
62
|
+
let cnt = 0
|
|
63
|
+
Object.keys(Tooltip.active).forEach(name => {
|
|
64
|
+
let overlay = Tooltip.active[name]
|
|
65
|
+
if (overlay.displayed) {
|
|
66
|
+
if (!overlay.anchor || !overlay.anchor.isConnected) {
|
|
67
|
+
overlay.hide()
|
|
68
|
+
} else {
|
|
69
|
+
cnt++
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
// remove observer, as there is no active tooltips
|
|
74
|
+
if (cnt === 0) {
|
|
75
|
+
Tooltip.observeRemove.disconnect()
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
trigger(event, data) {
|
|
80
|
+
if (arguments.length == 2) {
|
|
81
|
+
let type = event
|
|
82
|
+
event = data
|
|
83
|
+
data.type = type
|
|
84
|
+
}
|
|
85
|
+
if (event.overlay) {
|
|
86
|
+
return event.overlay.trigger(event)
|
|
87
|
+
} else {
|
|
88
|
+
console.log('ERROR: cannot find overlay where to trigger events')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get(name) {
|
|
93
|
+
if (arguments.length == 0) {
|
|
94
|
+
return Object.keys(Tooltip.active)
|
|
95
|
+
} else if (name === true) {
|
|
96
|
+
return Tooltip.active
|
|
97
|
+
} else {
|
|
98
|
+
return Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
attach(anchor, text) {
|
|
103
|
+
let options, overlay
|
|
104
|
+
let self = this
|
|
105
|
+
if (arguments.length == 0) {
|
|
106
|
+
return
|
|
107
|
+
} else if (arguments.length == 1 && anchor instanceof Object) {
|
|
108
|
+
options = anchor
|
|
109
|
+
anchor = options.anchor
|
|
110
|
+
} else if (arguments.length === 2 && typeof text === 'string') {
|
|
111
|
+
options = { anchor, html: text }
|
|
112
|
+
text = options.html
|
|
113
|
+
} else if (arguments.length === 2 && text != null && typeof text === 'object') {
|
|
114
|
+
options = text
|
|
115
|
+
text = options.html
|
|
116
|
+
}
|
|
117
|
+
options = w2utils.extend({}, this.defaults, options || {})
|
|
118
|
+
if (!text && options.text) text = options.text
|
|
119
|
+
if (!text && options.html) text = options.html
|
|
120
|
+
// anchor is func var
|
|
121
|
+
delete options.anchor
|
|
122
|
+
|
|
123
|
+
// define tooltip
|
|
124
|
+
let name = (options.name ? options.name : anchor?.id)
|
|
125
|
+
if (anchor == document || anchor == null) {
|
|
126
|
+
anchor = document.body
|
|
127
|
+
}
|
|
128
|
+
if (options.contextMenu) {
|
|
129
|
+
anchor = document.body
|
|
130
|
+
name = name ?? 'context-menu'
|
|
131
|
+
}
|
|
132
|
+
if (!name) {
|
|
133
|
+
name = 'noname-' + Object.keys(Tooltip.active).length
|
|
134
|
+
console.log('NOTICE: name property is not defined for tooltip, could lead to too many instances')
|
|
135
|
+
}
|
|
136
|
+
// clean name as it is used as id and css selector
|
|
137
|
+
name = name.replace(/[\s\.#]/g, '_')
|
|
138
|
+
if (Tooltip.active[name]) {
|
|
139
|
+
overlay = Tooltip.active[name]
|
|
140
|
+
overlay.prevOptions = overlay.options
|
|
141
|
+
overlay.options = options // do not merge or extend, otherwiser menu items get merged too
|
|
142
|
+
// overlay.options = w2utils.extend({}, overlay.options, options)
|
|
143
|
+
overlay.anchor = anchor // as HTML elements are not copied
|
|
144
|
+
if (overlay.prevOptions.html != overlay.options.html || overlay.prevOptions.class != overlay.options.class
|
|
145
|
+
|| overlay.prevOptions.style != overlay.options.style) {
|
|
146
|
+
overlay.needsUpdate = true
|
|
147
|
+
}
|
|
148
|
+
options = overlay.options // it was recreated
|
|
149
|
+
// clear all previous overlay events
|
|
150
|
+
Object.keys(overlay).forEach(key => {
|
|
151
|
+
let val = overlay[key]
|
|
152
|
+
if (key.startsWith('on') && typeof val == 'function') {
|
|
153
|
+
delete overlay[key]
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
} else {
|
|
157
|
+
overlay = new w2base()
|
|
158
|
+
Object.assign(overlay, {
|
|
159
|
+
id: 'w2overlay-' + name,
|
|
160
|
+
name, options, anchor, self,
|
|
161
|
+
displayed: false,
|
|
162
|
+
tmp: {
|
|
163
|
+
observeTooltipResize: new ResizeObserver(() => {
|
|
164
|
+
this.resize(overlay.name)
|
|
165
|
+
}),
|
|
166
|
+
observeAnchorResize: new ResizeObserver(() => {
|
|
167
|
+
this.resize(overlay.name)
|
|
168
|
+
}),
|
|
169
|
+
observeAnchorMove: new MutationObserver((mutations) => {
|
|
170
|
+
let target = mutations[0].target // all targets are the same
|
|
171
|
+
let currRect = target.getBoundingClientRect()
|
|
172
|
+
let lastRect = target._lastBoundingRect
|
|
173
|
+
if (!target._lastBoundingRect) {
|
|
174
|
+
target._lastBoundingRect = currRect
|
|
175
|
+
} else if (currRect.left !== lastRect.left || currRect.top !== lastRect.top) {
|
|
176
|
+
this.resize(overlay.name)
|
|
177
|
+
target._lastBoundingRect = currRect
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
},
|
|
181
|
+
hide() {
|
|
182
|
+
self.hide(name)
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
Tooltip.active[name] = overlay
|
|
186
|
+
}
|
|
187
|
+
// move events on to overlay layer
|
|
188
|
+
Object.keys(overlay.options).forEach(key => {
|
|
189
|
+
let val = overlay.options[key]
|
|
190
|
+
if (key.startsWith('on') && typeof val == 'function') {
|
|
191
|
+
overlay[key] = val
|
|
192
|
+
delete overlay.options[key]
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
// add event for auto show/hide
|
|
196
|
+
if (options.autoShow === true) {
|
|
197
|
+
options.autoShowOn = options.autoShowOn ?? 'mouseenter'
|
|
198
|
+
options.autoHideOn = options.autoHideOn ?? 'mouseleave'
|
|
199
|
+
options.autoShow = false
|
|
200
|
+
options._keep = true
|
|
201
|
+
}
|
|
202
|
+
if (options.autoShowOn) {
|
|
203
|
+
let scope = 'autoShow-' + overlay.name
|
|
204
|
+
query(anchor)
|
|
205
|
+
.off(`.${scope}`)
|
|
206
|
+
.on(`${options.autoShowOn}.${scope}`, event => {
|
|
207
|
+
self.show(overlay.name)
|
|
208
|
+
event.stopPropagation()
|
|
209
|
+
})
|
|
210
|
+
delete options.autoShowOn
|
|
211
|
+
options._keep = true
|
|
212
|
+
}
|
|
213
|
+
if (options.autoHideOn) {
|
|
214
|
+
let scope = 'autoHide-' + overlay.name
|
|
215
|
+
query(anchor)
|
|
216
|
+
.off(`.${scope}`)
|
|
217
|
+
.on(`${options.autoHideOn}.${scope}`, event => {
|
|
218
|
+
self.hide(overlay.name)
|
|
219
|
+
event.stopPropagation()
|
|
220
|
+
})
|
|
221
|
+
delete options.autoHideOn
|
|
222
|
+
options._keep = true
|
|
223
|
+
}
|
|
224
|
+
overlay.off('.attach')
|
|
225
|
+
let ret = {
|
|
226
|
+
overlay,
|
|
227
|
+
then: (callback) => {
|
|
228
|
+
overlay.on('show:after.attach', event => { callback(event) })
|
|
229
|
+
return ret
|
|
230
|
+
},
|
|
231
|
+
show: (callback) => {
|
|
232
|
+
overlay.on('show.attach', event => { callback(event) })
|
|
233
|
+
return ret
|
|
234
|
+
},
|
|
235
|
+
hide: (callback) => {
|
|
236
|
+
overlay.on('hide.attach', event => { callback(event) })
|
|
237
|
+
return ret
|
|
238
|
+
},
|
|
239
|
+
update: (callback) => {
|
|
240
|
+
overlay.on('update.attach', event => { callback(event) })
|
|
241
|
+
return ret
|
|
242
|
+
},
|
|
243
|
+
move: (callback) => {
|
|
244
|
+
overlay.on('move.attach', event => { callback(event) })
|
|
245
|
+
return ret
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return ret
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
update(name, html) {
|
|
252
|
+
let overlay = Tooltip.active[name]
|
|
253
|
+
if (overlay) {
|
|
254
|
+
overlay.needsUpdate = true
|
|
255
|
+
overlay.options.html = html
|
|
256
|
+
this.show(name)
|
|
257
|
+
} else {
|
|
258
|
+
console.log(`Tooltip "${name}" is not displayed. Cannot update it.`)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
show(name) {
|
|
263
|
+
if (name instanceof HTMLElement || name instanceof Object) {
|
|
264
|
+
let options = name
|
|
265
|
+
if (name instanceof HTMLElement) {
|
|
266
|
+
options = arguments[1] || {}
|
|
267
|
+
options.anchor = name
|
|
268
|
+
}
|
|
269
|
+
let ret = this.attach(options)
|
|
270
|
+
ret.overlay.tmp.hidden = false
|
|
271
|
+
|
|
272
|
+
query(ret.overlay.anchor)
|
|
273
|
+
.off('.autoShow-' + ret.overlay.name)
|
|
274
|
+
.off('.autoHide-' + ret.overlay.name)
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Need a timer, so that events in the 'return ret` would be preperty set as it is using chaning mechanism
|
|
278
|
+
* to set listeners: w2tooltip.show({}).then(...).show(...). Since it could be hidden before timer kick in
|
|
279
|
+
* to show it, need the check in the timeout.
|
|
280
|
+
*/
|
|
281
|
+
setTimeout(() => {
|
|
282
|
+
if (!ret.overlay.tmp.hidden) {
|
|
283
|
+
this.show(ret.overlay.name)
|
|
284
|
+
if (this.initControls) {
|
|
285
|
+
this.initControls(ret.overlay)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}, 1)
|
|
289
|
+
return ret
|
|
290
|
+
}
|
|
291
|
+
let edata
|
|
292
|
+
let self = this
|
|
293
|
+
let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
294
|
+
if (!overlay) return
|
|
295
|
+
let options = overlay.options
|
|
296
|
+
if (!overlay || (overlay.displayed && !overlay.needsUpdate)) {
|
|
297
|
+
this.resize(overlay?.name)
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
let position = options.position.split('|')
|
|
301
|
+
let isVertical = ['top', 'bottom'].includes(position[0])
|
|
302
|
+
// enforce nowrap only when align=both and vertical
|
|
303
|
+
let overlayStyles = (options.align == 'both' && isVertical ? '' : 'white-space: nowrap;')
|
|
304
|
+
if (options.maxWidth && w2utils.getStrWidth(options.html, '') > options.maxWidth) {
|
|
305
|
+
overlayStyles = 'width: '+ options.maxWidth + 'px; white-space: inherit; overflow: auto;'
|
|
306
|
+
}
|
|
307
|
+
overlayStyles += ' max-height: '+ (options.maxHeight ? options.maxHeight : window.innerHeight - 4) + 'px;'
|
|
308
|
+
// if empty content - then hide it
|
|
309
|
+
if (options.html === '' || options.html == null) {
|
|
310
|
+
self.hide(name)
|
|
311
|
+
return
|
|
312
|
+
} else if (overlay.box) {
|
|
313
|
+
// if already present, update it
|
|
314
|
+
edata = this.trigger('update', { target: name, overlay })
|
|
315
|
+
if (edata.isCancelled === true) {
|
|
316
|
+
// restore previous options
|
|
317
|
+
if (overlay.prevOptions) {
|
|
318
|
+
overlay.options = overlay.prevOptions
|
|
319
|
+
delete overlay.prevOptions
|
|
320
|
+
}
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
query(overlay.box)
|
|
324
|
+
.find('.w2ui-overlay-body')
|
|
325
|
+
.attr('style', (options.style || '') + '; ' + overlayStyles)
|
|
326
|
+
.removeClass() // removes all classes
|
|
327
|
+
.addClass('w2ui-overlay-body ' + options.class + (options.draggable ? ' w2ui-draggable' : ''))
|
|
328
|
+
.html(options.html)
|
|
329
|
+
this.resize(overlay.name)
|
|
330
|
+
} else {
|
|
331
|
+
// event before
|
|
332
|
+
edata = this.trigger('show', { target: name, overlay })
|
|
333
|
+
if (edata.isCancelled === true) return
|
|
334
|
+
// normal processing
|
|
335
|
+
query('body').append(
|
|
336
|
+
// pointer-events will be re-enabled leter
|
|
337
|
+
`<div id="${overlay.id}" name="${name}" style="display: none; pointer-events: none" class="w2ui-overlay"
|
|
338
|
+
data-click="stop" data-focusin="stop">
|
|
339
|
+
<style></style>
|
|
340
|
+
<div class="w2ui-overlay-body w2ui-eaction ${options.class} ${options.draggable ? 'w2ui-draggable' : ''}"
|
|
341
|
+
style="${options.style || ''}; ${overlayStyles}" ${options.draggable ? 'data-mousedown="startDrag|event"' : ''}>
|
|
342
|
+
${options.html}
|
|
343
|
+
</div>
|
|
344
|
+
</div>`)
|
|
345
|
+
overlay.box = query('#' + w2utils.escapeId(overlay.id))[0]
|
|
346
|
+
overlay.displayed = true
|
|
347
|
+
let names = query(overlay.anchor).data('tooltipName') ?? []
|
|
348
|
+
names.push(name)
|
|
349
|
+
query(overlay.anchor).data('tooltipName', names) // make available to element overlay attached to
|
|
350
|
+
w2utils.bindEvents(overlay.box, {})
|
|
351
|
+
// remember anchor's original styles
|
|
352
|
+
overlay.tmp.originalCSS = ''
|
|
353
|
+
if (query(overlay.anchor).length > 0) {
|
|
354
|
+
overlay.tmp.originalCSS = query(overlay.anchor)[0].style.cssText
|
|
355
|
+
}
|
|
356
|
+
this.resize(overlay.name)
|
|
357
|
+
}
|
|
358
|
+
if (options.anchorStyle) {
|
|
359
|
+
overlay.anchor.style.cssText += ';' + options.anchorStyle
|
|
360
|
+
}
|
|
361
|
+
if (options.anchorClass) {
|
|
362
|
+
// do not add w2ui-focus to body
|
|
363
|
+
if (!(options.anchorClass == 'w2ui-focus' && overlay.anchor == document.body)) {
|
|
364
|
+
query(overlay.anchor).addClass(options.anchorClass)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// add on hide events
|
|
368
|
+
if (typeof options.hideOn == 'string') options.hideOn = [options.hideOn]
|
|
369
|
+
if (!Array.isArray(options.hideOn)) options.hideOn = []
|
|
370
|
+
// initial scroll
|
|
371
|
+
Object.assign(overlay.tmp, {
|
|
372
|
+
scrollLeft: document.body.scrollLeft,
|
|
373
|
+
scrollTop: document.body.scrollTop
|
|
374
|
+
})
|
|
375
|
+
addHideEvents()
|
|
376
|
+
addWatchEvents(document.body)
|
|
377
|
+
// first show empty tooltip, so it will popup up in the right position
|
|
378
|
+
query(overlay.box).show()
|
|
379
|
+
overlay.tmp.observeTooltipResize.observe(overlay.box)
|
|
380
|
+
overlay.tmp.observeAnchorResize.observe(overlay.anchor)
|
|
381
|
+
overlay.tmp.observeAnchorMove.observe(overlay.anchor, { attributes: true })
|
|
382
|
+
// observer element removal from DOM
|
|
383
|
+
Tooltip.observeRemove.observe(document.body, { subtree: true, childList: true })
|
|
384
|
+
// then insert html and it will adjust
|
|
385
|
+
query(overlay.box)
|
|
386
|
+
.css('opacity', 1)
|
|
387
|
+
.find('.w2ui-overlay-body')
|
|
388
|
+
.html(options.html)
|
|
389
|
+
/**
|
|
390
|
+
* pointer-events: none is needed to avoid cases when popup is shown right under the cursor
|
|
391
|
+
* or it will trigger onmouseout, onmouseleave and other events.
|
|
392
|
+
*/
|
|
393
|
+
setTimeout(() => { query(overlay.box).css({ 'pointer-events': 'auto' }).data('ready', 'yes') }, 100)
|
|
394
|
+
|
|
395
|
+
// bind events
|
|
396
|
+
w2utils.bindEvents(query(overlay.box).find('.w2ui-eaction'), this)
|
|
397
|
+
|
|
398
|
+
delete overlay.needsUpdate
|
|
399
|
+
// expose overlay to DOM element
|
|
400
|
+
overlay.box.overlay = overlay
|
|
401
|
+
// event after
|
|
402
|
+
if (edata) edata.finish()
|
|
403
|
+
return { overlay }
|
|
404
|
+
|
|
405
|
+
function addWatchEvents(el) {
|
|
406
|
+
let scope = 'tooltip-' + overlay.name
|
|
407
|
+
let queryEl = el
|
|
408
|
+
if (el.tagName == 'BODY') {
|
|
409
|
+
queryEl = el.ownerDocument
|
|
410
|
+
}
|
|
411
|
+
query(queryEl)
|
|
412
|
+
.off(`.${scope}`)
|
|
413
|
+
.on(`scroll.${scope}`, event => {
|
|
414
|
+
Object.assign(overlay.tmp, {
|
|
415
|
+
scrollLeft: el.scrollLeft,
|
|
416
|
+
scrollTop: el.scrollTop
|
|
417
|
+
})
|
|
418
|
+
self.resize(overlay.name)
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function addHideEvents() {
|
|
423
|
+
let hide = (event) => { self.hide(overlay.name) }
|
|
424
|
+
let $anchor = query(overlay.anchor)
|
|
425
|
+
let scope = 'tooltip-' + overlay.name
|
|
426
|
+
// document click
|
|
427
|
+
query('html').off(`.${scope}`)
|
|
428
|
+
if (options.hideOn.includes('doc-click')) {
|
|
429
|
+
if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) {
|
|
430
|
+
// otherwise hides on click to focus
|
|
431
|
+
$anchor
|
|
432
|
+
.off(`.${scope}-doc`)
|
|
433
|
+
.on(`click.${scope}-doc`, (event) => { event.stopPropagation() })
|
|
434
|
+
}
|
|
435
|
+
query('html').on(`click.${scope}`, hide)
|
|
436
|
+
}
|
|
437
|
+
if (options.hideOn.includes('focus-change') || options.hideOn.includes('blur')) {
|
|
438
|
+
query('html')
|
|
439
|
+
.on(`focusin.${scope}`, (e) => {
|
|
440
|
+
if (document.activeElement != overlay.anchor) {
|
|
441
|
+
self.hide(overlay.name)
|
|
442
|
+
}
|
|
443
|
+
})
|
|
444
|
+
}
|
|
445
|
+
if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) {
|
|
446
|
+
$anchor.off(`.${scope}`)
|
|
447
|
+
options.hideOn.forEach(event => {
|
|
448
|
+
if (['doc-click', 'focus-change', 'blur'].indexOf(event) == -1) {
|
|
449
|
+
$anchor.on(`${event}.${scope}`, { once: true }, hide)
|
|
450
|
+
}
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
hide(name) {
|
|
457
|
+
let overlay
|
|
458
|
+
if (arguments.length == 0) {
|
|
459
|
+
// hide all tooltips
|
|
460
|
+
Object.keys(Tooltip.active).forEach(name => { this.hide(name) })
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
if (name instanceof HTMLElement) {
|
|
464
|
+
let names = query(name).data('tooltipName') ?? []
|
|
465
|
+
names.forEach(name => { this.hide(name) })
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
if (typeof name == 'string') {
|
|
469
|
+
name = name.replace(/[\s\.#]/g, '_')
|
|
470
|
+
overlay = Tooltip.active[name]
|
|
471
|
+
}
|
|
472
|
+
if (overlay?.tmp) overlay.tmp.hidden = true // it could be hidden before it is actually shown
|
|
473
|
+
if (!overlay || !overlay.box) return
|
|
474
|
+
|
|
475
|
+
// event before
|
|
476
|
+
let edata = this.trigger('hide', { target: name, overlay })
|
|
477
|
+
if (edata.isCancelled === true) return
|
|
478
|
+
|
|
479
|
+
// normal processing
|
|
480
|
+
if (!overlay.options._keep) delete Tooltip.active[name]
|
|
481
|
+
let scope = 'tooltip-' + overlay.name
|
|
482
|
+
overlay.tmp.observeTooltipResize?.disconnect()
|
|
483
|
+
overlay.tmp.observeAnchorResize?.disconnect()
|
|
484
|
+
overlay.tmp.observeAnchorMove?.disconnect()
|
|
485
|
+
if (overlay.options.watchScroll) {
|
|
486
|
+
query(overlay.options.watchScroll)
|
|
487
|
+
.off('.w2scroll-' + overlay.name)
|
|
488
|
+
}
|
|
489
|
+
// if no active tooltip then disable observeRemove
|
|
490
|
+
let cnt = 0
|
|
491
|
+
Object.keys(Tooltip.active).forEach(key => {
|
|
492
|
+
let overlay = Tooltip.active[key]
|
|
493
|
+
if (overlay.displayed) {
|
|
494
|
+
cnt++
|
|
495
|
+
}
|
|
496
|
+
})
|
|
497
|
+
if (cnt == 0) {
|
|
498
|
+
Tooltip.observeRemove.disconnect()
|
|
499
|
+
}
|
|
500
|
+
query('html').off(`.${scope}`) // hide to click event here
|
|
501
|
+
query(document).off(`.${scope}`) // scroll event here
|
|
502
|
+
// remove element
|
|
503
|
+
overlay.box?.remove()
|
|
504
|
+
overlay.box = null
|
|
505
|
+
overlay.displayed = false
|
|
506
|
+
// remove name from anchor properties
|
|
507
|
+
let names = query(overlay.anchor).data('tooltipName') ?? []
|
|
508
|
+
let ind = names.indexOf(overlay.name)
|
|
509
|
+
if (ind != -1) names.splice(names.indexOf(overlay.name), 1)
|
|
510
|
+
if (names.length == 0) {
|
|
511
|
+
query(overlay.anchor).removeData('tooltipName')
|
|
512
|
+
} else {
|
|
513
|
+
query(overlay.anchor).data('tooltipName', names)
|
|
514
|
+
}
|
|
515
|
+
// restore original CSS, only if anchor styles where extended
|
|
516
|
+
if (overlay.options.anchorStyle) {
|
|
517
|
+
overlay.anchor.style.cssText = overlay.tmp.originalCSS
|
|
518
|
+
}
|
|
519
|
+
query(overlay.anchor)
|
|
520
|
+
.off(`.${scope}`)
|
|
521
|
+
.removeClass(overlay.options.anchorClass)
|
|
522
|
+
// for remote data source
|
|
523
|
+
if (overlay.options.url) {
|
|
524
|
+
// remove all cached items
|
|
525
|
+
overlay.options.items.splice(0)
|
|
526
|
+
overlay.tmp.remote.hasMore = true
|
|
527
|
+
overlay.tmp.remote.search = null
|
|
528
|
+
}
|
|
529
|
+
// event after
|
|
530
|
+
edata.finish()
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
resize(name) {
|
|
534
|
+
let state = { moved: false, resize: false }
|
|
535
|
+
if (arguments.length == 0) {
|
|
536
|
+
Object.keys(Tooltip.active).forEach(key => {
|
|
537
|
+
let overlay = Tooltip.active[key]
|
|
538
|
+
if (overlay.displayed) this.resize(overlay.name)
|
|
539
|
+
})
|
|
540
|
+
return { multiple: true }
|
|
541
|
+
}
|
|
542
|
+
let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
543
|
+
let pos = this.getPosition(overlay.name)
|
|
544
|
+
let newPos = pos.left + 'x' + pos.top
|
|
545
|
+
let newSize = pos.width + 'x' + pos.height
|
|
546
|
+
let edata1, edata2
|
|
547
|
+
if (overlay.tmp.lastPos != newPos) {
|
|
548
|
+
edata1 = this.trigger('move', { target: name, overlay, pos })
|
|
549
|
+
state.moved = true
|
|
550
|
+
}
|
|
551
|
+
if (overlay.tmp.lastSize != newSize) {
|
|
552
|
+
edata2 = this.trigger('resize', { target: name, overlay, pos })
|
|
553
|
+
state.moved = true
|
|
554
|
+
}
|
|
555
|
+
query(overlay.box)
|
|
556
|
+
.css({
|
|
557
|
+
left: pos.left + 'px',
|
|
558
|
+
top : pos.top + 'px'
|
|
559
|
+
})
|
|
560
|
+
.then(query => {
|
|
561
|
+
if (pos.width != null) {
|
|
562
|
+
query.css('width', pos.width + 'px')
|
|
563
|
+
.find('.w2ui-overlay-body')
|
|
564
|
+
.css('width', '100%')
|
|
565
|
+
}
|
|
566
|
+
if (pos.height != null) {
|
|
567
|
+
query.css('height', pos.height + 'px')
|
|
568
|
+
.find('.w2ui-overlay-body')
|
|
569
|
+
.css('height', '100%')
|
|
570
|
+
}
|
|
571
|
+
})
|
|
572
|
+
.find('.w2ui-overlay-body')
|
|
573
|
+
.removeClass('w2ui-arrow-right w2ui-arrow-left w2ui-arrow-top w2ui-arrow-bottom')
|
|
574
|
+
.addClass(pos.arrow.class)
|
|
575
|
+
.closest('.w2ui-overlay')
|
|
576
|
+
.find('style')
|
|
577
|
+
.text(pos.arrow.style)
|
|
578
|
+
|
|
579
|
+
if (overlay.tmp.lastPos != newPos && edata1) {
|
|
580
|
+
overlay.tmp.lastPos = newPos
|
|
581
|
+
edata1.finish()
|
|
582
|
+
}
|
|
583
|
+
if (overlay.tmp.lastSize != newSize && edata2) {
|
|
584
|
+
overlay.tmp.lastSize = newSize
|
|
585
|
+
edata2.finish()
|
|
586
|
+
}
|
|
587
|
+
return state
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
getPosition(name) {
|
|
591
|
+
let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
592
|
+
if (!overlay || !overlay.box) {
|
|
593
|
+
return
|
|
594
|
+
}
|
|
595
|
+
let options = overlay.options
|
|
596
|
+
if (overlay.tmp.resizedY || overlay.tmp.resizedX) {
|
|
597
|
+
query(overlay.box).css({ width: '', height: '', scroll: 'auto' })
|
|
598
|
+
}
|
|
599
|
+
let scrollSize = w2utils.scrollBarSize()
|
|
600
|
+
let hasScrollBarX = !(document.body.scrollWidth == document.body.clientWidth)
|
|
601
|
+
let hasScrollBarY = !(document.body.scrollHeight == document.body.clientHeight)
|
|
602
|
+
let max = {
|
|
603
|
+
width: window.innerWidth - (hasScrollBarY ? scrollSize : 0),
|
|
604
|
+
height: window.innerHeight - (hasScrollBarX ? scrollSize : 0)
|
|
605
|
+
}
|
|
606
|
+
let position = options.position == 'auto'
|
|
607
|
+
? 'top|bottom|right|left'.split('|')
|
|
608
|
+
: Array.isArray(options.position) ? options.position : options.position.split('|')
|
|
609
|
+
let isVertical = ['top', 'bottom'].includes(position[0])
|
|
610
|
+
let content = overlay.box.getBoundingClientRect()
|
|
611
|
+
let anchor = overlay.anchor?.getBoundingClientRect?.()
|
|
612
|
+
|
|
613
|
+
if (overlay.anchor == document.body) {
|
|
614
|
+
// context menu
|
|
615
|
+
let evt = options.originalEvent
|
|
616
|
+
while (evt?.originalEvent) { evt = evt.originalEvent }
|
|
617
|
+
let { x, y, width, height } = evt ?? { x: options.x, y: options.y, width: 0, height: 10 }
|
|
618
|
+
anchor = {
|
|
619
|
+
left: x - (options.contextMenu ? 4: 0),
|
|
620
|
+
top: y - (options.contextMenu ? 4: 0),
|
|
621
|
+
width: width ?? 0,
|
|
622
|
+
height: height ?? 10,
|
|
623
|
+
arrow: options.contextMenu ? 'none' : null
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
let arrowSize = options.arrowSize
|
|
627
|
+
if (anchor.arrow == 'none') arrowSize = 0
|
|
628
|
+
if (isNaN(arrowSize)) arrowSize = this.defaults.arrowSize
|
|
629
|
+
|
|
630
|
+
// space available
|
|
631
|
+
let available = { // tipsize adjustment should be here, not in max.width/max.height
|
|
632
|
+
top: anchor.top - arrowSize,
|
|
633
|
+
bottom: max.height - arrowSize - (anchor.top + anchor.height) - (hasScrollBarX ? scrollSize : 0) - 2,
|
|
634
|
+
left: anchor.left,
|
|
635
|
+
right: max.width - (anchor.left + anchor.width) + (hasScrollBarY ? scrollSize : 0),
|
|
636
|
+
}
|
|
637
|
+
// size of empty tooltip
|
|
638
|
+
if (content.width < 22) content.width = 22
|
|
639
|
+
if (content.height < 14) content.height = 14
|
|
640
|
+
let left, top, width, height // tooltip position
|
|
641
|
+
let found = ''
|
|
642
|
+
let arrow = {
|
|
643
|
+
offset: 0,
|
|
644
|
+
class: '',
|
|
645
|
+
style: `#${overlay.id} { --tip-size: ${arrowSize}px; }`
|
|
646
|
+
}
|
|
647
|
+
let adjust = { left: 0, top: 0 }
|
|
648
|
+
let bestFit = { posX: '', x: 0, posY: '', y: 0 }
|
|
649
|
+
|
|
650
|
+
// find best position
|
|
651
|
+
position.forEach(pos => {
|
|
652
|
+
if (['top', 'bottom'].includes(pos)) {
|
|
653
|
+
if (!found && (content.height + arrowSize/1.893) < available[pos]) { // 1.893 = 1 + sin(90)
|
|
654
|
+
found = pos
|
|
655
|
+
}
|
|
656
|
+
if (available[pos] > bestFit.y) {
|
|
657
|
+
Object.assign(bestFit, { posY: pos, y: available[pos] })
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (['left', 'right'].includes(pos)) {
|
|
661
|
+
if (!found && (content.width + arrowSize/1.893) < available[pos]) { // 1.893 = 1 + sin(90)
|
|
662
|
+
found = pos
|
|
663
|
+
}
|
|
664
|
+
if (available[pos] > bestFit.x) {
|
|
665
|
+
Object.assign(bestFit, { posX: pos, x: available[pos] })
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
})
|
|
669
|
+
// if not found, use best (greatest available space) position
|
|
670
|
+
if (!found) {
|
|
671
|
+
if (isVertical) {
|
|
672
|
+
found = bestFit.posY
|
|
673
|
+
} else {
|
|
674
|
+
found = bestFit.posX
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (options.autoResize) {
|
|
678
|
+
if (['top', 'bottom'].includes(found)) {
|
|
679
|
+
if (content.height > available[found]) {
|
|
680
|
+
height = available[found]
|
|
681
|
+
overlay.tmp.resizedY = true
|
|
682
|
+
} else {
|
|
683
|
+
overlay.tmp.resizedY = false
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (['left', 'right'].includes(found)) {
|
|
687
|
+
if (content.width > available[found]) {
|
|
688
|
+
width = available[found]
|
|
689
|
+
overlay.tmp.resizedX = true
|
|
690
|
+
} else {
|
|
691
|
+
overlay.tmp.resizedX = false
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
usePosition(found)
|
|
696
|
+
if (isVertical) anchorAlignment()
|
|
697
|
+
|
|
698
|
+
// user offset
|
|
699
|
+
top += parseFloat(options.offsetY * (found == 'top' ? -1 : 1)) // offset will always move from original position
|
|
700
|
+
left += parseFloat(options.offsetX * (found == 'left' ? -1 : 1)) // offset will always move from original position
|
|
701
|
+
|
|
702
|
+
// make sure it is inside visible screen area
|
|
703
|
+
screenAdjust()
|
|
704
|
+
|
|
705
|
+
// adjust for scrollbar
|
|
706
|
+
let extraTop = (found == 'top' ? -options.margin : (found == 'bottom' ? options.margin : 0))
|
|
707
|
+
let extraLeft = (found == 'left' ? -options.margin : (found == 'right' ? options.margin : 0))
|
|
708
|
+
top = Math.floor((top + parseFloat(extraTop)) * 100) / 100
|
|
709
|
+
left = Math.floor((left + parseFloat(extraLeft)) * 100) / 100
|
|
710
|
+
|
|
711
|
+
return { left, top, arrow, adjust, width, height, pos: found }
|
|
712
|
+
|
|
713
|
+
function usePosition(pos) {
|
|
714
|
+
arrow.class = anchor.arrow ? anchor.arrow : `w2ui-arrow-${pos}`
|
|
715
|
+
switch (pos) {
|
|
716
|
+
case 'top': {
|
|
717
|
+
left = anchor.left + (anchor.width - (width ?? content.width)) / 2
|
|
718
|
+
top = anchor.top - (height ?? content.height) - arrowSize / 1.5 + 1
|
|
719
|
+
break
|
|
720
|
+
}
|
|
721
|
+
case 'bottom': {
|
|
722
|
+
left = anchor.left + (anchor.width - (width ?? content.width)) / 2
|
|
723
|
+
top = anchor.top + anchor.height + arrowSize / 1.25 + 1
|
|
724
|
+
break
|
|
725
|
+
}
|
|
726
|
+
case 'left': {
|
|
727
|
+
left = anchor.left - (width ?? content.width) - arrowSize / 1.2 - 1
|
|
728
|
+
top = anchor.top + (anchor.height - (height ?? content.height)) / 2
|
|
729
|
+
break
|
|
730
|
+
}
|
|
731
|
+
case 'right': {
|
|
732
|
+
left = anchor.left + anchor.width + arrowSize / 1.2 + 1
|
|
733
|
+
top = anchor.top + (anchor.height - (height ?? content.height)) / 2
|
|
734
|
+
break
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function anchorAlignment() {
|
|
740
|
+
// top/bottom alignments
|
|
741
|
+
if (options.align == 'left') {
|
|
742
|
+
adjust.left = anchor.left - left
|
|
743
|
+
left = anchor.left
|
|
744
|
+
}
|
|
745
|
+
if (options.align == 'right') {
|
|
746
|
+
adjust.left = (anchor.left + anchor.width - (width ?? content.width)) - left
|
|
747
|
+
left = anchor.left + anchor.width - (width ?? content.width)
|
|
748
|
+
}
|
|
749
|
+
if (['top', 'bottom'].includes(found) && options.align.startsWith('both')) {
|
|
750
|
+
let minWidth = options.align.split(':')[1] ?? 50
|
|
751
|
+
if (anchor.width >= minWidth) {
|
|
752
|
+
left = anchor.left
|
|
753
|
+
width = anchor.width
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
// left/right alignments
|
|
757
|
+
if (options.align == 'top') {
|
|
758
|
+
adjust.top = anchor.top - top
|
|
759
|
+
top = anchor.top
|
|
760
|
+
}
|
|
761
|
+
if (options.align == 'bottom') {
|
|
762
|
+
adjust.top = (anchor.top + anchor.height - (height ?? content.height)) - top
|
|
763
|
+
top = anchor.top + anchor.height - (height ?? content.height)
|
|
764
|
+
}
|
|
765
|
+
if (['left', 'right'].includes(found) && options.align.startsWith('both')) {
|
|
766
|
+
let minHeight = options.align.split(':')[1] ?? 50
|
|
767
|
+
if (anchor.height >= minHeight) {
|
|
768
|
+
top = anchor.top
|
|
769
|
+
height = anchor.height
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function screenAdjust() {
|
|
775
|
+
let adjustArrow
|
|
776
|
+
// adjust tip if needed after alignment
|
|
777
|
+
if ((['left', 'right'].includes(options.align) && anchor.width < (width ?? content.width))
|
|
778
|
+
|| (['top', 'bottom'].includes(options.align) && anchor.height < (height ?? content.height))
|
|
779
|
+
) {
|
|
780
|
+
adjustArrow = true
|
|
781
|
+
}
|
|
782
|
+
// if off screen then adjust
|
|
783
|
+
let minLeft = (found == 'right' ? arrowSize : options.screenMargin)
|
|
784
|
+
let minTop = (found == 'bottom' ? arrowSize : options.screenMargin)
|
|
785
|
+
let maxLeft = max.width - (width ?? content.width) - (found == 'left' ? arrowSize : options.screenMargin)
|
|
786
|
+
let maxTop = max.height - (height ?? content.height) - (found == 'top' ? arrowSize : options.screenMargin) + 3
|
|
787
|
+
// adjust X
|
|
788
|
+
if (['top', 'bottom'].includes(found) || options.autoResize) {
|
|
789
|
+
if (left < minLeft) {
|
|
790
|
+
adjustArrow = true
|
|
791
|
+
adjust.left -= left
|
|
792
|
+
left = minLeft
|
|
793
|
+
}
|
|
794
|
+
if (left > maxLeft) {
|
|
795
|
+
adjustArrow = true
|
|
796
|
+
adjust.left -= left - maxLeft
|
|
797
|
+
left += maxLeft - left
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
// adjust Y
|
|
801
|
+
if (['left', 'right'].includes(found) || options.autoResize) {
|
|
802
|
+
if (top < minTop) {
|
|
803
|
+
adjustArrow = true
|
|
804
|
+
adjust.top -= top
|
|
805
|
+
top = minTop
|
|
806
|
+
}
|
|
807
|
+
if (top > maxTop) {
|
|
808
|
+
adjustArrow = true
|
|
809
|
+
adjust.top -= top - maxTop
|
|
810
|
+
top += maxTop - top
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
// moves carret to adjust it with element width
|
|
814
|
+
if (adjustArrow) {
|
|
815
|
+
let aType = isVertical ? 'left' : 'top'
|
|
816
|
+
let sType = isVertical ? 'width' : 'height'
|
|
817
|
+
arrow.offset = -adjust[aType]
|
|
818
|
+
let maxOffset = content[sType] / 2 - arrowSize
|
|
819
|
+
if (Math.abs(arrow.offset) > maxOffset + arrowSize) {
|
|
820
|
+
arrow.class = '' // no arrow
|
|
821
|
+
}
|
|
822
|
+
if (Math.abs(arrow.offset) > maxOffset) {
|
|
823
|
+
arrow.offset = arrow.offset < 0 ? -maxOffset : maxOffset
|
|
824
|
+
}
|
|
825
|
+
arrow.style = w2utils.stripSpaces(`#${overlay.id} .w2ui-overlay-body:after,
|
|
826
|
+
#${overlay.id} .w2ui-overlay-body:before {
|
|
827
|
+
--tip-size: ${arrowSize}px;
|
|
828
|
+
margin-${aType}: ${arrow.offset}px;
|
|
829
|
+
}`)
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
startDrag(event) {
|
|
835
|
+
let initial
|
|
836
|
+
let el = query(event.target).closest('.w2ui-overlay')
|
|
837
|
+
initial = {
|
|
838
|
+
el,
|
|
839
|
+
x: parseFloat(el.css('left')),
|
|
840
|
+
y: parseFloat(el.css('top')),
|
|
841
|
+
pageX: event.pageX,
|
|
842
|
+
pageY: event.pageY,
|
|
843
|
+
}
|
|
844
|
+
query('html')
|
|
845
|
+
.off('.w2color')
|
|
846
|
+
.on('mousemove.w2color', mouseMove)
|
|
847
|
+
.on('mouseup.w2color', mouseUp)
|
|
848
|
+
|
|
849
|
+
function mouseUp(event) {
|
|
850
|
+
query('html').off('.w2color')
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function mouseMove(event) {
|
|
854
|
+
let divX = event.pageX - initial.pageX
|
|
855
|
+
let divY = event.pageY - initial.pageY
|
|
856
|
+
initial.el.css({
|
|
857
|
+
left: initial.x + divX + 'px',
|
|
858
|
+
top: initial.y + divY + 'px'
|
|
859
|
+
})
|
|
860
|
+
if (!initial._removed) {
|
|
861
|
+
initial._removed = true
|
|
862
|
+
initial.el.find(':scope > .w2ui-overlay-body')
|
|
863
|
+
.removeClass('w2ui-arrow-right w2ui-arrow-left w2ui-arrow-top w2ui-arrow-bottom')
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
class ColorTooltip extends Tooltip {
|
|
870
|
+
static custom_colors = []
|
|
871
|
+
constructor() {
|
|
872
|
+
super()
|
|
873
|
+
this.palette = [
|
|
874
|
+
['000000', '333333', '555555', '777777', '888888', '999999', 'AAAAAA', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'F7F7F7', 'FFFFFF'],
|
|
875
|
+
['FF011B', 'FF9838', 'FFC300', 'FFFD59', '86FF14', '14FF7A', '2EFFFC', '2693FF', '006CE7', '9B24F4', 'FF21F5', 'FF0099'],
|
|
876
|
+
['FFEAEA', 'FCEFE1', 'FCF4DC', 'FFFECF', 'EBFFD9', 'D9FFE9', 'E0FFFF', 'E8F4FF', 'ECF4FC', 'EAE6F4', 'FFF5FE', 'FCF0F7'],
|
|
877
|
+
['F4CCCC', 'FCE5CD', 'FFF1C2', 'FFFDA1', 'D5FCB1', 'B5F7D0', 'BFFFFF', 'D6ECFF', 'CFE2F3', 'D9D1E9', 'FFE3FD', 'FFD9F0'],
|
|
878
|
+
['EA9899', 'F9CB9C', 'FFE48C', 'F7F56F', 'B9F77E', '84F0B1', '83F7F7', 'B5DAFF', '9FC5E8', 'B4A7D6', 'FAB9F6', 'FFADDE'],
|
|
879
|
+
['E06666', 'F6B26B', 'DEB737', 'E0DE51', '8FDB48', '52D189', '4EDEDB', '76ACE3', '6FA8DC', '8E7CC3', 'E07EDA', 'F26DBD'],
|
|
880
|
+
['CC0814', 'E69138', 'AB8816', 'B5B20E', '6BAB30', '27A85F', '1BA8A6', '3C81C7', '3D85C6', '674EA7', 'A14F9D', 'BF4990'],
|
|
881
|
+
['99050C', 'B45F17', '80650E', '737103', '395E14', '10783D', '13615E', '094785', '0A5394', '351C75', '780172', '782C5A']
|
|
882
|
+
]
|
|
883
|
+
this.defaults = w2utils.extend({}, this.defaults, {
|
|
884
|
+
advanced : false,
|
|
885
|
+
transparent : true,
|
|
886
|
+
position : 'top|bottom',
|
|
887
|
+
class : 'w2ui-white',
|
|
888
|
+
color : '',
|
|
889
|
+
updateInput : true,
|
|
890
|
+
arrowSize : 12,
|
|
891
|
+
autoResize : false,
|
|
892
|
+
anchorClass : 'w2ui-focus',
|
|
893
|
+
autoShowOn : 'focus',
|
|
894
|
+
hideOn : ['doc-click', 'focus-change'],
|
|
895
|
+
onSelect : null,
|
|
896
|
+
onLiveUpdate: null
|
|
897
|
+
})
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
attach(anchor, text) {
|
|
901
|
+
let options
|
|
902
|
+
if (arguments.length == 1 && anchor instanceof Object) {
|
|
903
|
+
options = anchor
|
|
904
|
+
anchor = options.anchor
|
|
905
|
+
} else if (arguments.length === 2 && text != null && typeof text === 'object') {
|
|
906
|
+
options = text
|
|
907
|
+
options.anchor = anchor
|
|
908
|
+
}
|
|
909
|
+
let prevHideOn = options.hideOn
|
|
910
|
+
options = w2utils.extend({}, this.defaults, options || {})
|
|
911
|
+
if (prevHideOn) {
|
|
912
|
+
options.hideOn = prevHideOn
|
|
913
|
+
}
|
|
914
|
+
options.style += '; padding: 0;'
|
|
915
|
+
// add remove transparent color
|
|
916
|
+
if (options.transparent && this.palette[0][1] == '333333') {
|
|
917
|
+
this.palette[0].splice(1, 1)
|
|
918
|
+
this.palette[0].push('TRANSPARENT')
|
|
919
|
+
}
|
|
920
|
+
if (!options.transparent && this.palette[0][1] != '333333') {
|
|
921
|
+
this.palette[0].splice(1, 0, '333333')
|
|
922
|
+
this.palette[0].pop()
|
|
923
|
+
}
|
|
924
|
+
if (options.color) options.color = String(options.color).toUpperCase()
|
|
925
|
+
if (typeof options.color === 'string' && options.color.substr(0,1) === '#') options.color = options.color.substr(1)
|
|
926
|
+
// needed for keyboard navigation
|
|
927
|
+
this.index = [-1, -1]
|
|
928
|
+
let ret = super.attach(options)
|
|
929
|
+
let overlay = ret.overlay
|
|
930
|
+
overlay.options.html = this.getColorHTML(overlay.name, options)
|
|
931
|
+
overlay.on('show.attach', event => {
|
|
932
|
+
let overlay = event.detail.overlay
|
|
933
|
+
let anchor = overlay.anchor
|
|
934
|
+
let options = overlay.options
|
|
935
|
+
if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && !options.color && anchor.value) {
|
|
936
|
+
overlay.tmp.initColor = anchor.value
|
|
937
|
+
}
|
|
938
|
+
delete overlay.newColor
|
|
939
|
+
})
|
|
940
|
+
overlay.on('show:after.attach', event => {
|
|
941
|
+
if (ret.overlay?.box) {
|
|
942
|
+
let actions = query(ret.overlay.box).find('.w2ui-eaction')
|
|
943
|
+
w2utils.bindEvents(actions, this)
|
|
944
|
+
this.initControls(ret.overlay)
|
|
945
|
+
}
|
|
946
|
+
})
|
|
947
|
+
overlay.on('update:after.attach', event => {
|
|
948
|
+
if (ret.overlay?.box) {
|
|
949
|
+
let actions = query(ret.overlay.box).find('.w2ui-eaction')
|
|
950
|
+
w2utils.bindEvents(actions, this)
|
|
951
|
+
this.initControls(ret.overlay)
|
|
952
|
+
}
|
|
953
|
+
})
|
|
954
|
+
overlay.on('hide.attach', event => {
|
|
955
|
+
let overlay = event.detail.overlay
|
|
956
|
+
let anchor = overlay.anchor
|
|
957
|
+
let color = overlay.newColor ?? overlay.options.color ?? ''
|
|
958
|
+
// color has been selected
|
|
959
|
+
if (color !== '') {
|
|
960
|
+
if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && anchor.value != color && overlay.options.updateInput) {
|
|
961
|
+
anchor.value = color
|
|
962
|
+
}
|
|
963
|
+
let edata = this.trigger('select', { color, target: overlay.name, overlay })
|
|
964
|
+
if (edata.isCancelled === true) return
|
|
965
|
+
// event after
|
|
966
|
+
edata.finish()
|
|
967
|
+
}
|
|
968
|
+
})
|
|
969
|
+
ret.liveUpdate = (callback) => {
|
|
970
|
+
overlay.on('liveUpdate.attach', (event) => { callback(event) })
|
|
971
|
+
return ret
|
|
972
|
+
}
|
|
973
|
+
ret.select = (callback) => {
|
|
974
|
+
overlay.on('select.attach', (event) => { callback(event) })
|
|
975
|
+
return ret
|
|
976
|
+
}
|
|
977
|
+
return ret
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// regular panel handler, adds selection class
|
|
981
|
+
select(color, name) {
|
|
982
|
+
let target
|
|
983
|
+
this.index = [-1, -1]
|
|
984
|
+
if (typeof name != 'string') {
|
|
985
|
+
target = name.target
|
|
986
|
+
this.index = query(target).attr('index').split(':')
|
|
987
|
+
name = query(target).closest('.w2ui-overlay').attr('name')
|
|
988
|
+
}
|
|
989
|
+
let overlay = this.get(name)
|
|
990
|
+
// event before
|
|
991
|
+
let edata = this.trigger('liveUpdate', { color, target: name, overlay, param: arguments[1] })
|
|
992
|
+
if (edata.isCancelled === true) return
|
|
993
|
+
// if anchor is input - live update
|
|
994
|
+
if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName) && overlay.options.updateInput) {
|
|
995
|
+
query(overlay.anchor).val(color)
|
|
996
|
+
}
|
|
997
|
+
overlay.newColor = color
|
|
998
|
+
query(overlay.box).find('.w2ui-color.w2ui-selected').removeClass('w2ui-selected')
|
|
999
|
+
if (target) {
|
|
1000
|
+
query(target).addClass('w2ui-selected')
|
|
1001
|
+
}
|
|
1002
|
+
// event after
|
|
1003
|
+
edata.finish()
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// used for keyboard navigation, if any
|
|
1007
|
+
nextColor(direction) { // TODO: check it
|
|
1008
|
+
let pal = this.palette
|
|
1009
|
+
switch (direction) {
|
|
1010
|
+
case 'up':
|
|
1011
|
+
this.index[0]--
|
|
1012
|
+
break
|
|
1013
|
+
case 'down':
|
|
1014
|
+
this.index[0]++
|
|
1015
|
+
break
|
|
1016
|
+
case 'right':
|
|
1017
|
+
this.index[1]++
|
|
1018
|
+
break
|
|
1019
|
+
case 'left':
|
|
1020
|
+
this.index[1]--
|
|
1021
|
+
break
|
|
1022
|
+
}
|
|
1023
|
+
if (this.index[0] < 0) this.index[0] = 0
|
|
1024
|
+
if (this.index[0] > pal.length - 2) this.index[0] = pal.length - 2
|
|
1025
|
+
if (this.index[1] < 0) this.index[1] = 0
|
|
1026
|
+
if (this.index[1] > pal[0].length - 1) this.index[1] = pal[0].length - 1
|
|
1027
|
+
return pal[this.index[0]][this.index[1]]
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
tabClick(index, name) {
|
|
1031
|
+
if (typeof name != 'string') {
|
|
1032
|
+
name = query(name.target).closest('.w2ui-overlay').attr('name')
|
|
1033
|
+
}
|
|
1034
|
+
let overlay = this.get(name)
|
|
1035
|
+
let tab = query(overlay.box).find(`.w2ui-color-tab:nth-child(${index})`)
|
|
1036
|
+
query(overlay.box).find('.w2ui-color-tab').removeClass('w2ui-selected')
|
|
1037
|
+
query(tab).addClass('w2ui-selected')
|
|
1038
|
+
query(overlay.box)
|
|
1039
|
+
.find('.w2ui-tab-content')
|
|
1040
|
+
.hide()
|
|
1041
|
+
.closest('.w2ui-colors')
|
|
1042
|
+
.find('.tab-'+ index)
|
|
1043
|
+
.show()
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// generate HTML with color pallent and controls
|
|
1047
|
+
getColorHTML(name, options) {
|
|
1048
|
+
let html = `
|
|
1049
|
+
<div class="w2ui-colors-header w2ui-eaction" data-mousedown="startDrag|event">
|
|
1050
|
+
Colors
|
|
1051
|
+
</div>
|
|
1052
|
+
<div class="w2ui-colors">
|
|
1053
|
+
<div class="w2ui-tab-content tab-1">`
|
|
1054
|
+
for (let i = 0; i < this.palette.length; i++) {
|
|
1055
|
+
html += '<div class="w2ui-color-row">'
|
|
1056
|
+
for (let j = 0; j < this.palette[i].length; j++) {
|
|
1057
|
+
let color = this.palette[i][j]
|
|
1058
|
+
let border = ''
|
|
1059
|
+
if (color === 'FFFFFF') border = '; border: 1px solid #efefef'
|
|
1060
|
+
html += `
|
|
1061
|
+
<div class="w2ui-color w2ui-eaction ${color === 'TRANSPARENT' ? 'w2ui-no-color' : ''} ${options.color == color ? 'w2ui-selected' : ''}"
|
|
1062
|
+
style="background-color: #${color + border};" name="${color}" index="${i}:${j}"
|
|
1063
|
+
data-mousedown="select|'${color}'|event" data-mouseup="hide|${name}">
|
|
1064
|
+
</div>`
|
|
1065
|
+
}
|
|
1066
|
+
html += '</div>'
|
|
1067
|
+
if (i < 2) html += '<div style="height: 8px"></div>'
|
|
1068
|
+
}
|
|
1069
|
+
// custom colors
|
|
1070
|
+
html += `
|
|
1071
|
+
<div style="height: 8px"></div>
|
|
1072
|
+
<div class="w2ui-colors-custom">
|
|
1073
|
+
${this.getCustomColorsHTML(name)}
|
|
1074
|
+
</div>`
|
|
1075
|
+
html += '</div>'
|
|
1076
|
+
// advanced tab
|
|
1077
|
+
html += `
|
|
1078
|
+
<div class="w2ui-tab-content tab-2" style="display: none">
|
|
1079
|
+
<div class="color-info">
|
|
1080
|
+
<div class="color-preview-bg"><div class="color-preview"></div><div class="color-original"></div></div>
|
|
1081
|
+
<div class="color-part">
|
|
1082
|
+
<span>H</span> <input class="w2ui-input" name="h" maxlength="3" max="360" tabindex="101">
|
|
1083
|
+
<span>R</span> <input class="w2ui-input" name="r" maxlength="3" max="255" tabindex="104">
|
|
1084
|
+
</div>
|
|
1085
|
+
<div class="color-part">
|
|
1086
|
+
<span>S</span> <input class="w2ui-input" name="s" maxlength="3" max="100" tabindex="102">
|
|
1087
|
+
<span>G</span> <input class="w2ui-input" name="g" maxlength="3" max="255" tabindex="105">
|
|
1088
|
+
</div>
|
|
1089
|
+
<div class="color-part">
|
|
1090
|
+
<span>V</span> <input class="w2ui-input" name="v" maxlength="3" max="100" tabindex="103">
|
|
1091
|
+
<span>B</span> <input class="w2ui-input" name="b" maxlength="3" max="255" tabindex="106">
|
|
1092
|
+
</div>
|
|
1093
|
+
<div class="color-part opacity">
|
|
1094
|
+
<span>${w2utils.lang('Opacity')}</span>
|
|
1095
|
+
<input class="w2ui-input" name="a" maxlength="5" max="1" tabindex="107">
|
|
1096
|
+
</div>
|
|
1097
|
+
</div>
|
|
1098
|
+
<div class="palette" name="palette">
|
|
1099
|
+
<div class="palette-bg"></div>
|
|
1100
|
+
<div class="value1 move-x move-y"></div>
|
|
1101
|
+
</div>
|
|
1102
|
+
<div class="rainbow" name="rainbow">
|
|
1103
|
+
<div class="value2 move-x"></div>
|
|
1104
|
+
</div>
|
|
1105
|
+
<div class="alpha" name="alpha">
|
|
1106
|
+
<div class="alpha-bg"></div>
|
|
1107
|
+
<div class="value2 move-x"></div>
|
|
1108
|
+
</div>
|
|
1109
|
+
<div class="final" name="final">
|
|
1110
|
+
<span style="text-align: right"> Hex </span>
|
|
1111
|
+
<input class="w2ui-input final" name="hex" tabindex="107" style="width: 70px" readonly>
|
|
1112
|
+
<div class="w2ui-color w2ui-color-picker w2ui-eaction" data-click="pickAndUse|${name}">
|
|
1113
|
+
<span class="w2ui-icon w2ui-icon-eye-dropper"></span>
|
|
1114
|
+
</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
</div>`
|
|
1117
|
+
// color tabs on the bottom
|
|
1118
|
+
html += `
|
|
1119
|
+
<div class="w2ui-color-tabs">
|
|
1120
|
+
<div class="w2ui-color-tab w2ui-selected w2ui-eaction" data-click="tabClick|1|event|this"><span class="w2ui-icon w2ui-icon-colors"></span></div>
|
|
1121
|
+
<div class="w2ui-color-tab w2ui-eaction" data-click="tabClick|2|event|this"><span class="w2ui-icon w2ui-icon-settings"></span></div>
|
|
1122
|
+
<div style="padding: 5px; width: 100%; text-align: right;">
|
|
1123
|
+
${(typeof options.html == 'string' ? options.html : '')}
|
|
1124
|
+
</div>
|
|
1125
|
+
</div>`
|
|
1126
|
+
return html
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
getCustomColorsHTML(name) {
|
|
1130
|
+
let options = this.get(name)?.options
|
|
1131
|
+
let html = '<div class="w2ui-color-row" style="min-height: 21px">'
|
|
1132
|
+
ColorTooltip.custom_colors.forEach((color, i) => {
|
|
1133
|
+
let border = ''
|
|
1134
|
+
if (color === 'FFFFFF') border = '; border: 1px solid #efefef'
|
|
1135
|
+
html += `
|
|
1136
|
+
<div class="w2ui-color w2ui-eaction ${color === 'TRANSPARENT' ? 'w2ui-no-color' : ''} ${options.color == color ? 'w2ui-selected' : ''}"
|
|
1137
|
+
style="background-color: #${color + border};" name="${color}" index="c:${i}"
|
|
1138
|
+
data-mousedown="select|'${color}'|event" data-mouseup="hide|${name}">
|
|
1139
|
+
</div>`
|
|
1140
|
+
|
|
1141
|
+
})
|
|
1142
|
+
html += `
|
|
1143
|
+
<div class="w2ui-color w2ui-color-picker w2ui-eaction" data-click="pickAndSelect|${name}|event">
|
|
1144
|
+
<span class="w2ui-icon w2ui-icon-eye-dropper"></span>
|
|
1145
|
+
</div>
|
|
1146
|
+
</div>`
|
|
1147
|
+
return html
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// bind advanced tab controls
|
|
1151
|
+
initControls(overlay) {
|
|
1152
|
+
let initial // used for mouse events
|
|
1153
|
+
let self = this
|
|
1154
|
+
let options = overlay.options
|
|
1155
|
+
let color = options.color || overlay.tmp.initColor
|
|
1156
|
+
let rgb = w2utils.parseColor(color)
|
|
1157
|
+
if (rgb == null) {
|
|
1158
|
+
rgb = { r: 140, g: 150, b: 160, a: 1 }
|
|
1159
|
+
}
|
|
1160
|
+
let hsv = w2utils.rgb2hsv(rgb)
|
|
1161
|
+
if (options.advanced === true) {
|
|
1162
|
+
this.tabClick(2, overlay.name)
|
|
1163
|
+
}
|
|
1164
|
+
setColor(hsv, true, color ?? '') // should not be null or undefined
|
|
1165
|
+
|
|
1166
|
+
// even for rgb, hsv inputs
|
|
1167
|
+
query(overlay.box)
|
|
1168
|
+
.off('.w2color')
|
|
1169
|
+
.on('contextmenu.w2color', event => {
|
|
1170
|
+
event.preventDefault() // prevent browser context menu
|
|
1171
|
+
})
|
|
1172
|
+
.find('input')
|
|
1173
|
+
.off('.w2color')
|
|
1174
|
+
.on('change.w2color', (event) => {
|
|
1175
|
+
let el = query(event.target)
|
|
1176
|
+
let val = parseFloat(el.val())
|
|
1177
|
+
let max = parseFloat(el.attr('max'))
|
|
1178
|
+
if (isNaN(val)) {
|
|
1179
|
+
val = 0
|
|
1180
|
+
el.val(0)
|
|
1181
|
+
}
|
|
1182
|
+
if (max > 1) val = parseInt(val) // trancate fractions
|
|
1183
|
+
if (max > 0 && val > max) {
|
|
1184
|
+
el.val(max)
|
|
1185
|
+
val = max
|
|
1186
|
+
}
|
|
1187
|
+
if (val < 0) {
|
|
1188
|
+
el.val(0)
|
|
1189
|
+
val = 0
|
|
1190
|
+
}
|
|
1191
|
+
let name = el.attr('name')
|
|
1192
|
+
let color = {}
|
|
1193
|
+
if (['r', 'g', 'b', 'a'].indexOf(name) !== -1) {
|
|
1194
|
+
rgb[name] = val
|
|
1195
|
+
hsv = w2utils.rgb2hsv(rgb)
|
|
1196
|
+
} else if (['h', 's', 'v'].indexOf(name) !== -1) {
|
|
1197
|
+
color[name] = val
|
|
1198
|
+
}
|
|
1199
|
+
setColor(color, true)
|
|
1200
|
+
})
|
|
1201
|
+
|
|
1202
|
+
// click on original color resets it
|
|
1203
|
+
query(overlay.box).find('.color-original')
|
|
1204
|
+
.off('.w2color')
|
|
1205
|
+
.on('click.w2color', (event) => {
|
|
1206
|
+
let tmp = w2utils.parseColor(query(event.target).css('background-color'))
|
|
1207
|
+
if (tmp != null) {
|
|
1208
|
+
rgb = tmp
|
|
1209
|
+
hsv = w2utils.rgb2hsv(rgb)
|
|
1210
|
+
setColor(hsv, true)
|
|
1211
|
+
}
|
|
1212
|
+
})
|
|
1213
|
+
|
|
1214
|
+
// color sliders events
|
|
1215
|
+
let mDown = `${!w2utils.isMobile ? 'mousedown' : 'touchstart'}.w2color`
|
|
1216
|
+
let mUp = `${!w2utils.isMobile ? 'mouseup' : 'touchend'}.w2color`
|
|
1217
|
+
let mMove = `${!w2utils.isMobile ? 'mousemove' : 'touchmove'}.w2color`
|
|
1218
|
+
query(overlay.box).find('.palette, .rainbow, .alpha')
|
|
1219
|
+
.off('.w2color')
|
|
1220
|
+
.on(`${mDown}.w2color`, mouseDown)
|
|
1221
|
+
|
|
1222
|
+
this.setColor = setColor
|
|
1223
|
+
return
|
|
1224
|
+
|
|
1225
|
+
function setColor(color, fullUpdate, initial) {
|
|
1226
|
+
if (color.h != null) hsv.h = color.h
|
|
1227
|
+
if (color.s != null) hsv.s = color.s
|
|
1228
|
+
if (color.v != null) hsv.v = color.v
|
|
1229
|
+
if (color.a != null) { rgb.a = color.a; hsv.a = color.a }
|
|
1230
|
+
rgb = w2utils.hsv2rgb(hsv)
|
|
1231
|
+
let rgba = 'rgba('+ rgb.r +','+ rgb.g +','+ rgb.b +','+ rgb.a +')'
|
|
1232
|
+
let cl = [
|
|
1233
|
+
Number(rgb.r).toString(16).toUpperCase(),
|
|
1234
|
+
Number(rgb.g).toString(16).toUpperCase(),
|
|
1235
|
+
Number(rgb.b).toString(16).toUpperCase(),
|
|
1236
|
+
(Math.round(Number(rgb.a)*255)).toString(16).toUpperCase()
|
|
1237
|
+
]
|
|
1238
|
+
cl.forEach((item, ind) => { if (item.length === 1) cl[ind] = '0' + item })
|
|
1239
|
+
let newColor = cl[0] + cl[1] + cl[2] + cl[3]
|
|
1240
|
+
if (rgb.a === 1) {
|
|
1241
|
+
newColor = cl[0] + cl[1] + cl[2]
|
|
1242
|
+
}
|
|
1243
|
+
query(overlay.box).find('.color-preview').css('background-color', '#' + newColor)
|
|
1244
|
+
query(overlay.box).find('input').each(el => {
|
|
1245
|
+
if (el.name) {
|
|
1246
|
+
if (rgb[el.name] != null) el.value = rgb[el.name]
|
|
1247
|
+
if (hsv[el.name] != null) el.value = hsv[el.name]
|
|
1248
|
+
if (el.name === 'a') el.value = rgb.a
|
|
1249
|
+
if (el.name == 'hex') el.value = newColor
|
|
1250
|
+
if (el.name == 'rgb') el.value = rgba
|
|
1251
|
+
}
|
|
1252
|
+
})
|
|
1253
|
+
// if it is in pallette
|
|
1254
|
+
if (initial != null) {
|
|
1255
|
+
let color = overlay.tmp.initColor || newColor
|
|
1256
|
+
query(overlay.box).find('.color-original')
|
|
1257
|
+
.css('background-color', '#' + color)
|
|
1258
|
+
query(overlay.box).find('.w2ui-color.w2ui-selected')
|
|
1259
|
+
.removeClass('w2ui-selected')
|
|
1260
|
+
query(overlay.box).find(`.w2ui-colors [name="${color}"], .w2ui-colors [name="${initial}"]`) // color conversion might be slightly off
|
|
1261
|
+
.addClass('w2ui-selected')
|
|
1262
|
+
// if has transparent color, open advanced tab
|
|
1263
|
+
if (newColor.length == 8) {
|
|
1264
|
+
self.tabClick(2, overlay.name)
|
|
1265
|
+
}
|
|
1266
|
+
} else {
|
|
1267
|
+
self.select(newColor, overlay.name)
|
|
1268
|
+
}
|
|
1269
|
+
if (fullUpdate) {
|
|
1270
|
+
updateSliders()
|
|
1271
|
+
refreshPalette()
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function updateSliders() {
|
|
1276
|
+
let el1 = query(overlay.box).find('.palette .value1')
|
|
1277
|
+
let el2 = query(overlay.box).find('.rainbow .value2')
|
|
1278
|
+
let el3 = query(overlay.box).find('.alpha .value2')
|
|
1279
|
+
if (!el1[0] || !el2[0] || !el3[0]) return
|
|
1280
|
+
let offset1 = parseInt(el1[0].clientWidth) / 2
|
|
1281
|
+
let offset2 = parseInt(el2[0].clientWidth) / 2
|
|
1282
|
+
el1.css({
|
|
1283
|
+
'left': (hsv.s * 150 / 100 - offset1) + 'px',
|
|
1284
|
+
'top': ((100 - hsv.v) * 125 / 100 - offset1) + 'px'
|
|
1285
|
+
})
|
|
1286
|
+
el2.css('left', (hsv.h/(360/150) - offset2) + 'px')
|
|
1287
|
+
el3.css('left', (rgb.a*150 - offset2) + 'px')
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function refreshPalette() {
|
|
1291
|
+
let cl = w2utils.hsv2rgb(hsv.h, 100, 100)
|
|
1292
|
+
let rgb = `${cl.r},${cl.g},${cl.b}`
|
|
1293
|
+
query(overlay.box).find('.palette')
|
|
1294
|
+
.css('background-image', `linear-gradient(90deg, rgba(${rgb},0) 0%, rgba(${rgb},1) 100%)`)
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function mouseDown(event) {
|
|
1298
|
+
let el = query(this).find('.value1, .value2')
|
|
1299
|
+
let offset = parseInt(el.prop('clientWidth')) / 2
|
|
1300
|
+
if (el.hasClass('move-x')) el.css({ left: (event.offsetX - offset) + 'px' })
|
|
1301
|
+
if (el.hasClass('move-y')) el.css({ top: (event.offsetY - offset) + 'px' })
|
|
1302
|
+
initial = {
|
|
1303
|
+
el: el,
|
|
1304
|
+
x: event.pageX,
|
|
1305
|
+
y: event.pageY,
|
|
1306
|
+
width: el.prop('parentNode').clientWidth,
|
|
1307
|
+
height: el.prop('parentNode').clientHeight,
|
|
1308
|
+
left: parseInt(el.css('left')),
|
|
1309
|
+
top: parseInt(el.css('top'))
|
|
1310
|
+
}
|
|
1311
|
+
mouseMove(event)
|
|
1312
|
+
query('html')
|
|
1313
|
+
.off('.w2color')
|
|
1314
|
+
.on(mMove, mouseMove)
|
|
1315
|
+
.on(mUp, mouseUp)
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
function mouseUp(event) {
|
|
1319
|
+
query('html').off('.w2color')
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function mouseMove(event) {
|
|
1323
|
+
let el = initial.el
|
|
1324
|
+
let divX = event.pageX - initial.x
|
|
1325
|
+
let divY = event.pageY - initial.y
|
|
1326
|
+
let newX = initial.left + divX
|
|
1327
|
+
let newY = initial.top + divY
|
|
1328
|
+
let offset = parseInt(el.prop('clientWidth')) / 2
|
|
1329
|
+
if (newX < -offset) newX = -offset
|
|
1330
|
+
if (newY < -offset) newY = -offset
|
|
1331
|
+
if (newX > initial.width - offset) newX = initial.width - offset
|
|
1332
|
+
if (newY > initial.height - offset) newY = initial.height - offset
|
|
1333
|
+
if (el.hasClass('move-x')) el.css({ left : newX + 'px' })
|
|
1334
|
+
if (el.hasClass('move-y')) el.css({ top : newY + 'px' })
|
|
1335
|
+
|
|
1336
|
+
// move
|
|
1337
|
+
let name = query(el.get(0).parentNode).attr('name')
|
|
1338
|
+
let x = parseInt(el.css('left')) + offset
|
|
1339
|
+
let y = parseInt(el.css('top')) + offset
|
|
1340
|
+
if (name === 'palette') {
|
|
1341
|
+
setColor({
|
|
1342
|
+
s: Math.round(x / initial.width * 100),
|
|
1343
|
+
v: Math.round(100 - (y / initial.height * 100))
|
|
1344
|
+
})
|
|
1345
|
+
}
|
|
1346
|
+
if (name === 'rainbow') {
|
|
1347
|
+
let h = Math.round(360 / 150 * x)
|
|
1348
|
+
setColor({ h: h })
|
|
1349
|
+
refreshPalette()
|
|
1350
|
+
}
|
|
1351
|
+
if (name === 'alpha') {
|
|
1352
|
+
setColor({ a: parseFloat(Number(x / 150).toFixed(2)) })
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
addCustomColor(color, name) {
|
|
1358
|
+
if (typeof color == 'string' && color.substr(0, 1) == '#' && [7, 9].includes(color.length)) {
|
|
1359
|
+
color = color.substr(1).toUpperCase()
|
|
1360
|
+
let custom = ColorTooltip.custom_colors
|
|
1361
|
+
if (custom.includes(color)) {
|
|
1362
|
+
custom.splice(custom.indexOf(color), 1)
|
|
1363
|
+
}
|
|
1364
|
+
if (custom.length >= 5) {
|
|
1365
|
+
custom.pop() // removes last one
|
|
1366
|
+
}
|
|
1367
|
+
custom.unshift(color)
|
|
1368
|
+
}
|
|
1369
|
+
return color
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
async pickAndSelect(name, event) {
|
|
1373
|
+
let color = await this.pickColor()
|
|
1374
|
+
if (typeof color == 'string' && color.substr(0, 1) == '#' && [7, 9].includes(color.length)) {
|
|
1375
|
+
this.addCustomColor(color, name)
|
|
1376
|
+
let cnt = query(event.target).closest('.w2ui-colors-custom')
|
|
1377
|
+
cnt.html(this.getCustomColorsHTML(name))
|
|
1378
|
+
w2utils.bindEvents(cnt.find('.w2ui-eaction'), this)
|
|
1379
|
+
this.select(color.substr(1), name)
|
|
1380
|
+
// this.hide(name)
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
async pickAndUse(name) {
|
|
1385
|
+
let color = await this.pickColor()
|
|
1386
|
+
if (typeof color == 'string' && color.substr(0, 1) == '#' && [7, 9].includes(color.length)) {
|
|
1387
|
+
let hsv = w2utils.rgb2hsv(w2utils.parseColor(color))
|
|
1388
|
+
this.setColor(hsv, true)
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
async pickColor() {
|
|
1393
|
+
if (!window.EyeDropper) {
|
|
1394
|
+
console.error('EyeDropper API is not supported in this browser.')
|
|
1395
|
+
return
|
|
1396
|
+
}
|
|
1397
|
+
let eyeDropper = new EyeDropper()
|
|
1398
|
+
try {
|
|
1399
|
+
const result = await eyeDropper.open()
|
|
1400
|
+
return result.sRGBHex
|
|
1401
|
+
} catch (err) {
|
|
1402
|
+
console.error('Error picking color:', err)
|
|
1403
|
+
}
|
|
1404
|
+
return ''
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
class MenuTooltip extends Tooltip {
|
|
1409
|
+
constructor() {
|
|
1410
|
+
super()
|
|
1411
|
+
// ITEM STRUCTURE
|
|
1412
|
+
// item : {
|
|
1413
|
+
// id : null,
|
|
1414
|
+
// text : '',
|
|
1415
|
+
// style : '',
|
|
1416
|
+
// icon : '',
|
|
1417
|
+
// count : '',
|
|
1418
|
+
// tooltip : '',
|
|
1419
|
+
// hotkey : '',
|
|
1420
|
+
// removable: false,
|
|
1421
|
+
// help : '', // text for help tooltip
|
|
1422
|
+
// hotkey ; '', // hotkey text for the items
|
|
1423
|
+
// items : []
|
|
1424
|
+
// indent : 0,
|
|
1425
|
+
// type : null, // check/radio
|
|
1426
|
+
// group : false, // groupping for checks
|
|
1427
|
+
// expanded : false,
|
|
1428
|
+
// hidden : false,
|
|
1429
|
+
// checked : null,
|
|
1430
|
+
// disabled : false
|
|
1431
|
+
// ...
|
|
1432
|
+
// }
|
|
1433
|
+
this.defaults = w2utils.extend({}, this.defaults, {
|
|
1434
|
+
type : 'normal', // can be normal, radio, check
|
|
1435
|
+
items : [],
|
|
1436
|
+
selected : null, // current selected
|
|
1437
|
+
render : null,
|
|
1438
|
+
spinner : false,
|
|
1439
|
+
msgNoItems : w2utils.lang('No items found'),
|
|
1440
|
+
topHTML : '',
|
|
1441
|
+
menuStyle : '',
|
|
1442
|
+
search : false, // search input inside tooltip
|
|
1443
|
+
filter : false, // will apply filter, if anchor is INPUT or TEXTAREA
|
|
1444
|
+
match : 'contains', // is, begins, ends, contains
|
|
1445
|
+
markSearch : false,
|
|
1446
|
+
prefilter : false,
|
|
1447
|
+
altRows : false,
|
|
1448
|
+
arrowSize : 10,
|
|
1449
|
+
align : 'left',
|
|
1450
|
+
position : 'bottom|top',
|
|
1451
|
+
class : 'w2ui-white',
|
|
1452
|
+
anchorClass : 'w2ui-focus',
|
|
1453
|
+
autoShowOn : 'focus',
|
|
1454
|
+
hideOn : ['doc-click', 'focus-change', 'select'], // also can 'item-remove'
|
|
1455
|
+
onSelect : null,
|
|
1456
|
+
onSubMenu : null,
|
|
1457
|
+
onRemove : null,
|
|
1458
|
+
onTooltip : null,
|
|
1459
|
+
onMouseEnter: null,
|
|
1460
|
+
onMouseLeave: null
|
|
1461
|
+
})
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
attach(anchor, text) {
|
|
1465
|
+
let options
|
|
1466
|
+
if (arguments.length == 1 && anchor instanceof Object) {
|
|
1467
|
+
options = anchor
|
|
1468
|
+
anchor = options.anchor
|
|
1469
|
+
} else if (arguments.length === 2 && text != null && typeof text === 'object') {
|
|
1470
|
+
options = text
|
|
1471
|
+
options.anchor = anchor
|
|
1472
|
+
}
|
|
1473
|
+
let prevHideOn = options.hideOn
|
|
1474
|
+
options = w2utils.extend({}, this.defaults, options || {})
|
|
1475
|
+
if (prevHideOn) {
|
|
1476
|
+
options.hideOn = prevHideOn
|
|
1477
|
+
}
|
|
1478
|
+
options.style += '; padding: 0;'
|
|
1479
|
+
if (options.items == null) {
|
|
1480
|
+
options.items = []
|
|
1481
|
+
}
|
|
1482
|
+
if (options.cacheMax <= 0) {
|
|
1483
|
+
console.log(`The option "cacheMax" is ${options.cacheMax} but should be more than 0`)
|
|
1484
|
+
}
|
|
1485
|
+
options.items = w2utils.normMenu(options.items, options)
|
|
1486
|
+
options.html = this.getMenuHTML(options)
|
|
1487
|
+
let ret = super.attach(options)
|
|
1488
|
+
let overlay = ret.overlay
|
|
1489
|
+
overlay.on('show:after.attach, update:after.attach', event => {
|
|
1490
|
+
if (ret.overlay?.box) {
|
|
1491
|
+
let search = ''
|
|
1492
|
+
// reset selected and active chain
|
|
1493
|
+
overlay.selected = overlay.options.selected // this is needed so that menu item can be preselected
|
|
1494
|
+
let index = overlay.anchor.dataset?.selectedIndex
|
|
1495
|
+
if ((overlay.options.selected !== false && overlay.options.selected !== -1) || index != null) {
|
|
1496
|
+
if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) {
|
|
1497
|
+
search = overlay.anchor.value
|
|
1498
|
+
overlay.selected = null // no element should be pre-selected
|
|
1499
|
+
if (index != null) {
|
|
1500
|
+
overlay.selected = index
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
let actions = query(ret.overlay.box).find('.w2ui-eaction')
|
|
1505
|
+
if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) {
|
|
1506
|
+
overlay.tmp._new_search = false
|
|
1507
|
+
query(overlay.anchor).on('input.search-trigger', () => {
|
|
1508
|
+
overlay.tmp._new_search = true
|
|
1509
|
+
query(overlay.anchor).off('input.search-trigger')
|
|
1510
|
+
})
|
|
1511
|
+
}
|
|
1512
|
+
w2utils.bindEvents(actions, this)
|
|
1513
|
+
this.applyFilter(overlay.name, null, search)
|
|
1514
|
+
.then(data => {
|
|
1515
|
+
if (!Tooltip.active[overlay.name].displayed) {
|
|
1516
|
+
// if toolitp is not visible, do not proceed as it would make it visible
|
|
1517
|
+
return
|
|
1518
|
+
}
|
|
1519
|
+
this.getActiveChain(overlay.name, options.items) // need this to update chain for up/down key navigation
|
|
1520
|
+
overlay.tmp.searchCount = data.count
|
|
1521
|
+
overlay.tmp.search = data.search
|
|
1522
|
+
if (options.prefilter || search !== '') {
|
|
1523
|
+
// if selected is not in searched items
|
|
1524
|
+
if (data.count === 0 || !this.getActiveChain(overlay.name, options.items).includes(overlay.selected)) {
|
|
1525
|
+
overlay.selected = null
|
|
1526
|
+
}
|
|
1527
|
+
this.refreshSearch(overlay.name)
|
|
1528
|
+
}
|
|
1529
|
+
this.initControls(ret.overlay)
|
|
1530
|
+
this.refreshIndex(overlay.name, true)
|
|
1531
|
+
})
|
|
1532
|
+
}
|
|
1533
|
+
})
|
|
1534
|
+
overlay.next = () => {
|
|
1535
|
+
let chain = this.getActiveChain(overlay.name)
|
|
1536
|
+
if (overlay.selected == null || overlay.selected?.length == 0) {
|
|
1537
|
+
overlay.selected = chain[0]
|
|
1538
|
+
} else {
|
|
1539
|
+
let ind = chain.indexOf(String(overlay.selected)) // if nested menu, selected will be "2-2"
|
|
1540
|
+
// selected not in chain of items
|
|
1541
|
+
if (ind == -1) {
|
|
1542
|
+
overlay.selected = chain[0]
|
|
1543
|
+
}
|
|
1544
|
+
// not the last item
|
|
1545
|
+
if (ind < chain.length - 1) {
|
|
1546
|
+
overlay.selected = chain[ind + 1]
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
this.refreshIndex(overlay.name)
|
|
1550
|
+
this.showTooltip(overlay.name)
|
|
1551
|
+
}
|
|
1552
|
+
overlay.prev = () => {
|
|
1553
|
+
let chain = this.getActiveChain(overlay.name)
|
|
1554
|
+
if (overlay.selected == null || overlay.selected?.length == 0) {
|
|
1555
|
+
overlay.selected = chain[chain.length-1]
|
|
1556
|
+
} else {
|
|
1557
|
+
let ind = chain.indexOf(String(overlay.selected)) // if nested menu, selected will be "2-2"
|
|
1558
|
+
// selected not in chain of items
|
|
1559
|
+
if (ind == -1) {
|
|
1560
|
+
overlay.selected = chain[chain.length-1]
|
|
1561
|
+
}
|
|
1562
|
+
// not first item
|
|
1563
|
+
if (ind > 0) {
|
|
1564
|
+
overlay.selected = chain[ind - 1]
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
this.refreshIndex(overlay.name)
|
|
1568
|
+
this.showTooltip(overlay.name)
|
|
1569
|
+
}
|
|
1570
|
+
overlay.click = () => {
|
|
1571
|
+
$(overlay.box).find('.w2ui-selected').click()
|
|
1572
|
+
}
|
|
1573
|
+
overlay.on('hide:after.attach', event => {
|
|
1574
|
+
w2tooltip.hide(overlay.name + '-tooltip')
|
|
1575
|
+
})
|
|
1576
|
+
ret.select = (callback) => {
|
|
1577
|
+
overlay.on('select.attach', (event) => { callback(event) })
|
|
1578
|
+
return ret
|
|
1579
|
+
}
|
|
1580
|
+
ret.remove = (callback) => {
|
|
1581
|
+
overlay.on('remove.attach', (event) => { callback(event) })
|
|
1582
|
+
return ret
|
|
1583
|
+
}
|
|
1584
|
+
ret.subMenu = (callback) => {
|
|
1585
|
+
overlay.on('subMenu.attach', (event) => { callback(event) })
|
|
1586
|
+
return ret
|
|
1587
|
+
}
|
|
1588
|
+
return ret
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
update(name, items) {
|
|
1592
|
+
let overlay = Tooltip.active[name]
|
|
1593
|
+
if (overlay) {
|
|
1594
|
+
let options = overlay.options
|
|
1595
|
+
if (options.items != items) {
|
|
1596
|
+
options.items = items
|
|
1597
|
+
}
|
|
1598
|
+
let menuHTML = this.getMenuHTML(options)
|
|
1599
|
+
if (options.html != menuHTML) {
|
|
1600
|
+
options.html = menuHTML
|
|
1601
|
+
overlay.needsUpdate = true
|
|
1602
|
+
this.show(name)
|
|
1603
|
+
}
|
|
1604
|
+
} else {
|
|
1605
|
+
console.log(`Tooltip "${name}" is not displayed. Cannot update it.`)
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
initControls(overlay) {
|
|
1610
|
+
let mdown = 'mousedown'
|
|
1611
|
+
let mclick = 'click'
|
|
1612
|
+
if (w2utils.isMobile) {
|
|
1613
|
+
mdown = 'touchstart'
|
|
1614
|
+
mclick = 'touchend'
|
|
1615
|
+
}
|
|
1616
|
+
query(overlay.box).find('.w2ui-menu:not(.w2ui-sub-menu)')
|
|
1617
|
+
.off('.w2menu')
|
|
1618
|
+
.on('contextmenu.w2menu', event => {
|
|
1619
|
+
event.preventDefault() // prevent browser context menu
|
|
1620
|
+
})
|
|
1621
|
+
.on(`${mdown}.w2menu`, { delegate: '.w2ui-menu-item' }, event => {
|
|
1622
|
+
let dt = event.delegate.dataset
|
|
1623
|
+
this.menuDown(overlay, event, dt.index, dt.parents)
|
|
1624
|
+
if (w2utils.isMobile) {
|
|
1625
|
+
// need it for mobile so that it would not generate onclick (items under menu receive focus)
|
|
1626
|
+
event.preventDefault()
|
|
1627
|
+
}
|
|
1628
|
+
})
|
|
1629
|
+
.on(`${mclick}.w2menu`, { delegate: '.w2ui-menu-item' }, event => {
|
|
1630
|
+
let dt = event.delegate.dataset
|
|
1631
|
+
this.menuClick(overlay, event, parseInt(dt.index), dt.parents)
|
|
1632
|
+
})
|
|
1633
|
+
.find('.w2ui-menu-item')
|
|
1634
|
+
.off('.w2menu')
|
|
1635
|
+
.on('mouseEnter.w2menu', event => {
|
|
1636
|
+
let dt = event.target.dataset
|
|
1637
|
+
let item = overlay.options.items[dt.index]
|
|
1638
|
+
let edata = this.trigger('mouseEnter', { overlay, item, originalEvent: event })
|
|
1639
|
+
if (edata.isCancelled) {
|
|
1640
|
+
return
|
|
1641
|
+
}
|
|
1642
|
+
let tooltip = item?.tooltip
|
|
1643
|
+
if (tooltip && dt.hassubmenu != 'yes') {
|
|
1644
|
+
this.showTooltip(overlay.name, { tooltip, anchor: event.target })
|
|
1645
|
+
}
|
|
1646
|
+
// hide previous sub-menu if any
|
|
1647
|
+
let _menu = query(event.target).closest('.w2ui-menu').get(0)
|
|
1648
|
+
if (_menu._evt && _menu._evt.target != event.target) {
|
|
1649
|
+
this.closeSubMenu(_menu._evt)
|
|
1650
|
+
}
|
|
1651
|
+
// show new sub-menu
|
|
1652
|
+
if (dt.hassubmenu == 'yes') {
|
|
1653
|
+
let _evt = {
|
|
1654
|
+
index: parseInt(dt.index),
|
|
1655
|
+
parents: _menu.dataset.parents !== '' ? _menu.dataset.parents.split('-').map(ind => parseInt(ind)) : [],
|
|
1656
|
+
target: event.target,
|
|
1657
|
+
originalEvent: event,
|
|
1658
|
+
overlay
|
|
1659
|
+
}
|
|
1660
|
+
_menu._evt = _evt
|
|
1661
|
+
this.openSubMenu(_evt)
|
|
1662
|
+
}
|
|
1663
|
+
edata.finish()
|
|
1664
|
+
})
|
|
1665
|
+
.on('mouseLeave.w2menu', event => {
|
|
1666
|
+
let dt = event.target.dataset
|
|
1667
|
+
let item = overlay.options.items[dt.index]
|
|
1668
|
+
let edata = this.trigger('mouseLeave', { overlay, item, originalEvent: event })
|
|
1669
|
+
if (edata.isCancelled) {
|
|
1670
|
+
return
|
|
1671
|
+
}
|
|
1672
|
+
w2tooltip.hide(overlay.name + '-tooltip')
|
|
1673
|
+
edata.finish()
|
|
1674
|
+
})
|
|
1675
|
+
.find('.menu-help')
|
|
1676
|
+
.off('.w2menu')
|
|
1677
|
+
.on('mouseEnter.w2menu', event => {
|
|
1678
|
+
let dt = event.target.parentNode.parentNode.dataset
|
|
1679
|
+
let tooltip = overlay.options.items[dt.index]?.help
|
|
1680
|
+
if (tooltip) {
|
|
1681
|
+
w2tooltip.show({
|
|
1682
|
+
name: overlay.name + '-help-tp',
|
|
1683
|
+
anchor: event.target,
|
|
1684
|
+
html: tooltip,
|
|
1685
|
+
position: 'right|left',
|
|
1686
|
+
hideOn: ['doc-click']
|
|
1687
|
+
})
|
|
1688
|
+
}
|
|
1689
|
+
})
|
|
1690
|
+
.on('mouseLeave.w2menu', event => {
|
|
1691
|
+
w2tooltip.hide(overlay.name + '-help-tp')
|
|
1692
|
+
})
|
|
1693
|
+
if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) {
|
|
1694
|
+
query(overlay.anchor)
|
|
1695
|
+
.off('.w2menu')
|
|
1696
|
+
.on('input.w2menu', event => {
|
|
1697
|
+
// if user types, clear selection
|
|
1698
|
+
// let dt = event.target.dataset
|
|
1699
|
+
// delete dt.selected
|
|
1700
|
+
// delete dt.selectedIndex
|
|
1701
|
+
})
|
|
1702
|
+
.on('keyup.w2menu', event => {
|
|
1703
|
+
event._searchType = 'filter'
|
|
1704
|
+
this.keyUp(overlay, event)
|
|
1705
|
+
})
|
|
1706
|
+
}
|
|
1707
|
+
if (overlay.options.search) {
|
|
1708
|
+
query(overlay.box).find('#menu-search')
|
|
1709
|
+
.off('.w2menu')
|
|
1710
|
+
.on('keyup.w2menu', event => {
|
|
1711
|
+
event._searchType = 'search'
|
|
1712
|
+
this.keyUp(overlay, event)
|
|
1713
|
+
})
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
getCurrent(name, id) {
|
|
1718
|
+
let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
1719
|
+
let options = overlay.options
|
|
1720
|
+
let selected = String(id ? id : (overlay.selected || '')).split('-')
|
|
1721
|
+
if (selected?.[0] === '') {
|
|
1722
|
+
selected.shift()
|
|
1723
|
+
}
|
|
1724
|
+
let last = selected.length - 1
|
|
1725
|
+
let index = selected[last]
|
|
1726
|
+
let parents = selected.slice(0, selected.length - 1).join('-')
|
|
1727
|
+
index = w2utils.isInt(index) ? parseInt(index) : 0
|
|
1728
|
+
// items
|
|
1729
|
+
let items = options.items
|
|
1730
|
+
selected.forEach((id, ind) => {
|
|
1731
|
+
// do not go to the last one
|
|
1732
|
+
if (ind < selected.length - 1) {
|
|
1733
|
+
items = items[id].items
|
|
1734
|
+
}
|
|
1735
|
+
})
|
|
1736
|
+
return { last, index, items, item: items[index], parents }
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
getMenuHTML(options) {
|
|
1740
|
+
if (options.spinner) {
|
|
1741
|
+
return `
|
|
1742
|
+
<div class="w2ui-menu">
|
|
1743
|
+
<div class="w2ui-no-items">
|
|
1744
|
+
<div class="w2ui-spinner"></div>
|
|
1745
|
+
${w2utils.lang('Loading...')}
|
|
1746
|
+
</div>
|
|
1747
|
+
</div>`
|
|
1748
|
+
}
|
|
1749
|
+
let parents = options.parents ?? []
|
|
1750
|
+
let items = options.items
|
|
1751
|
+
if (!Array.isArray(items)) items = []
|
|
1752
|
+
let count = 0
|
|
1753
|
+
let icon = null
|
|
1754
|
+
let topHTML = ''
|
|
1755
|
+
if (options.search) {
|
|
1756
|
+
topHTML += `
|
|
1757
|
+
<div class="w2ui-menu-search">
|
|
1758
|
+
<span class="w2ui-icon w2ui-icon-search"></span>
|
|
1759
|
+
<input id="menu-search" class="w2ui-input" type="text"/>
|
|
1760
|
+
</div>`
|
|
1761
|
+
items.forEach(item => item.hidden = false)
|
|
1762
|
+
}
|
|
1763
|
+
if (options.topHTML) {
|
|
1764
|
+
topHTML += `<div class="w2ui-menu-top">${options.topHTML}</div>`
|
|
1765
|
+
}
|
|
1766
|
+
let menu_html = `
|
|
1767
|
+
${topHTML}
|
|
1768
|
+
<div class="w2ui-menu" style="${options.menuStyle}" data-parents="${parents.join('-')}">
|
|
1769
|
+
`
|
|
1770
|
+
items.forEach((mitem, f) => {
|
|
1771
|
+
icon = mitem.icon
|
|
1772
|
+
let index = (parents.length > 0 ? parents.join('-') + '-' : '') + f
|
|
1773
|
+
if (icon == null) icon = null // icon might be undefined
|
|
1774
|
+
if (['radio', 'check'].includes(options.type) && !Array.isArray(mitem.items) && mitem.group !== false) {
|
|
1775
|
+
if (mitem.checked === true) icon = 'w2ui-icon-check'; else icon = 'w2ui-icon-empty'
|
|
1776
|
+
}
|
|
1777
|
+
if (mitem.hidden !== true) {
|
|
1778
|
+
let txt = mitem.text
|
|
1779
|
+
let icon_dsp = ''
|
|
1780
|
+
if (typeof options.render === 'function') txt = options.render(mitem, options)
|
|
1781
|
+
if (typeof txt == 'function') txt = txt(mitem, options)
|
|
1782
|
+
if (icon) {
|
|
1783
|
+
let first = String(icon).trim().slice(0, 1)
|
|
1784
|
+
if (first == '#') {
|
|
1785
|
+
icon = `<span class="w2ui-icon w2ui-icon-empty" style="background-color: ${icon}"></span>`
|
|
1786
|
+
} else if (first !== '<') {
|
|
1787
|
+
icon = `<span class="w2ui-icon ${icon}"></span>`
|
|
1788
|
+
}
|
|
1789
|
+
icon_dsp = `<div class="menu-icon">${icon}</div>`
|
|
1790
|
+
}
|
|
1791
|
+
// for backward compatibility
|
|
1792
|
+
if (mitem.removable == null && mitem.remove != null) {
|
|
1793
|
+
mitem.removable = mitem.remove
|
|
1794
|
+
}
|
|
1795
|
+
// render only if non-empty
|
|
1796
|
+
if (mitem.type !== 'break' && txt != null && txt !== '' && String(txt).substr(0, 2) != '--') {
|
|
1797
|
+
let classes = ['w2ui-menu-item']
|
|
1798
|
+
if (options.altRows == true) {
|
|
1799
|
+
classes.push(count % 2 === 0 ? 'w2ui-even' : 'w2ui-odd')
|
|
1800
|
+
}
|
|
1801
|
+
let colspan = 1
|
|
1802
|
+
if (icon_dsp === '') colspan++
|
|
1803
|
+
if (mitem.count == null && mitem.hotkey == null && mitem.removable !== true && mitem.items == null) colspan++
|
|
1804
|
+
if (mitem.tooltip == null && mitem.hint != null) mitem.tooltip = mitem.hint // for backward compatibility
|
|
1805
|
+
let count_dsp = ''
|
|
1806
|
+
if (mitem.removable === true) {
|
|
1807
|
+
count_dsp = '<span class="menu-remove">x</span>'
|
|
1808
|
+
} else if (mitem.items != null) {
|
|
1809
|
+
classes.push('has-sub-menu')
|
|
1810
|
+
count_dsp = '<span style="background-color: transparent; border: transparent; box-shadow: none;"></span>' // used as drop arrow
|
|
1811
|
+
} else {
|
|
1812
|
+
if (mitem.count != null) count_dsp += '<span>' + mitem.count + '</span>'
|
|
1813
|
+
if (mitem.hotkey != null) count_dsp += '<span class="menu-hotkey">' + mitem.hotkey + '</span>'
|
|
1814
|
+
if (mitem.help != null) count_dsp += '<span class="menu-help">?</span>'
|
|
1815
|
+
}
|
|
1816
|
+
if (mitem.disabled === true) classes.push('w2ui-disabled')
|
|
1817
|
+
if (mitem._noSearchInside === true) classes.push('w2ui-no-search-inside')
|
|
1818
|
+
menu_html += `
|
|
1819
|
+
<div index="${index}" class="${classes.join(' ')}" style="${mitem.style ? mitem.style : ''}"
|
|
1820
|
+
data-index="${f}" data-hasSubmenu="${mitem.items != null ? 'yes' : ''}">
|
|
1821
|
+
<div style="width: ${parseInt(mitem.indent ?? 0)}px"></div>
|
|
1822
|
+
${icon_dsp}
|
|
1823
|
+
<div class="menu-text" colspan="${colspan}">${w2utils.lang(txt)}</div>
|
|
1824
|
+
<div class="menu-extra">${mitem.extra ?? ''}${count_dsp}</div>
|
|
1825
|
+
</div>`
|
|
1826
|
+
count++
|
|
1827
|
+
} else {
|
|
1828
|
+
// horizontal line
|
|
1829
|
+
let divText = (txt ?? '').replace(/^-+/g, '')
|
|
1830
|
+
menu_html += `
|
|
1831
|
+
<div index="${index}" class="w2ui-menu-divider ${divText != '' ? 'has-text' : ''}">
|
|
1832
|
+
<div class="line"></div>
|
|
1833
|
+
${divText ? `<div class="text">${divText}</div>` : ''}
|
|
1834
|
+
</div>`
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
items[f] = mitem
|
|
1838
|
+
})
|
|
1839
|
+
if (count === 0 && options.msgNoItems) {
|
|
1840
|
+
let overlay = Tooltip.active[options.name.replace(/[\s\.#]/g, '_')]
|
|
1841
|
+
let remote = overlay?.tmp.remote
|
|
1842
|
+
let msg = options.msgNoItems
|
|
1843
|
+
if (options.url) {
|
|
1844
|
+
if (count == 0 && remote?.hasMore === false) {
|
|
1845
|
+
// if search is applied, but there are no items
|
|
1846
|
+
msg = options.msgNoItems
|
|
1847
|
+
} else {
|
|
1848
|
+
msg = options.msgSearch
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
menu_html += `
|
|
1852
|
+
<div class="w2ui-no-items">
|
|
1853
|
+
${w2utils.lang(msg)}
|
|
1854
|
+
</div>`
|
|
1855
|
+
}
|
|
1856
|
+
menu_html += '</div>'
|
|
1857
|
+
return menu_html
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
openSubMenu(event) {
|
|
1861
|
+
let anchor = query(event.originalEvent.target).get(0)
|
|
1862
|
+
let { overlay } = event
|
|
1863
|
+
let { items } = overlay.options
|
|
1864
|
+
// build sub-items list
|
|
1865
|
+
let mitem = items[event.index]
|
|
1866
|
+
let _items = []
|
|
1867
|
+
if (typeof mitem.items == 'function') {
|
|
1868
|
+
_items = mitem.items(mitem)
|
|
1869
|
+
} else if (Array.isArray(mitem.items)) {
|
|
1870
|
+
_items = mitem.items
|
|
1871
|
+
}
|
|
1872
|
+
let prev = w2menu.get(overlay.name + '-submenu')
|
|
1873
|
+
if (prev) {
|
|
1874
|
+
prev.hide()
|
|
1875
|
+
}
|
|
1876
|
+
query(event.target).addClass('expanded')
|
|
1877
|
+
w2menu.show({
|
|
1878
|
+
name: overlay.name + '-submenu',
|
|
1879
|
+
anchor: anchor,
|
|
1880
|
+
items: _items,
|
|
1881
|
+
class: overlay.options.class + ' ' + mitem.overlay?.class,
|
|
1882
|
+
offsetX: -7,
|
|
1883
|
+
arrowSize: 0,
|
|
1884
|
+
parentOverlay: overlay,
|
|
1885
|
+
parents: [...event.parents, event.index],
|
|
1886
|
+
position: 'right|left',
|
|
1887
|
+
hideOn: ['doc-click', 'select']
|
|
1888
|
+
})
|
|
1889
|
+
.hide(evt => {
|
|
1890
|
+
query(event.target).removeClass('expanded')
|
|
1891
|
+
})
|
|
1892
|
+
// indicates if user cursor is over sub menu
|
|
1893
|
+
setTimeout(() => {
|
|
1894
|
+
query('#w2overlay-' + overlay.name + '-submenu')
|
|
1895
|
+
.on('mouseenter', event => { event.target._keepSubOpen = true })
|
|
1896
|
+
.on('mouseleave', event => { event.target._keepSubOpen = false })
|
|
1897
|
+
}, 10)
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
closeSubMenu(event) {
|
|
1901
|
+
let { overlay } = event
|
|
1902
|
+
if (event.target._keepSubOpen !== true) {
|
|
1903
|
+
let prev = w2menu.get(overlay.name + '-submenu')
|
|
1904
|
+
if (prev) {
|
|
1905
|
+
prev.hide()
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// Refreshed only selected item highligh, used in keyboard navigation
|
|
1911
|
+
refreshIndex(name, instant) {
|
|
1912
|
+
let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
1913
|
+
if (!overlay) return
|
|
1914
|
+
if (!overlay.displayed) {
|
|
1915
|
+
this.show(overlay.name)
|
|
1916
|
+
}
|
|
1917
|
+
let view = query(overlay.box).find('.w2ui-overlay-body').get(0)
|
|
1918
|
+
let search = query(overlay.box).find('.w2ui-menu-search, .w2ui-menu-top').get(0)
|
|
1919
|
+
query(overlay.box).find('.w2ui-menu-item.w2ui-selected')
|
|
1920
|
+
.removeClass('w2ui-selected')
|
|
1921
|
+
let el = query(overlay.box).find(`.w2ui-menu-item[index="${overlay.selected}"]`)
|
|
1922
|
+
.addClass('w2ui-selected')
|
|
1923
|
+
.get(0)
|
|
1924
|
+
if (el) {
|
|
1925
|
+
if (el.offsetTop + el.clientHeight > view.clientHeight + view.scrollTop) {
|
|
1926
|
+
el.scrollIntoView({
|
|
1927
|
+
behavior: instant ? 'instant' : 'smooth',
|
|
1928
|
+
block: instant ? 'center' : 'start',
|
|
1929
|
+
inline: instant ? 'center' : 'start'
|
|
1930
|
+
})
|
|
1931
|
+
}
|
|
1932
|
+
if (el.offsetTop < view.scrollTop + (search ? search.clientHeight : 0)) {
|
|
1933
|
+
el.scrollIntoView({
|
|
1934
|
+
behavior: instant ? 'instant' : 'smooth',
|
|
1935
|
+
block: instant ? 'center' : 'end',
|
|
1936
|
+
inline: instant ? 'center' : 'end'
|
|
1937
|
+
})
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
w2tooltip.hide(overlay.name + '-tooltip')
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
showTooltip(name, options) {
|
|
1944
|
+
let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
1945
|
+
if (!overlay || !overlay.displayed) return
|
|
1946
|
+
let anchor = options?.anchor ?? query(overlay.box).find(`.w2ui-menu-item[index="${overlay.selected}"]`).get(0)
|
|
1947
|
+
let tooltip = options?.tooltip ?? overlay.options.items[overlay.selected]?.tooltip
|
|
1948
|
+
if (tooltip) {
|
|
1949
|
+
let html = tooltip.html ?? tooltip
|
|
1950
|
+
w2tooltip.show(Object.assign({
|
|
1951
|
+
name: overlay.name + '-tooltip',
|
|
1952
|
+
anchor,
|
|
1953
|
+
html,
|
|
1954
|
+
position: 'right|left',
|
|
1955
|
+
hideOn: ['doc-click'],
|
|
1956
|
+
onShow(event) {
|
|
1957
|
+
overlay.self.trigger('tooltip', { overlay, action: 'show', originalEvent: event })
|
|
1958
|
+
},
|
|
1959
|
+
onHide(event) {
|
|
1960
|
+
overlay.self.trigger('tooltip', { overlay, action: 'hide', originalEvent: event })
|
|
1961
|
+
}
|
|
1962
|
+
}, typeof tooltip == 'object' && tooltip != null ? tooltip : {}))
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// show/hide searched items
|
|
1967
|
+
refreshSearch(name) {
|
|
1968
|
+
let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
1969
|
+
if (!overlay) return
|
|
1970
|
+
if (!overlay.displayed) {
|
|
1971
|
+
this.show(overlay.name)
|
|
1972
|
+
}
|
|
1973
|
+
w2tooltip.hide(overlay.name + '-tooltip')
|
|
1974
|
+
query(overlay.box).find('.w2ui-no-items').hide()
|
|
1975
|
+
query(overlay.box).find('.w2ui-menu-item, .w2ui-menu-divider').each(el => {
|
|
1976
|
+
let cur = this.getCurrent(name, el.getAttribute('index'))
|
|
1977
|
+
if (cur.item?.hidden) {
|
|
1978
|
+
query(el).hide()
|
|
1979
|
+
} else {
|
|
1980
|
+
let search = overlay.tmp?.search
|
|
1981
|
+
if (overlay.options.markSearch) {
|
|
1982
|
+
w2utils.marker(el, search, { onlyFirst: overlay.options.match == 'begins' })
|
|
1983
|
+
}
|
|
1984
|
+
query(el).show()
|
|
1985
|
+
}
|
|
1986
|
+
})
|
|
1987
|
+
// hide empty menus
|
|
1988
|
+
query(overlay.box).find('.w2ui-sub-menu').each(sub => {
|
|
1989
|
+
let hasItems = query(sub).find('.w2ui-menu-item').get().some(el => {
|
|
1990
|
+
return el.style.display != 'none' ? true : false
|
|
1991
|
+
})
|
|
1992
|
+
let parent = this.getCurrent(name, sub.dataset.parent)
|
|
1993
|
+
// only if parent is expaneded
|
|
1994
|
+
if (parent.item.expanded) {
|
|
1995
|
+
if (!hasItems) {
|
|
1996
|
+
query(sub).parent().hide()
|
|
1997
|
+
} else {
|
|
1998
|
+
query(sub).parent().show()
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
})
|
|
2002
|
+
// show empty message
|
|
2003
|
+
if (overlay.tmp.searchCount == 0 || overlay.options?.items?.length == 0) {
|
|
2004
|
+
if (query(overlay.box).find('.w2ui-no-items').length == 0) {
|
|
2005
|
+
query(overlay.box).find('.w2ui-menu:not(.w2ui-sub-menu)').append(`
|
|
2006
|
+
<div class="w2ui-no-items">
|
|
2007
|
+
${w2utils.lang(overlay.options.msgNoItems)}
|
|
2008
|
+
</div>`)
|
|
2009
|
+
}
|
|
2010
|
+
query(overlay.box).find('.w2ui-no-items').show()
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
/**
|
|
2015
|
+
* Loops through the items and markes item.hidden = true for those that need to be hidden, and item.hidden = false
|
|
2016
|
+
* for those that are visible. Return a promise (since items can be on the server) with the number of visible items.
|
|
2017
|
+
*/
|
|
2018
|
+
applyFilter(name, items, search, debounce) {
|
|
2019
|
+
let count = 0
|
|
2020
|
+
let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
2021
|
+
let options = overlay.options
|
|
2022
|
+
let resolve, reject
|
|
2023
|
+
let prom = new Promise((res, rej) => {
|
|
2024
|
+
resolve = res
|
|
2025
|
+
reject = rej
|
|
2026
|
+
})
|
|
2027
|
+
if (overlay.tmp._skip_filter === true) {
|
|
2028
|
+
return prom
|
|
2029
|
+
}
|
|
2030
|
+
if (search == null) {
|
|
2031
|
+
if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) {
|
|
2032
|
+
search = overlay.anchor.value
|
|
2033
|
+
} else {
|
|
2034
|
+
search = ''
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
/**
|
|
2038
|
+
* If it is input control that has a menu, and if filter is true, then show all the items unfiltered, unless user
|
|
2039
|
+
* starts filtering again.
|
|
2040
|
+
*/
|
|
2041
|
+
if (overlay.tmp._new_search === false) {
|
|
2042
|
+
search = ''
|
|
2043
|
+
}
|
|
2044
|
+
let selectedIds = []
|
|
2045
|
+
if (options.selected) {
|
|
2046
|
+
if (Array.isArray(options.selected)) {
|
|
2047
|
+
selectedIds = options.selected.map(item => {
|
|
2048
|
+
return item?.id ?? item
|
|
2049
|
+
})
|
|
2050
|
+
} else if (options.selected?.id) {
|
|
2051
|
+
selectedIds = [options.selected.id]
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
overlay.tmp.activeChain = null
|
|
2055
|
+
// if url is defined, get items from it
|
|
2056
|
+
let remote = overlay.tmp.remote ?? { hasMore: true, emptySet: false, search: null, cached: -1 }
|
|
2057
|
+
if (remote.hasMore == false) {
|
|
2058
|
+
let len = remote.hasMore_search.length
|
|
2059
|
+
if (search.substr(0, len) != remote.hasMore_search) {
|
|
2060
|
+
remote.hasMore = true
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
if (items == null && options.url && remote.hasMore && remote.search !== search) {
|
|
2064
|
+
let proceed = true
|
|
2065
|
+
// only when items == null because it is case of nested items
|
|
2066
|
+
let msg = w2utils.lang('Loading...')
|
|
2067
|
+
if (search.length < options.minLength && remote.emptySet !== true) {
|
|
2068
|
+
msg = w2utils.lang('${count} letters or more...', { count: options.minLength })
|
|
2069
|
+
proceed = false
|
|
2070
|
+
if (search === '') {
|
|
2071
|
+
msg = w2utils.lang(options.msgSearch)
|
|
2072
|
+
}
|
|
2073
|
+
// if there are items - then clear them
|
|
2074
|
+
if (options.items?.length > 0) {
|
|
2075
|
+
this.update(name, [])
|
|
2076
|
+
this.applyFilter(name, null, search)
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
query(overlay.box).find('.w2ui-no-items').html(msg)
|
|
2080
|
+
remote.search = search
|
|
2081
|
+
options.items = []
|
|
2082
|
+
overlay.tmp.remote = remote
|
|
2083
|
+
if (proceed) {
|
|
2084
|
+
this.request(overlay, search, debounce)
|
|
2085
|
+
.then(remoteItems => {
|
|
2086
|
+
overlay.tmp._skip_filter = true
|
|
2087
|
+
this.update(name, remoteItems)
|
|
2088
|
+
delete overlay.tmp._skip_filter
|
|
2089
|
+
overlay.tmp._new_search = true
|
|
2090
|
+
this.applyFilter(name, remoteItems, search).then(data => {
|
|
2091
|
+
this.getActiveChain(overlay.name, options.items) // need this to update chain for up/down key navigation
|
|
2092
|
+
overlay.tmp.searchCount = data.count
|
|
2093
|
+
overlay.tmp.search = data.search
|
|
2094
|
+
if (options.prefilter || search !== '') {
|
|
2095
|
+
// if selected is not in searched items
|
|
2096
|
+
if (data.count === 0 || !this.getActiveChain(overlay.name, options.items).includes(overlay.selected)) {
|
|
2097
|
+
overlay.selected = null
|
|
2098
|
+
}
|
|
2099
|
+
this.refreshSearch(overlay.name)
|
|
2100
|
+
}
|
|
2101
|
+
this.initControls(overlay)
|
|
2102
|
+
this.refreshIndex(overlay.name, true)
|
|
2103
|
+
|
|
2104
|
+
resolve(data)
|
|
2105
|
+
})
|
|
2106
|
+
})
|
|
2107
|
+
.catch(error => {
|
|
2108
|
+
console.log('Server Request error', error)
|
|
2109
|
+
})
|
|
2110
|
+
}
|
|
2111
|
+
return prom
|
|
2112
|
+
}
|
|
2113
|
+
let edata
|
|
2114
|
+
// only trigger search event when data is present and for the top level
|
|
2115
|
+
if (items == null) {
|
|
2116
|
+
edata = this.trigger('search', { search, overlay, prom, resolve, reject })
|
|
2117
|
+
if (edata.isCancelled === true) {
|
|
2118
|
+
return prom
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
if (items == null) {
|
|
2122
|
+
items = overlay.options.items
|
|
2123
|
+
}
|
|
2124
|
+
if (options.filter === false) {
|
|
2125
|
+
resolve({ count: -1, search })
|
|
2126
|
+
return prom
|
|
2127
|
+
}
|
|
2128
|
+
items.forEach(item => {
|
|
2129
|
+
let prefix = ''
|
|
2130
|
+
let suffix = ''
|
|
2131
|
+
if (['is', 'begins', 'begins with'].indexOf(options.match) !== -1) prefix = '^'
|
|
2132
|
+
if (['is', 'ends', 'ends with'].indexOf(options.match) !== -1) suffix = '$'
|
|
2133
|
+
try {
|
|
2134
|
+
let re = new RegExp(prefix + search + suffix, 'i')
|
|
2135
|
+
if (re.test(item.text) || item.text === '...') {
|
|
2136
|
+
item.hidden = false
|
|
2137
|
+
} else {
|
|
2138
|
+
item.hidden = true
|
|
2139
|
+
}
|
|
2140
|
+
} catch (e) {}
|
|
2141
|
+
// do not show selected items
|
|
2142
|
+
if (options.hideSelected && selectedIds.includes(item.id)) {
|
|
2143
|
+
item.hidden = true
|
|
2144
|
+
}
|
|
2145
|
+
// search nested items
|
|
2146
|
+
if (Array.isArray(item.items) && item.items.length > 0) {
|
|
2147
|
+
delete item._noSearchInside
|
|
2148
|
+
this.applyFilter(name, item.items, search).then(data => {
|
|
2149
|
+
let subCount = data.count
|
|
2150
|
+
if (subCount > 0) {
|
|
2151
|
+
count += subCount
|
|
2152
|
+
if (item.hidden) item._noSearchInside = true
|
|
2153
|
+
// only expand items if search is not empty
|
|
2154
|
+
if (search) item.expanded = true
|
|
2155
|
+
item.hidden = false
|
|
2156
|
+
}
|
|
2157
|
+
})
|
|
2158
|
+
}
|
|
2159
|
+
if (item.hidden !== true) count++
|
|
2160
|
+
})
|
|
2161
|
+
resolve({ count, search })
|
|
2162
|
+
edata?.finish()
|
|
2163
|
+
return prom
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
request(overlay, search, debounce) {
|
|
2167
|
+
let options = overlay.options
|
|
2168
|
+
let remote = overlay.tmp.remote
|
|
2169
|
+
let resolve, reject // promise functions
|
|
2170
|
+
if ((options.items.length === 0 && remote.cached !== 0)
|
|
2171
|
+
|| (remote.cached == options.cacheMax && search.length > remote.search.length)
|
|
2172
|
+
|| (search.length >= remote.search.length && search.substr(0, remote.search.length) !== remote.search)
|
|
2173
|
+
|| (search.length < remote.search.length))
|
|
2174
|
+
{
|
|
2175
|
+
// Aabort previous request if any
|
|
2176
|
+
if (remote.controller) {
|
|
2177
|
+
remote.controller.abort()
|
|
2178
|
+
}
|
|
2179
|
+
remote.loading = true
|
|
2180
|
+
clearTimeout(remote.timeout)
|
|
2181
|
+
remote.timeout = setTimeout(() => {
|
|
2182
|
+
let url = options.url
|
|
2183
|
+
let postData = { search, max: options.cacheMax }
|
|
2184
|
+
Object.assign(postData, options.postData)
|
|
2185
|
+
// trigger event
|
|
2186
|
+
let edata = this.trigger('request', {
|
|
2187
|
+
search, overlay, url, postData,
|
|
2188
|
+
httpMethod: options.method ?? 'GET',
|
|
2189
|
+
httpHeaders: {}
|
|
2190
|
+
})
|
|
2191
|
+
if (edata.isCancelled === true) return
|
|
2192
|
+
// if event updated url and postData, use it
|
|
2193
|
+
url = new URL(edata.detail.url, location)
|
|
2194
|
+
let fetchOptions = w2utils.prepareParams(url, {
|
|
2195
|
+
method: edata.detail.httpMethod,
|
|
2196
|
+
headers: edata.detail.httpHeaders,
|
|
2197
|
+
body: edata.detail.postData
|
|
2198
|
+
}, { caller: this, overlay, search })
|
|
2199
|
+
// Create new abort controller
|
|
2200
|
+
remote.controller = new AbortController()
|
|
2201
|
+
fetchOptions.signal = remote.controller.signal
|
|
2202
|
+
// send request
|
|
2203
|
+
fetch(url, fetchOptions)
|
|
2204
|
+
.then(resp => resp.json())
|
|
2205
|
+
.then(data => {
|
|
2206
|
+
remote.controller = null
|
|
2207
|
+
// trigger event
|
|
2208
|
+
let edata = overlay.trigger('load', { search: postData.search, overlay, data })
|
|
2209
|
+
if (edata.isCancelled === true) return
|
|
2210
|
+
// default behavior
|
|
2211
|
+
data = edata.detail.data
|
|
2212
|
+
if (typeof data === 'string') data = JSON.parse(data)
|
|
2213
|
+
// if server just returns array
|
|
2214
|
+
if (Array.isArray(data)) {
|
|
2215
|
+
data = { records: data }
|
|
2216
|
+
}
|
|
2217
|
+
// needed for backward compatibility
|
|
2218
|
+
if (data.records == null && data.items != null) {
|
|
2219
|
+
data.records = data.items
|
|
2220
|
+
delete data.items
|
|
2221
|
+
}
|
|
2222
|
+
// handles Golang marshal of empty arrays to null
|
|
2223
|
+
if (!data.error && data.records == null) {
|
|
2224
|
+
data.records = []
|
|
2225
|
+
}
|
|
2226
|
+
if (!Array.isArray(data.records)) {
|
|
2227
|
+
console.error('ERROR: server did not return proper JSON data structure', '\n',
|
|
2228
|
+
' - it should return', { records: [{ id: 1, text: 'item' }] }, '\n',
|
|
2229
|
+
' - or just an array ', [{ id: 1, text: 'item' }], '\n',
|
|
2230
|
+
' - or if errorr ', { error: true, message: 'error message' })
|
|
2231
|
+
return
|
|
2232
|
+
}
|
|
2233
|
+
// remove all extra items if more then needed for cache
|
|
2234
|
+
if (data.records.length >= options.cacheMax) {
|
|
2235
|
+
data.records.splice(options.cacheMax, data.records.length)
|
|
2236
|
+
remote.hasMore = true
|
|
2237
|
+
} else {
|
|
2238
|
+
remote.hasMore = false
|
|
2239
|
+
remote.hasMore_search = search
|
|
2240
|
+
}
|
|
2241
|
+
// map id and text
|
|
2242
|
+
if (options.recId == null && options.recid != null) options.recId = options.recid // since lower-case recid is used in grid
|
|
2243
|
+
if (options.recId || options.recText) {
|
|
2244
|
+
data.records.forEach((item) => {
|
|
2245
|
+
if (typeof options.recId === 'string') item.id = item[options.recId]
|
|
2246
|
+
if (typeof options.recId === 'function') item.id = options.recId(item)
|
|
2247
|
+
if (typeof options.recText === 'string') item.text = item[options.recText]
|
|
2248
|
+
if (typeof options.recText === 'function') item.text = options.recText(item)
|
|
2249
|
+
})
|
|
2250
|
+
}
|
|
2251
|
+
// remember stats
|
|
2252
|
+
remote.loading = false
|
|
2253
|
+
remote.search = search
|
|
2254
|
+
remote.cached = data.records.length == 0 ? -1 : data.records.length
|
|
2255
|
+
remote.lastError = ''
|
|
2256
|
+
remote.emptySet = (search === '' && data.records.length === 0 ? true : false)
|
|
2257
|
+
// event after
|
|
2258
|
+
edata.finish()
|
|
2259
|
+
resolve(w2utils.normMenu(data.records, data))
|
|
2260
|
+
})
|
|
2261
|
+
.catch(error => {
|
|
2262
|
+
let edata = this.trigger('error', { overlay, search, error })
|
|
2263
|
+
if (edata.isCancelled === true) return
|
|
2264
|
+
// default behavior
|
|
2265
|
+
if (error?.name !== 'AbortError') {
|
|
2266
|
+
console.error('ERROR: Server communication failed.', '\n',
|
|
2267
|
+
' - it should return', { records: [{ id: 1, text: 'item' }] }, '\n',
|
|
2268
|
+
' - or just an array ', [{ id: 1, text: 'item' }], '\n',
|
|
2269
|
+
' - or if errorr ', { error: true, message: 'error message' })
|
|
2270
|
+
}
|
|
2271
|
+
// reset stats
|
|
2272
|
+
remote.loading = false
|
|
2273
|
+
remote.search = ''
|
|
2274
|
+
remote.cached = -1
|
|
2275
|
+
remote.emptySet = true
|
|
2276
|
+
remote.lastError = (edata.detail.error || 'Server communication failed')
|
|
2277
|
+
options.items = []
|
|
2278
|
+
// event after
|
|
2279
|
+
edata.finish()
|
|
2280
|
+
reject()
|
|
2281
|
+
})
|
|
2282
|
+
// event after
|
|
2283
|
+
edata.finish()
|
|
2284
|
+
}, debounce ? (options.debounce ?? 350) : 0)
|
|
2285
|
+
}
|
|
2286
|
+
return new Promise((res, rej) => {
|
|
2287
|
+
resolve = res
|
|
2288
|
+
reject = rej
|
|
2289
|
+
})
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
/**
|
|
2293
|
+
* Builds an array of item ids that sequencial order for navigation with up/down keys. Skips hidden and disabled items
|
|
2294
|
+
* and goes into nested structures. It will remember last active chain in 'overlay.tmp.activeChain'
|
|
2295
|
+
*/
|
|
2296
|
+
getActiveChain(name, items, parents = [], res = [], noSave) {
|
|
2297
|
+
let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
|
|
2298
|
+
if (overlay.tmp.activeChain != null) {
|
|
2299
|
+
return overlay.tmp.activeChain
|
|
2300
|
+
}
|
|
2301
|
+
if (items == null) items = overlay.options.items
|
|
2302
|
+
items.forEach((item, ind) => {
|
|
2303
|
+
if (!item.hidden && !item.disabled && !item?.text?.startsWith?.('--')) {
|
|
2304
|
+
res.push(parents.concat([ind]).join('-'))
|
|
2305
|
+
if (Array.isArray(item.items) && item.items.length > 0 && item.expanded) {
|
|
2306
|
+
parents.push(ind)
|
|
2307
|
+
this.getActiveChain(name, item.items, parents, res, true)
|
|
2308
|
+
parents.pop()
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
})
|
|
2312
|
+
if (noSave == null) {
|
|
2313
|
+
overlay.tmp.activeChain = res
|
|
2314
|
+
}
|
|
2315
|
+
return res
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
menuDown(overlay, event, index, parents) {
|
|
2319
|
+
let options = overlay.options
|
|
2320
|
+
let items = options.items
|
|
2321
|
+
let icon = query(event.delegate).find('.w2ui-icon')
|
|
2322
|
+
let menu = query(event.target).closest('.w2ui-menu:not(.w2ui-sub-menu)')
|
|
2323
|
+
if (typeof parents == 'string' && parents !== '') {
|
|
2324
|
+
let ids = parents.split('-')
|
|
2325
|
+
ids.forEach(id => {
|
|
2326
|
+
items = items[id].items
|
|
2327
|
+
})
|
|
2328
|
+
}
|
|
2329
|
+
if (typeof items == 'function') {
|
|
2330
|
+
items = items({ overlay, index, parents, event })
|
|
2331
|
+
}
|
|
2332
|
+
let item = items[index]
|
|
2333
|
+
if (item == null || item.disabled) {
|
|
2334
|
+
return
|
|
2335
|
+
}
|
|
2336
|
+
let uncheck = (items, parent) => {
|
|
2337
|
+
items.forEach((other, ind) => {
|
|
2338
|
+
if (other.id == item.id) return
|
|
2339
|
+
if (other.group === item.group && other.checked) {
|
|
2340
|
+
menu
|
|
2341
|
+
.find(`.w2ui-menu-item[index="${(parent ? parent + '-' : '') + ind}"] .w2ui-icon`)
|
|
2342
|
+
.removeClass('w2ui-icon-check')
|
|
2343
|
+
.addClass('w2ui-icon-empty')
|
|
2344
|
+
items[ind].checked = false
|
|
2345
|
+
}
|
|
2346
|
+
if (Array.isArray(other.items)) {
|
|
2347
|
+
uncheck(other.items, ind)
|
|
2348
|
+
}
|
|
2349
|
+
})
|
|
2350
|
+
}
|
|
2351
|
+
if ((options.type === 'check' || options.type === 'radio') && item.group !== false
|
|
2352
|
+
&& !query(event.target).hasClass('menu-remove')
|
|
2353
|
+
&& !query(event.target).hasClass('menu-help')
|
|
2354
|
+
&& !query(event.target).closest('.w2ui-menu-item').hasClass('has-sub-menu')) {
|
|
2355
|
+
item.checked = options.type == 'radio' ? true : !item.checked
|
|
2356
|
+
if (item.checked) {
|
|
2357
|
+
if (options.type === 'radio') {
|
|
2358
|
+
query(event.target).closest('.w2ui-menu').find('.w2ui-icon')
|
|
2359
|
+
.removeClass('w2ui-icon-check')
|
|
2360
|
+
.addClass('w2ui-icon-empty')
|
|
2361
|
+
}
|
|
2362
|
+
if (options.type === 'check' && item.group != null) {
|
|
2363
|
+
uncheck(options.items)
|
|
2364
|
+
}
|
|
2365
|
+
icon.removeClass('w2ui-icon-empty').addClass('w2ui-icon-check')
|
|
2366
|
+
} else if (options.type === 'check') {
|
|
2367
|
+
icon.removeClass('w2ui-icon-check').addClass('w2ui-icon-empty')
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
// highlight record
|
|
2371
|
+
if (!query(event.target).hasClass('menu-remove') && !query(event.target).hasClass('menu-help')) {
|
|
2372
|
+
menu.find('.w2ui-menu-item').removeClass('w2ui-selected')
|
|
2373
|
+
// click on the item that has submenu will not select the item
|
|
2374
|
+
if (!query(event.delegate).hasClass('has-sub-menu')) {
|
|
2375
|
+
query(event.delegate).addClass('w2ui-selected')
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
menuClick(overlay, event, index, parents) {
|
|
2381
|
+
let options = overlay.options
|
|
2382
|
+
let items = options.items
|
|
2383
|
+
let $item = query(event.delegate).closest('.w2ui-menu-item')
|
|
2384
|
+
let keepOpen = options.hideOn.includes('select') ? false : true
|
|
2385
|
+
if (event.shiftKey || event.metaKey || event.ctrlKey) {
|
|
2386
|
+
keepOpen = true
|
|
2387
|
+
}
|
|
2388
|
+
if (typeof parents == 'string' && parents !== '') {
|
|
2389
|
+
parents = parents.split('-')
|
|
2390
|
+
} else if (!Array.isArray(parents)) {
|
|
2391
|
+
parents = null
|
|
2392
|
+
}
|
|
2393
|
+
// parse through parents to get to right items
|
|
2394
|
+
if (parents) {
|
|
2395
|
+
parents.forEach(id => {
|
|
2396
|
+
items = items[id].items
|
|
2397
|
+
})
|
|
2398
|
+
}
|
|
2399
|
+
if (typeof items == 'function') {
|
|
2400
|
+
items = items({ overlay, index, parents, event })
|
|
2401
|
+
}
|
|
2402
|
+
let item = items[index]
|
|
2403
|
+
if (!item || (item.disabled && !query(event.target).hasClass('menu-remove'))) {
|
|
2404
|
+
return
|
|
2405
|
+
}
|
|
2406
|
+
let edata
|
|
2407
|
+
let overlays = [overlay]
|
|
2408
|
+
let topOverlay = overlay
|
|
2409
|
+
let parentOverlay
|
|
2410
|
+
while (topOverlay.options.parentOverlay) {
|
|
2411
|
+
topOverlay = topOverlay.options.parentOverlay
|
|
2412
|
+
parentOverlay ??= topOverlay // not top overlay, but parent overlay items
|
|
2413
|
+
overlays.push(topOverlay)
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
if (query(event.target).hasClass('menu-remove')) {
|
|
2417
|
+
edata = topOverlay.trigger('remove', {
|
|
2418
|
+
originalEvent: event, target: overlay.name, overlay, topOverlay, parentOverlay,
|
|
2419
|
+
item, index, el: $item[0]
|
|
2420
|
+
})
|
|
2421
|
+
if (edata.isCancelled === true) {
|
|
2422
|
+
return
|
|
2423
|
+
}
|
|
2424
|
+
// delete from items
|
|
2425
|
+
let items = options.items
|
|
2426
|
+
let ind = items.findIndex(it => it.id == item.id)
|
|
2427
|
+
if (ind != -1) {
|
|
2428
|
+
let tmp = items.splice(ind, 1)
|
|
2429
|
+
// delete from the parent too
|
|
2430
|
+
if (overlay.options.parents) {
|
|
2431
|
+
let pind = overlay.options.parents[overlay.options.parents.length -1]
|
|
2432
|
+
let pitems = parentOverlay.options.items[pind].items
|
|
2433
|
+
if (pitems[ind].id == tmp[0].id) {
|
|
2434
|
+
pitems.splice(ind, 1)
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
keepOpen = !options.hideOn.includes('item-remove')
|
|
2439
|
+
let name = $item.closest('.w2ui-overlay').attr('name')
|
|
2440
|
+
overlay.self.update(name, items)
|
|
2441
|
+
|
|
2442
|
+
} else if ($item.hasClass('has-sub-menu')) {
|
|
2443
|
+
|
|
2444
|
+
edata = topOverlay.trigger('subMenu', {
|
|
2445
|
+
originalEvent: event, target: overlay.name, overlay, topOverlay, parentOverlay,
|
|
2446
|
+
item, index, el: $item[0]
|
|
2447
|
+
})
|
|
2448
|
+
if (edata.isCancelled === true) {
|
|
2449
|
+
return
|
|
2450
|
+
}
|
|
2451
|
+
keepOpen = true
|
|
2452
|
+
|
|
2453
|
+
} else {
|
|
2454
|
+
|
|
2455
|
+
// find items that are selected
|
|
2456
|
+
let selected = this.findChecked(options.items)
|
|
2457
|
+
let a_index = $item.attr('index')
|
|
2458
|
+
overlay.selected = isNaN(a_index) ? a_index: parseInt(a_index)
|
|
2459
|
+
edata = topOverlay.trigger('select', {
|
|
2460
|
+
originalEvent: event, target: overlay.name, overlay, topOverlay, parentOverlay,
|
|
2461
|
+
item, index, selected, keepOpen, el: $item[0]
|
|
2462
|
+
})
|
|
2463
|
+
if (edata.isCancelled === true) {
|
|
2464
|
+
return
|
|
2465
|
+
}
|
|
2466
|
+
if (item.keepOpen != null) {
|
|
2467
|
+
keepOpen = item.keepOpen
|
|
2468
|
+
}
|
|
2469
|
+
if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) {
|
|
2470
|
+
overlay.anchor.dataset.selected = item.id
|
|
2471
|
+
overlay.anchor.dataset.selectedIndex = overlay.selected
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
if (!keepOpen) {
|
|
2475
|
+
overlays.forEach(overlay => this.hide(overlay.name))
|
|
2476
|
+
}
|
|
2477
|
+
// if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) {
|
|
2478
|
+
// overlay.anchor.focus()
|
|
2479
|
+
// }
|
|
2480
|
+
// event after
|
|
2481
|
+
edata.finish()
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
findChecked(items) {
|
|
2485
|
+
let found = []
|
|
2486
|
+
items.forEach(item => {
|
|
2487
|
+
if (item.checked) found.push(item)
|
|
2488
|
+
if (Array.isArray(item.items)) {
|
|
2489
|
+
found = found.concat(this.findChecked(item.items))
|
|
2490
|
+
}
|
|
2491
|
+
})
|
|
2492
|
+
return found
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
keyUp(overlay, event) {
|
|
2496
|
+
let options = overlay.options
|
|
2497
|
+
let search = event.target.value
|
|
2498
|
+
let filter = true
|
|
2499
|
+
let refreshIndex = false
|
|
2500
|
+
switch (event.keyCode) {
|
|
2501
|
+
case 46: // delete
|
|
2502
|
+
case 8: { // backspace
|
|
2503
|
+
// if search empty and delete is clicked, do not filter nor show overlay
|
|
2504
|
+
if (search === '' && !overlay.displayed) filter = false
|
|
2505
|
+
break
|
|
2506
|
+
}
|
|
2507
|
+
case 13: { // enter
|
|
2508
|
+
if (!overlay.displayed || !overlay.selected) return
|
|
2509
|
+
let { index, parents } = this.getCurrent(overlay.name)
|
|
2510
|
+
event.delegate = query(overlay.box).find('.w2ui-selected').get(0)
|
|
2511
|
+
// reset active chain for folders
|
|
2512
|
+
this.menuClick(overlay, event, parseInt(index), parents)
|
|
2513
|
+
filter = false
|
|
2514
|
+
break
|
|
2515
|
+
}
|
|
2516
|
+
case 27: { // escape
|
|
2517
|
+
filter = false
|
|
2518
|
+
if (overlay.displayed) {
|
|
2519
|
+
this.hide(overlay.name)
|
|
2520
|
+
} else {
|
|
2521
|
+
// clear selected
|
|
2522
|
+
let el = overlay.anchor
|
|
2523
|
+
if (['INPUT', 'TEXTAREA'].includes(el.tagName)) {
|
|
2524
|
+
el.value = ''
|
|
2525
|
+
delete el.dataset.selected
|
|
2526
|
+
delete el.dataset.selectedIndex
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
break
|
|
2530
|
+
}
|
|
2531
|
+
case 37: { // left
|
|
2532
|
+
if (!overlay.displayed) return
|
|
2533
|
+
let { item, index, parents } = this.getCurrent(overlay.name)
|
|
2534
|
+
// collapse parent if any
|
|
2535
|
+
if (parents) {
|
|
2536
|
+
item = options.items[parents]
|
|
2537
|
+
index = parseInt(parents)
|
|
2538
|
+
parents = ''
|
|
2539
|
+
refreshIndex = true
|
|
2540
|
+
}
|
|
2541
|
+
if (Array.isArray(item?.items) && item.items.length > 0 && item.expanded) {
|
|
2542
|
+
event.delegate = query(overlay.box).find(`.w2ui-menu-item[index="${index}"]`).get(0)
|
|
2543
|
+
overlay.selected = index
|
|
2544
|
+
this.menuClick(overlay, event, parseInt(index), parents)
|
|
2545
|
+
}
|
|
2546
|
+
filter = false
|
|
2547
|
+
break
|
|
2548
|
+
}
|
|
2549
|
+
case 39: { // right
|
|
2550
|
+
if (!overlay.displayed) return
|
|
2551
|
+
let { item, index, parents } = this.getCurrent(overlay.name)
|
|
2552
|
+
if (Array.isArray(item?.items) && item.items.length > 0 && !item.expanded) {
|
|
2553
|
+
event.delegate = query(overlay.box).find('.w2ui-selected').get(0)
|
|
2554
|
+
this.menuClick(overlay, event, parseInt(index), parents)
|
|
2555
|
+
}
|
|
2556
|
+
filter = false
|
|
2557
|
+
break
|
|
2558
|
+
}
|
|
2559
|
+
case 38: { // up
|
|
2560
|
+
if (!overlay.displayed) {
|
|
2561
|
+
break
|
|
2562
|
+
}
|
|
2563
|
+
overlay.prev()
|
|
2564
|
+
filter = false
|
|
2565
|
+
event.preventDefault()
|
|
2566
|
+
break
|
|
2567
|
+
}
|
|
2568
|
+
case 40: { // down
|
|
2569
|
+
if (!overlay.displayed) {
|
|
2570
|
+
break
|
|
2571
|
+
}
|
|
2572
|
+
overlay.next()
|
|
2573
|
+
filter = false
|
|
2574
|
+
event.preventDefault()
|
|
2575
|
+
break
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
// filter
|
|
2579
|
+
if (filter && overlay.displayed
|
|
2580
|
+
&& ((options.filter && event._searchType == 'filter') || (options.search && event._searchType == 'search'))) {
|
|
2581
|
+
this.applyFilter(overlay.name, null, search, true)
|
|
2582
|
+
.then(data => {
|
|
2583
|
+
overlay.tmp.searchCount = data.count
|
|
2584
|
+
overlay.tmp.search = data.search
|
|
2585
|
+
// if selected is not in searched items
|
|
2586
|
+
if (data.count === 0 || !this.getActiveChain(overlay.name).includes(overlay.selected)) {
|
|
2587
|
+
overlay.selected = null
|
|
2588
|
+
}
|
|
2589
|
+
this.refreshSearch(overlay.name)
|
|
2590
|
+
})
|
|
2591
|
+
}
|
|
2592
|
+
if (refreshIndex) {
|
|
2593
|
+
this.refreshIndex(overlay.name)
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
class DateTooltip extends Tooltip {
|
|
2599
|
+
constructor() {
|
|
2600
|
+
super()
|
|
2601
|
+
let td = new Date()
|
|
2602
|
+
this.daysCount = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
2603
|
+
this.today = td.getFullYear() + '/' + (Number(td.getMonth()) + 1) + '/' + td.getDate()
|
|
2604
|
+
this.defaults = w2utils.extend({}, this.defaults, {
|
|
2605
|
+
position : 'top|bottom',
|
|
2606
|
+
class : 'w2ui-calendar',
|
|
2607
|
+
type : 'date', // can be date/time/datetime
|
|
2608
|
+
value : '', // initial date (in w2utils.settings format)
|
|
2609
|
+
format : '',
|
|
2610
|
+
start : null,
|
|
2611
|
+
end : null,
|
|
2612
|
+
btnNow : false,
|
|
2613
|
+
blockDates : [], // array of blocked dates
|
|
2614
|
+
blockWeekdays : [], // blocked weekdays 0 - sunday, 1 - monday, etc
|
|
2615
|
+
colored : {}, // ex: { '3/13/2022': 'bg-color|text-color' }
|
|
2616
|
+
arrowSize : 12,
|
|
2617
|
+
autoResize : false,
|
|
2618
|
+
anchorClass : 'w2ui-focus',
|
|
2619
|
+
autoShowOn : 'focus',
|
|
2620
|
+
hideOn : ['doc-click', 'focus-change'],
|
|
2621
|
+
onSelect : null
|
|
2622
|
+
})
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
attach(anchor, text) {
|
|
2626
|
+
let options
|
|
2627
|
+
if (arguments.length == 1 && anchor instanceof Object) {
|
|
2628
|
+
options = anchor
|
|
2629
|
+
anchor = options.anchor
|
|
2630
|
+
} else if (arguments.length === 2 && text != null && typeof text === 'object') {
|
|
2631
|
+
options = text
|
|
2632
|
+
options.anchor = anchor
|
|
2633
|
+
}
|
|
2634
|
+
let prevHideOn = options.hideOn
|
|
2635
|
+
options = w2utils.extend({}, this.defaults, options || {})
|
|
2636
|
+
if (prevHideOn) {
|
|
2637
|
+
options.hideOn = prevHideOn
|
|
2638
|
+
}
|
|
2639
|
+
if (!options.format) {
|
|
2640
|
+
let df = w2utils.settings.dateFormat
|
|
2641
|
+
let tf = w2utils.settings.timeFormat
|
|
2642
|
+
if (options.type == 'date') {
|
|
2643
|
+
options.format = df
|
|
2644
|
+
} else if (options.type == 'time') {
|
|
2645
|
+
options.format = tf
|
|
2646
|
+
} else {
|
|
2647
|
+
options.format = df + '|' + tf
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
let cal = options.type == 'time' ? this.getHourHTML(options) : this.getMonthHTML(options)
|
|
2651
|
+
options.style += '; padding: 0;'
|
|
2652
|
+
options.html = cal.html
|
|
2653
|
+
let ret = super.attach(options)
|
|
2654
|
+
let overlay = ret.overlay
|
|
2655
|
+
Object.assign(overlay.tmp, cal)
|
|
2656
|
+
overlay.on('show.attach', event => {
|
|
2657
|
+
let overlay = event.detail.overlay
|
|
2658
|
+
let anchor = overlay.anchor
|
|
2659
|
+
let options = overlay.options
|
|
2660
|
+
if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && !options.value && anchor.value) {
|
|
2661
|
+
overlay.tmp.initValue = anchor.value
|
|
2662
|
+
}
|
|
2663
|
+
delete overlay.newValue
|
|
2664
|
+
delete overlay.newDate
|
|
2665
|
+
})
|
|
2666
|
+
overlay.on('show:after.attach', event => {
|
|
2667
|
+
if (ret.overlay?.box) {
|
|
2668
|
+
this.initControls(ret.overlay)
|
|
2669
|
+
}
|
|
2670
|
+
})
|
|
2671
|
+
overlay.on('update:after.attach', event => {
|
|
2672
|
+
if (ret.overlay?.box) {
|
|
2673
|
+
this.initControls(ret.overlay)
|
|
2674
|
+
}
|
|
2675
|
+
})
|
|
2676
|
+
overlay.on('hide.attach', event => {
|
|
2677
|
+
let overlay = event.detail.overlay
|
|
2678
|
+
let anchor = overlay.anchor
|
|
2679
|
+
if (overlay.newValue != null) {
|
|
2680
|
+
if (overlay.newDate) {
|
|
2681
|
+
overlay.newValue = overlay.newDate + ' ' + overlay.newValue
|
|
2682
|
+
}
|
|
2683
|
+
if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && anchor.value != overlay.newValue) {
|
|
2684
|
+
anchor.value = overlay.newValue
|
|
2685
|
+
}
|
|
2686
|
+
let edata = this.trigger('select', { date: overlay.newValue, target: overlay.name, overlay })
|
|
2687
|
+
if (edata.isCancelled === true) return
|
|
2688
|
+
// event after
|
|
2689
|
+
edata.finish()
|
|
2690
|
+
}
|
|
2691
|
+
})
|
|
2692
|
+
ret.select = (callback) => {
|
|
2693
|
+
overlay.on('select.attach', (event) => { callback(event) })
|
|
2694
|
+
return ret
|
|
2695
|
+
}
|
|
2696
|
+
return ret
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
initControls(overlay) {
|
|
2700
|
+
let options = overlay.options
|
|
2701
|
+
let moveMonth = (inc) => {
|
|
2702
|
+
let { month, year } = overlay.tmp
|
|
2703
|
+
month += inc
|
|
2704
|
+
if (month > 12) {
|
|
2705
|
+
month = 1
|
|
2706
|
+
year++
|
|
2707
|
+
}
|
|
2708
|
+
if (month < 1 ) {
|
|
2709
|
+
month = 12
|
|
2710
|
+
year--
|
|
2711
|
+
}
|
|
2712
|
+
let cal = this.getMonthHTML(options, month, year)
|
|
2713
|
+
Object.assign(overlay.tmp, cal)
|
|
2714
|
+
query(overlay.box).find('.w2ui-overlay-body').html(cal.html)
|
|
2715
|
+
this.initControls(overlay)
|
|
2716
|
+
}
|
|
2717
|
+
let checkJump = (event, dblclick) => {
|
|
2718
|
+
query(event.target).parent().find('.w2ui-jump-month, .w2ui-jump-year')
|
|
2719
|
+
.removeClass('w2ui-selected')
|
|
2720
|
+
query(event.target).addClass('w2ui-selected')
|
|
2721
|
+
let dt = new Date()
|
|
2722
|
+
let { jumpMonth, jumpYear } = overlay.tmp
|
|
2723
|
+
if (dblclick) {
|
|
2724
|
+
if (jumpYear == null) jumpYear = dt.getFullYear()
|
|
2725
|
+
if (jumpMonth == null) jumpMonth = dt.getMonth() + 1
|
|
2726
|
+
}
|
|
2727
|
+
if (jumpMonth && jumpYear) {
|
|
2728
|
+
let cal = this.getMonthHTML(options, jumpMonth, jumpYear)
|
|
2729
|
+
Object.assign(overlay.tmp, cal)
|
|
2730
|
+
query(overlay.box).find('.w2ui-overlay-body').html(cal.html)
|
|
2731
|
+
overlay.tmp.jump = false
|
|
2732
|
+
this.initControls(overlay)
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
// events for next/prev buttons and title
|
|
2737
|
+
query(overlay.box)
|
|
2738
|
+
.find('.w2ui-cal-title')
|
|
2739
|
+
.off('.calendar')
|
|
2740
|
+
// click on title
|
|
2741
|
+
.on('click.calendar', event => {
|
|
2742
|
+
Object.assign(overlay.tmp, { jumpYear: null, jumpMonth: null })
|
|
2743
|
+
if (overlay.tmp.jump) {
|
|
2744
|
+
let { month, year } = overlay.tmp
|
|
2745
|
+
let cal = this.getMonthHTML(options, month, year)
|
|
2746
|
+
query(overlay.box).find('.w2ui-overlay-body').html(cal.html)
|
|
2747
|
+
overlay.tmp.jump = false
|
|
2748
|
+
} else {
|
|
2749
|
+
query(overlay.box).find('.w2ui-overlay-body .w2ui-cal-days')
|
|
2750
|
+
.replace(this.getYearHTML())
|
|
2751
|
+
let el = query(overlay.box).find(`[name="${overlay.tmp.year}"]`).get(0)
|
|
2752
|
+
if (el) el.scrollIntoView(true)
|
|
2753
|
+
overlay.tmp.jump = true
|
|
2754
|
+
}
|
|
2755
|
+
this.initControls(overlay)
|
|
2756
|
+
event.stopPropagation()
|
|
2757
|
+
})
|
|
2758
|
+
// prev button
|
|
2759
|
+
.find('.w2ui-cal-previous')
|
|
2760
|
+
.off('.calendar')
|
|
2761
|
+
.on('click.calendar', event => {
|
|
2762
|
+
moveMonth(-1)
|
|
2763
|
+
event.stopPropagation()
|
|
2764
|
+
})
|
|
2765
|
+
.parent()
|
|
2766
|
+
// next button
|
|
2767
|
+
.find('.w2ui-cal-next')
|
|
2768
|
+
.off('.calendar')
|
|
2769
|
+
.on('click.calendar', event => {
|
|
2770
|
+
moveMonth(1)
|
|
2771
|
+
event.stopPropagation()
|
|
2772
|
+
})
|
|
2773
|
+
// now button
|
|
2774
|
+
query(overlay.box).find('.w2ui-cal-now')
|
|
2775
|
+
.off('.calendar')
|
|
2776
|
+
.on('click.calendar', event => {
|
|
2777
|
+
if (options.type == 'datetime') {
|
|
2778
|
+
if (overlay.newDate) {
|
|
2779
|
+
overlay.newValue = w2utils.formatTime(new Date(), options.format.split('|')[1])
|
|
2780
|
+
} else {
|
|
2781
|
+
overlay.newValue = w2utils.formatDateTime(new Date(), options.format)
|
|
2782
|
+
}
|
|
2783
|
+
} else if (options.type == 'date') {
|
|
2784
|
+
overlay.newValue = w2utils.formatDate(new Date(), options.format)
|
|
2785
|
+
} else if (options.type == 'time') {
|
|
2786
|
+
overlay.newValue = w2utils.formatTime(new Date(), options.format)
|
|
2787
|
+
}
|
|
2788
|
+
this.hide(overlay.name)
|
|
2789
|
+
})
|
|
2790
|
+
// events for dates
|
|
2791
|
+
query(overlay.box)
|
|
2792
|
+
.off('.calendar')
|
|
2793
|
+
.on('contextmenu.calendar', event => {
|
|
2794
|
+
event.preventDefault() // prevent browser context menu
|
|
2795
|
+
})
|
|
2796
|
+
.on('click.calendar', { delegate: '.w2ui-day.w2ui-date' }, event => {
|
|
2797
|
+
if (options.type == 'datetime') {
|
|
2798
|
+
overlay.newDate = query(event.target).attr('date')
|
|
2799
|
+
query(overlay.box).find('.w2ui-overlay-body').html(this.getHourHTML(overlay.options).html)
|
|
2800
|
+
this.initControls(overlay)
|
|
2801
|
+
} else {
|
|
2802
|
+
overlay.newValue = query(event.target).attr('date')
|
|
2803
|
+
this.hide(overlay.name)
|
|
2804
|
+
}
|
|
2805
|
+
})
|
|
2806
|
+
// click on month
|
|
2807
|
+
.on('click.calendar', { delegate: '.w2ui-jump-month' }, event => {
|
|
2808
|
+
overlay.tmp.jumpMonth = parseInt(query(event.target).attr('name'))
|
|
2809
|
+
checkJump(event)
|
|
2810
|
+
})
|
|
2811
|
+
// double click on month
|
|
2812
|
+
.on('dblclick.calendar', { delegate: '.w2ui-jump-month' }, event => {
|
|
2813
|
+
overlay.tmp.jumpMonth = parseInt(query(event.target).attr('name'))
|
|
2814
|
+
checkJump(event, true)
|
|
2815
|
+
})
|
|
2816
|
+
// click on year
|
|
2817
|
+
.on('click.calendar', { delegate: '.w2ui-jump-year' }, event => {
|
|
2818
|
+
overlay.tmp.jumpYear = parseInt(query(event.target).attr('name'))
|
|
2819
|
+
checkJump(event)
|
|
2820
|
+
})
|
|
2821
|
+
// dbl click on year
|
|
2822
|
+
.on('dblclick.calendar', { delegate: '.w2ui-jump-year' }, event => {
|
|
2823
|
+
overlay.tmp.jumpYear = parseInt(query(event.target).attr('name'))
|
|
2824
|
+
checkJump(event, true)
|
|
2825
|
+
})
|
|
2826
|
+
// click on hour
|
|
2827
|
+
.on('click.calendar', { delegate: '.w2ui-time.hour' }, event => {
|
|
2828
|
+
let hour = query(event.target).attr('hour')
|
|
2829
|
+
let min = this.str2min(options.value) % 60
|
|
2830
|
+
if (overlay.tmp.initValue && !options.value) {
|
|
2831
|
+
min = this.str2min(overlay.tmp.initValue) % 60
|
|
2832
|
+
}
|
|
2833
|
+
if (options.noMinutes) {
|
|
2834
|
+
overlay.newValue = this.min2str(hour * 60, options.format)
|
|
2835
|
+
this.hide(overlay.name)
|
|
2836
|
+
} else {
|
|
2837
|
+
overlay.newValue = hour + ':' + min
|
|
2838
|
+
let html = this.getMinHTML(hour, options).html
|
|
2839
|
+
query(overlay.box).find('.w2ui-overlay-body').html(html)
|
|
2840
|
+
this.initControls(overlay)
|
|
2841
|
+
}
|
|
2842
|
+
})
|
|
2843
|
+
// click on minute
|
|
2844
|
+
.on('click.calendar', { delegate: '.w2ui-time.min' }, event => {
|
|
2845
|
+
let hour = Math.floor(this.str2min(overlay.newValue) / 60)
|
|
2846
|
+
let time = (hour * 60) + parseInt(query(event.target).attr('min'))
|
|
2847
|
+
overlay.newValue = this.min2str(time, options.format)
|
|
2848
|
+
this.hide(overlay.name)
|
|
2849
|
+
})
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
getMonthHTML(options, month, year) {
|
|
2853
|
+
let days = w2utils.settings.fulldays.slice() // creates copy of the array
|
|
2854
|
+
let sdays = w2utils.settings.shortdays.slice() // creates copy of the array
|
|
2855
|
+
if (w2utils.settings.weekStarts !== 'M') {
|
|
2856
|
+
days.unshift(days.pop())
|
|
2857
|
+
sdays.unshift(sdays.pop())
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
let DT = new Date()
|
|
2861
|
+
let dayLengthMil = 1000 * 60 * 60 * 24
|
|
2862
|
+
|
|
2863
|
+
let selected = options.type === 'datetime'
|
|
2864
|
+
? w2utils.isDateTime(options.value, options.format, true)
|
|
2865
|
+
: w2utils.isDate(options.value, options.format, true)
|
|
2866
|
+
let selected_dsp = w2utils.formatDate(selected)
|
|
2867
|
+
|
|
2868
|
+
// normalize date
|
|
2869
|
+
if (month == null || year == null) {
|
|
2870
|
+
year = selected ? selected.getFullYear() : DT.getFullYear()
|
|
2871
|
+
month = selected ? selected.getMonth() + 1 : DT.getMonth() + 1
|
|
2872
|
+
}
|
|
2873
|
+
if (month > 12) { month -= 12; year++ }
|
|
2874
|
+
if (month < 1 || month === 0) { month += 12; year-- }
|
|
2875
|
+
if (year/4 == Math.floor(year/4)) { this.daysCount[1] = 29 } else { this.daysCount[1] = 28 }
|
|
2876
|
+
options.current = month + '/' + year
|
|
2877
|
+
|
|
2878
|
+
let weekDaysHeaderHTML = ''
|
|
2879
|
+
let st = w2utils.settings.weekStarts
|
|
2880
|
+
for (let i = 0; i < sdays.length; i++) {
|
|
2881
|
+
let isSat = (st == 'M' && i == 5) || (st != 'M' && i == 6) ? true : false
|
|
2882
|
+
let isSun = (st == 'M' && i == 6) || (st != 'M' && i == 0) ? true : false
|
|
2883
|
+
weekDaysHeaderHTML += `<div class="w2ui-day w2ui-weekday ${isSat ? 'w2ui-sunday' : ''} ${isSun ? 'w2ui-saturday' : ''}">${sdays[i]}</div>`
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
let html = `
|
|
2887
|
+
<div class="w2ui-cal-title">
|
|
2888
|
+
<div class="w2ui-cal-previous">
|
|
2889
|
+
<div></div>
|
|
2890
|
+
</div>
|
|
2891
|
+
<div class="w2ui-cal-next">
|
|
2892
|
+
<div></div>
|
|
2893
|
+
</div>
|
|
2894
|
+
${w2utils.settings.fullmonths[month-1]}, ${year}
|
|
2895
|
+
<span class="arrow-down"></span>
|
|
2896
|
+
</div>
|
|
2897
|
+
<div class="w2ui-cal-days">
|
|
2898
|
+
${weekDaysHeaderHTML}
|
|
2899
|
+
`
|
|
2900
|
+
|
|
2901
|
+
// start with the required date
|
|
2902
|
+
DT = new Date(year, month-1, 1)
|
|
2903
|
+
|
|
2904
|
+
/**
|
|
2905
|
+
* Move to noon, instead of midnight. If not, then the date when time saving happens
|
|
2906
|
+
* will be duplicated in the calendar
|
|
2907
|
+
*/
|
|
2908
|
+
DT = new Date(DT.getTime() + dayLengthMil * 0.5)
|
|
2909
|
+
|
|
2910
|
+
// calendar offset
|
|
2911
|
+
let weekDayOffset = DT.getDay()
|
|
2912
|
+
if (w2utils.settings.weekStarts == 'M') {
|
|
2913
|
+
// offset should be 1 day more, but not negative (Sunday)
|
|
2914
|
+
weekDayOffset = weekDayOffset > 0 ? weekDayOffset - 1 : 6
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
// apply the offset for the first day in the calendar
|
|
2918
|
+
DT = new Date(DT.getTime() - (weekDayOffset * dayLengthMil))
|
|
2919
|
+
|
|
2920
|
+
const DaySat = 6, DaySun = 0
|
|
2921
|
+
for (let ci = 0; ci < 42; ci++) {
|
|
2922
|
+
let className = []
|
|
2923
|
+
let dt = `${DT.getFullYear()}/${DT.getMonth()+1}/${DT.getDate()}`
|
|
2924
|
+
if (DT.getDay() === DaySat) className.push('w2ui-saturday')
|
|
2925
|
+
if (DT.getDay() === DaySun) className.push('w2ui-sunday')
|
|
2926
|
+
if (DT.getMonth() + 1 !== month) className.push('outside')
|
|
2927
|
+
if (dt == this.today) className.push('w2ui-today')
|
|
2928
|
+
|
|
2929
|
+
let dspDay = DT.getDate()
|
|
2930
|
+
let col = ''
|
|
2931
|
+
let bgcol = ''
|
|
2932
|
+
let tmp_dt, tmp_dt_fmt
|
|
2933
|
+
if (options.type === 'datetime') {
|
|
2934
|
+
tmp_dt = w2utils.formatDateTime(dt, options.format)
|
|
2935
|
+
tmp_dt_fmt = w2utils.formatDate(dt, w2utils.settings.dateFormat)
|
|
2936
|
+
} else {
|
|
2937
|
+
tmp_dt = w2utils.formatDate(dt, options.format)
|
|
2938
|
+
tmp_dt_fmt = tmp_dt
|
|
2939
|
+
}
|
|
2940
|
+
if (options.colored && options.colored[tmp_dt_fmt] !== undefined) { // if there is predefined colors for dates
|
|
2941
|
+
let tmp = options.colored[tmp_dt_fmt].split('|')
|
|
2942
|
+
bgcol = 'background-color: ' + tmp[0] + ';'
|
|
2943
|
+
col = 'color: ' + tmp[1] + ';'
|
|
2944
|
+
}
|
|
2945
|
+
html += `<div class="w2ui-day ${this.inRange(tmp_dt, options, true)
|
|
2946
|
+
? 'w2ui-date ' + (tmp_dt_fmt == selected_dsp ? 'w2ui-selected' : '')
|
|
2947
|
+
: 'w2ui-blocked'
|
|
2948
|
+
} ${className.join(' ')}"
|
|
2949
|
+
style="${col + bgcol}" date="${tmp_dt_fmt}" data-date="${DT.getTime()}">
|
|
2950
|
+
${dspDay}
|
|
2951
|
+
</div>`
|
|
2952
|
+
DT = new Date(DT.getTime() + dayLengthMil)
|
|
2953
|
+
}
|
|
2954
|
+
html += '</div>'
|
|
2955
|
+
if (options.btnNow) {
|
|
2956
|
+
let label = w2utils.lang('Today' + (options.type == 'datetime' ? ' & Now' : ''))
|
|
2957
|
+
html += `<div class="w2ui-cal-now">${label}</div>`
|
|
2958
|
+
}
|
|
2959
|
+
return { html, month, year }
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
getYearHTML() {
|
|
2963
|
+
let mhtml = ''
|
|
2964
|
+
let yhtml = ''
|
|
2965
|
+
for (let m = 0; m < w2utils.settings.fullmonths.length; m++) {
|
|
2966
|
+
mhtml += `<div class="w2ui-jump-month" name="${m+1}">${w2utils.settings.shortmonths[m]}</div>`
|
|
2967
|
+
}
|
|
2968
|
+
for (let y = w2utils.settings.dateStartYear; y <= w2utils.settings.dateEndYear; y++) {
|
|
2969
|
+
yhtml += `<div class="w2ui-jump-year" name="${y}">${y}</div>`
|
|
2970
|
+
}
|
|
2971
|
+
return `<div class="w2ui-cal-jump">
|
|
2972
|
+
<div id="w2ui-jump-month">${mhtml}</div>
|
|
2973
|
+
<div id="w2ui-jump-year">${yhtml}</div>
|
|
2974
|
+
</div>`
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
getHourHTML(options) {
|
|
2978
|
+
options = options ?? {}
|
|
2979
|
+
if (!options.format) options.format = w2utils.settings.timeFormat
|
|
2980
|
+
let h24 = (options.format.indexOf('h24') > -1)
|
|
2981
|
+
let value = options.value ? options.value : (options.anchor ? options.anchor.value : '')
|
|
2982
|
+
|
|
2983
|
+
let tmp = []
|
|
2984
|
+
for (let a = 0; a < 24; a++) {
|
|
2985
|
+
let time = (a >= 12 && !h24 ? a - 12 : a) + ':00' + (!h24 ? (a < 12 ? ' am' : ' pm') : '')
|
|
2986
|
+
if (a == 12 && !h24) time = '12:00 pm'
|
|
2987
|
+
if (!tmp[Math.floor(a/8)]) tmp[Math.floor(a/8)] = ''
|
|
2988
|
+
let tm1 = this.min2str(this.str2min(time))
|
|
2989
|
+
let tm2 = this.min2str(this.str2min(time) + 59)
|
|
2990
|
+
if (options.type === 'datetime') {
|
|
2991
|
+
let dt = w2utils.isDateTime(value, options.format, true)
|
|
2992
|
+
let fm = options.format.split('|')[0].trim()
|
|
2993
|
+
tm1 = w2utils.formatDate(dt, fm) + ' ' + tm1
|
|
2994
|
+
tm2 = w2utils.formatDate(dt, fm) + ' ' + tm2
|
|
2995
|
+
}
|
|
2996
|
+
let valid = this.inRange(tm1, options) || this.inRange(tm2, options)
|
|
2997
|
+
tmp[Math.floor(a/8)] += `<span hour="${a}"
|
|
2998
|
+
class="hour ${valid ? 'w2ui-time ' : 'w2ui-blocked'}">${time}</span>`
|
|
2999
|
+
}
|
|
3000
|
+
let html = `<div class="w2ui-calendar">
|
|
3001
|
+
<div class="w2ui-time-title">${w2utils.lang('Select Hour')}</div>
|
|
3002
|
+
<div class="w2ui-cal-time">
|
|
3003
|
+
<div class="w2ui-cal-column">${tmp[0]}</div>
|
|
3004
|
+
<div class="w2ui-cal-column">${tmp[1]}</div>
|
|
3005
|
+
<div class="w2ui-cal-column">${tmp[2]}</div>
|
|
3006
|
+
</div>
|
|
3007
|
+
${options.btnNow ? `<div class="w2ui-cal-now">${w2utils.lang('Now')}</div>` : '' }
|
|
3008
|
+
</div>`
|
|
3009
|
+
return { html }
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
getMinHTML(hour, options) {
|
|
3013
|
+
if (hour == null) hour = 0
|
|
3014
|
+
options = options ?? {}
|
|
3015
|
+
if (!options.format) options.format = w2utils.settings.timeFormat
|
|
3016
|
+
let h24 = (options.format.indexOf('h24') > -1)
|
|
3017
|
+
let value = options.value ? options.value : (options.anchor ? options.anchor.value : '')
|
|
3018
|
+
|
|
3019
|
+
let tmp = []
|
|
3020
|
+
for (let a = 0; a < 60; a += 5) {
|
|
3021
|
+
let time = (hour > 12 && !h24 ? hour - 12 : hour) + ':' + (a < 10 ? 0 : '') + a + ' ' + (!h24 ? (hour < 12 ? 'am' : 'pm') : '')
|
|
3022
|
+
let tm = time
|
|
3023
|
+
let ind = a < 20 ? 0 : (a < 40 ? 1 : 2)
|
|
3024
|
+
if (!tmp[ind]) tmp[ind] = ''
|
|
3025
|
+
if (options.type === 'datetime') {
|
|
3026
|
+
let dt = w2utils.isDateTime(value, options.format, true)
|
|
3027
|
+
let fm = options.format.split('|')[0].trim()
|
|
3028
|
+
tm = w2utils.formatDate(dt, fm) + ' ' + tm
|
|
3029
|
+
}
|
|
3030
|
+
tmp[ind] += `<span min="${a}" class="min ${(this.inRange(tm, options) ? 'w2ui-time ' : 'w2ui-blocked')}">${time}</span>`
|
|
3031
|
+
}
|
|
3032
|
+
let html = `<div class="w2ui-calendar">
|
|
3033
|
+
<div class="w2ui-time-title">${w2utils.lang('Select Minute')}</div>
|
|
3034
|
+
<div class="w2ui-cal-time">
|
|
3035
|
+
<div class="w2ui-cal-column">${tmp[0]}</div>
|
|
3036
|
+
<div class="w2ui-cal-column">${tmp[1]}</div>
|
|
3037
|
+
<div class="w2ui-cal-column">${tmp[2]}</div>
|
|
3038
|
+
</div>
|
|
3039
|
+
${options.btnNow ? `<div class="w2ui-cal-now">${w2utils.lang('Now')}</div>` : '' }
|
|
3040
|
+
</div>`
|
|
3041
|
+
return { html }
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
// checks if date is in range (loost at start, end, blockDates, blockWeekdays)
|
|
3045
|
+
inRange(str, options, dateOnly) {
|
|
3046
|
+
let inRange = false
|
|
3047
|
+
if (options.type === 'date') {
|
|
3048
|
+
let dt = w2utils.isDate(str, options.format, true)
|
|
3049
|
+
if (dt) {
|
|
3050
|
+
// enable range
|
|
3051
|
+
if (options.start || options.end) {
|
|
3052
|
+
let st = (typeof options.start === 'string' ? options.start : query(options.start).val())
|
|
3053
|
+
let en = (typeof options.end === 'string' ? options.end : query(options.end).val())
|
|
3054
|
+
let start = w2utils.isDate(st, options.format, true)
|
|
3055
|
+
let end = w2utils.isDate(en, options.format, true)
|
|
3056
|
+
let current = new Date(dt)
|
|
3057
|
+
if (!start) start = current
|
|
3058
|
+
if (!end) end = current
|
|
3059
|
+
if (current >= start && current <= end) inRange = true
|
|
3060
|
+
} else {
|
|
3061
|
+
inRange = true
|
|
3062
|
+
}
|
|
3063
|
+
// block predefined dates
|
|
3064
|
+
if (Array.isArray(options.blockDates) && options.blockDates.includes(str)) inRange = false
|
|
3065
|
+
// block weekdays
|
|
3066
|
+
if (Array.isArray(options.blockWeekdays) && options.blockWeekdays.includes(dt.getDay())) inRange = false
|
|
3067
|
+
}
|
|
3068
|
+
} else if (options.type === 'time') {
|
|
3069
|
+
if (options.start || options.end) {
|
|
3070
|
+
let tm = this.str2min(str)
|
|
3071
|
+
let tm1 = this.str2min(options.start)
|
|
3072
|
+
let tm2 = this.str2min(options.end)
|
|
3073
|
+
if (!tm1) tm1 = tm
|
|
3074
|
+
if (!tm2) tm2 = tm
|
|
3075
|
+
if (tm >= tm1 && tm <= tm2) inRange = true
|
|
3076
|
+
} else {
|
|
3077
|
+
inRange = true
|
|
3078
|
+
}
|
|
3079
|
+
} else if (options.type === 'datetime') {
|
|
3080
|
+
let dt = w2utils.isDateTime(str, options.format, true)
|
|
3081
|
+
if (dt) {
|
|
3082
|
+
let format = options.format.split('|').map(format => format.trim())
|
|
3083
|
+
if (dateOnly) {
|
|
3084
|
+
let date = w2utils.formatDate(dt, format[0])
|
|
3085
|
+
let opts = w2utils.extend({}, options, { type: 'date', format: format[0] })
|
|
3086
|
+
if (this.inRange(date, opts)) inRange = true
|
|
3087
|
+
} else {
|
|
3088
|
+
let time = w2utils.formatTime(dt, format[1])
|
|
3089
|
+
let opts = { type: 'time', format: format[1], start: options.startTime, end: options.endTime }
|
|
3090
|
+
if (this.inRange(time, opts)) inRange = true
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
return inRange
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
// converts time into number of minutes since midnight -- '11:50am' => 710
|
|
3098
|
+
str2min(str) {
|
|
3099
|
+
if (typeof str !== 'string') return null
|
|
3100
|
+
let tmp = str.split(':')
|
|
3101
|
+
if (tmp.length === 2) {
|
|
3102
|
+
tmp[0] = parseInt(tmp[0])
|
|
3103
|
+
tmp[1] = parseInt(tmp[1])
|
|
3104
|
+
if (str.indexOf('pm') !== -1 && tmp[0] !== 12) tmp[0] += 12
|
|
3105
|
+
if (str.includes('am') && tmp[0] == 12) tmp[0] = 0 // 12:00am - is midnight
|
|
3106
|
+
} else {
|
|
3107
|
+
return null
|
|
3108
|
+
}
|
|
3109
|
+
return tmp[0] * 60 + tmp[1]
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
// converts minutes since midnight into time str -- 710 => '11:50am'
|
|
3113
|
+
min2str(time, format) {
|
|
3114
|
+
let ret = ''
|
|
3115
|
+
if (time >= 24 * 60) time = time % (24 * 60)
|
|
3116
|
+
if (time < 0) time = 24 * 60 + time
|
|
3117
|
+
let hour = Math.floor(time/60)
|
|
3118
|
+
let min = ((time % 60) < 10 ? '0' : '') + (time % 60)
|
|
3119
|
+
if (!format) { format = w2utils.settings.timeFormat}
|
|
3120
|
+
if (format.indexOf('h24') !== -1) {
|
|
3121
|
+
ret = hour + ':' + min
|
|
3122
|
+
} else {
|
|
3123
|
+
ret = (hour <= 12 ? hour : hour - 12) + ':' + min + ' ' + (hour >= 12 ? 'pm' : 'am')
|
|
3124
|
+
}
|
|
3125
|
+
return ret
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
let w2tooltip = new Tooltip()
|
|
3130
|
+
let w2menu = new MenuTooltip()
|
|
3131
|
+
let w2color = new ColorTooltip()
|
|
3132
|
+
let w2date = new DateTooltip()
|
|
3133
|
+
|
|
3134
|
+
export { w2tooltip, w2color, w2menu, w2date, Tooltip }
|