griffincss-ui 0.27.0 → 0.28.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/dist/griffincss-ui.js +1 -1
- package/dist/griffincss-ui.js.map +1 -1
- package/dist/griffinjs-combobox.js +1 -1
- package/dist/griffinjs-combobox.js.map +1 -1
- package/dist/griffinjs-core.js +1 -1
- package/dist/griffinjs-core.js.map +1 -1
- package/dist/griffinjs-counter.js +1 -1
- package/dist/griffinjs-counter.js.map +1 -1
- package/dist/griffinjs-datetime.js +1 -1
- package/dist/griffinjs-datetime.js.map +1 -1
- package/dist/griffinjs-dialog.js +1 -1
- package/dist/griffinjs-dialog.js.map +1 -1
- package/dist/griffinjs-dropdown.js +1 -1
- package/dist/griffinjs-dropdown.js.map +1 -1
- package/dist/griffinjs-fields.js +1 -1
- package/dist/griffinjs-fields.js.map +1 -1
- package/dist/griffinjs-file.js +1 -1
- package/dist/griffinjs-file.js.map +1 -1
- package/dist/griffinjs-lightbox.js +1 -1
- package/dist/griffinjs-lightbox.js.map +1 -1
- package/dist/griffinjs-loader.js +1 -1
- package/dist/griffinjs-loader.js.map +1 -1
- package/dist/griffinjs-mask.js +1 -1
- package/dist/griffinjs-mask.js.map +1 -1
- package/dist/griffinjs-megamenu.js +1 -1
- package/dist/griffinjs-megamenu.js.map +1 -1
- package/dist/griffinjs-otp.js +1 -1
- package/dist/griffinjs-otp.js.map +1 -1
- package/dist/griffinjs-phone.js +1 -1
- package/dist/griffinjs-phone.js.map +1 -1
- package/dist/griffinjs-range.js +1 -1
- package/dist/griffinjs-range.js.map +1 -1
- package/dist/griffinjs-rating.js +1 -1
- package/dist/griffinjs-rating.js.map +1 -1
- package/dist/griffinjs-scroll.js +1 -1
- package/dist/griffinjs-scroll.js.map +1 -1
- package/dist/griffinjs-slider.js +1 -1
- package/dist/griffinjs-slider.js.map +1 -1
- package/dist/griffinjs-sortable.js +1 -1
- package/dist/griffinjs-sortable.js.map +1 -1
- package/dist/griffinjs-tooltip.js +1 -1
- package/dist/griffinjs-tooltip.js.map +1 -1
- package/dist/griffinjs-track.js +1 -1
- package/dist/griffinjs-track.js.map +1 -1
- package/dist/griffinjs-validate.js +1 -1
- package/dist/griffinjs-validate.js.map +1 -1
- package/dist/griffinjs.js +1 -1
- package/dist/griffinjs.js.map +1 -1
- package/package.json +2 -2
- package/src/griffincss-ui.js +17 -9
- package/src/griffinjs/core/griffinjs-core.js +18 -11
- package/src/griffinjs/core/loader.js +6 -3
- package/src/griffinjs/core/options.js +1 -1
- package/src/griffinjs/core/track.js +1 -1
- package/src/griffinjs/engines/scroll.js +14 -0
- package/src/griffinjs/fields/counter.js +4 -2
- package/src/griffinjs/fields/datetime.js +5 -4
- package/src/griffinjs/fields/file.js +6 -2
- package/src/griffinjs/fields/mask.js +1 -1
- package/src/griffinjs/fields/otp.js +1 -1
- package/src/griffinjs/fields/phone.js +3 -3
- package/src/griffinjs/fields/rating.js +1 -1
- package/src/griffinjs/fields/validate.js +7 -4
- package/src/griffinjs/widgets/combobox.js +52 -7
- package/src/griffinjs/widgets/dialog.js +58 -13
- package/src/griffinjs/widgets/dropdown.js +1 -1
- package/src/griffinjs/widgets/lightbox.js +108 -19
- package/src/griffinjs/widgets/megamenu.js +45 -9
- package/src/griffinjs/widgets/range.js +6 -3
- package/src/griffinjs/widgets/slider.js +1 -1
- package/src/griffinjs/widgets/sortable.js +1 -1
- package/src/griffinjs/widgets/tooltip.js +1 -1
|
@@ -9,8 +9,23 @@
|
|
|
9
9
|
* в один кадр без кнопок. Адрес кадра — data-gr-src, без него — href:
|
|
10
10
|
* у видео это разные адреса — во фрейм встаёт только плеер, страницу ролика
|
|
11
11
|
* хостинги во фрейме не показывают, а ссылка без скрипта ведёт именно на неё.
|
|
12
|
-
* Тип — по адресу кадра (картинка, видео, iframe для
|
|
13
|
-
*
|
|
12
|
+
* Тип — по адресу кадра (картинка, видео, iframe для хостов списка кадров)
|
|
13
|
+
* или явно: data-gr-type="iframe".
|
|
14
|
+
*
|
|
15
|
+
* Адрес кадра — http(s), относительный, data: или blob:. Любой другой кадр
|
|
16
|
+
* не открывает: javascript: во фрейме исполнился бы в origin страницы,
|
|
17
|
+
* а data-* редакторы и фильтры CMS не проверяют как ссылку. Отвергнутый
|
|
18
|
+
* кадр из группы выпадает; щелчок по нему остаётся браузеру.
|
|
19
|
+
*
|
|
20
|
+
* Фреймом — только адрес из списка GriffinJS.config.frames: иначе в окне
|
|
21
|
+
* сайта с его адресом в строке встала бы любая страница (форма входа,
|
|
22
|
+
* data: без сервера). Запись — хост (поддомены входят), хост с началом
|
|
23
|
+
* пути или 'self' — свой origin. Список по умолчанию ставит модуль, если
|
|
24
|
+
* сайт не задал свой; push расширяет, присваивание заменяет, читается он
|
|
25
|
+
* при каждом открытии. Каждый кадр — в песочнице: вкладку не уводит,
|
|
26
|
+
* allow-same-origin — только хостам списка (без него плееры не
|
|
27
|
+
* поднимаются), своему origin — никогда: его SVG со <script> получил бы
|
|
28
|
+
* доступ к странице.
|
|
14
29
|
*
|
|
15
30
|
* База без JS — ссылка на большое изображение или на страницу ролика.
|
|
16
31
|
* Окно строится при открытии и уничтожается при закрытии: так видео
|
|
@@ -23,7 +38,13 @@
|
|
|
23
38
|
|
|
24
39
|
var ATTR = 'data-gr-lightbox';
|
|
25
40
|
var VIDEO = /\.(mp4|webm|ogv|mov)(\?|#|$)/i;
|
|
26
|
-
var
|
|
41
|
+
var SCHEMES = /^(https?|data|blob):$/;
|
|
42
|
+
// Набор — по замеру настоящих плееров: allow-presentation им не нужен,
|
|
43
|
+
// а WebKit на него пишет ошибку в консоль.
|
|
44
|
+
var SANDBOX = 'allow-scripts allow-popups allow-popups-to-escape-sandbox';
|
|
45
|
+
var config = G.config;
|
|
46
|
+
|
|
47
|
+
if (!config.frames) config.frames = ['self', 'youtube.com', 'youtu.be', 'vimeo.com', 'rutube.ru', 'vkvideo.ru', 'vk.com/video'];
|
|
27
48
|
|
|
28
49
|
var dialog = null;
|
|
29
50
|
var track = null;
|
|
@@ -31,14 +52,48 @@
|
|
|
31
52
|
var events = G.listeners();
|
|
32
53
|
var listen = events.add;
|
|
33
54
|
|
|
55
|
+
// Адрес по правилам браузера: регистр, пробелы по краям и табы внутри
|
|
56
|
+
// схемы разбор снимает. Не адрес — null.
|
|
57
|
+
function parse(src) {
|
|
58
|
+
try {
|
|
59
|
+
return new URL(src, document.baseURI);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Адрес в списке кадров: 'self' — свой origin, 'host' — хост списка,
|
|
66
|
+
// '' — нет. Хост и путь сверяются с началом записи: хостинг — сам хост
|
|
67
|
+
// или его поддомен, а не подстрока где угодно (…/rutube.ru-logo.png —
|
|
68
|
+
// не плеер). Свой origin — всегда 'self', даже под записью-хостом.
|
|
69
|
+
// Страница с диска: origin у неё и у её файлов непрозрачный ('null'),
|
|
70
|
+
// своё узнаётся по протоколу file:; data: и blob: кадром не бывают.
|
|
71
|
+
function listed(url) {
|
|
72
|
+
if (!url || !/^(https?|file):$/.test(url.protocol)) return '';
|
|
73
|
+
|
|
74
|
+
var own = url.protocol === 'file:' ? location.protocol === 'file:' : url.origin === location.origin;
|
|
75
|
+
var where = (url.hostname + url.pathname).toLowerCase();
|
|
76
|
+
var list = [].concat(config.frames);
|
|
77
|
+
|
|
78
|
+
for (var i = 0; i < list.length; i++) {
|
|
79
|
+
var entry = String(list[i]).toLowerCase();
|
|
80
|
+
var at = where.indexOf(entry.indexOf('/') < 0 ? entry + '/' : entry);
|
|
81
|
+
|
|
82
|
+
if (entry === 'self' ? own : !at || (at > 0 && at < url.hostname.length && where.charAt(at - 1) === '.')) return own ? 'self' : 'host';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Без data-gr-type фреймом угадываются хосты списка, но не свой сайт:
|
|
89
|
+
// свои адреса — картинки и видео.
|
|
34
90
|
function typeOf(node, src) {
|
|
35
91
|
var explicit = node && node.getAttribute && node.getAttribute('data-gr-type');
|
|
36
92
|
|
|
37
93
|
if (explicit) return explicit;
|
|
38
94
|
if (VIDEO.test(src)) return 'video';
|
|
39
|
-
if (EMBED.test(src)) return 'iframe';
|
|
40
95
|
|
|
41
|
-
return 'image';
|
|
96
|
+
return listed(parse(src)) === 'host' ? 'iframe' : 'image';
|
|
42
97
|
}
|
|
43
98
|
|
|
44
99
|
// Элемент разметки → описание кадра. data-gr-src старше href: у видео
|
|
@@ -55,15 +110,19 @@
|
|
|
55
110
|
};
|
|
56
111
|
}
|
|
57
112
|
|
|
113
|
+
// Имя группы сравнивается, а не склеивается в селектор: кавычка в нём
|
|
114
|
+
// роняла бы querySelectorAll, подобранное — собирала бы чужие ссылки.
|
|
58
115
|
function groupOf(node) {
|
|
59
116
|
var name = node.getAttribute(ATTR);
|
|
60
117
|
|
|
61
118
|
if (!name) return [node];
|
|
62
119
|
|
|
63
|
-
var all = document.querySelectorAll('[' + ATTR + '
|
|
120
|
+
var all = document.querySelectorAll('[' + ATTR + ']');
|
|
64
121
|
var out = [];
|
|
65
122
|
|
|
66
|
-
for (var i = 0; i < all.length; i++)
|
|
123
|
+
for (var i = 0; i < all.length; i++) {
|
|
124
|
+
if (all[i].getAttribute(ATTR) === name) out.push(all[i]);
|
|
125
|
+
}
|
|
67
126
|
|
|
68
127
|
return out;
|
|
69
128
|
}
|
|
@@ -86,7 +145,7 @@
|
|
|
86
145
|
}
|
|
87
146
|
|
|
88
147
|
if (item.type === 'iframe') {
|
|
89
|
-
return element('iframe', null, { src: item.src, allow: 'autoplay; fullscreen; picture-in-picture', allowfullscreen: '', title: item.caption || item.alt || 'Видео' });
|
|
148
|
+
return element('iframe', null, { sandbox: SANDBOX + (item.same ? ' allow-same-origin' : ''), src: item.src, allow: 'autoplay; fullscreen; picture-in-picture', allowfullscreen: '', title: item.caption || item.alt || 'Видео' });
|
|
90
149
|
}
|
|
91
150
|
|
|
92
151
|
return element('img', null, { src: item.src, alt: item.alt });
|
|
@@ -97,15 +156,43 @@
|
|
|
97
156
|
// --- Открытие ----------------------------------------------------------------
|
|
98
157
|
|
|
99
158
|
function open(items, index, opts) {
|
|
159
|
+
var list = [];
|
|
160
|
+
var at = index || 0; // номер открываемого в исходной группе
|
|
161
|
+
|
|
162
|
+
// Кадр с отвергнутым адресом выпадает из группы; отвергнут
|
|
163
|
+
// открываемый — окна нет. Новый номер — число принятых до него.
|
|
164
|
+
// Адрес приводится к строке один раз: она проверяется, она же
|
|
165
|
+
// встаёт в кадр — в копию описания, объект вызывающего не меняется.
|
|
166
|
+
// Фрейм — только по списку кадров.
|
|
167
|
+
index = 0;
|
|
168
|
+
|
|
169
|
+
for (var k = 0; k < items.length; k++) {
|
|
170
|
+
var src = String(items[k].src);
|
|
171
|
+
var url = parse(src);
|
|
172
|
+
// Схема — http(s), data:, blob: или та же, что у страницы: с диска
|
|
173
|
+
// свои файлы — file:, относительный адрес иначе не открылся бы.
|
|
174
|
+
var kind = url && (SCHEMES.test(url.protocol) || url.protocol === location.protocol) && (items[k].type === 'iframe' ? listed(url) : 'any');
|
|
175
|
+
|
|
176
|
+
if (!kind) {
|
|
177
|
+
G.warn('lightbox: frame address refused: ' + src + ' (iframe: GriffinJS.config.frames)');
|
|
178
|
+
|
|
179
|
+
if (k === at) return null;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (k < at) index++;
|
|
184
|
+
list.push(Object.assign({}, items[k], { src: src, same: kind === 'host' }));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!list.length) return null;
|
|
188
|
+
|
|
100
189
|
close();
|
|
101
190
|
|
|
102
191
|
opts = opts || {};
|
|
103
|
-
|
|
192
|
+
items = list;
|
|
104
193
|
|
|
105
194
|
var n = items.length;
|
|
106
195
|
|
|
107
|
-
if (n === 0) return null;
|
|
108
|
-
|
|
109
196
|
dialog = element('dialog', 'gr-modal gr-lightbox', { 'aria-label': opts.label || 'Просмотр' });
|
|
110
197
|
|
|
111
198
|
var trackEl = element('div', 'gr-track gr-lightbox-track', { tabindex: '0' });
|
|
@@ -169,6 +256,9 @@
|
|
|
169
256
|
|
|
170
257
|
document.body.appendChild(dialog);
|
|
171
258
|
|
|
259
|
+
if (typeof dialog.showModal === 'function') dialog.showModal();
|
|
260
|
+
else dialog.setAttribute('open', '');
|
|
261
|
+
|
|
172
262
|
if (n > 1) {
|
|
173
263
|
track = G.track(trackEl, { loop: true, index: index });
|
|
174
264
|
listen(trackEl, G.track.CHANGE, function (event) {
|
|
@@ -176,15 +266,10 @@
|
|
|
176
266
|
});
|
|
177
267
|
}
|
|
178
268
|
|
|
179
|
-
if (typeof dialog.showModal === 'function') dialog.showModal();
|
|
180
|
-
else dialog.setAttribute('open', '');
|
|
181
|
-
|
|
182
269
|
return api;
|
|
183
270
|
}
|
|
184
271
|
|
|
185
272
|
function openFrom(node) {
|
|
186
|
-
trigger = node;
|
|
187
|
-
|
|
188
273
|
var group = groupOf(node);
|
|
189
274
|
var items = [];
|
|
190
275
|
var index = 0;
|
|
@@ -194,7 +279,11 @@
|
|
|
194
279
|
items.push(itemOf(group[i]));
|
|
195
280
|
}
|
|
196
281
|
|
|
197
|
-
|
|
282
|
+
var opened = open(items, index);
|
|
283
|
+
|
|
284
|
+
if (opened) trigger = node;
|
|
285
|
+
|
|
286
|
+
return opened;
|
|
198
287
|
}
|
|
199
288
|
|
|
200
289
|
// --- Закрытие ----------------------------------------------------------------
|
|
@@ -241,10 +330,10 @@
|
|
|
241
330
|
|
|
242
331
|
function onClick(event, node) {
|
|
243
332
|
// Ctrl/Cmd/средняя кнопка — читатель хочет новую вкладку, а не окно.
|
|
244
|
-
|
|
333
|
+
// Кадр не открылся — ссылка работает как без скрипта.
|
|
334
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.button || !openFrom(node)) return false;
|
|
245
335
|
|
|
246
336
|
if (event.preventDefault) event.preventDefault();
|
|
247
|
-
openFrom(node);
|
|
248
337
|
|
|
249
338
|
return true;
|
|
250
339
|
}
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
* закрывает и возвращает фокус на заголовок;
|
|
28
28
|
* * закрытие щелчком мимо полосы и уходом фокуса;
|
|
29
29
|
* * ajax-панели: data-gr-src на пункте грузится при первом открытии,
|
|
30
|
-
* состояния loading → ready | error пишутся на
|
|
30
|
+
* состояния loading → ready | error пишутся на пункт. Только ответ
|
|
31
|
+
* своего origin с типом text/html: чужой адрес, data: и прочие
|
|
32
|
+
* схемы — error без запроса;
|
|
31
33
|
* * закрытие с состоянием (Этап 48d): панель, чей уход тема анимирует,
|
|
32
34
|
* не пропадает в первом же кадре — на пункт ставится
|
|
33
35
|
* data-gr-state="closing", open снимается по transitionend/animationend
|
|
@@ -98,6 +100,44 @@
|
|
|
98
100
|
return document.activeElement === node;
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
// Фрагмент — только ответ своего origin с типом text/html: разметку
|
|
104
|
+
// из innerHTML браузер оживляет, и <img onerror> чужого ответа исполнился
|
|
105
|
+
// бы в origin страницы. Адрес — http(s) своего origin (у data: origin —
|
|
106
|
+
// строка 'null'); редирект на чужой отклоняет mode: 'same-origin'; тип
|
|
107
|
+
// ответа отсекает свой JSON или текст, отражающий запрос. Тип — целиком:
|
|
108
|
+
// text/htmlx — не HTML, а склейку двух заголовков («text/html,
|
|
109
|
+
// application/json») браузер читает по последнему типу.
|
|
110
|
+
function fragment(src) {
|
|
111
|
+
var url = null;
|
|
112
|
+
|
|
113
|
+
try { url = new URL(src, document.baseURI); } catch (e) { /* не адрес */ }
|
|
114
|
+
|
|
115
|
+
if (!url || !/^https?:$/.test(url.protocol) || url.origin !== location.origin) {
|
|
116
|
+
return Promise.reject(new Error('foreign origin'));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return fetch(url.href, { mode: 'same-origin' }).then(function (response) {
|
|
120
|
+
if (!response.ok) throw new Error('HTTP ' + response.status);
|
|
121
|
+
if (!/^text\/html\s*(;[^,]*)?$/i.test(response.headers.get('content-type'))) throw new Error('not text/html');
|
|
122
|
+
|
|
123
|
+
return response.text();
|
|
124
|
+
}).then(function (html) {
|
|
125
|
+
// Кусок разметки — без meta, base и link: refresh увёл бы страницу
|
|
126
|
+
// без щелчка, base сменил бы базу адресов всей страницы, а в куске
|
|
127
|
+
// разметки они — ошибка или подлог. В <template> они не срабатывают
|
|
128
|
+
// и снимаются до вставки; <script> куска не исполняется и так.
|
|
129
|
+
var tpl = document.createElement('template');
|
|
130
|
+
|
|
131
|
+
tpl.innerHTML = html;
|
|
132
|
+
|
|
133
|
+
var bad = tpl.content.querySelectorAll('meta, base, link');
|
|
134
|
+
|
|
135
|
+
for (var i = 0; i < bad.length; i++) bad[i].remove();
|
|
136
|
+
|
|
137
|
+
return tpl.content;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
101
141
|
// Длительность --gr-transition у узла, мс: «0.2s ease» → 200, «150ms» → 150,
|
|
102
142
|
// пусто или 0s → 0. Одно чтение вычисленного стиля при закрытии.
|
|
103
143
|
function duration(node) {
|
|
@@ -282,22 +322,18 @@
|
|
|
282
322
|
item.loaded = true;
|
|
283
323
|
|
|
284
324
|
if (typeof fetch !== 'function') {
|
|
285
|
-
G.warn('
|
|
325
|
+
G.warn('megamenu: fetch is unavailable, panel ' + src + ' not loaded');
|
|
286
326
|
setAttr(item.el, 'data-gr-state', 'error');
|
|
287
327
|
return;
|
|
288
328
|
}
|
|
289
329
|
|
|
290
330
|
setAttr(item.el, 'data-gr-state', 'loading');
|
|
291
331
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return response.text();
|
|
296
|
-
}).then(function (html) {
|
|
297
|
-
item.panel.innerHTML = html;
|
|
332
|
+
fragment(src).then(function (html) {
|
|
333
|
+
item.panel.replaceChildren(html);
|
|
298
334
|
setAttr(item.el, 'data-gr-state', 'ready');
|
|
299
335
|
}, function (error) {
|
|
300
|
-
G.warn('
|
|
336
|
+
G.warn('megamenu: panel ' + src + ' not loaded: ' + (error && error.message));
|
|
301
337
|
setAttr(item.el, 'data-gr-state', 'error');
|
|
302
338
|
});
|
|
303
339
|
}
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
var listeners = G.listeners();
|
|
75
75
|
var fields = [];
|
|
76
76
|
|
|
77
|
-
if (!inputs.length) throw new Error('
|
|
77
|
+
if (!inputs.length) throw new Error('no <input type="range"> inside');
|
|
78
78
|
|
|
79
79
|
function doneOf(input) {
|
|
80
80
|
var min = edge(input, 'min', 0);
|
|
@@ -168,11 +168,14 @@
|
|
|
168
168
|
|
|
169
169
|
if (opts && opts.fields) {
|
|
170
170
|
var selectors = String(opts.fields).split(',');
|
|
171
|
+
// Поля — <input> своей формы (без формы — документа): селекторы
|
|
172
|
+
// приходят и из данных.
|
|
173
|
+
var scope = G.closest(el, 'form') || document;
|
|
171
174
|
|
|
172
175
|
for (var f = 0; f < inputs.length; f++) {
|
|
173
|
-
var field =
|
|
176
|
+
var field = scope.querySelector(selectors[f] || '#');
|
|
174
177
|
|
|
175
|
-
if (!field) throw new Error('
|
|
178
|
+
if (!field || field.tagName !== 'INPUT') throw new Error('field "' + selectors[f] + '" not found');
|
|
176
179
|
|
|
177
180
|
fields.push(field);
|
|
178
181
|
pull(f);
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
var trackEl = el.querySelector('.gr-track') || el.firstElementChild;
|
|
45
45
|
|
|
46
|
-
if (!trackEl) throw new Error('
|
|
46
|
+
if (!trackEl) throw new Error('the slider has no track');
|
|
47
47
|
|
|
48
48
|
var track = G.track(trackEl, { loop: o.loop, rewind: o.rewind, step: o.step, engine: o.engine, index: o.index || 0 });
|
|
49
49
|
var attrs = G.recorder();
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
return el.querySelector('tbody');
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
if (!bodyOf() || !heads.length) throw new Error('
|
|
92
|
+
if (!bodyOf() || !heads.length) throw new Error('needs tbody and headers with aria-sort');
|
|
93
93
|
|
|
94
94
|
var attrs = G.recorder();
|
|
95
95
|
var events = G.listeners();
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
var target = el.firstElementChild;
|
|
54
54
|
var tip = el.querySelector('[role="tooltip"]') || el.lastElementChild;
|
|
55
55
|
|
|
56
|
-
if (!target || !tip || tip === target) throw new Error('
|
|
56
|
+
if (!target || !tip || tip === target) throw new Error('the tooltip has no anchor and text');
|
|
57
57
|
|
|
58
58
|
var shown = false;
|
|
59
59
|
var timer = null;
|