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.
Files changed (182) hide show
  1. package/README.md +169 -0
  2. package/index.js +3 -0
  3. package/package.json +27 -0
  4. package/sample.js +37 -0
  5. package/scripts/airbnb.js +73 -0
  6. package/scripts/airbnb2.js +74 -0
  7. package/scripts/airbnb2_queries.js +231 -0
  8. package/scripts/airbnb_queries.js +264 -0
  9. package/scripts/downloadFile.js +42 -0
  10. package/scripts/express.js +46 -0
  11. package/scripts/fastify.js +79 -0
  12. package/scripts/flights.js +75 -0
  13. package/scripts/flights2.js +81 -0
  14. package/scripts/flights2_queries.js +453 -0
  15. package/scripts/flights_queries.js +403 -0
  16. package/src/QyAcl.js +252 -0
  17. package/src/QyAclSaver.js +14 -0
  18. package/src/QyAclSaver_Worker.js +29 -0
  19. package/src/QyAclWriter.js +19 -0
  20. package/src/QyBatch.js +501 -0
  21. package/src/QyBinWriter.js +140 -0
  22. package/src/QyCD.js +61 -0
  23. package/src/QyCache.js +212 -0
  24. package/src/QyCompressor.js +60 -0
  25. package/src/QyCompressor_Worker.js +13 -0
  26. package/src/QyDB.js +122 -0
  27. package/src/QyDefaultOptions.js +59 -0
  28. package/src/QyDir.js +192 -0
  29. package/src/QyFetch.js +38 -0
  30. package/src/QyFile.js +124 -0
  31. package/src/QyFileContentUtils.js +175 -0
  32. package/src/QyFileLogger.js +58 -0
  33. package/src/QyIndex.js +237 -0
  34. package/src/QyIndexPropTree.js +92 -0
  35. package/src/QyIntersectNode.js +48 -0
  36. package/src/QyItem.js +105 -0
  37. package/src/QyJsonSaver.js +20 -0
  38. package/src/QyKVData.js +289 -0
  39. package/src/QyKVDataCleaner.js +17 -0
  40. package/src/QyKVDataCleaner_Worker.js +94 -0
  41. package/src/QyKVDataLoader.js +65 -0
  42. package/src/QyKVDataSaver.js +17 -0
  43. package/src/QyKVDataSaver_Worker.js +81 -0
  44. package/src/QyLogFileSaver.js +14 -0
  45. package/src/QyLogFileSaver_Worker.js +26 -0
  46. package/src/QyLogger.js +76 -0
  47. package/src/QyMessageWorker.js +41 -0
  48. package/src/QyMessager.js +84 -0
  49. package/src/QyMessager_Run.js +13 -0
  50. package/src/QyMod.js +15 -0
  51. package/src/QyOpRunner.js +73 -0
  52. package/src/QyQueue.js +60 -0
  53. package/src/QySaver.js +83 -0
  54. package/src/QySnapshotCompactor.js +17 -0
  55. package/src/QySnapshotCompactor_Worker.js +151 -0
  56. package/src/QySnapshotSaver.js +17 -0
  57. package/src/QySnapshotSaver_Worker.js +164 -0
  58. package/src/QySnapshots.js +354 -0
  59. package/src/QyStructure.js +50 -0
  60. package/src/QyTrigger.js +133 -0
  61. package/src/QyUnionNode.js +105 -0
  62. package/src/QyUtils.js +250 -0
  63. package/src/QyWorker.js +33 -0
  64. package/src/hackernews.js +93 -0
  65. package/src/rpc/evalCode.js +9 -0
  66. package/src/rpc/getFileContent.js +16 -0
  67. package/src/rpc/getSubItems.js +32 -0
  68. package/src/rpc/queryFiles.js +33 -0
  69. package/src/rpc/reloadRpc.js +1 -0
  70. package/src/simulate.js +114 -0
  71. package/src/simulate2.js +110 -0
  72. package/src/soc_pokec.js +43 -0
  73. package/static/bootstrap-5.3.8-dist/css/bootstrap.min.css +6 -0
  74. package/static/bootstrap-5.3.8-dist/js/bootstrap.bundle.js +6312 -0
  75. package/static/datafolderui/QyStyledElement.js +30 -0
  76. package/static/datafolderui/QyUIFolderTree.js +203 -0
  77. package/static/datafolderui/QyUIUtils.js +42 -0
  78. package/static/datafolderui/display-tree.js +108 -0
  79. package/static/datafolderui/explore-tree.js +122 -0
  80. package/static/datafolderui/folder-tree.js +5 -0
  81. package/static/datafolderui/hscrpt.js +25 -0
  82. package/static/datafolderui/index.css +6 -0
  83. package/static/datafolderui/index.html +20 -0
  84. package/static/datafolderui/json-view.js +74 -0
  85. package/static/datafolderui/kv-input.js +66 -0
  86. package/static/datafolderui/list-input.js +52 -0
  87. package/static/datafolderui/main-page.js +98 -0
  88. package/static/datafolderui/num-input.js +27 -0
  89. package/static/datafolderui/query-editor.js +229 -0
  90. package/static/datafolderui/querySelectorShadowRoot.js +24 -0
  91. package/static/datafolderui/resizable-input.js +67 -0
  92. package/static/favicon.ico +0 -0
  93. package/static/icons-1.13.1/font/bootstrap-icons.min.css +5 -0
  94. package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff +0 -0
  95. package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
  96. package/static/json-view/dist/jsonview.js +433 -0
  97. package/static/tree.js/tree.css +106 -0
  98. package/static/tree.js/tree.js +374 -0
  99. package/static/w2ui/dist/w2ui.min.css +2 -0
  100. package/static/w2ui/src/less/icons/icons.json +1 -0
  101. package/static/w2ui/src/less/icons/preview.html +33 -0
  102. package/static/w2ui/src/less/icons/readme.md +15 -0
  103. package/static/w2ui/src/less/icons/svg/box.svg +3 -0
  104. package/static/w2ui/src/less/icons/svg/check.svg +3 -0
  105. package/static/w2ui/src/less/icons/svg/colors.svg +3 -0
  106. package/static/w2ui/src/less/icons/svg/columns.svg +5 -0
  107. package/static/w2ui/src/less/icons/svg/cross.svg +4 -0
  108. package/static/w2ui/src/less/icons/svg/drop.svg +6 -0
  109. package/static/w2ui/src/less/icons/svg/empty.svg +3 -0
  110. package/static/w2ui/src/less/icons/svg/eye-dropper.svg +4 -0
  111. package/static/w2ui/src/less/icons/svg/info.svg +8 -0
  112. package/static/w2ui/src/less/icons/svg/paste.svg +4 -0
  113. package/static/w2ui/src/less/icons/svg/pencil.svg +5 -0
  114. package/static/w2ui/src/less/icons/svg/plus.svg +3 -0
  115. package/static/w2ui/src/less/icons/svg/reload.svg +5 -0
  116. package/static/w2ui/src/less/icons/svg/search.svg +3 -0
  117. package/static/w2ui/src/less/icons/svg/settings.svg +8 -0
  118. package/static/w2ui/src/less/icons/w2ui-font.css +36 -0
  119. package/static/w2ui/src/less/icons/w2ui-font.woff +0 -0
  120. package/static/w2ui/src/less/src/buttons.less +184 -0
  121. package/static/w2ui/src/less/src/common.less +541 -0
  122. package/static/w2ui/src/less/src/fields.less +302 -0
  123. package/static/w2ui/src/less/src/form.less +319 -0
  124. package/static/w2ui/src/less/src/grid.less +1057 -0
  125. package/static/w2ui/src/less/src/icons.less +37 -0
  126. package/static/w2ui/src/less/src/layout.less +86 -0
  127. package/static/w2ui/src/less/src/mixins.less +87 -0
  128. package/static/w2ui/src/less/src/popup.less +277 -0
  129. package/static/w2ui/src/less/src/sidebar.less +343 -0
  130. package/static/w2ui/src/less/src/tabs.less +145 -0
  131. package/static/w2ui/src/less/src/toolbar.less +219 -0
  132. package/static/w2ui/src/less/src/tooltip.less +948 -0
  133. package/static/w2ui/src/less/w2ui.less +196 -0
  134. package/static/w2ui/src/locale/az-az.json +114 -0
  135. package/static/w2ui/src/locale/ba-ba.json +114 -0
  136. package/static/w2ui/src/locale/bg-bg.json +114 -0
  137. package/static/w2ui/src/locale/ca-es.json +114 -0
  138. package/static/w2ui/src/locale/da-dk.json +114 -0
  139. package/static/w2ui/src/locale/de-de.json +114 -0
  140. package/static/w2ui/src/locale/en-gb.json +17 -0
  141. package/static/w2ui/src/locale/en-us.json +17 -0
  142. package/static/w2ui/src/locale/es-ar.json +115 -0
  143. package/static/w2ui/src/locale/es-cr.json +119 -0
  144. package/static/w2ui/src/locale/es-es.json +114 -0
  145. package/static/w2ui/src/locale/es-mx.json +114 -0
  146. package/static/w2ui/src/locale/fr-fr.json +114 -0
  147. package/static/w2ui/src/locale/gl-es.json +114 -0
  148. package/static/w2ui/src/locale/hr-hr.json +114 -0
  149. package/static/w2ui/src/locale/hu-hu.json +114 -0
  150. package/static/w2ui/src/locale/id-id.json +114 -0
  151. package/static/w2ui/src/locale/it-it.json +114 -0
  152. package/static/w2ui/src/locale/ja-jp.json +114 -0
  153. package/static/w2ui/src/locale/kk-kz.json +116 -0
  154. package/static/w2ui/src/locale/ko-kr.json +114 -0
  155. package/static/w2ui/src/locale/lt-lt.json +114 -0
  156. package/static/w2ui/src/locale/nl-nl.json +114 -0
  157. package/static/w2ui/src/locale/no-no.json +114 -0
  158. package/static/w2ui/src/locale/pl-pl.json +114 -0
  159. package/static/w2ui/src/locale/pt-br.json +114 -0
  160. package/static/w2ui/src/locale/ru-ru.json +116 -0
  161. package/static/w2ui/src/locale/sk-sk.json +114 -0
  162. package/static/w2ui/src/locale/sl-si.json +116 -0
  163. package/static/w2ui/src/locale/sr-rs.json +114 -0
  164. package/static/w2ui/src/locale/tr-tr.json +114 -0
  165. package/static/w2ui/src/locale/uk-ua.json +114 -0
  166. package/static/w2ui/src/locale/zh-cn.json +114 -0
  167. package/static/w2ui/src/locale/zh-tw.json +114 -0
  168. package/static/w2ui/src/query.js +583 -0
  169. package/static/w2ui/src/readme.md +11 -0
  170. package/static/w2ui/src/w2base.js +311 -0
  171. package/static/w2ui/src/w2compat.js +207 -0
  172. package/static/w2ui/src/w2field.js +1854 -0
  173. package/static/w2ui/src/w2form.js +2411 -0
  174. package/static/w2ui/src/w2grid.js +8916 -0
  175. package/static/w2ui/src/w2layout.js +1194 -0
  176. package/static/w2ui/src/w2locale.js +129 -0
  177. package/static/w2ui/src/w2popup.js +909 -0
  178. package/static/w2ui/src/w2sidebar.js +1769 -0
  179. package/static/w2ui/src/w2tabs.js +669 -0
  180. package/static/w2ui/src/w2toolbar.js +1180 -0
  181. package/static/w2ui/src/w2tooltip.js +3134 -0
  182. package/static/w2ui/src/w2utils.js +2457 -0
@@ -0,0 +1,1180 @@
1
+ /**
2
+ * Part of w2ui 2.0 library
3
+ * - Dependencies: mQuery, w2utils, w2base, w2tooltip, w2color, w2menu
4
+ *
5
+ * == TODO ==
6
+ * - tab navigation (index state)
7
+ * - vertical toolbar
8
+ * - w2menu on second click of tb button should hide
9
+ * - button display groups for each show/hide, possibly add state: { single: t/f, multiple: t/f, type: 'font' }
10
+ * - item.count - should just support html, so a custom block can be created, such as a colored line
11
+ *
12
+ * == 2.0 changes
13
+ * - CSP - fixed inline events
14
+ * - removed jQuery dependency
15
+ * - item.icon - can be class or <custom-icon-component> or <svg>
16
+ * - new w2tooltips and w2menu
17
+ * - scroll returns promise
18
+ * - added onMouseEntter, onMouseLeave, onMouseDown, onMouseUp events
19
+ * - add(..., skipRefresh), insert(..., skipRefresh)
20
+ * - item.items can be a function
21
+ * - item.icon_style - style for the icon
22
+ * - item.icon - can be a function
23
+ * - item.type = 'label', item.type = 'input'
24
+ * - item.placeholder
25
+ * - item.input: { spinner, style, min, max, step, precision, suffix }
26
+ * - item.backColor
27
+ * - onLiveUpdate - for colors
28
+ */
29
+
30
+ import { w2base } from './w2base.js'
31
+ import { w2ui, w2utils } from './w2utils.js'
32
+ import { query } from './query.js'
33
+ import { w2tooltip, w2color, w2menu } from './w2tooltip.js'
34
+
35
+ class w2toolbar extends w2base {
36
+ constructor(options) {
37
+ super(options.name)
38
+ this.box = null // DOM Element that holds the element
39
+ this.name = null // unique name for w2ui
40
+ this.routeData = {} // data for dynamic routes
41
+ this.items = []
42
+ this.right = '' // HTML text on the right of toolbar
43
+ this.tooltip = 'top|left'// can be top, bottom, left, right
44
+ this.onClick = null
45
+ this.onChange = null
46
+ this.onMouseDown = null
47
+ this.onMouseUp = null
48
+ this.onMouseEnter = null // mouse enter the button event
49
+ this.onMouseLeave = null
50
+ this.onRender = null
51
+ this.onRefresh = null
52
+ this.onResize = null
53
+ this.onDestroy = null
54
+ this.onLiveUpdate = null
55
+ this.item_template = {
56
+ id: null, // command to be sent to all event handlers
57
+ type: 'button', // button, check, radio, drop, menu, menu-radio, menu-check, break, html, label, input spacer
58
+ text: null,
59
+ html: '',
60
+ tooltip: null, // w2toolbar.tooltip should be
61
+ count: null,
62
+ hidden: false,
63
+ disabled: false,
64
+ checked: false, // used for radio buttons
65
+ icon: null,
66
+ route: null, // if not null, it is route to go
67
+ arrow: null, // arrow down for drop/menu types
68
+ style: null, // extra css style for caption
69
+ group: null, // used for radio buttons
70
+ items: null, // for type menu* it is an array of items in the menu
71
+ selected: null, // used for menu-check, menu-radio
72
+ color: null, // color value - used in color pickers
73
+ backColor: null, // background color value for color pickter
74
+ overlay: { // additional options for overlay
75
+ anchorClass: ''
76
+ },
77
+ onClick: null,
78
+ onRefresh: null
79
+ }
80
+ this.last = {
81
+ badge: {},
82
+ pendingRefresh: {} // what should be refreshed with a debounce
83
+ }
84
+ /**
85
+ * This _refresh function is needed for speed. It will store what should be refreshed in this.last.refesh
86
+ * obect and then call _refreshDebounced(), which will do it withing 15 ms. However, if new items are added
87
+ * they will not cause multiple unnecessary refreshes
88
+ */
89
+ this._refresh = ({ effected, resize, refreshTooltip }) => {
90
+ let options = this.last.pendingRefresh
91
+ options.ids ??= []
92
+ options.ids.push(...effected)
93
+ Object.assign(options, { resize, refreshTooltip })
94
+ this._refreshDebounced()
95
+ }
96
+ this._refreshDebounced = w2utils.debounce(() => {
97
+ let options = this.last.pendingRefresh
98
+ // new Set will make array unique
99
+ new Set(options.ids).forEach(id => {
100
+ this.refresh(id)
101
+ if (options.hideTooltip) this.tooltipHide(id)
102
+ })
103
+ if (options.resize) this.resize()
104
+ // once refresh is complete, then clear refresh object
105
+ this.last.pendingRefresh = {}
106
+ }, 15)
107
+ // mix in options, w/o items
108
+ let items = options.items
109
+ delete options.items
110
+ Object.assign(this, options)
111
+ // add item via method to makes sure item_template is applied
112
+ if (Array.isArray(items)) this.add(items, true)
113
+ // need to reassign back to keep it in config
114
+ options.items = items
115
+
116
+ // render if box specified
117
+ if (typeof this.box == 'string') this.box = query(this.box).get(0)
118
+ if (this.box) this.render(this.box)
119
+ }
120
+
121
+ add(items, skipRefresh) {
122
+ this.insert(null, items, skipRefresh)
123
+ }
124
+
125
+ insert(id, items, skipRefresh) {
126
+ if (!Array.isArray(items)) items = [items]
127
+ items.forEach((item, idx, arr) => {
128
+ if (typeof item === 'string') {
129
+ item = arr[idx] = { id: item, text: item }
130
+ }
131
+ // checks
132
+ let valid = ['button', 'check', 'radio', 'drop', 'menu', 'menu-radio', 'menu-check', 'color', 'text-color', 'html', 'label', 'input',
133
+ 'group', 'break', 'spacer', 'new-line']
134
+ if (!valid.includes(String(item.type))) {
135
+ console.log('ERROR: The parameter "type" should be one of the following:', valid, `, but ${item.type} is supplied.`, item)
136
+ return
137
+ }
138
+ if (item.id == null && !['break', 'spacer', 'new-line'].includes(item.type)) {
139
+ console.log('ERROR: The parameter "id" is required but not supplied.', item)
140
+ return
141
+ }
142
+ if (item.type == null) {
143
+ console.log('ERROR: The parameter "type" is required but not supplied.', item)
144
+ return
145
+ }
146
+ if (!w2utils.checkUniqueId(item.id, this.items, 'toolbar', this.name)) return
147
+ // add item
148
+ let newItem = w2utils.extend({}, this.item_template, item)
149
+ if (newItem.type == 'group' && Array.isArray(newItem.items)) {
150
+ newItem.items.forEach((it, ind) => {
151
+ newItem.items[ind] = w2utils.extend({}, this.item_template, newItem.items[ind])
152
+ })
153
+ }
154
+ if (newItem.type == 'menu-check') {
155
+ if (!Array.isArray(newItem.selected)) newItem.selected = []
156
+ if (Array.isArray(newItem.items)) {
157
+ newItem.items.forEach(it => {
158
+ if (typeof it === 'string') {
159
+ it = arr[idx] = { id: it, text: it }
160
+ }
161
+ if (it.checked && !newItem.selected.includes(it.id)) newItem.selected.push(it.id)
162
+ if (!it.checked && newItem.selected.includes(it.id)) it.checked = true
163
+ if (it.checked == null) it.checked = false
164
+ })
165
+ }
166
+ } else if (newItem.type == 'menu-radio') {
167
+ if (Array.isArray(newItem.items)) {
168
+ newItem.items.forEach((it, idx, arr) => {
169
+ if (typeof it === 'string') {
170
+ it = arr[idx] = { id: it, text: it }
171
+ }
172
+ if (it.checked && newItem.selected == null) newItem.selected = it.id; else it.checked = false
173
+ if (!it.checked && newItem.selected == it.id) it.checked = true
174
+ if (it.checked == null) it.checked = false
175
+ })
176
+ }
177
+ }
178
+ if (id == null) {
179
+ this.items.push(newItem)
180
+ } else {
181
+ let middle = this.get(id, true)
182
+ this.items = this.items.slice(0, middle).concat([newItem], this.items.slice(middle))
183
+ }
184
+ newItem.line = newItem.line ?? 1
185
+ if (skipRefresh !== true) this.refresh(newItem.id)
186
+ })
187
+ if (skipRefresh !== true) this.resize()
188
+ }
189
+
190
+ remove() {
191
+ let effected = 0
192
+ Array.from(arguments).forEach(item => {
193
+ let it = this.get(item)
194
+ if (!it || String(item).indexOf(':') != -1) return
195
+ effected++
196
+ // remove from screen
197
+ query(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id)).remove()
198
+ // remove from array
199
+ let ind = this.get(it.id, true)
200
+ if (ind != null) this.items.splice(ind, 1)
201
+ })
202
+ this.resize()
203
+ return effected
204
+ }
205
+
206
+ set(id, newOptions) {
207
+ let item = this.get(id)
208
+ if (item == null) return false
209
+ Object.assign(item, newOptions)
210
+ this.refresh(String(id).split(':')[0])
211
+ return true
212
+ }
213
+
214
+ get(id, returnIndex, items) {
215
+ if (arguments.length === 0) {
216
+ let all = []
217
+ for (let i1 = 0; i1 < this.items.length; i1++) {
218
+ let it = this.items[i1]
219
+ if (it.id != null) all.push(it.id)
220
+ if (it.type == 'group') {
221
+ for (let i2 = 0; i2 < it.items.length; i2++) {
222
+ if (it.items[i2].id != null) all.push(it.items[i2].id)
223
+ }
224
+ }
225
+ }
226
+ return all
227
+ }
228
+ let tmp = String(id).split(':')
229
+ if (items == null) items = this.items
230
+ for (let i1 = 0; i1 < items.length; i1++) {
231
+ let it = items[i1]
232
+ // find a menu item
233
+ if (['menu', 'menu-radio', 'menu-check'].includes(it.type) && tmp.length == 2 && it.id == tmp[0]) {
234
+ let subItems = it.items
235
+ if (typeof subItems == 'function') subItems = subItems(this)
236
+ for (let i = 0; i < subItems.length; i++) {
237
+ let item = subItems[i]
238
+ if (item.id == tmp[1] || (item.id == null && item.text == tmp[1])) {
239
+ if (returnIndex == true) return i; else return item
240
+ }
241
+ if (Array.isArray(item.items)) {
242
+ for (let j = 0; j < item.items.length; j++) {
243
+ if (item.items[j].id == tmp[1] || (item.items[j].id == null && item.items[j].text == tmp[1])) {
244
+ if (returnIndex == true) return i; else return item.items[j]
245
+ }
246
+ }
247
+ }
248
+ }
249
+ } else if (it.id == tmp[0]) {
250
+ if (returnIndex == true) return i1; else return it
251
+ } else if (it.type == 'group') {
252
+ let sub = this.get(id, returnIndex, it.items)
253
+ if (sub != null) return sub
254
+ }
255
+ }
256
+ return null
257
+ }
258
+
259
+ setCount(id, count, className, style) {
260
+ let btn = query(this.box).find(`#tb_${this.name}_item_${w2utils.escapeId(id)} .w2ui-tb-count > span`)
261
+ if (btn.length > 0) {
262
+ btn.removeClass()
263
+ .addClass(className ?? '')
264
+ .text(count)
265
+ .get(0).style.cssText = style ?? ''
266
+ this.last.badge[id] = {
267
+ className: className ?? '',
268
+ style: style ?? ''
269
+ }
270
+ let item = this.get(id)
271
+ item.count = count
272
+ } else {
273
+ this.set(id, { count: count })
274
+ this.setCount(...arguments) // to update styles
275
+ }
276
+ }
277
+
278
+ show() {
279
+ let effected = []
280
+ Array.from(arguments).forEach(item => {
281
+ let it = this.get(item)
282
+ if (!it) return
283
+ // since group can have style, it should still be shown
284
+ it.hidden = false
285
+ effected.push(String(item).split(':')[0])
286
+ if (it.type == 'group') {
287
+ it.items.forEach(itm => this.show(itm.id))
288
+ }
289
+ })
290
+ this._refresh({ effected, resize: true }) // debounced, needed for speed
291
+ return effected
292
+ }
293
+
294
+ hide() {
295
+ let effected = []
296
+ Array.from(arguments).forEach(item => {
297
+ let it = this.get(item)
298
+ if (!it) return
299
+ // since group can have style, it should still be hidden
300
+ it.hidden = true
301
+ effected.push(String(item).split(':')[0])
302
+ if (it.type == 'group') {
303
+ it.items.forEach(itm => this.hide(itm.id))
304
+ }
305
+ })
306
+ this._refresh({ effected, hideTooltip: true, resize: true }) // debounced, needed for speed
307
+ return effected
308
+ }
309
+
310
+ enable() {
311
+ let effected = []
312
+ Array.from(arguments).forEach(item => {
313
+ let it = this.get(item)
314
+ if (!it) return
315
+ if (it.type == 'group') {
316
+ it.items.forEach(itm => this.enable(itm.id))
317
+ } else {
318
+ it.disabled = false
319
+ effected.push(String(item).split(':')[0])
320
+ }
321
+ })
322
+ this._refresh({ effected }) // debounced, needed for speed
323
+ return effected
324
+ }
325
+
326
+ disable() {
327
+ let effected = []
328
+ Array.from(arguments).forEach(item => {
329
+ let it = this.get(item)
330
+ if (!it) return
331
+ if (it.type == 'group') {
332
+ it.items.forEach(itm => this.disable(itm.id))
333
+ } else {
334
+ it.disabled = true
335
+ effected.push(String(item).split(':')[0])
336
+ }
337
+ })
338
+ this._refresh({ effected, hideTooltip: true }) // debounced, needed for speed
339
+ return effected
340
+ }
341
+
342
+ check() {
343
+ let effected = []
344
+ Array.from(arguments).forEach(item => {
345
+ let it = this.get(item)
346
+ if (!it || String(item).indexOf(':') != -1) return
347
+ if (it.type == 'group') {
348
+ it.items.forEach(itm => this.check(itm.id))
349
+ } else {
350
+ it.checked = true
351
+ effected.push(String(item).split(':')[0])
352
+ }
353
+ })
354
+ this._refresh({ effected }) // debounced, needed for speed
355
+ return effected
356
+ }
357
+
358
+ uncheck() {
359
+ let effected = []
360
+ Array.from(arguments).forEach(item => {
361
+ let it = this.get(item)
362
+ if (!it || String(item).indexOf(':') != -1) return
363
+ // remove overlay
364
+ if (['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(it.type) && it.checked) {
365
+ w2tooltip.hide(this.name + '-drop')
366
+ }
367
+ if (it.type == 'group') {
368
+ it.items.forEach(itm => this.uncheck(itm.id))
369
+ } else {
370
+ it.checked = false
371
+ effected.push(String(item).split(':')[0])
372
+ }
373
+ })
374
+ this._refresh({ effected }) // debounced, needed for speed
375
+ return effected
376
+ }
377
+
378
+ click(id, event) {
379
+ // click on menu items
380
+ let tmp = String(id).split(':')
381
+ let it = this.get(tmp[0])
382
+ let items = (it && it.items ? w2utils.normMenu.call(this, it.items, it) : [])
383
+
384
+ if (tmp.length > 1) {
385
+ let subItem = this.get(id)
386
+ if (subItem && !subItem.disabled) {
387
+ this.menuClick({ name: this.name, item: it, subItem: subItem, originalEvent: event })
388
+ }
389
+ return
390
+ }
391
+ if (it && !it.disabled) {
392
+ // event before
393
+ let edata = this.trigger('click', {
394
+ target: (id != null ? id : this.name),
395
+ item: it, object: it, originalEvent: event
396
+ })
397
+ if (edata.isCancelled === true) return
398
+ // read items again, they might have been changed in the click event handler
399
+ items = (it && it.items ? w2utils.normMenu.call(this, it.items, it) : [])
400
+
401
+ let btn = '#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id)
402
+ query(this.box).find(btn).removeClass('down') // need to re-query at the moment -- as well as elsewhere in this function
403
+
404
+ if (it.type == 'radio') {
405
+ for (let i = 0; i < this.items.length; i++) {
406
+ let itt = this.items[i]
407
+ if (itt.type == 'group') {
408
+ for (let i1 = 0; i1 < itt.items.length; i1++) {
409
+ let itt1 = itt.items[i1]
410
+ if (itt1 == null || itt1.id == it.id || itt1.type !== 'radio') continue
411
+ if (itt1.group == it.group && itt1.checked) {
412
+ itt1.checked = false
413
+ this.refresh(itt1.id)
414
+ }
415
+ }
416
+ }
417
+ if (itt == null || itt.id == it.id || itt.type !== 'radio') continue
418
+ if (itt.group == it.group && itt.checked) {
419
+ itt.checked = false
420
+ this.refresh(itt.id)
421
+ }
422
+ }
423
+ it.checked = true
424
+ query(this.box).find(btn).addClass('checked')
425
+ }
426
+
427
+ if (['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(it.type)) {
428
+ this.tooltipHide(id)
429
+ if (it.checked) {
430
+ w2tooltip.hide(this.name + '-drop')
431
+ return
432
+ } else {
433
+ /**
434
+ * Need to clear all previous event listeners, since tooltip name is reused and it finds the old configuration and
435
+ * extends it. If events are not cleared, it would trigger old listeners too.
436
+ */
437
+ let overlay = w2tooltip.get(this.name + '-drop')
438
+ if (overlay?.displayed) overlay.hide()
439
+ overlay?.listeners?.splice(0)
440
+
441
+ // timeout is needed to make sure previous overlay hides
442
+ setTimeout(() => {
443
+ let hideDrop = (id, btn) => {
444
+ // need a closure to capture id variable
445
+ let self = this
446
+ return function () {
447
+ self.set(id, { checked: false })
448
+ }
449
+ }
450
+ let el = query(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id))
451
+ if (!w2utils.isPlainObject(it.overlay)) it.overlay = {}
452
+ if (it.type == 'drop') {
453
+ w2tooltip.show(w2utils.extend({
454
+ html: it.html,
455
+ class: 'w2ui-white',
456
+ hideOn: ['doc-click']
457
+ }, it.overlay, {
458
+ anchor: el[0],
459
+ name: this.name + '-drop',
460
+ data: { item: it, btn }
461
+ }))
462
+ .hide(hideDrop(it.id, btn))
463
+ }
464
+ if (['menu', 'menu-radio', 'menu-check'].includes(it.type)) {
465
+ let menuType = 'normal'
466
+ if (it.type == 'menu-radio') {
467
+ menuType = 'radio'
468
+ items.forEach((item) => {
469
+ if (it.selected == item.id) item.checked = true; else item.checked = false
470
+ })
471
+ }
472
+ if (it.type == 'menu-check') {
473
+ menuType = 'check'
474
+ items.forEach((item) => {
475
+ if (Array.isArray(it.selected) && it.selected.includes(item.id)) item.checked = true; else item.checked = false
476
+ })
477
+ }
478
+ w2menu.show(w2utils.extend({
479
+ items,
480
+ selected: -1,
481
+ align: it.text ? 'left' : 'none', // if there is no text, then no alignent
482
+ }, it.overlay, {
483
+ type: menuType,
484
+ name : this.name + '-drop',
485
+ anchor: el[0],
486
+ data: { item: it, btn }
487
+ }))
488
+ .hide(hideDrop(it.id, btn))
489
+ .remove(event => {
490
+ this.menuClick({ name: this.name, remove: true, item: it, subItem: event.detail.item,
491
+ originalEvent: event })
492
+ })
493
+ .select(event => {
494
+ this.menuClick({ name: this.name, item: it, subItem: event.detail.item,
495
+ originalEvent: event })
496
+ })
497
+ }
498
+ if (['color', 'text-color'].includes(it.type)) {
499
+ w2color.show(w2utils.extend({
500
+ color: it.color
501
+ }, it.overlay, {
502
+ anchor: el[0],
503
+ name: this.name + '-drop',
504
+ data: { item: it, btn }
505
+ }))
506
+ .hide(hideDrop(it.id, btn))
507
+ .liveUpdate(event => {
508
+ let edata = this.trigger('liveUpdate', { name: this.name, item: it, color: event.detail.color })
509
+ edata.finish()
510
+ })
511
+ .select(event => {
512
+ if (event.detail.color != null) {
513
+ this.colorClick({ name: this.name, item: it, color: event.detail.color })
514
+ }
515
+ })
516
+ }
517
+ }, 0)
518
+ }
519
+ }
520
+
521
+ if (['check', 'menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(it.type)) {
522
+ it.checked = !it.checked
523
+ if (it.checked) {
524
+ query(this.box).find(btn).addClass('checked')
525
+ } else {
526
+ query(this.box).find(btn).removeClass('checked')
527
+ }
528
+ }
529
+ // route processing
530
+ if (it.route) {
531
+ let route = String('/'+ it.route).replace(/\/{2,}/g, '/')
532
+ let info = w2utils.parseRoute(route)
533
+ if (info.keys.length > 0) {
534
+ for (let k = 0; k < info.keys.length; k++) {
535
+ route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name])
536
+ }
537
+ }
538
+ setTimeout(() => { window.location.hash = route }, 1)
539
+ }
540
+ // need to refresh toolbar as it might be dynamic
541
+ this.tooltipShow(id)
542
+ // event after
543
+ edata.finish()
544
+ }
545
+ }
546
+
547
+ scroll(direction, line, instant) {
548
+ return new Promise((resolve, reject) => {
549
+ let scrollBox = query(this.box).find(`.w2ui-tb-line:nth-child(${line}) .w2ui-scroll-wrapper`)
550
+ let scrollLeft = scrollBox.get(0).scrollLeft
551
+ let right = scrollBox.find('.w2ui-tb-right').get(0)
552
+ let width1 = scrollBox.parent().get(0).getBoundingClientRect().width
553
+ let width2 = scrollLeft + parseInt(right.offsetLeft) + parseInt(right.clientWidth )
554
+
555
+ switch (direction) {
556
+ case 'left': {
557
+ scroll = scrollLeft - width1 + 50 // 35 is width of both button
558
+ if (scroll <= 0) scroll = 0
559
+ scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' })
560
+ break
561
+ }
562
+ case 'right': {
563
+ scroll = scrollLeft + width1 - 50 // 35 is width of both button
564
+ if (scroll >= width2 - width1) scroll = width2 - width1
565
+ scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' })
566
+ break
567
+ }
568
+ }
569
+ /**
570
+ * Timeout is needed because browser animates scroll. Also, I found that 500ms is not enough
571
+ * as it could take longer then that, but animation seems to be around 500ms
572
+ */
573
+ setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 600)
574
+ })
575
+ }
576
+
577
+ render(box) {
578
+ let time = Date.now()
579
+ if (typeof box == 'string') box = query(box).get(0)
580
+ // event before
581
+ let edata = this.trigger('render', { target: this.name, box: box ?? this.box })
582
+ if (edata.isCancelled === true) return
583
+ // defaul action
584
+ if (box != null) {
585
+ this.unmount() // clean previous control
586
+ this.box = box
587
+ }
588
+ if (!this.box) return
589
+ if (!Array.isArray(this.right)) {
590
+ this.right = [this.right]
591
+ }
592
+ // render all buttons
593
+ let html = ''
594
+ let line = 0
595
+ for (let i = 0; i < this.items.length; i++) {
596
+ let it = this.items[i]
597
+ if (it == null) continue
598
+ if (it.id == null) it.id = 'item_' + i
599
+ if (it.caption != null) {
600
+ console.log('NOTICE: toolbar item.caption property is deprecated, please use item.text. Item -> ', it)
601
+ }
602
+ if (it.hint != null) {
603
+ console.log('NOTICE: toolbar item.hint property is deprecated, please use item.tooltip. Item -> ', it)
604
+ }
605
+ if (i === 0 || it.type == 'new-line') {
606
+ line++
607
+ html += `
608
+ <div class="w2ui-tb-line">
609
+ <div class="w2ui-scroll-wrapper w2ui-eaction" data-mousedown="resize">
610
+ <div class="w2ui-tb-right">${this.right[line-1] ?? ''}</div>
611
+ </div>
612
+ <div class="w2ui-scroll-left w2ui-eaction" data-click='["scroll", "left", "${line}"]'></div>
613
+ <div class="w2ui-scroll-right w2ui-eaction" data-click='["scroll", "right", "${line}"]'></div>
614
+ </div>
615
+ `
616
+ }
617
+ it.line = line
618
+ }
619
+ query(this.box)
620
+ .attr('name', this.name)
621
+ .addClass('w2ui-reset w2ui-toolbar')
622
+ .html(html)
623
+ if (query(this.box).length > 0) {
624
+ query(this.box)[0].style.cssText += this.style
625
+ }
626
+ // overflow buttons
627
+ w2utils.bindEvents(query(this.box).find('.w2ui-tb-line .w2ui-eaction'), this)
628
+ // observe div resize
629
+ this.last.observeResize = new ResizeObserver(() => { this.resize() })
630
+ this.last.observeResize.observe(this.box)
631
+ // refresh all
632
+ this.refresh()
633
+ this.resize()
634
+ // event after
635
+ edata.finish()
636
+ return Date.now() - time
637
+ }
638
+
639
+ refresh(id) {
640
+ let time = Date.now()
641
+ // event before
642
+ let edata = this.trigger('refresh', { target: (id != null ? id : this.name), item: this.get(id) })
643
+ if (edata.isCancelled === true) return
644
+ let edata2
645
+ // refresh all
646
+ if (id == null) {
647
+ for (let i = 0; i < this.items.length; i++) {
648
+ let it1 = this.items[i]
649
+ if (it1.id == null) it1.id = 'item_' + i
650
+ this.refresh(it1.id)
651
+ }
652
+ return
653
+ }
654
+ // create or refresh only one item
655
+ let it = this.get(id)
656
+ if (it == null) return false
657
+ if (typeof it.onRefresh == 'function') {
658
+ edata2 = this.trigger('refresh', { target: id, item: it, object: it })
659
+ if (edata2.isCancelled === true) return
660
+ }
661
+ let selector = `#tb_${this.name}_item_${w2utils.escapeId(it.id)}`
662
+ let btn = query(this.box).find(selector)
663
+ let html = this.getItemHTML(it)
664
+ // hide tooltip
665
+ this.tooltipHide(id)
666
+
667
+ // if there is a spacer, then right HTML is not 100%
668
+ if (it.type == 'spacer') {
669
+ query(this.box).find(`.w2ui-tb-line:nth-child(${it.line ?? 1})`).find('.w2ui-tb-right').css('width', 'auto')
670
+ }
671
+
672
+ if (btn.length === 0) {
673
+ let next = parseInt(this.get(id, true)) + 1
674
+ let $next = query(this.box).find(`#tb_${this.name}_item_${w2utils.escapeId(this.items[next] ? this.items[next].id : '--')}`) // "--" is needed or it will insert wrong
675
+ if ($next.length == 0) {
676
+ $next = query(this.box).find(`.w2ui-tb-line:nth-child(${it.line}`).find('.w2ui-tb-right').before(html)
677
+ } else {
678
+ $next.after(html)
679
+ }
680
+ w2utils.bindEvents(query(this.box).find(`${selector}, ${selector} .w2ui-eaction`), this)
681
+ } else {
682
+ // refresh
683
+ query(this.box).find(selector).replace(query.html(html))
684
+ let newBtn = query(this.box).find(selector)
685
+ w2utils.bindEvents(newBtn, this)
686
+ w2utils.bindEvents(newBtn.find('.w2ui-eaction'), this)
687
+ // update overlay's anchor if changed
688
+ let overlays = w2tooltip.get(true)
689
+ Object.keys(overlays).forEach(key => {
690
+ if (overlays[key].anchor == btn.get(0)) {
691
+ overlays[key].anchor = newBtn.get(0)
692
+ }
693
+ })
694
+ }
695
+ if (['menu', 'menu-radio', 'menu-check'].includes(it.type) && it.checked) {
696
+ // check selected items
697
+ let selected = Array.isArray(it.selected) ? it.selected : [it.selected]
698
+ let items = typeof it.items == 'function' ? it.items(it) : [...it.items]
699
+ items.forEach((item) => {
700
+ if (selected.includes(item.id)) item.checked = true; else item.checked = false
701
+ })
702
+ w2menu.update(this.name + '-drop', items)
703
+ }
704
+ // event after
705
+ if (typeof it.onRefresh == 'function') {
706
+ edata2.finish()
707
+ }
708
+ edata.finish()
709
+ return Date.now() - time
710
+ }
711
+
712
+ resize() {
713
+ let time = Date.now()
714
+ // event before
715
+ let edata = this.trigger('resize', { target: this.name })
716
+ if (edata.isCancelled === true) return
717
+
718
+ query(this.box).find('.w2ui-tb-line').each(el => {
719
+ // show hide overflow buttons
720
+ let box = query(el)
721
+ box.find('.w2ui-scroll-left, .w2ui-scroll-right').hide()
722
+ let scrollBox = box.find('.w2ui-scroll-wrapper').get(0)
723
+ let $right = box.find('.w2ui-tb-right')
724
+ let boxWidth = box.get(0).getBoundingClientRect().width
725
+ // Do not use $right[0].getBoundingClientRect(). right box is the most left div
726
+ let itemsWidth = ($right.length > 0 ? $right[0].offsetLeft + $right[0].clientWidth : 0)
727
+ if (boxWidth < itemsWidth) {
728
+ // we have overflown content
729
+ if (scrollBox.scrollLeft > 0) {
730
+ box.find('.w2ui-scroll-left').show()
731
+ }
732
+ if (boxWidth < itemsWidth - scrollBox.scrollLeft) {
733
+ box.find('.w2ui-scroll-right').show()
734
+ }
735
+ }
736
+ })
737
+ // event after
738
+ edata.finish()
739
+ return Date.now() - time
740
+ }
741
+
742
+ destroy() {
743
+ // event before
744
+ let edata = this.trigger('destroy', { target: this.name })
745
+ if (edata.isCancelled === true) return
746
+ // clean up
747
+ if (query(this.box).find('.w2ui-scroll-wrapper').length > 0) {
748
+ this.unmount()
749
+ }
750
+ delete w2ui[this.name]
751
+ // event after
752
+ edata.finish()
753
+ }
754
+
755
+ unmount() {
756
+ super.unmount()
757
+ this.last.observeResize?.disconnect()
758
+ }
759
+
760
+ // ========================================
761
+ // --- Internal Functions
762
+
763
+ getItemHTML(item) {
764
+ let html = ''
765
+ if (item.caption != null && item.text == null) item.text = item.caption // for backward compatibility
766
+ if (item.text == null) item.text = ''
767
+ if (item.tooltip == null && item.hint != null) item.tooltip = item.hint // for backward compatibility
768
+ if (item.tooltip == null) item.tooltip = ''
769
+ if (typeof item.get !== 'function' && (Array.isArray(item.items) || typeof item.items == 'function')) {
770
+ item.get = function get(id) { // need scope, cannot be arrow func
771
+ let tmp = item.items
772
+ if (typeof tmp == 'function') tmp = item.items(item)
773
+ return tmp.find(it => it.id == id ? true : false)
774
+ }
775
+ }
776
+ let icon = ''
777
+ let text = (typeof item.text == 'function' ? item.text.call(this, item) : item.text)
778
+ if (item.icon) {
779
+ icon = item.icon
780
+ if (typeof item.icon == 'function') {
781
+ icon = item.icon.call(this, item)
782
+ }
783
+ if (String(icon).slice(0, 1) !== '<') {
784
+ icon = `<span class="${icon}" ${item.icon_style ? `style="${item.icon_style}"` : ''}></span>`
785
+ }
786
+ icon = `<div class="w2ui-tb-icon">${icon}</div>`
787
+ }
788
+ let classes = ['w2ui-tb-button', 'w2ui-eaction']
789
+ if (item.checked) classes.push('checked')
790
+ if (item.disabled) classes.push('disabled')
791
+ if (item.hidden) classes.push('hidden')
792
+ if (!icon) classes.push('no-icon')
793
+
794
+ switch (item.type) {
795
+ case 'color':
796
+ case 'text-color':
797
+ if (typeof item.color == 'string') {
798
+ if (item.color.slice(0, 1) == '#') item.color = item.color.slice(1)
799
+ if ([3, 6, 8].includes(item.color.length)) item.color = '#' + item.color
800
+ }
801
+ if (item.type == 'color') {
802
+ text = `<span class="w2ui-tb-color-box" style="background-color: ${(item.color != null ? item.color : '#fff')}"></span>
803
+ ${(item.text ? `<div style="margin-left: 17px;">${w2utils.lang(item.text)}</div>` : '')}`
804
+ }
805
+ if (item.type == 'text-color') {
806
+ let color = (item.color != null ? item.color : '#444')
807
+ let bcolor = item.backColor
808
+ if (item.backColor === true) {
809
+ bcolor = '#fff'
810
+ if (w2utils.colorContrast('#fff', color) < 2) {
811
+ bcolor = '#555'
812
+ }
813
+ }
814
+ text = `<span style="color: ${color}">${item.text
815
+ ? w2utils.lang(item.text)
816
+ : (item.backColor
817
+ ? `<b style="background-color: ${bcolor ?? 'transparent'}; padding: 2px 5px; border-radius: 3px;">Ab</b>`
818
+ : '<b>Ab</b>'
819
+ )
820
+ }</span>`
821
+ }
822
+ case 'menu':
823
+ case 'menu-check':
824
+ case 'menu-radio':
825
+ case 'button':
826
+ case 'check':
827
+ case 'radio':
828
+ case 'label':
829
+ case 'drop': {
830
+ let arrow = (item.arrow === true
831
+ || (item.arrow !== false && ['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(item.type)))
832
+ html = `
833
+ <div id="tb_${this.name}_item_${item.id}" class="${classes.join(' ')} ${(item.class ? item.class : '')}"
834
+ style="${(item.hidden ? 'display: none;' : '')} ${item.type == 'label' ? (item.style ?? '') + ';' : ''}"
835
+ ${!item.disabled
836
+ ? `data-click='["click","${item.id}", "event"]'
837
+ data-mouseenter='["mouseAction", "event", "this", "Enter", "${item.id}"]'
838
+ data-mouseleave='["mouseAction", "event", "this", "Leave", "${item.id}"]'
839
+ data-mousedown='["mouseAction", "event", "this", "Down", "${item.id}"]'
840
+ data-mouseup='["mouseAction", "event", "this", "Up", "${item.id}"]'`
841
+ : ''}
842
+ >
843
+ ${ icon }
844
+ ${ (text != '' && text != null) || item.count != null || arrow
845
+ ? `<div class="w2ui-tb-text" style="${item.type != 'label' ? (item.style ?? '') : ''}; ${!text ? 'padding-left: 0; margin-left: 23px;' : ''}">
846
+ ${ w2utils.lang(text) }
847
+ ${ item.count != null
848
+ ? w2utils.stripSpaces(`
849
+ <span class="w2ui-tb-count">
850
+ <span class="${this.last.badge[item.id] ? this.last.badge[item.id].className ?? '' : ''}"
851
+ style="${this.last.badge[item.id] ? this.last.badge[item.id].style ?? '' : ''}">${item.count}</span>
852
+ </span>`)
853
+ : ''
854
+ }
855
+ ${ arrow
856
+ ? `<span class="w2ui-tb-down" ${!text && !item.count ? 'style="margin-left: -3px"' : ''}><span></span></span>`
857
+ : ''
858
+ }
859
+ </div>`
860
+ : ''
861
+ }
862
+ </div>
863
+ `
864
+ break
865
+ }
866
+ case 'break': {
867
+ html = `<div id="tb_${this.name}_item_${item.id}" class="w2ui-tb-break"
868
+ style="${(item.hidden ? 'display: none' : '')}; ${(item.style ? item.style : '')}">
869
+ &#160;
870
+ </div>`
871
+ break
872
+ }
873
+ case 'spacer': {
874
+ html = `<div id="tb_${this.name}_item_${item.id}" class="w2ui-tb-spacer"
875
+ style="${(item.hidden ? 'display: none' : '')}; ${(item.style ? item.style : '')}">
876
+ </div>`
877
+ break
878
+ }
879
+ case 'html': {
880
+ if(item.html instanceof HTMLElement) return item.html;
881
+ html = `<div id="tb_${this.name}_item_${item.id}" class="w2ui-tb-html ${classes.join(' ')}"
882
+ style="${(item.hidden ? 'display: none' : '')}; ${(item.style ? item.style : '')}">
883
+ ${(typeof item.html == 'function' ? item.html.call(this, item) : item.html)}
884
+ </div>`
885
+ break
886
+ }
887
+ case 'input': {
888
+ let ph = item.placeholder
889
+ let val = item.value
890
+ // For backword compatibility
891
+ if (item.spinner && typeof item.spinner == 'object') {
892
+ item.input ??= {}
893
+ Object.assign(item.input, item.spinner, { spinner: true })
894
+ }
895
+ // round to step
896
+ if (val != null && String(val).trim() !== '' && item.input?.spinner) {
897
+ let step = item.input?.step ?? 1
898
+ let prec = item.input?.precision ?? String(step).split('.')[1]?.length ?? 0
899
+ val = isNaN(val) ? val : Number(val).toFixed(prec)
900
+ }
901
+ const divId = `tb_${this.name}_item_${item.id}`;
902
+ html = `<div id="${divId}" class="w2ui-tb-input w2ui-eaction ${classes.join(' ')}"
903
+ style="${(item.hidden ? 'display: none' : '')}; ${(item.style ? item.style : '')}"
904
+ >
905
+ <span class="w2ui-input-label">${item.text ?? ''}</span>
906
+ ${item.input?.spinner
907
+ ? `<span class="w2ui-spinner-dec w2ui-eaction" data-click='["spinner", "${item.id}", "dec", "event"]'> – </span>`
908
+ : ''}
909
+ <input id="${divId}_input" class="w2ui-toolbar-input w2ui-eaction ${item.input?.spinner ? 'w2ui-has-spinner' : ''}"
910
+ ${ph ? `placeholder="${ph}"` : ''} style="${item.input?.style ?? ''}"
911
+ value="${val ?? ''}${item.input?.suffix ?? ''}" ${item.input?.attrs ?? ''}
912
+ data-input='["change", "${item.id}", "this", true]'
913
+ data-change='["change", "${item.id}", "this"]'
914
+ data-keydown='["spinner", "${item.id}", "key", "event"]'
915
+ data-mouseenter='["mouseAction", "event", "this", "Enter", "${item.id}"]'
916
+ data-mouseleave='["mouseAction", "event", "this", "Leave", "${item.id}"]'
917
+ >
918
+ ${item.input?.spinner
919
+ ? `<span class="w2ui-spinner-inc w2ui-eaction" data-click='["spinner", "${item.id}", "inc", "event"]'> + </span>`
920
+ : ''}
921
+ </div>`
922
+ break
923
+ }
924
+ case 'group': {
925
+ html = `<div id="tb_${this.name}_item_${item.id}" class="w2ui-tb-group"
926
+ style="display: flex; ${(item.hidden ? 'display: none' : '')}; ${(item.style ? item.style : '')}">`
927
+ if (Array.isArray(item.items)) {
928
+ item.items.forEach(it => {
929
+ html += this.getItemHTML(it)
930
+ })
931
+ } else {
932
+ console.log('ERROR: toolbar group is empty')
933
+ }
934
+ html += '</div>'
935
+ }
936
+ }
937
+ return html
938
+ }
939
+
940
+ spinner(id, action, event) {
941
+ let it = this.get(id)
942
+ let inc = 0
943
+ let edata = this.trigger('keyDown', { id, item: it, originalEvent: event })
944
+ switch (action) {
945
+ case 'inc': {
946
+ inc = (it.input?.step ?? 1)
947
+ break
948
+ }
949
+ case 'dec': {
950
+ inc = -(it.input?.step ?? 1)
951
+ break
952
+ }
953
+ case 'key': {
954
+ if (it.input?.spinner || it.input?.step != null) {
955
+ let mult = 1
956
+ if (event.shiftKey || event.metaKey) mult = 10
957
+ if (event.altKey) mult = 0.1
958
+ switch (event.key) {
959
+ case 'ArrowUp': {
960
+ inc = (it.input?.step ?? 1) * mult
961
+ event.preventDefault()
962
+ break
963
+ }
964
+ case 'ArrowDown': {
965
+ inc = -(it.input?.step ?? 1) * mult
966
+ event.preventDefault()
967
+ break
968
+ }
969
+ }
970
+ }
971
+ break
972
+ }
973
+ }
974
+ if (inc !== 0) {
975
+ this.change(id, parseFloat(it.value ?? 0) + inc)
976
+ }
977
+ edata.finish()
978
+ }
979
+
980
+ change(id, value, dynamic) {
981
+ let it = this.get(id)
982
+ let input = query(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(id)).find('input.w2ui-toolbar-input')
983
+ if (value instanceof HTMLInputElement) {
984
+ value = value.value
985
+ }
986
+ if (value == null) value = input.val()
987
+ if (it.input?.spinner || it.input?.min != null || it.input?.max != null || it.input?.step != null) {
988
+ value = parseFloat(value)
989
+ }
990
+ // remove suffix if it is there
991
+ if (it.input?.suffix != null && String(value).substr(-it.input.suffix.length) == it.input.suffix) {
992
+ value = String(value).substr(0, value.length - it.input.suffix.length)
993
+ }
994
+ // min/max
995
+ if (it.input?.min != null && it.input.min > value) {
996
+ value = it.input.min
997
+ }
998
+ if (it.input?.max != null && it.input.max < value) {
999
+ value = it.input.max
1000
+ }
1001
+ // round to step
1002
+ if (it.input?.step != null) {
1003
+ if (isNaN(value)) value = it.input.min ?? 0
1004
+ let step = it.input.step ?? 1
1005
+ let prec = it.input.precision ?? String(step).split('.')[1]?.length ?? 0
1006
+ value = Number(value).toFixed(prec)
1007
+ }
1008
+
1009
+ // event beofre
1010
+ let edata = this.trigger(dynamic ? 'input' : 'change', { target: id, id, value, item: it })
1011
+ if (edata.isCancelled) {
1012
+ return
1013
+ }
1014
+ it.value = value
1015
+ let suffix = ''
1016
+ if (it.input?.suffix != null && String(value).substr(-it.input.suffix.length) != it.input.suffix) {
1017
+ suffix = it.input.suffix
1018
+ }
1019
+ if (!dynamic) input.val(value + suffix)
1020
+ // event after
1021
+ edata.finish()
1022
+ }
1023
+
1024
+ tooltipShow(id) {
1025
+ if (this.tooltip == null) return
1026
+ let el = query(this.box).find('#tb_'+ this.name + '_item_'+ w2utils.escapeId(id)).get(0)
1027
+ let item = this.get(id)
1028
+ let overlay = (typeof this.tooltip == 'string' ? { position: this.tooltip } : this.tooltip)
1029
+ let txt = item.tooltip
1030
+ if (typeof txt == 'function') txt = txt.call(this, item)
1031
+ // not for opened drop downs
1032
+ if (['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(item.type)
1033
+ && item.checked == true) {
1034
+ return
1035
+ }
1036
+ w2tooltip.show({
1037
+ anchor: el,
1038
+ name: this.name + '-tooltip',
1039
+ html: txt,
1040
+ ...overlay
1041
+ })
1042
+ return
1043
+ }
1044
+
1045
+ tooltipHide(id) {
1046
+ if (this.tooltip == null) return
1047
+ w2tooltip.hide(this.name + '-tooltip')
1048
+ }
1049
+
1050
+ menuClick(event) {
1051
+ if (event.item && !event.item.disabled) {
1052
+ // event before
1053
+ let edata = this.trigger((event.remove !== true ? 'click' : 'remove'), {
1054
+ target: event.item.id + ':' + event.subItem.id, item: event.item,
1055
+ subItem: event.subItem, originalEvent: event.originalEvent
1056
+ })
1057
+ if (edata.isCancelled === true) return
1058
+
1059
+ // route processing
1060
+ let it = event.subItem
1061
+ let item = this.get(event.item.id)
1062
+ let items = item.items
1063
+ if (typeof items == 'function') items = item.items()
1064
+ if (item.type == 'menu') {
1065
+ item.selected = it.id
1066
+ }
1067
+ if (item.type == 'menu-radio') {
1068
+ item.selected = it.id
1069
+ if (Array.isArray(items)) {
1070
+ items.forEach((item) => {
1071
+ if (item.checked === true) delete item.checked
1072
+ if (Array.isArray(item.items)) {
1073
+ item.items.forEach((item) => {
1074
+ if (item.checked === true) delete item.checked
1075
+ })
1076
+ }
1077
+ })
1078
+ }
1079
+ it.checked = true
1080
+ }
1081
+ if (item.type == 'menu-check') {
1082
+ if (!Array.isArray(item.selected)) item.selected = []
1083
+ if (it.group == null) {
1084
+ let ind = item.selected.indexOf(it.id)
1085
+ if (ind == -1) {
1086
+ item.selected.push(it.id)
1087
+ it.checked = true
1088
+ } else {
1089
+ item.selected.splice(ind, 1)
1090
+ it.checked = false
1091
+ }
1092
+ } else if (it.group === false) {
1093
+ // if group is false, then it is not part of checkboxes
1094
+ } else {
1095
+ let unchecked = []
1096
+ let ind = item.selected.indexOf(it.id)
1097
+ let checkNested = (items) => {
1098
+ items.forEach((sub) => {
1099
+ if (sub.group === it.group) {
1100
+ let ind = item.selected.indexOf(sub.id)
1101
+ if (ind != -1) {
1102
+ if (sub.id != it.id) unchecked.push(sub.id)
1103
+ item.selected.splice(ind, 1)
1104
+ }
1105
+ }
1106
+ if (Array.isArray(sub.items)) checkNested(sub.items)
1107
+ })
1108
+ }
1109
+ checkNested(items)
1110
+ if (ind == -1) {
1111
+ item.selected.push(it.id)
1112
+ it.checked = true
1113
+ }
1114
+ }
1115
+ }
1116
+ if (typeof it.route == 'string') {
1117
+ let route = it.route !== '' ? String('/'+ it.route).replace(/\/{2,}/g, '/') : ''
1118
+ let info = w2utils.parseRoute(route)
1119
+ if (info.keys.length > 0) {
1120
+ for (let k = 0; k < info.keys.length; k++) {
1121
+ if (this.routeData[info.keys[k].name] == null) continue
1122
+ route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name])
1123
+ }
1124
+ }
1125
+ setTimeout(() => { window.location.hash = route }, 1)
1126
+ }
1127
+ this.refresh(event.item.id)
1128
+ // event after
1129
+ edata.finish()
1130
+ }
1131
+ }
1132
+
1133
+ colorClick(event) {
1134
+ let obj = this
1135
+ if (event.item && !event.item.disabled) {
1136
+ // event before
1137
+ let edata = this.trigger('click', { target: event.item.id, item: event.item, color: event.color, final: true })
1138
+ if (edata.isCancelled === true) return
1139
+
1140
+ // default behavior
1141
+ event.item.color = event.color
1142
+ obj.refresh(event.item.id)
1143
+
1144
+ // event after
1145
+ edata.finish()
1146
+ }
1147
+ }
1148
+
1149
+ mouseAction(event, target, action, id) {
1150
+ let btn = this.get(id)
1151
+ let edata = this.trigger('mouse' + action, { target: id, item: btn, object: btn, originalEvent: event })
1152
+ if (edata.isCancelled === true || btn.disabled || btn.hidden) return
1153
+ switch (action) {
1154
+ case 'Enter':
1155
+ if (!['label', 'input'].includes(btn.type)) {
1156
+ query(target).addClass('over')
1157
+ }
1158
+ this.tooltipShow(id)
1159
+ break
1160
+ case 'Leave':
1161
+ if (!['label', 'input'].includes(btn.type)) {
1162
+ query(target).removeClass('over down')
1163
+ }
1164
+ this.tooltipHide(id)
1165
+ break
1166
+ case 'Down':
1167
+ if (!['label', 'input'].includes(btn.type)) {
1168
+ query(target).addClass('down')
1169
+ }
1170
+ break
1171
+ case 'Up':
1172
+ if (!['label', 'input'].includes(btn.type)) {
1173
+ query(target).removeClass('down')
1174
+ }
1175
+ break
1176
+ }
1177
+ edata.finish()
1178
+ }
1179
+ }
1180
+ export { w2toolbar }