@ticatec/uniface-element 5.0.0 → 5.0.2

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 (282) hide show
  1. package/README.md +29 -54
  2. package/README_CN.md +37 -57
  3. package/dist/app_layout/ClassicLayout.svelte +8 -40
  4. package/dist/app_layout/ColumnsLayout.svelte +3 -3
  5. package/dist/app_layout/HeaderLayout.svelte +4 -20
  6. package/dist/app_layout/RowsLayout.svelte +3 -3
  7. package/dist/app_layout/SidebarLayout.svelte +4 -20
  8. package/dist/base-calendar/CalendarBase.svelte +29 -6
  9. package/dist/base-calendar/CalendarBase.svelte.d.ts +4 -0
  10. package/dist/base-calendar/MonthPickupPanel.svelte +19 -6
  11. package/dist/base-calendar/MonthPickupPanel.svelte.d.ts +1 -1
  12. package/dist/base-calendar/MonthlyCalendar.svelte +18 -2
  13. package/dist/base-calendar/YearPickupPanel.svelte +24 -6
  14. package/dist/base-calendar/YearPickupPanel.svelte.d.ts +1 -1
  15. package/dist/composite/pagination_panel/PaginationPanel.svelte +2 -0
  16. package/dist/composite/transfer/Transfer.svelte +12 -6
  17. package/dist/data_display/accordion/Accordion.svelte +1 -4
  18. package/dist/data_display/card/CommonCardActionBar.svelte +31 -37
  19. package/dist/data_display/concise_data_table/Column.d.ts +6 -0
  20. package/dist/data_display/concise_data_table/ConciseListTable.svelte +4 -3
  21. package/dist/data_display/concise_data_table/DataCell.svelte +3 -3
  22. package/dist/data_display/list_box/ListBox.svelte +96 -23
  23. package/dist/data_display/list_box/ListBox.svelte.d.ts +4 -0
  24. package/dist/data_display/tree_view/TreeNodeView.svelte +25 -4
  25. package/dist/data_table/DataTable.svelte +5 -5
  26. package/dist/data_table/DataTable.svelte.d.ts +36 -16
  27. package/dist/data_table/lib/ActionsColumn.d.ts +2 -2
  28. package/dist/data_table/lib/DataColumn.d.ts +7 -1
  29. package/dist/data_table/lib/RowAction.d.ts +5 -5
  30. package/dist/data_table/lib/clickOutSide.js +6 -5
  31. package/dist/data_table/parts/FixedHeaderPanel.svelte +18 -3
  32. package/dist/data_table/parts/FixedRow.svelte +10 -1
  33. package/dist/data_table/parts/HrefCell.svelte +1 -1
  34. package/dist/data_table/parts/TableHeaderPanel.svelte +18 -2
  35. package/dist/data_table/parts/TextCell.svelte +2 -2
  36. package/dist/form/attachment_files/AttachmentEditor.svelte +20 -15
  37. package/dist/form/attachment_files/FileRender.svelte +1 -1
  38. package/dist/form/attachment_files/FileUploadBar.svelte +5 -2
  39. package/dist/form/attachment_files/FileUploadPanel.svelte +11 -73
  40. package/dist/form/cascade_options_select/CascadeOptionSelect.svelte +10 -2
  41. package/dist/form/cascade_options_select/OptionsBox.svelte +48 -2
  42. package/dist/form/checkbox/CheckBox.svelte +2 -7
  43. package/dist/form/color_picker/ColorPicker.svelte +150 -35
  44. package/dist/form/color_picker/ColorPicker.svelte.d.ts +7 -25
  45. package/dist/form/color_picker/ColorPickerPanel.svelte +23 -20
  46. package/dist/form/color_picker/ColorPickerPanel.svelte.d.ts +5 -18
  47. package/dist/form/color_picker/README.md +14 -7
  48. package/dist/form/color_picker/README_CN.md +14 -7
  49. package/dist/form/common_editor/CommonEditor.svelte +18 -4
  50. package/dist/form/common_editor/CommonPicker.svelte +14 -10
  51. package/dist/form/common_editor/CommonPopupEditor.svelte +8 -1
  52. package/dist/form/common_range_editor/CommonRangeEditor.svelte +11 -2
  53. package/dist/form/date_picker/DatePicker.svelte +1 -1
  54. package/dist/form/date_picker/DateTimePicker.svelte +1 -1
  55. package/dist/form/date_picker/ScrollBar.svelte +20 -12
  56. package/dist/form/date_picker/ScrollBar.svelte.d.ts +4 -18
  57. package/dist/form/date_range/DateRangeEditor.svelte +11 -3
  58. package/dist/form/group_box/group-check-box/GroupCheckBox.svelte +6 -1
  59. package/dist/form/group_box/group-radio-box/GroupRadioBox.svelte +6 -1
  60. package/dist/form/image-files/ImageFilesField.svelte +51 -7
  61. package/dist/form/image-files/ImagePreview.svelte +23 -9
  62. package/dist/form/image-files/ImageRender.svelte +69 -11
  63. package/dist/form/input_options_select/InputOptionsSelect.svelte +10 -2
  64. package/dist/form/lookup_editor/LookupEditor.svelte +9 -2
  65. package/dist/form/memo_editor/MemoEditor.svelte +2 -2
  66. package/dist/form/number_editor/NumberEditor.svelte +2 -2
  67. package/dist/form/options_multi_select/OptionsMultiSelect.svelte +20 -12
  68. package/dist/form/options_select/OptionsSelect.svelte +15 -6
  69. package/dist/form/options_select/OptionsSelect.svelte.d.ts +39 -17
  70. package/dist/form/prompts_text_editor/PromptsTextEditor.svelte +10 -2
  71. package/dist/form/radio_button/RadioButton.svelte +2 -8
  72. package/dist/form/switch/Switch.svelte +4 -4
  73. package/dist/form/text_editor/TextEditor.svelte +2 -2
  74. package/dist/form/unit-number-editor/UnitNumberEditor.svelte +10 -2
  75. package/dist/general/button/Button.svelte +5 -0
  76. package/dist/general/button/Button.svelte.d.ts +1 -0
  77. package/dist/general/button/IconButton.svelte +3 -1
  78. package/dist/general/button/IconButton.svelte.d.ts +1 -0
  79. package/dist/general/button/TextButton.svelte +3 -0
  80. package/dist/general/button/TextButton.svelte.d.ts +1 -0
  81. package/dist/general/icon/Icon.svelte +18 -1
  82. package/dist/general/split/Split.svelte +24 -4
  83. package/dist/general/tag/Tag.svelte +9 -1
  84. package/dist/navigation/nav_menu/MenuItem.d.ts +4 -2
  85. package/dist/navigation/nav_menu/NavigatorMenu.svelte +157 -34
  86. package/dist/navigation/nav_menu/NavigatorMenu.svelte.d.ts +2 -3
  87. package/dist/navigation/nav_menu/NavigatorMenuItem.svelte +25 -13
  88. package/dist/navigation/nav_menu/NavigatorMenuItem.svelte.d.ts +3 -3
  89. package/dist/navigation/navigator/Navigator.svelte +17 -17
  90. package/dist/navigation/page-stack/PageStack.svelte +160 -0
  91. package/dist/navigation/page-stack/PageStack.svelte.d.ts +24 -0
  92. package/dist/navigation/page-stack/PageStackContext.d.ts +25 -0
  93. package/dist/navigation/page-stack/PageStackContext.js +12 -0
  94. package/dist/navigation/page-stack/PageStackFrame.svelte +24 -0
  95. package/dist/navigation/page-stack/PageStackFrame.svelte.d.ts +11 -0
  96. package/dist/navigation/page-stack/README.md +154 -0
  97. package/dist/navigation/page-stack/README_CN.md +154 -0
  98. package/dist/navigation/page-stack/index.d.ts +6 -0
  99. package/dist/navigation/page-stack/index.js +4 -0
  100. package/dist/navigation/pagination/Pagination.svelte +114 -14
  101. package/dist/navigation/progress_step_bar/ProgressStepBlock.svelte +1 -5
  102. package/dist/navigation/tabs/Tabs.svelte +42 -10
  103. package/dist/navigation/tabs/Tabs.svelte.d.ts +2 -2
  104. package/dist/overlay/common/FloatingPanel.svelte +3 -17
  105. package/dist/overlay/common/FullscreenOverlay.svelte +27 -3
  106. package/dist/overlay/common/FullscreenOverlay.svelte.d.ts +6 -27
  107. package/dist/overlay/common/ModalPopover.svelte +18 -2
  108. package/dist/overlay/common/Popover.svelte +61 -57
  109. package/dist/overlay/common/Popover.svelte.d.ts +1 -5
  110. package/dist/overlay/common/escapeClose.d.ts +28 -0
  111. package/dist/overlay/common/escapeClose.js +47 -0
  112. package/dist/overlay/common/focusTrap.d.ts +8 -0
  113. package/dist/overlay/common/focusTrap.js +83 -0
  114. package/dist/overlay/common/mountSingleton.d.ts +27 -0
  115. package/dist/overlay/common/mountSingleton.js +45 -0
  116. package/dist/overlay/common/outsideClick.d.ts +28 -0
  117. package/dist/overlay/common/outsideClick.js +20 -0
  118. package/dist/overlay/common/scrollLock.d.ts +8 -0
  119. package/dist/overlay/common/scrollLock.js +49 -0
  120. package/dist/overlay/common/uniface-utils.js +0 -1
  121. package/dist/overlay/context-menu/ContextMenu.svelte.js +2 -15
  122. package/dist/overlay/context-menu/ContextMenuPanel.svelte +27 -3
  123. package/dist/overlay/dialog/Dialog.svelte +48 -32
  124. package/dist/overlay/dialog/DialogManager.svelte.js +7 -9
  125. package/dist/overlay/dialog/DialogWrapper.svelte +5 -2
  126. package/dist/overlay/drawer/Drawer.svelte +30 -3
  127. package/dist/overlay/indicator/Indicator.svelte.js +2 -15
  128. package/dist/overlay/indicator/IndicatorPanel.svelte +4 -1
  129. package/dist/overlay/message-box/MessageBox.svelte.js +2 -15
  130. package/dist/overlay/message-box/MessageBoxPanel.svelte +19 -3
  131. package/dist/overlay/toast/Toast.svelte.js +2 -15
  132. package/dist/overlay/toast/ToastPanel.svelte +1 -1
  133. package/dist/overlay/tooltip/Tooltip.svelte +1 -1
  134. package/dist/ticatec-uniface-web.css +128 -13
  135. package/dist/types.d.ts +4 -3
  136. package/dist/utils/prefixFilter.d.ts +3 -3
  137. package/dist/utils/prefixFilter.js +3 -5
  138. package/docs/components/README.md +133 -0
  139. package/docs/components/accordion/README.md +60 -0
  140. package/docs/components/accordion/example.svelte +7 -0
  141. package/docs/components/action-bar/README.md +114 -0
  142. package/docs/components/action-bar/example.svelte +7 -0
  143. package/docs/components/app-top-bar/README.md +60 -0
  144. package/docs/components/app-top-bar/example.svelte +9 -0
  145. package/docs/components/attachment-files/README.md +332 -0
  146. package/docs/components/attachment-files/example.svelte +16 -0
  147. package/docs/components/box/README.md +63 -0
  148. package/docs/components/box/example.svelte +10 -0
  149. package/docs/components/breadcrumbs/README.md +86 -0
  150. package/docs/components/breadcrumbs/example.svelte +11 -0
  151. package/docs/components/button/README.md +103 -0
  152. package/docs/components/button/example.svelte +10 -0
  153. package/docs/components/card/README.md +113 -0
  154. package/docs/components/card/example.svelte +10 -0
  155. package/docs/components/cascade-options-select/README.md +162 -0
  156. package/docs/components/cascade-options-select/example.svelte +18 -0
  157. package/docs/components/checkbox/README.md +103 -0
  158. package/docs/components/checkbox/example.svelte +15 -0
  159. package/docs/components/classic-layout/README.md +76 -0
  160. package/docs/components/classic-layout/example.svelte +16 -0
  161. package/docs/components/color-picker/README.md +132 -0
  162. package/docs/components/color-picker/example.svelte +16 -0
  163. package/docs/components/columns-layout/README.md +70 -0
  164. package/docs/components/columns-layout/example.svelte +12 -0
  165. package/docs/components/common-dialog/README.md +109 -0
  166. package/docs/components/common-dialog/example.svelte +12 -0
  167. package/docs/components/concise-data-table/README.md +307 -0
  168. package/docs/components/concise-data-table/example.svelte +16 -0
  169. package/docs/components/context-menu/README.md +99 -0
  170. package/docs/components/context-menu/example.svelte +10 -0
  171. package/docs/components/criteria-field/README.md +69 -0
  172. package/docs/components/criteria-field/example.svelte +9 -0
  173. package/docs/components/data-table/README.md +365 -0
  174. package/docs/components/data-table/example.svelte +23 -0
  175. package/docs/components/date-picker/README.md +136 -0
  176. package/docs/components/date-picker/example.svelte +16 -0
  177. package/docs/components/date-range/README.md +122 -0
  178. package/docs/components/date-range/example.svelte +15 -0
  179. package/docs/components/date-time-picker/README.md +142 -0
  180. package/docs/components/date-time-picker/example.svelte +16 -0
  181. package/docs/components/dialog/README.md +174 -0
  182. package/docs/components/dialog/example.svelte +13 -0
  183. package/docs/components/dialog-board/README.md +41 -0
  184. package/docs/components/dialog-board/example.svelte +5 -0
  185. package/docs/components/drawer/README.md +70 -0
  186. package/docs/components/drawer/example.svelte +12 -0
  187. package/docs/components/flex-form/README.md +55 -0
  188. package/docs/components/flex-form/example.svelte +7 -0
  189. package/docs/components/flex-row-form/README.md +56 -0
  190. package/docs/components/flex-row-form/example.svelte +7 -0
  191. package/docs/components/form-field/README.md +84 -0
  192. package/docs/components/form-field/example.svelte +10 -0
  193. package/docs/components/full-screen-overlay/README.md +66 -0
  194. package/docs/components/full-screen-overlay/example.svelte +11 -0
  195. package/docs/components/grid-form/README.md +75 -0
  196. package/docs/components/grid-form/example.svelte +9 -0
  197. package/docs/components/group-check-box/README.md +147 -0
  198. package/docs/components/group-check-box/example.svelte +21 -0
  199. package/docs/components/group-radio-box/README.md +144 -0
  200. package/docs/components/group-radio-box/example.svelte +21 -0
  201. package/docs/components/header-layout/README.md +65 -0
  202. package/docs/components/header-layout/example.svelte +12 -0
  203. package/docs/components/icon/README.md +87 -0
  204. package/docs/components/icon/example.svelte +9 -0
  205. package/docs/components/icon-button/README.md +91 -0
  206. package/docs/components/icon-button/example.svelte +10 -0
  207. package/docs/components/image-files/README.md +174 -0
  208. package/docs/components/image-files/example.svelte +19 -0
  209. package/docs/components/indicator/README.md +53 -0
  210. package/docs/components/indicator/example.svelte +14 -0
  211. package/docs/components/input-options-select/README.md +163 -0
  212. package/docs/components/input-options-select/example.svelte +16 -0
  213. package/docs/components/list-box/ListItem.svelte +14 -0
  214. package/docs/components/list-box/README.md +221 -0
  215. package/docs/components/list-box/example.svelte +61 -0
  216. package/docs/components/lookup-editor/README.md +137 -0
  217. package/docs/components/lookup-editor/example.svelte +16 -0
  218. package/docs/components/memo-editor/README.md +143 -0
  219. package/docs/components/memo-editor/example.svelte +16 -0
  220. package/docs/components/message-box/README.md +68 -0
  221. package/docs/components/message-box/example.svelte +10 -0
  222. package/docs/components/navigator/README.md +75 -0
  223. package/docs/components/navigator/example.svelte +13 -0
  224. package/docs/components/navigator-menu/README.md +113 -0
  225. package/docs/components/navigator-menu/example.svelte +10 -0
  226. package/docs/components/number-editor/README.md +144 -0
  227. package/docs/components/number-editor/example.svelte +16 -0
  228. package/docs/components/number-range/README.md +116 -0
  229. package/docs/components/number-range/example.svelte +15 -0
  230. package/docs/components/options-multi-select/README.md +161 -0
  231. package/docs/components/options-multi-select/example.svelte +20 -0
  232. package/docs/components/options-select/README.md +160 -0
  233. package/docs/components/options-select/example.svelte +21 -0
  234. package/docs/components/page/README.md +77 -0
  235. package/docs/components/page/example.svelte +10 -0
  236. package/docs/components/pagination/README.md +88 -0
  237. package/docs/components/pagination/example.svelte +10 -0
  238. package/docs/components/pagination-panel/README.md +120 -0
  239. package/docs/components/pagination-panel/example.svelte +16 -0
  240. package/docs/components/popup-hint/README.md +44 -0
  241. package/docs/components/popup-hint/example.svelte +5 -0
  242. package/docs/components/progress-bar/README.md +64 -0
  243. package/docs/components/progress-bar/example.svelte +8 -0
  244. package/docs/components/progress-step-bar/README.md +93 -0
  245. package/docs/components/progress-step-bar/example.svelte +7 -0
  246. package/docs/components/prompts-text-editor/README.md +146 -0
  247. package/docs/components/prompts-text-editor/example.svelte +17 -0
  248. package/docs/components/radio-button/README.md +121 -0
  249. package/docs/components/radio-button/example.svelte +18 -0
  250. package/docs/components/rows-layout/README.md +70 -0
  251. package/docs/components/rows-layout/example.svelte +12 -0
  252. package/docs/components/search-box/README.md +136 -0
  253. package/docs/components/search-box/example.svelte +16 -0
  254. package/docs/components/separator/README.md +44 -0
  255. package/docs/components/separator/example.svelte +7 -0
  256. package/docs/components/sidebar-layout/README.md +65 -0
  257. package/docs/components/sidebar-layout/example.svelte +12 -0
  258. package/docs/components/split/README.md +48 -0
  259. package/docs/components/split/example.svelte +8 -0
  260. package/docs/components/switch/README.md +77 -0
  261. package/docs/components/switch/example.svelte +11 -0
  262. package/docs/components/tabs/README.md +127 -0
  263. package/docs/components/tabs/example.svelte +22 -0
  264. package/docs/components/tag/README.md +63 -0
  265. package/docs/components/tag/example.svelte +8 -0
  266. package/docs/components/text-button/README.md +90 -0
  267. package/docs/components/text-button/example.svelte +9 -0
  268. package/docs/components/text-editor/README.md +147 -0
  269. package/docs/components/text-editor/example.svelte +16 -0
  270. package/docs/components/time-editor/README.md +130 -0
  271. package/docs/components/time-editor/example.svelte +16 -0
  272. package/docs/components/toast/README.md +52 -0
  273. package/docs/components/toast/example.svelte +9 -0
  274. package/docs/components/tooltip/README.md +44 -0
  275. package/docs/components/tooltip/example.svelte +5 -0
  276. package/docs/components/transfer/README.md +109 -0
  277. package/docs/components/transfer/example.svelte +13 -0
  278. package/docs/components/tree-view/README.md +163 -0
  279. package/docs/components/tree-view/example.svelte +26 -0
  280. package/docs/components/unit-number-editor/README.md +192 -0
  281. package/docs/components/unit-number-editor/example.svelte +18 -0
  282. package/package.json +15 -7
@@ -0,0 +1,154 @@
1
+ # PageStack
2
+
3
+ 一个局部(非模态)的导航栈:push 一个组件覆盖当前内容区,呈现为一个新"页面";这个页面可以自己关闭,回到下面被覆盖的内容。适用于最常见的 App 外壳形态——固定的 topbar/bottombar + 一个可以不断"钻进"子页面的内容区,且不依赖任何路由方案。
4
+
5
+ 面向 **Svelte 5**,使用 snippet API。和 `Dialog`(`overlay/dialog`)不同,`PageStack` **不是全局单例**,也**不会**挂载到 `<body>` 上——每个实例拥有自己独立的栈,作用域仅限于它被渲染的那个容器,一个页面里可以同时存在多个互不干扰的 `PageStack` 实例。
6
+
7
+ [English](./README.md)
8
+
9
+ ## 引入
10
+
11
+ ```svelte
12
+ <script lang="ts">
13
+ import PageStack, {getPageStack} from '@ticatec/uniface-element/PageStack';
14
+ import type {PageStackApi} from '@ticatec/uniface-element/PageStack';
15
+ </script>
16
+ ```
17
+
18
+ ## 基本用法
19
+
20
+ ```svelte
21
+ <script lang="ts">
22
+ import PageStack from '@ticatec/uniface-element/PageStack';
23
+ import DetailPage from './DetailPage.svelte';
24
+
25
+ let stack: PageStack;
26
+
27
+ async function openDetail(id: string) {
28
+ const result = await stack.push(DetailPage, {id});
29
+ console.log('详情页关闭,结果:', result);
30
+ }
31
+ </script>
32
+
33
+ <PageStack bind:this={stack}>
34
+ <HomePage onSelect={openDetail}/>
35
+ </PageStack>
36
+ ```
37
+
38
+ 在 `DetailPage.svelte` 内部,通过 context 拿到 `close`,自己关闭自己(可选带一个返回值)——不需要把 `PageStack` 实例一路通过 props 传下去:
39
+
40
+ ```svelte
41
+ <script lang="ts">
42
+ import {getPageStack} from '@ticatec/uniface-element/PageStack';
43
+
44
+ interface Props { id: string }
45
+ let {id}: Props = $props();
46
+
47
+ const stackApi = getPageStack();
48
+ </script>
49
+
50
+ <button onclick={() => stackApi?.close({saved: true})}>保存并返回</button>
51
+ <button onclick={() => stackApi?.close()}>取消</button>
52
+ ```
53
+
54
+ ## 和外壳布局组合使用
55
+
56
+ `PageStack` 只负责"内容区可以 push/pop 子页面"这一件事。持久的 topbar/bottombar 外壳交给 `app-layout` 里的 `RowsLayout`(或 `HeaderLayout`)来组合:
57
+
58
+ ```svelte
59
+ <script lang="ts">
60
+ import {RowsLayout} from '@ticatec/uniface-element/app-layout';
61
+ import PageStack from '@ticatec/uniface-element/PageStack';
62
+
63
+ let stack: PageStack;
64
+ </script>
65
+
66
+ <RowsLayout topHeight="48px" bottomHeight="56px">
67
+ {#snippet top()}<TopBar/>{/snippet}
68
+ {#snippet bottom()}<BottomBar onBack={() => stack.pop()}/>{/snippet}
69
+
70
+ <PageStack bind:this={stack}>
71
+ <HomePage/>
72
+ </PageStack>
73
+ </RowsLayout>
74
+ ```
75
+
76
+ ## Props
77
+
78
+ | Prop | 类型 | 默认值 | 说明 |
79
+ |------|------|--------|------|
80
+ | `style` | `string` | `''` | 根元素的行内样式 |
81
+ | `transitionDuration` | `number` | `260` | 用来拼出默认的过渡参数(`{x: '100%', duration, easing: cubicOut}`),仅在没有传 `transitionParams` 时生效;设为 `0` 关闭动画。一旦传了 `transitionParams`,这个值会被忽略。 |
82
+ | `transition` | `(node, params?) => TransitionConfig` | `fly`(从右侧滑入) | 换成任意 `svelte/transition` 函数(`fade`、`scale`、`slide`、`fly`……)或者自定义函数,见下面。 |
83
+ | `transitionParams` | `Record<string, any>` | — | 传给 `transition` 的参数。一旦设置,会完全替代基于 `transitionDuration` 生成的默认参数——需要 `duration`/`easing` 的话要自己带上。 |
84
+ | `children` | `Snippet` | — | 根页面内容(深度为 `0`) |
85
+
86
+ ### 更换 push/pop 的动画效果
87
+
88
+ 默认每一帧从右侧滑入(`fly`,贴近常见的移动端"钻入下一层"观感)。通过 `transition`/`transitionParams` 换成任意 `svelte/transition`,或者干脆关掉:
89
+
90
+ ```svelte
91
+ <script lang="ts">
92
+ import PageStack from '@ticatec/uniface-element/PageStack';
93
+ import {fade} from 'svelte/transition';
94
+ </script>
95
+
96
+ <!-- 换成淡入淡出,而不是滑动 -->
97
+ <PageStack transition={fade} transitionParams={{duration: 200}}>
98
+ ...
99
+ </PageStack>
100
+
101
+ <!-- 从左侧滑入,而不是右侧 -->
102
+ <PageStack transitionParams={{x: '-100%', duration: 200}}>
103
+ ...
104
+ </PageStack>
105
+
106
+ <!-- 完全关闭动画 -->
107
+ <PageStack transitionDuration={0}>
108
+ ...
109
+ </PageStack>
110
+ ```
111
+
112
+ 自定义函数也可以,只要符合 Svelte 过渡函数的签名 `(node, params) => {duration?, easing?, css?, tick?}`:
113
+
114
+ ```svelte
115
+ <script lang="ts">
116
+ function popIn(node: Element, {duration = 200} = {}) {
117
+ return {
118
+ duration,
119
+ css: (t: number) => `transform: scale(${0.9 + t * 0.1}); opacity: ${t}`
120
+ };
121
+ }
122
+ </script>
123
+
124
+ <PageStack transition={popIn} transitionParams={{duration: 150}}>
125
+ ...
126
+ </PageStack>
127
+ ```
128
+
129
+ ## 实例方法(通过 `bind:this` 调用)
130
+
131
+ | 方法 | 说明 |
132
+ |------|------|
133
+ | `push<R>(component, props?): Promise<R>` | 把一个组件 push 到栈顶,覆盖当前内容。返回的 Promise 会在该页面调用 `close()` 时以其传入值 resolve(或被外部 `pop()` 的参数 resolve,或在 `reset()` 时以 `undefined` resolve)。 |
134
+ | `pop(result?): void` | 从外部关闭当前栈顶的一帧(例如 bottombar 上的返回按钮)。栈为空时是 no-op。 |
135
+ | `reset(): void` | 立即清空整个栈(不做过渡动画),并把所有还没 resolve 的 `push()` Promise 以 `undefined` resolve。适合"注销登录、切换上下文"这类场景。 |
136
+
137
+ ## Context API(供被 push 进来的页面使用)
138
+
139
+ `getPageStack()` 在 `PageStack` 之外调用返回 `undefined`;在 `PageStack` 内部(无论是根页面 `children`,还是任意一层被 push 进来的页面)调用都能拿到一个 `PageStackApi`:
140
+
141
+ | 字段 | 类型 | 说明 |
142
+ |------|------|------|
143
+ | `close` | `(result?) => void` | 关闭*当前*这一页。在根页面上调用是 no-op(会打一条开发环境的 warning)——根页面不是被 push 进来的,没有"返回上一层"这个语义。 |
144
+ | `push` | `<R>(component, props?) => Promise<R>` | 和 `PageStack` 实例上的 `push` 是同一个函数,让被 push 进来的页面可以继续往下钻,不需要拿到外层组件的引用。 |
145
+ | `depth` | `number` | 当前页面在栈中的深度,根页面为 `0`。 |
146
+
147
+ ## 被 push 的页面内部弹出 Dialog/Popover 等浮层
148
+
149
+ 不需要任何特殊处理。`Dialog` / `Popover` / `FloatingPanel` 都是挂载到 `document.body` 上的(见 `overlay/common/mountSingleton.ts`),并不是 `PageStack` 某一帧的 DOM 后代,所以帧上基于 `transform` 的滑入滑出动画不会影响、裁剪或错位它们的定位。唯一要避免的是:页面自己手写一个 `position: fixed` 的元素而不是用库里现成的浮层组件——那样的元素才会在过渡动画期间被帧的 `transform` "困住"。
150
+
151
+ ## 说明
152
+
153
+ - 只有栈顶的一帧可交互;更早的帧仍然挂载在下面(不是 `display: none`),纯粹是为了让滑动过渡有内容可以"揭示"出来。被覆盖时它们会被标记为 `inert`(移出 Tab 顺序和点击命中),所以这只是"视觉上留在后面",目前还不是"保活/保留滚动位置"机制。
154
+ - 每次 push 都是全新挂载。如果需要在 push/pop 一个来回之后保留某个页面的状态,把状态放在调用方(发起 `push` 的那个组件)里,再通过 `props` 传回去,或者提到某个 store 里。
@@ -0,0 +1,6 @@
1
+ import PageStack from './PageStack.svelte';
2
+ import { getPageStack } from './PageStackContext';
3
+ import type { PageStackApi } from './PageStackContext';
4
+ export default PageStack;
5
+ export { getPageStack };
6
+ export type { PageStackApi };
@@ -0,0 +1,4 @@
1
+ import PageStack from './PageStack.svelte';
2
+ import { getPageStack } from './PageStackContext';
3
+ export default PageStack;
4
+ export { getPageStack };
@@ -25,29 +25,39 @@
25
25
 
26
26
  let pagination = $state<Array<number | null>>([]);
27
27
 
28
+ // Derived rather than mutating the pageNo prop directly: pageNo isn't
29
+ // $bindable, so writing to it only ever changed a local copy for one
30
+ // render and never reached the parent.
31
+ let normalizedPageNo = $derived.by(() => {
32
+ if (pageCount <= 0) return 0;
33
+ if (pageNo < 1) return 1;
34
+ if (pageNo > pageCount) return pageCount;
35
+ return pageNo;
36
+ });
37
+
28
38
  const buildPagination = () => {
29
- if (pageNo < 1) {
30
- pageNo = 1;
31
- } else if (pageNo > pageCount) {
32
- pageNo = pageCount;
39
+ if (pageCount <= 0) {
40
+ pagination = [];
41
+ return;
33
42
  }
43
+ const current = normalizedPageNo;
34
44
  const arr: Array<number | null> = [1];
35
45
  if (pageCount < 6) {
36
46
  for (let i = 2; i <= pageCount; i++) {
37
47
  arr.push(i);
38
48
  }
39
49
  } else {
40
- if (pageNo == 1 || pageNo == 2) {
50
+ if (current == 1 || current == 2) {
41
51
  arr.push(2, 3, null, pageCount);
42
- } else if (pageNo == pageCount || pageNo == pageCount - 1) {
52
+ } else if (current == pageCount || current == pageCount - 1) {
43
53
  arr.push(null, pageCount - 2, pageCount - 1, pageCount);
44
- } else if (pageNo == 3) {
54
+ } else if (current == 3) {
45
55
  arr.push(2, 3, 4, null, pageCount);
46
- } else if (pageNo == pageCount - 2) {
56
+ } else if (current == pageCount - 2) {
47
57
  arr.push(null, pageCount - 3, pageCount - 2, pageCount - 1, pageCount);
48
58
  } else {
49
59
  arr.push(null);
50
- for (let i = pageNo - 1; i <= pageNo + 1; i++) {
60
+ for (let i = current - 1; i <= current + 1; i++) {
51
61
  arr.push(i);
52
62
  }
53
63
  arr.push(null);
@@ -57,22 +67,53 @@
57
67
  pagination = arr;
58
68
  }
59
69
 
60
- const selectPage = (value: number) => (e: MouseEvent) => {
61
- if (pageNo != value) {
70
+ const selectPage = (value: number) => () => {
71
+ if (normalizedPageNo != value) {
62
72
  onchange?.(value);
63
73
  }
64
74
  };
65
75
 
76
+ const handlePageKeyDown = (value: number) => (e: KeyboardEvent) => {
77
+ if (e.key === 'Enter' || e.key === ' ') {
78
+ e.preventDefault();
79
+ selectPage(value)();
80
+ }
81
+ };
82
+
83
+ let hasPrev = $derived(pageCount > 0 && normalizedPageNo > 1);
84
+ let hasNext = $derived(pageCount > 0 && normalizedPageNo < pageCount);
85
+
86
+ const goPrev = () => {
87
+ if (hasPrev) onchange?.(normalizedPageNo - 1);
88
+ };
89
+
90
+ const goNext = () => {
91
+ if (hasNext) onchange?.(normalizedPageNo + 1);
92
+ };
93
+
94
+ const handleNavKeyDown = (action: () => void) => (e: KeyboardEvent) => {
95
+ if (e.key === 'Enter' || e.key === ' ') {
96
+ e.preventDefault();
97
+ action();
98
+ }
99
+ };
100
+
66
101
  $effect(() => {
67
102
  buildPagination();
68
103
  });
69
104
  </script>
70
105
 
71
- <div class="uniface-pagination-panel {type}" class:big {style} data-theme={theme}>
106
+ <div class="uniface-pagination-panel {type}" class:big {style} data-theme={theme} role="navigation" aria-label="Pagination">
107
+ <div class="uniface-page-nav" class:disabled={!hasPrev} role="button" tabindex={hasPrev ? 0 : -1}
108
+ aria-label="Previous page" aria-disabled={!hasPrev} onclick={goPrev} onkeydown={handleNavKeyDown(goPrev)}>
109
+ <i class="icon_google_keyboard_arrow_left" aria-hidden="true"></i>
110
+ </div>
72
111
  {#each pagination as page}
73
112
  {#if page}
74
- <div class="uniface-page-no" onclick={selectPage(page)} aria-hidden="true" class:selected={page === pageNo}>
75
- <span tabindex="-1">
113
+ <div class="uniface-page-no" onclick={selectPage(page)} onkeydown={handlePageKeyDown(page)}
114
+ role="button" tabindex="0" aria-current={page === normalizedPageNo ? 'page' : undefined}
115
+ aria-label="Page {page}" class:selected={page === normalizedPageNo}>
116
+ <span>
76
117
  {page}
77
118
  </span>
78
119
  </div>
@@ -82,6 +123,10 @@
82
123
  </div>
83
124
  {/if}
84
125
  {/each}
126
+ <div class="uniface-page-nav" class:disabled={!hasNext} role="button" tabindex={hasNext ? 0 : -1}
127
+ aria-label="Next page" aria-disabled={!hasNext} onclick={goNext} onkeydown={handleNavKeyDown(goNext)}>
128
+ <i class="icon_google_keyboard_arrow_right" aria-hidden="true"></i>
129
+ </div>
85
130
  </div>
86
131
 
87
132
  <style>
@@ -143,12 +188,67 @@
143
188
  cursor: default;
144
189
  }
145
190
 
191
+ .uniface-page-no:focus-visible {
192
+ outline: 2px solid var(--uniface-primary-border-color, #298DFF);
193
+ outline-offset: 1px;
194
+ }
195
+
146
196
  .uniface-page-no.selected:active {
147
197
  top: 0;
148
198
  color: var(--uniface-pagination-selected-color, #ffffff);
149
199
  background-color: var(--uniface-button-primary-border-color, #42A5F5);
150
200
  }
151
201
 
202
+ .uniface-page-nav {
203
+ position: relative;
204
+ display: inline-flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+ margin: 0 4px;
208
+ width: 2em;
209
+ height: 2em;
210
+ border-radius: 4px;
211
+ color: var(--uniface-default-text-color, #374151);
212
+ border: 1px solid var(--uniface-primary-border-color, #E2E8F0);
213
+ cursor: pointer;
214
+ vertical-align: top;
215
+ }
216
+
217
+ .uniface-page-nav:hover {
218
+ border-color: var(--uniface-default-text-hover-color, #298DFF80);
219
+ }
220
+
221
+ .uniface-page-nav:active {
222
+ top: 1px;
223
+ color: var(--uniface-default-active-text-color, #0061FF);
224
+ }
225
+
226
+ .uniface-page-nav:focus-visible {
227
+ outline: 2px solid var(--uniface-primary-border-color, #298DFF);
228
+ outline-offset: 1px;
229
+ }
230
+
231
+ .uniface-page-nav.disabled {
232
+ cursor: not-allowed;
233
+ opacity: 0.4;
234
+ pointer-events: none;
235
+ }
236
+
237
+ .uniface-pagination-panel.plain .uniface-page-nav {
238
+ background-color: inherit;
239
+ border: none;
240
+ color: #7f7f7f;
241
+ }
242
+
243
+ .uniface-pagination-panel.plain .uniface-page-nav:hover {
244
+ color: var(--uniface-button-primary-border-color, #42A5F5);
245
+ }
246
+
247
+ .uniface-pagination-panel.big .uniface-page-nav {
248
+ width: 3em;
249
+ height: 3em;
250
+ }
251
+
152
252
  /* Plain type */
153
253
  .uniface-pagination-panel.plain .uniface-page-no {
154
254
  background-color: inherit;
@@ -157,10 +157,6 @@
157
157
  color: var(--uniface-progress-step-completed-color, #374151);
158
158
  }
159
159
 
160
- .progress-step-block:last-child {
161
- /* Keep the line for the last step */
162
- }
163
-
164
160
  /* Vertical styles */
165
161
  .progress-step-block.vertical {
166
162
  margin-left: 0;
@@ -198,4 +194,4 @@
198
194
  .progress-step-block.vertical:last-child .step-indicator > div {
199
195
  background: transparent;
200
196
  }
201
- </style>
197
+ </style>
@@ -19,7 +19,7 @@
19
19
  maxTabWidth?: number;
20
20
  tabRender?: TabRender;
21
21
  onrefresh?: TabActionHandler;
22
- onremove?: TabActionHandler;
22
+ onremove?: TabCloseHandler;
23
23
  children?: Snippet;
24
24
  }
25
25
 
@@ -110,6 +110,21 @@
110
110
  }
111
111
  }
112
112
 
113
+ const handleTabKeyDown = (tab: any) => (e: KeyboardEvent) => {
114
+ if (e.key === 'Enter' || e.key === ' ') {
115
+ e.preventDefault();
116
+ handleTabClick(tab)();
117
+ }
118
+ }
119
+
120
+ const handleIconKeyDown = (handler: () => void) => (e: KeyboardEvent) => {
121
+ if (e.key === 'Enter' || e.key === ' ') {
122
+ e.preventDefault();
123
+ e.stopPropagation();
124
+ handler();
125
+ }
126
+ }
127
+
113
128
  const closeTab = (tab: any) => async (e: MouseEvent) => {
114
129
  let pos = tabs.indexOf(tab);
115
130
  if (pos > -1) {
@@ -140,34 +155,42 @@
140
155
  <div class="uniface-tab-panel {className}" style={panelStyle}>
141
156
  <div>
142
157
  <div class="uniface-tabs-wrap" class:simple>
143
- <div class="scroll-left" style="visibility: {showLeft ? 'visible' : 'hidden'}">
144
- <i class="icon_google_arrow_left" aria-hidden="true" onclick={() => scroll(-1)}></i>
158
+ <div class="scroll-left" role="button" tabindex="0" aria-label="Scroll tabs left"
159
+ style="visibility: {showLeft ? 'visible' : 'hidden'}"
160
+ onclick={() => scroll(-1)} onkeydown={handleIconKeyDown(() => scroll(-1))}>
161
+ <i class="icon_google_arrow_left" aria-hidden="true"></i>
145
162
  </div>
146
163
  <div class="tabs-panel" style="overflow: hidden;">
147
- <div style="position: relative; left: {-scrollX.current}px" bind:this={container}>
164
+ <div style="position: relative; left: {-scrollX.current}px" bind:this={container} role="tablist">
148
165
  {#each tabs as tab (tab)}
149
- <div class="uniface-tab{isActive(tab) ? ' active' : ''}" onclick={handleTabClick(tab)} aria-hidden="true">
166
+ <div class="uniface-tab{isActive(tab) ? ' active' : ''}" onclick={handleTabClick(tab)}
167
+ onkeydown={handleTabKeyDown(tab)} role="tab" tabindex={isActive(tab) ? 0 : -1}
168
+ aria-selected={isActive(tab)}>
150
169
  {#if tabRender != null}
151
170
  {@const TabRender = tabRender}
152
171
  <TabRender {tab}
153
- closeTab={onrefresh != null ? closeTab(tab) : null}
172
+ closeTab={(closable === true || (typeof (closable) == "function" && closable(tab))) ? closeTab(tab) : null}
154
173
  reloadTab={onrefresh != null ? reloadTab(tab) : null}
155
174
  closable={closable === true || (typeof (closable) == "function" && closable(tab))}/>
156
175
  {:else}
157
176
  <span>{tab[textField]}</span>
158
177
  {#if onrefresh != null}
159
- <i class="icon_google_refresh tab-refresh" onclick={reloadTab(tab)} aria-hidden="true"></i>
178
+ <i class="icon_google_refresh tab-refresh" role="button" tabindex="0" aria-label="Refresh tab"
179
+ onclick={reloadTab(tab)} onkeydown={handleIconKeyDown(reloadTab(tab))}></i>
160
180
  {/if}
161
181
  {#if tabs.length > 1 && (closable === true || (typeof (closable) == "function" && closable(tab)))}
162
- <i class="icon_google_clear tab-action" onclick={closeTab(tab)} aria-hidden="true"></i>
182
+ <i class="icon_google_clear tab-action" role="button" tabindex="0" aria-label="Close tab"
183
+ onclick={closeTab(tab)} onkeydown={handleIconKeyDown(() => closeTab(tab)(new MouseEvent('click')))}></i>
163
184
  {/if}
164
185
  {/if}
165
186
  </div>
166
187
  {/each}
167
188
  </div>
168
189
  </div>
169
- <div class="scroll-right" style="visibility: {showRight ? 'visible' : 'hidden'}">
170
- <i class="icon_google_arrow_right" aria-hidden="true" onclick={() => scroll(1)}></i>
190
+ <div class="scroll-right" role="button" tabindex="0" aria-label="Scroll tabs right"
191
+ style="visibility: {showRight ? 'visible' : 'hidden'}"
192
+ onclick={() => scroll(1)} onkeydown={handleIconKeyDown(() => scroll(1))}>
193
+ <i class="icon_google_arrow_right" aria-hidden="true"></i>
171
194
  </div>
172
195
  </div>
173
196
  <div class="tab-content" style="flex: 1 1 auto; height: 100%; overflow: hidden">
@@ -218,6 +241,10 @@
218
241
  position: relative;
219
242
  top: 1px;
220
243
  }
244
+
245
+ &:focus-visible {
246
+ outline: 2px solid var(--uniface-primary-border-color, #298DFF);
247
+ }
221
248
  }
222
249
 
223
250
  .tabs-panel {
@@ -291,6 +318,11 @@
291
318
  }
292
319
  }
293
320
 
321
+ &:focus-visible {
322
+ outline: 2px solid var(--uniface-primary-border-color, #298DFF);
323
+ outline-offset: -2px;
324
+ }
325
+
294
326
  &.active {
295
327
  color: var(--uniface-tab-active-text-color, #0061FF);
296
328
  background-color: var(--uniface-tab-bg, #F8FAFC);
@@ -1,5 +1,5 @@
1
1
  import type { Snippet } from "svelte";
2
- import type { TabActionHandler, TabRender } from "../../types";
2
+ import type { TabActionHandler, TabCloseHandler, TabRender } from "../../types";
3
3
  interface Props {
4
4
  class?: string;
5
5
  simple?: boolean;
@@ -13,7 +13,7 @@ interface Props {
13
13
  maxTabWidth?: number;
14
14
  tabRender?: TabRender;
15
15
  onrefresh?: TabActionHandler;
16
- onremove?: TabActionHandler;
16
+ onremove?: TabCloseHandler;
17
17
  children?: Snippet;
18
18
  }
19
19
  declare const Tabs: import("svelte").Component<Props, {}, "tabs" | "activeTab">;
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import {type Snippet} from "svelte";
3
+ import {outsideClick} from "./outsideClick";
3
4
 
4
5
  interface Props {
5
6
  /** 锚定目标元素;面板会出现在目标下方 */
@@ -33,14 +34,6 @@
33
34
  };
34
35
  };
35
36
 
36
- const handleClickOutside = (e: MouseEvent) => {
37
- if (!panelEl) return;
38
- const t = e.target as Node;
39
- if (panelEl.contains(t)) return;
40
- if (target && target.contains(t)) return;
41
- onclose?.();
42
- };
43
-
44
37
  // 把面板挂到 body 下,跳出任何父容器的 overflow / stacking context。
45
38
  // 不依赖 Portal 库,直接 DOM API。
46
39
  const mountToBody = (node: HTMLElement) => {
@@ -55,12 +48,10 @@
55
48
  $effect(() => {
56
49
  if (!panelEl) return;
57
50
  updatePosition();
58
- document.addEventListener('click', handleClickOutside, true);
59
51
  window.addEventListener('resize', updatePosition);
60
52
  // 捕获阶段监听所有 scroll(包括内部容器),保证面板跟随目标
61
53
  window.addEventListener('scroll', updatePosition, true);
62
54
  return () => {
63
- document.removeEventListener('click', handleClickOutside, true);
64
55
  window.removeEventListener('resize', updatePosition);
65
56
  window.removeEventListener('scroll', updatePosition, true);
66
57
  };
@@ -70,14 +61,9 @@
70
61
  <div
71
62
  bind:this={panelEl}
72
63
  class="floating-panel {className}"
73
- style="position: fixed; top: {coords.top}px; left: {coords.left}px; z-index: 1000;"
64
+ style="position: fixed; top: {coords.top}px; left: {coords.left}px; z-index: var(--uniface-z-floating-panel, 1010);"
74
65
  use:mountToBody
66
+ use:outsideClick={{ callback: () => onclose?.(), exclude: [target] }}
75
67
  >
76
68
  {@render children?.()}
77
69
  </div>
78
-
79
- <style>
80
- .floating-panel {
81
- /* 视觉样式由调用方在 children 上控制。这里只负责定位 + 挂载到 body。 */
82
- }
83
- </style>
@@ -1,17 +1,41 @@
1
1
  <script lang="ts">
2
2
 
3
3
  import Portal from "svelte-portal";
4
+ import {onMount} from "svelte";
5
+ import type {Snippet} from "svelte";
6
+ import {acquireScrollLock} from "./scrollLock";
7
+ import {registerEscapeHandler} from "./escapeClose";
4
8
 
5
- export let visible: boolean = false;
9
+ interface Props {
10
+ visible?: boolean;
11
+ children?: Snippet;
12
+ }
6
13
 
14
+ let {visible = $bindable(false), children}: Props = $props();
15
+
16
+ // Regression: this modal surface previously never locked body scroll or
17
+ // closed on Escape.
18
+ onMount(() => {
19
+ const release = acquireScrollLock();
20
+ const unregisterEscape = registerEscapeHandler(() => { visible = false; });
21
+ return () => {
22
+ release();
23
+ unregisterEscape();
24
+ };
25
+ });
7
26
  </script>
8
27
 
9
28
  {#if visible}
10
29
  <Portal target="body">
11
30
  <div class="uniface-mask-overlay">
31
+ <!-- Backdrop click-to-close convenience only; aria-hidden must not be used
32
+ here since it would hide the real, arbitrary interactive content
33
+ rendered via `children` from assistive tech. -->
34
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
35
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
12
36
  <div style="position: relative; width: 100%; height: 100%; overflow: hidden"
13
- on:click={()=>{visible = false}} aria-hidden="true">
14
- <slot></slot>
37
+ onclick={()=>{visible = false}}>
38
+ {@render children?.()}
15
39
  </div>
16
40
  </div>
17
41
  </Portal>
@@ -1,29 +1,8 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: Props & {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
14
- type $$__sveltets_2_PropsWithChildren<Props, Slots> = Props & (Slots extends {
15
- default: any;
16
- } ? Props extends Record<string, never> ? any : {
17
- children?: any;
18
- } : {});
19
- declare const FullscreenOverlay: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWithChildren<{
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
20
3
  visible?: boolean;
21
- }, {
22
- default: {};
23
- }>, {
24
- [evt: string]: CustomEvent<any>;
25
- }, {
26
- default: {};
27
- }, {}, string>;
28
- type FullscreenOverlay = InstanceType<typeof FullscreenOverlay>;
4
+ children?: Snippet;
5
+ }
6
+ declare const FullscreenOverlay: import("svelte").Component<Props, {}, "visible">;
7
+ type FullscreenOverlay = ReturnType<typeof FullscreenOverlay>;
29
8
  export default FullscreenOverlay;
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
2
  import {type Snippet, onMount} from "svelte";
3
3
  import Portal from "svelte-portal";
4
+ import {acquireScrollLock} from "./scrollLock";
5
+ import {registerEscapeHandler} from "./escapeClose";
4
6
 
5
7
  interface Props {
6
8
  visible?: boolean;
@@ -20,15 +22,29 @@
20
22
 
21
23
  onMount(() => {
22
24
  setTimeout(() => hidden = false, 100);
25
+ // Regression: this modal surface previously never locked body scroll
26
+ // or closed on Escape.
27
+ const release = acquireScrollLock();
28
+ const unregisterEscape = registerEscapeHandler(() => {
29
+ if (canClose) visible = false;
30
+ });
31
+ return () => {
32
+ release();
33
+ unregisterEscape();
34
+ };
23
35
  });
24
36
  </script>
25
37
 
26
38
  {#if visible}
27
39
  <Portal target="body">
28
40
  <div class="uniface-mask-overlay" class:hidden data-theme={theme}>
41
+ <!-- Backdrop click-to-close convenience only; aria-hidden must not be used
42
+ here since it would hide the real, arbitrary interactive content
43
+ rendered via `children` from assistive tech. -->
44
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
45
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
29
46
  <div class="uniface-mask-center"
30
- onclick={() => { if (canClose) visible = false; }}
31
- aria-hidden="true">
47
+ onclick={() => { if (canClose) visible = false; }}>
32
48
  <div style="flex: 0 0 auto">
33
49
  {@render children()}
34
50
  </div>