@ticatec/uniface-element 5.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app_layout/ClassicLayout.svelte +8 -40
- package/dist/app_layout/ColumnsLayout.svelte +3 -3
- package/dist/app_layout/HeaderLayout.svelte +4 -20
- package/dist/app_layout/RowsLayout.svelte +3 -3
- package/dist/app_layout/SidebarLayout.svelte +4 -20
- package/dist/composite/transfer/Transfer.svelte +12 -6
- package/dist/data_display/accordion/Accordion.svelte +1 -4
- package/dist/data_display/card/CommonCardActionBar.svelte +1 -27
- package/dist/data_display/list_box/ListBox.svelte +6 -8
- package/dist/data_table/lib/clickOutSide.js +6 -5
- package/dist/form/attachment_files/AttachmentEditor.svelte +13 -8
- package/dist/form/attachment_files/FileUploadBar.svelte +5 -2
- package/dist/form/attachment_files/FileUploadPanel.svelte +6 -72
- package/dist/form/checkbox/CheckBox.svelte +2 -7
- package/dist/form/color_picker/ColorPicker.svelte +150 -35
- package/dist/form/color_picker/ColorPicker.svelte.d.ts +7 -25
- package/dist/form/color_picker/README.md +14 -7
- package/dist/form/color_picker/README_CN.md +14 -7
- package/dist/form/common_editor/CommonPicker.svelte +4 -7
- package/dist/form/image-files/ImagePreview.svelte +15 -7
- package/dist/form/memo_editor/MemoEditor.svelte +2 -2
- package/dist/form/number_editor/NumberEditor.svelte +2 -2
- package/dist/form/options_multi_select/OptionsMultiSelect.svelte +1 -9
- package/dist/form/radio_button/RadioButton.svelte +2 -8
- package/dist/form/text_editor/TextEditor.svelte +2 -2
- package/dist/general/button/Button.svelte +2 -0
- package/dist/general/split/Split.svelte +24 -4
- package/dist/navigation/nav_menu/MenuItem.d.ts +4 -2
- package/dist/navigation/nav_menu/NavigatorMenu.svelte +157 -34
- package/dist/navigation/nav_menu/NavigatorMenu.svelte.d.ts +2 -3
- package/dist/navigation/nav_menu/NavigatorMenuItem.svelte +15 -12
- package/dist/navigation/nav_menu/NavigatorMenuItem.svelte.d.ts +3 -3
- package/dist/navigation/progress_step_bar/ProgressStepBlock.svelte +1 -5
- package/dist/overlay/common/FloatingPanel.svelte +0 -6
- package/dist/overlay/common/Popover.svelte +8 -3
- package/dist/overlay/common/uniface-utils.js +0 -1
- package/dist/overlay/context-menu/ContextMenuPanel.svelte +14 -2
- package/dist/overlay/dialog/Dialog.svelte +3 -4
- package/dist/overlay/dialog/DialogWrapper.svelte +5 -2
- package/dist/ticatec-uniface-web.css +26 -6
- package/dist/utils/prefixFilter.d.ts +3 -3
- package/dist/utils/prefixFilter.js +3 -5
- package/package.json +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import {onMount} from "svelte";
|
|
2
3
|
import type MenuItem from "./MenuItem";
|
|
3
|
-
import type {OnMenuClick} from "./MenuItem";
|
|
4
|
-
import type {IHierarchyData} from "../../lib";
|
|
4
|
+
import type {MenuNode, OnMenuClick} from "./MenuItem";
|
|
5
5
|
import NavigatorMenuItem from "./NavigatorMenuItem.svelte";
|
|
6
6
|
|
|
7
7
|
type GetText<T> = (item: T) => string;
|
|
8
8
|
type Variant = 'vertical' | 'horizontal';
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
11
|
-
menuItems: Array<
|
|
11
|
+
menuItems: Array<MenuNode>;
|
|
12
12
|
style?: string;
|
|
13
13
|
textField: string | GetText<MenuItem>;
|
|
14
14
|
onItemClick: OnMenuClick;
|
|
@@ -27,15 +27,46 @@
|
|
|
27
27
|
|
|
28
28
|
// 横向模式:当前展开的 root 索引(同时只开一个 dropdown)
|
|
29
29
|
let openRootIdx = $state<number | null>(null);
|
|
30
|
+
let scrollViewport = $state<HTMLDivElement>();
|
|
31
|
+
let scrollTrack = $state<HTMLDivElement>();
|
|
32
|
+
let horizontalMenu = $state<HTMLElement>();
|
|
33
|
+
let hasOverflow = $state(false);
|
|
34
|
+
let canScrollLeft = $state(false);
|
|
35
|
+
let canScrollRight = $state(false);
|
|
36
|
+
let dropdownLeft = $state(0);
|
|
30
37
|
|
|
31
|
-
const rootText = (root:
|
|
38
|
+
const rootText = (root: MenuNode): string => {
|
|
32
39
|
return typeof textField === 'string'
|
|
33
|
-
? root.
|
|
34
|
-
: textField(root.
|
|
40
|
+
? root.item?.[textField] ?? ''
|
|
41
|
+
: textField(root.item);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const updateDropdownPosition = () => {
|
|
45
|
+
if (openRootIdx === null || !horizontalMenu || !scrollTrack) return;
|
|
46
|
+
const item = scrollTrack.children[openRootIdx] as HTMLElement | undefined;
|
|
47
|
+
if (!item) return;
|
|
48
|
+
dropdownLeft = item.getBoundingClientRect().left - horizontalMenu.getBoundingClientRect().left;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const updateScrollState = () => {
|
|
52
|
+
if (!scrollViewport) return;
|
|
53
|
+
const maxScrollLeft = scrollViewport.scrollWidth - scrollViewport.clientWidth;
|
|
54
|
+
hasOverflow = maxScrollLeft > 1;
|
|
55
|
+
canScrollLeft = scrollViewport.scrollLeft > 1;
|
|
56
|
+
canScrollRight = scrollViewport.scrollLeft < maxScrollLeft - 1;
|
|
57
|
+
updateDropdownPosition();
|
|
35
58
|
};
|
|
36
59
|
|
|
37
60
|
const handleRootClick = (idx: number) => {
|
|
38
61
|
openRootIdx = openRootIdx === idx ? null : idx;
|
|
62
|
+
requestAnimationFrame(updateDropdownPosition);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const scrollMenu = (direction: -1 | 1) => {
|
|
66
|
+
scrollViewport?.scrollBy({
|
|
67
|
+
left: direction * Math.max(scrollViewport.clientWidth * 0.7, 160),
|
|
68
|
+
behavior: 'smooth'
|
|
69
|
+
});
|
|
39
70
|
};
|
|
40
71
|
|
|
41
72
|
// 横向模式下点叶子:触发 onItemClick + 关闭 dropdown
|
|
@@ -59,6 +90,15 @@
|
|
|
59
90
|
}
|
|
60
91
|
};
|
|
61
92
|
};
|
|
93
|
+
|
|
94
|
+
onMount(() => {
|
|
95
|
+
if (!scrollViewport || !scrollTrack) return;
|
|
96
|
+
const resizeObserver = new ResizeObserver(updateScrollState);
|
|
97
|
+
resizeObserver.observe(scrollViewport);
|
|
98
|
+
resizeObserver.observe(scrollTrack);
|
|
99
|
+
updateScrollState();
|
|
100
|
+
return () => resizeObserver.disconnect();
|
|
101
|
+
});
|
|
62
102
|
</script>
|
|
63
103
|
|
|
64
104
|
{#if variant === 'vertical'}
|
|
@@ -72,36 +112,50 @@
|
|
|
72
112
|
{/if}
|
|
73
113
|
</div>
|
|
74
114
|
{:else}
|
|
75
|
-
<nav class="uniface-navigator-menu horizontal" {style} data-theme={theme || undefined}>
|
|
76
|
-
{#
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
tabindex="0"
|
|
85
|
-
>
|
|
86
|
-
<span class="label">{rootText(root)}</span>
|
|
87
|
-
{#if root.children.length > 0}
|
|
88
|
-
<i class="caret" aria-hidden="true"></i>
|
|
89
|
-
{/if}
|
|
90
|
-
{#if openRootIdx === idx && root.children.length > 0}
|
|
115
|
+
<nav bind:this={horizontalMenu} class="uniface-navigator-menu horizontal" {style} data-theme={theme || undefined}>
|
|
116
|
+
{#if hasOverflow}
|
|
117
|
+
<button class="scroll-button previous" aria-label="向左滚动菜单" disabled={!canScrollLeft} onclick={() => scrollMenu(-1)}>
|
|
118
|
+
<i aria-hidden="true"></i>
|
|
119
|
+
</button>
|
|
120
|
+
{/if}
|
|
121
|
+
<div bind:this={scrollViewport} class="scroll-viewport" onscroll={updateScrollState}>
|
|
122
|
+
<div bind:this={scrollTrack} class="scroll-track">
|
|
123
|
+
{#each menuItems as root, idx (idx)}
|
|
91
124
|
<div
|
|
92
|
-
class="
|
|
93
|
-
|
|
94
|
-
|
|
125
|
+
class="root-item"
|
|
126
|
+
class:open={openRootIdx === idx}
|
|
127
|
+
class:leaf={!root.children?.length}
|
|
128
|
+
onclick={(e) => { e.stopPropagation(); handleRootClick(idx); }}
|
|
129
|
+
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleRootClick(idx); }}
|
|
130
|
+
role="button"
|
|
131
|
+
tabindex="0"
|
|
95
132
|
>
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
</ul>
|
|
133
|
+
<span class="label">{rootText(root)}</span>
|
|
134
|
+
{#if root.children?.length}
|
|
135
|
+
<i class="caret" aria-hidden="true"></i>
|
|
136
|
+
{/if}
|
|
101
137
|
</div>
|
|
102
|
-
{/
|
|
138
|
+
{/each}
|
|
103
139
|
</div>
|
|
104
|
-
|
|
140
|
+
</div>
|
|
141
|
+
{#if hasOverflow}
|
|
142
|
+
<button class="scroll-button next" aria-label="向右滚动菜单" disabled={!canScrollRight} onclick={() => scrollMenu(1)}>
|
|
143
|
+
<i aria-hidden="true"></i>
|
|
144
|
+
</button>
|
|
145
|
+
{/if}
|
|
146
|
+
{#if openRootIdx !== null && menuItems[openRootIdx]?.children?.length}
|
|
147
|
+
<div
|
|
148
|
+
class="dropdown-panel"
|
|
149
|
+
style:left="{dropdownLeft}px"
|
|
150
|
+
use:outerClick={() => { openRootIdx = null; }}
|
|
151
|
+
>
|
|
152
|
+
<ul class="dropdown">
|
|
153
|
+
{#each menuItems[openRootIdx].children ?? [] as child}
|
|
154
|
+
<NavigatorMenuItem menu={child} onMenuItemClick={handleLeafClickInDropdown} {textField} cascade={true}/>
|
|
155
|
+
{/each}
|
|
156
|
+
</ul>
|
|
157
|
+
</div>
|
|
158
|
+
{/if}
|
|
105
159
|
</nav>
|
|
106
160
|
{/if}
|
|
107
161
|
|
|
@@ -143,6 +197,76 @@
|
|
|
143
197
|
height: 48px;
|
|
144
198
|
}
|
|
145
199
|
|
|
200
|
+
.scroll-viewport {
|
|
201
|
+
flex: 1 1 auto;
|
|
202
|
+
min-width: 0;
|
|
203
|
+
height: 100%;
|
|
204
|
+
overflow-x: auto;
|
|
205
|
+
overflow-y: hidden;
|
|
206
|
+
scrollbar-width: none;
|
|
207
|
+
scroll-behavior: smooth;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.scroll-viewport::-webkit-scrollbar {
|
|
211
|
+
display: none;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.scroll-track {
|
|
215
|
+
display: flex;
|
|
216
|
+
align-items: center;
|
|
217
|
+
width: max-content;
|
|
218
|
+
min-width: 100%;
|
|
219
|
+
height: 100%;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.scroll-button {
|
|
223
|
+
flex: 0 0 32px;
|
|
224
|
+
width: 32px;
|
|
225
|
+
height: 100%;
|
|
226
|
+
padding: 0;
|
|
227
|
+
border: 0;
|
|
228
|
+
background: var(--uniface-nav-menu-bg);
|
|
229
|
+
color: inherit;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
opacity: 0;
|
|
232
|
+
visibility: hidden;
|
|
233
|
+
pointer-events: none;
|
|
234
|
+
transition: opacity 0.15s ease, visibility 0.15s ease;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.uniface-navigator-menu.horizontal:hover .scroll-button,
|
|
238
|
+
.uniface-navigator-menu.horizontal:focus-within .scroll-button {
|
|
239
|
+
opacity: 1;
|
|
240
|
+
visibility: visible;
|
|
241
|
+
pointer-events: auto;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.scroll-button:hover:not(:disabled) {
|
|
245
|
+
background-color: var(--uniface-nav-menu-hover-bg);
|
|
246
|
+
color: var(--uniface-nav-menu-hover-color);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.scroll-button:disabled {
|
|
250
|
+
cursor: default;
|
|
251
|
+
opacity: 0.35;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.scroll-button i {
|
|
255
|
+
display: inline-block;
|
|
256
|
+
width: 8px;
|
|
257
|
+
height: 8px;
|
|
258
|
+
border-top: 2px solid currentColor;
|
|
259
|
+
border-left: 2px solid currentColor;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.scroll-button.previous i {
|
|
263
|
+
transform: rotate(-45deg);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.scroll-button.next i {
|
|
267
|
+
transform: rotate(135deg);
|
|
268
|
+
}
|
|
269
|
+
|
|
146
270
|
.uniface-navigator-menu.horizontal .root-item {
|
|
147
271
|
position: relative;
|
|
148
272
|
padding: 0 16px;
|
|
@@ -176,7 +300,6 @@
|
|
|
176
300
|
.dropdown-panel {
|
|
177
301
|
position: absolute;
|
|
178
302
|
top: 100%;
|
|
179
|
-
left: 0;
|
|
180
303
|
z-index: 1000;
|
|
181
304
|
}
|
|
182
305
|
|
|
@@ -190,4 +313,4 @@
|
|
|
190
313
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
191
314
|
min-width: 180px;
|
|
192
315
|
}
|
|
193
|
-
</style>
|
|
316
|
+
</style>
|
|
@@ -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<
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
36
|
-
let menuText = $derived(typeof textField == 'string' ? menu.
|
|
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,7 +56,7 @@
|
|
|
53
56
|
}
|
|
54
57
|
expand = !expand;
|
|
55
58
|
} else {
|
|
56
|
-
onMenuItemClick?.(menu.
|
|
59
|
+
onMenuItemClick?.(menu.item);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
}
|
|
@@ -74,14 +77,14 @@
|
|
|
74
77
|
{#if expandable && expand}
|
|
75
78
|
{#if cascade}
|
|
76
79
|
<ul class="flyout" transition:fly={{duration: 200, x: flyLeft ? -20 : 20}}>
|
|
77
|
-
{#each menu.children as el}
|
|
78
|
-
<
|
|
80
|
+
{#each menu.children ?? [] as el}
|
|
81
|
+
<NavigatorMenuItem menu={el} {onMenuItemClick} {textField} {cascade}/>
|
|
79
82
|
{/each}
|
|
80
83
|
</ul>
|
|
81
84
|
{:else}
|
|
82
85
|
<ul class="inline" transition:slide={{duration: 200}}>
|
|
83
|
-
{#each menu.children as el}
|
|
84
|
-
<
|
|
86
|
+
{#each menu.children ?? [] as el}
|
|
87
|
+
<NavigatorMenuItem menu={el} {onMenuItemClick} {textField} {cascade}/>
|
|
85
88
|
{/each}
|
|
86
89
|
</ul>
|
|
87
90
|
{/if}
|
|
@@ -168,4 +171,4 @@
|
|
|
168
171
|
left: auto;
|
|
169
172
|
right: 100%;
|
|
170
173
|
}
|
|
171
|
-
</style>
|
|
174
|
+
</style>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type MenuItem from "./MenuItem";
|
|
2
|
-
import type { OnMenuClick } from "./MenuItem";
|
|
3
|
-
import
|
|
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:
|
|
6
|
+
menu: MenuNode;
|
|
7
7
|
textField: string | GetText<MenuItem>;
|
|
8
8
|
onMenuItemClick: OnMenuClick;
|
|
9
9
|
defaultExpand?: boolean;
|
|
@@ -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>
|
|
@@ -110,8 +110,13 @@
|
|
|
110
110
|
}, 50);
|
|
111
111
|
}, 50); // 延迟显示 portal
|
|
112
112
|
window.addEventListener("resize", updatePosition);
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// capture:true — scroll events don't bubble, so a listener on the bubble phase
|
|
114
|
+
// only ever sees window/document-level scroll. Capturing catches scroll on any
|
|
115
|
+
// nested scrollable ancestor (a table body, a dialog's scroll area, etc.) too,
|
|
116
|
+
// which is what keeps the popover pinned to its anchor while that container
|
|
117
|
+
// scrolls. Matches the pattern in FloatingPanel.svelte.
|
|
118
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
119
|
+
const interval = setInterval(checkPosition, 10); // 每 10ms 检测一次
|
|
115
120
|
const resizeObserver = new ResizeObserver(entries => {
|
|
116
121
|
for (let entry of entries) {
|
|
117
122
|
updatePosition();
|
|
@@ -120,7 +125,7 @@
|
|
|
120
125
|
resizeObserver.observe(contentPanel);
|
|
121
126
|
return () => {
|
|
122
127
|
window.removeEventListener("resize", updatePosition);
|
|
123
|
-
|
|
128
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
124
129
|
resizeObserver.disconnect();
|
|
125
130
|
clearInterval(interval);
|
|
126
131
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export function clickOutside(node) {
|
|
2
2
|
const handleClick = (event) => {
|
|
3
|
-
// @ts-ignore 因为 event.target 是 EventTarget,不能直接传给 contains
|
|
4
3
|
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
|
5
4
|
node.dispatchEvent(new CustomEvent('outerClick', {
|
|
6
5
|
bubbles: true, // 通常自定义事件要冒泡,方便捕获
|
|
@@ -78,6 +78,16 @@
|
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
function activateItem(event: MouseEvent | KeyboardEvent, index: number) {
|
|
83
|
+
if (event instanceof KeyboardEvent && event.key !== 'Enter' && event.key !== ' ') return;
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
const item = contextMenuSvelte.items[index];
|
|
87
|
+
if (item.disabled) return;
|
|
88
|
+
item.action?.();
|
|
89
|
+
contextMenuSvelte.hide();
|
|
90
|
+
}
|
|
81
91
|
</script>
|
|
82
92
|
|
|
83
93
|
{#if contextMenuSvelte.visible}
|
|
@@ -103,8 +113,10 @@
|
|
|
103
113
|
class:focused={focusedIndex === index}
|
|
104
114
|
style={item.style || ''}
|
|
105
115
|
role="menuitem"
|
|
116
|
+
tabindex="-1"
|
|
106
117
|
aria-disabled={item.disabled}
|
|
107
|
-
onclick={() =>
|
|
118
|
+
onclick={(event) => activateItem(event, index)}
|
|
119
|
+
onkeydown={(event) => activateItem(event, index)}
|
|
108
120
|
onmouseenter={() => { if(!item.disabled) focusedIndex = index; }}
|
|
109
121
|
>
|
|
110
122
|
{#if item.icon}
|
|
@@ -184,4 +196,4 @@
|
|
|
184
196
|
height: 1px;
|
|
185
197
|
background-color: var(--uniface-contextmenu-color-separator);
|
|
186
198
|
margin: var(--uniface-contextmenu-separator-margin) 0;
|
|
187
|
-
}</style>
|
|
199
|
+
}</style>
|
|
@@ -128,11 +128,10 @@
|
|
|
128
128
|
);
|
|
129
129
|
</script>
|
|
130
130
|
|
|
131
|
-
<svelte:window onkeydown={handleWindowKeyDown}/>
|
|
131
|
+
<svelte:window onkeydown={handleWindowKeyDown} onmousemove={handleMouseMove} onmouseup={handleMouseUp}/>
|
|
132
132
|
|
|
133
133
|
<div class="uniface-dialog" data-dialog-id={dialogId}
|
|
134
|
-
style="{startMove ? 'cursor: move' : ''}"
|
|
135
|
-
onmousemove={handleMouseMove} onmouseleave={handleMouseUp}>
|
|
134
|
+
style="{startMove ? 'cursor: move' : ''}">
|
|
136
135
|
<div class="uniface-dialog-box"
|
|
137
136
|
style="{dialog$style}; top: {top}px; left: {left}px; width: {width}; height: {height};">
|
|
138
137
|
<div class="title-bar" onmousedown={handleMouseDown} onmouseup={handleMouseUp} aria-hidden="true">
|
|
@@ -230,4 +229,4 @@
|
|
|
230
229
|
align-items: center;
|
|
231
230
|
justify-content: flex-end;
|
|
232
231
|
border-top: 1px solid var(--uniface-dialog-divider-color, #F0F0F0);
|
|
233
|
-
}</style>
|
|
232
|
+
}</style>
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
let {id, component: Comp, params}: {id: string, component: any, params: Record<string, any>} = $props();
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// Context identity is fixed for the lifetime of this wrapper; later prop changes
|
|
7
|
+
// must not replace the dialog registration used by its descendants.
|
|
8
|
+
const getInitialDialogId = () => id;
|
|
9
|
+
setContext('dialogId', getInitialDialogId());
|
|
7
10
|
</script>
|
|
8
11
|
|
|
9
|
-
<Comp {...params}/>
|
|
12
|
+
<Comp {...params}/>
|
|
@@ -412,6 +412,11 @@
|
|
|
412
412
|
--uniface-progress-step-completed-color: #374151;
|
|
413
413
|
--uniface-progress-step-completed-icon-bg: #0061FF1A;
|
|
414
414
|
--uniface-progress-step-completed-icon-color: #0061FF;
|
|
415
|
+
/* TreeView Tokens */
|
|
416
|
+
--uniface-treeview-chevron-color: #6b7280;
|
|
417
|
+
--uniface-treeview-chevron-hover-color: #111827;
|
|
418
|
+
--uniface-treeview-item-bg: #EEF4FF;
|
|
419
|
+
--uniface-treeview-item-selected-color: #0061FF;
|
|
415
420
|
--uniface-listbox-separator-color: #F0F0F0;
|
|
416
421
|
--uniface-list-box-hover-surface: #FFFFE1;
|
|
417
422
|
--uniface-list-box-selected-surface: #b1c8f6;
|
|
@@ -521,6 +526,11 @@
|
|
|
521
526
|
--uniface-calendar-color-selected-surface: #2563eb;
|
|
522
527
|
--uniface-calendar-color-selected: #ffffff;
|
|
523
528
|
--uniface-calendar-color-day-surface-hover: rgba(255, 255, 255, 0.08);
|
|
529
|
+
/* TreeView Tokens */
|
|
530
|
+
--uniface-treeview-chevron-color: #9ca3af;
|
|
531
|
+
--uniface-treeview-chevron-hover-color: #e5e7eb;
|
|
532
|
+
--uniface-treeview-item-bg: rgba(255, 255, 255, 0.08);
|
|
533
|
+
--uniface-treeview-item-selected-color: #60a5fa;
|
|
524
534
|
/* Menu Separator */
|
|
525
535
|
--uniface-menu-separator-color: rgba(255, 255, 255, 0.08);
|
|
526
536
|
/* Drawer Tokens */
|
|
@@ -991,11 +1001,6 @@
|
|
|
991
1001
|
background-color: var(--uniface-editor-color-filled-surface);
|
|
992
1002
|
transition: background-color 0.2s ease, border-color 0.2s ease;
|
|
993
1003
|
}
|
|
994
|
-
.uniface-form-editor.plain {
|
|
995
|
-
background-color: transparent;
|
|
996
|
-
border-bottom-color: transparent;
|
|
997
|
-
border-radius: 0;
|
|
998
|
-
}
|
|
999
1004
|
.uniface-form-editor {
|
|
1000
1005
|
/* 1.2 状态感知拦截 (States) */
|
|
1001
1006
|
}
|
|
@@ -1005,6 +1010,14 @@
|
|
|
1005
1010
|
.uniface-form-editor:focus-within {
|
|
1006
1011
|
border-color: var(--uniface-editor-color-active-border);
|
|
1007
1012
|
}
|
|
1013
|
+
.uniface-form-editor.plain {
|
|
1014
|
+
background-color: transparent;
|
|
1015
|
+
border-bottom-color: transparent;
|
|
1016
|
+
border-radius: 0;
|
|
1017
|
+
}
|
|
1018
|
+
.uniface-form-editor.plain.focus-within {
|
|
1019
|
+
border-color: transparent;
|
|
1020
|
+
}
|
|
1008
1021
|
.uniface-form-editor.disabled {
|
|
1009
1022
|
color: var(--uniface-editor-color-disabled);
|
|
1010
1023
|
cursor: not-allowed;
|
|
@@ -1201,7 +1214,14 @@
|
|
|
1201
1214
|
}
|
|
1202
1215
|
|
|
1203
1216
|
.uniface-popover-wrapper {
|
|
1204
|
-
position
|
|
1217
|
+
/* Popover.svelte computes position.top/left from target.getBoundingClientRect(),
|
|
1218
|
+
which is viewport-relative. `position: absolute` places this element relative to
|
|
1219
|
+
the document instead, so it drifted away from its anchor by the page's (or any
|
|
1220
|
+
ancestor's) scroll offset. `fixed` keeps the same coordinate frame as the values
|
|
1221
|
+
driving top/left, and combined with the scroll listener in Popover.svelte the
|
|
1222
|
+
overlay stays pinned to its anchor while scrolling. See also ContextMenuPanel.svelte
|
|
1223
|
+
and FloatingPanel.svelte, which use the same fixed + viewport-relative-coords pattern. */
|
|
1224
|
+
position: fixed;
|
|
1205
1225
|
display: block;
|
|
1206
1226
|
z-index: 100;
|
|
1207
1227
|
opacity: 1;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
declare const prefixFilter: (attrs:
|
|
2
|
-
declare const eventFilter: (attrs:
|
|
3
|
-
declare const filterProps: (attrs:
|
|
1
|
+
declare const prefixFilter: (attrs: Record<string, unknown>, prefix: string, excludes?: Array<string>) => Record<string, unknown>;
|
|
2
|
+
declare const eventFilter: (attrs: Record<string, unknown>) => Record<string, unknown>;
|
|
3
|
+
declare const filterProps: (attrs: Record<string, unknown>, keys: Array<string>) => Record<string, unknown>;
|
|
4
4
|
export default prefixFilter;
|
|
5
5
|
export { eventFilter, filterProps };
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { keyboardAttrs } from "../form/common_editor/EditorEvents";
|
|
2
2
|
const prefixFilter = (attrs, prefix, excludes = []) => {
|
|
3
|
-
|
|
3
|
+
const obj = {};
|
|
4
4
|
let len = prefix.length;
|
|
5
5
|
for (let attr in attrs) {
|
|
6
6
|
if (attr.startsWith(prefix) && attr.length > len) {
|
|
7
7
|
let newAttr = attr.substring(len);
|
|
8
8
|
if (excludes.indexOf(newAttr) < 0) {
|
|
9
|
-
// @ts-ignore
|
|
10
9
|
obj[newAttr] = attrs[attr];
|
|
11
10
|
}
|
|
12
11
|
}
|
|
@@ -14,17 +13,16 @@ const prefixFilter = (attrs, prefix, excludes = []) => {
|
|
|
14
13
|
return obj;
|
|
15
14
|
};
|
|
16
15
|
const eventFilter = (attrs) => {
|
|
17
|
-
|
|
16
|
+
const obj = {};
|
|
18
17
|
for (let attr in attrs) {
|
|
19
18
|
if (attr.startsWith('on') && attr.length > 2) {
|
|
20
|
-
// @ts-ignore
|
|
21
19
|
obj[attr] = attrs[attr];
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
return obj;
|
|
25
23
|
};
|
|
26
24
|
const filterProps = (attrs, keys) => {
|
|
27
|
-
|
|
25
|
+
const obj = {};
|
|
28
26
|
for (let attr in attrs) {
|
|
29
27
|
if (keys.indexOf(attr) > -1) {
|
|
30
28
|
obj[attr] = attrs[attr];
|
package/package.json
CHANGED