@pgcorp/ui-kit 0.14.0 → 0.15.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.
Files changed (117) hide show
  1. package/README.md +330 -12
  2. package/dist/assets/src/components/layout/{SResizeHandle.vue_vue_type_style_index_0_scoped_13b9b27a_lang.css → SResizeHandle.vue_vue_type_style_index_0_scoped_dc65aba1_lang.css} +1 -1
  3. package/dist/assets/src/components/shared/_internal/SDialogSurface.css_vue_type_style_index_0_src_a108e8f1_scoped_a108e8f1_lang.css +2 -0
  4. package/dist/assets/src/components/shared/containers/{SCollapsiblePanel.css_vue_type_style_index_0_src_71824088_scoped_71824088_lang.css → SCollapsiblePanel.css_vue_type_style_index_0_src_db85261d_scoped_db85261d_lang.css} +1 -1
  5. package/dist/assets/src/components/shared/containers/{SOverlayHost.css_vue_type_style_index_0_src_8f9be94e_scoped_8f9be94e_lang.css → SOverlayHost.css_vue_type_style_index_0_src_09215628_scoped_09215628_lang.css} +1 -1
  6. package/dist/assets/src/components/shared/containers/{SPopover.css_vue_type_style_index_0_src_4091d007_scoped_4091d007_lang.css → SPopover.css_vue_type_style_index_0_src_a4f1f64a_scoped_a4f1f64a_lang.css} +1 -1
  7. package/dist/assets/src/components/shared/containers/SSidebarSection.css_vue_type_style_index_0_src_52396e42_scoped_52396e42_lang.css +2 -0
  8. package/dist/assets/src/components/shared/controls/SButton.css_vue_type_style_index_0_src_7a5acda9_scoped_7a5acda9_lang.css +1 -1
  9. package/dist/assets/src/components/shared/controls/SCheckbox.css_vue_type_style_index_0_src_2562f916_scoped_2562f916_lang.css +2 -0
  10. package/dist/assets/src/components/shared/controls/SCombobox.css_vue_type_style_index_0_src_d630c922_scoped_d630c922_lang.css +2 -0
  11. package/dist/assets/src/components/shared/controls/SContextMenu.css_vue_type_style_index_0_src_43b6e378_scoped_43b6e378_lang.css +1 -0
  12. package/dist/assets/src/components/shared/controls/{SDropdownMenu.css_vue_type_style_index_0_src_857e2649_scoped_857e2649_lang.css → SDropdownMenu.css_vue_type_style_index_0_src_40040ae5_scoped_40040ae5_lang.css} +1 -1
  13. package/dist/assets/src/components/shared/controls/{SMenuSurface.css_vue_type_style_index_0_src_84109ad9_scoped_84109ad9_lang.css → SMenuSurface.css_vue_type_style_index_0_src_d2b7d71b_scoped_d2b7d71b_lang.css} +1 -1
  14. package/dist/assets/src/components/shared/controls/SSelect.css_vue_type_style_index_0_src_d1ead6c0_scoped_d1ead6c0_lang.css +2 -0
  15. package/dist/assets/src/components/shared/data-display/SControlledToastViewport.css_vue_type_style_index_0_src_a69aa84b_scoped_a69aa84b_lang.css +2 -0
  16. package/dist/assets/src/components/shared/data-display/SScrollViewport.css_vue_type_style_index_0_src_d94fcf8c_scoped_d94fcf8c_lang.css +2 -0
  17. package/dist/assets/src/components/shared/data-display/STooltip.css_vue_type_style_index_0_src_true_lang.css +1 -1
  18. package/dist/assets/src/components/shared/data-display/_internal/STableFrame.css_vue_type_style_index_0_src_914bad28_scoped_914bad28_lang.css +2 -0
  19. package/dist/assets/src/components/shared/navigation/{SBottomNav.vue_vue_type_style_index_0_scoped_24be0f2b_lang.css → SBottomNav.vue_vue_type_style_index_0_scoped_68cf84b3_lang.css} +1 -1
  20. package/dist/src/components/layout/SResizeHandle.vue.js +2 -2
  21. package/dist/src/components/layout/SResizeHandle.vue_vue_type_script_setup_true_lang.js +4 -2
  22. package/dist/src/components/shared/_internal/SDialogSurface.vue.d.ts +9 -1
  23. package/dist/src/components/shared/_internal/SDialogSurface.vue.js +2 -2
  24. package/dist/src/components/shared/_internal/SDialogSurface.vue_vue_type_script_setup_true_lang.js +174 -82
  25. package/dist/src/components/shared/_internal/dialogSurface.d.ts +2 -0
  26. package/dist/src/components/shared/_internal/dialogSurface.js +30 -27
  27. package/dist/src/components/shared/_internal/dialogSurfaceMotion.d.ts +14 -0
  28. package/dist/src/components/shared/_internal/dialogSurfaceMotion.js +84 -0
  29. package/dist/src/components/shared/_internal/focusTrap.js +15 -13
  30. package/dist/src/components/shared/containers/SCollapsiblePanel.vue.d.ts +5 -0
  31. package/dist/src/components/shared/containers/SCollapsiblePanel.vue.js +2 -2
  32. package/dist/src/components/shared/containers/SCollapsiblePanel.vue_vue_type_script_setup_true_lang.js +123 -121
  33. package/dist/src/components/shared/containers/SDrawer.vue.d.ts +4 -4
  34. package/dist/src/components/shared/containers/SDrawer.vue.js +3 -6
  35. package/dist/src/components/shared/containers/SDrawer.vue_vue_type_script_setup_true_lang.js +104 -128
  36. package/dist/src/components/shared/containers/SModal.vue.d.ts +8 -8
  37. package/dist/src/components/shared/containers/SModal.vue.js +3 -6
  38. package/dist/src/components/shared/containers/SModal.vue_vue_type_script_setup_true_lang.js +87 -103
  39. package/dist/src/components/shared/containers/SOverlayHost.vue.js +2 -2
  40. package/dist/src/components/shared/containers/SOverlayHost.vue_vue_type_script_setup_true_lang.js +32 -31
  41. package/dist/src/components/shared/containers/SPanel.vue.d.ts +1 -1
  42. package/dist/src/components/shared/containers/SPopover.vue.js +2 -2
  43. package/dist/src/components/shared/containers/SPopover.vue_vue_type_script_setup_true_lang.js +225 -200
  44. package/dist/src/components/shared/containers/SSidebarSection.vue.js +2 -2
  45. package/dist/src/components/shared/containers/SSidebarSection.vue_vue_type_script_setup_true_lang.js +4 -3
  46. package/dist/src/components/shared/controls/SCheckbox.vue.js +2 -2
  47. package/dist/src/components/shared/controls/SCheckbox.vue_vue_type_script_setup_true_lang.js +13 -7
  48. package/dist/src/components/shared/controls/SCombobox.vue.js +2 -2
  49. package/dist/src/components/shared/controls/SCombobox.vue_vue_type_script_setup_true_lang.js +104 -94
  50. package/dist/src/components/shared/controls/SContextMenu.vue.js +2 -2
  51. package/dist/src/components/shared/controls/SContextMenu.vue_vue_type_script_setup_true_lang.js +86 -81
  52. package/dist/src/components/shared/controls/SDropdownMenu.vue.js +2 -2
  53. package/dist/src/components/shared/controls/SDropdownMenu.vue_vue_type_script_setup_true_lang.js +77 -72
  54. package/dist/src/components/shared/controls/SMenuSurface.vue.js +2 -2
  55. package/dist/src/components/shared/controls/SMenuSurface.vue_vue_type_script_setup_true_lang.js +81 -77
  56. package/dist/src/components/shared/controls/SSelect.vue.js +2 -2
  57. package/dist/src/components/shared/controls/SSelect.vue_vue_type_script_setup_true_lang.js +85 -77
  58. package/dist/src/components/shared/data-display/SControlledToastViewport.vue.js +2 -2
  59. package/dist/src/components/shared/data-display/SControlledToastViewport.vue_vue_type_script_setup_true_lang.js +58 -49
  60. package/dist/src/components/shared/data-display/SScrollViewport.vue.d.ts +10 -5
  61. package/dist/src/components/shared/data-display/SScrollViewport.vue.js +2 -2
  62. package/dist/src/components/shared/data-display/SScrollViewport.vue_vue_type_script_setup_true_lang.js +162 -111
  63. package/dist/src/components/shared/data-display/STooltip.vue_vue_type_script_setup_true_lang.js +276 -224
  64. package/dist/src/components/shared/data-display/_internal/STableFrame.vue.d.ts +3 -3
  65. package/dist/src/components/shared/data-display/_internal/STableFrame.vue.js +2 -2
  66. package/dist/src/components/shared/data-display/_internal/STableFrame.vue_vue_type_script_setup_true_lang.js +62 -46
  67. package/dist/src/components/shared/data-display/scrollViewport.d.ts +2 -0
  68. package/dist/src/components/shared/navigation/SBottomNav.vue.js +2 -2
  69. package/dist/src/components/shared/navigation/SBottomNav.vue_vue_type_script_setup_true_lang.js +3 -1
  70. package/dist/src/components/shared/navigation/STabList.vue.d.ts +1 -1
  71. package/dist/src/composables/useDialogOverlay.d.ts +12 -2
  72. package/dist/src/composables/useDialogOverlay.js +192 -119
  73. package/dist/src/internal/flatTree.d.ts +8 -0
  74. package/dist/src/internal/flatTree.js +23 -0
  75. package/dist/src/internal/focusNavigation.d.ts +3 -0
  76. package/dist/src/internal/focusNavigation.js +28 -15
  77. package/dist/src/internal/inertLease.d.ts +8 -0
  78. package/dist/src/internal/inertLease.js +33 -0
  79. package/dist/src/internal/layerStack.d.ts +40 -0
  80. package/dist/src/internal/layerStack.js +301 -94
  81. package/dist/src/internal/nativeFocus.d.ts +4 -0
  82. package/dist/src/internal/nativeFocus.js +12 -0
  83. package/dist/src/internal/overlayPortal.d.ts +17 -0
  84. package/dist/src/internal/overlayPortal.js +23 -0
  85. package/dist/src/internal/semanticSizing.d.ts +2 -2
  86. package/dist/src/internal/semanticSizing.js +2 -1
  87. package/dist/src/internal/useMenuLayer.d.ts +1 -0
  88. package/dist/src/internal/useMenuLayer.js +63 -57
  89. package/dist/src/internal/viewportModality.d.ts +27 -0
  90. package/dist/src/internal/viewportModality.js +132 -0
  91. package/dist/typography.js +17 -62
  92. package/docs/overlay-lifecycle.md +195 -0
  93. package/docs/public-api.md +141 -4
  94. package/docs/resize.md +34 -5
  95. package/docs/scroll-viewport.md +30 -10
  96. package/docs/theming.md +42 -8
  97. package/package.json +1 -1
  98. package/src/components/shared/_internal/SDialogSurface.css +185 -40
  99. package/src/components/shared/controls/SButton.css +0 -3
  100. package/src/components/shared/controls/SCheckbox.css +19 -7
  101. package/src/components/shared/data-display/SControlledToastViewport.css +2 -10
  102. package/src/components/shared/data-display/SScrollViewport.css +17 -1
  103. package/src/components/shared/data-display/STooltip.css +1 -1
  104. package/src/components/shared/data-display/_internal/STableFrame.css +1 -5
  105. package/dist/assets/src/components/shared/_internal/SDialogSurface.css_vue_type_style_index_0_src_0e58afdc_scoped_0e58afdc_lang.css +0 -2
  106. package/dist/assets/src/components/shared/containers/SDrawer.css_vue_type_style_index_0_src_61cf20dd_scoped_61cf20dd_lang.css +0 -2
  107. package/dist/assets/src/components/shared/containers/SModal.css_vue_type_style_index_0_src_42e1bd5f_scoped_42e1bd5f_lang.css +0 -2
  108. package/dist/assets/src/components/shared/containers/SSidebarSection.css_vue_type_style_index_0_src_21f71ae8_scoped_21f71ae8_lang.css +0 -2
  109. package/dist/assets/src/components/shared/controls/SCheckbox.css_vue_type_style_index_0_src_55d4826e_scoped_55d4826e_lang.css +0 -2
  110. package/dist/assets/src/components/shared/controls/SCombobox.css_vue_type_style_index_0_src_537f5744_scoped_537f5744_lang.css +0 -2
  111. package/dist/assets/src/components/shared/controls/SContextMenu.css_vue_type_style_index_0_src_03921269_scoped_03921269_lang.css +0 -1
  112. package/dist/assets/src/components/shared/controls/SSelect.css_vue_type_style_index_0_src_df3d6688_scoped_df3d6688_lang.css +0 -2
  113. package/dist/assets/src/components/shared/data-display/SControlledToastViewport.css_vue_type_style_index_0_src_5864b6c6_scoped_5864b6c6_lang.css +0 -2
  114. package/dist/assets/src/components/shared/data-display/SScrollViewport.css_vue_type_style_index_0_src_e5d11342_scoped_e5d11342_lang.css +0 -2
  115. package/dist/assets/src/components/shared/data-display/_internal/STableFrame.css_vue_type_style_index_0_src_52b5825e_scoped_52b5825e_lang.css +0 -2
  116. package/src/components/shared/containers/SDrawer.css +0 -87
  117. package/src/components/shared/containers/SModal.css +0 -27
@@ -0,0 +1,195 @@
1
+ # Жизненный цикл overlay / Overlay lifecycle
2
+
3
+ ## Русский
4
+
5
+ `SModal` и viewport `SDrawer` используют один native dialog owner. Публичные
6
+ props, slots и events этих компонентов не требуют consumer DOM API.
7
+ Составные окна, меню, поля выбора, popover и tooltip используют принадлежащий
8
+ окну portal. Global toast остаётся background notification owner; modal-local
9
+ controlled toast принадлежит окну, в котором он объявлен.
10
+
11
+ ### Custom leaf через `useDialogOverlay`
12
+
13
+ Контракт `composables/useDialogOverlay` предназначен для leaf, который владеет
14
+ полной semantic, visual и accessibility поверхностью своего диалога.
15
+ Приложения используют готовые `SModal`/`SDrawer`, если их props и slots выражают
16
+ нужную композицию.
17
+
18
+ Миграция `0.15` относится к собственным viewport leaf, использующим
19
+ экспортируемый composable: semantic surface — `HTMLDialogElement`, а portal —
20
+ его непосредственный дочерний HTMLElement. Публичные props, slots и events
21
+ готовых `SModal`/`SDrawer` сохраняются; contained leaf сохраняет HTMLElement
22
+ surface и `SOverlayHost`.
23
+
24
+ Для viewport leaf:
25
+
26
+ 1. Передать controlled boolean `open`, boolean `closable` и callback
27
+ `requestClose`. Callback формирует намерение, а состояние задаёт parent.
28
+ 2. Рендерить один подключённый `HTMLDialogElement` через `renderOpen` и связать
29
+ его ref с `setPanelElement`. Не задавать `open`, `autofocus` или `tabindex`
30
+ semantic root. Lifecycle native methods принадлежит composable.
31
+ 3. Создать прямой дочерний HTMLElement portal, связать его ref с
32
+ `setPortalElement` и применить `portalStyle`. Portal находится вне transform,
33
+ overflow clipping и visual scrolling frame.
34
+ 4. Связать visible frame с `setDismissalElement`; применить `backdropStyle` и
35
+ `surfaceStyle` к соответствующим принадлежащим leaf visual surfaces.
36
+ 5. Установить доступное имя диалога через heading/`aria-labelledby` либо
37
+ `aria-label`. Footer/submit completion запрашивает controlled close.
38
+
39
+ ### Исполняемый typed binding
40
+
41
+ Полная композиция — export `NativeOwnerContract` в
42
+ [`SModal.stories.ts`](https://github.com/pglyanenko/sputnig/blob/master/packages/ui-kit/src/components/shared/containers/SModal.stories.ts).
43
+ В Storybook она называется **«Публичный владелец нативного диалога»**,
44
+ story ID — `ui-kit-контейнеры-smodal--native-owner-contract`.
45
+ Тип `DialogOverlayController` импортируется из публичного
46
+ `@pgcorp/ui-kit/composables/useDialogOverlay`; native ref binding типизирован
47
+ как `DialogOverlayController['setPanelElement']`.
48
+
49
+ | Владелец в story | Binding | Контракт |
50
+ | --- | --- | --- |
51
+ | Родительское состояние | `open`, `closable`, `requestClose` | Callback формирует close intent; parent подтверждает его изменением `open` |
52
+ | Native root | `controller.renderOpen.value`, `bindSubject` → `controller.setPanelElement` | Единственный подключённый `<dialog>`; root без `open`, `autofocus` и `tabindex` |
53
+ | Доступное имя | `aria-labelledby="native-owner-title"`, `<h2 id="native-owner-title">` | Видимый heading именует semantic root |
54
+ | Backdrop | `controller.backdropStyle.value` | Собственная поверхность leaf, `aria-hidden="true"`, отдельная от content |
55
+ | Контейнер размещения и прокрутки | `controller.surfaceStyle.value` | Paint band visual frame; portal не является потомком scroll/transform owner |
56
+ | Видимая frame | `controller.setDismissalElement` | Точная граница pointer dismissal; semantic focus scope остаётся native root |
57
+ | Native portal | `controller.setPortalElement`, `controller.portalStyle.value` | Прямой дочерний HTMLElement dialog, sibling visual surfaces, `display: contents` |
58
+ | Кнопка завершения | `SButton` → `requestClose` | Обычное UI-kit действие без native `dialog.close()` |
59
+ | Явное освобождение | `controller.dispose()` | Retirement без запроса возврата focus |
60
+
61
+ В source приведены полные стили semantic root, backdrop, positioner и frame,
62
+ включая focus-visible frame. Native methods вызываются только shared owner.
63
+ Кнопки изменения admission, `beforetoggle` probes и неподдержанное завершение
64
+ формы — диагностические действия regression story, а не требования к consumer.
65
+ Этот же export исполняется в
66
+ [`ui-kit-native-dialog-isolation.visual.spec.ts`](https://github.com/pglyanenko/sputnig/blob/master/lvs/frontend/tests/storybook/ui-kit-native-dialog-isolation.visual.spec.ts):
67
+ проверяются подтверждённое закрытие, no-ACK, `closable=false`, explicit dispose,
68
+ отменённый focus intent, reentrant retirement и сохранение source attributes в
69
+ Chromium, Firefox и WebKit.
70
+
71
+ `opened` вызывается после подтверждённого initial focus claim самого semantic
72
+ root. Initial controlled child menu/popover имеет самостоятельного focus owner;
73
+ его доступность не требует initial focus permission родителя.
74
+ `dispose()` явно освобождает cycle без запроса возврата фокуса. Обычный unmount
75
+ использует shared guarded return к доступному launcher, захваченному из
76
+ фактического native focus. Pointer activation сама по себе не гарантирует
77
+ фокус кнопки во всех браузерах. Скрытый retained dock
78
+ отменяет admission и не возвращает focus скрытому trigger. Возврат видимости
79
+ не открывает отменённое окно: требуется новое явное изменение `open` с `false`
80
+ на `true`. Содержимое retained dock сохраняет свой экземпляр и состояние.
81
+
82
+ `scope="contained"` сохраняет HTMLElement surface внутри exact `SOverlayHost`
83
+ portal и локальный HTML inert lease. Native `showModal()` в этом режиме не
84
+ вызывается; внешние controls остаются доступными.
85
+
86
+ ### Граница lifecycle
87
+
88
+ Consumer не вызывает native `showModal()`/`close()`, не меняет `open` attribute
89
+ и не использует `form method="dialog"` как обход controlled состояния.
90
+ Для завершения формы предусмотрены обычный submit и явное намерение parent.
91
+ Уже отменённый submit не создаёт close intent. Native `cancel` не закрывает
92
+ controlled окно без обновления `open`; `closable=false` запрещает dismissal.
93
+
94
+ Native Document blocking охватывает HTML, SVG, MathML и background sources
95
+ внутри closed shadow root. Открытый дочерний native dialog изолирует предыдущие
96
+ native owners и их portal-содержимое на весь свой lifecycle. Их временные inert
97
+ leases освобождаются после native close до shared guarded focus return;
98
+ исходные атрибуты и независимые изменения host сохраняются.
99
+ Сторонний native modal внутри opaque closed shadow
100
+ root не входит в поддерживаемую nesting-композицию без exact host-owner
101
+ capability. Прямое вмешательство host в принадлежащий cycle или его временные
102
+ modality guards нарушает owner boundary.
103
+
104
+ ### Браузерная проверка
105
+
106
+ `make -C lvs frontend-browser-install` устанавливает browser revisions из
107
+ зафиксированного Playwright workspace. Команда не запускает quality gates.
108
+ `LVS_STORYBOOK_NATIVE_DIALOG_ENGINES=1 make -C lvs check-frontend-storybook-proof
109
+ PLAYWRIGHT_STORYBOOK_ARGS="tests/storybook/ui-kit-native-dialog-isolation.visual.spec.ts"`
110
+ проверяет native lifecycle в Chromium, Firefox и WebKit.
111
+
112
+ ## English
113
+
114
+ `SModal` and viewport `SDrawer` share one native dialog owner. Their public
115
+ props, slots and events require no consumer DOM lifecycle calls. Owned floating
116
+ surfaces use the dialog's portal; global notifications remain background-owned.
117
+
118
+ The `0.15` migration applies to custom viewport leaves using the exported
119
+ composable: the semantic surface is an `HTMLDialogElement`, and its portal is a
120
+ direct-child HTMLElement. The ready-made `SModal`/`SDrawer` retain their public
121
+ props, slots, and events. Contained leaves retain their HTMLElement surface and
122
+ `SOverlayHost` contract.
123
+
124
+ Custom `useDialogOverlay` viewport leaves provide one connected
125
+ `HTMLDialogElement` through `setPanelElement`, an exact direct-child portal
126
+ through `setPortalElement`, and the visible dismissal frame through
127
+ `setDismissalElement`. Render from `renderOpen`; apply the controller's
128
+ `backdropStyle`, `surfaceStyle` and `portalStyle` to their exact visual owners.
129
+ The semantic root has an accessible name and no `open`, `autofocus` or
130
+ `tabindex` assignment. Controlled `open` owns the lifecycle; `requestClose`
131
+ is an intent, not permission to mutate native state.
132
+
133
+ ### Executable typed binding
134
+
135
+ The complete composition is the `NativeOwnerContract` export in
136
+ [`SModal.stories.ts`](https://github.com/pglyanenko/sputnig/blob/master/packages/ui-kit/src/components/shared/containers/SModal.stories.ts).
137
+ Its Storybook name is **«Публичный владелец нативного диалога»** and its story ID
138
+ is `ui-kit-контейнеры-smodal--native-owner-contract`. `DialogOverlayController`
139
+ is imported from the public `@pgcorp/ui-kit/composables/useDialogOverlay`; the
140
+ native ref binding uses `DialogOverlayController['setPanelElement']`.
141
+
142
+ | Story owner | Binding | Contract |
143
+ | --- | --- | --- |
144
+ | Parent state | `open`, `closable`, `requestClose` | The callback produces a close intent; the parent acknowledges it by updating `open` |
145
+ | Native root | `controller.renderOpen.value`, `bindSubject` → `controller.setPanelElement` | One connected `<dialog>` without `open`, `autofocus`, or `tabindex` assignments |
146
+ | Accessible name | `aria-labelledby="native-owner-title"`, `<h2 id="native-owner-title">` | The visible heading names the semantic root |
147
+ | Backdrop | `controller.backdropStyle.value` | An owned leaf surface with `aria-hidden="true"`, separate from content |
148
+ | Placement and scrolling container | `controller.surfaceStyle.value` | The visual frame's paint band; the portal is outside scroll/transform ownership |
149
+ | Visible frame | `controller.setDismissalElement` | The exact pointer dismissal boundary; native root retains semantic focus scope |
150
+ | Native portal | `controller.setPortalElement`, `controller.portalStyle.value` | A direct-child HTMLElement dialog portal, sibling to visual surfaces, with `display: contents` |
151
+ | Completion button | `SButton` → `requestClose` | A regular UI-kit action without native `dialog.close()` |
152
+ | Explicit disposal | `controller.dispose()` | Retirement without a focus-return request |
153
+
154
+ The source includes complete semantic-root, backdrop, positioner, and frame
155
+ styles, including the focus-visible frame. Only the shared owner calls native
156
+ methods. Admission controls, `beforetoggle` probes, and unsupported form
157
+ completion are regression-story diagnostics, not consumer requirements.
158
+ The same export is exercised by
159
+ [`ui-kit-native-dialog-isolation.visual.spec.ts`](https://github.com/pglyanenko/sputnig/blob/master/lvs/frontend/tests/storybook/ui-kit-native-dialog-isolation.visual.spec.ts):
160
+ acknowledged close, no-ACK, `closable=false`, explicit disposal, cancelled focus
161
+ intent, reentrant retirement, and preserved source attributes are verified in
162
+ Chromium, Firefox, and WebKit.
163
+
164
+ `opened` follows the root's successful initial focus claim. Initially open
165
+ child layers retain their own focus ownership. Explicit `dispose()` requests
166
+ no return; ordinary unmount uses the shared guarded return to the available
167
+ launcher captured from actual native focus. Pointer activation does not
168
+ guarantee button focus in every browser. Hiding a retained dock revokes admission
169
+ without returning focus to its hidden trigger. Revealing the dock does not revive
170
+ the cancelled window: a fresh explicit `open` change from `false` to `true` is
171
+ required. The retained dock keeps its content instance and state.
172
+ Contained scope retains its local HTMLElement/`SOverlayHost` contract and does
173
+ not call native `showModal()`.
174
+
175
+ Direct `showModal()`, `close()`, `open` writes and `form method="dialog"` exits
176
+ are outside the controlled API. Use regular form submission and an explicit
177
+ parent close intent. Native cancel respects controlled state and dismissal
178
+ permission. Already cancelled submissions produce no close intent.
179
+
180
+ Native Document blocking covers HTML, SVG, MathML and ordinary closed-shadow
181
+ background sources. An open child native dialog isolates previous native owners
182
+ and their portal content for its entire lifecycle. Temporary inert leases release
183
+ after native close and before the shared guarded focus return; original attributes
184
+ and independent host writes are preserved.
185
+ Nesting inside a foreign native modal in an opaque closed
186
+ shadow root requires a separate exact host-owner capability. Host mutation of
187
+ the owned native cycle or its temporary modality guards violates ownership.
188
+
189
+ ### Browser verification
190
+
191
+ `make -C lvs frontend-browser-install` installs the browser revisions required
192
+ by the locked Playwright workspace; it does not run quality gates.
193
+ `LVS_STORYBOOK_NATIVE_DIALOG_ENGINES=1 make -C lvs check-frontend-storybook-proof
194
+ PLAYWRIGHT_STORYBOOK_ARGS="tests/storybook/ui-kit-native-dialog-isolation.visual.spec.ts"`
195
+ verifies the native lifecycle in Chromium, Firefox and WebKit.
@@ -27,6 +27,9 @@ UI-kit-owned атрибуты `data-s-*` относятся к внутренн
27
27
  runtime-зависимости через layout конкретного package manager. Сборка использует
28
28
  `target/lib: ES2020`, совпадающий с baseline `@vue/tsconfig/tsconfig.dom.json`,
29
29
  и не требует скрытых полифиллов.
30
+ Сборка Tailwind entrypoints использует Node ESM `dist/typography.js` с native
31
+ `node:util`, полным upstream prose contract и options `className`/`target`
32
+ для `modern | legacy`; browser Web Storage и DOM globals не требуются.
30
33
  `icons` публикует именованные tree-shakeable компоненты, включая `HardDrive`,
31
34
  `Usb`, `Disc`, `CalendarDays`, `ArrowUpRight`, `Paperclip`, `GitFork`, `ZoomIn`,
32
35
  `ZoomOut`, `RotateCw`, `Volume2`, `Radio`, `Pause`, `ChevronLeft` и `Zap`. `Zap`
@@ -74,10 +77,20 @@ Link surface требует ровно один target и доступное и
74
77
  подключённого Vue Router. Static surface отклоняет tooltip binding.
75
78
  Sibling `#actions` сохраняет независимые focus/activation. Подсказка
76
79
  открывается по pointer/focus и закрывается через Escape.
77
- Пассивная tooltip panel поддерживает hover без focus или Tab stop; выход из
80
+ Пассивный visual portal исключён из самостоятельного AX-чтения через
81
+ `aria-hidden`; полный rich content доступен как `aria-describedby` описание
82
+ focus owner. Внешние description references сохраняются; имя, роль и Tab
83
+ порядок owner не изменяются. Tooltip не принимает интерактивный контент.
84
+ Пассивная tooltip panel поддерживает геометрический hover без focus или Tab stop
85
+ и не перехватывает указатель у расположенных под ней действий. Выход из
78
86
  target и панели закрывает её через `hideDelay`. Escape отменяет pending
79
87
  `showDelay` без перехвата события у другого overlay. Blur, скрытие документа,
80
88
  disabled и освобождение target завершают activation lease и его слушатели.
89
+ Binding допускает canonical focus owner в сохранённой скрытой секции.
90
+ Активация требует подключённого, видимого и доступного владельца. Скрытие
91
+ его ancestor отменяет pending-таймеры, сразу исключает visual portal из
92
+ взаимодействия и снимает tooltip description; последующий reveal сохраняет
93
+ тот же owner и допускает новое pointer/focus открытие.
81
94
  `SUiKitLocaleProvider` задаёт `en | ru | zh-CN | zh-TW` для одного Vue subtree,
82
95
  а `composables/useUiKitLocale` экспортирует readonly context, полный список
83
96
  локалей и типы messages/overrides/ключей. Provider не создаёт DOM owner и
@@ -97,6 +110,10 @@ callbacks и trust flags не входят в API.
97
110
  `sm/md/lg = 32/36/40 px` и label typography `12/14/14 px`; тот же
98
111
  `density="compact"` у `SInputText` даёт совместимую геометрию поля без
99
112
  consumer CSS.
113
+ Foreground/background `SButton` применяются атомарно без color interpolation
114
+ при disabled/loading → enabled и смене визуального состояния. Native
115
+ activation, `href` и Tab availability следуют текущим props без ожидания
116
+ декоративных shadow/transform transitions.
100
117
  `SInputText.minLength`/`maxLength` и `STextarea.minLength`/`maxLength` передают строгие
101
118
  неотрицательные safe integers нативному textual input; `minLength <= maxLength`
102
119
  проверяется до рендера, а numeric/temporal families отвергают этот контракт.
@@ -128,9 +145,32 @@ retired controlled `open=true` получает один close intent и тре
128
145
  sibling overlays и consumer-owned arbitrary portals не затрагиваются.
129
146
  `SModal.headingLevel` и `SDrawer.headingLevel` принимают только `1 | 2 | 3 | 4 |
130
147
  5 | 6`, используют `2` по умолчанию и сохраняют связь видимого заголовка с
131
- dialog surface через `aria-labelledby`.
148
+ dialog surface через `aria-labelledby`. Локальная шапка диалога содержит
149
+ семантический heading и не создаёт page-level `banner`.
132
150
  `SModal.size="4xl"` задаёт responsive maximum `56rem`; при ошибке дочернего
133
151
  mount modal освобождает body lock, layer registration и owned teleport nodes.
152
+ Viewport `SModal`/`SDrawer` владеют единственным нативным `dialog` в modal top
153
+ layer, без `tabindex` на semantic root. Фон всех DOM namespaces исключён из
154
+ pointer/focus interaction и accessibility tree. Дочерние UI-kit portals
155
+ принадлежат actual dialog вне transform/scroll clipping; вложенный viewport
156
+ dialog изолирует родителя. Retirement синхронно освобождает native blocking,
157
+ сохраняя отдельный shared focus-return и visual leave lifecycle. Contained
158
+ drawer ограничивает HTML inert lease своим host и не вызывает `showModal()`.
159
+ Экспортируемый `useDialogOverlay` в viewport-режиме требует actual connected
160
+ `HTMLDialogElement` через `setPanelElement` и exact direct-child portal через
161
+ `setPortalElement`; contained-режим сохраняет HTMLElement surface внутри
162
+ SOverlayHost. Native methods принадлежат composable, не consumer.
163
+ Для custom viewport leaf `dialog` и portal находятся в одном Document;
164
+ portal — непосредственный дочерний HTMLElement semantic root. Visible frame
165
+ передаётся через `setDismissalElement`, его расположение не меняет native focus
166
+ scope. Controlled `open` является единственным запросом открытия/закрытия;
167
+ `showModal()`, `close()`, запись `open` и native `form method="dialog"` не являются
168
+ consumer API. Завершение формы использует обычный submit и явное controlled
169
+ намерение закрытия. Native `cancel` обрабатывается shared owner без потери
170
+ контроля над `open`. Композиция в стороннем native modal внутри opaque closed
171
+ shadow root требует отдельного host-owner контракта и не поддерживается этим API.
172
+ Обычные background sources внутри closed shadow root покрывает native Document
173
+ blocking. Контракт миграции custom leaf описан в [overlay-lifecycle.md](./overlay-lifecycle.md).
134
174
  `SDrawer.placement` принимает `left | right | bottom` с default `right`.
135
175
  Боковые размещения физические, не зависят от text direction и используют
136
176
  общий size/scroll/focus owner; `left` зеркалирует сторону панели и её chrome.
@@ -143,9 +183,20 @@ placement. `extent` запрещён для viewport-режима. `scrollBehavi
143
183
  делегирует прокрутку дочернему viewport без второго scroll owner/полей.
144
184
  Закрытие/retirement завершают дочерние overlay leases в общем stack; consumer
145
185
  обрабатывает `update:open`, повторное открытие требует явного нового open-cycle.
186
+ Открытие через `SDropdownMenu` переносит exact current focus intent от
187
+ временного menuitem к dialog и сохраняет устойчивый menu trigger как цель
188
+ возврата, включая trigger вне contained host. Внешний focus отменяет pending
189
+ admission и stale return; contained Escape boundary остаётся локальной.
146
190
  `SPopover` сохраняет native anchor и исходного focus owner trigger-запроса до
147
191
  подтверждения controlled `open`, без перехвата последующего внешнего фокуса.
148
192
  Программный `close()` отменяет ожидающий запрос через `update:open=false`.
193
+ Удаление или disabled текущего focused action не является outside-переходом:
194
+ панель остаётся открытой, принятое закрытие через Escape возвращает focus
195
+ доступному registered trigger.
196
+ Явный blur доступного элемента и внешний focus сохраняют outside-политику.
197
+ `SDrawer`, `SPopover`, `STooltip`, `SContextMenu` и
198
+ `SControlledToastViewport` сохраняют `opacity: 1` content при enter/leave:
199
+ transform motion не меняет прозрачность выбранной surface, backdrop анимируется отдельно.
149
200
  Ширина `trigger` является минимальной шириной anchor, `content` — intrinsic,
150
201
  `sm/md/lg` — viewport-bounded `20/28/36rem`. Chrome и focus owner используют
151
202
  единые bounds; прозрачная поверхность задаёт всему dialog максимум `90vw`,
@@ -163,6 +214,20 @@ inline-сторонам вложенное меню использует дос
163
214
  collision owner.
164
215
  `SCollapsiblePanel` связывает compact/dense density с
165
216
  32-пиксельной disclosure geometry через публичный API `SButton`.
217
+ `contentRole="region"` обозначает самостоятельный landmark и является значением
218
+ по умолчанию; `contentRole="group"` задаёт именованную локальную группу для
219
+ повторяемых или вложенных сведений. Обе роли связаны с видимым summary через
220
+ `aria-labelledby`; trigger сохраняет `aria-expanded` и `aria-controls`.
221
+ Raw `role` attrs не разрешены, неизвестный `contentRole` отклоняется без coercion.
222
+ `lazy` монтирует slot при первом принятом раскрытии и сохраняет его после
223
+ collapse/reopen в controlled и uncontrolled режимах, включая анимацию.
224
+ Controlled `open` эмитит `update:open` intent до prop acknowledgement;
225
+ uncontrolled режим изменяет собственное состояние без этого события.
226
+ Принятое сворачивание сразу исключает content из Tab/AX через inert и
227
+ aria-hidden, сохраняя mounted slot и завершающую анимацию. Owned focus
228
+ возвращается доступному trigger; внешний focus не перехватывается.
229
+ Отсутствие height transition завершает motion сразу; stale completion
230
+ не влияет на повторное раскрытие или изменённую высоту.
166
231
  `SStatus` использует общую feedback tone-пару; `neutral` соответствует
167
232
  `secondary`. Текстовый статус в passive content `SButton` использует foreground
168
233
  интерактивной поверхности во всех доступных состояниях. `appearance="soft"`
@@ -174,6 +239,13 @@ semantics сохраняются; `SCollapsiblePanel.summaryStatus` исполь
174
239
  ResizeObserver lifecycle. `shared/data-display/scrollViewport` экспортирует
175
240
  сериализуемое physical/semantic state и exposed API `focus`,
176
241
  `scrollToBoundary`, `reveal` без доступа к внутреннему DOM.
242
+ `focusPolicy="always"` — default; `overflow` добавляет region и Tab stop только
243
+ при переполнении разрешённой оси. Ровно один из `accessibleLabel` и
244
+ `accessibleLabelledby` задаёт имя. `blockSize="content"` сохраняет естественную
245
+ высоту, `maxBlockSize="none|sm|md|lg"` задаёт semantic limit. Канонический table
246
+ frame композирует этот owner, сохраняя native table semantics и nested controls.
247
+ При исчезновении overflow focused root сохраняет `tabindex="-1"` до native blur
248
+ без дополнительной остановки Tab или программного переноса focus.
177
249
 
178
250
  ## English
179
251
 
@@ -202,6 +274,9 @@ Consumer type checkers do not compile internal SFCs or resolve their runtime
202
274
  dependencies through a package-manager-specific layout. The build uses the
203
275
  `target/lib: ES2020` baseline from `@vue/tsconfig/tsconfig.dom.json` and does not
204
276
  require implicit polyfills.
277
+ Tailwind entrypoints use the Node ESM `dist/typography.js` artifact with
278
+ native `node:util`, the complete upstream prose contract, and `className`/
279
+ `target` options for `modern | legacy`, without browser Web Storage or DOM globals.
205
280
  `icons` exports named tree-shakeable components, including `HardDrive`, `Usb`,
206
281
  `Disc`, `CalendarDays`, `ArrowUpRight`, `Paperclip`, `GitFork`, `ZoomIn`, `ZoomOut`,
207
282
  `RotateCw`, `Volume2`, `Radio`, `Pause`, `ChevronLeft`, and `Zap`. `Zap` represents
@@ -250,10 +325,16 @@ Router `to`. A link surface requires exactly one target and an accessible name;
250
325
  `to` requires Vue Router installed in the application. A static surface rejects
251
326
  the tooltip binding. Sibling `#actions` retain independent focus and activation.
252
327
  The tooltip opens on pointer/focus and closes with Escape.
253
- The passive tooltip panel supports hover without focus or a Tab stop; leaving
328
+ The passive tooltip panel supports geometric hover without focus or a Tab stop
329
+ and does not intercept pointer activation of underlying actions. Leaving
254
330
  the target and panel closes it after `hideDelay`. Escape cancels a pending
255
331
  `showDelay` without consuming another overlay's event. Blur, a hidden document,
256
332
  disabled state, and target release terminate the activation lease and listeners.
333
+ Binding accepts a canonical focus owner in a retained hidden section.
334
+ Activation requires a connected, visible, available owner. Hiding its
335
+ ancestor cancels pending timers, immediately retires the visual portal from
336
+ interaction and removes the tooltip description. Revealing the section
337
+ retains the same owner and permits fresh pointer/focus activation.
257
338
  `SUiKitLocaleProvider` selects `en | ru | zh-CN | zh-TW` for one Vue subtree,
258
339
  while `composables/useUiKitLocale` exports the readonly context, the complete
259
340
  locale list, and message/override/key types. The provider renders no DOM owner
@@ -274,6 +355,10 @@ and trust flags are not part of the API.
274
355
  `sm/md/lg = 32/36/40 px` with `12/14/14 px` label typography. The matching
275
356
  `SInputText.density="compact"` exposes the same field geometry without consumer
276
357
  CSS.
358
+ The `SButton` foreground/background pair changes atomically, without color
359
+ interpolation, when disabled/loading actions become enabled or presentation
360
+ changes. Native activation, `href`, and Tab availability follow current props
361
+ without waiting for decorative shadow/transform transitions.
277
362
  `SInputText.minLength`/`maxLength` and `STextarea.minLength`/`maxLength` pass strict non-negative safe
278
363
  integers to the native textual input. `minLength <= maxLength` is validated
279
364
  before rendering, and numeric/temporal families reject this contract.
@@ -306,9 +391,29 @@ request. Nested docks compose admission with AND; independent sibling overlays
306
391
  and arbitrary consumer-owned portals are unaffected.
307
392
  `SModal.headingLevel` and `SDrawer.headingLevel` accept `1 | 2 | 3 | 4 | 5 | 6`
308
393
  only, default to `2`, and preserve the visible-title relationship with the
309
- dialog surface through `aria-labelledby`.
394
+ dialog surface through `aria-labelledby`. Local dialog chrome contains the
395
+ semantic heading without creating a page-level `banner` landmark.
310
396
  `SModal.size="4xl"` provides a responsive `56rem` maximum; a failed child mount
311
397
  releases body lock, layer registration, and owned teleport nodes.
398
+ Viewport `SModal`/`SDrawer` own one native `dialog` in the modal top layer, with
399
+ no `tabindex` on the semantic root. All background DOM namespaces leave
400
+ pointer/focus interaction and the accessibility tree. Descendant UI-kit portals
401
+ belong to the actual dialog outside transforms and scroll clipping; a nested
402
+ viewport dialog isolates its parent. Retirement releases native blocking
403
+ synchronously while preserving the shared focus-return and visual leave
404
+ lifecycles. A contained drawer limits an HTML inert lease to its host and does
405
+ not call `showModal()`.
406
+ The exported `useDialogOverlay` requires an actual connected `HTMLDialogElement`
407
+ through `setPanelElement` and its exact direct-child portal through
408
+ `setPortalElement` in viewport mode. Contained mode retains an HTMLElement
409
+ surface inside SOverlayHost. The composable owns native methods, not consumers.
410
+ The custom viewport leaf binds its visible frame through `setDismissalElement`
411
+ and applies `portalStyle` to its direct-child portal. Controlled `open` is the
412
+ only opening/closing request; direct native lifecycle calls, `open` writes and
413
+ `form method="dialog"` exits are not consumer APIs. Regular form submission
414
+ uses an explicit parent close intent. Nesting inside a foreign native modal in
415
+ an opaque closed shadow root requires a separate host-owner contract.
416
+ See [overlay-lifecycle.md](./overlay-lifecycle.md) for the custom-leaf migration.
312
417
  `SDrawer.placement` accepts `left | right | bottom`, defaulting to `right`.
313
418
  Side placements are physical, independent of text direction, and share the
314
419
  size/scroll/focus owner; `left` mirrors the panel edge and its chrome.
@@ -321,10 +426,21 @@ the placement axis. Viewport mode rejects `extent`. `scrollBehavior="content"`
321
426
  delegates scrolling to a child viewport without a second scroll owner/padding.
322
427
  Close/retirement retires descendant overlay leases in the shared stack; the
323
428
  consumer handles `update:open` and an explicit new open-cycle controls reopening.
429
+ Opening through `SDropdownMenu` transfers the exact current focus intent
430
+ from a transient menu item to the dialog and retains its stable menu trigger
431
+ for restoration, including triggers outside a contained host. External focus
432
+ cancels pending admission and stale restoration; contained Escape remains local.
324
433
  `SPopover` preserves the native anchor and original focus owner of a trigger
325
434
  request through controlled `open` acknowledgement without taking subsequent
326
435
  external focus. Programmatic `close()` cancels a pending request with
327
436
  `update:open=false`.
437
+ Removing or disabling the currently focused action is not an outside move:
438
+ the panel stays open, and an accepted Escape close restores the available
439
+ registered trigger.
440
+ Explicit blur of an available element and external focus retain the outside policy.
441
+ `SDrawer`, `SPopover`, `STooltip`, `SContextMenu`, and
442
+ `SControlledToastViewport` keep content at `opacity: 1` during entry/exit:
443
+ transform motion preserves the selected surface translucency, and backdrop animation is independent.
328
444
  The `trigger` width is the anchor minimum, `content` is intrinsic, and
329
445
  `sm/md/lg` are viewport-bounded `20/28/36rem`. Chrome and focus owner share
330
446
  the same bounds; a transparent surface sets the entire dialog's maximum to `90vw`.
@@ -342,6 +458,20 @@ side fits a submenu, it uses the available top/bottom side of the shared
342
458
  collision owner.
343
459
  `SCollapsiblePanel` maps compact/dense density to
344
460
  the 32px disclosure geometry through the public `SButton` API.
461
+ `contentRole="region"` marks an independently navigable landmark and is the
462
+ default; `contentRole="group"` provides a named local group for repeated or
463
+ nested details. Both roles are labelled by the visible summary through
464
+ `aria-labelledby`; the trigger retains `aria-expanded` and `aria-controls`.
465
+ Raw `role` attrs are not admitted; unknown `contentRole` values fail without
466
+ coercion. `lazy` mounts its slot on the first accepted opening and retains it
467
+ across collapse/reopen in controlled and uncontrolled modes, including animation.
468
+ Controlled `open` emits `update:open` intent before prop acknowledgement;
469
+ uncontrolled mode updates its owned state without that event.
470
+ Accepted collapse immediately excludes content from Tab/AX using inert
471
+ and aria-hidden, retaining the mounted slot and closing visual transition.
472
+ Owned focus returns to an available trigger; external focus is not taken.
473
+ An absent height transition settles motion immediately; stale completion
474
+ does not affect reopening or an updated content height.
345
475
  `SStatus` uses the shared feedback tone pair; `neutral` maps to `secondary`.
346
476
  A text status in `SButton` passive content uses the interactive surface's
347
477
  foreground across its available states. `appearance="soft"` owns its
@@ -353,3 +483,10 @@ enabled axes, semantic block size, root-only keyboard navigation, and
353
483
  ResizeObserver lifecycle. `shared/data-display/scrollViewport` exports
354
484
  serializable physical and semantic state plus the `focus`, `scrollToBoundary`,
355
485
  and `reveal` exposed API without access to internal DOM.
486
+ `focusPolicy="always"` is the default; `overflow` adds a region and Tab stop
487
+ only when an enabled axis overflows. Exactly one of `accessibleLabel` and
488
+ `accessibleLabelledby` supplies the name. `blockSize="content"` retains intrinsic
489
+ height, and `maxBlockSize="none|sm|md|lg"` supplies a semantic limit. The canonical
490
+ table frame composes this owner, retaining native table semantics and nested controls.
491
+ After overflow disappears, a focused root retains `tabindex="-1"` until native
492
+ blur, without an additional Tab stop or programmatic focus move.
package/docs/resize.md CHANGED
@@ -52,8 +52,36 @@ Consumer применяет `resize` к preview. В `resize-end` он прини
52
52
  актуальной host geometry; persistence preference восстанавливается из его
53
53
  собственного transaction snapshot, не из `initialValue`. Отменённый preview
54
54
  не сохраняется. На время pointer transaction persistent watcher должен быть
55
- отделён от preview state. Клавиатурные Arrow/Home/End являются
56
- дискретным `resize` без pointer transaction, `resize-end` или `resize-cancel`.
55
+ отделён от preview state.
56
+
57
+ ## Клавиатурное завершение
58
+
59
+ Arrow/Home/End применяют одно дискретное изменение: `resize(value)`, затем
60
+ `resize-end(value)` без `SResizeHandleCompletion`. Отсутствие completion
61
+ обозначает клавиатурный источник; pointer completion всегда содержит
62
+ `source: 'pointer'`. Клавиатура не открывает pointer transaction и не выдаёт
63
+ `resize-start` или `resize-cancel`.
64
+
65
+ Значение ограничивается текущими `min/max`. На границе диапазона, для
66
+ ортогональной стрелки, при `disabled` или во время активного pointer gesture
67
+ события изменения и завершения не выдаются. Если обработчик `resize` удаляет
68
+ native owner, завершение после его disposal не выдаётся.
69
+
70
+ Consumer может хранить preview в `resize` и сохранять preference в
71
+ `resize-end` для обоих источников. Если составной owner уже атомарно сохраняет
72
+ клавиатурный `resize`, terminal notification не выполняет повторную запись:
73
+ обработчик явно разделяет источники по completion. Pointer commit требует
74
+ собственной живой transaction, а не разрешается при её отсутствии.
75
+
76
+ ```ts
77
+ function finishSizeChange(value: number, completion?: SResizeHandleCompletion): void {
78
+ if (completion === undefined) {
79
+ commitKeyboardSize(value)
80
+ return
81
+ }
82
+ commitPointerSize(value, completion)
83
+ }
84
+ ```
57
85
 
58
86
  ```vue
59
87
  <SDockRegion
@@ -65,7 +93,7 @@ Consumer применяет `resize` к preview. В `resize-end` он прини
65
93
  hitbox="standard"
66
94
  @resize-start="beginSizeTransaction"
67
95
  @resize="previewSize"
68
- @resize-end="commitSizeTransaction"
96
+ @resize-end="finishSizeChange"
69
97
  @resize-cancel="cancelSizeTransaction"
70
98
  >
71
99
  <ProjectPanel />
@@ -102,7 +130,7 @@ Pointer cancellation не вызывает `collapse`. Keyboard Home устан
102
130
  hitbox="standard"
103
131
  @resize-start="beginSizeTransaction"
104
132
  @resize="previewSize"
105
- @resize-end="commitSizeTransaction"
133
+ @resize-end="finishSizeChange"
106
134
  @resize-cancel="cancelSizeTransaction"
107
135
  @collapse="collapseSizeTransaction"
108
136
  >
@@ -125,5 +153,6 @@ Pointer cancellation не вызывает `collapse`. Keyboard Home устан
125
153
  payload; его диапазон принадлежит текущей рабочей области. Consumers используют
126
154
  эти owners, а не создают собственный drag engine.
127
155
 
128
- Storybook: `SResizeHandle/PointerTouchAndConcurrentLifecycle` и
156
+ Storybook: `SResizeHandle/KeyboardCommitPersistence`,
157
+ `SResizeHandle/PointerTouchAndConcurrentLifecycle` и
129
158
  `SDockRegion/CompanionDocks`, `CompanionDocksDark`, `CompanionDocksNarrow`.
@@ -26,9 +26,11 @@ import type {
26
26
 
27
27
  | Prop | Contract |
28
28
  | --- | --- |
29
- | `accessibleLabel` | Обязательное непустое имя `region`. / Required non-empty name of the `region`. |
29
+ | `accessibleLabel` / `accessibleLabelledby` | Ровно один источник имени: непустая строка либо exact single-space DOM IDREF-list. / Exactly one name source: non-empty text or an exact single-space DOM IDREF list. |
30
30
  | `axis` | `'x' \| 'y' \| 'both'`; default `y`. Запрещённая ось не прокручивается и недоступна API. / A disabled axis neither scrolls nor accepts API commands. |
31
- | `blockSize` | `'sm' \| 'md' \| 'lg' \| 'fill'`; semantic token, raw CSS length запрещён. / Semantic token; raw CSS lengths are rejected. |
31
+ | `blockSize` | `'sm' \| 'md' \| 'lg' \| 'fill' \| 'content'`; default `md`. `content` сохраняет естественный flow. / Semantic size; `content` preserves natural flow. Raw CSS lengths are rejected. |
32
+ | `maxBlockSize` | `'none' \| 'sm' \| 'md' \| 'lg'`; default `none`. Семантический верхний предел высоты. / Semantic maximum block size. |
33
+ | `focusPolicy` | `'always' \| 'overflow'`; default `always`. `overflow` добавляет named region и остановку Tab только при переполнении разрешённой оси. / `overflow` adds a named region and Tab stop only during enabled-axis overflow. |
32
34
  | `focusable` | Включает root tab stop и `focus()`; default `true`. / Enables the root tab stop and `focus()`; defaults to `true`. |
33
35
  | `keyboardNavigation` | Управление корнем с клавиатуры; требует `focusable=true`. / Root keyboard control; requires `focusable=true`. |
34
36
  | `overscrollBehavior` | `'auto' \| 'contain' \| 'none'`; default `contain`. |
@@ -39,6 +41,21 @@ classes и `data-s-*` не являются selector API.
39
41
  Consumer-owned `data-*` are supported on the root region for tour and test
40
42
  hooks. Internal classes and `data-s-*` are not selector APIs.
41
43
 
44
+ `focusable` разрешает root focus; `focusPolicy="overflow"` учитывает фактический
45
+ scroll range только разрешённых осей. При исчезновении переполнения component
46
+ удаляет Tab stop и named region, не перемещая существующий focus: focused root
47
+ сохраняет `tabindex="-1"` до native blur, после которого атрибут снимается. `focus()` в
48
+ этом состоянии завершается явной ошибкой. Содержимое таблиц использует такой
49
+ контракт через общий canonical table owner; native table name и ElementApi
50
+ сохраняются.
51
+
52
+ `focusable` permits root focus; `focusPolicy="overflow"` checks the actual range
53
+ of enabled axes. When overflow disappears, the component removes the Tab stop
54
+ and named region without moving existing focus: a focused root retains
55
+ `tabindex="-1"` until native blur removes the attribute. `focus()` fails explicitly in
56
+ that state. Canonical table composition uses this policy while retaining the
57
+ native table name and ElementApi.
58
+
42
59
  ## Состояние / State
43
60
 
44
61
  `scroll-state` эмитится после mount, прокрутки, resize viewport/content и
@@ -130,14 +147,18 @@ into view.
130
147
 
131
148
  ## Lifecycle
132
149
 
133
- Один `ResizeObserver` отслеживает root viewport и content owner. Passive scroll
134
- listener и observer освобождаются при unmount; callback после teardown не
135
- публикует state. Компонент не создаёт global listener и не использует polling.
150
+ Один `ResizeObserver` отслеживает viewport, content owner и его непосредственные
151
+ дочерние элементы. Scoped content `MutationObserver` обновляет intrinsic
152
+ геометрию и наблюдаемый набор; capture load listener учитывает размеры ресурсов.
153
+ Observers/listeners освобождаются при unmount; callback после teardown не
154
+ публикует state. Global document observer/listener и polling не используются.
136
155
 
137
- One `ResizeObserver` tracks the root viewport and content owner. The passive
138
- scroll listener and observer are released on unmount, and callbacks after
139
- teardown publish no state. The component creates no global listener and uses no
140
- polling.
156
+ One `ResizeObserver` tracks the viewport, content owner and direct content
157
+ element children. A scoped content `MutationObserver` updates intrinsic
158
+ geometry and the observed child set; a content capture load listener covers
159
+ resource dimensions. Observers/listeners are released on unmount, and callbacks
160
+ after teardown publish no state. The component creates no document-global
161
+ observer/listener and uses no polling.
141
162
 
142
163
  ## Пример / Example
143
164
 
@@ -164,4 +185,3 @@ const position = ref<SScrollViewportState | null>(null)
164
185
  </SScrollViewport>
165
186
  </template>
166
187
  ```
167
-