griffincss-ui 0.26.2 → 0.27.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.
Files changed (80) hide show
  1. package/dist/griffincss-ui-scoped.css +1 -1
  2. package/dist/griffincss-ui.css +1 -1
  3. package/dist/griffincss-ui.js +1 -1
  4. package/dist/griffincss-ui.js.map +1 -1
  5. package/dist/griffinjs-combobox.js +1 -1
  6. package/dist/griffinjs-combobox.js.map +1 -1
  7. package/dist/griffinjs-core.js +1 -1
  8. package/dist/griffinjs-core.js.map +1 -1
  9. package/dist/griffinjs-counter.js +1 -1
  10. package/dist/griffinjs-counter.js.map +1 -1
  11. package/dist/griffinjs-datetime.js +1 -1
  12. package/dist/griffinjs-datetime.js.map +1 -1
  13. package/dist/griffinjs-dialog.js +1 -1
  14. package/dist/griffinjs-dialog.js.map +1 -1
  15. package/dist/griffinjs-dropdown.js +1 -1
  16. package/dist/griffinjs-dropdown.js.map +1 -1
  17. package/dist/griffinjs-fields.js +1 -1
  18. package/dist/griffinjs-fields.js.map +1 -1
  19. package/dist/griffinjs-file.js +1 -1
  20. package/dist/griffinjs-file.js.map +1 -1
  21. package/dist/griffinjs-lightbox.js +1 -1
  22. package/dist/griffinjs-lightbox.js.map +1 -1
  23. package/dist/griffinjs-loader.js +1 -1
  24. package/dist/griffinjs-loader.js.map +1 -1
  25. package/dist/griffinjs-mask.js +1 -1
  26. package/dist/griffinjs-mask.js.map +1 -1
  27. package/dist/griffinjs-megamenu.js +1 -1
  28. package/dist/griffinjs-megamenu.js.map +1 -1
  29. package/dist/griffinjs-otp.js +1 -1
  30. package/dist/griffinjs-otp.js.map +1 -1
  31. package/dist/griffinjs-phone.js +1 -1
  32. package/dist/griffinjs-phone.js.map +1 -1
  33. package/dist/griffinjs-range.js +1 -1
  34. package/dist/griffinjs-range.js.map +1 -1
  35. package/dist/griffinjs-rating.js +1 -1
  36. package/dist/griffinjs-rating.js.map +1 -1
  37. package/dist/griffinjs-slider.js +1 -1
  38. package/dist/griffinjs-slider.js.map +1 -1
  39. package/dist/griffinjs-sortable.js +1 -1
  40. package/dist/griffinjs-sortable.js.map +1 -1
  41. package/dist/griffinjs-tooltip.js +1 -1
  42. package/dist/griffinjs-tooltip.js.map +1 -1
  43. package/dist/griffinjs-track.js +1 -1
  44. package/dist/griffinjs-track.js.map +1 -1
  45. package/dist/griffinjs-validate.js +1 -1
  46. package/dist/griffinjs-validate.js.map +1 -1
  47. package/dist/griffinjs.css +1 -1
  48. package/dist/griffinjs.js +1 -1
  49. package/dist/griffinjs.js.map +1 -1
  50. package/package.json +2 -2
  51. package/scss/_prose.scss +116 -0
  52. package/scss/_steps.scss +81 -38
  53. package/scss/_table.scss +17 -6
  54. package/scss/griffincss-ui-scoped.scss +1 -0
  55. package/scss/griffincss-ui.scss +1 -0
  56. package/scss/griffinjs/_track.scss +10 -0
  57. package/scss/griffinjs/griffinjs.scss +1 -1
  58. package/src/griffincss-ui.js +5 -5
  59. package/src/griffinjs/core/griffinjs-core.js +10 -10
  60. package/src/griffinjs/core/loader.js +2 -2
  61. package/src/griffinjs/core/options.js +1 -1
  62. package/src/griffinjs/core/track.js +2 -2
  63. package/src/griffinjs/fields/counter.js +1 -1
  64. package/src/griffinjs/fields/datetime.js +2 -2
  65. package/src/griffinjs/fields/file.js +1 -1
  66. package/src/griffinjs/fields/mask.js +1 -1
  67. package/src/griffinjs/fields/otp.js +1 -1
  68. package/src/griffinjs/fields/phone.js +3 -3
  69. package/src/griffinjs/fields/rating.js +1 -1
  70. package/src/griffinjs/fields/validate.js +1 -1
  71. package/src/griffinjs/widgets/combobox.js +19 -6
  72. package/src/griffinjs/widgets/dialog.js +41 -12
  73. package/src/griffinjs/widgets/dropdown.js +1 -1
  74. package/src/griffinjs/widgets/gallery.js +1 -1
  75. package/src/griffinjs/widgets/lightbox.js +67 -20
  76. package/src/griffinjs/widgets/megamenu.js +28 -8
  77. package/src/griffinjs/widgets/range.js +2 -2
  78. package/src/griffinjs/widgets/slider.js +1 -1
  79. package/src/griffinjs/widgets/sortable.js +1 -1
  80. package/src/griffinjs/widgets/tooltip.js +1 -1
package/scss/_steps.scss CHANGED
@@ -18,12 +18,17 @@
18
18
  // Состояний три: пройденный (.gr-step-done), текущий (aria-current="step")
19
19
  // и ожидающий — он же состояние по умолчанию. Текущий находится по тому же
20
20
  // признаку, по которому его находит программа чтения с экрана; класса
21
- // для него нет намеренно.
21
+ // для него нет намеренно. Перечню этапов без хода — «Этапы работы»
22
+ // лендинга — состояния не нужны: .gr-steps-plain делает все шаги
23
+ // равноправными.
22
24
  //
23
25
  // Знак в маркере кладут в разметку: галочка у пройденного шага и номер
24
26
  // у остальных — это содержимое, а не оформление. Цвет остаётся вторым
25
27
  // признаком поверх знака и позиции, а не единственным.
26
28
 
29
+ @use 'griffincss-core/scss/breakpoints' as bp;
30
+ @use 'griffincss-core/scss/container-mixins' as cq;
31
+
27
32
  .gr-steps {
28
33
  --gr-step-marker-size: 2rem;
29
34
 
@@ -125,6 +130,26 @@
125
130
  line-height: 1.35;
126
131
  }
127
132
 
133
+ // Подпись и пояснение одним блоком: в колонке он встаёт справа от маркера,
134
+ // в ряду — под ним, и там ведёт себя как её отсутствие: строки друг под
135
+ // другом с тем же зазором, что у шага. Нужен разметке, которая бывает
136
+ // и тем и другим (.gr-steps-stack-*).
137
+ .gr-step-body {
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: calc(var(--gr-ui-gap) * 0.5);
141
+ }
142
+
143
+ // --- Равноправный список ----------------------------------------------------
144
+
145
+ // Перечень этапов без хода процесса: подпись цвета текста, маркер
146
+ // акцентный, линия нейтральная. Вес — как у .gr-step, и стоит до состояний:
147
+ // пройденный и текущий шаг в таком списке по-прежнему сильнее.
148
+ :where(.gr-steps-plain) .gr-step {
149
+ --gr-step-color: var(--gr-color-text);
150
+ --gr-step-marker-fg: var(--gr-color-accent);
151
+ }
152
+
128
153
  // --- Состояния --------------------------------------------------------------
129
154
 
130
155
  // Пройденный: его хвост залит акцентом — путь от него дальше проделан.
@@ -157,41 +182,59 @@
157
182
 
158
183
  // Колонка: маркер слева, подпись справа. На узком экране это единственный
159
184
  // работающий вариант — горизонтальный ряд из четырёх шагов там нечитаем.
160
- .gr-steps-vertical {
161
- flex-direction: column;
162
- gap: var(--gr-gap);
163
- }
164
-
165
- .gr-steps-vertical > :where(li) {
166
- flex: none;
167
- }
168
-
169
- .gr-steps-vertical .gr-step {
170
- flex: none;
171
- flex-direction: row;
172
- align-items: flex-start;
173
- gap: var(--gr-ui-gap);
174
- text-align: start;
175
- }
176
-
177
- // В колонке хвост идёт вниз от маркера до маркера следующего шага. Длину
178
- // задают оба вылета сразу, а block-size: auto считает её из них: сколько бы
179
- // текста ни висело под маркером, линия дотянется — это и есть довод
180
- // в пользу хвоста, а не отрезка фиксированной длины.
181
- .gr-steps-vertical .gr-step::after {
182
- inset-block: var(--gr-step-marker-size) calc(var(--gr-gap) * -1);
183
- inset-inline: calc(var(--gr-step-marker-size) / 2) auto;
184
- inline-size: var(--gr-border-width);
185
- block-size: auto;
186
- margin-block-start: 0;
187
- margin-inline-start: calc(var(--gr-border-width) * -0.5);
188
- }
189
-
190
- // Подпись и пояснение встают в колонку: в строке они разъезжались бы
191
- // по базовой линии маркера.
192
- .gr-steps-vertical .gr-step-body {
193
- display: flex;
194
- flex-direction: column;
195
- gap: calc(var(--gr-ui-gap) * 0.25);
196
- padding-block-start: calc((var(--gr-step-marker-size) - 1.3em) / 2);
185
+ // Правила колонки описаны один раз: .gr-steps-vertical — всегда,
186
+ // .gr-steps-stack-{bp} и -c{bp} — ниже порога (см. ниже).
187
+ @mixin gr-steps-vertical($s) {
188
+ #{$s} {
189
+ flex-direction: column;
190
+ gap: var(--gr-gap);
191
+ }
192
+
193
+ #{$s} > :where(li) {
194
+ flex: none;
195
+ }
196
+
197
+ #{$s} .gr-step {
198
+ flex: none;
199
+ flex-direction: row;
200
+ align-items: flex-start;
201
+ gap: var(--gr-ui-gap);
202
+ text-align: start;
203
+ }
204
+
205
+ // В колонке хвост идёт вниз от маркера до маркера следующего шага. Длину
206
+ // задают оба вылета сразу, а block-size: auto считает её из них: сколько бы
207
+ // текста ни висело под маркером, линия дотянется — это и есть довод
208
+ // в пользу хвоста, а не отрезка фиксированной длины.
209
+ #{$s} .gr-step::after {
210
+ inset-block: var(--gr-step-marker-size) calc(var(--gr-gap) * -1);
211
+ inset-inline: calc(var(--gr-step-marker-size) / 2) auto;
212
+ inline-size: var(--gr-border-width);
213
+ block-size: auto;
214
+ margin-block-start: 0;
215
+ margin-inline-start: calc(var(--gr-border-width) * -0.5);
216
+ }
217
+
218
+ // Подпись и пояснение встают в колонку рядом с маркером (сама колонка —
219
+ // у .gr-step-body всегда); первая строка — по центру маркера.
220
+ #{$s} .gr-step-body {
221
+ gap: calc(var(--gr-ui-gap) * 0.25);
222
+ padding-block-start: calc((var(--gr-step-marker-size) - 1.3em) / 2);
223
+ }
224
+ }
225
+
226
+ @include gr-steps-vertical('.gr-steps-vertical');
227
+
228
+ // Столбиком ниже порога, рядом — от порога: суффикс называет порог,
229
+ // как у .gr-table-stack-md. -md — колонка, пока окно уже 768px;
230
+ // -cmd — пока уже 768px ближайший предок с .gr-cq. Разметка одна на оба
231
+ // вида: подпись и пояснение — в .gr-step-body.
232
+ @each $bp-name, $bp-value in bp.$gr-breakpoints {
233
+ @media (width < $bp-value) {
234
+ @include gr-steps-vertical('.gr-steps-stack-#{$bp-name}');
235
+ }
236
+
237
+ @include cq.gr-container-below($bp-name) {
238
+ @include gr-steps-vertical('.gr-steps-stack-c#{$bp-name}');
239
+ }
197
240
  }
package/scss/_table.scss CHANGED
@@ -6,12 +6,18 @@
6
6
  //
7
7
  // Ресет опционален, поэтому border-collapse задаётся здесь: без него
8
8
  // у ячеек остались бы двойные линии и зазоры border-spacing.
9
+ //
10
+ // Таблица внутри .gr-prose оформлена теми же правилами: класс на <table>
11
+ // редактор не поставит. Селекторы прозы — под :where(), с нулевым весом:
12
+ // варианты (.gr-table-compact и другие) на такой таблице по-прежнему
13
+ // сильнее базы.
9
14
 
10
15
  @use 'sass:string';
11
16
  @use 'griffincss-core/scss/breakpoints' as bp;
12
17
  @use 'griffincss-core/scss/container-mixins' as cq;
13
18
 
14
- .gr-table {
19
+ .gr-table,
20
+ :where(.gr-prose table) {
15
21
  inline-size: 100%;
16
22
  border-collapse: collapse;
17
23
  color: var(--gr-color-text);
@@ -19,7 +25,8 @@
19
25
  }
20
26
 
21
27
  .gr-table th,
22
- .gr-table td {
28
+ .gr-table td,
29
+ :where(.gr-prose table :is(th, td)) {
23
30
  padding-block: calc(var(--gr-gap) * 0.5);
24
31
  padding-inline: calc(var(--gr-gap) * 0.75);
25
32
  border-block-end: var(--gr-border-width) solid var(--gr-color-border);
@@ -27,24 +34,28 @@
27
34
  vertical-align: top;
28
35
  }
29
36
 
30
- .gr-table th {
37
+ .gr-table th,
38
+ :where(.gr-prose table th) {
31
39
  color: var(--gr-color-text-secondary);
32
40
  font-size: 0.875rem;
33
41
  font-weight: 600;
34
42
  white-space: nowrap;
35
43
  }
36
44
 
37
- .gr-table thead th {
45
+ .gr-table thead th,
46
+ :where(.gr-prose table thead th) {
38
47
  border-block-end: calc(var(--gr-border-width) * 2) solid var(--gr-color-border-strong);
39
48
  }
40
49
 
41
- .gr-table tfoot td {
50
+ .gr-table tfoot td,
51
+ :where(.gr-prose table tfoot td) {
42
52
  border-block-end: 0;
43
53
  color: var(--gr-color-text-secondary);
44
54
  font-size: 0.875rem;
45
55
  }
46
56
 
47
- .gr-table caption {
57
+ .gr-table caption,
58
+ :where(.gr-prose table caption) {
48
59
  padding-block-end: calc(var(--gr-gap) * 0.5);
49
60
  color: var(--gr-color-text-secondary);
50
61
  font-size: 0.875rem;
@@ -64,6 +64,7 @@
64
64
  @include meta.load-css('nav');
65
65
  @include meta.load-css('pagination');
66
66
  @include meta.load-css('progress');
67
+ @include meta.load-css('prose');
67
68
  @include meta.load-css('range');
68
69
  @include meta.load-css('rating');
69
70
  @include meta.load-css('skeleton');
@@ -58,6 +58,7 @@
58
58
  @include meta.load-css('nav');
59
59
  @include meta.load-css('pagination');
60
60
  @include meta.load-css('progress');
61
+ @include meta.load-css('prose');
61
62
  @include meta.load-css('range');
62
63
  @include meta.load-css('rating');
63
64
  @include meta.load-css('skeleton');
@@ -46,6 +46,16 @@
46
46
  }
47
47
  }
48
48
 
49
+ // Слайды своей ширины: лента историй, полоса тегов, ряд логотипов. Ширину
50
+ // даёт содержимое — аватар, атрибуты width/height картинки, текст кнопки;
51
+ // утилита ширины на слайде доли 100% не перебьёт, основа здесь — flex-basis.
52
+ // Не сжимаются (flex-shrink: 0 базового правила): лишнее уходит в прокрутку.
53
+ // Стоит до суффиксных долей: .gr-track-auto .gr-track-3-lg — своя ширина
54
+ // на узком окне и три в ряд на широком.
55
+ .gr-track-auto > * {
56
+ flex-basis: auto;
57
+ }
58
+
49
59
  // Доля слайда описана один раз: без суффикса — база, с суффиксом
50
60
  // брейкпоинта — от ширины окна, с буквой c — от ширины ближайшего предка
51
61
  // с .gr-cq (Этап 47d): виджет в слоте меряется слотом. Виджету дорожки
@@ -8,7 +8,7 @@
8
8
  // griffinjs.css подключается всегда ПОВЕРХ griffincss-ui.css, где он есть.
9
9
  //
10
10
  // Оформление привязывается к состояниям, а не к классам, которые JS
11
- // добавляет: [data-gr-state="open"], .gr-slide[aria-current],
11
+ // добавляет: [data-gr-state="open"], .gr-track > [aria-current],
12
12
  // var(--gr-progress). Движение — здесь, в CSS; JS transform и opacity
13
13
  // не пишет никогда (инварианты слоя — docs/griffinjs-architecture.html).
14
14
  //
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Griffincss UI — Runtime v0.26.2
2
+ * Griffincss UI — Runtime v0.27.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.26.2';
53
+ var VERSION = '0.27.1';
54
54
 
55
55
  var OVERLAY_ATTR = 'data-gr-overlay-close';
56
56
 
@@ -234,7 +234,7 @@
234
234
  : closest(trigger, DISMISSIBLE);
235
235
 
236
236
  if (!target) {
237
- warn('крестику нечего закрывать: ни цели в data-gr-dismiss, ни сообщения вокруг');
237
+ warn('close button has nothing to close: no data-gr-dismiss target and no enclosing message');
238
238
  return true;
239
239
  }
240
240
 
@@ -343,8 +343,8 @@
343
343
  var urgent = options.assertive === undefined ? status === 'danger' : !!options.assertive;
344
344
 
345
345
  if (status && !options.title) {
346
- warn('тост со статусом "' + status + '" без title: статус остался одним цветом, '
347
- + 'а он не переживает чёрно-белую печать и дальтонизм');
346
+ warn('toast with status "' + status + '" has no title: the status is shown by colour alone, '
347
+ + 'which is lost in greyscale print and to colour-blind readers');
348
348
  }
349
349
 
350
350
  var node = buildToast(text, options, status);
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * GriffinJS — Core v0.26.2
2
+ * GriffinJS — Core v0.27.1
3
3
  * Опциональный слой виджетов с состоянием поверх Griffincss: слайдер,
4
4
  * галерея, лайтбокс, параллакс, мегаменю. Подключается одной строкой
5
5
  * и одним файлом; страница без него — законное и рабочее состояние.
@@ -45,7 +45,7 @@
45
45
  })(function () {
46
46
  'use strict';
47
47
 
48
- var VERSION = '0.26.2';
48
+ var VERSION = '0.27.1';
49
49
 
50
50
  // Реестры. Модуль зовёт defineEngine/defineWidget; ядро — единственное
51
51
  // место, которое знает, как их применить.
@@ -84,13 +84,13 @@
84
84
  if (typeof opts === 'function') { factory = opts; opts = null; }
85
85
 
86
86
  if (typeof name !== 'string' || !name) {
87
- throw new Error('GriffinJS: ' + kind + ' без имени');
87
+ throw new Error('GriffinJS: ' + kind + ' without a name');
88
88
  }
89
89
  if (typeof factory !== 'function') {
90
- throw new Error('GriffinJS: ' + kind + ' «' + name + '» — не функция');
90
+ throw new Error('GriffinJS: ' + kind + ' "' + name + '" is not a function');
91
91
  }
92
92
  if (registry[name]) {
93
- throw new Error('GriffinJS: ' + kind + ' «' + name + '» уже зарегистрирован');
93
+ throw new Error('GriffinJS: ' + kind + ' "' + name + '" is already registered');
94
94
  }
95
95
 
96
96
  if (opts && opts.needs) needs(name, opts.needs);
@@ -101,11 +101,11 @@
101
101
  }
102
102
 
103
103
  function defineEngine(name, opts, factory) {
104
- return define(engines, 'движок', name, opts, factory);
104
+ return define(engines, 'engine', name, opts, factory);
105
105
  }
106
106
 
107
107
  function defineWidget(name, opts, factory) {
108
- return define(widgets, 'виджет', name, opts, factory);
108
+ return define(widgets, 'widget', name, opts, factory);
109
109
  }
110
110
 
111
111
  // Проверка сборки на старте: модуль подключён, а часть, которую он берёт
@@ -124,7 +124,7 @@
124
124
  // Зависимостью бывает и часть G (track, anchor), и целый модуль:
125
125
  // галерея строится поверх слайдера.
126
126
  if (!api[dep] && !widgets[dep] && !engines[dep]) {
127
- warn('«' + names[i] + '» не работает без griffinjs-' + dep + '.js');
127
+ warn('"' + names[i] + '" does not work without griffinjs-' + dep + '.js');
128
128
  }
129
129
  }
130
130
  }
@@ -228,7 +228,7 @@
228
228
  // два слайдера на одной дорожке — ошибка автора, а не два состояния.
229
229
  function mount(el, name, opts) {
230
230
  if (!el || !widgets[name]) {
231
- warn('виджет «' + name + '» не зарегистрирован');
231
+ warn('widget "' + name + '" is not registered');
232
232
  return null;
233
233
  }
234
234
 
@@ -244,7 +244,7 @@
244
244
  record.instance = widgets[name](el, opts, api) || {};
245
245
  } catch (e) {
246
246
  mounted.splice(mounted.indexOf(record), 1);
247
- warn('виджет «' + name + '» не поднялся: ' + (e && e.message));
247
+ warn('widget "' + name + '" failed to mount: ' + (e && e.message));
248
248
  return null;
249
249
  }
250
250
 
@@ -62,7 +62,7 @@
62
62
  if (config.fields && config.fields.src) return request();
63
63
 
64
64
  warned[name] = true;
65
- G.warn('поле «' + name + '» не собрано: подключите griffinjs-fields.js или задайте GriffinJS.config.fields');
65
+ G.warn('field "' + name + '" is not loaded: include griffinjs-fields.js or set GriffinJS.config.fields');
66
66
  }
67
67
  }
68
68
 
@@ -85,7 +85,7 @@
85
85
  function settle(event) {
86
86
  if (event.type === 'error') {
87
87
  ok = false;
88
- G.warn('бандл полей не загружен: ' + event.target.src + ' — поля остались базой без скрипта');
88
+ G.warn('fields bundle failed to load: ' + event.target.src + '; fields stay without the script');
89
89
  }
90
90
 
91
91
  if (--left) return;
@@ -37,7 +37,7 @@
37
37
  try {
38
38
  return JSON.parse(text);
39
39
  } catch (e) {
40
- G.warn('параметры не разобраны как JSON: ' + text);
40
+ G.warn('options are not valid JSON: ' + text);
41
41
  return out;
42
42
  }
43
43
  }
@@ -20,7 +20,7 @@
20
20
  * состояние, только physical.
21
21
  *
22
22
  * JS пишет только aria-*, inert, --gr-progress и --gr-snap (снап-точки
23
- * по страницам); оформление активного слайда — .gr-slide[aria-current] в CSS.
23
+ * по страницам); оформление активного слайда — .gr-track > [aria-current] в CSS.
24
24
  */
25
25
  (function (G) {
26
26
  'use strict';
@@ -542,7 +542,7 @@
542
542
  var factory = G.engines[o.engine];
543
543
 
544
544
  if (!factory) {
545
- G.warn('движок «' + o.engine + '» не зарегистрирован — дорожка без движения');
545
+ G.warn('engine "' + o.engine + '" is not registered; the track will not move');
546
546
  } else {
547
547
  engine = factory(api, el, o) || null;
548
548
  }
@@ -32,7 +32,7 @@
32
32
  var attrs = G.recorder();
33
33
  var events = G.listeners();
34
34
 
35
- if (!(max > 0)) throw new Error('у поля нет maxlength — считать не к чему');
35
+ if (!(max > 0)) throw new Error('the field has no maxlength: nothing to count');
36
36
 
37
37
  var out = o.out ? document.querySelector(o.out) : null;
38
38
  var created = false;
@@ -235,7 +235,7 @@
235
235
  var native = el.getAttribute('type');
236
236
  var mode = MODES[native];
237
237
 
238
- if (!mode) throw new Error('нужен type="date", "time" или "datetime-local"');
238
+ if (!mode) throw new Error('needs type="date", "time" or "datetime-local"');
239
239
 
240
240
  var attrs = G.recorder();
241
241
  var events = G.listeners(); // на всё время жизни
@@ -797,7 +797,7 @@
797
797
  var other = target && G.mount(target, 'datetime');
798
798
 
799
799
  if (other) { peer = other; role = 'from'; other.pair(api); push(); }
800
- else G.warn('datetime: поле «по» не найдено — ' + o.to);
800
+ else G.warn('datetime: the "to" field is not found: ' + o.to);
801
801
  }
802
802
 
803
803
  return api;
@@ -77,7 +77,7 @@
77
77
  G.defineWidget('file', function (el, opts) {
78
78
  var o = G.merge(DEFAULTS, opts);
79
79
 
80
- if (typeof DataTransfer !== 'function') throw new Error('без DataTransfer убрать один файл из выбранного нельзя');
80
+ if (typeof DataTransfer !== 'function') throw new Error('without DataTransfer a single file cannot be removed from the selection');
81
81
 
82
82
  var attrs = G.recorder();
83
83
  var events = G.listeners();
@@ -163,7 +163,7 @@
163
163
  // разобрался бы как пара «00: 00», поэтому без ключа берётся строка как есть.
164
164
  var format = o.format ? String(o.format) : (el.getAttribute('data-gr-mask') || '').trim();
165
165
 
166
- if (!format) throw new Error('у маски нет формата');
166
+ if (!format) throw new Error('the mask has no format');
167
167
 
168
168
  var tokens;
169
169
  var last = null; // последнее записанное значение
@@ -35,7 +35,7 @@
35
35
  G.defineWidget('otp', function (el) {
36
36
  var cells = el.querySelectorAll('input');
37
37
 
38
- if (!cells.length) throw new Error('внутри нет ни одной ячейки <input>');
38
+ if (!cells.length) throw new Error('no <input> cells inside');
39
39
 
40
40
  var attrs = G.recorder();
41
41
  var events = G.listeners();
@@ -93,7 +93,7 @@
93
93
  var input = el.querySelector('input');
94
94
  var locale = lang();
95
95
 
96
- if (!input) throw new Error('у поля телефона нет <input>');
96
+ if (!input) throw new Error('the phone field has no <input>');
97
97
 
98
98
  var created = []; // позиции, добавленные из таблицы
99
99
  var texts = []; // [позиция, подпись автора] — для destroy
@@ -142,11 +142,11 @@
142
142
  // Без country первой стоит первая по алфавиту языка страницы —
143
143
  // на русской это Австралия, +61. Угадывать страну за автора виджет
144
144
  // не берётся: ошибся бы тише, чем сейчас. Но и молчать не должен.
145
- if (!o.country) G.warn('телефон: страна не задана — выбрана первая по алфавиту; укажите country');
145
+ if (!o.country) G.warn('phone: no country set, the first one alphabetically is used; set country');
146
146
  }
147
147
 
148
148
  if (!options().length) {
149
- G.warn('телефон: список кодов пуст — перечислите <option> сами или подключите griffinjs-countries.js');
149
+ G.warn('phone: the code list is empty; list the <option>s yourself or include griffinjs-countries.js');
150
150
  }
151
151
 
152
152
  // Подписи: флаг + имя в языке страницы + код. Только там, где есть ISO
@@ -37,7 +37,7 @@
37
37
  var display = el.querySelector('.gr-rating');
38
38
  var radios = el.querySelectorAll('input[type="radio"]');
39
39
 
40
- if (!display || !radios.length) throw new Error('нужны .gr-rating и <input type="radio"> внутри');
40
+ if (!display || !radios.length) throw new Error('needs .gr-rating and <input type="radio"> inside');
41
41
 
42
42
  var attrs = G.recorder();
43
43
  var events = G.listeners();
@@ -45,7 +45,7 @@
45
45
  }
46
46
 
47
47
  G.defineWidget('validate', function (el, opts) {
48
- if (String(el.tagName).toLowerCase() !== 'form') throw new Error('нужен <form>');
48
+ if (String(el.tagName).toLowerCase() !== 'form') throw new Error('needs a <form>');
49
49
 
50
50
  var o = G.merge(DEFAULTS, opts);
51
51
  var attrs = G.recorder();
@@ -8,6 +8,7 @@
8
8
  * Источник — адрес с {q} (ответ — JSON: массив строк или объектов
9
9
  * {value, label, href, group, id}, либо {items: […]}) или функция
10
10
  * через JS: GriffinJS.mount(el, 'combobox', { source: (q, signal) => Promise }).
11
+ * С флагом navigate выбор позиции переходит по её href — только http(s).
11
12
  * Рендер позиции подменяется render(item) → узел.
12
13
  *
13
14
  * База без JS — само поле: форма поиска отправляется Enter, как всегда.
@@ -140,12 +141,12 @@
140
141
 
141
142
  var input = el.querySelector('input');
142
143
 
143
- if (!input) throw new Error('у комбобокса нет поля ввода');
144
+ if (!input) throw new Error('the combobox has no input');
144
145
 
145
146
  var choice = o.multiple ? el.querySelector('select[multiple]') : null;
146
147
 
147
- if (o.multiple && !choice) throw new Error('для multiple нужен <select multiple> внутри');
148
- if (o.free && !choice) { G.warn('комбобокс: free без multiple смысла не имеет — флаг пропущен'); o.free = false; }
148
+ if (o.multiple && !choice) throw new Error('multiple needs a <select multiple> inside');
149
+ if (o.free && !choice) { G.warn('combobox: free makes no sense without multiple; flag ignored'); o.free = false; }
149
150
 
150
151
  var list = el.querySelector('[role="listbox"]');
151
152
  var created = false;
@@ -276,7 +277,7 @@
276
277
  return response.json();
277
278
  });
278
279
  } else {
279
- G.warn('комбобокс: нет источника (src или source)');
280
+ G.warn('combobox: no source (src or source)');
280
281
  return;
281
282
  }
282
283
 
@@ -292,7 +293,7 @@
292
293
  render(query, found);
293
294
  }, function (error) {
294
295
  if (error && error.name === 'AbortError') return;
295
- G.warn('комбобокс: запрос не удался: ' + (error && error.message));
296
+ G.warn('combobox: request failed: ' + (error && error.message));
296
297
  });
297
298
  }
298
299
 
@@ -440,11 +441,23 @@
440
441
  close();
441
442
  G.emit('griffin:select', input, { item: item, index: index, input: input });
442
443
 
443
- if (o.navigate && item.href && typeof location !== 'undefined') location.href = item.href;
444
+ if (o.navigate && item.href && typeof location !== 'undefined') navigate(item.href);
444
445
 
445
446
  return api;
446
447
  }
447
448
 
449
+ // Переход — только по http(s): href приходит из ответа источника,
450
+ // и javascript: исполнился бы в origin страницы. Выбор при отказе
451
+ // остаётся — значение в поле, событие ушло.
452
+ function navigate(href) {
453
+ var url = null;
454
+
455
+ try { url = new URL(href, document.baseURI); } catch (e) { /* не адрес */ }
456
+
457
+ if (url && /^https?:$/.test(url.protocol)) location.href = href;
458
+ else G.warn('combobox: navigation refused: ' + href);
459
+ }
460
+
448
461
  // --- События --------------------------------------------------------------
449
462
 
450
463
  function onInput() { query(); }