@rt-tools/ui-kit-v2 0.41.0 → 0.42.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rt-tools/ui-kit-v2",
3
- "version": "0.41.0",
3
+ "version": "0.42.0",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Yauheni Krumin",
6
6
  "type": "module",
@@ -31,8 +31,10 @@
31
31
 
32
32
  **Календарь ничего не считает.** Раскладку месяца готовит потребитель: сколько пустых клеток в
33
33
  начале (`leadingBlanks`), какие дни заняты (`state`), что писать под числом (`sublabel`), какие
34
- дни недоступны (`disabled`). Компонент только показывает готовое — поэтому он одинаково годится
35
- и для выбора дат, и для витрины занятости.
34
+ дни недоступны (`disabled`). Вместо пустых клеток месяц может нести дни соседних месяцев
35
+ (`leadingDays` и `trailingDays`). Они бледные и не нажимаются, а с `crossed` ещё и зачёркнуты.
36
+ Компонент только показывает готовое — поэтому он одинаково годится и для выбора дат, и для витрины
37
+ занятости.
36
38
 
37
39
  **Это не поле ввода.** Значение он не хранит и в форму не пишет; для ввода даты есть
38
40
  [`rt-date-picker`](../date-picker/CONTEXT.md), для периода — [`rt-date-range`](../date-range/CONTEXT.md).
@@ -140,6 +140,11 @@
140
140
  box-shadow: var(--rt-calendar-today-ring);
141
141
  }
142
142
 
143
+ /* Число сегодняшнего дня — цвета действия и полужирное, пока день не залит выбором. */
144
+ &[data-today]:not([data-state='start'], [data-state='end'], [data-state='chosen'], [data-state='in-range']) {
145
+ color: var(--rt-color-action-primary);
146
+ }
147
+
143
148
  &:focus-visible {
144
149
  outline: var(--rt-focus-ring-width) solid var(--rt-focus-ring-color);
145
150
  outline-offset: var(--rt-focus-ring-offset);
@@ -175,6 +180,27 @@
175
180
  font-weight: var(--rt-font-weight-medium);
176
181
  }
177
182
 
183
+ &__day[data-today] &__day-num {
184
+ font-weight: var(--rt-font-weight-semibold);
185
+ }
186
+
187
+ /* День соседнего месяца: стоит в сетке как день, бледный, не нажимается. За границей
188
+ выбора ещё и зачёркнут, как занятый день. */
189
+ &__outside {
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ color: var(--rt-color-text-muted);
194
+ font-size: var(--rt-text-sm);
195
+ font-weight: var(--rt-font-weight-medium);
196
+ min-block-size: var(--rt-calendar-day-size);
197
+ opacity: 0.45;
198
+
199
+ &[data-crossed] {
200
+ text-decoration: line-through;
201
+ }
202
+ }
203
+
178
204
  &__day-sublabel {
179
205
  font-size: var(--rt-text-2xs);
180
206
  opacity: 0.75;
@@ -185,8 +211,9 @@
185
211
  grid-template-columns: 1fr;
186
212
  }
187
213
 
188
- /* на мобиле показываем один месяц — навигация листает по одному */
189
- &__month:last-child {
214
+ /* на мобиле показываем один месяц — навигация листает по одному. Прячется только второй
215
+ из двух: единственный месяц тоже последний, и без `:not(:first-child)` пропадала сетка */
216
+ &__month:last-child:not(:first-child) {
190
217
  display: none;
191
218
  }
192
219
  }
@@ -24,6 +24,11 @@
24
24
  или «Применить». На широком экране панель стоит в поповере, на узком — в нижней шторке. Признак
25
25
  узкого экрана даёт `BreakpointsService.narrow`. Договорённость — `docs/specs/ui-kit-v2/date-panel/`.
26
26
 
27
+ **Панель выдаётся и отдельно.** `RtDatePanelComponent` идёт из этой же точки входа для выбора без
28
+ текстового поля. Поповер или шторку держит потребитель: он создаёт панель заново на каждое открытие,
29
+ закрывает её по `closed`, а значение получает из `picked`. Своей поверхности у панели нет, в поповере
30
+ фон, рамку и тень даёт потребитель. Вход `sheet` включает вид для шторки.
31
+
27
32
  **Текст в поле — дата в порядке языка интерфейса.** Под `ru` это `01.08.2026`, под `en-US` —
28
33
  «08/01/2026», время всегда `HH:mm`. Язык берётся из `RT_KIT_LOCALE`: приложение, меняющее язык на
29
34
  ходу, меняет и порядок. Подсказка в пустом поле — та же форма буквами кита (`дд.мм.гггг`). Набранный
@@ -0,0 +1,42 @@
1
+ # `rt-date-panel`
2
+
3
+ ```html
4
+ <rt-date-panel type="datetime-local" [value]="value()" (picked)="value.set($event)" (closed)="close()" />
5
+ ```
6
+
7
+ Панель выбора даты и времени. Её открывает [`rt-date-picker`](../CONTEXT.md), а для выбора без
8
+ текстового поля она идёт из той же точки входа `@rt-tools/ui-kit-v2/date-picker`.
9
+
10
+ | вход | тип | умолчание |
11
+ | -------------------- | -------------------------------------- | ---------- |
12
+ | `type` | `'date' \| 'time' \| 'datetime-local'` | обязателен |
13
+ | `value` | `string` (форма значения поля) | `''` |
14
+ | `min` / `max` | `string \| null` (форма значения) | `null` |
15
+ | `minuteStep` | `number` | `5` |
16
+ | `sheet` | `boolean` | `false` |
17
+ | `now` | `Date \| null` | `null` |
18
+ | `isTodayButtonShown` | `boolean` | `true` |
19
+
20
+ `isTodayButtonShown` показывает «Сегодня» у типа `date`. Без кнопки подвала у даты нет, день
21
+ выбирается нажатием и сразу закрывает панель. Подвал других типов этот вход не трогает.
22
+
23
+ Месяц панели дописан днями соседних месяцев до полных недель, а день за границей зачёркнут. Над
24
+ колонками времени стоят подписи «Часы» и «Минуты». В поповере даты со временем подвал несёт
25
+ «Сейчас» слева, затем «Сбросить» и «Применить». «Сбросить» отдаёт пустое значение и закрывает
26
+ панель. В шторке вместо «Сейчас» и «Сбросить» слева стоит выбранное значение.
27
+
28
+ | выход | значение | когда |
29
+ | -------- | -------- | ----------------------------------------------------------- |
30
+ | `picked` | `string` | готовое значение в форме `value` |
31
+ | `closed` | — | панель просит закрыть себя: после выбора и по Escape в днях |
32
+
33
+ ## Главное, что нужно знать
34
+
35
+ **Поповер или шторку держит потребитель.** Панель сама не открывается и не закрывается: он создаёт
36
+ её заново на каждое открытие и закрывает по `closed`. Черновик живёт, пока панель открыта.
37
+
38
+ **Своей поверхности у панели нет.** В поповере фон, рамку и тень даёт потребитель, в
39
+ `rt-bottom-sheet` их даёт шторка. Вход `sheet` включает вид для шторки: дни под палец, а у даты со
40
+ временем переключатель «Дата | Время».
41
+
42
+ Как это выглядит, показывают истории `DatePanelOpening` в разделе полей формы.
@@ -102,7 +102,46 @@
102
102
 
103
103
  &__time {
104
104
  display: flex;
105
- gap: var(--rt-space-1);
105
+ gap: var(--rt-space-2);
106
+ }
107
+
108
+ /* Колонки времени рядом с месяцем отделены чертой на всю высоту тела. */
109
+ &__time:not(:first-child) {
110
+ align-self: stretch;
111
+ border-inline-start: var(--rt-date-panel-divider-width) solid var(--rt-color-border-subtle);
112
+ padding-inline-start: var(--rt-space-4);
113
+ }
114
+
115
+ /* Переключатель «Дата | Время» по ширине своих сегментов, по центру. */
116
+ &__tabs {
117
+ align-self: center;
118
+ }
119
+
120
+ /* Колонка времени с подписью над ней: подпись не прокручивается вместе с ячейками. */
121
+ &__time-group {
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: var(--rt-space-2);
125
+ }
126
+
127
+ &__time-title {
128
+ padding: var(--rt-space-1) 0;
129
+ color: var(--rt-color-text-muted);
130
+ font-size: var(--rt-text-xs);
131
+ text-align: center;
132
+ }
133
+
134
+ /* Первая кнопка подвала прижата влево, остальные стоят справа. */
135
+ &__lead {
136
+ margin-inline-end: auto;
137
+ }
138
+
139
+ /* Выбранное значение в подвале шторки: слева, напротив «Применить». */
140
+ &__value {
141
+ align-self: center;
142
+ color: var(--rt-color-text-muted);
143
+ font-size: var(--rt-text-sm);
144
+ margin-inline-end: auto;
106
145
  }
107
146
 
108
147
  &__column {
@@ -156,6 +195,20 @@
156
195
  }
157
196
  }
158
197
 
198
+ /* Ячейка времени — полоса на ширину колонки, число у левого края. Выбранная ячейка залита
199
+ бледным цветом действия, число тёмное и полужирное. */
200
+ &__column &__cell {
201
+ min-inline-size: calc(var(--rt-size-10) * 1.5);
202
+ padding-inline: var(--rt-space-4) 0;
203
+ text-align: start;
204
+
205
+ &[aria-pressed='true'] {
206
+ background: var(--rt-color-action-primary-subtle);
207
+ color: var(--rt-color-text-primary);
208
+ font-weight: var(--rt-font-weight-semibold);
209
+ }
210
+ }
211
+
159
212
  &__footer {
160
213
  display: flex;
161
214
  justify-content: flex-end;
@@ -197,7 +250,7 @@
197
250
  justify-content: center;
198
251
  }
199
252
 
200
- &--sheet &__column {
253
+ &--sheet &__time-group {
201
254
  flex: 1 1 0;
202
255
  }
203
256
  }
@@ -48,7 +48,7 @@ rtChatMessageActions let-message>` рисуется в меню «…» у ре
48
48
  ставит курсор в конец. Файлы остаются, сигнала набора нет, закрытое поле текст не принимает.
49
49
  Работу метода показывает история `QuickReplies` страницы переписки: кнопка у поля ответа и
50
50
  подсказка в боковой панели ставят текст в поле, гостю ничего не уходит. Метод `draftText` читает
51
- текст поля без разметки и файлы не трогает.
51
+ текст поля без разметки и файлы не трогает, а метод `draftFiles` отдаёт выбранные файлы.
52
52
 
53
53
  **Подпись на пузыре даёт приложение.** `dateLabel` стоит вместо времени, `dateIcon` на месте значка
54
54
  статуса. Значки ошибки отправки и недоставки остаются: они предупреждают. Подписи дней считаются от
@@ -38,12 +38,24 @@ export declare namespace IRtCalendar {
38
38
  key: string;
39
39
  day: Day;
40
40
  }
41
- /** View-модель месяца: подпись, пустые ячейки до первого дня, дни. */
41
+ /** День соседнего месяца в начале или в конце сетки: бледный, не нажимается. */
42
+ interface OutsideDay {
43
+ key: string;
44
+ dayOfMonth: number;
45
+ /** День за границей выбора — ещё и зачёркнут. */
46
+ crossed: boolean;
47
+ }
48
+ /**
49
+ * View-модель месяца: подпись, пустые ячейки до первого дня, дни. Дни соседних месяцев
50
+ * необязательны: заданы — встают на место пустых ячеек и дописывают последнюю неделю.
51
+ */
42
52
  interface Month {
43
53
  key: string;
44
54
  label: string;
45
55
  leadingBlanks: readonly number[];
46
56
  days: readonly Day[];
57
+ leadingDays?: readonly OutsideDay[];
58
+ trailingDays?: readonly OutsideDay[];
47
59
  }
48
60
  }
49
61
  export declare class RtCalendarComponent {
@@ -1,11 +1,12 @@
1
- import { NumberInput } from "@angular/cdk/coercion";
1
+ import { BooleanInput, NumberInput } from "@angular/cdk/coercion";
2
2
  import * as i0 from "@angular/core";
3
- import { ElementRef, InputSignal, InputSignalWithTransform, Signal, WritableSignal } from "@angular/core";
3
+ import { ElementRef, InputSignal, InputSignalWithTransform, OutputEmitterRef, Signal, WritableSignal } from "@angular/core";
4
4
  import { IRtIcon, IRtInput, TRtKitLabelMap } from "@rt-tools/ui-kit-v2/core";
5
+ import { IRtCalendar } from "@rt-tools/ui-kit-v2/calendar";
6
+ import { IRtToggleButtonGroup } from "@rt-tools/ui-kit-v2/toggle-button-group";
5
7
  import { RtFormControlBase } from "@rt-tools/ui-kit-v2/form-control";
6
8
  import { RtPopoverDirective } from "@rt-tools/ui-kit-v2/popover";
7
9
  import * as i1 from "@rt-tools/ui-kit-v2/radius";
8
- import { IRtCalendar } from "@rt-tools/ui-kit-v2/calendar";
9
10
  /** Контракт rt-date-picker. */
10
11
  export declare namespace IRtDatePicker {
11
12
  /** Тип значения: дата, время или дата со временем. */
@@ -70,6 +71,151 @@ export declare namespace IRtDatePicker {
70
71
  max: string | null;
71
72
  }
72
73
  }
74
+ type TView = 'days' | 'months' | 'years';
75
+ type TTab = 'date' | 'time';
76
+ /**
77
+ * Панель выбора даты и времени: месяц с листанием, сетки месяцев и годов, колонки часов и минут,
78
+ * подвал с «Сегодня», «Сейчас», «Готово» или «Применить» — по типу значения. Её открывает
79
+ * rt-date-picker, а без текстового поля она стоит в поповере или в rt-bottom-sheet потребителя.
80
+ *
81
+ * Панель не пишет в форму: готовое значение уходит событием `picked`, просьба закрыться —
82
+ * `closed`. Черновик живёт, пока панель открыта, поэтому её создают заново на каждое открытие.
83
+ * Поверхности у панели нет: фон, рамку и тень даёт то, во что её положили.
84
+ * Вся арифметика дат — в `rt-date-panel.logic`.
85
+ */
86
+ export declare class RtDatePanelComponent {
87
+ #private;
88
+ protected readonly t: Signal<TRtKitLabelMap>;
89
+ protected readonly weekdays: Signal<readonly string[]>;
90
+ protected readonly timeCells: Signal<readonly ElementRef<HTMLButtonElement>[]>;
91
+ protected readonly view: WritableSignal<TView>;
92
+ protected readonly tab: WritableSignal<TTab>;
93
+ protected readonly activeKey: WritableSignal<string | null>;
94
+ protected readonly today: Signal<string>;
95
+ protected readonly draftDay: WritableSignal<string | null>;
96
+ protected readonly draftTime: WritableSignal<string | null>;
97
+ protected readonly shownMonth: WritableSignal<string>;
98
+ protected readonly year: WritableSignal<number>;
99
+ protected readonly split: Signal<boolean>;
100
+ protected readonly showDate: Signal<boolean>;
101
+ protected readonly showTime: Signal<boolean>;
102
+ protected readonly months: Signal<IRtCalendar.Month[]>;
103
+ protected readonly canPrev: Signal<boolean>;
104
+ protected readonly canNext: Signal<boolean>;
105
+ protected readonly monthCells: Signal<IRtDatePicker.MonthCell[]>;
106
+ protected readonly canPrevYear: Signal<boolean>;
107
+ protected readonly canNextYear: Signal<boolean>;
108
+ /** Первый год блока, который показывает сетка годов. */
109
+ protected readonly yearStart: WritableSignal<number>;
110
+ protected readonly yearCells: Signal<IRtDatePicker.YearCell[]>;
111
+ protected readonly yearSpan: Signal<string>;
112
+ /** Выбранный год — год значения; текущий — год сегодняшнего дня. */
113
+ protected readonly chosenYear: Signal<string | null>;
114
+ protected readonly currentYear: Signal<string>;
115
+ protected readonly currentMonth: Signal<string>;
116
+ protected readonly canPrevYears: Signal<boolean>;
117
+ protected readonly canNextYears: Signal<boolean>;
118
+ protected readonly draftHour: Signal<number | null>;
119
+ protected readonly draftMinute: Signal<number | null>;
120
+ protected readonly columns: Signal<IRtDatePicker.TimeColumns>;
121
+ protected readonly todayOff: Signal<boolean>;
122
+ /** У даты в подвале только «Сегодня»: без неё пустая черта не рисуется. */
123
+ protected readonly hasFooter: Signal<boolean>;
124
+ /** Черновик собирается в значение и лежит в границах. */
125
+ protected readonly canApply: Signal<boolean>;
126
+ /** Черновик даты со временем текстом для подвала шторки: `15.03.2026, 09:30`; не собран — пусто. */
127
+ protected readonly draftText: Signal<string>;
128
+ protected readonly tabs: Signal<IRtToggleButtonGroup.Option<TTab>[]>;
129
+ /** Тип значения: дата, время или дата со временем. */
130
+ readonly type: InputSignal<IRtDatePicker.Type>;
131
+ /** Значение в форме нативного input'а: `YYYY-MM-DD`, `HH:mm`, `YYYY-MM-DDTHH:mm`; пустая строка — пусто. */
132
+ readonly value: InputSignal<string>;
133
+ /** Нижняя граница в форме значения; `null` — границы нет. */
134
+ readonly min: InputSignal<string | null>;
135
+ /** Верхняя граница в форме значения; `null` — границы нет. */
136
+ readonly max: InputSignal<string | null>;
137
+ /** Шаг колонки минут. */
138
+ readonly minuteStep: InputSignalWithTransform<number, NumberInput>;
139
+ /** Раскладка нижней шторки: дни под палец, у даты со временем — переключатель «Дата | Время». */
140
+ readonly sheet: InputSignalWithTransform<boolean, BooleanInput>;
141
+ /** Показывать ли «Сегодня» в подвале у типа `date`. Выключена — подвала у даты нет. */
142
+ readonly isTodayButtonShown: InputSignalWithTransform<boolean, BooleanInput>;
143
+ /**
144
+ * Момент, от которого считаются «сегодня» и «Сейчас». Без него панель читает часы сама; задаёт
145
+ * его витрина, чтобы кадр не менялся день ото дня.
146
+ */
147
+ readonly now: InputSignal<Date | null>;
148
+ /** Готовое значение в форме `value`: панель просит его записать. */
149
+ readonly picked: OutputEmitterRef<string>;
150
+ /** Панель просит закрыть себя: после выбора, а также по Escape в днях. */
151
+ readonly closed: OutputEmitterRef<void>;
152
+ constructor();
153
+ protected pickDay(day: IRtCalendar.Day): void;
154
+ protected pickToday(): void;
155
+ protected pickNow(): void;
156
+ protected pickHour(hour: number): void;
157
+ protected pickMinute(minute: number): void;
158
+ protected onGridKey(event: IRtCalendar.GridKey): void;
159
+ protected page(delta: number): void;
160
+ protected openMonths(): void;
161
+ protected pageYear(delta: number): void;
162
+ protected pickMonth(cell: IRtDatePicker.MonthCell): void;
163
+ protected openYears(): void;
164
+ protected pageYears(delta: number): void;
165
+ protected pickYear(cell: IRtDatePicker.YearCell): void;
166
+ /** Escape в сетке годов или месяцев возвращает на вид назад; в днях закрывает панель. */
167
+ protected onEscape(): void;
168
+ /** «Готово» и «Применить»: черновик уходит значением, панель закрывается. */
169
+ protected apply(): void;
170
+ /** «Сбросить»: значение становится пустым, панель закрывается. */
171
+ protected reset(): void;
172
+ static ɵfac: i0.ɵɵFactoryDeclaration<RtDatePanelComponent, never>;
173
+ static ɵcmp: i0.ɵɵComponentDeclaration<RtDatePanelComponent, "rt-date-panel", never, {
174
+ "type": {
175
+ "alias": "type";
176
+ "required": true;
177
+ "isSignal": true;
178
+ };
179
+ "value": {
180
+ "alias": "value";
181
+ "required": false;
182
+ "isSignal": true;
183
+ };
184
+ "min": {
185
+ "alias": "min";
186
+ "required": false;
187
+ "isSignal": true;
188
+ };
189
+ "max": {
190
+ "alias": "max";
191
+ "required": false;
192
+ "isSignal": true;
193
+ };
194
+ "minuteStep": {
195
+ "alias": "minuteStep";
196
+ "required": false;
197
+ "isSignal": true;
198
+ };
199
+ "sheet": {
200
+ "alias": "sheet";
201
+ "required": false;
202
+ "isSignal": true;
203
+ };
204
+ "isTodayButtonShown": {
205
+ "alias": "isTodayButtonShown";
206
+ "required": false;
207
+ "isSignal": true;
208
+ };
209
+ "now": {
210
+ "alias": "now";
211
+ "required": false;
212
+ "isSignal": true;
213
+ };
214
+ }, {
215
+ "picked": "picked";
216
+ "closed": "closed";
217
+ }, never, never, true, never>;
218
+ }
73
219
  /**
74
220
  * Поле даты, времени или даты со временем со своей панелью кита, визуально и по токенам
75
221
  * идентичное rt-input (host — box `--rt-input-*`, input — прозрачный `__field`). Кнопка в конце
@@ -186,7 +332,12 @@ export declare function rtDateAddMonths(month: string, delta: number): string;
186
332
  export declare function rtDateCanPage(month: string, delta: number, min: string | null, max: string | null): boolean;
187
333
  /** Месяц `YYYY-MM` для календаря: подпись по локали, пустые ячейки до первого дня, дни. */
188
334
  export declare function rtDateMonth(month: string, ctx: IRtDatePicker.MonthContext): IRtCalendar.Month;
189
- /** Короткие имена дней недели по локали, начиная с её первого дня. */
335
+ /**
336
+ * Месяц панели даты: тот же месяц, что у поля, но сетка дописана днями соседних месяцев до полных
337
+ * недель, а дни за границей выбора зачёркнуты, как занятые.
338
+ */
339
+ export declare function rtDatePanelMonth(month: string, ctx: IRtDatePicker.MonthContext): IRtCalendar.Month;
340
+ /** Короткие имена дней недели по локали, начиная с её первого дня, с заглавной буквы: «Пн», «Mon». */
190
341
  export declare function rtDateWeekdays(locale: string): string[];
191
342
  /** Двенадцать месяцев года для выбора месяца; месяц за границей выключен. */
192
343
  export declare function rtDateMonths(year: number, locale: string, min: string | null, max: string | null): IRtDatePicker.MonthCell[];
@@ -218,4 +369,5 @@ export declare function rtDateShape(type: IRtDatePicker.Type, layout: IRtDatePic
218
369
  * Читает набранный текст в значение ISO; `null` — текст не значение этого типа. Читается и
219
370
  * порядок локали, и сама форма значения: вставленная `2026-08-01` тоже становится значением.
220
371
  */
221
- export declare function rtDateParse(text: string, type: IRtDatePicker.Type, layout: IRtDatePicker.TextLayout): string | null;
372
+ export declare function rtDateParse(text: string, type: IRtDatePicker.Type, layout: IRtDatePicker.TextLayout): string | null;
373
+ export {};
@@ -545,6 +545,8 @@ export declare class RtChatComponent {
545
545
  setDraft(text: string): void;
546
546
  /** Текст поля ответа в виде, который сейчас на экране. Файлы сюда не входят, поля нет — пустая строка. */
547
547
  draftText(): string;
548
+ /** Файлы, выбранные к черновику. Вложений нет или нет поля — пустой список. */
549
+ draftFiles(): readonly File[];
548
550
  protected onSubmit(): void;
549
551
  protected onComposerKeydown(event: KeyboardEvent): void;
550
552
  /** Релей submit'а от `rt-message-composer` (rich-режим) наружу как `send`. */