@retailcrm/embed-ui-v1-endpoint 0.9.18 → 0.9.21

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 CHANGED
@@ -41,4 +41,6 @@ runEndpoint(runner)
41
41
  - [`createEndpoint`](./docs/create-endpoint.md) — как вручную создать endpoint с transport и messenger.
42
42
  - [`runEndpoint`](./docs/run-endpoint.md) — как поднять endpoint в worker entry одной строкой.
43
43
  - [`targets` и `defineTarget`](./docs/targets.md) — как типизировать цели виджетов и маршрутизировать их по target.
44
+ - [`menu-placements`](./docs/menu-placements.md) — как описывать меню и пункты навигации для remote-страниц.
45
+ - [`page-routes`](./docs/page-routes.md) — как связывать page `code`, CRM route и `definePageRunner`.
44
46
  - [`layout`](./docs/layout.md) — как выбирать layout-паттерны страниц, `modal sidebar` и `modal window`, и из каких `v1-components` их собирать.
package/docs/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Документация `@retailcrm/embed-ui-v1-endpoint`
2
+
3
+ В этом каталоге собраны продвинутые гайды по публичному API `v1-endpoint`.
4
+
5
+ ## По методам
6
+
7
+ - [`defineRunner`](./define-runner.md) — объединение page/widget runners в один endpoint runner.
8
+ - [`definePageRunner`](./define-page-runner.md) — запуск remote-страниц по `code`.
9
+ - [`defineWidgetRunner`](./define-widget-runner.md) — запуск remote-виджетов по `target`.
10
+ - [`createEndpoint`](./create-endpoint.md) — ручное создание endpoint с transport/messenger.
11
+ - [`runEndpoint`](./run-endpoint.md) — запуск endpoint в worker одной строкой.
12
+
13
+ ## Дополнительно
14
+
15
+ - [`targets` и `defineTarget`](./targets.md) — типизированные цели для виджетов.
16
+ - [`menu-placements`](./menu-placements.md) — как описывать меню и пункты навигации, из которых открываются remote-страницы.
17
+ - [`page-routes`](./page-routes.md) — как связывать page `code`, CRM route и `definePageRunner`.
18
+ - [`layout`](./layout.md) — практический гайд по layout-паттернам страниц, шторок и модалок.
@@ -0,0 +1,54 @@
1
+ # `createEndpoint`
2
+
3
+ `createEndpoint` связывает runner и transport (messenger), после чего
4
+ экспортирует endpoint API (`run`, `release`, `reset`) через `@remote-ui/rpc`.
5
+
6
+ ## Сигнатура
7
+
8
+ ```ts
9
+ createEndpoint(
10
+ runner: {
11
+ page: PageRunner;
12
+ widget: WidgetRunner;
13
+ },
14
+ messenger: MessageEndpoint
15
+ ): Endpoint<RemoteApi>
16
+ ```
17
+
18
+ ## Что делает под капотом
19
+
20
+ При `run(...)`:
21
+
22
+ 1. сбрасывает предыдущий mount для того же `id` (widget) или `code` (page),
23
+ 2. поднимает remote root (`mountEndpointRoot`),
24
+ 3. создаёт `pinia` и инжектит endpoint/context accessors,
25
+ 4. вызывает нужный runner (`page.run` или `widget.run`),
26
+ 5. сохраняет destroy-функцию в registry.
27
+
28
+ При `release(...)`:
29
+
30
+ - вызывает destroy для конкретного `id` или `code`.
31
+
32
+ При `reset()`:
33
+
34
+ - вызывает destroy для всех активных page/widget инстансов.
35
+
36
+ ## Пример (низкоуровневый)
37
+
38
+ ```ts
39
+ import { defineRunner, createEndpoint } from '@retailcrm/embed-ui-v1-endpoint/remote'
40
+
41
+ const runner = defineRunner({
42
+ pages: [MyPageRoot],
43
+ widgets: [MyWidgetRoot],
44
+ })
45
+
46
+ // messenger зависит от среды исполнения
47
+ createEndpoint(runner, self as unknown as MessageEndpoint)
48
+ ```
49
+
50
+ ## Когда нужен именно `createEndpoint`
51
+
52
+ - Нужна кастомная интеграция transport-слоя.
53
+ - Вы сами контролируете, где и как создаётся `MessageEndpoint`.
54
+ - Нужно использовать endpoint не через стандартный worker-entry сценарий.
@@ -0,0 +1,62 @@
1
+ # `definePageRunner`
2
+
3
+ `definePageRunner` создаёт runner для remote-страниц.
4
+ При запуске в компонент пробрасывается проп `code`.
5
+
6
+ ## Перегрузки
7
+
8
+ ```ts
9
+ definePageRunner(component: Component, beforeMount?: (app, pinia) => void | Promise<void>): Runner
10
+ definePageRunner(runners: Record<string, Runner>): Runner
11
+ ```
12
+
13
+ ## Вариант 1. Один компонент на все `code`
14
+
15
+ ```ts
16
+ import { definePageRunner } from '@retailcrm/embed-ui-v1-endpoint/remote'
17
+
18
+ import PageRoot from './PageRoot.vue'
19
+
20
+ const pageRunner = definePageRunner(PageRoot)
21
+ ```
22
+
23
+ Компонент получит `code` как prop:
24
+
25
+ ```ts
26
+ // внутри PageRoot
27
+ defineProps<{
28
+ code: string;
29
+ }>()
30
+ ```
31
+
32
+ ## Вариант 2. Разные раннеры по `code`
33
+
34
+ ```ts
35
+ import { definePageRunner } from '@retailcrm/embed-ui-v1-endpoint/remote'
36
+
37
+ import OrdersPage from './OrdersPage.vue'
38
+ import CustomersPage from './CustomersPage.vue'
39
+
40
+ const pageRunner = definePageRunner({
41
+ orders: definePageRunner(OrdersPage),
42
+ customers: definePageRunner(CustomersPage),
43
+ })
44
+ ```
45
+
46
+ Если для `code` нет раннера, будет warning в консоль и noop-destroy.
47
+
48
+ ## `beforeMount`
49
+
50
+ `beforeMount` вызывается после `app.use(pinia)` и до `app.mount(...)`.
51
+ Подходит для регистрации плагинов, глобальных компонентов и начальной синхронизации сторов.
52
+
53
+ ```ts
54
+ const pageRunner = definePageRunner(PageRoot, async (app, pinia) => {
55
+ // init code
56
+ })
57
+ ```
58
+
59
+ Читайте также:
60
+
61
+ - [`page-routes`](./page-routes.md) — как связать page `code`, CRM route и компонент страницы.
62
+ - [`menu-placements`](./menu-placements.md) — как описывать пункты меню, которые открывают remote-страницы.
@@ -0,0 +1,63 @@
1
+ # `defineRunner`
2
+
3
+ `defineRunner` — это фасад над `definePageRunner` и `defineWidgetRunner`.
4
+ Он создаёт единый runner для endpoint, который умеет запускать и страницы, и виджеты.
5
+
6
+ ## Сигнатура
7
+
8
+ ```ts
9
+ defineRunner(config: {
10
+ pages: Parameters<typeof definePageRunner>;
11
+ widgets: Parameters<typeof defineWidgetRunner>;
12
+ }): {
13
+ page: PageRunner;
14
+ widget: WidgetRunner;
15
+ }
16
+ ```
17
+
18
+ ## Когда использовать
19
+
20
+ - Когда в одной интеграции есть и page-, и widget-сценарии.
21
+ - Когда нужен единый объект для `createEndpoint(...)` или `runEndpoint(...)`.
22
+
23
+ ## Пример
24
+
25
+ ```ts
26
+ import { defineRunner } from '@retailcrm/embed-ui-v1-endpoint/remote'
27
+
28
+ import OrdersPage from './pages/OrdersPage.vue'
29
+ import OrderCommonWidget from './widgets/OrderCommonWidget.vue'
30
+
31
+ const runner = defineRunner({
32
+ pages: [OrdersPage],
33
+ widgets: [OrderCommonWidget],
34
+ })
35
+ ```
36
+
37
+ ## Пример с beforeMount и маппингом
38
+
39
+ ```ts
40
+ import { definePageRunner, defineRunner, defineWidgetRunner } from '@retailcrm/embed-ui-v1-endpoint/remote'
41
+
42
+ import OrdersPage from './pages/OrdersPage.vue'
43
+ import CustomersPage from './pages/CustomersPage.vue'
44
+ import CommonBeforeWidget from './widgets/CommonBeforeWidget.vue'
45
+ import CommonAfterWidget from './widgets/CommonAfterWidget.vue'
46
+
47
+ const runner = defineRunner({
48
+ pages: [{
49
+ orders: definePageRunner(OrdersPage),
50
+ customers: definePageRunner(CustomersPage),
51
+ }],
52
+
53
+ widgets: [{
54
+ 'order/card:common.before': defineWidgetRunner(CommonBeforeWidget),
55
+ 'order/card:common.after': defineWidgetRunner(CommonAfterWidget),
56
+ }],
57
+ })
58
+ ```
59
+
60
+ ## Что важно помнить
61
+
62
+ - `pages` и `widgets` передаются как tuple-аргументы соответствующих методов.
63
+ - Если нужен полный контроль над каждым типом runner, можно использовать `definePageRunner` и `defineWidgetRunner` напрямую.
@@ -0,0 +1,56 @@
1
+ # `defineWidgetRunner`
2
+
3
+ `defineWidgetRunner` создаёт runner для remote-виджетов.
4
+ При запуске в компонент пробрасывается проп `target`.
5
+
6
+ ## Перегрузки
7
+
8
+ ```ts
9
+ defineWidgetRunner(component: Component, beforeMount?: (app, pinia) => void | Promise<void>): Runner
10
+ defineWidgetRunner(runners: Partial<Record<TargetName, Runner>>): Runner
11
+ ```
12
+
13
+ ## Вариант 1. Один компонент на все target
14
+
15
+ ```ts
16
+ import { defineWidgetRunner } from '@retailcrm/embed-ui-v1-endpoint/remote'
17
+
18
+ import WidgetRoot from './WidgetRoot.vue'
19
+
20
+ const widgetRunner = defineWidgetRunner(WidgetRoot)
21
+ ```
22
+
23
+ Компонент получит `target` как prop:
24
+
25
+ ```ts
26
+ // внутри WidgetRoot
27
+ defineProps<{
28
+ target: string;
29
+ }>()
30
+ ```
31
+
32
+ ## Вариант 2. Разные раннеры по `target`
33
+
34
+ ```ts
35
+ import { defineWidgetRunner } from '@retailcrm/embed-ui-v1-endpoint/remote'
36
+
37
+ import BeforeWidget from './BeforeWidget.vue'
38
+ import AfterWidget from './AfterWidget.vue'
39
+
40
+ const widgetRunner = defineWidgetRunner({
41
+ 'order/card:common.before': defineWidgetRunner(BeforeWidget),
42
+ 'order/card:common.after': defineWidgetRunner(AfterWidget),
43
+ })
44
+ ```
45
+
46
+ Если для `target` нет раннера, будет warning в консоль и noop-destroy.
47
+
48
+ ## `beforeMount`
49
+
50
+ `beforeMount` работает аналогично page-раннеру: выполняется перед mount и после подключения `pinia`.
51
+
52
+ ```ts
53
+ const widgetRunner = defineWidgetRunner(WidgetRoot, async (app, pinia) => {
54
+ // init code
55
+ })
56
+ ```
package/docs/layout.md ADDED
@@ -0,0 +1,363 @@
1
+ # Гайд по layout для страниц и связанных экранов
2
+
3
+ Этот документ собирает в одном месте практические правила по тому, как проектировать
4
+ страницы для `@retailcrm/embed-ui-v1-endpoint`: списки, карточки, страницы с несколькими
5
+ колонками, страницы с collapse-блоками, а также случаи, когда вместо полноценной страницы
6
+ следует использовать `modal sidebar` или `modal window`.
7
+
8
+ Это не API-справка по `v1-endpoint`, а UX/layout guide для людей, которые собирают remote-page
9
+ через `definePageRunner(...)` и хотят держаться общего визуального языка CRM.
10
+
11
+ ## Базовая ментальная модель
12
+
13
+ Обычная страница в CRM чаще всего состоит из таких зон:
14
+
15
+ 1. Заголовок страницы.
16
+ 2. Справа от заголовка: primary и вспомогательные действия.
17
+ 3. Опционально: фильтры или tabs под заголовком.
18
+ 4. Основной контент на белых подложках.
19
+ 5. Опционально: нижняя панель сохранения.
20
+
21
+ Если экран перестаёт быть удобной полноценной страницей, его не следует усложнять бесконечно;
22
+ в этом случае его следует переводить в другой паттерн: `modal sidebar` или `modal window`.
23
+
24
+ ## Термины
25
+
26
+ В этом документе используются следующие термины:
27
+
28
+ - `modal sidebar` — боковая выезжающая панель. В разговорной речи может называться “шторкой”.
29
+ - `modal window` — всплывающее модальное окно. В разговорной речи может называться “модалкой”.
30
+ - `страница` — основной route-level экран, который занимает центральную рабочую область CRM.
31
+
32
+ Далее по тексту предпочтительно используются термины `modal sidebar` и `modal window`, потому что
33
+ они напрямую соотносятся с компонентами `UiModalSidebar`, `UiModalWindow` и `UiModalWindowSurface`.
34
+
35
+ ## Общие правила
36
+
37
+ ### Отступы и сетка
38
+
39
+ - Базовая система расстояний строится на шаге `4px`.
40
+ - Используйте расстояния, кратные `4px`: `4`, `8`, `12`, `16`, `20`, `24`, `28`, `32`.
41
+ - Для контентных подложек базовое правило такое:
42
+ - отступ от верхнего края подложки: `24px`;
43
+ - отступы от левого, правого и нижнего края: `32px`.
44
+ - Между смысловыми блоками оставляйте заметную дистанцию, а не склеивайте их.
45
+
46
+ ### Заголовок страницы
47
+
48
+ - Заголовок страницы обычно строится на `Text large Accent 24`.
49
+ - Заголовок может быть статическим или редактируемым inline.
50
+ - Справа от заголовка может быть одна или несколько кнопок.
51
+ - Рядом с кнопками может жить дополнительный текстовый статус или небольшая вспомогательная ссылка.
52
+
53
+ Типичная компонентная основа:
54
+
55
+ - `UiPageHeader` для верхнего блока страницы.
56
+ - `UiButton`, `UiToolbarButton`, `UiToolbarLink` для действий справа от заголовка.
57
+
58
+ ### Кнопки
59
+
60
+ - На странице допускается одна основная `Default Primary` кнопка.
61
+ - Отдельно может быть одна `Success Primary`, если сценарий этого требует.
62
+ - Остальные действия рекомендуется оформлять через `Default Secondary`, `Default Tertiary`, `Success Secondary` и похожие менее доминирующие варианты.
63
+ - Если действий много, одно оставляют главным, а остальные уводят во вторичный слой или dropdown.
64
+
65
+ Типичная компонентная основа:
66
+
67
+ - `UiButton` для primary, secondary и tertiary действий.
68
+ - `UiMenuItem` и `UiPopper` для вторичных выпадающих меню, если действий слишком много для строки заголовка.
69
+
70
+ ### Подложки
71
+
72
+ - Основной контент страницы обычно расположен на белых подложках.
73
+ - Подложка должна собирать один смысловой блок, а не случайный набор элементов.
74
+ - Если на экране есть несколько независимых смысловых секций, предпочтительнее использовать несколько подложек, чем одну перегруженную.
75
+
76
+ ## 1. Страница со списком сущностей
77
+
78
+ ### Когда использовать
79
+
80
+ - Списки заказов.
81
+ - Списки клиентов.
82
+ - Списки рассылок.
83
+ - Списки задач.
84
+ - Любые индексные страницы, где важны поиск, фильтрация и массовый просмотр данных.
85
+
86
+ ### Из чего обычно состоит
87
+
88
+ 1. Заголовок страницы.
89
+ 2. Одна или несколько кнопок справа от заголовка.
90
+ 3. Блок фильтров.
91
+ 4. Таблица со списком сущностей.
92
+ 5. Пагинация и сопутствующие действия таблицы.
93
+
94
+ ### Структура блока фильтров
95
+
96
+ - Фильтры собираются из полей вроде `Select`, `Input`, `Combo-box`, date-range и других компактных контролов.
97
+ - Под блоком фильтров обычно есть кнопка применения и отдельная кнопка сброса.
98
+ - Фильтры удобно располагать в ряды примерно по `4-5` штук.
99
+ - Если фильтров больше, они переходят на следующую строку.
100
+ - Фильтры допускается сворачивать, если экран ими перегружается.
101
+
102
+ Типичная компонентная основа:
103
+
104
+ - `UiField` как обертка для подписи, hint и validation state.
105
+ - `UiTextbox`, `UiSelect`, `UiDatePicker`, `UiTimePicker`, `UiCheckbox`, `UiRadioSwitch` как фильтрующие контролы.
106
+ - `UiButton` для действий `Применить`, `Сбросить`, `Очистить`.
107
+
108
+ ### Роль таблицы
109
+
110
+ - Таблица является главным содержимым списка.
111
+ - Она может листаться, пагинироваться и поддерживать выгрузку.
112
+ - Не следует смешивать в одном списочном экране слишком много “карточной” логики; список должен оставаться в первую очередь инструментом поиска и просмотра.
113
+
114
+ Типичная компонентная основа:
115
+
116
+ - `UiTable` как корневой компонент списка.
117
+ - `UiTableColumn`, `UiTableHeadCell`, `UiTableBodyCell`, `UiTableSorter`, `UiTableFooterSection`, `UiTableFooterButton` как составные части таблицы.
118
+
119
+ ## 2. Страница-карточка
120
+
121
+ ### Когда использовать
122
+
123
+ - Страница настроек.
124
+ - Карточка сущности.
125
+ - Экран редактирования одной логической сущности.
126
+
127
+ ### Из чего обычно состоит
128
+
129
+ 1. Заголовок страницы, иногда редактируемый inline.
130
+ 2. Справа: основные действия.
131
+ 3. Опционально: tabs под заголовком.
132
+ 4. Одна или несколько подложек с контентом.
133
+ 5. Опционально: нижняя панель сохранения.
134
+
135
+ ### Типичный состав
136
+
137
+ - Текстовые блоки.
138
+ - Кнопки и ссылки.
139
+ - Поля формы.
140
+ - `Checkbox` и `Radio`.
141
+ - `Switch`.
142
+ - Небольшие встроенные таблицы.
143
+
144
+ Типичная компонентная основа:
145
+
146
+ - `UiField`, `UiTextbox`, `UiSelect`, `UiCheckbox`, `UiRadio`, `UiSwitch`, `UiRadioSwitch` для формы.
147
+ - `UiButton` и `UiLink` для действий внутри карточки.
148
+ - `UiTable` для компактных встроенных списков.
149
+
150
+ ### Когда нужны tabs
151
+
152
+ - Когда одна карточка делится на несколько крупных разделов.
153
+ - Когда переключение между разделами должно происходить без ухода на другой экран.
154
+ - Tabs рекомендуется использовать как верхнеуровневую навигацию по смысловым секциям, а не как замену каждому внутреннему блоку.
155
+
156
+ Типичная компонентная основа:
157
+
158
+ - `UiTabGroup` для управления активным разделом.
159
+ - `UiTab` для декларации вкладок, иконок, счетчиков и содержимого активной панели.
160
+
161
+ ### Когда нужна панель сохранения
162
+
163
+ - Когда на странице много редактируемых полей и важно явно отделить действия `Сохранить`, `Сохранить и выйти`, `Закрыть`, `Удалить`.
164
+ - Если каждый блок сохраняется отдельно, глобальная нижняя панель обычно не нужна.
165
+
166
+ Типичная компонентная основа:
167
+
168
+ - `UiButton` для основных действий в нижней панели.
169
+ - `UiToolbarButton` и `UiToolbarLink` для вторичных действий, если они должны выглядеть менее тяжело.
170
+
171
+ ## 2.1. Страница с несколькими колонками
172
+
173
+ Это частный случай страницы-карточки, когда контент логично раскладывается не только по вертикали, но и по горизонтали.
174
+
175
+ ### Когда использовать
176
+
177
+ - Карточка заказа.
178
+ - Карточка клиента.
179
+ - Страница просмотра товара.
180
+ - Любой экран, где рядом должны жить несколько смысловых секций.
181
+
182
+ ### Правила раскладки
183
+
184
+ - Контент всё равно живёт на подложках.
185
+ - Между соседними блоками держите примерно `24px`.
186
+ - Типовые раскладки колонок:
187
+ - `100%`;
188
+ - `50% / 50%`;
189
+ - `30% / 70%`.
190
+
191
+ Типичная компонентная основа:
192
+
193
+ - обычные form/content blocks на базе `UiField`, `UiTextbox`, `UiSelect`, `UiCheckbox`, `UiSwitch`;
194
+ - при необходимости небольшие встроенные `UiTable`;
195
+ - tabs по-прежнему остаются верхним уровнем и не заменяют сетку колонок.
196
+
197
+ ### Когда это уместно
198
+
199
+ - Когда блоки действительно связаны и их необходимо видеть рядом.
200
+ - Когда экран выигрывает от сравнения двух секций одновременно.
201
+
202
+ ### Признаки неудачной раскладки
203
+
204
+ - Если одна из колонок становится слишком длинной, а вторая остаётся почти пустой.
205
+ - Если блоки начинают требовать собственную внутреннюю сложную навигацию.
206
+
207
+ ## 3. Страница с collapse-блоками
208
+
209
+ ### Когда использовать
210
+
211
+ - Настройки, разбитые на смысловые группы.
212
+ - Большие формы, где не всё нужно держать открытым одновременно.
213
+ - Редактирование товара, рассылки и похожих сущностей с независимыми секциями.
214
+
215
+ ### Особенности
216
+
217
+ - Если на странице используются `Collapse`-блоки, дополнительная общая белая подложка вокруг них обычно не нужна.
218
+ - Если подложка видна только ради пары collapse-блоков, от неё следует отказаться.
219
+ - Обычно и общая нижняя панель сохранения не нужна, потому что каждый блок сохраняется отдельно.
220
+
221
+ ### Типичный состав
222
+
223
+ - Текст.
224
+ - Кнопки.
225
+ - Поля и контролы.
226
+ - Небольшие таблицы.
227
+
228
+ Типичная компонентная основа:
229
+
230
+ - `UiCollapseBox` и `UiCollapseGroup` для секций.
231
+ - Внутри секции: `UiField`, `UiTextbox`, `UiSelect`, `UiCheckbox`, `UiRadio`, `UiSwitch`, `UiButton`.
232
+ - При необходимости: компактные `UiTable`.
233
+
234
+ ### Ограничения
235
+
236
+ - Сложные большие таблицы.
237
+ - Другие collapse-блоки.
238
+ - Контент, разнесённый на две колонки по разным подложкам.
239
+
240
+ ## 4. Когда вместо страницы нужен `modal sidebar`
241
+
242
+ ### Когда использовать
243
+
244
+ - Небольшая форма “по месту”.
245
+ - Карточка маленькой сущности с ограниченным числом полей.
246
+ - Дополнительная информация, которую не хочется разворачивать в отдельную страницу.
247
+ - Сценарии вроде задач, уведомлений, нового обращения, редактирования шага.
248
+
249
+ ### Размеры
250
+
251
+ - Часто используется одна из двух ширин: `720px` или `416px`.
252
+
253
+ ### Основные части
254
+
255
+ 1. Закреплённый `header`.
256
+ 2. Прокручиваемый контент.
257
+ 3. Закреплённый `footer`.
258
+
259
+ Типичная компонентная основа:
260
+
261
+ - `UiModalSidebar` как контейнер `modal sidebar`.
262
+ - Внутри: обычные form/content primitives из `v1-components`.
263
+ - В `footer`: `UiButton` для сохранения, закрытия и удаления.
264
+
265
+ ### Типичный состав
266
+
267
+ - Формы.
268
+ - Небольшие карточки.
269
+ - Небольшие таблицы.
270
+ - Вспомогательный текст.
271
+
272
+ ### Ограничения
273
+
274
+ - `Collapse`-блоки.
275
+ - Контент в двух колонках на разных подложках.
276
+ - Громоздкие, большие и сложные интерфейсы.
277
+
278
+ Если экран фактически превращается в полноценную страницу или большую предметную область, `modal sidebar` уже не является подходящим выбором.
279
+
280
+ ## 5. Когда вместо страницы нужен `modal window`
281
+
282
+ ### Когда использовать
283
+
284
+ - Для отображения большой вспомогательной таблицы “по месту”.
285
+ - Для дополнительных настроек, когда `modal sidebar` уже не хватает.
286
+ - Для сценариев просмотра или выбора, не заслуживающих отдельного route-level экрана.
287
+
288
+ ### Размеры
289
+
290
+ - Обычный `modal window` шириной около `960px`.
291
+ - Или полноэкранный режим с внешними отступами по краям.
292
+
293
+ ### Основные части
294
+
295
+ 1. Закреплённый `header`.
296
+ 2. Основной контент.
297
+ 3. Закреплённый `footer`.
298
+
299
+ Типичная компонентная основа:
300
+
301
+ - `UiModalWindow` и `UiModalWindowSurface` как базовый контейнер модального сценария.
302
+ - Внутри: `UiTable`, `UiField`, `UiTextbox`, `UiSelect`, `UiButton`, а при необходимости `UiTabGroup`.
303
+
304
+ ### Практические правила
305
+
306
+ - В целом контент в `modal window` может быть любым, но чаще всего там живут таблицы и компактные формы.
307
+ - Если в `modal window` есть таблица, её обычно растягивают на всю ширину окна.
308
+ - Если `modal window` делится на крупные разделы, вместо усложнённого header рекомендуется использовать большие tabs.
309
+
310
+ ## Выбор паттерна
311
+
312
+ ### Полноценная страница подходит, если
313
+
314
+ - сценарий является самостоятельным рабочим экраном;
315
+ - у экрана есть собственная навигационная ценность;
316
+ - контента много, и ему нужен нормальный вертикальный ритм;
317
+ - ожидается несколько крупных смысловых секций.
318
+
319
+ ### `Modal sidebar` подходит, если
320
+
321
+ - это быстрый побочный сценарий;
322
+ - форма маленькая или средняя;
323
+ - пользователь должен остаться в контексте текущего экрана.
324
+
325
+ ### `Modal window` подходит, если
326
+
327
+ - требуется показать большой вспомогательный контент по месту;
328
+ - требуется быстро завершить отдельный шаг без перехода на полноценную страницу;
329
+ - `modal sidebar` уже тесен, но полноценная страница всё ещё избыточна.
330
+
331
+ ## Сопоставление с `embed-ui`
332
+
333
+ Ниже не жёсткий contract, а практичное соответствие layout-паттернов библиотечным примитивам:
334
+
335
+ - заголовок страницы: `UiPageHeader`;
336
+ - верхние actions: `UiButton`, `UiToolbarButton`, `UiToolbarLink`;
337
+ - tabs: `UiTabGroup` и `UiTab`;
338
+ - collapse-группы: `UiCollapseBox`, `UiCollapseGroup`;
339
+ - фильтры и формы: `UiField`, `UiTextbox`, `UiSelect`, `UiCheckbox`, `UiRadio`, `UiSwitch`, `UiRadioSwitch`;
340
+ - таблицы: `UiTable` и связанные table primitives;
341
+ - `modal sidebar`: `UiModalSidebar`;
342
+ - `modal window`: `UiModalWindow` и `UiModalWindowSurface`.
343
+
344
+ ## Чеклист перед реализацией страницы
345
+
346
+ Перед тем как собирать новый page runner, полезно ответить на несколько вопросов:
347
+
348
+ 1. Это действительно страница, а не `modal sidebar` и не `modal window`?
349
+ 2. Это список, карточка, карточка с колонками или collapse-настройки?
350
+ 3. Нужны ли tabs, или блоки следует оставить на одном полотне?
351
+ 4. Нужна ли общая панель сохранения, или каждый блок должен сохраняться отдельно?
352
+ 5. Не перегружен ли экран количеством primary-действий?
353
+ 6. Держатся ли отступы и расстояния сетки `4px`?
354
+
355
+ Если на эти вопросы нет уверенного ответа, сначала следует выбрать подходящий layout-паттерн,
356
+ а уже потом собирать компоненты и wiring через `v1-endpoint`.
357
+
358
+ ---
359
+
360
+ Примечание: все упоминаемые в этом документе компоненты вида `Ui*` относятся к пакету
361
+ `@retailcrm/embed-ui-v1-components`. Термины `modal sidebar`, `modal window`, tabs, collapse-группы,
362
+ таблицы, поля и другие layout-примитивы в этом гайде привязаны именно к публичным компонентам
363
+ из `v1-components`, а не к произвольной внутренней терминологии проекта.
@@ -0,0 +1,71 @@
1
+ # Меню и точки входа страниц
2
+
3
+ Этот справочник описывает, как документировать меню и пункты навигации, из которых запускаются
4
+ remote-страницы расширения.
5
+
6
+ Важно: `v1-endpoint` не экспортирует типизированный registry CRM-меню. Пакет отвечает за запуск
7
+ страницы по `code`, а список меню, подпунктов и их видимость задаются на стороне host/manifest
8
+ конкретного расширения.
9
+
10
+ ## Термины
11
+
12
+ - `menu placement` — зона CRM, куда host добавляет пункт навигации.
13
+ - `menu item` — конкретный пункт меню, который видит пользователь.
14
+ - `page code` — стабильный код remote-страницы, который host передаёт в `definePageRunner`.
15
+ - `route` — маршрут CRM или route name, через который host открывает страницу.
16
+
17
+ `menu item` не равен `target`. Меню открывает полноценную remote-страницу по `code`, а `target`
18
+ используется для виджетов, которые встраиваются внутрь уже существующей CRM-страницы.
19
+
20
+ ## Что нужно фиксировать в справочнике проекта
21
+
22
+ Для каждого пункта меню указывайте:
23
+
24
+ | Поле | Что означает |
25
+ | --- | --- |
26
+ | `placement` | Зона CRM, где находится пункт меню. |
27
+ | `item code` | Стабильный код пункта меню в host/manifest. |
28
+ | `label` | Пользовательское название пункта меню. |
29
+ | `page code` | Код remote-страницы, который получит `definePageRunner`. |
30
+ | `route` | Имя или путь CRM-маршрута, если пункт открывается через host routing. |
31
+ | `visibility` | Условия показа: права, настройки, тариф, доступность фичи. |
32
+
33
+ ## Пример справочника меню
34
+
35
+ Это пример формата. Конкретные `placement`, `item code` и `route` нужно брать из host/manifest
36
+ расширения.
37
+
38
+ | Placement | Item code | Label | Page code | Route | Когда использовать |
39
+ | --- | --- | --- | --- | --- | --- |
40
+ | `main` | `orders-dashboard` | Заказы | `orders-dashboard` | `embed.page.orders_dashboard` | Основной пункт навигации расширения. |
41
+ | `settings` | `integration-settings` | Настройки интеграции | `integration-settings` | `embed.page.integration_settings` | Страница настроек, доступная администраторам. |
42
+ | `customer/card:actions` | `customer-tools` | Инструменты клиента | `customer-tools` | `embed.page.customer_tools` | Пункт в контексте карточки клиента. |
43
+
44
+ ## Связь с `definePageRunner`
45
+
46
+ `page code` из справочника меню должен совпадать с ключом runner:
47
+
48
+ ```ts
49
+ import { definePageRunner } from '@retailcrm/embed-ui-v1-endpoint/remote'
50
+
51
+ import CustomerToolsPage from './pages/CustomerToolsPage.vue'
52
+ import IntegrationSettingsPage from './pages/IntegrationSettingsPage.vue'
53
+ import OrdersDashboardPage from './pages/OrdersDashboardPage.vue'
54
+
55
+ const pageRunner = definePageRunner({
56
+ 'customer-tools': definePageRunner(CustomerToolsPage),
57
+ 'integration-settings': definePageRunner(IntegrationSettingsPage),
58
+ 'orders-dashboard': definePageRunner(OrdersDashboardPage),
59
+ })
60
+ ```
61
+
62
+ Если host откроет страницу с `code`, которого нет в runner map, `definePageRunner` запишет warning
63
+ в консоль и не смонтирует страницу.
64
+
65
+ Читайте также:
66
+
67
+ - [`page-routes`](./page-routes.md) — как описывать page `code` и CRM route.
68
+ - [`definePageRunner`](./define-page-runner.md) — как remote-страница получает `code`.
69
+ - [`targets`](./targets.md) — точки встраивания виджетов, не пунктов меню.
70
+ - [`layout`](./layout.md) — когда делать полноценную страницу, а когда `modal sidebar` или `modal window`.
71
+ - [`UiMenuItem`](../../v1-components/docs/profiles/UiMenuItem.yml) — компонент строки меню внутри UI расширения.
@@ -0,0 +1,82 @@
1
+ # Роуты страниц
2
+
3
+ Remote-страницы в `v1-endpoint` запускаются по `code`. Этот `code` приходит от host и пробрасывается
4
+ в компонент через `definePageRunner`.
5
+
6
+ `v1-endpoint` не задаёт фиксированный список page routes. Список страниц и CRM-маршрутов принадлежит
7
+ host/manifest конкретного расширения, поэтому его нужно хранить в справочнике проекта рядом с кодом
8
+ расширения.
9
+
10
+ ## Что такое `page code`
11
+
12
+ `page code` — стабильный идентификатор remote-страницы внутри расширения. Он отвечает на вопрос:
13
+ «какую страницу расширения нужно смонтировать?».
14
+
15
+ Пример:
16
+
17
+ ```ts
18
+ import { definePageRunner } from '@retailcrm/embed-ui-v1-endpoint/remote'
19
+
20
+ import OrdersDashboardPage from './pages/OrdersDashboardPage.vue'
21
+
22
+ const pageRunner = definePageRunner({
23
+ 'orders-dashboard': definePageRunner(OrdersDashboardPage),
24
+ })
25
+ ```
26
+
27
+ Когда host запускает страницу с `code: 'orders-dashboard'`, компонент `OrdersDashboardPage` получает
28
+ этот код как prop:
29
+
30
+ ```vue
31
+ <script setup lang="ts">
32
+ defineProps<{
33
+ code: string;
34
+ }>()
35
+ </script>
36
+ ```
37
+
38
+ ## Что такое `route`
39
+
40
+ `route` — CRM-маршрут или route name, через который host открывает страницу. В remote-коде route
41
+ может использоваться для переходов через `HostApi.goTo(route, params)`.
42
+
43
+ Данные Symfony JS router доступны в контексте `settings` в поле `system.routing`. Это полезно,
44
+ когда нужно проверить доступные route names или собрать ссылку тем же routing data, что отдаёт host.
45
+
46
+ ```ts
47
+ import { useContext as useSettingsContext } from '@retailcrm/embed-ui-v1-contexts/remote/settings'
48
+
49
+ const settings = useSettingsContext()
50
+
51
+ console.log(settings['system.routing'].routes)
52
+ ```
53
+
54
+ ## Пример справочника роутов страниц
55
+
56
+ Это пример формата. Конкретные `code`, `route` и параметры нужно брать из host/manifest расширения.
57
+
58
+ | Page code | Route | Params | Открывается из | Компонент |
59
+ | --- | --- | --- | --- | --- |
60
+ | `orders-dashboard` | `embed.page.orders_dashboard` | `{}` | `main / orders-dashboard` | `OrdersDashboardPage.vue` |
61
+ | `integration-settings` | `embed.page.integration_settings` | `{}` | `settings / integration-settings` | `IntegrationSettingsPage.vue` |
62
+ | `customer-tools` | `embed.page.customer_tools` | `{ customerId }` | `customer/card:actions / customer-tools` | `CustomerToolsPage.vue` |
63
+
64
+ ## Переход на CRM route
65
+
66
+ Если странице нужен переход на другой CRM-маршрут, используйте host API, а не прямую сборку URL.
67
+ Способ получения `HostApi` зависит от host-интеграции проекта, но сам публичный контракт выглядит так:
68
+
69
+ ```ts
70
+ import type { HostApi } from '@retailcrm/embed-ui-v1-types/host'
71
+
72
+ const openSettings = (host: HostApi) => {
73
+ host.goTo('embed.page.integration_settings')
74
+ }
75
+ ```
76
+
77
+ Читайте также:
78
+
79
+ - [`menu-placements`](./menu-placements.md) — как связать пункт меню, page `code` и route.
80
+ - [`definePageRunner`](./define-page-runner.md) — как page `code` попадает в компонент.
81
+ - [`settings context`](../../v1-contexts/docs/ru/CONCEPT.md) — общий принцип работы контекстов.
82
+ - [`HostApi`](../../v1-types/host.d.ts) — публичный тип host API с `goTo`.
@@ -0,0 +1,37 @@
1
+ # `runEndpoint`
2
+
3
+ `runEndpoint` — shortcut для worker entry:
4
+ он вызывает `createEndpoint(runner, self as Worker)`.
5
+
6
+ ## Сигнатура
7
+
8
+ ```ts
9
+ runEndpoint(runner: Runner): Endpoint<RemoteApi>
10
+ ```
11
+
12
+ ## Базовый сценарий
13
+
14
+ ```ts
15
+ // endpoint.worker.ts
16
+ import { defineRunner, runEndpoint } from '@retailcrm/embed-ui-v1-endpoint/remote'
17
+
18
+ import OrdersPage from './pages/OrdersPage.vue'
19
+ import OrderWidget from './widgets/OrderWidget.vue'
20
+
21
+ const runner = defineRunner({
22
+ pages: [OrdersPage],
23
+ widgets: [OrderWidget],
24
+ })
25
+
26
+ runEndpoint(runner)
27
+ ```
28
+
29
+ ## Когда использовать
30
+
31
+ - Почти всегда, если endpoint запускается как web worker.
32
+ - Когда не нужен ручной контроль над messenger/transport.
33
+
34
+ ## Когда лучше `createEndpoint`
35
+
36
+ - Когда transport создаётся не от `self` worker.
37
+ - Когда у вас кастомный runtime/bridge между host и remote.
@@ -0,0 +1,111 @@
1
+ # `targets` и `defineTarget`
2
+
3
+ Модуль `@retailcrm/embed-ui-v1-endpoint/common` содержит
4
+ типизированные target-идентификаторы и helper для объявления целей.
5
+
6
+ ## Что экспортируется
7
+
8
+ - `targets` — словарь встроенных target-конфигураций.
9
+ - `TargetName` — union всех ключей `targets`.
10
+ - `defineTarget(id, contexts)` — helper для объявления target с типами контекстов.
11
+
12
+ ## Что такое `target` и `context`
13
+
14
+ `target` — это идентификатор места встраивания виджета в интерфейсе CRM. Он отвечает на вопрос:
15
+ «куда CRM сейчас монтирует remote-виджет?». Например, `order/card:common.before` означает конкретную
16
+ точку встраивания на карточке заказа.
17
+
18
+ `context` — это набор реактивных данных, доступных виджету в этом месте. Он отвечает на вопрос:
19
+ «какие данные CRM можно читать или менять из этого виджета?». Список контекстов задаётся в
20
+ `targets[target].contexts`.
21
+
22
+ `target` не является данными заказа, клиента или пользователя. Это только место встраивания.
23
+ Данные нужно брать из контекстов, которые привязаны к этому `target`.
24
+
25
+ ## Примеры встроенных `target`
26
+
27
+ | Target | Где встраивается | Доступные контексты |
28
+ | --- | --- | --- |
29
+ | `order/card:common.before` | Карточка заказа, перед блоком общей информации | `order/card`, `user/current`, `settings` |
30
+ | `order/card:customer.phone` | Карточка заказа, поле телефона клиента | `order/card`, `user/current`, `settings` |
31
+ | `customer/card:phone` | Карточка клиента, телефонный блок | `customer/card`, `customer/card:phone`, `user/current`, `settings` |
32
+ | `order/mg:list.before` | Карточка заказа, перед списком в multi-goods блоке | `order/card`, `order/card:settings`, `user/current`, `settings` |
33
+
34
+ ## Проверка контекстов для `target`
35
+
36
+ ```ts
37
+ import { targets } from '@retailcrm/embed-ui-v1-endpoint/common'
38
+
39
+ const target = targets['customer/card:phone']
40
+
41
+ console.log(target.contexts)
42
+ // ['customer/card', 'customer/card:phone', 'user/current', 'settings']
43
+ ```
44
+
45
+ Если виджет монтируется в `customer/card:phone`, можно использовать контексты клиента, телефонного
46
+ блока, текущего пользователя и настроек. Контексты заказа в этой точке встраивания использовать не нужно.
47
+
48
+ ## Пример использования `TargetName`
49
+
50
+ ```ts
51
+ import type { TargetName } from '@retailcrm/embed-ui-v1-endpoint/common'
52
+
53
+ const selectedTarget: TargetName = 'order/card:common.before'
54
+ ```
55
+
56
+ ## Пример виджета с `target` и контекстами
57
+
58
+ ```vue
59
+ <script setup lang="ts">
60
+ import type { TargetName } from '@retailcrm/embed-ui-v1-endpoint/common'
61
+
62
+ import { useContext as useOrderContext } from '@retailcrm/embed-ui-v1-contexts/remote/order/card'
63
+ import { useContext as useSettingsContext } from '@retailcrm/embed-ui-v1-contexts/remote/settings'
64
+ import { useContext as useUserContext } from '@retailcrm/embed-ui-v1-contexts/remote/user/current'
65
+
66
+ defineProps<{
67
+ target: TargetName;
68
+ }>()
69
+
70
+ const order = useOrderContext()
71
+ const settings = useSettingsContext()
72
+ const user = useUserContext()
73
+ </script>
74
+ ```
75
+
76
+ Такой набор контекстов подходит для `order/card:common.before`, потому что этот `target` объявлен с
77
+ контекстами `order/card`, `user/current` и `settings`.
78
+
79
+ Для `customer/card:phone` набор импортов будет другим:
80
+
81
+ ```ts
82
+ import { useContext as useCustomerContext } from '@retailcrm/embed-ui-v1-contexts/remote/customer/card'
83
+ import { useContext as usePhoneContext } from '@retailcrm/embed-ui-v1-contexts/remote/customer/card-phone'
84
+ import { useContext as useSettingsContext } from '@retailcrm/embed-ui-v1-contexts/remote/settings'
85
+ import { useContext as useUserContext } from '@retailcrm/embed-ui-v1-contexts/remote/user/current'
86
+ ```
87
+
88
+ ## Пример `defineTarget`
89
+
90
+ ```ts
91
+ import { defineTarget } from '@retailcrm/embed-ui-v1-endpoint/common'
92
+
93
+ const customTarget = defineTarget('order/card:custom.after', [
94
+ 'order/card',
95
+ 'user/current',
96
+ 'settings',
97
+ ] as const)
98
+ ```
99
+
100
+ ## Практический совет
101
+
102
+ Если раннер маппится по `target`, старайтесь использовать ключи из `TargetName`.
103
+ Это снижает риск опечаток и помогает IDE/TS сразу показать несовместимые значения.
104
+
105
+ Читайте также:
106
+
107
+ - [`defineWidgetRunner`](./define-widget-runner.md) — как `target` попадает в remote-компонент.
108
+ - [`menu-placements`](./menu-placements.md) — чем пункты меню для страниц отличаются от widget `target`.
109
+ - [`page-routes`](./page-routes.md) — как описывать page `code` и CRM route.
110
+ - [`CONCEPT`](../../v1-contexts/docs/ru/CONCEPT.md) — общий принцип работы контекстов.
111
+ - [`CUSTOM`](../../v1-contexts/docs/ru/CUSTOM.md) — пользовательский контекст для custom fields.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@retailcrm/embed-ui-v1-endpoint",
3
3
  "type": "module",
4
- "version": "0.9.18",
4
+ "version": "0.9.21",
5
5
  "description": "Endpoint API for integrations in RetailCRM",
6
6
  "license": "MIT",
7
7
  "author": "RetailDriverLLC <integration@retailcrm.ru>",
@@ -67,6 +67,7 @@
67
67
  },
68
68
  "files": [
69
69
  "dist",
70
+ "docs",
70
71
  "README.md"
71
72
  ],
72
73
  "scripts": {
@@ -78,27 +79,27 @@
78
79
  },
79
80
  "peerDependencies": {
80
81
  "@remote-ui/rpc": "^1.4",
81
- "@retailcrm/embed-ui-v1-contexts": "^0.9.18",
82
- "@retailcrm/embed-ui-v1-types": "^0.9.18",
82
+ "@retailcrm/embed-ui-v1-contexts": "^0.9.21",
83
+ "@retailcrm/embed-ui-v1-types": "^0.9.21",
83
84
  "pinia": "^2.2",
84
85
  "vue": "^3.5"
85
86
  },
86
87
  "dependencies": {
87
88
  "@remote-ui/rpc": "^1.4.7",
88
- "@retailcrm/embed-ui-v1-components": "^0.9.18",
89
- "@retailcrm/embed-ui-v1-contexts": "^0.9.18",
90
- "@retailcrm/embed-ui-v1-types": "^0.9.18"
89
+ "@retailcrm/embed-ui-v1-components": "^0.9.21",
90
+ "@retailcrm/embed-ui-v1-contexts": "^0.9.21",
91
+ "@retailcrm/embed-ui-v1-types": "^0.9.21"
91
92
  },
92
93
  "devDependencies": {
93
94
  "@retailcrm/image-preview": "^1.0.2",
94
- "@vitest/browser": "4.0.15",
95
- "@vitest/browser-playwright": "4.0.15",
95
+ "@vitest/browser": "4.1.3",
96
+ "@vitest/browser-playwright": "4.1.3",
96
97
  "date-fns": "^4.1.0",
97
98
  "lodash.isequal": "^4.5.0",
98
99
  "playwright": "1.58.2",
99
- "vite": "^7.2.7",
100
+ "vite": "^7.3.2",
100
101
  "vite-plugin-dts": "^4.5.4",
101
- "vitest": "4.0.15"
102
+ "vitest": "4.1.3"
102
103
  },
103
104
  "publishConfig": {
104
105
  "access": "public"