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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// Движение — здесь. JS пишет только aria-*, data-gr-state и --gr-progress.
|
|
11
11
|
|
|
12
12
|
@use 'griffincss-core/scss/breakpoints' as bp;
|
|
13
|
+
@use 'griffincss-core/scss/container-mixins' as cq;
|
|
13
14
|
|
|
14
15
|
.gr-track {
|
|
15
16
|
display: flex;
|
|
@@ -45,12 +46,50 @@
|
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
// Доля слайда описана один раз: без суффикса — база, с суффиксом
|
|
50
|
+
// брейкпоинта — от ширины окна, с буквой c — от ширины ближайшего предка
|
|
51
|
+
// с .gr-cq (Этап 47d): виджет в слоте меряется слотом. Виджету дорожки
|
|
52
|
+
// всё равно, откуда пришла доля — perView он считает по геометрии.
|
|
53
|
+
@mixin gr-track-per-view($suffix) {
|
|
54
|
+
@each $n in (1, 2, 3, 4) {
|
|
55
|
+
.gr-track-#{$n}#{$suffix} > * {
|
|
56
|
+
flex-basis: calc((100% - var(--gr-gap) * #{$n - 1}) / #{$n});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
@each $bp-name, $bp-value in bp.$gr-breakpoints {
|
|
49
62
|
@media (width >= $bp-value) {
|
|
50
|
-
@
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
@include gr-track-per-view('-#{$bp-name}');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@each $bp-name, $bp-value in bp.$gr-breakpoints {
|
|
68
|
+
@include cq.gr-container-media($bp-name) {
|
|
69
|
+
@include gr-track-per-view('-c#{$bp-name}');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Лента столбиком в узком гнезде (Этап 47d, фидбек темы griffin, п. 8):
|
|
74
|
+
// подборка товаров в боковой колонке — список, в содержимом — лента.
|
|
75
|
+
// Порог — ширина ближайшего предка с .gr-cq, суффикс называет его
|
|
76
|
+
// (как у .gr-table-stack-c*): .gr-track-stack-csm — столбик, пока гнездо
|
|
77
|
+
// уже 640px. Оконных порогов нет: в узком окне лента остаётся лентой,
|
|
78
|
+
// свайп и есть её мобильный режим. Слайды отпускаются по главной оси —
|
|
79
|
+
// flex-basis: auto бьёт долю .gr-track-N > * тем, что стоит позже: между
|
|
80
|
+
// 640 и 768 px гнезда .gr-track-2-csm и .gr-track-stack-cmd совпадают,
|
|
81
|
+
// и столбик обязан стоять в файле после долей.
|
|
82
|
+
// Слайдеру не подходит: его кнопки и точки останутся на месте.
|
|
83
|
+
@each $bp-name, $bp-value in bp.$gr-breakpoints {
|
|
84
|
+
@include cq.gr-container-below($bp-name) {
|
|
85
|
+
.gr-track-stack-c#{$bp-name} {
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
overflow-x: visible;
|
|
88
|
+
scroll-snap-type: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.gr-track-stack-c#{$bp-name} > * {
|
|
92
|
+
flex-basis: auto;
|
|
54
93
|
}
|
|
55
94
|
}
|
|
56
95
|
}
|
|
@@ -166,6 +166,13 @@
|
|
|
166
166
|
outline-offset: -2px;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
// Пара «с — по»: дни между концами — той же заливкой, что у наведения,
|
|
170
|
+
// концы — как выбранный день; между ними и по бокам сетки остаётся
|
|
171
|
+
// тот же зазор, что и у всех клеток.
|
|
172
|
+
.gr-datetime-day[data-gr-in-range] {
|
|
173
|
+
background-color: hsl(var(--gr-hsl-accent), 12%);
|
|
174
|
+
}
|
|
175
|
+
|
|
169
176
|
.gr-datetime-day[aria-selected="true"] {
|
|
170
177
|
background-color: var(--gr-color-accent);
|
|
171
178
|
color: var(--gr-color-on-accent);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GriffinJS — сумма с разрядами (Этап 43b)
|
|
2
|
+
// Своих стилей у поля нет: на экране обычный .gr-input с текстом. Скрытый
|
|
3
|
+
// спутник с числом встаёт сразу за полем (у суммы — всегда, у даты —
|
|
4
|
+
// на указателе мыши), и в .gr-input-group с аддоном после поля он был бы
|
|
5
|
+
// последним ребёнком: до Этапа 45 поле теряло бы скругление края, и его
|
|
6
|
+
// возвращало правило `:has(+ [hidden]:last-child)` здесь. Теперь края
|
|
7
|
+
// группы считаются по видимым полям в самом компоненте (_form.scss,
|
|
8
|
+
// `:nth-last-child(1 of S)`), и спутник, как любой скрытый узел, края
|
|
9
|
+
// не трогает. Файл остаётся в списке сборки пустым: место для стилей поля,
|
|
10
|
+
// если они понадобятся, — здесь, а не в чужом модуле.
|
|
@@ -14,13 +14,14 @@
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@use 'sass:meta';
|
|
17
|
-
@layer griffincss.reset, griffincss.core, griffincss.ui, griffincss.utils, griffincss.style;
|
|
17
|
+
@layer griffincss.reset, griffincss.tokens, griffincss.core, griffincss.ui, griffincss.utils, griffincss.style, griffincss.hidden;
|
|
18
18
|
|
|
19
19
|
// Весь вывод — в griffincss.ui, как у компонентов.
|
|
20
20
|
@layer griffincss.ui {
|
|
21
21
|
@include meta.load-css('counter');
|
|
22
22
|
@include meta.load-css('phone');
|
|
23
23
|
@include meta.load-css('datetime');
|
|
24
|
+
@include meta.load-css('number');
|
|
24
25
|
@include meta.load-css('file');
|
|
25
26
|
@include meta.load-css('rating');
|
|
26
27
|
@include meta.load-css('otp');
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
@use 'sass:meta';
|
|
21
|
-
@layer griffincss.reset, griffincss.core, griffincss.ui, griffincss.utils, griffincss.style;
|
|
21
|
+
@layer griffincss.reset, griffincss.tokens, griffincss.core, griffincss.ui, griffincss.utils, griffincss.style, griffincss.hidden;
|
|
22
22
|
|
|
23
23
|
// Весь вывод слоя — в griffincss.ui, как у компонентов.
|
|
24
24
|
@layer griffincss.ui {
|
package/src/griffincss-ui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Griffincss UI — Runtime v0.
|
|
2
|
+
* Griffincss UI — Runtime v0.26.1
|
|
3
3
|
* Опциональный рантайм пакета компонентов. Делает ровно то, чего платформа
|
|
4
4
|
* не даёт вовсе; всё, что умеют <details>, <dialog> и Popover API, остаётся
|
|
5
5
|
* за ними. Без этого файла компоненты работают — просто без перечисленного.
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
})(function () {
|
|
51
51
|
'use strict';
|
|
52
52
|
|
|
53
|
-
var VERSION = '0.
|
|
53
|
+
var VERSION = '0.26.1';
|
|
54
54
|
|
|
55
55
|
var OVERLAY_ATTR = 'data-gr-overlay-close';
|
|
56
56
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* GriffinJS — Core v0.
|
|
2
|
+
* GriffinJS — Core v0.26.1
|
|
3
3
|
* Опциональный слой виджетов с состоянием поверх Griffincss: слайдер,
|
|
4
4
|
* галерея, лайтбокс, параллакс, мегаменю. Подключается одной строкой
|
|
5
5
|
* и одним файлом; страница без него — законное и рабочее состояние.
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* поднимает всё, destroy() снимает всё до последнего атрибута.
|
|
11
11
|
*
|
|
12
12
|
* В griffinjs-core.js склеивается только то, без чего не обходится ни один
|
|
13
|
-
* виджет: options, registry, scanner. Остальное из core/ —
|
|
14
|
-
* motion, gesture, track — общие части ВНЕ ядра (Этап 22): ими пользуется
|
|
13
|
+
* виджет: options, registry, scanner. Остальное из core/ — loader, anchor,
|
|
14
|
+
* media, motion, gesture, track — общие части ВНЕ ядра (Этап 22): ими пользуется
|
|
15
15
|
* меньшинство, и каждая уезжает своим файлом griffinjs-<часть>.js. Что
|
|
16
16
|
* модуль из этого берёт, он объявляет сам — вторым аргументом defineWidget
|
|
17
17
|
* или needs(); start() сверяет объявленное со сборкой.
|
|
@@ -20,6 +20,13 @@
|
|
|
20
20
|
* window.GriffinJS, а не window.Griffincss — там слияние рантаймов
|
|
21
21
|
* CSS-части, и порядок тегов не значим; слою с состоянием чужой
|
|
22
22
|
* глобал ни к чему.
|
|
23
|
+
*
|
|
24
|
+
* Участник жизненного цикла (use) кроме start/destroy может объявить
|
|
25
|
+
* init(root): ядро зовёт его после подъёма виджетов в корне — и на старте,
|
|
26
|
+
* и на каждой порции сканера. Так догрузка бандла полей (core/loader.js,
|
|
27
|
+
* Этап 48c) видит data-gr-<поле> без виджета, не будучи частью ядра.
|
|
28
|
+
* GriffinJS.config — общий объект настроек: ядру он не нужен, но это
|
|
29
|
+
* его поверхность, и модуль читает из неё (config.fields у загрузчика).
|
|
23
30
|
*/
|
|
24
31
|
(function (factory) {
|
|
25
32
|
'use strict';
|
|
@@ -38,7 +45,7 @@
|
|
|
38
45
|
})(function () {
|
|
39
46
|
'use strict';
|
|
40
47
|
|
|
41
|
-
var VERSION = '0.
|
|
48
|
+
var VERSION = '0.26.1';
|
|
42
49
|
|
|
43
50
|
// Реестры. Модуль зовёт defineEngine/defineWidget; ядро — единственное
|
|
44
51
|
// место, которое знает, как их применить.
|
|
@@ -61,6 +68,9 @@
|
|
|
61
68
|
|
|
62
69
|
var started = false;
|
|
63
70
|
|
|
71
|
+
// Настройки — одна поверхность на слой; ядро само ничего отсюда не читает.
|
|
72
|
+
var config = {};
|
|
73
|
+
|
|
64
74
|
// Объявление и чтение зависимостей: needs('slider', ['track', 'motion'])
|
|
65
75
|
// записывает, needs('slider') возвращает.
|
|
66
76
|
function needs(name, list) {
|
|
@@ -273,6 +283,10 @@
|
|
|
273
283
|
for (var j = 0; j < nodes.length; j++) mount(nodes[j], name);
|
|
274
284
|
}
|
|
275
285
|
|
|
286
|
+
for (var k = 0; k < lifecycle.length; k++) {
|
|
287
|
+
if (typeof lifecycle[k].init === 'function') lifecycle[k].init(root);
|
|
288
|
+
}
|
|
289
|
+
|
|
276
290
|
return api;
|
|
277
291
|
}
|
|
278
292
|
|
|
@@ -332,22 +346,54 @@
|
|
|
332
346
|
|
|
333
347
|
// Автостарт: только в браузере и только если его не отключили атрибутом
|
|
334
348
|
// data-auto="false" на теге <script>.
|
|
349
|
+
//
|
|
350
|
+
// Момент старта — когда поставка зарегистрирована целиком. Ядро — первый
|
|
351
|
+
// IIFE бандла, и старт при выполнении собственного файла обгоняет
|
|
352
|
+
// defineWidget модулей дальше в том же файле; под defer так и было:
|
|
353
|
+
// readyState уже «interactive», старт шёл сразу, init(document) не
|
|
354
|
+
// находил ни одного виджета, поздних никто не поднимал (фидбек темы
|
|
355
|
+
// griffin по 0.26.0, п. 1). Три момента:
|
|
356
|
+
// * разбор идёт (loading) — DOMContentLoaded, как прежде;
|
|
357
|
+
// * свой тег отложен (defer) — тоже DOMContentLoaded: отложенные теги
|
|
358
|
+
// выполняются после разбора и ДО события, значит к нему выполнятся
|
|
359
|
+
// и griffinjs-fields.js с griffinjs-countries.js следующими тегами —
|
|
360
|
+
// та же схема, что синхронные теги в подвале. Не readystatechange:
|
|
361
|
+
// «interactive» наступает ДО отложенных тегов;
|
|
362
|
+
// * иначе (документ готов, тег вставлен скриптом) — микрозадачей:
|
|
363
|
+
// после текущего файла целиком, до любого следующего тега. Набору
|
|
364
|
+
// «ядро + нужное», вставленному скриптом, нужен data-auto="false"
|
|
365
|
+
// и GriffinJS.start() после последнего тега.
|
|
366
|
+
// Тег с defer, вставленный скриптом после DOMContentLoaded, отложенным
|
|
367
|
+
// не является, и события он не дождётся; страховка от вечного
|
|
368
|
+
// ожидания — readystatechange: при «interactive» следующее — «complete».
|
|
335
369
|
function autoStart() {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
370
|
+
var script = null;
|
|
371
|
+
|
|
372
|
+
try { script = document.currentScript; } catch (e) { /* currentScript недоступен */ }
|
|
373
|
+
|
|
374
|
+
if (script && script.getAttribute('data-auto') === 'false') return;
|
|
375
|
+
|
|
376
|
+
var deferred = !!script && script.hasAttribute('defer') && !script.hasAttribute('async');
|
|
377
|
+
|
|
378
|
+
if (document.readyState === 'loading' || (deferred && document.readyState !== 'complete')) {
|
|
379
|
+
var ready = function () {
|
|
380
|
+
document.removeEventListener('DOMContentLoaded', ready);
|
|
381
|
+
document.removeEventListener('readystatechange', ready);
|
|
382
|
+
start();
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
document.addEventListener('DOMContentLoaded', ready);
|
|
386
|
+
if (deferred) document.addEventListener('readystatechange', ready);
|
|
340
387
|
|
|
341
|
-
if (document.readyState === 'loading') {
|
|
342
|
-
document.addEventListener('DOMContentLoaded', function () { start(); });
|
|
343
388
|
return;
|
|
344
389
|
}
|
|
345
390
|
|
|
346
|
-
|
|
391
|
+
Promise.resolve().then(start);
|
|
347
392
|
}
|
|
348
393
|
|
|
349
394
|
var api = {
|
|
350
395
|
version: VERSION,
|
|
396
|
+
config: config,
|
|
351
397
|
engines: engines,
|
|
352
398
|
widgets: widgets,
|
|
353
399
|
defineEngine: defineEngine,
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* GriffinJS — догрузка бандла полей по потребности (Этап 48c).
|
|
3
|
+
*
|
|
4
|
+
* <script defer src="griffinjs.js"
|
|
5
|
+
* data-fields="griffinjs-fields.js"
|
|
6
|
+
* data-countries="griffinjs-countries.js"></script>
|
|
7
|
+
*
|
|
8
|
+
* GriffinJS.config.fields = { src: 'griffinjs-fields.js', countries: 'griffinjs-countries.js' };
|
|
9
|
+
*
|
|
10
|
+
* Витрине поля нужны на трёх страницах из трёхсот, а тег бандла стоит
|
|
11
|
+
* в шаблоне шапки для всех. Вместо тега — путь: встретив data-gr-<поле>
|
|
12
|
+
* без зарегистрированного виджета, слой вставляет <script> сам — один раз
|
|
13
|
+
* на документ, с nonce со своего тега (под строгим CSP без него файл
|
|
14
|
+
* отброшен молча), таблица стран вторым тегом в том же порядке, — и после
|
|
15
|
+
* загрузки ВСЕХ файлов поднимает поля по документу и шлёт
|
|
16
|
+
* griffinjs:fields-loaded. Ошибка загрузки — предупреждение, поля остаются
|
|
17
|
+
* базой без скрипта, второй попытки нет. Без config — предупреждение раз
|
|
18
|
+
* на имя поля: раньше такое поле молчало.
|
|
19
|
+
*
|
|
20
|
+
* Единственное, что здесь известно поимённо, — не виджет, а файл поставки:
|
|
21
|
+
* девять имён второго бандла, те же, что в списке FIELDS сборки
|
|
22
|
+
* (scripts/build-griffinjs.mjs; тест сверяет). Ядро о них не знает:
|
|
23
|
+
* модуль подписывается на init(root) и лежит вне ядра, в SHARED, —
|
|
24
|
+
* за него не платит набор из двух файлов ради одной подсказки.
|
|
25
|
+
*
|
|
26
|
+
* Пути и nonce читаются у своего тега в момент выполнения — позже
|
|
27
|
+
* document.currentScript уже null. Атрибуты, а не только свойство, —
|
|
28
|
+
* ради тега с defer: инлайновый скрипт перед ним глобала ещё не видит.
|
|
29
|
+
*/
|
|
30
|
+
(function (G) {
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
var FIELDS = ['mask', 'phone', 'datetime', 'number', 'file', 'rating', 'otp', 'counter', 'validate'];
|
|
34
|
+
|
|
35
|
+
var config = G.config;
|
|
36
|
+
var nonce = '';
|
|
37
|
+
var loading = null; // null — не запрашивали; pending | done | failed
|
|
38
|
+
var warned = {}; // поле → без config уже сказано
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
var script = document.currentScript;
|
|
42
|
+
|
|
43
|
+
if (script) {
|
|
44
|
+
nonce = script.nonce || '';
|
|
45
|
+
|
|
46
|
+
var src = script.getAttribute('data-fields');
|
|
47
|
+
|
|
48
|
+
if (src) config.fields = { src: src, countries: script.getAttribute('data-countries') || '' };
|
|
49
|
+
}
|
|
50
|
+
} catch (e) { /* currentScript недоступен */ }
|
|
51
|
+
|
|
52
|
+
// Поля в корне, чьих виджетов нет: с config — запрос бандла, без него —
|
|
53
|
+
// предупреждение. Узлы не трогаются: база без скрипта — рабочее состояние.
|
|
54
|
+
function check(root) {
|
|
55
|
+
for (var i = 0; i < FIELDS.length; i++) {
|
|
56
|
+
var name = FIELDS[i];
|
|
57
|
+
var selector = '[data-gr-' + name + ']';
|
|
58
|
+
|
|
59
|
+
if (G.widgets[name] || warned[name]) continue;
|
|
60
|
+
if (!(root.matches && root.matches(selector)) && !(root.querySelector && root.querySelector(selector))) continue;
|
|
61
|
+
|
|
62
|
+
if (config.fields && config.fields.src) return request();
|
|
63
|
+
|
|
64
|
+
warned[name] = true;
|
|
65
|
+
G.warn('поле «' + name + '» не собрано: подключите griffinjs-fields.js или задайте GriffinJS.config.fields');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Поля поднимаются после ВСЕХ файлов: телефон читает таблицу стран
|
|
70
|
+
// при монтировании, и она обязана лечь раньше. Что успело
|
|
71
|
+
// зарегистрироваться при ошибке, всё равно поднимается.
|
|
72
|
+
function request() {
|
|
73
|
+
if (loading) return;
|
|
74
|
+
|
|
75
|
+
loading = 'pending';
|
|
76
|
+
|
|
77
|
+
var files = [config.fields.src];
|
|
78
|
+
var ok = true;
|
|
79
|
+
var left;
|
|
80
|
+
|
|
81
|
+
if (config.fields.countries) files.push(config.fields.countries);
|
|
82
|
+
|
|
83
|
+
left = files.length;
|
|
84
|
+
|
|
85
|
+
function settle(event) {
|
|
86
|
+
if (event.type === 'error') {
|
|
87
|
+
ok = false;
|
|
88
|
+
G.warn('бандл полей не загружен: ' + event.target.src + ' — поля остались базой без скрипта');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (--left) return;
|
|
92
|
+
|
|
93
|
+
loading = ok ? 'done' : 'failed';
|
|
94
|
+
G.init(document);
|
|
95
|
+
|
|
96
|
+
if (ok) G.emit('griffinjs:fields-loaded', document, { files: files });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (var i = 0; i < files.length; i++) insert(files[i], settle);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// async = false: вставленный скриптом тег по умолчанию async, а порядок
|
|
103
|
+
// «поля, затем страны» нужен. nonce — свойством: атрибут после разбора
|
|
104
|
+
// браузер прячет.
|
|
105
|
+
function insert(src, settle) {
|
|
106
|
+
var tag = document.createElement('script');
|
|
107
|
+
|
|
108
|
+
tag.src = src;
|
|
109
|
+
tag.async = false;
|
|
110
|
+
if (nonce) tag.nonce = nonce;
|
|
111
|
+
|
|
112
|
+
tag.addEventListener('load', settle);
|
|
113
|
+
tag.addEventListener('error', settle);
|
|
114
|
+
|
|
115
|
+
(document.head || document.documentElement).appendChild(tag);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
G.loader = {
|
|
119
|
+
_fields: function () { return FIELDS.slice(); },
|
|
120
|
+
_nonce: function (value) { if (value !== undefined) nonce = value; return nonce; }
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
G.use({ init: check });
|
|
124
|
+
})(typeof window !== 'undefined' && window.GriffinJS ? window.GriffinJS : require('./griffinjs-core.js'));
|
|
@@ -46,6 +46,22 @@
|
|
|
46
46
|
* invalid, range — сообщения проверки; label, prev, next, year, time —
|
|
47
47
|
* подписи панели.
|
|
48
48
|
*
|
|
49
|
+
* Диапазон «с — по» (Этап 43a) — пара нативных полей, связанная атрибутом:
|
|
50
|
+
* <input type="date" name="from" data-gr-datetime="to: #until">
|
|
51
|
+
* <input type="date" name="until" id="until" data-gr-datetime>
|
|
52
|
+
* Одного контрола с двумя концами нет намеренно: база без скрипта — два
|
|
53
|
+
* нативных поля, и панель одного поля пишет одно поле. Связка строится
|
|
54
|
+
* на готовом: значение «с» становится min у «по», значение «по» — max
|
|
55
|
+
* у «с», через собственный API соседа (limit), — и проверка границ,
|
|
56
|
+
* сообщения и aria-disabled в панели работают без новой логики; на тач-
|
|
57
|
+
* устройстве системный календарь сам скроет недоступное. Авторская
|
|
58
|
+
* граница остаётся, если она уже; очистка и destroy() возвращают её.
|
|
59
|
+
* Порядок дат виджет не толкает: «по» раньше «с» — ошибка ввода, и её
|
|
60
|
+
* показывает проверка границ, а не молчаливая правка второго поля.
|
|
61
|
+
* Сверх границ — только панель: пустое поле открывается на месяце
|
|
62
|
+
* соседа, оба конца — aria-selected, дни между ними — data-gr-in-range
|
|
63
|
+
* (красит CSS панели), и range: {from, to} в detail griffin:change обоих.
|
|
64
|
+
*
|
|
49
65
|
* Виджет пишет value контрола — расширение инварианта 7, объявленное
|
|
50
66
|
* в docs/griffinjs-architecture.html («Инварианты»).
|
|
51
67
|
*/
|
|
@@ -209,6 +225,11 @@
|
|
|
209
225
|
return node;
|
|
210
226
|
}
|
|
211
227
|
|
|
228
|
+
function attr(node, name, value) {
|
|
229
|
+
if (value) node.setAttribute(name, value);
|
|
230
|
+
else node.removeAttribute(name);
|
|
231
|
+
}
|
|
232
|
+
|
|
212
233
|
G.defineWidget('datetime', { needs: ['mask', 'anchor'] }, function (el, opts) {
|
|
213
234
|
var o = G.merge(DEFAULTS, opts);
|
|
214
235
|
var native = el.getAttribute('type');
|
|
@@ -241,11 +262,52 @@
|
|
|
241
262
|
var view = null; // первое число показанного месяца
|
|
242
263
|
var active = null; // активный день панели, 'YYYY-MM-DD'
|
|
243
264
|
var last = null; // последнее ISO, о котором сказано событием
|
|
265
|
+
var peer = null; // второе поле пары «с — по»
|
|
266
|
+
var role = ''; // 'from' | 'to' — кто это поле в паре
|
|
267
|
+
var own = { min: el.getAttribute('min'), max: el.getAttribute('max') }; // авторские границы
|
|
244
268
|
|
|
245
269
|
function iso() {
|
|
246
270
|
return companion ? companion.value : el.value;
|
|
247
271
|
}
|
|
248
272
|
|
|
273
|
+
// --- Пара «с — по» ------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
function range() {
|
|
276
|
+
var mine = iso();
|
|
277
|
+
var theirs = peer.value();
|
|
278
|
+
|
|
279
|
+
return role === 'from' ? { from: mine, to: theirs } : { from: theirs, to: mine };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Граница на поле и спутнике; проверка и панель узнают о ней тем же
|
|
283
|
+
// путём, что о всякой другой.
|
|
284
|
+
function apply(name, value) {
|
|
285
|
+
attr(el, name, value);
|
|
286
|
+
|
|
287
|
+
if (companion) { attr(companion, name, value); check(); }
|
|
288
|
+
if (opened && body) render(view);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Граница от соседа: авторская остаётся, если она уже; пустое
|
|
292
|
+
// значение возвращает авторскую.
|
|
293
|
+
function limit(name, value) {
|
|
294
|
+
var mine = own[name];
|
|
295
|
+
|
|
296
|
+
apply(name, !value ? mine : mine && (name === 'min' ? mine > value : mine < value) ? mine : value);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function push() {
|
|
300
|
+
if (peer) peer.limit(role === 'from' ? 'min' : 'max', iso());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function unpair() {
|
|
304
|
+
var name = role === 'from' ? 'max' : 'min';
|
|
305
|
+
|
|
306
|
+
peer = null;
|
|
307
|
+
role = '';
|
|
308
|
+
apply(name, own[name]);
|
|
309
|
+
}
|
|
310
|
+
|
|
249
311
|
// --- Проверка ---------------------------------------------------------------
|
|
250
312
|
|
|
251
313
|
function check() {
|
|
@@ -275,7 +337,8 @@
|
|
|
275
337
|
|
|
276
338
|
if (value !== last) {
|
|
277
339
|
last = value;
|
|
278
|
-
G.emit('griffin:change', el, { value: value });
|
|
340
|
+
G.emit('griffin:change', el, peer ? { value: value, range: range() } : { value: value });
|
|
341
|
+
push();
|
|
279
342
|
}
|
|
280
343
|
}
|
|
281
344
|
|
|
@@ -365,6 +428,11 @@
|
|
|
365
428
|
if (companion) open();
|
|
366
429
|
}
|
|
367
430
|
|
|
431
|
+
// Нативное поле: значение пишет платформа, и сосед по паре узнаёт о нём здесь.
|
|
432
|
+
function onNativeInput() {
|
|
433
|
+
if (!companion) push();
|
|
434
|
+
}
|
|
435
|
+
|
|
368
436
|
function onInput() {
|
|
369
437
|
check();
|
|
370
438
|
|
|
@@ -522,7 +590,10 @@
|
|
|
522
590
|
var m = base.getMonth();
|
|
523
591
|
var offset = (new Date(y, m, 1).getDay() - fd + 7) % 7;
|
|
524
592
|
var start = new Date(y, m, 1 - offset);
|
|
525
|
-
|
|
593
|
+
// Концы отрезка: у одиночного поля оба — его значение.
|
|
594
|
+
var ends = peer ? range() : { from: iso(), to: iso() };
|
|
595
|
+
var lo = dateOf(ends.from);
|
|
596
|
+
var hi = dateOf(ends.to);
|
|
526
597
|
var today = isoDate(new Date());
|
|
527
598
|
var min = dateOf(el.getAttribute('min'));
|
|
528
599
|
var max = dateOf(el.getAttribute('max'));
|
|
@@ -543,8 +614,9 @@
|
|
|
543
614
|
var cell = element('div', 'gr-datetime-day', { role: 'gridcell', id: panel.id + '-' + key, 'data-gr-date': key });
|
|
544
615
|
|
|
545
616
|
cell.textContent = String(date.getDate());
|
|
546
|
-
cell.setAttribute('aria-selected', key ===
|
|
617
|
+
cell.setAttribute('aria-selected', key === lo || key === hi ? 'true' : 'false');
|
|
547
618
|
|
|
619
|
+
if (lo && hi && lo < key && key < hi) cell.setAttribute('data-gr-in-range', '');
|
|
548
620
|
if (key === today) cell.setAttribute('aria-current', 'date');
|
|
549
621
|
if ((min && key < min) || (max && key > max)) cell.setAttribute('aria-disabled', 'true');
|
|
550
622
|
if (key === active) { cell.setAttribute('data-gr-state', 'active'); descendant = cell.id; }
|
|
@@ -566,7 +638,8 @@
|
|
|
566
638
|
opened = true;
|
|
567
639
|
active = null;
|
|
568
640
|
|
|
569
|
-
|
|
641
|
+
// Пустое поле пары открывается на месяце соседа.
|
|
642
|
+
var d = dateOf(iso()) || (peer ? dateOf(peer.value()) : '');
|
|
570
643
|
|
|
571
644
|
if (body) render(d ? fromIso(d) : new Date());
|
|
572
645
|
if (clock) clock.value = timeOf(iso());
|
|
@@ -682,21 +755,33 @@
|
|
|
682
755
|
|
|
683
756
|
events.add(el, 'pointerdown', onPointerDown);
|
|
684
757
|
events.add(el, 'click', onClick);
|
|
758
|
+
events.add(el, 'input', onNativeInput);
|
|
685
759
|
|
|
686
|
-
|
|
760
|
+
var api = {
|
|
687
761
|
el: el,
|
|
688
762
|
open: open,
|
|
689
763
|
close: close,
|
|
690
764
|
value: iso,
|
|
691
765
|
set: function (value) {
|
|
692
766
|
if (companion) write(value);
|
|
693
|
-
else el.value = value;
|
|
767
|
+
else { el.value = value; push(); }
|
|
694
768
|
},
|
|
769
|
+
// Пара: limit — граница от соседа (единственный путь, которым виджет
|
|
770
|
+
// пишет min/max чужого поля), pair — «other — моё „с“», unpair — развод.
|
|
771
|
+
limit: limit,
|
|
772
|
+
pair: function (other) {
|
|
773
|
+
peer = other;
|
|
774
|
+
role = 'to';
|
|
775
|
+
push();
|
|
776
|
+
},
|
|
777
|
+
unpair: unpair,
|
|
695
778
|
get panel() { return panel; },
|
|
696
779
|
destroy: function () {
|
|
697
780
|
restore();
|
|
698
781
|
events.removeAll();
|
|
699
782
|
|
|
783
|
+
if (peer) { var other = peer; unpair(); other.unpair(); }
|
|
784
|
+
|
|
700
785
|
if (panel) {
|
|
701
786
|
if (G.anchor) G.anchor.clear(panel);
|
|
702
787
|
panel.remove();
|
|
@@ -704,6 +789,18 @@
|
|
|
704
789
|
}
|
|
705
790
|
}
|
|
706
791
|
};
|
|
792
|
+
|
|
793
|
+
// Сосед по паре: уже поднятый — как есть, иначе поднимается здесь;
|
|
794
|
+
// сканер на нём потом найдёт живой экземпляр.
|
|
795
|
+
if (o.to && mode !== 'time') {
|
|
796
|
+
var target = document.querySelector(o.to);
|
|
797
|
+
var other = target && G.mount(target, 'datetime');
|
|
798
|
+
|
|
799
|
+
if (other) { peer = other; role = 'from'; other.pair(api); push(); }
|
|
800
|
+
else G.warn('datetime: поле «по» не найдено — ' + o.to);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return api;
|
|
707
804
|
});
|
|
708
805
|
|
|
709
806
|
G.datetime = { layout: layout, parse: parse, format: format };
|