beyond-rails 0.0.186 → 0.0.191

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/src/js/components/Alert.js +1 -1
  3. data/src/js/components/Autocomplete.js +1 -1
  4. data/src/js/components/AutocompleteMenu.js +1 -1
  5. data/src/js/components/BarChart.js +452 -0
  6. data/src/js/components/Btn.js +1 -1
  7. data/src/js/components/Checkbox.js +1 -1
  8. data/src/js/components/DateInput.js +1 -1
  9. data/src/js/components/DateMenu.js +1 -1
  10. data/src/js/components/DateTimeRanger.js +1 -1
  11. data/src/js/components/Datepicker.js +1 -1
  12. data/src/js/components/DatepickerBtnArrow.js +1 -1
  13. data/src/js/components/Dropdown.js +1 -1
  14. data/src/js/components/LineChart.js +535 -0
  15. data/src/js/components/Menu.js +1 -1
  16. data/src/js/components/Modal.js +64 -13
  17. data/src/js/components/Navbar.js +1 -1
  18. data/src/js/components/Radio.js +1 -1
  19. data/src/js/components/SearchDropdown.js +1 -1
  20. data/src/js/components/Sidebar.js +1 -1
  21. data/src/js/components/Tabbox.js +1 -1
  22. data/src/js/components/TimeInput.js +1 -1
  23. data/src/js/components/TimeMenu.js +1 -1
  24. data/src/js/components/Timepicker.js +1 -1
  25. data/src/js/components/Toast.js +1 -1
  26. data/src/js/components/ToastItem.js +1 -1
  27. data/src/js/components/Tooltip.js +1 -1
  28. data/src/js/decorators/chartCommon.js +218 -0
  29. data/src/js/{utils → decorators}/supportDom.js +1 -1
  30. data/src/js/index.js +6 -2
  31. data/src/js/jquery/bindModalFn.js +57 -6
  32. data/src/js/utils/index.js +10 -1
  33. data/src/js/utils/isDef.js +3 -0
  34. data/src/js/utils/isInt.js +3 -0
  35. data/src/js/utils/isUndef.js +3 -0
  36. data/src/sass/_beyond.scss +1 -0
  37. data/src/sass/abstracts/_variables.scss +1 -1
  38. data/src/sass/components/_breadcrumb.scss +3 -0
  39. data/src/sass/components/_chart.scss +14 -0
  40. data/src/sass/components/_mega-menu.scss +28 -28
  41. data/src/sass/components/_modal.scss +12 -1
  42. data/src/sass/components/_nav.scss +1 -0
  43. metadata +28 -9
@@ -1,4 +1,4 @@
1
- import supportDom from '../utils/supportDom'
1
+ import supportDom from '../decorators/supportDom'
2
2
  import { toPixel } from '../utils'
3
3
 
4
4
  @supportDom
@@ -1,4 +1,4 @@
1
- import supportDom from '../utils/supportDom'
1
+ import supportDom from '../decorators/supportDom'
2
2
 
3
3
  @supportDom
4
4
  export default class Checkbox {
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_TIMEZONE } from '../consts'
2
- import supportDom from '../utils/supportDom'
2
+ import supportDom from '../decorators/supportDom'
3
3
  import isTouchDevice from '../utils/isTouchDevice'
4
4
  import { format } from '../utils'
5
5
 
@@ -2,7 +2,7 @@ import getFloatedTargetPos from '../utils/getFloatedTargetPos'
2
2
  import isTouchDevice from '../utils/isTouchDevice'
3
3
  import dateLt from '../utils/dateLt'
4
4
  import dateEq from '../utils/dateEq'
5
- import supportDom from '../utils/supportDom'
5
+ import supportDom from '../decorators/supportDom'
6
6
  import {
7
7
  addDays,
8
8
  addMonths,
@@ -5,7 +5,7 @@ import TimeMenu from './TimeMenu'
5
5
  import DatepickerBtnArrow from './DatepickerBtnArrow'
6
6
  import dateGt from '../utils/dateGt'
7
7
  import dateLt from '../utils/dateLt'
8
- import supportDom from '../utils/supportDom'
8
+ import supportDom from '../decorators/supportDom'
9
9
  import {
10
10
  endOfDay,
11
11
  dateToTimestamp,
@@ -2,7 +2,7 @@ import DateInput from './DateInput'
2
2
  import TimeInput from './TimeInput'
3
3
  import DateMenu from './DateMenu'
4
4
  import TimeMenu from './TimeMenu'
5
- import supportDom from '../utils/supportDom'
5
+ import supportDom from '../decorators/supportDom'
6
6
  import { DEFAULT_TIMEZONE } from '../consts'
7
7
  import {
8
8
  dateToTimestamp,
@@ -1,4 +1,4 @@
1
- import supportDom from '../utils/supportDom'
1
+ import supportDom from '../decorators/supportDom'
2
2
 
3
3
  @supportDom
4
4
  export default class DatepickerBtnArrow {
@@ -1,5 +1,5 @@
1
1
  import getFloatedTargetPos from '../utils/getFloatedTargetPos'
2
- import supportDom from '../utils/supportDom'
2
+ import supportDom from '../decorators/supportDom'
3
3
  import { isFunction, toPixel, throttle } from '../utils'
4
4
 
5
5
  @supportDom
@@ -0,0 +1,535 @@
1
+ import supportDom from '../decorators/supportDom'
2
+ import chartCommon from '../decorators/chartCommon'
3
+ import isDef from '../utils/isDef'
4
+ import isUndef from '../utils/isUndef'
5
+ import { mem, range, sortBy, throttle, uniqBy } from '../utils'
6
+
7
+ /**
8
+ * -------------------------------------------------------------------------------------------------
9
+ * ^ |
10
+ * | |
11
+ * yPadding |
12
+ * | |
13
+ * v |
14
+ * ----------------------------------------------------------- yLabel |
15
+ * ----------------------------------------------------------- yLabel |
16
+ * <- xPadding -> ----------------------------------------------------------- yLabel <- xPadding -> |
17
+ * ----------------------------------------------------------- yLabel |
18
+ * ^ |
19
+ * yGutter |
20
+ * v |
21
+ * ----------------------------------------------------------- yLabel |
22
+ * xLabel <- xGutter -> xLabel <- xGutter -> xLabel |
23
+ * ^ |
24
+ * | |
25
+ * yPadding |
26
+ * | |
27
+ * v |
28
+ * --------------------------------------------------------------------------------------------------
29
+ **/
30
+ const defaultLineStyles = [
31
+ '#5469d4',
32
+ '#7c54d4',
33
+ '#a254d4'
34
+ ]
35
+
36
+ @supportDom
37
+ @chartCommon
38
+ export default class LineChart {
39
+
40
+ constructor(dom, options = {}) {
41
+ this.dom = dom
42
+ this.options = options
43
+ this.pointsArr = []
44
+ this.height = options.height || 150
45
+ this.width = options.width
46
+
47
+ this.toXLabel = isDef(options.toXLabel) ? mem(options.toXLabel) : (v => v)
48
+ this.toYLabel = isDef(options.toYLabel) ? mem(options.toYLabel) : (v => v)
49
+
50
+ this.xPadding = isDef(options.xPadding) ? options.xPadding : 20
51
+ this.yPadding = isDef(options.yPadding) ? options.yPadding : 20
52
+
53
+ this.xGutter = isDef(options.xGutter) ? options.xGutter : 100
54
+ this.yGutter = isDef(options.yGutter) ? options.yGutter : 10
55
+
56
+ this.xLabelMargin = isDef(options.xLabelMargin) ? options.xLabelMargin : 10
57
+ this.yLabelMargin = isDef(options.yLanelMargin) ? options.yLabelMargin : 10
58
+
59
+ this.lineStyles = options.lineStyles || defaultLineStyles
60
+
61
+ this.bgColor = options.bgColor || '#fff'
62
+ this.fontSize = options.fontSize || 12
63
+
64
+ this.xStep = options.xStep
65
+ this.yStep = options.yStep
66
+
67
+ this.lineLabels = options.lineLabels || []
68
+ this.lineLabelMargin = isDef(options.lineLabelMargin) ? options.lineLabelMargin : 20
69
+
70
+ this.pointPosMap = new Map()
71
+ this.xLabelRows = []
72
+ this.yLabelRows = []
73
+
74
+ this.init()
75
+ }
76
+
77
+ init() {
78
+ this.setDpr()
79
+ this.setDomWidthIfNeeded()
80
+ this.setCanvas()
81
+ this.clear()
82
+ this.bindMedia()
83
+ this.bindPointMouseOver()
84
+ }
85
+
86
+ get contentWidth() {
87
+ return this.width - (this.xPadding * 2) - this.yLabelMargin -
88
+ this.yLabelWidth - (this.xLabelWidth / 2)
89
+ }
90
+
91
+ get contentHeight() {
92
+ return this.height - (this.yPadding * 2) - this.xLabelMargin -
93
+ this.xLabelHeight - this.lineLabelBoxHeight
94
+ }
95
+
96
+ get firstX() {
97
+ return this.xLabelRows[0].value
98
+ }
99
+
100
+ get lastX() {
101
+ const { xLabelRows } = this
102
+ return xLabelRows[xLabelRows.length - 1].value
103
+ }
104
+
105
+ get firstY() {
106
+ return this.yLabelRows[0].value
107
+ }
108
+
109
+ get lastY() {
110
+ const { yLabelRows } = this
111
+ return yLabelRows[yLabelRows.length - 1].value
112
+ }
113
+
114
+ get lineLabelHeight() {
115
+ return this.fontSize
116
+ }
117
+
118
+ get lineLabelBoxHeight() {
119
+ if (this.lineLabels.length > 0) {
120
+ return this.lineLabelMargin + this.lineLabelHeight
121
+ }
122
+ return 0
123
+ }
124
+
125
+ get xAxisStart() {
126
+ return this.xPadding + (this.xLabelWidth / 2)
127
+ }
128
+
129
+ get xAxisEnd() {
130
+ return this.xAxisStart + this.contentWidth
131
+ }
132
+
133
+ get xRatio() {
134
+ const lineWidth = this.xAxisEnd - this.xAxisStart
135
+ const xDelta = this.lastX - this.firstX
136
+ return xDelta / lineWidth
137
+ }
138
+
139
+ get yAxisStart() {
140
+ return this.height - this.yPadding - this.lineLabelBoxHeight -
141
+ this.xLabelHeight - this.xLabelMargin + (this.yLabelHeight / 2)
142
+ }
143
+
144
+ get yAxisEnd() {
145
+ return this.yAxisStart - this.contentHeight
146
+ }
147
+
148
+ get yRatio() {
149
+ const lineHeight = this.yAxisStart - this.yAxisEnd
150
+ const yDelta = Math.abs(this.lastY - this.firstY)
151
+ return yDelta / lineHeight
152
+ }
153
+
154
+ bindPointMouseOver() {
155
+ if (isUndef(this.options.onPointMouseOver)) {
156
+ return
157
+ }
158
+ if (! ('onmousemove' in this.canvas)) {
159
+ return
160
+ }
161
+ this.addLayer()
162
+ const canvas = this.getHighestCanvas()
163
+ this.addEvent(canvas, 'mousemove', throttle(this.handleMouseMove.bind(this), 30))
164
+ }
165
+
166
+ clearPointPos() {
167
+ this.pointPosMap.clear()
168
+ }
169
+
170
+ draw() {
171
+ this.clear()
172
+ this.drawXAxis()
173
+ this.drawYAxis()
174
+ this.drawBgLines()
175
+ this.drawLines()
176
+ this.drawLineLables()
177
+ }
178
+
179
+ drawBgLines() {
180
+
181
+ const { ctx, yLabelRows, contentWidth, firstY, xAxisStart, yAxisStart, yRatio } = this
182
+
183
+ ctx.strokeStyle = 'rgba(224, 224, 224, .5)'
184
+ ctx.lineWidth = 1
185
+
186
+ yLabelRows.forEach(row => {
187
+
188
+ const y = yAxisStart - ((row.value - firstY) / yRatio)
189
+
190
+ ctx.beginPath()
191
+ ctx.moveTo(xAxisStart, y)
192
+ ctx.lineTo(xAxisStart + contentWidth, y)
193
+ ctx.stroke()
194
+ ctx.closePath
195
+ })
196
+ }
197
+
198
+ drawLines() {
199
+ const { ctx, pointPosMap, lineStyles } = this
200
+ ctx.lineWidth = 2
201
+
202
+ this.pointsArr.forEach((points, i) => {
203
+ ctx.beginPath()
204
+ ctx.strokeStyle = lineStyles[i] ? lineStyles[i] : '#000'
205
+ points.forEach(p => {
206
+ const pos = pointPosMap.get(p)
207
+ ctx.lineTo(pos.x, pos.y)
208
+ })
209
+ ctx.stroke()
210
+ ctx.closePath()
211
+ })
212
+ }
213
+
214
+ drawLineLables() {
215
+ const { ctx, lineStyles, lineLabelHeight } = this
216
+ const rectSize = 7
217
+
218
+ const rectGutter = 7
219
+ const labelGutter = 14
220
+ const rectMargin = 1
221
+ const y = this.height - this.yPadding
222
+ let x = this.xPadding
223
+
224
+ this.lineLabels.forEach((name, i) => {
225
+ ctx.fillStyle = lineStyles[i] || '#000'
226
+ ctx.fillRect(x, y - lineLabelHeight + rectMargin, rectSize, rectSize)
227
+
228
+ x += (rectSize + rectGutter)
229
+ ctx.fillStyle = '#000'
230
+ ctx.textAlign = 'left'
231
+ ctx.textBaseline = 'top'
232
+ ctx.fillText(name, x, y - lineLabelHeight)
233
+ x += (ctx.measureText(name).width + labelGutter)
234
+ })
235
+ }
236
+
237
+ clearVerticalLine() {
238
+ const { ctx } = this.firstLayer
239
+ ctx.clearRect(0, 0, this.width, this.height)
240
+ }
241
+
242
+ drawVerticalLine(point, index) {
243
+ const { ctx } = this.firstLayer
244
+ const pos = this.pointPosMap.get(point)
245
+ const style = this.lineStyles[index] || '#000'
246
+ ctx.strokeStyle = style
247
+ ctx.lineWidth = 1
248
+ ctx.beginPath()
249
+ ctx.moveTo(pos.x, 0)
250
+ ctx.lineTo(pos.x, this.height)
251
+ ctx.stroke()
252
+ ctx.closePath()
253
+
254
+ this.fillCircle(ctx, pos.x, pos.y, 8, style, .2)
255
+ this.fillCircle(ctx, pos.x, pos.y, 4, style)
256
+ }
257
+
258
+ drawXAxis() {
259
+ const { ctx, firstX, xLabelRows, xAxisStart, xRatio } = this
260
+
261
+ const y = this.height - this.yPadding - this.lineLabelBoxHeight
262
+
263
+ const scaleMargin = 4
264
+ const scaleSize = 4
265
+ const scaleStart = y - scaleMargin - scaleSize
266
+ const scaleEnd = y - scaleMargin
267
+
268
+ ctx.textBaseline = 'top'
269
+ ctx.fillStyle = '#3c4257'
270
+ ctx.textAlign = 'center'
271
+
272
+ ctx.strokeStyle = '#3c4257'
273
+
274
+ xLabelRows.forEach((row, i) => {
275
+
276
+ const x = xAxisStart + ((row.value - firstX) / xRatio)
277
+
278
+ ctx.beginPath()
279
+ ctx.moveTo(x, scaleStart)
280
+ ctx.lineTo(x, scaleEnd)
281
+ ctx.stroke()
282
+ ctx.closePath()
283
+
284
+ ctx.fillText(row.label, x, y)
285
+ })
286
+ }
287
+
288
+ drawYAxis() {
289
+ const { ctx, firstY, yLabelRows, yAxisStart, yRatio } = this
290
+ const x = this.width - this.xPadding
291
+ const halfYLabelHeight = this.yLabelHeight / 2
292
+
293
+ ctx.fillStyle = '#3c4257'
294
+ ctx.textAlign = 'right'
295
+
296
+ yLabelRows.forEach(row => {
297
+ const y = yAxisStart - ((row.value - firstY) / yRatio)
298
+ ctx.fillText(row.label, x, y - halfYLabelHeight)
299
+ })
300
+ }
301
+
302
+ findClosetPoint(canvasMousePos) {
303
+ const { pointsArr, pointPosMap } = this
304
+ let index = 0
305
+ for (const points of pointsArr) {
306
+ for (const point of points) {
307
+ const pos = pointPosMap.get(point)
308
+ if (this.inDetectedZone(canvasMousePos, pos)) {
309
+ return {
310
+ index,
311
+ point
312
+ }
313
+ }
314
+ }
315
+ index++
316
+ }
317
+ }
318
+
319
+ handleDprChange() {
320
+ this.setDpr()
321
+ this.refresh()
322
+ }
323
+
324
+ handleMouseMove(event) {
325
+
326
+ const canvasMousePos = this.getMousePosInCanvas(event)
327
+ const res = this.findClosetPoint(canvasMousePos)
328
+
329
+ this.raf(() => {
330
+ this.clearVerticalLine()
331
+ if (res) {
332
+ this.drawVerticalLine(res.point, res.index)
333
+ }
334
+ // only fires if res differs
335
+ if (this.lastClosetPointRes !== res) {
336
+ const mousePos = this.getMousePos(canvasMousePos)
337
+ this.options.onPointMouseOver(mousePos, res)
338
+ }
339
+ this.lastClosetPointRes = res
340
+ })
341
+ }
342
+
343
+ inDetectedZone(canvasMousePos, pointPos) {
344
+ const zoneLength = 14
345
+ const { x: mouseX, y: mouseY } = canvasMousePos
346
+ const { x: pointX, y: pointY } = pointPos
347
+ /**
348
+ * # is canvasMousePos
349
+ * see if PointPos is landed in the zone below
350
+ *
351
+ * A -------- B
352
+ * | |
353
+ * | |
354
+ * #
355
+ * | |
356
+ * | |
357
+ * C -------- D
358
+ */
359
+ const a = {
360
+ x: mouseX - zoneLength,
361
+ y: mouseY - zoneLength
362
+ }
363
+ const b = {
364
+ x: mouseX + zoneLength,
365
+ y: mouseY - zoneLength
366
+ }
367
+ const c = {
368
+ x: mouseX - zoneLength,
369
+ y: mouseY + zoneLength
370
+ }
371
+ return (a.x <= pointX) && (pointX <= b.x) &&
372
+ (a.y <= pointY) && (pointY <= c.y)
373
+ }
374
+
375
+ getUniqSortedPoints(axis) {
376
+ const points = uniqBy(this.pointsArr.flat(), axis)
377
+ return sortBy(points, [axis])
378
+ }
379
+
380
+ getLabelRows(options = {}) {
381
+
382
+ const axis = options.axis || 'x'
383
+ const gutter = options.gutter || this.xGutter
384
+ const contentLength = options.contentLength || this.contentWidth
385
+ const toLabel = options.toLabel || this.toXLabel
386
+ const measureLength = options.measureLength || (v => this.ctx.measureText(v).width)
387
+
388
+ const points = this.getUniqSortedPoints(axis)
389
+ const firstPoint = points[0]
390
+ const lastPoint = points[points.length - 1]
391
+
392
+ if (points.length <= 2) {
393
+ return points.map(p => {
394
+ const value = p[axis]
395
+ const label = toLabel(value)
396
+ const length = measureLength(label)
397
+ return { label, length, value }
398
+ })
399
+ }
400
+
401
+ const firstPointValue = firstPoint[axis]
402
+ const lastPointValue = lastPoint[axis]
403
+ const pointLength = points.length
404
+
405
+ let step = options.step
406
+
407
+ if (isUndef(step)) {
408
+ step = this.getAutoStep(firstPointValue, lastPointValue, pointLength)
409
+ }
410
+
411
+ const [stepStart, stepEnd] = this.getStepStartEnd(step, firstPointValue, lastPointValue)
412
+ const values = range(stepStart, stepEnd + step, step)
413
+
414
+ const valueCount = values.length
415
+ const initialGap = parseInt((valueCount - 2) / 2, 10)
416
+
417
+ const firstValue = values[0]
418
+ const lastValue = values[valueCount - 1]
419
+ const firstLabel = toLabel(firstValue)
420
+ const lastLabel = toLabel(lastValue)
421
+
422
+ let stepRows = [
423
+ { label: firstLabel, length: measureLength(firstLabel), value: firstValue },
424
+ { label: lastLabel, length: measureLength(lastLabel), value: lastValue },
425
+ ]
426
+
427
+ for (let gap = initialGap; gap >= 0; gap--) {
428
+ const { lengthTotal, rows } = this.getLengthTotalData(gap, gutter, values, measureLength, toLabel)
429
+ if (lengthTotal <= contentLength) {
430
+ stepRows = rows
431
+ continue
432
+ }
433
+ return stepRows
434
+ }
435
+ return stepRows
436
+ }
437
+
438
+ refresh() {
439
+ this.raf(() => {
440
+ this.clearPointPos()
441
+ this.setDomWidthIfNeeded()
442
+ this.setCanvasSize(this.canvas)
443
+ this.layers.forEach(layer => this.setCanvasSize(layer.canvas))
444
+ this.setLabelWidths()
445
+ this.setLabelHeights()
446
+ this.setAxisData()
447
+ this.setPointPos()
448
+ this.draw()
449
+ })
450
+ }
451
+
452
+ setLabelHeights() {
453
+ this.xLabelHeight = this.fontSize
454
+ this.yLabelHeight = this.fontSize
455
+ }
456
+
457
+ setLabelWidths() {
458
+
459
+ const { toXLabel, toYLabel, ctx } = this
460
+ const { xLabelWidth, yLabelWidth } = this.pointsArr.flat()
461
+ .filter(p => p)
462
+ .reduce((o, p) => {
463
+
464
+ const { xLabelWidth, yLabelWidth } = o
465
+ const measuredXLabelWidth = ctx.measureText(toXLabel(p.x)).width
466
+ const measuredYLabelWidth = ctx.measureText(toYLabel(p.y)).width
467
+
468
+ return {
469
+ xLabelWidth: (measuredXLabelWidth > xLabelWidth) ? measuredXLabelWidth : xLabelWidth,
470
+ yLabelWidth: (measuredYLabelWidth > yLabelWidth) ? measuredYLabelWidth : yLabelWidth,
471
+ }
472
+ }, {
473
+ xLabelWidth: 0,
474
+ yLabelWidth: 0
475
+ })
476
+
477
+ this.xLabelWidth = xLabelWidth
478
+ this.yLabelWidth = yLabelWidth
479
+ }
480
+
481
+ setAxisData() {
482
+ this.xLabelRows = this.getLabelRows({ step: this.xStep })
483
+ this.yLabelRows = this.getLabelRows({
484
+ axis: 'y',
485
+ step: this.yStep,
486
+ gutter: this.yGutter,
487
+ contentLength: this.contentHeight,
488
+ toLabel: this.toYLabel,
489
+ measureLength: () => this.yLabelHeight
490
+ })
491
+ }
492
+
493
+ setData(pointsArr) {
494
+ this.pointsArr = pointsArr
495
+ this.clearPointPos()
496
+ this.setLabelWidths()
497
+ this.setLabelHeights()
498
+ this.setAxisData()
499
+ this.setPointPos()
500
+ this.raf(() => this.draw())
501
+ }
502
+
503
+ setPointPos() {
504
+ const { firstX, firstY, pointPosMap, xAxisStart, xRatio, yAxisStart, yRatio } = this
505
+
506
+ this.pointsArr.forEach((points, i) => {
507
+ points.forEach(point => {
508
+ const x = xAxisStart + ((point.x - firstX) / xRatio)
509
+ const y = yAxisStart - ((point.y - firstY) / yRatio)
510
+ const pos = { x, y }
511
+ pointPosMap.set(point, pos)
512
+ })
513
+ })
514
+ }
515
+
516
+ destroy() {
517
+ const { dom, canvas } = this
518
+ const { toXLabel, toYLabel } = this.options
519
+
520
+ if (isDef(toXLabel)) {
521
+ mem.clear(this.toXLabel)
522
+ }
523
+ if (isDef(toYLabel)) {
524
+ mem.clear(this.toYLabel)
525
+ }
526
+ this.clearPointPos()
527
+ this.unbindMedia()
528
+ this.removeAllLayers()
529
+
530
+ if (dom.contains(canvas)) {
531
+ dom.removeChild(canvas)
532
+ dom.style.removeProperty('position')
533
+ }
534
+ }
535
+ }