@ryuzaki13/react-foundation-lib 1.4.1 → 1.4.3
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/README.md +154 -100
- package/dist/chunks/{formatters-CBgrBeSA.js → formatters-D33Uh3si.js} +7 -8
- package/dist/chunks/{formatters-CBgrBeSA.js.map → formatters-D33Uh3si.js.map} +1 -1
- package/dist/chunks/odata-service-CWkHASdw.js +2211 -0
- package/dist/chunks/odata-service-CWkHASdw.js.map +1 -0
- package/dist/chunks/{validate-BW-7BZy_.js → validate-DyzojM0w.js} +2 -2
- package/dist/chunks/{validate-BW-7BZy_.js.map → validate-DyzojM0w.js.map} +1 -1
- package/dist/error-report/index.js +2 -2
- package/dist/excel/index.js +20 -21
- package/dist/excel/index.js.map +1 -1
- package/dist/formatters/index.js +4 -5
- package/dist/{odata → odata-service}/buildSeparatedArrays.d.ts +1 -1
- package/dist/odata-service/buildSeparatedArrays.d.ts.map +1 -0
- package/dist/{odata/types.d.ts → odata-service/collectionTypes.d.ts} +1 -1
- package/dist/odata-service/collectionTypes.d.ts.map +1 -0
- package/dist/{odata → odata-service}/dateUtils.d.ts +1 -1
- package/dist/odata-service/dateUtils.d.ts.map +1 -0
- package/dist/{odata → odata-service}/findCollectionPairs.d.ts +1 -1
- package/dist/odata-service/findCollectionPairs.d.ts.map +1 -0
- package/dist/odata-service/index.d.ts +5 -0
- package/dist/odata-service/index.d.ts.map +1 -1
- package/dist/odata-service/index.js +2 -2
- package/dist/{odata/store.d.ts → odata-service/odataCollectionStore.d.ts} +1 -1
- package/dist/odata-service/odataCollectionStore.d.ts.map +1 -0
- package/dist/query-client/index.js.map +1 -1
- package/dist/query-client/queryClient.d.ts +1 -3
- package/dist/query-client/queryClient.d.ts.map +1 -1
- package/package.json +1 -5
- package/src/bounded-copy-stack/README.mdx +257 -0
- package/src/context-menu/README.mdx +326 -0
- package/src/currency/README.mdx +155 -0
- package/src/date-segments/README.mdx +319 -0
- package/src/error/README.mdx +95 -0
- package/src/error-report/README.mdx +612 -0
- package/src/excel/README.mdx +449 -0
- package/src/fingerprint/README.mdx +100 -0
- package/src/form/README.mdx +101 -0
- package/src/foundationLib.mdx +70 -54
- package/src/hooks/README.mdx +988 -0
- package/src/http/README.mdx +126 -0
- package/src/media/README.mdx +1853 -0
- package/src/notifications/README.mdx +323 -0
- package/src/number-scale/README.mdx +318 -0
- package/src/odata-service/README.mdx +2916 -0
- package/src/presets/README.mdx +136 -0
- package/src/pwa/README.mdx +394 -0
- package/src/query-client/README.mdx +2500 -0
- package/src/range-output/README.mdx +161 -0
- package/src/seo/README.mdx +279 -0
- package/src/session-storage/README.mdx +125 -0
- package/src/string-comparison/README.mdx +96 -0
- package/src/table/README.mdx +3106 -0
- package/src/three-scene/README.mdx +248 -0
- package/src/types/README.mdx +172 -0
- package/src/utils/README.mdx +1724 -0
- package/src/validators/README.mdx +239 -0
- package/src/virtualizer/README.mdx +138 -0
- package/src/xml/README.mdx +149 -0
- package/dist/chunks/dateUtils-B5V5aXB-.js +0 -847
- package/dist/chunks/dateUtils-B5V5aXB-.js.map +0 -1
- package/dist/chunks/odata-service-B6TxBSLm.js +0 -1277
- package/dist/chunks/odata-service-B6TxBSLm.js.map +0 -1
- package/dist/odata/buildSeparatedArrays.d.ts.map +0 -1
- package/dist/odata/dateUtils.d.ts.map +0 -1
- package/dist/odata/findCollectionPairs.d.ts.map +0 -1
- package/dist/odata/index.d.ts +0 -6
- package/dist/odata/index.d.ts.map +0 -1
- package/dist/odata/index.js +0 -102
- package/dist/odata/index.js.map +0 -1
- package/dist/odata/store.d.ts.map +0 -1
- package/dist/odata/types.d.ts.map +0 -1
- package/src/pwa/README.md +0 -638
- package/src/query-client/README.md +0 -565
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Foundation Lib/Interaction/Context Menu State" />
|
|
4
|
+
|
|
5
|
+
# Состояние и anchors контекстного меню через `@ryuzaki13/react-foundation-lib/context-menu`
|
|
6
|
+
|
|
7
|
+
Модуль описывает открытое/закрытое состояние контекстного меню, источник открытия и два вида anchor: DOM element или экранная точка. Также он создаёт virtual anchor для Floating UI.
|
|
8
|
+
|
|
9
|
+
Модуль не содержит React hook и не рисует меню.
|
|
10
|
+
|
|
11
|
+
## Содержание
|
|
12
|
+
|
|
13
|
+
- [Назначение и границы](#назначение-и-границы)
|
|
14
|
+
- [Установка и импорт](#установка-и-импорт)
|
|
15
|
+
- [Модель данных](#модель-данных)
|
|
16
|
+
- [Получение точки](#получение-точки)
|
|
17
|
+
- [`createVirtualAnchor`](#createvirtualanchor)
|
|
18
|
+
- [State helpers](#state-helpers)
|
|
19
|
+
- [React reducer recipe](#react-reducer-recipe)
|
|
20
|
+
- [Floating UI recipe](#floating-ui-recipe)
|
|
21
|
+
- [Доступность](#доступность)
|
|
22
|
+
- [Полный API и ошибки](#полный-api-и-ошибки)
|
|
23
|
+
|
|
24
|
+
## Назначение и границы
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
click/contextmenu/keyboard
|
|
28
|
+
│
|
|
29
|
+
├─ element anchor
|
|
30
|
+
└─ point {x,y}
|
|
31
|
+
│
|
|
32
|
+
▼
|
|
33
|
+
MenuState
|
|
34
|
+
│
|
|
35
|
+
▼
|
|
36
|
+
host React/Floating UI/menu component
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Модуль:
|
|
40
|
+
|
|
41
|
+
- не добавляет event listeners;
|
|
42
|
+
- не вызывает `preventDefault`;
|
|
43
|
+
- не хранит global state;
|
|
44
|
+
- не позиционирует floating element;
|
|
45
|
+
- не закрывает меню по Escape/outside click;
|
|
46
|
+
- не управляет focus/ARIA;
|
|
47
|
+
- не клонирует DOM elements.
|
|
48
|
+
|
|
49
|
+
Готовый visual primitive находится в `@ryuzaki13/react-foundation-ui/context-menu`.
|
|
50
|
+
|
|
51
|
+
## Установка и импорт
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import {
|
|
55
|
+
closeMenu,
|
|
56
|
+
createVirtualAnchor,
|
|
57
|
+
getMenuPointFromEvent,
|
|
58
|
+
getMenuPointFromRect,
|
|
59
|
+
initialMenuState,
|
|
60
|
+
openMenu,
|
|
61
|
+
toggleMenu
|
|
62
|
+
} from "@ryuzaki13/react-foundation-lib/context-menu";
|
|
63
|
+
|
|
64
|
+
import type {
|
|
65
|
+
ElementMenuAnchor,
|
|
66
|
+
MenuAnchor,
|
|
67
|
+
MenuOpenSource,
|
|
68
|
+
MenuPoint,
|
|
69
|
+
MenuState,
|
|
70
|
+
OpenMenuPayload,
|
|
71
|
+
PointMenuAnchor
|
|
72
|
+
} from "@ryuzaki13/react-foundation-lib/context-menu";
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Functions pure, но types/anchors используют DOM `Element`/`HTMLElement`. Публичный subpath не импортирует Floating UI runtime.
|
|
76
|
+
|
|
77
|
+
## Модель данных
|
|
78
|
+
|
|
79
|
+
### Point
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
interface MenuPoint {
|
|
83
|
+
x: number;
|
|
84
|
+
y: number;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Координаты соответствуют viewport/client coordinates, а не page scroll coordinates.
|
|
89
|
+
|
|
90
|
+
### Источник открытия
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
type MenuOpenSource = "click" | "contextmenu" | "keyboard" | "programmatic";
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Source помогает UI выбирать focus/animation/recovery, но helpers сами не меняют поведение по source.
|
|
97
|
+
|
|
98
|
+
### Anchors
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
type ElementMenuAnchor = {
|
|
102
|
+
type: "element";
|
|
103
|
+
element: HTMLElement;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
type PointMenuAnchor = {
|
|
107
|
+
type: "point";
|
|
108
|
+
point: MenuPoint;
|
|
109
|
+
contextElement?: Element | null;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
type MenuAnchor = ElementMenuAnchor | PointMenuAnchor;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`contextElement` помогает Floating UI определить document/layout context для virtual element.
|
|
116
|
+
|
|
117
|
+
### State
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
interface MenuState {
|
|
121
|
+
open: boolean;
|
|
122
|
+
source: MenuOpenSource | null;
|
|
123
|
+
anchor: MenuAnchor | null;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Нормальное закрытое состояние:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
{
|
|
131
|
+
open: false,
|
|
132
|
+
source: null,
|
|
133
|
+
anchor: null
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Получение точки
|
|
138
|
+
|
|
139
|
+
### `getMenuPointFromEvent`
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
function onContextMenu(event: React.MouseEvent<HTMLElement>) {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
|
|
145
|
+
const point = getMenuPointFromEvent(event);
|
|
146
|
+
// { x: event.clientX, y: event.clientY }.
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Функции нужен только structural object с `clientX`/`clientY`, поэтому подходят native и React mouse events.
|
|
151
|
+
|
|
152
|
+
Она не вызывает `preventDefault` и не проверяет finite coordinates.
|
|
153
|
+
|
|
154
|
+
### `getMenuPointFromRect`
|
|
155
|
+
|
|
156
|
+
Возвращает геометрический центр rect:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const point = getMenuPointFromRect({
|
|
160
|
+
left: 10,
|
|
161
|
+
top: 20,
|
|
162
|
+
width: 80,
|
|
163
|
+
height: 40
|
|
164
|
+
});
|
|
165
|
+
// { x: 50, y: 40 }.
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Подходит для keyboard opening от центра `element.getBoundingClientRect()`:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const point = getMenuPointFromRect(button.getBoundingClientRect());
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## `createVirtualAnchor`
|
|
175
|
+
|
|
176
|
+
Создаёт object с `getBoundingClientRect`, представляющий точку нулевого размера:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const virtualAnchor = createVirtualAnchor({ x: 120, y: 240 }, triggerElement);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Возвращаемый rect:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
{
|
|
186
|
+
x: 120,
|
|
187
|
+
y: 240,
|
|
188
|
+
top: 240,
|
|
189
|
+
left: 120,
|
|
190
|
+
bottom: 240,
|
|
191
|
+
right: 120,
|
|
192
|
+
width: 0,
|
|
193
|
+
height: 0,
|
|
194
|
+
toJSON: () => undefined
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Если `contextElement` равен `null`/`undefined`, в результате поле становится `undefined`.
|
|
199
|
+
|
|
200
|
+
Point object читается при каждом `getBoundingClientRect`. Не мутируйте его; создайте новый anchor при изменении координат.
|
|
201
|
+
|
|
202
|
+
## State helpers
|
|
203
|
+
|
|
204
|
+
### `initialMenuState`
|
|
205
|
+
|
|
206
|
+
Экспортируется один module-level object. Рассматривайте его как readonly-константу и не мутируйте:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const [state, setState] = useState<MenuState>(initialMenuState);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### `openMenu`
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
const state = openMenu({
|
|
216
|
+
source: "contextmenu",
|
|
217
|
+
anchor: {
|
|
218
|
+
type: "point",
|
|
219
|
+
point: { x: 100, y: 200 }
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Всегда возвращает новый state `{ open: true, source, anchor }`. Anchor/element переносятся по ссылке.
|
|
225
|
+
|
|
226
|
+
### `closeMenu`
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
const closed = closeMenu(opened);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Открытое состояние превращается в canonical closed state. Если `state.open === false` и `state.anchor === null`, возвращается исходная ссылка `state`, что удобно для reducer no-op.
|
|
233
|
+
|
|
234
|
+
Передавайте согласованное состояние. Если вручную создать `{ open:false, source:"click", anchor:null }`, `closeMenu` вернёт его без очистки source.
|
|
235
|
+
|
|
236
|
+
### `toggleMenu`
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
const next = toggleMenu(current, payload);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
- если `current.open === true`, закрывает меню и игнорирует payload;
|
|
243
|
+
- если меню закрыто, вызывает `openMenu(payload)`.
|
|
244
|
+
|
|
245
|
+
Toggle подходит для click button. Для `contextmenu` чаще нужен явный `openMenu`, чтобы повторный right-click перемещал anchor, а не закрывал меню.
|
|
246
|
+
|
|
247
|
+
## React reducer recipe
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
const [menu, setMenu] = useState<MenuState>(initialMenuState);
|
|
251
|
+
|
|
252
|
+
function handleContextMenu(event: React.MouseEvent<HTMLElement>) {
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
|
|
255
|
+
setMenu(
|
|
256
|
+
openMenu({
|
|
257
|
+
source: "contextmenu",
|
|
258
|
+
anchor: {
|
|
259
|
+
type: "point",
|
|
260
|
+
point: getMenuPointFromEvent(event),
|
|
261
|
+
contextElement: event.currentTarget
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function handleClose() {
|
|
268
|
+
setMenu((current) => closeMenu(current));
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Не сохраняйте `MenuState` в JSON/localStorage: он может содержать `HTMLElement`.
|
|
273
|
+
|
|
274
|
+
## Floating UI recipe
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
const reference =
|
|
278
|
+
menu.anchor?.type === "element"
|
|
279
|
+
? menu.anchor.element
|
|
280
|
+
: menu.anchor?.type === "point"
|
|
281
|
+
? createVirtualAnchor(menu.anchor.point, menu.anchor.contextElement)
|
|
282
|
+
: null;
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Передайте `reference` в public API выбранного Floating UI wrapper. Не импортируйте private package files.
|
|
286
|
+
|
|
287
|
+
## Доступность
|
|
288
|
+
|
|
289
|
+
Host обязан реализовать:
|
|
290
|
+
|
|
291
|
+
- открытие клавишей Context Menu/Shift+F10 или принятой командой;
|
|
292
|
+
- focus первого/выбранного item;
|
|
293
|
+
- Arrow/Home/End navigation;
|
|
294
|
+
- закрытие по Escape и возврат focus;
|
|
295
|
+
- `role="menu"`/`menuitem` только если semantics действительно menu;
|
|
296
|
+
- disabled items;
|
|
297
|
+
- outside click и focus dismissal;
|
|
298
|
+
- сохранение trigger element для keyboard recovery.
|
|
299
|
+
|
|
300
|
+
Для keyboard source element anchor обычно лучше point без pointer coordinates.
|
|
301
|
+
|
|
302
|
+
## Полный API и ошибки
|
|
303
|
+
|
|
304
|
+
| Export | Назначение |
|
|
305
|
+
| ----------------------- | --------------------------- |
|
|
306
|
+
| `getMenuPointFromEvent` | Читает client coordinates |
|
|
307
|
+
| `getMenuPointFromRect` | Центр rect |
|
|
308
|
+
| `createVirtualAnchor` | Point-like DOMRect provider |
|
|
309
|
+
| `initialMenuState` | Canonical closed state |
|
|
310
|
+
| `openMenu` | Новый opened state |
|
|
311
|
+
| `closeMenu` | Закрывает/очищает state |
|
|
312
|
+
| `toggleMenu` | Toggle по `open` |
|
|
313
|
+
| `MenuPoint` | `{ x, y }` |
|
|
314
|
+
| `MenuOpenSource` | Источник открытия |
|
|
315
|
+
| `MenuAnchor` | Element/point union |
|
|
316
|
+
| `MenuState` | Полное state |
|
|
317
|
+
| `OpenMenuPayload` | `{ source, anchor }` |
|
|
318
|
+
|
|
319
|
+
Типичные ошибки:
|
|
320
|
+
|
|
321
|
+
- Использовать `pageX/pageY` вместо client coordinates.
|
|
322
|
+
- Вызывать toggle при каждом `contextmenu` и неожиданно закрывать открытое меню.
|
|
323
|
+
- Мутировать `initialMenuState`.
|
|
324
|
+
- Persist DOM element.
|
|
325
|
+
- Считать virtual anchor готовым positioner-ом.
|
|
326
|
+
- Не возвращать focus после keyboard menu.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Foundation Lib/Data/Currency" />
|
|
4
|
+
|
|
5
|
+
# Валютные подписи через `@ryuzaki13/react-foundation-lib/currency`
|
|
6
|
+
|
|
7
|
+
Модуль связывает общий OData-параметр `p_curr` с отображаемой подписью показателя. Он определяет один из двух режимов валюты и добавляет соответствующий суффикс к label.
|
|
8
|
+
|
|
9
|
+
## Содержание
|
|
10
|
+
|
|
11
|
+
- [Назначение](#назначение)
|
|
12
|
+
- [Установка и импорт](#установка-и-импорт)
|
|
13
|
+
- [`CurrencyMode`](#currencymode)
|
|
14
|
+
- [`resolveCurrencyModeFromODataParameters`](#resolvecurrencymodefromodataparameters)
|
|
15
|
+
- [`resolveCurrencyAwareLabel`](#resolvecurrencyawarelabel)
|
|
16
|
+
- [Готовый pipeline](#готовый-pipeline)
|
|
17
|
+
- [Ограничения и ошибки](#ограничения-и-ошибки)
|
|
18
|
+
- [API-справочник](#api-справочник)
|
|
19
|
+
|
|
20
|
+
## Назначение
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
wrapped OData params
|
|
24
|
+
{ p_curr: { value: true } }
|
|
25
|
+
│
|
|
26
|
+
▼
|
|
27
|
+
CurrencyMode "rub"
|
|
28
|
+
│
|
|
29
|
+
▼
|
|
30
|
+
"Выручка" + ["за тн", "руб/тн"]
|
|
31
|
+
│
|
|
32
|
+
▼
|
|
33
|
+
"Выручка руб/тн"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Модуль не форматирует денежное число, не конвертирует валюту и не строит OData query. Для чисел используйте `/formatters`, для OData params — `/odata-service`.
|
|
37
|
+
|
|
38
|
+
## Установка и импорт
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { resolveCurrencyAwareLabel, resolveCurrencyModeFromODataParameters } from "@ryuzaki13/react-foundation-lib/currency";
|
|
42
|
+
|
|
43
|
+
import type { CurrencyMode } from "@ryuzaki13/react-foundation-lib/currency";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Модуль ESM, не требует React или browser API. Параметры должны иметь публичную wrapped-форму из `/odata-service`.
|
|
47
|
+
|
|
48
|
+
## `CurrencyMode`
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
type CurrencyMode = "internal" | "rub";
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `internal` — внутренний режим;
|
|
55
|
+
- `rub` — рублёвый режим.
|
|
56
|
+
|
|
57
|
+
`null` используется resolver-функцией, когда режим определить нельзя.
|
|
58
|
+
|
|
59
|
+
## `resolveCurrencyModeFromODataParameters`
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const mode = resolveCurrencyModeFromODataParameters(params);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Функция читает только собственное поле `p_curr.value`:
|
|
66
|
+
|
|
67
|
+
| Вход | Результат |
|
|
68
|
+
| ------------------------------- | ------------ |
|
|
69
|
+
| параметров нет | `null` |
|
|
70
|
+
| поля `p_curr` нет | `null` |
|
|
71
|
+
| `{ p_curr: { value: false } }` | `"internal"` |
|
|
72
|
+
| `{ p_curr: { value: true } }` | `"rub"` |
|
|
73
|
+
| `{ p_curr: { value: "true" } }` | `null` |
|
|
74
|
+
| `{ p_curr: { value: 1 } }` | `null` |
|
|
75
|
+
|
|
76
|
+
Строка `"true"` намеренно не приводится к boolean. Нормализуйте внешний payload на OData boundary, а не внутри UI label.
|
|
77
|
+
|
|
78
|
+
Пример с helper `/odata-service`:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { wrapODataParams } from "@ryuzaki13/react-foundation-lib/odata-service";
|
|
82
|
+
|
|
83
|
+
const params = wrapODataParams({ p_curr: true });
|
|
84
|
+
const mode = resolveCurrencyModeFromODataParameters(params);
|
|
85
|
+
// "rub".
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## `resolveCurrencyAwareLabel`
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const label = resolveCurrencyAwareLabel("Выручка", ["за тн", "руб/тн"], "rub");
|
|
92
|
+
// "Выручка руб/тн".
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Пара `currencyLabels` имеет фиксированный смысл:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
readonly[(internalLabel, rubLabel)];
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| Mode | Индекс суффикса |
|
|
102
|
+
| ---------- | --------------: |
|
|
103
|
+
| `internal` | 0 |
|
|
104
|
+
| `rub` | 1 |
|
|
105
|
+
|
|
106
|
+
Точные правила:
|
|
107
|
+
|
|
108
|
+
- без `currencyLabels` возвращается исходный `label`;
|
|
109
|
+
- при `null`/`undefined` mode возвращается исходный `label`;
|
|
110
|
+
- суффикс обрезается через `trim()`;
|
|
111
|
+
- пустой суффикс не добавляется;
|
|
112
|
+
- обычно между label и суффиксом добавляется один пробел;
|
|
113
|
+
- если обрезанный суффикс начинается с запятой, дополнительный пробел не добавляется.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
resolveCurrencyAwareLabel("Выручка", [", за тн", ", руб/тн"], "rub");
|
|
117
|
+
// "Выручка, руб/тн".
|
|
118
|
+
|
|
119
|
+
resolveCurrencyAwareLabel("Выручка", ["", " "], "rub");
|
|
120
|
+
// "Выручка".
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Исходный `label` не обрезается и не нормализуется.
|
|
124
|
+
|
|
125
|
+
## Готовый pipeline
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
function buildMetricLabel(
|
|
129
|
+
baseLabel: string,
|
|
130
|
+
currencyLabels: readonly [string, string] | undefined,
|
|
131
|
+
params: WrappedODataParameters | undefined
|
|
132
|
+
) {
|
|
133
|
+
const mode = resolveCurrencyModeFromODataParameters(params);
|
|
134
|
+
return resolveCurrencyAwareLabel(baseLabel, currencyLabels, mode);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Resolver чистый: одинаковый вход даёт одинаковую строку и не меняет params/labels.
|
|
139
|
+
|
|
140
|
+
## Ограничения и ошибки
|
|
141
|
+
|
|
142
|
+
- Поддерживается только параметр с точным id `p_curr`.
|
|
143
|
+
- Поддерживаются только два режима; код валюты `USD`, `EUR` и exchange rate не обрабатываются.
|
|
144
|
+
- Функция не проверяет грамматику и единицы измерения.
|
|
145
|
+
- Запятая должна входить в сам суффикс, если нужна форма `"Выручка, руб"`.
|
|
146
|
+
- Не передавайте плоский `{ p_curr: true }`: ожидается `{ p_curr: { value: true } }`.
|
|
147
|
+
- Не используйте label helper для финансовых вычислений.
|
|
148
|
+
|
|
149
|
+
## API-справочник
|
|
150
|
+
|
|
151
|
+
| Export | Результат |
|
|
152
|
+
| ---------------------------------------- | ----------------------- |
|
|
153
|
+
| `CurrencyMode` | `"internal" \| "rub"` |
|
|
154
|
+
| `resolveCurrencyModeFromODataParameters` | `CurrencyMode \| null` |
|
|
155
|
+
| `resolveCurrencyAwareLabel` | Итоговая display-строка |
|