@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
@@ -1,10 +1,9 @@
1
1
  import type MenuItem from "./MenuItem";
2
- import type { OnMenuClick } from "./MenuItem";
3
- import type { IHierarchyData } from "../../lib";
2
+ import type { MenuNode, OnMenuClick } from "./MenuItem";
4
3
  type GetText<T> = (item: T) => string;
5
4
  type Variant = 'vertical' | 'horizontal';
6
5
  interface Props {
7
- menuItems: Array<IHierarchyData<MenuItem>>;
6
+ menuItems: Array<MenuNode>;
8
7
  style?: string;
9
8
  textField: string | GetText<MenuItem>;
10
9
  onItemClick: OnMenuClick;
@@ -1,13 +1,13 @@
1
1
  <script lang="ts">
2
2
  import {slide, fly} from "svelte/transition";
3
3
  import type MenuItem from "./MenuItem";
4
- import type {OnMenuClick} from "./MenuItem";
5
- import type {IHierarchyData} from "../../lib";
4
+ import type {MenuNode, OnMenuClick} from "./MenuItem";
5
+ import NavigatorMenuItem from "./NavigatorMenuItem.svelte";
6
6
 
7
7
  type GetText<T> = (item: T) => string;
8
8
 
9
9
  interface Props {
10
- menu: IHierarchyData<MenuItem>;
10
+ menu: MenuNode;
11
11
  textField: string | GetText<MenuItem>;
12
12
  onMenuItemClick: OnMenuClick;
13
13
  defaultExpand?: boolean;
@@ -26,14 +26,17 @@
26
26
  cascade = false
27
27
  }: Props = $props();
28
28
 
29
- let expand = $state(defaultExpand);
29
+ // defaultExpand seeds local interaction state once; subsequent prop changes must
30
+ // not overwrite a choice the user has already made.
31
+ const getInitialExpanded = () => defaultExpand;
32
+ let expand = $state(getInitialExpanded());
30
33
  let respond = $state(true);
31
34
  let liEl: HTMLElement;
32
35
  /** true = 子菜单飞到左边(右边空间不够时) */
33
36
  let flyLeft = $state(false);
34
37
 
35
- let expandable = $derived(menu.children.length > 0);
36
- let menuText = $derived(typeof textField == 'string' ? menu.data?.[textField] ?? 'Unknown item' : textField(menu.data));
38
+ let expandable = $derived(!!menu.children?.length);
39
+ let menuText = $derived(typeof textField == 'string' ? menu.item?.[textField] ?? 'Unknown item' : textField(menu.item));
37
40
 
38
41
  // 级联模式下,展开前先测一下右边够不够放子菜单
39
42
  const measureFlyout = () => {
@@ -53,16 +56,25 @@
53
56
  }
54
57
  expand = !expand;
55
58
  } else {
56
- onMenuItemClick?.(menu.data);
59
+ onMenuItemClick?.(menu.item);
57
60
  }
58
61
  }
59
62
  }
63
+
64
+ function handleMenuItemKeyDown(e: KeyboardEvent) {
65
+ if (e.key === 'Enter' || e.key === ' ') {
66
+ e.preventDefault();
67
+ handleMenuItemClick();
68
+ }
69
+ }
60
70
  </script>
61
71
 
62
72
  <li bind:this={liEl} class:expandable class:fold={!expand} class:cascade class:fly-left={flyLeft}>
63
73
  <span
64
74
  onclick={handleMenuItemClick}
65
- aria-hidden="true"
75
+ onkeydown={handleMenuItemKeyDown}
76
+ role="button" tabindex="0"
77
+ aria-expanded={expandable ? expand : undefined}
66
78
  >
67
79
  <span class="label">{menuText}</span>
68
80
  {#if expandable}
@@ -74,14 +86,14 @@
74
86
  {#if expandable && expand}
75
87
  {#if cascade}
76
88
  <ul class="flyout" transition:fly={{duration: 200, x: flyLeft ? -20 : 20}}>
77
- {#each menu.children as el}
78
- <svelte:self menu={el} {onMenuItemClick} {textField} {cascade}/>
89
+ {#each menu.children ?? [] as el}
90
+ <NavigatorMenuItem menu={el} {onMenuItemClick} {textField} {cascade}/>
79
91
  {/each}
80
92
  </ul>
81
93
  {:else}
82
94
  <ul class="inline" transition:slide={{duration: 200}}>
83
- {#each menu.children as el}
84
- <svelte:self menu={el} {onMenuItemClick} {textField} {cascade}/>
95
+ {#each menu.children ?? [] as el}
96
+ <NavigatorMenuItem menu={el} {onMenuItemClick} {textField} {cascade}/>
85
97
  {/each}
86
98
  </ul>
87
99
  {/if}
@@ -168,4 +180,4 @@
168
180
  left: auto;
169
181
  right: 100%;
170
182
  }
171
- </style>
183
+ </style>
@@ -1,9 +1,9 @@
1
1
  import type MenuItem from "./MenuItem";
2
- import type { OnMenuClick } from "./MenuItem";
3
- import type { IHierarchyData } from "../../lib";
2
+ import type { MenuNode, OnMenuClick } from "./MenuItem";
3
+ import NavigatorMenuItem from "./NavigatorMenuItem.svelte";
4
4
  type GetText<T> = (item: T) => string;
5
5
  interface Props {
6
- menu: IHierarchyData<MenuItem>;
6
+ menu: MenuNode;
7
7
  textField: string | GetText<MenuItem>;
8
8
  onMenuItemClick: OnMenuClick;
9
9
  defaultExpand?: boolean;
@@ -26,16 +26,23 @@
26
26
  activeItem = item;
27
27
  itemClickHandler?.(item);
28
28
  };
29
+
30
+ const handleKeyDown = (item: any) => (e: KeyboardEvent) => {
31
+ if (e.key === 'Enter' || e.key === ' ') {
32
+ e.preventDefault();
33
+ handleClick(item);
34
+ }
35
+ };
29
36
  </script>
30
37
 
31
38
  <div class="uniface-navigator-panel" {style}>
32
- <div class="navigator-content">
39
+ <div class="navigator-content" role="list">
33
40
  {#each items as item}
34
41
  <div class="uniface-navigator-item {retrieveStatus?.(item) ?? NavItemStatus.Completed}"
35
42
  class:active={activeItem === item}
36
43
  class:highlight={highlights.indexOf(item) > -1}
37
- aria-hidden="true"
38
- onclick={() => handleClick(item)}>
44
+ role="button" tabindex="0" aria-current={activeItem === item ? 'step' : undefined}
45
+ onclick={() => handleClick(item)} onkeydown={handleKeyDown(item)}>
39
46
  <span>{item.text}</span>
40
47
  </div>
41
48
  {/each}
@@ -44,15 +51,7 @@
44
51
 
45
52
  <style>
46
53
  .uniface-navigator-panel {
47
- --uniface-navigator-background-color: var(--uniface-application-bg);
48
- --uniface-navigator-vertical-padding: 16px;
49
- --uniface-navigator-completed-text-color: #374151;
50
- --uniface-navigator-not-start-text-color: #6B7280;
51
- --uniface-navigator-warning-dot-color: #C62828;
52
- --uniface-navigator-hover-text-color: #298DFF;
53
- --uniface-navigator-active-text-color: #0061FF;
54
54
  --uniface-navigator-active-text-weight: 600;
55
- --uniface-navigator-highlight-text-color: #0061FF;
56
55
 
57
56
  position: relative;
58
57
  width: 100%;
@@ -76,7 +75,7 @@
76
75
  font-size: 12px;
77
76
  padding: 4px 8px 4px 24px;
78
77
  position: relative;
79
- background: linear-gradient(to right, transparent 12px, lightgray 12px, lightgray 13px, transparent 13px);
78
+ background: linear-gradient(to right, transparent 12px, var(--uniface-navigator-connector-color) 12px, var(--uniface-navigator-connector-color) 13px, transparent 13px);
80
79
  }
81
80
 
82
81
  .uniface-navigator-item:first-child {
@@ -134,14 +133,15 @@
134
133
  .uniface-navigator-item.active::before {
135
134
  content: "";
136
135
  position: absolute;
137
- left: 5px;
136
+ left: 8px;
138
137
  top: 50%;
139
138
  transform: translateY(-50%);
140
- width: 15px;
141
- height: 15px;
142
- border: 4px solid var(--uniface-navigator-active-text-color);
139
+ box-sizing: border-box;
140
+ width: 10px;
141
+ height: 10px;
142
+ border: 3px solid var(--uniface-navigator-active-text-color);
143
143
  border-radius: 50%;
144
- background-color: white;
144
+ background-color: var(--uniface-navigator-background-color, white);
145
145
  }
146
146
 
147
147
  .uniface-navigator-item.active:hover {
@@ -0,0 +1,160 @@
1
+ <script lang="ts">
2
+ import {untrack} from 'svelte';
3
+ import type {Snippet} from 'svelte';
4
+ import type {TransitionConfig} from 'svelte/transition';
5
+ import {fly} from 'svelte/transition';
6
+ import {cubicOut} from 'svelte/easing';
7
+ import PageStackFrame from './PageStackFrame.svelte';
8
+ import {providePageStackContext} from './PageStackContext';
9
+
10
+ interface StackEntry {
11
+ id: string;
12
+ component: any;
13
+ props: Record<string, any>;
14
+ resolve: (value: any) => void;
15
+ }
16
+
17
+ type TransitionFn = (node: Element, params?: any) => TransitionConfig;
18
+
19
+ interface Props {
20
+ style?: string;
21
+ /** 每一帧 push/pop 的滑入滑出时长(ms)。仅在没有传 transitionParams 时生效;设为 0 关闭动画。 */
22
+ transitionDuration?: number;
23
+ /**
24
+ * 换成任意 svelte/transition 函数(fly/fade/scale/slide...)或自定义的过渡函数。
25
+ * 默认是从右侧滑入的 fly,效果同 transitionParams 未设置时的 {x:'100%', duration:transitionDuration, easing:cubicOut}。
26
+ */
27
+ transition?: TransitionFn;
28
+ /** 传给 transition 函数的参数;一旦提供,会完全替代基于 transitionDuration 生成的默认参数。 */
29
+ transitionParams?: Record<string, any>;
30
+ /** 根页面(初始内容,depth = 0) */
31
+ children?: Snippet;
32
+ }
33
+
34
+ let {
35
+ style = '',
36
+ transitionDuration = 260,
37
+ transition = fly,
38
+ transitionParams,
39
+ children
40
+ }: Props = $props();
41
+
42
+ let resolvedTransitionParams = $derived(
43
+ transitionParams ?? {x: '100%', duration: transitionDuration, easing: cubicOut}
44
+ );
45
+
46
+ let stack = $state<StackEntry[]>([]);
47
+
48
+ const generateId = (): string => `page_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
49
+
50
+ function closeEntry(id: string, result?: any): void {
51
+ // untrack: push()/close()/reset() are meant to be callable from anywhere,
52
+ // including from inside a caller's own $effect (e.g. a page that
53
+ // auto-navigates further based on its props). Reading `stack` (to find/
54
+ // filter it) *and* writing it back in the same synchronous call would
55
+ // otherwise get attributed to whatever effect happens to be running at
56
+ // call time, and the write would then re-trigger that same effect —
57
+ // Svelte reports this as "effect reads and writes the same state" /
58
+ // effect_update_depth_exceeded. untrack() keeps this internal
59
+ // read+write from being picked up as a dependency of an unrelated,
60
+ // currently-running effect; existing subscribers of `stack` (the
61
+ // {#each} below) are unaffected — untrack only suppresses *new*
62
+ // spurious subscriptions created during this call.
63
+ let entry: StackEntry | undefined;
64
+ untrack(() => {
65
+ entry = stack.find(e => e.id === id);
66
+ if (!entry) return;
67
+ // 先移除再 resolve:resolve 可能同步触发调用方的后续逻辑(比如再次 push),
68
+ // 那时候这一帧必须已经不在栈里了。
69
+ stack = stack.filter(e => e.id !== id);
70
+ });
71
+ entry?.resolve(result);
72
+ }
73
+
74
+ /**
75
+ * push 一个组件进栈,覆盖显示在当前内容之上。返回的 Promise 在该页面调用
76
+ * getPageStack().close(result) 时以 result resolve;也可以用 PageStack 实例的
77
+ * pop(result) 从外部(比如 bottombar 的返回按钮)关闭当前栈顶。
78
+ */
79
+ export function push<R = any>(component: any, props: Record<string, any> = {}): Promise<R> {
80
+ return new Promise<R>(resolve => {
81
+ const id = generateId();
82
+ // see closeEntry() above for why this is wrapped in untrack()
83
+ untrack(() => {
84
+ stack = [...stack, {id, component, props, resolve: resolve as (v: any) => void}];
85
+ });
86
+ });
87
+ }
88
+
89
+ /** 关闭当前栈顶的一帧。栈为空时是 no-op。 */
90
+ export function pop(result?: any): void {
91
+ const top = stack[stack.length - 1];
92
+ if (top) closeEntry(top.id, result);
93
+ }
94
+
95
+ /** 清空整个栈,直接回到根页面。不逐帧过渡,适合"注销/切换上下文"这类场景。 */
96
+ export function reset(): void {
97
+ // see closeEntry() above for why this is wrapped in untrack()
98
+ let entries: StackEntry[] = [];
99
+ untrack(() => {
100
+ entries = stack;
101
+ stack = [];
102
+ });
103
+ entries.forEach(entry => entry.resolve(undefined));
104
+ }
105
+
106
+ // 根页面(children)本身也活在这个 context 里,depth = 0,close() 是 no-op——
107
+ // 根页面不是被 push 进来的,没有"关闭返回上一层"的语义,但它应该能拿到 push()
108
+ // 来发起第一层导航,不需要额外用 bind:this 去引用 PageStack 实例。
109
+ providePageStackContext({
110
+ close: () => {
111
+ if (typeof console !== 'undefined') {
112
+ console.warn('[PageStack] root page has no close() semantics — it was not pushed onto the stack');
113
+ }
114
+ },
115
+ push,
116
+ depth: 0
117
+ });
118
+ </script>
119
+
120
+ <div class="uniface-page-stack" {style}>
121
+ <div class="page-stack-root" inert={stack.length > 0}>
122
+ {@render children?.()}
123
+ </div>
124
+ {#each stack as entry, i (entry.id)}
125
+ <div class="page-stack-frame" style="z-index: {i + 1}" inert={i < stack.length - 1}
126
+ transition:transition={resolvedTransitionParams}>
127
+ <PageStackFrame
128
+ id={entry.id}
129
+ component={entry.component}
130
+ props={entry.props}
131
+ depth={i + 1}
132
+ close={(result) => closeEntry(entry.id, result)}
133
+ {push}
134
+ />
135
+ </div>
136
+ {/each}
137
+ </div>
138
+
139
+ <style>
140
+ .uniface-page-stack {
141
+ width: 100%;
142
+ height: 100%;
143
+ position: relative;
144
+ overflow: hidden;
145
+ box-sizing: border-box;
146
+ }
147
+
148
+ .page-stack-root {
149
+ width: 100%;
150
+ height: 100%;
151
+ position: relative;
152
+ }
153
+
154
+ .page-stack-frame {
155
+ position: absolute;
156
+ inset: 0;
157
+ overflow: auto;
158
+ background-color: var(--uniface-page-stack-color-surface, var(--uniface-color-surface));
159
+ }
160
+ </style>
@@ -0,0 +1,24 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { TransitionConfig } from 'svelte/transition';
3
+ type TransitionFn = (node: Element, params?: any) => TransitionConfig;
4
+ interface Props {
5
+ style?: string;
6
+ /** 每一帧 push/pop 的滑入滑出时长(ms)。仅在没有传 transitionParams 时生效;设为 0 关闭动画。 */
7
+ transitionDuration?: number;
8
+ /**
9
+ * 换成任意 svelte/transition 函数(fly/fade/scale/slide...)或自定义的过渡函数。
10
+ * 默认是从右侧滑入的 fly,效果同 transitionParams 未设置时的 {x:'100%', duration:transitionDuration, easing:cubicOut}。
11
+ */
12
+ transition?: TransitionFn;
13
+ /** 传给 transition 函数的参数;一旦提供,会完全替代基于 transitionDuration 生成的默认参数。 */
14
+ transitionParams?: Record<string, any>;
15
+ /** 根页面(初始内容,depth = 0) */
16
+ children?: Snippet;
17
+ }
18
+ declare const PageStack: import("svelte").Component<Props, {
19
+ push: <R = any>(component: any, props?: Record<string, any>) => Promise<R>;
20
+ pop: (result?: any) => void;
21
+ reset: () => void;
22
+ }, "">;
23
+ type PageStack = ReturnType<typeof PageStack>;
24
+ export default PageStack;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 每一层被 push 进 PageStack 的页面组件,都可以通过 getPageStack() 拿到这个 API。
3
+ * 根页面(PageStack 的 children)同样能拿到(depth = 0),但它的 close() 是 no-op——
4
+ * 根页面不是被 push 进来的,没有"关闭并返回上一层"这个语义。
5
+ */
6
+ export interface PageStackApi<T = any> {
7
+ /**
8
+ * 关闭当前页面。对应这一页 push() 时返回的 Promise 会以 result resolve。
9
+ * 根页面调用无效果(会在开发模式下打一条 warning)。
10
+ */
11
+ close: (result?: T) => void;
12
+ /**
13
+ * 在当前页面之上继续 push 一个新页面,和 PageStack 实例上的 push() 是同一个函数——
14
+ * 多级 drill-down 时不需要一路把最外层的 PageStack 引用传下去。
15
+ */
16
+ push: <R = any>(component: any, props?: Record<string, any>) => Promise<R>;
17
+ /** 当前页面在栈中的深度,根页面为 0,第一层 push 进来的页面为 1,以此类推。 */
18
+ depth: number;
19
+ }
20
+ export declare function providePageStackContext(api: PageStackApi): void;
21
+ /**
22
+ * 在被 PageStack push 进来的页面组件(或它的任意后代)里调用,拿到 {close, push, depth}。
23
+ * 在 PageStack 之外调用返回 undefined。
24
+ */
25
+ export declare function getPageStack<T = any>(): PageStackApi<T> | undefined;
@@ -0,0 +1,12 @@
1
+ import { getContext, setContext, hasContext } from 'svelte';
2
+ const CONTEXT_KEY = 'uniface-page-stack';
3
+ export function providePageStackContext(api) {
4
+ setContext(CONTEXT_KEY, api);
5
+ }
6
+ /**
7
+ * 在被 PageStack push 进来的页面组件(或它的任意后代)里调用,拿到 {close, push, depth}。
8
+ * 在 PageStack 之外调用返回 undefined。
9
+ */
10
+ export function getPageStack() {
11
+ return hasContext(CONTEXT_KEY) ? getContext(CONTEXT_KEY) : undefined;
12
+ }
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import {providePageStackContext} from './PageStackContext';
3
+
4
+ interface Props {
5
+ id: string;
6
+ component: any;
7
+ props: Record<string, any>;
8
+ depth: number;
9
+ close: (result?: any) => void;
10
+ push: <R = any>(component: any, props?: Record<string, any>) => Promise<R>;
11
+ }
12
+
13
+ let {id, component: Comp, props, depth, close, push}: Props = $props();
14
+
15
+ // Context identity is fixed for the lifetime of this frame — mirrors
16
+ // DialogWrapper's getInitialDialogId pattern (overlay/dialog/DialogWrapper.svelte):
17
+ // later prop changes (e.g. depth staying the same but close/push identity
18
+ // churning on parent re-render) must not replace what this frame's
19
+ // descendants already captured via getPageStack().
20
+ const getInitialContext = () => ({close, push, depth});
21
+ providePageStackContext(getInitialContext());
22
+ </script>
23
+
24
+ <Comp {...props}/>
@@ -0,0 +1,11 @@
1
+ interface Props {
2
+ id: string;
3
+ component: any;
4
+ props: Record<string, any>;
5
+ depth: number;
6
+ close: (result?: any) => void;
7
+ push: <R = any>(component: any, props?: Record<string, any>) => Promise<R>;
8
+ }
9
+ declare const PageStackFrame: import("svelte").Component<Props, {}, "">;
10
+ type PageStackFrame = ReturnType<typeof PageStackFrame>;
11
+ export default PageStackFrame;
@@ -0,0 +1,154 @@
1
+ # PageStack
2
+
3
+ A local (non-modal) navigation stack: push a component to cover the current content with a new "page", and let it close itself to return to what was underneath. Built for the common app-shell shape — a persistent top bar / bottom bar with a content area that drills down into sub-pages — without pulling in routing.
4
+
5
+ Targets **Svelte 5** and the snippet API. Unlike `Dialog` (`overlay/dialog`), `PageStack` is **not** a global singleton and does **not** portal to `<body>`: each instance owns its own stack, is scoped to whatever container it's rendered in, and multiple independent instances can coexist on the same screen.
6
+
7
+ [中文文档](./README_CN.md)
8
+
9
+ ## Import
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
+ ## Basic usage
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('detail closed with', result);
30
+ }
31
+ </script>
32
+
33
+ <PageStack bind:this={stack}>
34
+ <HomePage onSelect={openDetail}/>
35
+ </PageStack>
36
+ ```
37
+
38
+ Inside `DetailPage.svelte`, close itself (optionally returning a value) via context — no need to thread a reference to the `PageStack` instance down through 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})}>Save & back</button>
51
+ <button onclick={() => stackApi?.close()}>Cancel</button>
52
+ ```
53
+
54
+ ## Composing with the app-shell layouts
55
+
56
+ `PageStack` only owns the "content area drills down into pages" behavior. Combine it with `app-layout`'s `RowsLayout` (or `HeaderLayout`) for the persistent top bar / bottom bar chrome:
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 | Type | Default | Description |
79
+ |------|------|---------|-------------|
80
+ | `style` | `string` | `''` | Inline style applied to the root element |
81
+ | `transitionDuration` | `number` | `260` | Used to build the default transition params (`{x: '100%', duration, easing: cubicOut}`) when `transitionParams` isn't set. `0` disables the animation. Ignored once `transitionParams` is provided. |
82
+ | `transition` | `(node, params?) => TransitionConfig` | `fly` (slide in from the right) | Swap in any `svelte/transition` function (`fade`, `scale`, `slide`, `fly`, ...) or a custom one — see below. |
83
+ | `transitionParams` | `Record<string, any>` | — | Params passed to `transition`. Once set, it fully replaces the `transitionDuration`-derived default — include `duration`/`easing` yourself if you need them. |
84
+ | `children` | `Snippet` | — | Root page content (depth `0`) |
85
+
86
+ ### Changing the push/pop animation
87
+
88
+ By default each pushed frame slides in from the right (`fly`, matching the common mobile drill-down feel). Swap it for any other `svelte/transition` — or turn it off — via `transition`/`transitionParams`:
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
+ <!-- cross-fade instead of sliding -->
97
+ <PageStack transition={fade} transitionParams={{duration: 200}}>
98
+ ...
99
+ </PageStack>
100
+
101
+ <!-- slide in from the left instead of the right -->
102
+ <PageStack transitionParams={{x: '-100%', duration: 200}}>
103
+ ...
104
+ </PageStack>
105
+
106
+ <!-- no animation at all -->
107
+ <PageStack transitionDuration={0}>
108
+ ...
109
+ </PageStack>
110
+ ```
111
+
112
+ A custom function works too, as long as it matches Svelte's transition signature `(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
+ ## Instance methods (`bind:this`)
130
+
131
+ | Method | Description |
132
+ |--------|-------------|
133
+ | `push<R>(component, props?): Promise<R>` | Pushes a component on top of the stack, covering the current content. Resolves with whatever value the pushed page passes to `close()` (or `pop()`'s argument, or `undefined` if the stack is `reset()`). |
134
+ | `pop(result?): void` | Closes the current top frame from outside (e.g. a bottom bar back button). No-op if the stack is empty. |
135
+ | `reset(): void` | Clears the whole stack immediately (no transition) and resolves every pending `push()` promise with `undefined`. Useful for "sign out / switch context" flows. |
136
+
137
+ ## Context API (for a pushed page itself)
138
+
139
+ `getPageStack()` returns `undefined` outside a `PageStack`, and a `PageStackApi` for anything rendered inside one — both the root (`children`, depth `0`) and any pushed frame (depth `1`, `2`, ...):
140
+
141
+ | Field | Type | Description |
142
+ |-------|------|-------------|
143
+ | `close` | `(result?) => void` | Closes *this* page. On the root page this is a no-op (logs a dev warning) — the root wasn't pushed, so it has nothing to "return" to. |
144
+ | `push` | `<R>(component, props?) => Promise<R>` | Same function as the `PageStack` instance's `push` — lets a pushed page drill down further without a reference to the outer component. |
145
+ | `depth` | `number` | This page's depth in the stack. `0` for the root. |
146
+
147
+ ## Popups inside a pushed page
148
+
149
+ No special handling needed. `Dialog` / `Popover` / `FloatingPanel` mount to `document.body` (see `overlay/common/mountSingleton.ts`), so they are never DOM descendants of a `PageStack` frame — a `transform`-based push/pop animation on the frame wrapper can't clip or reposition them. The one thing to avoid: a page hand-rolling its own `position: fixed` element instead of using the library's overlay components — that *would* get trapped by the frame's `transform` during the transition.
150
+
151
+ ## Notes
152
+
153
+ - Only the top frame is interactive; earlier frames stay mounted underneath (not `display: none`) purely so the slide transition has something to reveal. They're marked `inert` (removed from tab order and hit-testing) while covered, so this is a visual keep-behind, not a "keep-alive/preserve scroll position" mechanism today.
154
+ - Every pushed frame is a fresh mount. If you need to preserve a page's state across a push/pop round trip, keep that state in the caller (the component that called `push`) and pass it back in via `props`, or lift it into a store.