@soxa-ui/theme-base 0.0.0-stage → 0.3.0-beta.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/LICENSE +21 -0
- package/README.md +399 -2
- package/dist/setup/index.d.ts +31 -0
- package/dist/setup/index.js +4 -0
- package/dist/setup/plugins/index.d.ts +3 -0
- package/dist/setup/plugins/table-head.plugin.d.ts +44 -0
- package/dist/setup/plugins/table-head.plugin.js +86 -0
- package/dist/setup/plugins/tabs-view.plugin.d.ts +32 -0
- package/dist/setup/plugins/tabs-view.plugin.js +100 -0
- package/dist/setup/plugins/toasts-front.plugin.d.ts +63 -0
- package/dist/setup/plugins/toasts-front.plugin.js +131 -0
- package/dist/setup/plugins/transitions.d.ts +43 -0
- package/dist/setup/plugins/transitions.js +75 -0
- package/package.json +55 -4
- package/src/anchored.css +75 -0
- package/src/caption.css +71 -0
- package/src/components/accordion.css +96 -0
- package/src/components/button.css +139 -0
- package/src/components/calendar.css +594 -0
- package/src/components/check-box.css +121 -0
- package/src/components/date-input.css +224 -0
- package/src/components/date-picker.css +208 -0
- package/src/components/dialog.css +95 -0
- package/src/components/drag-and-drop.css +18 -0
- package/src/components/drawer.css +171 -0
- package/src/components/field.css +440 -0
- package/src/components/icon.css +61 -0
- package/src/components/input.css +189 -0
- package/src/components/label.css +160 -0
- package/src/components/list-box.css +165 -0
- package/src/components/menu.css +224 -0
- package/src/components/number-input.css +183 -0
- package/src/components/pagination.css +164 -0
- package/src/components/popover.css +164 -0
- package/src/components/progress-linear.css +88 -0
- package/src/components/progress-spinner.css +127 -0
- package/src/components/radio-group.css +130 -0
- package/src/components/rating.css +141 -0
- package/src/components/scroller.css +136 -0
- package/src/components/select.css +233 -0
- package/src/components/skeleton.css +33 -0
- package/src/components/slider.css +482 -0
- package/src/components/switch.css +142 -0
- package/src/components/table.css +1513 -0
- package/src/components/tabs.css +166 -0
- package/src/components/tags.css +371 -0
- package/src/components/textarea.css +179 -0
- package/src/components/toasts.css +447 -0
- package/src/components/tooltip.css +59 -0
- package/src/direction.css +30 -0
- package/src/disabled.css +26 -0
- package/src/group.css +90 -0
- package/src/index.css +97 -0
- package/src/modal.css +183 -0
- package/src/place.css +30 -0
- package/src/required.css +40 -0
- package/src/reset.css +172 -0
- package/src/swipe.css +71 -0
- package/src/target.css +46 -0
- package/theme-contract.json +3373 -0
- package/theme-variables.json +336 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/* ─── Input ───────────────────────────────────────────────────
|
|
2
|
+
* Поле ввода в строку — коробка поля семьи Input: слот `leading` у начала
|
|
3
|
+
* поля, ввод, слот `trailing` у конца. Ввод растёт на всё место между
|
|
4
|
+
* слотами, слоты стоят у краёв, а стороны логические: в RTL `leading` стоит
|
|
5
|
+
* справа, и поле слота у него справа.
|
|
6
|
+
*
|
|
7
|
+
* .so-input корень — коробка поля
|
|
8
|
+
* .so-input__leading слот у начала
|
|
9
|
+
* input ввод, без класса
|
|
10
|
+
* .so-input__trailing слот у конца
|
|
11
|
+
* .so-input__clear кнопка очистки, первой в слоте — Button
|
|
12
|
+
*
|
|
13
|
+
* Геометрия поля — значениями, а не процентами: высота размера
|
|
14
|
+
* (`--so-input-height`, правила размера), рамка, просвет слота от рамки и
|
|
15
|
+
* строка слота — высота размера без рамки и двух просветов. В строку встают
|
|
16
|
+
* слоты и теги в них (`components/tags.css`) и кнопка очистки — квадрат в
|
|
17
|
+
* строку слота. Процентом от поля нельзя: поле Select с тегами растёт по
|
|
18
|
+
* строкам тегов, а процент от поля с авто-высотой не разрешается — тег и
|
|
19
|
+
* очистка взяли бы высоту размера целиком.
|
|
20
|
+
*
|
|
21
|
+
* Шкала поля — переменные корня документа по размеру (`sm`…`2xl`): высота,
|
|
22
|
+
* кегль и интерлиньяж текста, отступ текста от рамки. Шкала — не только
|
|
23
|
+
* Input: по ней стоят поля той же семьи — Textarea (`textarea.css`) и
|
|
24
|
+
* NumberInput (`number-input.css`), — и в одной форме они вровень по высоте и
|
|
25
|
+
* по началу текста. Кегля у `normal` нет: поле берёт кегль места.
|
|
26
|
+
* Интерлиньяж шкалы — строка многострочного поля: у поля в строку она во всю
|
|
27
|
+
* высоту поля. Умолчания — числа темы oren; тема задаёт свои на корне
|
|
28
|
+
* документа.
|
|
29
|
+
*
|
|
30
|
+
* Рамку рисует тема, и её толщину база не объявляет: без темы рамки нет.
|
|
31
|
+
* Толщину тема задаёт на блоке (`--so-input-border`, контракт), и строка
|
|
32
|
+
* слота считается без неё.
|
|
33
|
+
*
|
|
34
|
+
* Облик — рамка, поверхность, скругление, кольцо фокуса, подсказка пустого
|
|
35
|
+
* поля, вид кнопки очистки, цвет точки обязательного поля, варианты и
|
|
36
|
+
* ошибка — рисует тема. */
|
|
37
|
+
|
|
38
|
+
:root {
|
|
39
|
+
--so-input-height-sm: 1.75rem;
|
|
40
|
+
--so-input-height-normal: 2rem;
|
|
41
|
+
--so-input-height-lg: 2.25rem;
|
|
42
|
+
--so-input-height-xl: 2.5rem;
|
|
43
|
+
--so-input-height-2xl: 2.75rem;
|
|
44
|
+
|
|
45
|
+
/* The text of a field counts from the font of the place, which `normal`
|
|
46
|
+
* takes as is: `sm` is 2px smaller, `lg`, `xl` and `2xl` 2px, 4px and 8px
|
|
47
|
+
* larger — 14 / 16 / 18 / 20 / 24px at a 16px place, 12 / 14 / 16 / 18 /
|
|
48
|
+
* 22px at 14px. `em` resolves where the variable is read, against the
|
|
49
|
+
* parent, that is the place the part stands in. */
|
|
50
|
+
--so-input-font-size-sm: calc(1em - 0.125rem);
|
|
51
|
+
--so-input-line-height-sm: calc(1.25 / 0.875);
|
|
52
|
+
--so-input-line-height-normal: 1.5;
|
|
53
|
+
--so-input-font-size-lg: calc(1em + 0.125rem);
|
|
54
|
+
--so-input-line-height-lg: calc(1.75 / 1.125);
|
|
55
|
+
--so-input-font-size-xl: calc(1em + 0.25rem);
|
|
56
|
+
--so-input-line-height-xl: calc(1.75 / 1.25);
|
|
57
|
+
--so-input-font-size-2xl: calc(1em + 0.5rem);
|
|
58
|
+
--so-input-line-height-2xl: calc(2 / 1.5);
|
|
59
|
+
|
|
60
|
+
--so-input-indent-sm: 0.375rem;
|
|
61
|
+
--so-input-indent-normal: 0.5rem;
|
|
62
|
+
--so-input-indent-lg: 0.625rem;
|
|
63
|
+
--so-input-indent-xl: 0.75rem;
|
|
64
|
+
--so-input-indent-2xl: 0.875rem;
|
|
65
|
+
|
|
66
|
+
--so-input-slot-gap: 3px;
|
|
67
|
+
--so-input-slot-padding: 0.25rem;
|
|
68
|
+
--so-input-slot-spacing: 0.25rem;
|
|
69
|
+
|
|
70
|
+
--so-input-min-width: 10rem;
|
|
71
|
+
--so-input-text-min-width: 4rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Строка по центру. `inline-flex` сжимается по содержимому, и минимум
|
|
75
|
+
* ширины держит поле в узком контейнере: без него оно уходило в полосу.
|
|
76
|
+
* `relative` — опора точки обязательного поля (`required.css`). */
|
|
77
|
+
.so-input {
|
|
78
|
+
--so-input-slot-row: calc(
|
|
79
|
+
var(--so-input-height) - 2 * var(--so-input-border, 0px) - 2 * var(--so-input-slot-gap)
|
|
80
|
+
);
|
|
81
|
+
position: relative;
|
|
82
|
+
display: inline-flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
height: var(--so-input-height);
|
|
85
|
+
min-width: var(--so-input-min-width);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Выключенное поле гаснет коробкой целиком — с вводом, иконками слотов и
|
|
89
|
+
* кнопками (`disabled.css`). Корень — `div`, и `:disabled` у него не
|
|
90
|
+
* срабатывает: выключено вложенное поле, и выключенность читается с него. Его
|
|
91
|
+
* тег фиксирован, поэтому псевдокласс на нём надёжен, а нативный `disabled`
|
|
92
|
+
* поля ставит и выключенный предок `<fieldset>`. Поле — прямой ребёнок корня:
|
|
93
|
+
* ввод в слоте (разметка потребителя) выключенность поля не решает. Нажатия
|
|
94
|
+
* коробка не гасит: в слотах — разметка потребителя. */
|
|
95
|
+
.so-input:has(> input:disabled) {
|
|
96
|
+
opacity: var(--so-disabled-opacity);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.so-input:has(> input:disabled) > * {
|
|
100
|
+
--so-disabled-opacity: 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Ввод — всё место между слотами, но не уже своего минимума: в узком поле
|
|
104
|
+
* уступают слоты. Строка ввода — во всю высоту поля, и текст стоит по центру
|
|
105
|
+
* коробки. */
|
|
106
|
+
.so-input input {
|
|
107
|
+
width: 100%;
|
|
108
|
+
min-width: var(--so-input-text-min-width);
|
|
109
|
+
line-height: var(--so-input-height);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.so-input input:disabled {
|
|
113
|
+
cursor: default;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Высота слота — строка поля, а не процент от него: у поля Select с тегами
|
|
117
|
+
* высота авто. */
|
|
118
|
+
.so-input__leading,
|
|
119
|
+
.so-input__trailing {
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
justify-content: center;
|
|
123
|
+
gap: var(--so-input-slot-spacing);
|
|
124
|
+
height: var(--so-input-slot-row);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.so-input__leading {
|
|
128
|
+
padding-inline-start: var(--so-input-slot-padding);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.so-input__trailing {
|
|
132
|
+
padding-inline-end: var(--so-input-slot-padding);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Кнопка очистки — первой в слоте у конца, квадрат со стороной в строку
|
|
136
|
+
* слота: на том же просвете от рамки, что и содержимое слотов, и на любом
|
|
137
|
+
* размере не выше строки. Поля шкалы Button у неё нет. Не сжимается: в узком
|
|
138
|
+
* поле уступает ввод, а не кнопка. Цель на мелком размере ей добирает зона
|
|
139
|
+
* нажатия (`target.css`). */
|
|
140
|
+
.so-input__clear {
|
|
141
|
+
flex-shrink: 0;
|
|
142
|
+
padding-inline: 0;
|
|
143
|
+
inline-size: var(--so-input-slot-row);
|
|
144
|
+
block-size: var(--so-input-slot-row);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.so-input--size-sm {
|
|
148
|
+
--so-input-height: var(--so-input-height-sm);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.so-input--size-sm input {
|
|
152
|
+
font-size: var(--so-input-font-size-sm);
|
|
153
|
+
text-indent: var(--so-input-indent-sm);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.so-input--size-normal {
|
|
157
|
+
--so-input-height: var(--so-input-height-normal);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.so-input--size-normal input {
|
|
161
|
+
text-indent: var(--so-input-indent-normal);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.so-input--size-lg {
|
|
165
|
+
--so-input-height: var(--so-input-height-lg);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.so-input--size-lg input {
|
|
169
|
+
font-size: var(--so-input-font-size-lg);
|
|
170
|
+
text-indent: var(--so-input-indent-lg);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.so-input--size-xl {
|
|
174
|
+
--so-input-height: var(--so-input-height-xl);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.so-input--size-xl input {
|
|
178
|
+
font-size: var(--so-input-font-size-xl);
|
|
179
|
+
text-indent: var(--so-input-indent-xl);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.so-input--size-2xl {
|
|
183
|
+
--so-input-height: var(--so-input-height-2xl);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.so-input--size-2xl input {
|
|
187
|
+
font-size: var(--so-input-font-size-2xl);
|
|
188
|
+
text-indent: var(--so-input-indent-2xl);
|
|
189
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/* ─── Label ───────────────────────────────────────────────────
|
|
2
|
+
* Подпись контрола: текст рядом с CheckBox, Switch или радио. Корень —
|
|
3
|
+
* `label`, связь с контролом — вложением: клик по тексту доходит до поля
|
|
4
|
+
* контрола, и контрол переключается.
|
|
5
|
+
*
|
|
6
|
+
* Корень — `inline-flex`, как у радио со своим текстом: ширина по
|
|
7
|
+
* содержимому, и клик мимо текста контрол не переключает. Порядок в DOM один
|
|
8
|
+
* на все стороны — контрол, потом текст, — сторону ставит модификатор
|
|
9
|
+
* `--position-*`. Сторона по умолчанию, `end`, — это сам блок, правила у
|
|
10
|
+
* `--position-end` нет. Ни одного `left`/`right`: `start` и `end`
|
|
11
|
+
* логические, и в RTL текст сам встаёт с другой стороны — `dir` приходит на
|
|
12
|
+
* корень.
|
|
13
|
+
*
|
|
14
|
+
* Размер — кегль текста и зазор до контрола, по шкале подписи
|
|
15
|
+
* (`caption.css`). Кегль — у текста и распорки, а не у корня: иконки
|
|
16
|
+
* контрола меряются в `em` (`icon.css`), и кегль подписи раздул бы галку
|
|
17
|
+
* CheckBox и иконки в ручке Switch. Зазоры — переменными блока: сбоку
|
|
18
|
+
* `--so-label-gap`, в столбце `--so-label-stack`. Без модификатора размера —
|
|
19
|
+
* `normal`.
|
|
20
|
+
*
|
|
21
|
+
* Облик — цвет текста и вариант — рисует тема. */
|
|
22
|
+
|
|
23
|
+
/* `baseline` ровняет распорку контрола (`__control::before`) с первой
|
|
24
|
+
* строкой текста. Заодно первая строка становится базовой линией всей
|
|
25
|
+
* подписи: в строке формы рядом с чужим текстом подпись встаёт с ним на одну
|
|
26
|
+
* линию. */
|
|
27
|
+
.so-label {
|
|
28
|
+
--so-label-gap: var(--so-caption-gap-normal);
|
|
29
|
+
--so-label-stack: var(--so-caption-stack-normal);
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
align-items: baseline;
|
|
32
|
+
gap: var(--so-label-gap);
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
-webkit-user-select: none;
|
|
35
|
+
user-select: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.so-label--size-sm {
|
|
39
|
+
--so-label-gap: var(--so-caption-gap-sm);
|
|
40
|
+
--so-label-stack: var(--so-caption-stack-sm);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.so-label--size-sm > .so-label__text,
|
|
44
|
+
.so-label--size-sm > .so-label__control::before {
|
|
45
|
+
font-size: var(--so-caption-font-size-sm);
|
|
46
|
+
line-height: var(--so-caption-line-height-sm);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.so-label--size-normal {
|
|
50
|
+
--so-label-gap: var(--so-caption-gap-normal);
|
|
51
|
+
--so-label-stack: var(--so-caption-stack-normal);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.so-label--size-lg {
|
|
55
|
+
--so-label-gap: var(--so-caption-gap-lg);
|
|
56
|
+
--so-label-stack: var(--so-caption-stack-lg);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.so-label--size-lg > .so-label__text,
|
|
60
|
+
.so-label--size-lg > .so-label__control::before {
|
|
61
|
+
font-size: var(--so-caption-font-size-lg);
|
|
62
|
+
line-height: var(--so-caption-line-height-lg);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.so-label--size-xl {
|
|
66
|
+
--so-label-gap: var(--so-caption-gap-xl);
|
|
67
|
+
--so-label-stack: var(--so-caption-stack-xl);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.so-label--size-xl > .so-label__text,
|
|
71
|
+
.so-label--size-xl > .so-label__control::before {
|
|
72
|
+
font-size: var(--so-caption-font-size-xl);
|
|
73
|
+
line-height: var(--so-caption-line-height-xl);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.so-label--size-2xl {
|
|
77
|
+
--so-label-gap: var(--so-caption-gap-2xl);
|
|
78
|
+
--so-label-stack: var(--so-caption-stack-2xl);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.so-label--size-2xl > .so-label__text,
|
|
82
|
+
.so-label--size-2xl > .so-label__control::before {
|
|
83
|
+
font-size: var(--so-caption-font-size-2xl);
|
|
84
|
+
line-height: var(--so-caption-line-height-2xl);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Обёртка контрола. Контрол не сжимается, какой бы длинной ни была
|
|
88
|
+
* подпись. */
|
|
89
|
+
.so-label__control {
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-shrink: 0;
|
|
92
|
+
align-items: center;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Распорка — строка нулевой ширины кеглем текста. Обёртка с ней не ниже
|
|
96
|
+
* строки, контрол стоит по её центру, а базовая линия обёртки — базовая
|
|
97
|
+
* линия распорки. Корень ровняет эту линию с первой строкой текста, и
|
|
98
|
+
* контрол встаёт по центру первой строки. Контрол выше строки — опускается
|
|
99
|
+
* уже текст, к середине контрола. Высоту контрола знать не нужно, поэтому
|
|
100
|
+
* размеры подписи и контрола вправе не совпасть, а в обёртке может стоять
|
|
101
|
+
* любой контрол. `hidden` убирает распорку из доступного имени. */
|
|
102
|
+
.so-label__control::before {
|
|
103
|
+
content: '\200b';
|
|
104
|
+
visibility: hidden;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Пустая обёртка текста места не занимает, иначе зазор остался бы рядом с
|
|
108
|
+
* одиноким контролом. */
|
|
109
|
+
.so-label__text:empty {
|
|
110
|
+
display: none;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.so-label--position-start > .so-label__text {
|
|
114
|
+
order: -1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Текст над контролом и под ним — столбец, прижатый к началу строки. Зазор —
|
|
118
|
+
* `stack` шкалы подписи. Распорки нет: ровнять по строке нечего, а обёртка
|
|
119
|
+
* высотой в строку добавила бы зазора вокруг контрола, который ниже строки. */
|
|
120
|
+
.so-label--position-top,
|
|
121
|
+
.so-label--position-bottom {
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
align-items: flex-start;
|
|
124
|
+
gap: var(--so-label-stack);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.so-label--position-top > .so-label__control::before,
|
|
128
|
+
.so-label--position-bottom > .so-label__control::before {
|
|
129
|
+
content: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.so-label--position-top > .so-label__text {
|
|
133
|
+
order: -1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Выключенный контрол. Своего `disabled` у подписи нет — состояние у
|
|
137
|
+
* контрола, и подпись читает его по вложенному полю: тег поля у CheckBox,
|
|
138
|
+
* Switch и радио фиксирован. Гаснет только текст (`disabled.css`), контрол
|
|
139
|
+
* гасит себя сам. */
|
|
140
|
+
.so-label:has(> .so-label__control input:disabled) {
|
|
141
|
+
cursor: default;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.so-label:has(> .so-label__control input:disabled) > .so-label__text {
|
|
145
|
+
opacity: var(--so-disabled-opacity);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.so-label:has(> .so-label__control input:disabled) > .so-label__text > * {
|
|
149
|
+
--so-disabled-opacity: 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
* A readonly control does not react to label clicks, so its label must not
|
|
154
|
+
* promise an action through a pointer cursor. Only the cursor is reset; the theme
|
|
155
|
+
* chooses text appearance. Read data-readonly on the direct control root without
|
|
156
|
+
* depending on its block name: Label may wrap CheckBox, Switch or a custom control.
|
|
157
|
+
*/
|
|
158
|
+
.so-label:has(> .so-label__control > [data-readonly='true']) {
|
|
159
|
+
cursor: default;
|
|
160
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/* ─── ListBox ─────────────────────────────────────────────────
|
|
2
|
+
* Список выбора: шапка, узел списка и подвал столбцом.
|
|
3
|
+
*
|
|
4
|
+
* .so-list-box корень — рамка вокруг шапки, списка и
|
|
5
|
+
* подвала
|
|
6
|
+
* .so-list-box__list узел списка — элементы столбцом
|
|
7
|
+
* .so-list-box-item элемент — строка (Button)
|
|
8
|
+
* .so-list-box-item__indicator отметка выбранного
|
|
9
|
+
* .so-list-box-group группа — подпись и её элементы
|
|
10
|
+
* .so-list-box-group__label подпись группы (`group.css`)
|
|
11
|
+
* .so-list-box__separator разделитель секций групп (`group.css`)
|
|
12
|
+
* .so-list-box__filler распорка окна `Virtual`
|
|
13
|
+
*
|
|
14
|
+
* Состояние (`data-disabled`, `data-content-fit`) ядро отдаёт на корень, а
|
|
15
|
+
* роль, имя и остановку Tab — узлу списка: в `listbox` ARIA пускает только
|
|
16
|
+
* опции и группы, и шапка с подвалом стоят рядом с ним.
|
|
17
|
+
*
|
|
18
|
+
* Части корня стоят с тем же зазором, что элементы в списке, а поле корня
|
|
19
|
+
* держит их от его края. Ширина — между минимумом и потолком: короткий список
|
|
20
|
+
* не складывается в узкий столбец, а длинный текст строки режется
|
|
21
|
+
* многоточием. `contentFit` — одно свойство на два уровня: со списка читается
|
|
22
|
+
* только `expand` (ширина контейнера — решение общее для всех строк), а
|
|
23
|
+
* перенос строки — с самого элемента. Парного правила переноса на корне нет
|
|
24
|
+
* намеренно: оно перекрывало бы элемент, отказавшийся от переноса.
|
|
25
|
+
*
|
|
26
|
+
* Прокручивается узел списка, а не корень: предел строк (`maxRows`)
|
|
27
|
+
* `TListHeightPlugin` ставит узлу списка — и элементам групп — инлайновыми
|
|
28
|
+
* `max-height` и прокруткой, и шапка с подвалом остаются на месте. Своих полей
|
|
29
|
+
* и рамки у узла нет, поэтому предел — ровно строки с зазорами. Высоту корня
|
|
30
|
+
* может ограничить и приложение: тогда сжимается и прокручивается тот же узел.
|
|
31
|
+
*
|
|
32
|
+
* Распорка окна (обёртка `Virtual`) — пустая полоса на месте пропущенных
|
|
33
|
+
* элементов, высотой в них (`--so-filler-height`, её пишет библиотека): без
|
|
34
|
+
* полей и рамок, под `aria-hidden`. Не сжимается: пустой флекс-элемент в
|
|
35
|
+
* списке под `max-height` сжался бы до нуля, и окно съехало бы к началу. Шаг
|
|
36
|
+
* окна — элемент и зазор списка, а высота распорки — пропущенные шаги
|
|
37
|
+
* целиком. Зазор за распоркой список добавит ещё раз, поэтому нижнее поле у
|
|
38
|
+
* неё — минус тот же зазор: иначе каждая распорка сдвигала бы элементы за
|
|
39
|
+
* собой.
|
|
40
|
+
*
|
|
41
|
+
* Группа — столбец с тем же зазором, что у узла списка, и не сжимается: в
|
|
42
|
+
* списке под пределом строк она прокручивается вместе с ним. Флекс у группы
|
|
43
|
+
* свой, и место элемента в составе (`order`) действует внутри неё. Подпись —
|
|
44
|
+
* с полями сверху и снизу поровну: вместе с зазором списка над группой и
|
|
45
|
+
* зазором группы под подписью над её текстом и под ним свободно одинаково.
|
|
46
|
+
*
|
|
47
|
+
* Строка элемента — прямой ребёнок, и её правила пишутся ребёнком, а не
|
|
48
|
+
* потомком: кнопка, которую потребитель положил в слот строки, тоже
|
|
49
|
+
* `.so-button`. Элемент списка — не кнопка действия: курсор обычный.
|
|
50
|
+
* Содержимое строки — у начала строки, как у заголовка Accordion. Отметка
|
|
51
|
+
* выбранного — обёртка, которая стоит и у невыбранных и держит ширину:
|
|
52
|
+
* размер — ей, а не иконке, и подменённая слотом иконка строку не сдвигает.
|
|
53
|
+
*
|
|
54
|
+
* Зазор, поле, минимум и потолок ширины — переменные корня документа: по ним
|
|
55
|
+
* же стоят список Select и панель Menu. Умолчания — числа темы oren; тема
|
|
56
|
+
* задаёт свои на корне документа.
|
|
57
|
+
*
|
|
58
|
+
* Облик — рамка корня и её наведение, кольцо фокуса узла списка, вид строк и
|
|
59
|
+
* отметки — рисует тема. */
|
|
60
|
+
|
|
61
|
+
:root {
|
|
62
|
+
--so-list-box-gap: 0.25rem;
|
|
63
|
+
--so-list-box-padding: 0.25rem;
|
|
64
|
+
--so-list-box-min-width: 10rem;
|
|
65
|
+
--so-list-box-max-width: 20rem;
|
|
66
|
+
--so-list-box-group-label-padding: 0.25rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.so-list-box {
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
gap: var(--so-list-box-gap);
|
|
73
|
+
min-width: var(--so-list-box-min-width);
|
|
74
|
+
max-width: var(--so-list-box-max-width);
|
|
75
|
+
padding: var(--so-list-box-padding);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Выключенный список гаснет корнем целиком — рамкой, шапкой, строками и
|
|
79
|
+
* подвалом (`disabled.css`). Строки выключены вместе с ним и под погасшим
|
|
80
|
+
* корнем второй раз не гаснут. */
|
|
81
|
+
.so-list-box[data-disabled='true'] {
|
|
82
|
+
opacity: var(--so-disabled-opacity);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.so-list-box[data-disabled='true'] > * {
|
|
86
|
+
--so-disabled-opacity: 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.so-list-box[data-content-fit='expand'] {
|
|
90
|
+
width: max-content;
|
|
91
|
+
min-width: 0;
|
|
92
|
+
max-width: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.so-list-box__list {
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
gap: var(--so-list-box-gap);
|
|
99
|
+
min-height: 0;
|
|
100
|
+
overflow-y: auto;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.so-list-box__filler {
|
|
104
|
+
flex-shrink: 0;
|
|
105
|
+
margin-block-end: calc(-1 * var(--so-list-box-gap));
|
|
106
|
+
block-size: var(--so-filler-height, 0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.so-list-box-group {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-shrink: 0;
|
|
112
|
+
flex-direction: column;
|
|
113
|
+
gap: var(--so-list-box-gap);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.so-list-box-group__label {
|
|
117
|
+
padding-block: var(--so-list-box-group-label-padding);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Секции без разделителя — `groupSeparators` выключен, у ListBox это
|
|
121
|
+
* умолчание: группа за группой или за элементом без группы и элемент без
|
|
122
|
+
* группы за группой. Сверх линии разделитель занимал зазор списка, и секция
|
|
123
|
+
* отступает на него и на линию — встаёт там же, где стояла бы за
|
|
124
|
+
* разделителем (`group.css`). */
|
|
125
|
+
:is(.so-list-box-group, .so-list-box-item) + .so-list-box-group,
|
|
126
|
+
.so-list-box-group + .so-list-box-item {
|
|
127
|
+
margin-block-start: calc(var(--so-list-box-gap) + var(--so-group-separator-width, 0px));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.so-list-box-item {
|
|
131
|
+
min-height: fit-content;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.so-list-box-item > .so-button {
|
|
135
|
+
cursor: default;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Область целиком, а не её текст: отступы детей — нулевой специфичности, и
|
|
139
|
+
* собственный класс содержимого с отступом их перекрывает (`button.css`). */
|
|
140
|
+
.so-list-box-item > .so-button > .so-button__text {
|
|
141
|
+
min-width: 0;
|
|
142
|
+
text-align: start;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
:where(.so-list-box-item > .so-button > .so-button__text > *) {
|
|
146
|
+
margin-inline: 0 auto;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.so-list-box-item__indicator {
|
|
150
|
+
display: inline-flex;
|
|
151
|
+
flex-shrink: 0;
|
|
152
|
+
align-items: center;
|
|
153
|
+
justify-content: center;
|
|
154
|
+
width: 1em;
|
|
155
|
+
height: 1em;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.so-list-box-item[data-content-fit='wrap'] > .so-button {
|
|
159
|
+
min-height: fit-content;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.so-list-box-item[data-content-fit='wrap'] > .so-button > .so-button__text {
|
|
163
|
+
white-space: normal;
|
|
164
|
+
overflow: visible;
|
|
165
|
+
}
|