griffincss-ui 0.24.0-dev → 0.26.1
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-scoped.css +1 -1
- package/dist/griffincss-ui.css +1 -1
- 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-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.css +1 -1
- package/dist/griffinjs-fields.js +1 -1
- package/dist/griffinjs-fields.js.map +1 -1
- package/dist/griffinjs-loader.js +3 -0
- package/dist/griffinjs-loader.js.map +1 -0
- package/dist/griffinjs-megamenu.js +1 -1
- package/dist/griffinjs-megamenu.js.map +1 -1
- package/dist/griffinjs-number.js +3 -0
- package/dist/griffinjs-number.js.map +1 -0
- package/dist/griffinjs-range.js +1 -1
- package/dist/griffinjs-range.js.map +1 -1
- package/dist/griffinjs-validate.js +1 -1
- package/dist/griffinjs-validate.js.map +1 -1
- package/dist/griffinjs.css +1 -1
- package/dist/griffinjs.js +1 -1
- package/dist/griffinjs.js.map +1 -1
- package/package.json +4 -3
- package/scss/_accordion.scss +5 -2
- package/scss/_alert.scss +1 -1
- package/scss/_card.scss +33 -7
- package/scss/_choice.scss +126 -4
- package/scss/_dropdown.scss +7 -3
- package/scss/_form.scss +42 -1
- package/scss/_menu.scss +4 -1
- package/scss/_modal.scss +44 -7
- package/scss/_nav.scss +5 -2
- package/scss/_rating.scss +7 -1
- package/scss/_table.scss +91 -0
- package/scss/_tabs.scss +4 -1
- package/scss/griffincss-ui-scoped.scss +17 -7
- package/scss/griffincss-ui.scss +16 -10
- package/scss/griffinjs/_track.scss +43 -4
- package/scss/griffinjs/fields/_datetime.scss +7 -0
- package/scss/griffinjs/fields/_number.scss +10 -0
- package/scss/griffinjs/fields/fields.scss +2 -1
- package/scss/griffinjs/griffinjs.scss +1 -1
- package/src/griffincss-ui.js +2 -2
- package/src/griffinjs/core/griffinjs-core.js +57 -11
- package/src/griffinjs/core/loader.js +124 -0
- package/src/griffinjs/fields/datetime.js +103 -6
- package/src/griffinjs/fields/number.js +327 -0
- package/src/griffinjs/fields/validate.js +24 -3
- package/src/griffinjs/widgets/combobox.js +15 -2
- package/src/griffinjs/widgets/dialog.js +67 -2
- package/src/griffinjs/widgets/dropdown.js +94 -4
- package/src/griffinjs/widgets/megamenu.js +90 -5
- package/src/griffinjs/widgets/range.js +81 -7
|
@@ -21,10 +21,22 @@
|
|
|
21
21
|
* * позицию popover-панели у кнопки: считается здесь, а не CSS anchor
|
|
22
22
|
* positioning, — одинаково во всех движках (нужен griffinjs-anchor.js);
|
|
23
23
|
* * наведение с задержкой намерения (hover) — только для pointerType
|
|
24
|
-
* mouse/pen: касание открывает щелчком, как без
|
|
24
|
+
* mouse/pen: касание открывает щелчком, как без скрипта;
|
|
25
|
+
* * закрытие <details> с состоянием (Этап 48d): панель, чей уход тема
|
|
26
|
+
* анимирует, не пропадает в первом же кадре — обёртка получает
|
|
27
|
+
* data-gr-state="closing", open снимается по transitionend/animationend
|
|
28
|
+
* самой панели или по таймауту в длительность --gr-transition плюс
|
|
29
|
+
* 50 мс. Щелчок по кнопке открытого дропдауна идёт тем же путём.
|
|
30
|
+
* Во всех браузерах, без развилки по ::details-content: Firefox 153
|
|
31
|
+
* поддерживает селектор и allow-discrete, но закрытие не анимирует —
|
|
32
|
+
* замер в browser/closing.spec.mjs (решение владельца 2026-09-21).
|
|
33
|
+
* CSS слоя переходов не задаёт — при 0s закрывается сразу.
|
|
34
|
+
* Popover-панель анимирует платформа через display allow-discrete —
|
|
35
|
+
* ей это не нужно.
|
|
25
36
|
*
|
|
26
37
|
* Открытое состояние — data-gr-state="open" на обёртке: у варианта
|
|
27
38
|
* по наведению (.gr-dropdown-hover) панель по нему показывает CSS слоя.
|
|
39
|
+
* Между open и ready у <details> бывает closing.
|
|
28
40
|
*/
|
|
29
41
|
(function (G) {
|
|
30
42
|
'use strict';
|
|
@@ -70,6 +82,18 @@
|
|
|
70
82
|
return document.activeElement === node;
|
|
71
83
|
}
|
|
72
84
|
|
|
85
|
+
// Длительность --gr-transition у узла, мс: «0.2s ease» → 200, «150ms» → 150,
|
|
86
|
+
// пусто или 0s → 0. Одно чтение вычисленного стиля при закрытии.
|
|
87
|
+
function duration(node) {
|
|
88
|
+
var value = '';
|
|
89
|
+
|
|
90
|
+
try { value = getComputedStyle(node).getPropertyValue('--gr-transition'); } catch (e) { /* без CSSOM */ }
|
|
91
|
+
|
|
92
|
+
var m = /(\d*\.?\d+)(m?s)/.exec(value);
|
|
93
|
+
|
|
94
|
+
return m ? parseFloat(m[1]) * (m[2] === 's' ? 1000 : 1) : 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
73
97
|
G.defineWidget('dropdown', { needs: ['anchor'] }, function (el, opts) {
|
|
74
98
|
var o = G.merge(DEFAULTS, opts);
|
|
75
99
|
var attrs = G.recorder();
|
|
@@ -86,6 +110,7 @@
|
|
|
86
110
|
var opened = false; // для popover и css: <details> знает сам
|
|
87
111
|
var viaKeyboard = false; // открыто с клавиатуры — фокус уходит в меню
|
|
88
112
|
var timer = null;
|
|
113
|
+
var closing = null; // таймаут состояния closing у <details>
|
|
89
114
|
|
|
90
115
|
// --- Состояние ------------------------------------------------------------
|
|
91
116
|
|
|
@@ -111,6 +136,7 @@
|
|
|
111
136
|
|
|
112
137
|
function sync(now) {
|
|
113
138
|
if (kind !== 'details') opened = now;
|
|
139
|
+
if (!now) settleClosing();
|
|
114
140
|
|
|
115
141
|
setAttr(trigger, 'aria-expanded', now ? 'true' : 'false');
|
|
116
142
|
setAttr(el, 'data-gr-state', now ? 'open' : 'ready');
|
|
@@ -121,6 +147,59 @@
|
|
|
121
147
|
viaKeyboard = false;
|
|
122
148
|
}
|
|
123
149
|
|
|
150
|
+
// --- Закрытие <details> с состоянием closing ------------------------------
|
|
151
|
+
|
|
152
|
+
// open снимается не сразу: обёртка получает data-gr-state="closing",
|
|
153
|
+
// панель уходит переходом темы; конец — transitionend/animationend самой
|
|
154
|
+
// панели (не потомка) или таймаут. Затем toggle → sync(false) → ready.
|
|
155
|
+
function closeDetails() {
|
|
156
|
+
if (closing) return;
|
|
157
|
+
|
|
158
|
+
var wait = duration(panel);
|
|
159
|
+
|
|
160
|
+
if (!wait) {
|
|
161
|
+
el.open = false;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
closing = setTimeout(finishClosing, wait + 50);
|
|
166
|
+
panel.addEventListener('transitionend', onEnd);
|
|
167
|
+
panel.addEventListener('animationend', onEnd);
|
|
168
|
+
setAttr(el, 'data-gr-state', 'closing');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function onEnd(event) {
|
|
172
|
+
if (event.target === panel) finishClosing();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function settleClosing() {
|
|
176
|
+
if (!closing) return;
|
|
177
|
+
|
|
178
|
+
clearTimeout(closing);
|
|
179
|
+
closing = null;
|
|
180
|
+
panel.removeEventListener('transitionend', onEnd);
|
|
181
|
+
panel.removeEventListener('animationend', onEnd);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function finishClosing() {
|
|
185
|
+
if (!closing) return;
|
|
186
|
+
|
|
187
|
+
settleClosing();
|
|
188
|
+
el.open = false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Щелчок (и Enter/Space) по кнопке открытого <details>: платформа сняла бы
|
|
192
|
+
// open в тот же кадр — перехватывается и идёт через closing. Щелчок
|
|
193
|
+
// по закрывающемуся — передумали: панель остаётся.
|
|
194
|
+
function onTriggerClick(event) {
|
|
195
|
+
if (kind !== 'details' || !el.open) return;
|
|
196
|
+
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
|
|
199
|
+
if (closing) open();
|
|
200
|
+
else close();
|
|
201
|
+
}
|
|
202
|
+
|
|
124
203
|
// beforetoggle приходит синхронно, до показа: панель ещё без размера,
|
|
125
204
|
// но для «снизу, по началу строки» размер и не нужен, а без этой
|
|
126
205
|
// предварительной позиции она на один кадр вспыхивала бы в статическом
|
|
@@ -137,6 +216,14 @@
|
|
|
137
216
|
|
|
138
217
|
function open() {
|
|
139
218
|
cancelTimer();
|
|
219
|
+
|
|
220
|
+
// Открытие во время закрытия отменяет его: панель остаётся на месте.
|
|
221
|
+
if (closing) {
|
|
222
|
+
settleClosing();
|
|
223
|
+
setAttr(el, 'data-gr-state', 'open');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
140
227
|
if (isOpen()) return;
|
|
141
228
|
|
|
142
229
|
if (kind === 'details') el.open = true;
|
|
@@ -149,7 +236,7 @@
|
|
|
149
236
|
cancelTimer();
|
|
150
237
|
if (!isOpen()) return;
|
|
151
238
|
|
|
152
|
-
if (kind === 'details')
|
|
239
|
+
if (kind === 'details') closeDetails();
|
|
153
240
|
else if (kind === 'popover') {
|
|
154
241
|
try { panel.hidePopover(); } catch (e) { sync(false); }
|
|
155
242
|
} else sync(false);
|
|
@@ -286,6 +373,7 @@
|
|
|
286
373
|
|
|
287
374
|
function destroy() {
|
|
288
375
|
cancelTimer();
|
|
376
|
+
finishClosing();
|
|
289
377
|
events.removeAll();
|
|
290
378
|
if (kind === 'popover' && G.anchor) G.anchor.clear(panel);
|
|
291
379
|
attrs.restore();
|
|
@@ -298,8 +386,10 @@
|
|
|
298
386
|
|
|
299
387
|
roles();
|
|
300
388
|
|
|
301
|
-
if (kind === 'details')
|
|
302
|
-
|
|
389
|
+
if (kind === 'details') {
|
|
390
|
+
listen(el, 'toggle', function () { sync(!!el.open); });
|
|
391
|
+
listen(trigger, 'click', onTriggerClick);
|
|
392
|
+
} else if (kind === 'popover') {
|
|
303
393
|
listen(panel, 'beforetoggle', onBeforeToggle);
|
|
304
394
|
listen(panel, 'toggle', function (event) { sync(event.newState === 'open'); });
|
|
305
395
|
listen(document, 'scroll', onReflow, true);
|
|
@@ -27,7 +27,16 @@
|
|
|
27
27
|
* закрывает и возвращает фокус на заголовок;
|
|
28
28
|
* * закрытие щелчком мимо полосы и уходом фокуса;
|
|
29
29
|
* * ajax-панели: data-gr-src на пункте грузится при первом открытии,
|
|
30
|
-
* состояния loading → ready | error пишутся на
|
|
30
|
+
* состояния loading → ready | error пишутся на пункт;
|
|
31
|
+
* * закрытие с состоянием (Этап 48d): панель, чей уход тема анимирует,
|
|
32
|
+
* не пропадает в первом же кадре — на пункт ставится
|
|
33
|
+
* data-gr-state="closing", open снимается по transitionend/animationend
|
|
34
|
+
* самой панели или по таймауту в длительность --gr-transition плюс
|
|
35
|
+
* 50 мс. Щелчок по заголовку открытого пункта идёт тем же путём.
|
|
36
|
+
* Во всех браузерах, без развилки по ::details-content: Firefox 153
|
|
37
|
+
* поддерживает селектор и allow-discrete, но закрытие не анимирует —
|
|
38
|
+
* замер в browser/closing.spec.mjs (решение владельца 2026-09-21).
|
|
39
|
+
* CSS слоя переходов не задаёт — при 0s закрывается сразу.
|
|
31
40
|
*
|
|
32
41
|
* Пункт — <details>, не вложенный в другой <details> полосы, с панелью
|
|
33
42
|
* после <summary>. Переключатель шапки (.gr-nav-toggle) панели не имеет
|
|
@@ -89,6 +98,18 @@
|
|
|
89
98
|
return document.activeElement === node;
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
// Длительность --gr-transition у узла, мс: «0.2s ease» → 200, «150ms» → 150,
|
|
102
|
+
// пусто или 0s → 0. Одно чтение вычисленного стиля при закрытии.
|
|
103
|
+
function duration(node) {
|
|
104
|
+
var value = '';
|
|
105
|
+
|
|
106
|
+
try { value = getComputedStyle(node).getPropertyValue('--gr-transition'); } catch (e) { /* без CSSOM */ }
|
|
107
|
+
|
|
108
|
+
var m = /(\d*\.?\d+)(m?s)/.exec(value);
|
|
109
|
+
|
|
110
|
+
return m ? parseFloat(m[1]) * (m[2] === 's' ? 1000 : 1) : 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
92
113
|
G.defineWidget('megamenu', { needs: ['media'] }, function (el, opts) {
|
|
93
114
|
var o = G.merge(DEFAULTS, opts);
|
|
94
115
|
var attrs = G.recorder();
|
|
@@ -159,6 +180,7 @@
|
|
|
159
180
|
setAttr(item.summary, 'aria-expanded', item.el.open ? 'true' : 'false');
|
|
160
181
|
|
|
161
182
|
listen(item.el, 'toggle', function () { sync(item); });
|
|
183
|
+
listen(item.summary, 'click', function (event) { onSummaryClick(event, item); });
|
|
162
184
|
}
|
|
163
185
|
|
|
164
186
|
function sync(item) {
|
|
@@ -166,10 +188,13 @@
|
|
|
166
188
|
|
|
167
189
|
setAttr(item.summary, 'aria-expanded', isOpen ? 'true' : 'false');
|
|
168
190
|
|
|
169
|
-
if (!isOpen)
|
|
191
|
+
if (!isOpen) {
|
|
192
|
+
settleClosing(item);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
170
195
|
|
|
171
196
|
for (var i = 0; i < items.length; i++) {
|
|
172
|
-
if (items[i] !== item
|
|
197
|
+
if (items[i] !== item) close(items[i]);
|
|
173
198
|
}
|
|
174
199
|
|
|
175
200
|
load(item);
|
|
@@ -177,11 +202,69 @@
|
|
|
177
202
|
|
|
178
203
|
function open(item) {
|
|
179
204
|
cancelTimers();
|
|
180
|
-
if (
|
|
205
|
+
if (!item) return;
|
|
206
|
+
|
|
207
|
+
// Указатель вернулся на закрывающийся пункт — он остаётся открытым.
|
|
208
|
+
if (item.closing) {
|
|
209
|
+
settleClosing(item);
|
|
210
|
+
setAttr(item.el, 'data-gr-state', item.was);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!item.el.open) item.el.open = true;
|
|
181
214
|
}
|
|
182
215
|
|
|
216
|
+
// --- Закрытие с состоянием closing ----------------------------------------
|
|
217
|
+
|
|
218
|
+
// open снимается не сразу: пункт получает data-gr-state="closing", и его
|
|
219
|
+
// панель уходит переходом темы; конец — transitionend/animationend самой
|
|
220
|
+
// панели (не потомка) или таймаут. Прежнее состояние пункта (ajax:
|
|
221
|
+
// ready | error) возвращается на место.
|
|
183
222
|
function close(item) {
|
|
184
|
-
if (item
|
|
223
|
+
if (!item || !item.el.open || item.closing) return;
|
|
224
|
+
|
|
225
|
+
var wait = duration(item.panel);
|
|
226
|
+
|
|
227
|
+
if (!wait) {
|
|
228
|
+
item.el.open = false;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
item.was = item.el.getAttribute('data-gr-state');
|
|
233
|
+
item.onEnd = function (event) { if (event.target === item.panel) finishClosing(item); };
|
|
234
|
+
item.closing = setTimeout(function () { finishClosing(item); }, wait + 50);
|
|
235
|
+
|
|
236
|
+
item.panel.addEventListener('transitionend', item.onEnd);
|
|
237
|
+
item.panel.addEventListener('animationend', item.onEnd);
|
|
238
|
+
setAttr(item.el, 'data-gr-state', 'closing');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function settleClosing(item) {
|
|
242
|
+
if (!item.closing) return;
|
|
243
|
+
|
|
244
|
+
clearTimeout(item.closing);
|
|
245
|
+
item.closing = null;
|
|
246
|
+
item.panel.removeEventListener('transitionend', item.onEnd);
|
|
247
|
+
item.panel.removeEventListener('animationend', item.onEnd);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function finishClosing(item) {
|
|
251
|
+
if (!item.closing) return;
|
|
252
|
+
|
|
253
|
+
settleClosing(item);
|
|
254
|
+
setAttr(item.el, 'data-gr-state', item.was);
|
|
255
|
+
item.el.open = false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Щелчок (и Enter/Space) по заголовку открытого пункта: платформа сняла бы
|
|
259
|
+
// open в тот же кадр — перехватывается и идёт через closing. Щелчок
|
|
260
|
+
// по закрывающемуся пункту — передумали: он остаётся открытым.
|
|
261
|
+
function onSummaryClick(event, item) {
|
|
262
|
+
if (!item.el.open) return;
|
|
263
|
+
|
|
264
|
+
event.preventDefault();
|
|
265
|
+
|
|
266
|
+
if (item.closing) open(item);
|
|
267
|
+
else close(item);
|
|
185
268
|
}
|
|
186
269
|
|
|
187
270
|
function closeAll() {
|
|
@@ -284,6 +367,7 @@
|
|
|
284
367
|
if (item === active || item === pending) {
|
|
285
368
|
if (closeTimer) clearTimeout(closeTimer);
|
|
286
369
|
closeTimer = null;
|
|
370
|
+
if (item.closing) open(item);
|
|
287
371
|
return;
|
|
288
372
|
}
|
|
289
373
|
|
|
@@ -456,6 +540,7 @@
|
|
|
456
540
|
|
|
457
541
|
function destroy() {
|
|
458
542
|
cancelTimers();
|
|
543
|
+
for (var i = 0; i < items.length; i++) finishClosing(items[i]);
|
|
459
544
|
if (unmedia) unmedia();
|
|
460
545
|
events.removeAll();
|
|
461
546
|
attrs.restore();
|
|
@@ -22,6 +22,20 @@
|
|
|
22
22
|
* Второе дело скрипта в паре — порядок значений: тот, кого потянули
|
|
23
23
|
* за чужую границу, толкает вторую ручку перед собой. Пересечься им
|
|
24
24
|
* нельзя — отрезок между ручками вывернулся бы наизнанку.
|
|
25
|
+
*
|
|
26
|
+
* Третье — связка с числовыми полями (Этап 42a):
|
|
27
|
+
*
|
|
28
|
+
* <div class="gr-range-pair" data-gr-range="fields: #price-min, #price-max">
|
|
29
|
+
*
|
|
30
|
+
* Селекторов столько же, сколько ползунков. Движение ручки пишет число
|
|
31
|
+
* в своё поле (после наведения порядка); ввод в поле двигает ручку
|
|
32
|
+
* с зажимом по min/max, а порядок наводится только на change — «1»
|
|
33
|
+
* по пути к «1000» иначе толкнул бы вторую ручку. Форму отправляют поля:
|
|
34
|
+
* name — у них, у ползунков его нет, иначе в GET уехали бы четыре
|
|
35
|
+
* параметра. Правда — в полях: страница вернулась с ними, а атрибуты
|
|
36
|
+
* value ползунков остались начальными, поэтому на монтаже ручки встают
|
|
37
|
+
* по полям; пустое поле — ручка на своём краю. Само поле от монтажа
|
|
38
|
+
* не заполняется: «фильтр не задан» остаётся пустым.
|
|
25
39
|
*/
|
|
26
40
|
(function (G) {
|
|
27
41
|
'use strict';
|
|
@@ -50,20 +64,59 @@
|
|
|
50
64
|
return num(current, num(input.getAttribute('min'), 0));
|
|
51
65
|
}
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
function edge(input, name, fallback) {
|
|
68
|
+
return num(input.getAttribute(name), fallback);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
G.defineWidget('range', function (el, opts) {
|
|
54
72
|
var single = el.tagName === 'INPUT';
|
|
55
73
|
var inputs = single ? [el] : [].slice.call(el.querySelectorAll('input[type="range"]'));
|
|
56
74
|
var listeners = G.listeners();
|
|
75
|
+
var fields = [];
|
|
57
76
|
|
|
58
77
|
if (!inputs.length) throw new Error('внутри нет ни одного <input type="range">');
|
|
59
78
|
|
|
60
79
|
function doneOf(input) {
|
|
61
|
-
var min =
|
|
62
|
-
var span =
|
|
80
|
+
var min = edge(input, 'min', 0);
|
|
81
|
+
var span = edge(input, 'max', 100) - min;
|
|
63
82
|
|
|
64
83
|
return round(span > 0 ? (valueOf(input) - min) / span : 0);
|
|
65
84
|
}
|
|
66
85
|
|
|
86
|
+
// Число из поля — в ползунок: пустое поле ставит ручку на свой край
|
|
87
|
+
// (нижняя граница не задана — от min, верхняя — до max), число зажимается.
|
|
88
|
+
function pull(i) {
|
|
89
|
+
var input = inputs[i];
|
|
90
|
+
var min = edge(input, 'min', 0);
|
|
91
|
+
var max = edge(input, 'max', 100);
|
|
92
|
+
var value = fields[i].value;
|
|
93
|
+
|
|
94
|
+
input.value = value === '' ? (i ? max : min) : Math.max(min, Math.min(max, num(value, min)));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Значения ручек — в поля, кроме того, откуда пришёл ввод: оно уже
|
|
98
|
+
// содержит то, что человек написал, и переписывать его под рукой нельзя.
|
|
99
|
+
function push(except) {
|
|
100
|
+
for (var i = 0; i < fields.length; i++) if (fields[i] !== except) fields[i].value = valueOf(inputs[i]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function typed(event) {
|
|
104
|
+
var field = event.target;
|
|
105
|
+
var i = fields.indexOf(field);
|
|
106
|
+
|
|
107
|
+
pull(i);
|
|
108
|
+
|
|
109
|
+
// Пока число не дописано, ручки могут стоять наоборот: отрезок между
|
|
110
|
+
// ними на это время вырождается в точку, и это лучше толчка второй ручки.
|
|
111
|
+
if (event.type === 'change') {
|
|
112
|
+
order(inputs[i]);
|
|
113
|
+
push(field);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
paint();
|
|
117
|
+
emit();
|
|
118
|
+
}
|
|
119
|
+
|
|
67
120
|
// Порядок значений в паре. Толкает именно вторую ручку, а не возвращает
|
|
68
121
|
// первую: человек ведёт ту, за которую взялся, и остановка под пальцем
|
|
69
122
|
// читалась бы как поломка.
|
|
@@ -79,20 +132,26 @@
|
|
|
79
132
|
else to.value = from.value;
|
|
80
133
|
}
|
|
81
134
|
|
|
82
|
-
function
|
|
83
|
-
order(event && event.target);
|
|
84
|
-
|
|
135
|
+
function paint() {
|
|
85
136
|
if (inputs.length > 1) {
|
|
86
137
|
el.style.setProperty(FROM, doneOf(inputs[0]));
|
|
87
138
|
el.style.setProperty(TO, doneOf(inputs[1]));
|
|
88
139
|
} else {
|
|
89
140
|
inputs[0].style.setProperty(PROP, doneOf(inputs[0]));
|
|
90
141
|
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function update(event) {
|
|
145
|
+
order(event && event.target);
|
|
146
|
+
paint();
|
|
91
147
|
|
|
92
148
|
// Событие — после наведения порядка, и только на живое движение ручки.
|
|
93
149
|
// Подпись рядом с парой иначе показала бы значения ДО толчка: свой
|
|
94
150
|
// обработчик на ползунке отработает раньше нашего, стоящего на обёртке.
|
|
95
|
-
if (event)
|
|
151
|
+
if (event) {
|
|
152
|
+
push();
|
|
153
|
+
emit();
|
|
154
|
+
}
|
|
96
155
|
}
|
|
97
156
|
|
|
98
157
|
function emit() {
|
|
@@ -107,6 +166,21 @@
|
|
|
107
166
|
// и второй ручке отдельная подписка не нужна.
|
|
108
167
|
listeners.add(el, 'input', update);
|
|
109
168
|
|
|
169
|
+
if (opts && opts.fields) {
|
|
170
|
+
var selectors = String(opts.fields).split(',');
|
|
171
|
+
|
|
172
|
+
for (var f = 0; f < inputs.length; f++) {
|
|
173
|
+
var field = document.querySelector(selectors[f] || '#');
|
|
174
|
+
|
|
175
|
+
if (!field) throw new Error('поле «' + selectors[f] + '» не найдено');
|
|
176
|
+
|
|
177
|
+
fields.push(field);
|
|
178
|
+
pull(f);
|
|
179
|
+
listeners.add(field, 'input', typed);
|
|
180
|
+
listeners.add(field, 'change', typed);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
110
184
|
update();
|
|
111
185
|
|
|
112
186
|
return {
|