griffincss-ui 0.25.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.
Files changed (48) 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-core.js +1 -1
  6. package/dist/griffinjs-core.js.map +1 -1
  7. package/dist/griffinjs-dialog.js +1 -1
  8. package/dist/griffinjs-dialog.js.map +1 -1
  9. package/dist/griffinjs-dropdown.js +1 -1
  10. package/dist/griffinjs-dropdown.js.map +1 -1
  11. package/dist/griffinjs-fields.css +1 -1
  12. package/dist/griffinjs-fields.js +1 -1
  13. package/dist/griffinjs-fields.js.map +1 -1
  14. package/dist/griffinjs-loader.js +3 -0
  15. package/dist/griffinjs-loader.js.map +1 -0
  16. package/dist/griffinjs-megamenu.js +1 -1
  17. package/dist/griffinjs-megamenu.js.map +1 -1
  18. package/dist/griffinjs-validate.js +1 -1
  19. package/dist/griffinjs-validate.js.map +1 -1
  20. package/dist/griffinjs.css +1 -1
  21. package/dist/griffinjs.js +1 -1
  22. package/dist/griffinjs.js.map +1 -1
  23. package/package.json +4 -3
  24. package/scss/_accordion.scss +5 -2
  25. package/scss/_alert.scss +1 -1
  26. package/scss/_card.scss +33 -7
  27. package/scss/_choice.scss +36 -4
  28. package/scss/_dropdown.scss +7 -3
  29. package/scss/_form.scss +42 -1
  30. package/scss/_menu.scss +4 -1
  31. package/scss/_modal.scss +44 -7
  32. package/scss/_nav.scss +5 -2
  33. package/scss/_rating.scss +7 -1
  34. package/scss/_table.scss +91 -0
  35. package/scss/_tabs.scss +4 -1
  36. package/scss/griffincss-ui-scoped.scss +17 -7
  37. package/scss/griffincss-ui.scss +16 -10
  38. package/scss/griffinjs/_track.scss +43 -4
  39. package/scss/griffinjs/fields/_number.scss +9 -11
  40. package/scss/griffinjs/fields/fields.scss +1 -1
  41. package/scss/griffinjs/griffinjs.scss +1 -1
  42. package/src/griffincss-ui.js +2 -2
  43. package/src/griffinjs/core/griffinjs-core.js +57 -11
  44. package/src/griffinjs/core/loader.js +124 -0
  45. package/src/griffinjs/fields/validate.js +24 -3
  46. package/src/griffinjs/widgets/dialog.js +67 -2
  47. package/src/griffinjs/widgets/dropdown.js +94 -4
  48. package/src/griffinjs/widgets/megamenu.js +90 -5
package/scss/_choice.scss CHANGED
@@ -134,7 +134,6 @@
134
134
  display: block;
135
135
  padding-block: 0.375rem;
136
136
  padding-inline: var(--gr-ui-gap);
137
- border-inline-start: var(--gr-border-width) solid var(--gr-color-border);
138
137
  color: var(--gr-color-text-secondary);
139
138
  font-size: 0.875rem;
140
139
  text-align: center;
@@ -143,18 +142,27 @@
143
142
  transition: background-color var(--gr-transition), color var(--gr-transition);
144
143
  }
145
144
 
145
+ // Внутренняя рамка — у сегмента, перед которым стоит другой сегмент,
146
+ // а не у каждого с последующим снятием у первого. Края и рамка считаются
147
+ // по ярлыкам, а не по любым потомкам: у <fieldset> штатное имя — <legend>
148
+ // (в том числе скрытый, .gr-sr-only), и первым потомком он отнимал
149
+ // у первого сегмента скругление и левую рамку, а хвостовой <script>
150
+ // или скрытое поле — скругление у последнего (фидбек темы griffin, п. 2).
151
+ .gr-segmented label ~ label span {
152
+ border-inline-start: var(--gr-border-width) solid var(--gr-color-border);
153
+ }
154
+
146
155
  // Скругление крайних сегментов задаётся явно, а не берётся из overflow:
147
156
  // hidden на обёртке. Обрезка по overflow идёт по внутреннему краю рамки,
148
157
  // но с ВНЕШНИМ радиусом, поэтому в углах между заливкой и рамкой оставался
149
158
  // зазор — фон переключателя не совпадал с его границами. Свои радиусы
150
159
  // у сегментов ложатся по рамке точно.
151
- .gr-segmented label:first-child span {
152
- border-inline-start: 0;
160
+ .gr-segmented label:first-of-type span {
153
161
  border-start-start-radius: var(--gr-segmented-radius);
154
162
  border-end-start-radius: var(--gr-segmented-radius);
155
163
  }
156
164
 
157
- .gr-segmented label:last-child span {
165
+ .gr-segmented label:last-of-type span {
158
166
  border-start-end-radius: var(--gr-segmented-radius);
159
167
  border-end-end-radius: var(--gr-segmented-radius);
160
168
  }
@@ -201,6 +209,30 @@
201
209
  cursor: not-allowed;
202
210
  }
203
211
 
212
+ // Два модификатора, а не рецепты из утилит (фидбек темы griffin, п. 6).
213
+ // Утилиты флекса лежат в слое core и проигрывают align-items и display
214
+ // компонента в ui при любой специфичности; .gr-w-full живёт в utils,
215
+ // а пакет компонентов обязан собираться и без него.
216
+ //
217
+ // Согласие «Я прочитал условия…» в две-три строки: контрол стоит
218
+ // по первой строке, а не по центру блока. Сдвиг — к середине первой
219
+ // строки: при line-height 1,4 квадратик 1,15em ложился бы по её верху.
220
+ .gr-choice-start {
221
+ align-items: flex-start;
222
+ }
223
+
224
+ .gr-choice-start > input {
225
+ margin-block-start: 0.15em;
226
+ }
227
+
228
+ // Строка настроек «подпись — тумблер»: блочный флекс во всю ширину,
229
+ // подпись слева, переключатель у правого края, кликается вся строка.
230
+ // В инлайновом контексте автор берёт .gr-choice.
231
+ .gr-choice-row {
232
+ display: flex;
233
+ justify-content: space-between;
234
+ }
235
+
204
236
  // --- Образец цвета и плашка размера -----------------------------------------
205
237
 
206
238
  // Радио, которую видят как образец: кружок цвета товара или плашка
@@ -38,15 +38,19 @@
38
38
 
39
39
  // --- Панель -----------------------------------------------------------------
40
40
 
41
+ // Ширина и отступ панели — токенами (Этап 46): панель — голая коробка
42
+ // позиционирования, рамку и фон даёт .gr-menu внутри, а теме нужна панель
43
+ // заданной ширины под свою разметку и с полем вокруг неё.
41
44
  .gr-dropdown-panel {
42
45
  position: absolute;
43
46
  z-index: var(--gr-z-dropdown);
44
47
  inset-block-start: calc(100% + var(--gr-ui-gap) * 0.5);
45
48
  inset-inline-start: 0;
46
49
  box-sizing: border-box;
47
- min-inline-size: max-content;
50
+ min-inline-size: var(--gr-dropdown-min-width, max-content);
48
51
  max-inline-size: min(20rem, 90vw);
49
52
  margin: 0;
53
+ padding: var(--gr-dropdown-pad, 0);
50
54
  }
51
55
 
52
56
  // Раскрытие к концу строки — для панели у правого края шапки, которая иначе
@@ -74,7 +78,7 @@
74
78
  max-inline-size: 100%;
75
79
  margin: 0;
76
80
  border: 0;
77
- padding: 0;
81
+ padding: var(--gr-dropdown-pad, 0);
78
82
  background: none;
79
83
  color: inherit;
80
84
  overflow: visible;
@@ -102,7 +106,7 @@
102
106
  position-anchor: --gr-dropdown-anchor;
103
107
  inset-block: calc(anchor(end) + var(--gr-ui-gap) * 0.5) auto;
104
108
  inset-inline: anchor(start) auto;
105
- min-inline-size: max-content;
109
+ min-inline-size: var(--gr-dropdown-min-width, max-content);
106
110
  max-inline-size: min(20rem, 90%);
107
111
 
108
112
  // Если снизу места нет — панель переворачивается вверх, если не хватает
package/scss/_form.scss CHANGED
@@ -168,6 +168,14 @@
168
168
  // :user-invalid, а не :invalid — пустое обязательное поле не должно краснеть
169
169
  // до того, как человек его тронул. Атрибут aria-invalid рядом: его ставит
170
170
  // серверная проверка, которой правила браузера не касаются.
171
+ //
172
+ // Третий источник — состояние на обёртке: .gr-field[data-gr-state="error"]
173
+ // (фидбек темы griffin, п. 7). Скрипты, которые красят ошибки после ответа
174
+ // сервера, получают список полей и ставят состояние на обёртку — так
175
+ // работает OpenCart (.form-group.has-error) и большинство валидаторов,
176
+ // и до сих пор обёртке .gr-field соответствовать было нечему. Состояние
177
+ // красит вложенные контролы; aria-invalid на контроле остаётся источником
178
+ // для программы чтения с экрана — виджет validate ставит оба.
171
179
  .gr-input:user-invalid,
172
180
  .gr-textarea:user-invalid,
173
181
  .gr-select:user-invalid,
@@ -175,7 +183,11 @@
175
183
  .gr-input[aria-invalid="true"],
176
184
  .gr-textarea[aria-invalid="true"],
177
185
  .gr-select[aria-invalid="true"],
178
- .gr-file[aria-invalid="true"] {
186
+ .gr-file[aria-invalid="true"],
187
+ .gr-field[data-gr-state="error"] .gr-input,
188
+ .gr-field[data-gr-state="error"] .gr-textarea,
189
+ .gr-field[data-gr-state="error"] .gr-select,
190
+ .gr-field[data-gr-state="error"] .gr-file {
179
191
  border-color: var(--gr-color-danger);
180
192
  }
181
193
 
@@ -377,6 +389,22 @@
377
389
  border-radius: 0;
378
390
  }
379
391
 
392
+ // Края — по видимым полям. Скрытые потомки в группе — обычная практика
393
+ // форм: полное значение составного поля (телефон = код + номер) в
394
+ // <input type="hidden">, признак формы, панель подсказки, которую живой
395
+ // поиск дописывает внутрь, спутник поля даты или суммы из второго бандла.
396
+ // По :first-child / :last-child скругление доставалось скрытому узлу,
397
+ // и видимый край группы оставался прямым (фидбек темы griffin, п. 1).
398
+ //
399
+ // Два яруса правил, а не один. Прежние :first-child / :last-child
400
+ // остаются запасным путём: форма `:nth-child(… of S)` появилась
401
+ // в Chrome 111 и Firefox 113 — позже каскадных слоёв (99 / 97), — и там,
402
+ // где она неизвестна, правило отбрасывается целиком, а группа выглядит
403
+ // как раньше. Мягкая деградация названа в docs/compatibility.html.
404
+ // Правила по видимым стоят ниже и специфичнее (0,3,0 против 0,2,0),
405
+ // поэтому в новых движках побеждают они.
406
+ $_gr-group-visible: ':not([type="hidden"], [hidden], template, script, datalist)';
407
+
380
408
  .gr-input-group > :first-child {
381
409
  border-start-start-radius: var(--gr-radius);
382
410
  border-end-start-radius: var(--gr-radius);
@@ -391,6 +419,19 @@
391
419
  margin-inline-start: calc(-1 * var(--gr-border-width));
392
420
  }
393
421
 
422
+ // Первое видимое поле — ещё и без сдвига на рамку: скрытый узел перед ним
423
+ // делает его для `* + *` вторым, и без этой строки оно уезжало бы на 1px.
424
+ .gr-input-group > :nth-child(1 of #{$_gr-group-visible}) {
425
+ margin-inline-start: 0;
426
+ border-start-start-radius: var(--gr-radius);
427
+ border-end-start-radius: var(--gr-radius);
428
+ }
429
+
430
+ .gr-input-group > :nth-last-child(1 of #{$_gr-group-visible}) {
431
+ border-start-end-radius: var(--gr-radius);
432
+ border-end-end-radius: var(--gr-radius);
433
+ }
434
+
394
435
  // Поле в группе тянется, аддон — нет.
395
436
  .gr-input-group > .gr-input,
396
437
  .gr-input-group > .gr-select {
package/scss/_menu.scss CHANGED
@@ -17,7 +17,10 @@
17
17
  padding-block: calc(var(--gr-ui-gap) * 0.5);
18
18
  padding-inline: 0;
19
19
  border: var(--gr-border-width) solid var(--gr-color-border);
20
- border-radius: var(--gr-radius);
20
+
21
+ // Радиус контейнера, как у карточки и окна: меню — видимая коробка
22
+ // выпадающей панели (Этап 46).
23
+ border-radius: var(--gr-radius-container, var(--gr-radius));
21
24
  background-color: var(--gr-color-surface-overlay);
22
25
  color: var(--gr-color-text);
23
26
  box-shadow: var(--gr-shadow-lg);
package/scss/_modal.scss CHANGED
@@ -30,16 +30,23 @@
30
30
  // Размеры — в процентах от окна, а не в 100vw: единица vw включает ширину
31
31
  // полосы прокрутки, поэтому окно «во всю ширину минус поля» получалось шире
32
32
  // видимой области и центрировалось со сдвигом в половину полосы.
33
+ //
34
+ // Минимальная высота — для окна, чьё содержимое приходит ajax'ом: форма
35
+ // обратного звонка, быстрый заказ, выбор города открываются сразу, а до
36
+ // ответа сервера <dialog> пуст и рисовался полоской высотой в рамку
37
+ // (фидбек темы griffin, п. 3). Плашка в 10rem — место кольцу загрузки
38
+ // ниже; плавный рост высоты с приходом содержимого — работа слоя.
33
39
  .gr-modal {
34
40
  position: fixed;
35
41
  box-sizing: border-box;
36
42
  flex-direction: column;
37
43
  inline-size: min(32rem, calc(100% - var(--gr-gap) * 2));
38
44
  max-inline-size: none;
45
+ min-block-size: 10rem;
39
46
  max-block-size: min(85dvh, calc(100% - var(--gr-gap) * 2));
40
47
  padding: 0;
41
48
  border: var(--gr-border-width) solid var(--gr-color-border);
42
- border-radius: var(--gr-radius);
49
+ border-radius: var(--gr-radius-container, var(--gr-radius));
43
50
  background-color: var(--gr-color-surface-overlay);
44
51
  color: var(--gr-color-text);
45
52
  box-shadow: var(--gr-shadow-xl);
@@ -62,6 +69,25 @@
62
69
  background-color: var(--gr-overlay);
63
70
  }
64
71
 
72
+ // Состояние загрузки: кольцо по центру пустого окна, тем же keyframe,
73
+ // что у .gr-spinner. Одним слоем, а не двумя, как у самого спиннера:
74
+ // у окна псевдоэлемент один, и дорожка красится цветом рамки вместо
75
+ // полупрозрачного currentcolor — окно стоит на известной поверхности.
76
+ // Центр — авто-отступами флекс-элемента: окно в состоянии [open] уже
77
+ // колонка, и ::before в ней — обычный элемент. Ставит и снимает состояние
78
+ // тот же скрипт, что грузит содержимое; рядом уместен aria-busy="true".
79
+ .gr-modal[data-gr-state="loading"]::before {
80
+ content: "";
81
+ box-sizing: border-box;
82
+ inline-size: 2.5em;
83
+ block-size: 2.5em;
84
+ margin: auto;
85
+ border: 0.18em solid var(--gr-color-border);
86
+ border-block-start-color: var(--gr-color-accent);
87
+ border-radius: var(--gr-radius-pill);
88
+ animation: gr-spin 0.8s linear infinite;
89
+ }
90
+
65
91
  // --- Пояса ------------------------------------------------------------------
66
92
 
67
93
  // Шапка и подвал не сжимаются и не прокручиваются: прокручивается тело.
@@ -86,11 +112,14 @@
86
112
  line-height: 1.3;
87
113
  }
88
114
 
115
+ // Прокрутка тела — токеном (Этап 46): окну с собственной прокруткой
116
+ // внутри (карта, список) тема ставит --gr-modal-body-overflow: hidden
117
+ // на обёртке, а не борется со слоем ui на самом теле.
89
118
  .gr-modal-body {
90
119
  flex: 1 1 auto;
91
120
  padding: var(--gr-gap);
92
121
  line-height: var(--gr-leading-base);
93
- overflow-y: auto;
122
+ overflow-y: var(--gr-modal-body-overflow, auto);
94
123
  overscroll-behavior: contain;
95
124
  }
96
125
 
@@ -128,16 +157,18 @@
128
157
 
129
158
  // Окно проявляется и подъезжает снизу, подложка — только проявляется.
130
159
  // display и overlay переводятся через allow-discrete: без этого окно
131
- // возникало бы мгновенно, минуя переход.
160
+ // возникало бы мгновенно, минуя переход. Длительность и кривая — токеном
161
+ // --gr-modal-transition (Этап 46): тема задаёт окну свой темп на обёртке,
162
+ // не переписывая четыре свойства перехода в правиле слоя ui.
132
163
  @supports (transition-behavior: allow-discrete) {
133
164
  .gr-modal {
134
165
  opacity: 0;
135
166
  translate: 0 var(--gr-gap);
136
167
  transition:
137
- opacity var(--gr-transition),
138
- translate var(--gr-transition),
139
- overlay var(--gr-transition) allow-discrete,
140
- display var(--gr-transition) allow-discrete;
168
+ opacity var(--gr-modal-transition, var(--gr-transition)),
169
+ translate var(--gr-modal-transition, var(--gr-transition)),
170
+ overlay var(--gr-modal-transition, var(--gr-transition)) allow-discrete,
171
+ display var(--gr-modal-transition, var(--gr-transition)) allow-discrete;
141
172
  }
142
173
 
143
174
  .gr-modal[open] {
@@ -178,4 +209,10 @@
178
209
  .gr-modal::backdrop {
179
210
  transition-duration: 0.01ms;
180
211
  }
212
+
213
+ // Кольцо замедляется, но не останавливается — как .gr-spinner:
214
+ // остановленный индикатор занятости сообщает «зависло».
215
+ .gr-modal[data-gr-state="loading"]::before {
216
+ animation-duration: 2.4s;
217
+ }
181
218
  }
package/scss/_nav.scss CHANGED
@@ -31,10 +31,13 @@ $gr-nav-expand: map.get(bp.$gr-breakpoints, md);
31
31
  // Ряд на <ul>: обёртка <li> не должна ни добавлять полей, ни мешать пункту
32
32
  // растянуться по ширине колонки. :where() держит специфичность нулевой,
33
33
  // чтобы класс на самом <li> оставался сильнее.
34
+ // Отступ пункта — токеном с нулевым умолчанием (Этап 46): правило темы
35
+ // на самом <li> проигрывает этому по слою, и тема оборачивала пункт лишним
36
+ // узлом. То же у радиуса ссылки ниже.
34
37
  .gr-nav > :where(li) {
35
38
  display: flex;
36
39
  margin: 0;
37
- padding: 0;
40
+ padding: var(--gr-nav-item-pad, 0);
38
41
  list-style: none;
39
42
  }
40
43
 
@@ -61,7 +64,7 @@ $gr-nav-expand: map.get(bp.$gr-breakpoints, md);
61
64
  padding-block: 0;
62
65
  padding-inline: calc(var(--gr-control-padding-x) * 0.75);
63
66
  border: 0;
64
- border-radius: var(--gr-radius);
67
+ border-radius: var(--gr-nav-link-radius, var(--gr-radius));
65
68
  background-color: transparent;
66
69
  color: var(--gr-nav-fg);
67
70
  font-family: inherit;
package/scss/_rating.scss CHANGED
@@ -13,6 +13,12 @@
13
13
  // не везёт: подставить свой глиф можно одной строкой на весь сайт, а SVG
14
14
  // в модуле означал бы иконочный набор, которого здесь не будет.
15
15
  //
16
+ // Цвет заливки — семантический токен --gr-color-rating из темы: его же
17
+ // читает всё, что изображает оценку рядом с рядом знаков (Этап 45).
18
+ // --gr-rating-color — переопределение на одном ряду или во всём проекте;
19
+ // в :root оно не объявлено, поэтому без него ряд берёт цвет темы, и
20
+ // вложенная секция с другой темой перекрашивает звёзды вместе с собой.
21
+ //
16
22
  // Число ОБЯЗАНО быть и словом: aria-label вида «4,5 из 5». Пять знаков —
17
23
  // это цвет и форма, то есть ровно то, что не переживает ни печать,
18
24
  // ни программу чтения с экрана; правило «статус словом, а не только
@@ -74,5 +80,5 @@ $_gr-rating-row: var(--gr-rating-symbol) var(--gr-rating-symbol) var(--gr-rating
74
80
  inset-inline-start: 0;
75
81
  inline-size: var(--gr-rating-fill);
76
82
  overflow: hidden;
77
- color: var(--gr-rating-color);
83
+ color: var(--gr-rating-color, var(--gr-color-rating));
78
84
  }
package/scss/_table.scss CHANGED
@@ -8,6 +8,8 @@
8
8
  // у ячеек остались бы двойные линии и зазоры border-spacing.
9
9
 
10
10
  @use 'sass:string';
11
+ @use 'griffincss-core/scss/breakpoints' as bp;
12
+ @use 'griffincss-core/scss/container-mixins' as cq;
11
13
 
12
14
  .gr-table {
13
15
  inline-size: 100%;
@@ -112,12 +114,101 @@
112
114
  }
113
115
  }
114
116
 
117
+ // --- Карточки вместо строк --------------------------------------------------
118
+
119
+ // Ниже порога таблица кабинета — заказы, транзакции, бонусы — становится
120
+ // стопкой карточек: строка — блок с обводкой, ячейки в столбик, подпись
121
+ // колонки — из data-label ячейки (фидбек темы griffin по 0.25.0, п. 5).
122
+ //
123
+ // Шапка спрятана по образцу .gr-sr-only (без объявлений для строчных
124
+ // элементов — у блока-обёртки им нечего делать), а не через display: none:
125
+ // программе чтения с экрана заголовки колонок нужны.
126
+ // Подпись — только у ячейки с data-label: пустой ::before у ячейки без
127
+ // атрибута занимал бы свою долю строки и сдвигал значение. Без атрибута
128
+ // ячейка идёт без подписи — не ломается, но и не подписана.
129
+ //
130
+ // Суффикс называет ПОРОГ: .gr-table-stack-md — карточки ниже 768px ширины
131
+ // окна, .gr-table-stack-cmd — ниже 768px ширины ближайшего предка с .gr-cq;
132
+ // от порога и выше — обычная таблица. Это обратная сторона суффиксов
133
+ // «от брейкпоинта» у остальных адаптивных классов: класс описывает узкое
134
+ // состояние, а широкое — просто таблица. Без суффикса — карточки всегда.
135
+ @mixin gr-table-stack($s) {
136
+ #{$s},
137
+ #{$s} tbody,
138
+ #{$s} tfoot,
139
+ #{$s} tr {
140
+ display: block;
141
+ }
142
+
143
+ #{$s} thead {
144
+ position: absolute;
145
+ inline-size: 1px;
146
+ block-size: 1px;
147
+ overflow: hidden;
148
+ clip-path: inset(50%);
149
+ }
150
+
151
+ #{$s} tr {
152
+ margin-block-end: var(--gr-gap);
153
+ border: var(--gr-border-width) solid var(--gr-color-border);
154
+ border-radius: var(--gr-radius-container, var(--gr-radius));
155
+ }
156
+
157
+ #{$s} tr:last-child {
158
+ margin-block-end: 0;
159
+ }
160
+
161
+ // Ячейка — строка «подпись — значение». gap даёт зазор между ними,
162
+ // подпись занимает свою долю, значение — остальное.
163
+ #{$s} tr > * {
164
+ display: flex;
165
+ gap: var(--gr-gap);
166
+ white-space: normal;
167
+ }
168
+
169
+ #{$s} tr > :last-child {
170
+ border-block-end: 0;
171
+ }
172
+
173
+ // text-align: start — подпись не следует за .gr-text-end ячейки: класс
174
+ // ставят ради чисел, а подпись обязана стоять в начале своей доли.
175
+ // Остальное (шрифт, кегль через em) подпись наследует от ячейки, как
176
+ // любой ::before; класс шрифта для значения ставится на самом значении.
177
+ #{$s} tr > [data-label]::before {
178
+ content: attr(data-label);
179
+ flex: 0 0 40%;
180
+ color: var(--gr-color-text-secondary);
181
+ font-size: 0.875rem;
182
+ font-weight: 600;
183
+ text-align: start;
184
+ }
185
+ }
186
+
187
+ @include gr-table-stack('.gr-table-stack');
188
+
189
+ @each $bp-name, $bp-value in bp.$gr-breakpoints {
190
+ @media (width < $bp-value) {
191
+ @include gr-table-stack('.gr-table-stack-#{$bp-name}');
192
+ }
193
+
194
+ @include cq.gr-container-below($bp-name) {
195
+ @include gr-table-stack('.gr-table-stack-c#{$bp-name}');
196
+ }
197
+ }
198
+
115
199
  // --- Прокрутка и липкая шапка -----------------------------------------------
116
200
 
117
201
  // Широкая таблица не сжимается ниже своего содержимого, поэтому без обёртки
118
202
  // вбок уезжала бы вся страница, а не одна таблица. Прокручиваемая область
119
203
  // объявлена tabindex="0" в разметке — иначе до неё не добраться с клавиатуры.
204
+ //
205
+ // position: relative — обёртка становится содержащим блоком для абсолютно
206
+ // позиционированных потомков. Без него .gr-sr-only внутри кнопки-иконки
207
+ // (кнопка не позиционирована) считался от блока снаружи прокрутки, вставал
208
+ // у правого края широкой таблицы за её пределами и растягивал документ:
209
+ // страница уезжала вбок ровно от того, что обёртка должна была удержать.
120
210
  .gr-table-wrap {
211
+ position: relative;
121
212
  inline-size: 100%;
122
213
  max-block-size: 100%;
123
214
  overflow: auto;
package/scss/_tabs.scss CHANGED
@@ -59,7 +59,10 @@
59
59
  min-block-size: var(--gr-control-height);
60
60
  margin: 0;
61
61
  padding-block: 0;
62
- padding-inline: var(--gr-control-padding-x);
62
+
63
+ // Отступ вкладки — токеном (Этап 46): правило темы на .gr-tab
64
+ // проигрывает слою ui, а ряд вкладок в шапке витрины хочет быть плотнее.
65
+ padding-inline: var(--gr-tabs-tab-pad, var(--gr-control-padding-x));
63
66
  border: 0;
64
67
 
65
68
  // Линия объявлена прозрачной у всех вкладок сразу: появись она только
@@ -25,16 +25,20 @@
25
25
  // остаётся в DOM внутри .griffin, поэтому префикс до него достаёт.
26
26
 
27
27
  @use 'sass:meta';
28
- @layer griffincss.reset, griffincss.core, griffincss.ui, griffincss.utils, griffincss.style;
28
+ @layer griffincss.reset, griffincss.tokens, griffincss.core, griffincss.ui, griffincss.utils, griffincss.style, griffincss.hidden;
29
29
 
30
- // Весь вывод пакета — в слое griffincss.ui.
31
- @layer griffincss.ui {
30
+ // Токены и тема — вне области и в общем слое griffincss.tokens, как
31
+ // в обычной сборке (Этап 46): атрибут data-gr-theme стоит на <html>,
32
+ // то есть заведомо снаружи области .griffin.
33
+ @layer griffincss.tokens {
32
34
  @include meta.load-css('griffincss-core/scss/tokens');
33
-
34
- // Тема — тоже вне области: атрибут data-gr-theme стоит на <html>,
35
- // то есть заведомо снаружи области .griffin. Оба файла, как в обычной
36
- // сборке: правила режима обязаны лежать в одном слое с вариантами ссылки.
37
35
  @include meta.load-css('griffincss-core/scss/theme-tokens');
36
+ }
37
+
38
+ // Остальной вывод пакета — в слое griffincss.ui.
39
+ @layer griffincss.ui {
40
+ // Правила режима со свойствами — в одном слое с вариантами ссылки,
41
+ // как в обычной сборке.
38
42
  @include meta.load-css('griffincss-core/scss/theme-rules');
39
43
 
40
44
  // @keyframes — вне области: имя анимации ищется глобально, префикс
@@ -71,3 +75,9 @@
71
75
  @include meta.load-css('tooltip');
72
76
  }
73
77
  }
78
+
79
+ // [hidden] — вне области: атрибут обязан работать на любом элементе страницы,
80
+ // а не только внутри .griffin.
81
+ @layer griffincss.hidden {
82
+ @include meta.load-css('griffincss-core/scss/hidden');
83
+ }
@@ -17,23 +17,25 @@
17
17
  // margin-bottom.
18
18
 
19
19
  @use 'sass:meta';
20
- @layer griffincss.reset, griffincss.core, griffincss.ui, griffincss.utils, griffincss.style;
20
+ @layer griffincss.reset, griffincss.tokens, griffincss.core, griffincss.ui, griffincss.utils, griffincss.style, griffincss.hidden;
21
21
 
22
- // Весь вывод пакета — в слое griffincss.ui.
23
- @layer griffincss.ui {
22
+ // Токены и тема подключаются и здесь, а не только в ядре: собранный файл
23
+ // самодостаточен. Но эмитятся они в общий слой griffincss.tokens, а не в ui
24
+ // (Этап 46): копия та же, что в ядре и в утилитах, и все копии стоят в одном
25
+ // слое — подслой темы потребителя сразу после tokens перебивает их при любой
26
+ // специфичности. Раньше копия в ui была старше и подслоя, и ядра.
27
+ @layer griffincss.tokens {
24
28
  @include meta.load-css('griffincss-core/scss/tokens');
29
+ @include meta.load-css('griffincss-core/scss/theme-tokens');
30
+ }
25
31
 
26
- // Тема подключается и здесь, а не только в ядре: слой griffincss.ui старше
27
- // griffincss.core, поэтому переопределения темы, приехавшие только
28
- // с griffincss-core.css, проиграли бы базовому :root из этой сборки —
29
- // независимо от специфичности селектора.
30
- //
31
- // Правила режима со свойствами тоже здесь — в отличие от utils. Правило
32
+ // Остальной вывод пакета — в слое griffincss.ui.
33
+ @layer griffincss.ui {
34
+ // Правила режима со свойствами — здесь, в отличие от utils. Правило
32
35
  // [data-gr-a11y="low-vision"] a[href] (0,1,1) обязано побеждать варианты
33
36
  // ссылки .gr-link-quiet и .gr-link-inherit (0,1,0), а для этого лежать
34
37
  // с ними в одном слое: из ядра оно бы им проиграло. Компонент с заливкой
35
38
  // защищается от него сам — a.gr-btn в _button.scss стоит позже по порядку.
36
- @include meta.load-css('griffincss-core/scss/theme-tokens');
37
39
  @include meta.load-css('griffincss-core/scss/theme-rules');
38
40
 
39
41
  // @keyframes живут отдельным файлом ради scoped-сборки — см. _keyframes.scss.
@@ -71,3 +73,7 @@
71
73
  // модуля нет намеренно, см. его шапку.
72
74
  @include meta.load-css('style-rules');
73
75
  }
76
+
77
+ @layer griffincss.hidden {
78
+ @include meta.load-css('griffincss-core/scss/hidden');
79
+ }
@@ -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
- @each $n in (1, 2, 3, 4) {
51
- .gr-track-#{$n}-#{$bp-name} > * {
52
- flex-basis: calc((100% - var(--gr-gap) * #{$n - 1}) / #{$n});
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
  }
@@ -1,12 +1,10 @@
1
1
  // GriffinJS — сумма с разрядами (Этап 43b)
2
- // Своих стилей у поля нет: на экране обычный .gr-input с текстом. Единственное
3
- // правило — про спутник. Скрытый input с числом встаёт сразу за полем
4
- // (у суммы — всегда, у даты — на указателе мыши), и в .gr-input-group
5
- // с аддоном после поля он был бы последним ребёнком: поле теряло бы
6
- // скругление края. Скругление возвращается тому, за кем стоит спутник.
7
- // Без :has() остаётся прямой угол у поля — деградация видимая, но мягкая.
8
-
9
- .gr-input-group > :has(+ [hidden]:last-child) {
10
- border-start-end-radius: var(--gr-radius);
11
- border-end-end-radius: var(--gr-radius);
12
- }
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,7 +14,7 @@
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 {