datavis-glide 4.0.0-PRE.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +45 -0
- package/README.md +129 -0
- package/datavis.js +101 -0
- package/dist/wcdatavis.css +1957 -0
- package/dist/wcdatavis.min.js +1 -0
- package/global-jquery.js +4 -0
- package/ie-fixes.js +13 -0
- package/index.js +70 -0
- package/meteor.js +1 -0
- package/package.json +102 -0
- package/src/flags.js +6 -0
- package/src/graph.js +1079 -0
- package/src/graph_renderer.js +85 -0
- package/src/grid.js +2777 -0
- package/src/grid_control.js +1957 -0
- package/src/grid_filter.js +1073 -0
- package/src/grid_renderer.js +276 -0
- package/src/group_fun_win.js +121 -0
- package/src/lang/en-US.js +188 -0
- package/src/lang/es-MX.js +188 -0
- package/src/lang/fr-FR.js +188 -0
- package/src/lang/id-ID.js +188 -0
- package/src/lang/nl-NL.js +188 -0
- package/src/lang/pt-BR.js +188 -0
- package/src/lang/ru-RU.js +188 -0
- package/src/lang/th-TH.js +188 -0
- package/src/lang/vi-VN.js +188 -0
- package/src/lang/zh-Hans-CN.js +188 -0
- package/src/operations_palette.js +176 -0
- package/src/prefs_modules.js +132 -0
- package/src/reg/graph_renderer.js +17 -0
- package/src/renderers/graph/chartjs.js +457 -0
- package/src/renderers/graph/google.js +584 -0
- package/src/renderers/graph/jit.js +61 -0
- package/src/renderers/graph/svelte-gantt.js +168 -0
- package/src/renderers/grid/dummy.js +79 -0
- package/src/renderers/grid/handlebars.js +217 -0
- package/src/renderers/grid/squirrelly.js +215 -0
- package/src/renderers/grid/table/group_detail.js +1404 -0
- package/src/renderers/grid/table/group_summary.js +380 -0
- package/src/renderers/grid/table/pivot.js +915 -0
- package/src/renderers/grid/table/plain.js +1592 -0
- package/src/renderers/grid/table.js +2510 -0
- package/src/trans.js +101 -0
- package/src/ui/collapsible.js +234 -0
- package/src/ui/filters/date.js +283 -0
- package/src/ui/grid_filter.js +398 -0
- package/src/ui/popup_menu.js +224 -0
- package/src/ui/popup_window.js +572 -0
- package/src/ui/slider.js +156 -0
- package/src/ui/tabs.js +202 -0
- package/src/ui/templates.js +131 -0
- package/src/ui/toolbar.js +63 -0
- package/src/ui/toolbars/grid.js +873 -0
- package/src/ui/windows/col_config.js +341 -0
- package/src/ui/windows/debug.js +164 -0
- package/src/ui/windows/grid_table_opts.js +139 -0
- package/src/util/handlebars.js +158 -0
- package/src/util/jquery.js +630 -0
- package/src/util/misc.js +1058 -0
- package/src/util/squirrelly.js +155 -0
- package/wcdatavis.css +1601 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import jQuery from 'jquery';
|
|
2
|
+
import {
|
|
3
|
+
icon,
|
|
4
|
+
makeSubclass,
|
|
5
|
+
mixinEventHandling,
|
|
6
|
+
} from '../util/misc.js';
|
|
7
|
+
import { trans } from '../trans.js';
|
|
8
|
+
|
|
9
|
+
// PopupWindow {{{1
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A modal popup window that replaces jQuery UI's dialog widget.
|
|
13
|
+
*
|
|
14
|
+
* The window is always modal (with a backdrop overlay), supports dragging by the titlebar,
|
|
15
|
+
* resizing via a corner handle, and has a built-in 100ms fade transition.
|
|
16
|
+
*
|
|
17
|
+
* @class
|
|
18
|
+
*
|
|
19
|
+
* @param {object} [options]
|
|
20
|
+
* @param {string} [options.title] Window title text.
|
|
21
|
+
* @param {number|string} [options.width=600] CSS width for the window.
|
|
22
|
+
* @param {number|null} [options.maxHeight] Max-height for the content area.
|
|
23
|
+
* @param {object} [options.position] Positioning spec: { my, at, of }.
|
|
24
|
+
* @param {Element|jQuery} [options.content] Initial content element.
|
|
25
|
+
* @param {Array} [options.buttons] Array of { icon, label, callback } specs.
|
|
26
|
+
*
|
|
27
|
+
* @property {object} ui
|
|
28
|
+
* @property {jQuery} ui.overlay
|
|
29
|
+
* @property {jQuery} ui.win
|
|
30
|
+
* @property {jQuery} ui.titlebar
|
|
31
|
+
* @property {jQuery} ui.title
|
|
32
|
+
* @property {jQuery} ui.closeBtn
|
|
33
|
+
* @property {jQuery} ui.content
|
|
34
|
+
* @property {jQuery} ui.buttonbar
|
|
35
|
+
* @property {jQuery} ui.resize
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
var PopupWindow = makeSubclass('PopupWindow', Object, function (options) {
|
|
39
|
+
var self = this;
|
|
40
|
+
|
|
41
|
+
var opts = options || {};
|
|
42
|
+
|
|
43
|
+
self._options = {
|
|
44
|
+
title: opts.title || '',
|
|
45
|
+
width: opts.width != null ? opts.width : 600,
|
|
46
|
+
maxHeight: opts.maxHeight || null,
|
|
47
|
+
position: opts.position || { my: 'center', at: 'center', of: window }
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
self.ui = {};
|
|
51
|
+
self._destroyed = false;
|
|
52
|
+
self._prevFocus = null;
|
|
53
|
+
self._dragState = null;
|
|
54
|
+
self._resizeState = null;
|
|
55
|
+
|
|
56
|
+
self._build();
|
|
57
|
+
|
|
58
|
+
if (opts.content) {
|
|
59
|
+
self.setContent(opts.content);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (opts.buttons) {
|
|
63
|
+
self.setButtons(opts.buttons);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Events {{{2
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fired when the window is opened.
|
|
71
|
+
* @event PopupWindow#open
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fired when the window is closed.
|
|
76
|
+
* @event PopupWindow#close
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
mixinEventHandling(PopupWindow, ['open', 'close']);
|
|
80
|
+
|
|
81
|
+
// #_build {{{2
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build the DOM structure for the popup window.
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
PopupWindow.prototype._build = function () {
|
|
89
|
+
var self = this;
|
|
90
|
+
|
|
91
|
+
// Generate a unique ID for aria-labelledby.
|
|
92
|
+
var titleId = 'wcdv-pw-title-' + Math.random().toString(36).substr(2, 9);
|
|
93
|
+
|
|
94
|
+
self.ui.title = jQuery('<span>', {
|
|
95
|
+
'class': 'wcdv-popup-window-title',
|
|
96
|
+
id: titleId
|
|
97
|
+
}).text(self._options.title);
|
|
98
|
+
|
|
99
|
+
self.ui.closeBtn = jQuery('<button>', {
|
|
100
|
+
'class': 'wcdv-popup-window-close',
|
|
101
|
+
'aria-label': trans('POPUP_WINDOW.CLOSE'),
|
|
102
|
+
type: 'button'
|
|
103
|
+
}).append(icon('x'))
|
|
104
|
+
.on('click', function () {
|
|
105
|
+
self.close();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
self.ui.titlebar = jQuery('<div>', {
|
|
109
|
+
'class': 'wcdv-popup-window-titlebar'
|
|
110
|
+
}).append(self.ui.title)
|
|
111
|
+
.append(self.ui.closeBtn);
|
|
112
|
+
|
|
113
|
+
self.ui.content = jQuery('<div>', {
|
|
114
|
+
'class': 'wcdv-popup-window-content'
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (self._options.maxHeight) {
|
|
118
|
+
self.ui.content.css('max-height', self._options.maxHeight + 'px');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
self.ui.buttonbar = jQuery('<div>', {
|
|
122
|
+
'class': 'wcdv-popup-window-buttonbar wcdv_button_bar'
|
|
123
|
+
}).hide();
|
|
124
|
+
|
|
125
|
+
self.ui.resize = jQuery('<div>', {
|
|
126
|
+
'class': 'wcdv-popup-window-resize'
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
self.ui.win = jQuery('<div>', {
|
|
130
|
+
'class': 'wcdv-popup-window',
|
|
131
|
+
role: 'dialog',
|
|
132
|
+
'aria-modal': 'true',
|
|
133
|
+
'aria-labelledby': titleId,
|
|
134
|
+
tabindex: '-1'
|
|
135
|
+
}).css('width', self._options.width)
|
|
136
|
+
.append(self.ui.titlebar)
|
|
137
|
+
.append(self.ui.content)
|
|
138
|
+
.append(self.ui.buttonbar)
|
|
139
|
+
.append(self.ui.resize);
|
|
140
|
+
|
|
141
|
+
self.ui.overlay = jQuery('<div>', {
|
|
142
|
+
'class': 'wcdv-popup-window-overlay'
|
|
143
|
+
}).append(self.ui.win);
|
|
144
|
+
|
|
145
|
+
self._initDrag();
|
|
146
|
+
self._initResize();
|
|
147
|
+
self._initKeyboard();
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// #open {{{2
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Open the popup window, appending it to the document body and fading it in.
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
PopupWindow.prototype.open = function () {
|
|
157
|
+
var self = this;
|
|
158
|
+
|
|
159
|
+
if (self._destroyed) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
self._prevFocus = document.activeElement;
|
|
164
|
+
|
|
165
|
+
// Force the initial state for the CSS transition.
|
|
166
|
+
self.ui.overlay.css('opacity', 0);
|
|
167
|
+
self.ui.overlay.appendTo(document.body);
|
|
168
|
+
|
|
169
|
+
self._applyPosition();
|
|
170
|
+
|
|
171
|
+
// Trigger reflow, then fade in.
|
|
172
|
+
void self.ui.overlay[0].offsetHeight;
|
|
173
|
+
self.ui.overlay.css('opacity', 1);
|
|
174
|
+
|
|
175
|
+
self.ui.win.focus();
|
|
176
|
+
self._initFocusTrap();
|
|
177
|
+
|
|
178
|
+
self.fire('open');
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// #close {{{2
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Close the popup window with a fade-out, then remove it from the DOM.
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
PopupWindow.prototype.close = function () {
|
|
188
|
+
var self = this;
|
|
189
|
+
|
|
190
|
+
if (self._destroyed) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
self.ui.overlay.css({
|
|
195
|
+
'opacity': 0,
|
|
196
|
+
'pointer-events': 'none'
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
setTimeout(function () {
|
|
200
|
+
self.ui.overlay.detach();
|
|
201
|
+
self.ui.overlay.css('pointer-events', '');
|
|
202
|
+
|
|
203
|
+
// Restore focus to the previously focused element.
|
|
204
|
+
if (self._prevFocus && self._prevFocus.focus) {
|
|
205
|
+
self._prevFocus.focus();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
self.fire('close');
|
|
209
|
+
}, 100);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// #destroy {{{2
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Destroy the popup window. Removes all DOM elements and unbinds all events.
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
PopupWindow.prototype.destroy = function () {
|
|
219
|
+
var self = this;
|
|
220
|
+
|
|
221
|
+
if (self._destroyed) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
self._destroyed = true;
|
|
226
|
+
|
|
227
|
+
self.ui.overlay.remove();
|
|
228
|
+
self.ui = {};
|
|
229
|
+
self._prevFocus = null;
|
|
230
|
+
self._dragState = null;
|
|
231
|
+
self._resizeState = null;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// #setTitle {{{2
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Set the title text of the popup window.
|
|
238
|
+
*
|
|
239
|
+
* @param {string} text
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
PopupWindow.prototype.setTitle = function (text) {
|
|
243
|
+
var self = this;
|
|
244
|
+
|
|
245
|
+
self.ui.title.text(text);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// #setContent {{{2
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Set the content of the popup window. The content area is emptied and the given element is
|
|
252
|
+
* appended.
|
|
253
|
+
*
|
|
254
|
+
* @param {Element|jQuery} elt
|
|
255
|
+
* A DOM element or jQuery object to place inside the content area.
|
|
256
|
+
*/
|
|
257
|
+
|
|
258
|
+
PopupWindow.prototype.setContent = function (elt) {
|
|
259
|
+
var self = this;
|
|
260
|
+
|
|
261
|
+
self.ui.content.empty().append(elt);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// #setButtons {{{2
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Set the buttons displayed at the bottom of the popup window.
|
|
268
|
+
*
|
|
269
|
+
* @param {Array} buttonSpecs
|
|
270
|
+
* Array of objects, each with:
|
|
271
|
+
* - {string} icon - Lucide icon name
|
|
272
|
+
* - {string} label - Button text
|
|
273
|
+
* - {function} callback - Click handler
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
PopupWindow.prototype.setButtons = function (buttonSpecs) {
|
|
277
|
+
var self = this;
|
|
278
|
+
|
|
279
|
+
self.ui.buttonbar.empty();
|
|
280
|
+
|
|
281
|
+
if (!buttonSpecs || buttonSpecs.length === 0) {
|
|
282
|
+
self.ui.buttonbar.hide();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (var i = 0; i < buttonSpecs.length; i++) {
|
|
287
|
+
(function (spec) {
|
|
288
|
+
var btn = jQuery('<button>', {
|
|
289
|
+
'class': 'wcdv-popup-window-btn',
|
|
290
|
+
type: 'button'
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (spec.icon) {
|
|
294
|
+
btn.append(icon(spec.icon));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (spec.label) {
|
|
298
|
+
btn.append(jQuery('<span>').text(spec.label));
|
|
299
|
+
btn.attr('title', spec.label);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (spec.attrs) {
|
|
303
|
+
btn.attr(spec.attrs);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
btn.on('click', function () {
|
|
307
|
+
if (typeof spec.callback === 'function') {
|
|
308
|
+
spec.callback();
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
self.ui.buttonbar.append(btn);
|
|
313
|
+
})(buttonSpecs[i]);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
self.ui.buttonbar.show();
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// #_applyPosition {{{2
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Position the window according to the position option.
|
|
323
|
+
* Supports a simplified version of jQuery UI's { my, at, of } spec.
|
|
324
|
+
* @private
|
|
325
|
+
*/
|
|
326
|
+
|
|
327
|
+
PopupWindow.prototype._applyPosition = function () {
|
|
328
|
+
var self = this;
|
|
329
|
+
|
|
330
|
+
var pos = self._options.position;
|
|
331
|
+
var win = self.ui.win[0];
|
|
332
|
+
var winRect = win.getBoundingClientRect();
|
|
333
|
+
var target = pos.of;
|
|
334
|
+
var targetRect;
|
|
335
|
+
|
|
336
|
+
if (target === window || target == null) {
|
|
337
|
+
targetRect = {
|
|
338
|
+
top: 0,
|
|
339
|
+
left: 0,
|
|
340
|
+
bottom: window.innerHeight,
|
|
341
|
+
right: window.innerWidth,
|
|
342
|
+
width: window.innerWidth,
|
|
343
|
+
height: window.innerHeight
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
var targetElt = target instanceof jQuery ? target[0] : target;
|
|
348
|
+
targetRect = targetElt.getBoundingClientRect();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Compute the anchor point on the target ('at').
|
|
352
|
+
var atParts = (pos.at || 'center').split(/\s+/);
|
|
353
|
+
var atH = atParts[0] || 'center';
|
|
354
|
+
var atV = atParts.length > 1 ? atParts[1] : atH;
|
|
355
|
+
var anchorX = self._resolveH(atH, targetRect);
|
|
356
|
+
var anchorY = self._resolveV(atV, targetRect);
|
|
357
|
+
|
|
358
|
+
// Compute the offset on the window ('my').
|
|
359
|
+
var myParts = (pos.my || 'center').split(/\s+/);
|
|
360
|
+
var myH = myParts[0] || 'center';
|
|
361
|
+
var myV = myParts.length > 1 ? myParts[1] : myH;
|
|
362
|
+
var offsetX = self._resolveH(myH, { left: 0, right: winRect.width, width: winRect.width });
|
|
363
|
+
var offsetY = self._resolveV(myV, { top: 0, bottom: winRect.height, height: winRect.height });
|
|
364
|
+
|
|
365
|
+
var left = anchorX - offsetX;
|
|
366
|
+
var top = anchorY - offsetY;
|
|
367
|
+
|
|
368
|
+
// Clamp to viewport.
|
|
369
|
+
var vpW = window.innerWidth;
|
|
370
|
+
var vpH = window.innerHeight;
|
|
371
|
+
if (left + winRect.width > vpW) {
|
|
372
|
+
left = vpW - winRect.width;
|
|
373
|
+
}
|
|
374
|
+
if (left < 0) {
|
|
375
|
+
left = 0;
|
|
376
|
+
}
|
|
377
|
+
if (top + winRect.height > vpH) {
|
|
378
|
+
top = vpH - winRect.height;
|
|
379
|
+
}
|
|
380
|
+
if (top < 0) {
|
|
381
|
+
top = 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
self.ui.win.css({
|
|
385
|
+
position: 'fixed',
|
|
386
|
+
left: left + 'px',
|
|
387
|
+
top: top + 'px'
|
|
388
|
+
});
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// #_resolveH {{{2
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Resolve a horizontal keyword to a pixel value within a rect.
|
|
395
|
+
* @private
|
|
396
|
+
*/
|
|
397
|
+
|
|
398
|
+
PopupWindow.prototype._resolveH = function (keyword, rect) {
|
|
399
|
+
if (keyword === 'left') {
|
|
400
|
+
return rect.left;
|
|
401
|
+
}
|
|
402
|
+
if (keyword === 'right') {
|
|
403
|
+
return rect.left + rect.width;
|
|
404
|
+
}
|
|
405
|
+
// center
|
|
406
|
+
return rect.left + rect.width / 2;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// #_resolveV {{{2
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Resolve a vertical keyword to a pixel value within a rect.
|
|
413
|
+
* @private
|
|
414
|
+
*/
|
|
415
|
+
|
|
416
|
+
PopupWindow.prototype._resolveV = function (keyword, rect) {
|
|
417
|
+
if (keyword === 'top') {
|
|
418
|
+
return rect.top;
|
|
419
|
+
}
|
|
420
|
+
if (keyword === 'bottom') {
|
|
421
|
+
return rect.top + rect.height;
|
|
422
|
+
}
|
|
423
|
+
// center
|
|
424
|
+
return rect.top + rect.height / 2;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// #_initDrag {{{2
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Set up titlebar dragging.
|
|
431
|
+
* @private
|
|
432
|
+
*/
|
|
433
|
+
|
|
434
|
+
PopupWindow.prototype._initDrag = function () {
|
|
435
|
+
var self = this;
|
|
436
|
+
|
|
437
|
+
self.ui.titlebar.on('mousedown', function (e) {
|
|
438
|
+
// Don't drag when clicking the close button.
|
|
439
|
+
if (jQuery(e.target).closest('.wcdv-popup-window-close').length) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
e.preventDefault();
|
|
444
|
+
var winPos = self.ui.win[0].getBoundingClientRect();
|
|
445
|
+
self._dragState = {
|
|
446
|
+
startX: e.clientX,
|
|
447
|
+
startY: e.clientY,
|
|
448
|
+
origLeft: winPos.left,
|
|
449
|
+
origTop: winPos.top
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
jQuery(document).on('mousemove.wcdvpwdrag', function (e) {
|
|
453
|
+
if (!self._dragState) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
var dx = e.clientX - self._dragState.startX;
|
|
457
|
+
var dy = e.clientY - self._dragState.startY;
|
|
458
|
+
self.ui.win.css({
|
|
459
|
+
left: (self._dragState.origLeft + dx) + 'px',
|
|
460
|
+
top: (self._dragState.origTop + dy) + 'px'
|
|
461
|
+
});
|
|
462
|
+
}).on('mouseup.wcdvpwdrag', function () {
|
|
463
|
+
self._dragState = null;
|
|
464
|
+
jQuery(document).off('.wcdvpwdrag');
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// #_initResize {{{2
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Set up corner resize handle.
|
|
473
|
+
* @private
|
|
474
|
+
*/
|
|
475
|
+
|
|
476
|
+
PopupWindow.prototype._initResize = function () {
|
|
477
|
+
var self = this;
|
|
478
|
+
|
|
479
|
+
self.ui.resize.on('mousedown', function (e) {
|
|
480
|
+
e.preventDefault();
|
|
481
|
+
var winRect = self.ui.win[0].getBoundingClientRect();
|
|
482
|
+
self._resizeState = {
|
|
483
|
+
startX: e.clientX,
|
|
484
|
+
startY: e.clientY,
|
|
485
|
+
origWidth: winRect.width,
|
|
486
|
+
origHeight: winRect.height
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
jQuery(document).on('mousemove.wcdvpwresize', function (e) {
|
|
490
|
+
if (!self._resizeState) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
var dx = e.clientX - self._resizeState.startX;
|
|
494
|
+
var dy = e.clientY - self._resizeState.startY;
|
|
495
|
+
var newW = Math.max(200, self._resizeState.origWidth + dx);
|
|
496
|
+
var newH = Math.max(100, self._resizeState.origHeight + dy);
|
|
497
|
+
self.ui.win.css({
|
|
498
|
+
width: newW + 'px',
|
|
499
|
+
height: newH + 'px'
|
|
500
|
+
});
|
|
501
|
+
}).on('mouseup.wcdvpwresize', function () {
|
|
502
|
+
self._resizeState = null;
|
|
503
|
+
jQuery(document).off('.wcdvpwresize');
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// #_initKeyboard {{{2
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Set up keyboard handling (ESC to close).
|
|
512
|
+
* @private
|
|
513
|
+
*/
|
|
514
|
+
|
|
515
|
+
PopupWindow.prototype._initKeyboard = function () {
|
|
516
|
+
var self = this;
|
|
517
|
+
|
|
518
|
+
self.ui.overlay.on('keydown', function (e) {
|
|
519
|
+
if (e.keyCode === 27) { // ESC
|
|
520
|
+
e.stopPropagation();
|
|
521
|
+
self.close();
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// #_initFocusTrap {{{2
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Trap focus within the popup window so that Tab and Shift+Tab cycle through
|
|
530
|
+
* focusable elements inside the dialog.
|
|
531
|
+
* @private
|
|
532
|
+
*/
|
|
533
|
+
|
|
534
|
+
PopupWindow.prototype._initFocusTrap = function () {
|
|
535
|
+
var self = this;
|
|
536
|
+
|
|
537
|
+
self.ui.overlay.off('keydown.wcdvpwfocus').on('keydown.wcdvpwfocus', function (e) {
|
|
538
|
+
if (e.keyCode !== 9) { // Tab
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
var focusable = self.ui.win.find(
|
|
543
|
+
'a[href], button:not([disabled]), textarea:not([disabled]), ' +
|
|
544
|
+
'input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
if (focusable.length === 0) {
|
|
548
|
+
e.preventDefault();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
var first = focusable.first()[0];
|
|
553
|
+
var last = focusable.last()[0];
|
|
554
|
+
|
|
555
|
+
if (e.shiftKey) {
|
|
556
|
+
if (document.activeElement === first) {
|
|
557
|
+
e.preventDefault();
|
|
558
|
+
last.focus();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
if (document.activeElement === last) {
|
|
563
|
+
e.preventDefault();
|
|
564
|
+
first.focus();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// }}}
|
|
571
|
+
|
|
572
|
+
export { PopupWindow };
|
package/src/ui/slider.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import jQuery from 'jquery';
|
|
2
|
+
import {
|
|
3
|
+
makeSubclass,
|
|
4
|
+
mixinEventHandling,
|
|
5
|
+
} from '../util/misc.js';
|
|
6
|
+
|
|
7
|
+
// Constructor {{{1
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a new slider instance. A slider is a UI element that slides in from the right side of the
|
|
11
|
+
* screen. It's typically used to display details of a row, but could be adapted to other uses.
|
|
12
|
+
*
|
|
13
|
+
* @class
|
|
14
|
+
* @property {object} ui
|
|
15
|
+
* @property {jQuery} ui.root
|
|
16
|
+
* @property {jQuery} ui.header
|
|
17
|
+
* @property {jQuery} ui.closeBtn
|
|
18
|
+
* @property {jQuery} ui.body
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
var Slider = makeSubclass('Slider', Object, function () {
|
|
22
|
+
var self = this;
|
|
23
|
+
|
|
24
|
+
self.ui = {};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Events {{{2
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Fired when the slider is shown.
|
|
31
|
+
*
|
|
32
|
+
* @event Slider#show
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fired when the slider is hidden.
|
|
37
|
+
*
|
|
38
|
+
* @event Slider#hide
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
mixinEventHandling(Slider, ['show', 'hide']);
|
|
42
|
+
|
|
43
|
+
// #draw {{{2
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Draw the slider and append it to the specified element.
|
|
47
|
+
*
|
|
48
|
+
* @param {jQuery} root
|
|
49
|
+
* Where to put the new slider.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
Slider.prototype.draw = function (root) {
|
|
53
|
+
var self = this;
|
|
54
|
+
|
|
55
|
+
if (!(root instanceof jQuery)) {
|
|
56
|
+
throw new Error('Call Error: `root` must be an instance of jQuery');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
self.ui.root = jQuery('<div>', {
|
|
60
|
+
'class': 'wcdv-slider'
|
|
61
|
+
});
|
|
62
|
+
self.ui.header = jQuery('<h1>');
|
|
63
|
+
self.ui.closeBtn = jQuery('<button>', {
|
|
64
|
+
'class': 'wcdv-slider-close'
|
|
65
|
+
}).text('×').on('click', function () {
|
|
66
|
+
self.hide();
|
|
67
|
+
});
|
|
68
|
+
self.ui.body = jQuery('<div>', {
|
|
69
|
+
'class': 'wcdv-slider-body'
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
self.ui.root
|
|
73
|
+
.append(
|
|
74
|
+
jQuery('<div>')
|
|
75
|
+
.addClass('wcdv-slider-header')
|
|
76
|
+
.append(self.ui.header)
|
|
77
|
+
.append(self.ui.closeBtn)
|
|
78
|
+
)
|
|
79
|
+
.append(self.ui.body)
|
|
80
|
+
.appendTo(root);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// #show {{{2
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Show the slider if it's currently invisible.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
Slider.prototype.show = function () {
|
|
90
|
+
var self = this;
|
|
91
|
+
|
|
92
|
+
if (!self.ui.root.hasClass('show')) {
|
|
93
|
+
self.ui.root.addClass('show');
|
|
94
|
+
self.fire('show');
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// #hide {{{2
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Hide the slider if it's currently visible.
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
Slider.prototype.hide = function () {
|
|
105
|
+
var self = this;
|
|
106
|
+
|
|
107
|
+
if (self.ui.root.hasClass('show')) {
|
|
108
|
+
self.ui.root.removeClass('show');
|
|
109
|
+
self.fire('hide');
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// #setHeader {{{2
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Sets the header of the slider.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} s
|
|
119
|
+
* The header's text will be replaced with this.
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
Slider.prototype.setHeader = function (s) {
|
|
123
|
+
var self = this;
|
|
124
|
+
|
|
125
|
+
self.ui.header.text(s);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// #setBody {{{2
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Sets the body of the slider.
|
|
132
|
+
*
|
|
133
|
+
* @param {jQuery} elt
|
|
134
|
+
* This will replace the current slider body.
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
Slider.prototype.setBody = function (elt) {
|
|
138
|
+
var self = this;
|
|
139
|
+
|
|
140
|
+
self.ui.body.html('');
|
|
141
|
+
self.ui.body.append(elt);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// #destroy {{{2
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Removes the slider element from the page.
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
Slider.prototype.destroy = function () {
|
|
151
|
+
var self = this;
|
|
152
|
+
|
|
153
|
+
self.ui.root.remove();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export default Slider;
|