react-extjs 0.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/LICENSE +21 -0
- package/package.json +36 -0
- package/src/components/AbsoluteLayout.tsx +206 -0
- package/src/components/Accordion.tsx +266 -0
- package/src/components/AnchorLayout.tsx +131 -0
- package/src/components/BorderLayout.tsx +512 -0
- package/src/components/Box.tsx +289 -0
- package/src/components/Button.tsx +300 -0
- package/src/components/ButtonGroup.tsx +170 -0
- package/src/components/CalendarPicker.tsx +431 -0
- package/src/components/CardLayout.tsx +122 -0
- package/src/components/CenterLayout.tsx +95 -0
- package/src/components/CheckboxGroup.tsx +200 -0
- package/src/components/ColorMenu.tsx +195 -0
- package/src/components/ColorPalette.tsx +126 -0
- package/src/components/ColumnLayout.tsx +183 -0
- package/src/components/ColumnTree.tsx +383 -0
- package/src/components/CycleButton.tsx +302 -0
- package/src/components/DataView.tsx +244 -0
- package/src/components/DataViewMore.tsx +337 -0
- package/src/components/DataViewTransition.tsx +235 -0
- package/src/components/DateMenu.tsx +163 -0
- package/src/components/DatePicker.tsx +387 -0
- package/src/components/DisplayField.tsx +73 -0
- package/src/components/Editor.tsx +214 -0
- package/src/components/EditorGrid.tsx +361 -0
- package/src/components/FieldLabeler.tsx +81 -0
- package/src/components/FieldReplicator.tsx +135 -0
- package/src/components/FieldSet.tsx +149 -0
- package/src/components/FitLayout.tsx +76 -0
- package/src/components/Form.tsx +3484 -0
- package/src/components/Grid.tsx +2173 -0
- package/src/components/GridFilters.tsx +332 -0
- package/src/components/GroupTab.tsx +274 -0
- package/src/components/HtmlEditor.tsx +391 -0
- package/src/components/ImageOrganizer.tsx +984 -0
- package/src/components/ImageOrganizerService.ts +361 -0
- package/src/components/ListView.tsx +221 -0
- package/src/components/LoadMask.tsx +74 -0
- package/src/components/Menu.tsx +227 -0
- package/src/components/MessageBox.tsx +138 -0
- package/src/components/MultiSlider.tsx +369 -0
- package/src/components/Organizer.tsx +403 -0
- package/src/components/PagingToolbar.tsx +255 -0
- package/src/components/Panel.tsx +451 -0
- package/src/components/ProgressBar.tsx +127 -0
- package/src/components/PropertyGrid.tsx +142 -0
- package/src/components/QuickTip.tsx +174 -0
- package/src/components/Resizable.tsx +241 -0
- package/src/components/RowLayout.tsx +126 -0
- package/src/components/Slider.tsx +353 -0
- package/src/components/SliderField.tsx +206 -0
- package/src/components/SpinnerField.tsx +141 -0
- package/src/components/SplitBar.tsx +215 -0
- package/src/components/Spotlight.tsx +262 -0
- package/src/components/StatusBar.tsx +118 -0
- package/src/components/TabPanel.tsx +540 -0
- package/src/components/TableLayout.tsx +132 -0
- package/src/components/ThemeSwitcher.tsx +167 -0
- package/src/components/ToolTip.tsx +226 -0
- package/src/components/Toolbar.tsx +321 -0
- package/src/components/Tree.tsx +809 -0
- package/src/components/TreeGrid.tsx +418 -0
- package/src/components/Viewport.tsx +218 -0
- package/src/components/Window.tsx +441 -0
- package/src/components/Writer.tsx +303 -0
- package/src/components/XmlTreeLoader.tsx +233 -0
- package/src/components/dnd/DndContext.tsx +328 -0
- package/src/components/dnd/DragSource.tsx +202 -0
- package/src/components/dnd/DragZone.tsx +66 -0
- package/src/components/dnd/DropTarget.tsx +86 -0
- package/src/components/dnd/DropZone.tsx +166 -0
- package/src/components/dnd/PanelProxy.tsx +81 -0
- package/src/components/dnd/PanelResizer.tsx +143 -0
- package/src/components/dnd/Portal.tsx +254 -0
- package/src/components/dnd/Reorderer.tsx +73 -0
- package/src/components/dnd/ToolbarReorderer.tsx +226 -0
- package/src/main.tsx +2649 -0
- package/src/styles/ext-theme.css +889 -0
- package/src/util/theme.ts +95 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useImperativeHandle,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 菜单项定义(保真复刻 Ext.menu.Item / Ext.menu.Separator 的常用字段)。
|
|
11
|
+
*/
|
|
12
|
+
export interface MenuItem {
|
|
13
|
+
text?: string;
|
|
14
|
+
iconCls?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** 为 true 时渲染为分隔线(li.x-menu-sep-li > span.x-menu-sep) */
|
|
17
|
+
separator?: boolean;
|
|
18
|
+
/** 点击回调(等价 Ext 的 handler) */
|
|
19
|
+
handler?: () => void;
|
|
20
|
+
/** 子菜单项(存在则本项渲染 x-menu-item-arrow,hover 时右侧展开子菜单) */
|
|
21
|
+
menu?: MenuItem[];
|
|
22
|
+
/** 单/多选标记(Ext.menu.CheckItem:渲染 x-menu-item-checked,x-cycle-menu 下高亮背景) */
|
|
23
|
+
checked?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* 自定义渲染内容(对齐 Ext.menu.Item 承载任意内容 / Ext.menu.Adapter)。
|
|
26
|
+
* 传入则不渲染标准 icon+text,直接在 a.x-menu-item 内渲染该节点——
|
|
27
|
+
* 用于 GridFilters 等需要在菜单内嵌入文本框/数字区间/日期/复选列表的插件。
|
|
28
|
+
*/
|
|
29
|
+
render?: React.ReactNode | (() => React.ReactNode);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MenuProps {
|
|
33
|
+
items: MenuItem[];
|
|
34
|
+
onItemClick?: (item: MenuItem, index: number) => void;
|
|
35
|
+
/** 根容器附加样式(子菜单用它定位,如 position/left/top/zIndex) */
|
|
36
|
+
style?: React.CSSProperties;
|
|
37
|
+
/** 根容器附加 class(如 x-cycle-menu),用于 CycleButton 菜单高亮 */
|
|
38
|
+
rootClassName?: string;
|
|
39
|
+
/** 演示/测试用:强制常开指定索引的子菜单(默认 undefined,仍由 hover 控制)。 */
|
|
40
|
+
submenuAlwaysOpen?: number;
|
|
41
|
+
/** 透传 ref:暴露 add / remove / getItems / getSubmenu(对齐 Ext.menu.Menu.add / remove)。 */
|
|
42
|
+
ref?: React.Ref<MenuHandle>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Menu 命令句柄(对齐 Ext.menu.Menu.add / remove)。
|
|
47
|
+
* - add(item, id?):追加一项到当前项列表尾部,返回稳定 id(便于后续 remove)。
|
|
48
|
+
* - remove(id):按 id 移除一项。
|
|
49
|
+
* - getItems():返回当前项列表(含 id),顺序为 [原始 items, 动态追加项]。
|
|
50
|
+
* - getSubmenu(id):返回指定项(其 menu 子菜单)的 MenuHandle,便于对级联子菜单动态增删;
|
|
51
|
+
* 仅在该子菜单已渲染(常开或 hover 展开)时可用,否则返回 undefined。
|
|
52
|
+
*/
|
|
53
|
+
export interface MenuHandle {
|
|
54
|
+
add: (item: MenuItem, id?: string) => string;
|
|
55
|
+
remove: (id: string) => void;
|
|
56
|
+
getItems: () => Array<{ id: string; item: MenuItem }>;
|
|
57
|
+
getSubmenu: (id: string) => MenuHandle | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 内部受管项:在原始项之外附加稳定 id 以支持动态增删。 */
|
|
61
|
+
interface ManagedItem {
|
|
62
|
+
id: string;
|
|
63
|
+
item: MenuItem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Menu —— 保真复刻 Ext.menu.Menu(src/widgets/menu/Menu.js onRender 193-200 + Item.js itemTpl)。
|
|
68
|
+
*
|
|
69
|
+
* 保真契约(DOM/class 严格对齐源码):
|
|
70
|
+
* - 根:x-menu x-menu-floating x-layer(绝对定位,z-index 15000)
|
|
71
|
+
* - a.x-menu-focus(隐藏聚焦元素,等同 onRender 198)+ ul.x-menu-list
|
|
72
|
+
* - 普通项:li.x-menu-list-item > a.x-menu-item[ x-menu-item-arrow][ cls] hidefocus unselectable href="#"
|
|
73
|
+
* > img.x-menu-item-icon {iconCls} + span.x-menu-item-text
|
|
74
|
+
* - 分隔项:li.x-menu-sep-li > span.x-menu-sep(Separator.js 38)
|
|
75
|
+
* - 子菜单:项含 menu 时 <a> 加 x-menu-item-arrow(Item.js 36749,CSS 引 menu-parent.gif),
|
|
76
|
+
* hover(onMouseOver)时在 li 右侧展开嵌套 .x-menu(position:absolute left:100% top:0)。
|
|
77
|
+
* - 状态:hover -> li.x-menu-item-active(onMouseOver 309);disabled -> li.x-menu-item-disabled
|
|
78
|
+
*
|
|
79
|
+
* 交互(行为契约):点击项 -> 执行 handler + 触发 onItemClick(onClick 255);
|
|
80
|
+
* 关闭逻辑由外层(如 Button)统一管理(外部点击 / Esc / 点项)。
|
|
81
|
+
*/
|
|
82
|
+
export const Menu = forwardRef<MenuHandle, MenuProps>((
|
|
83
|
+
{ items, onItemClick, style, rootClassName, submenuAlwaysOpen },
|
|
84
|
+
ref,
|
|
85
|
+
) => {
|
|
86
|
+
const [active, setActive] = useState<number | null>(null);
|
|
87
|
+
const [openSub, setOpenSub] = useState<number | null>(null);
|
|
88
|
+
|
|
89
|
+
// items prop 恒为静态项来源(受控);动态增删只影响独立的 dynamicItems 状态,
|
|
90
|
+
// 绝不会被 prop 变更冲掉。未开动态时 managed === items,DOM 完全一致(像素基线零回归)。
|
|
91
|
+
const [dynamicItems, setDynamicItems] = useState<ManagedItem[]>([]);
|
|
92
|
+
const [removedStatic, setRemovedStatic] = useState<Set<string>>(new Set());
|
|
93
|
+
const seqRef = useRef(0);
|
|
94
|
+
// 登记各子菜单(按父项 id)的句柄,供 getSubmenu 动态增删级联子菜单。
|
|
95
|
+
const submenuRefs = useRef<Map<string, MenuHandle>>(new Map());
|
|
96
|
+
|
|
97
|
+
const managed = useMemo<ManagedItem[]>(
|
|
98
|
+
() => [
|
|
99
|
+
...items
|
|
100
|
+
.map((it, i) => ({ id: `seed-${i}`, item: it }))
|
|
101
|
+
.filter((m) => !removedStatic.has(m.id)),
|
|
102
|
+
...dynamicItems,
|
|
103
|
+
],
|
|
104
|
+
[items, dynamicItems, removedStatic],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
useImperativeHandle(ref, () => ({
|
|
108
|
+
add: (item, id) => {
|
|
109
|
+
const newId = id ?? `mi-${++seqRef.current}`;
|
|
110
|
+
setDynamicItems((prev) => [...prev, { id: newId, item }]);
|
|
111
|
+
return newId;
|
|
112
|
+
},
|
|
113
|
+
remove: (id) => {
|
|
114
|
+
// 动态追加项从 dynamicItems 删除;原始 seed 项登记到 removedStatic(对齐 Ext menu.remove 可删任意项)
|
|
115
|
+
if (id.startsWith('seed-')) {
|
|
116
|
+
setRemovedStatic((prev) => {
|
|
117
|
+
const next = new Set(prev);
|
|
118
|
+
next.add(id);
|
|
119
|
+
return next;
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
setDynamicItems((prev) => prev.filter((m) => m.id !== id));
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
getItems: () => managed,
|
|
126
|
+
getSubmenu: (id) => submenuRefs.current.get(id),
|
|
127
|
+
}), [managed]);
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div
|
|
131
|
+
className={['x-menu x-menu-floating x-layer', rootClassName].filter(Boolean).join(' ')}
|
|
132
|
+
style={{ position: 'absolute', zIndex: 15000, visibility: 'visible', ...style }}
|
|
133
|
+
>
|
|
134
|
+
<a href="#" className="x-menu-focus" tabIndex={-1} onClick={(e) => e.preventDefault()} />
|
|
135
|
+
{/* 级联子菜单以嵌套 DOM 渲染在 li 内(便于 hover 进入),但 .x-menu-list 默认
|
|
136
|
+
overflow:hidden 会裁切延伸到右侧的子菜单。当有子菜单展开时临时放开 overflow,
|
|
137
|
+
逃出裁切;关闭后恢复 CSS 默认值(兼容启用滚动的长菜单)。 */}
|
|
138
|
+
<ul className="x-menu-list" style={openSub !== null ? { overflow: 'visible' } : undefined}>
|
|
139
|
+
{managed.map((m, i) => {
|
|
140
|
+
const it = m.item;
|
|
141
|
+
return it.separator ? (
|
|
142
|
+
<li className="x-menu-sep-li" key={m.id} data-menu-id={m.id}>
|
|
143
|
+
<span className="x-menu-sep" />
|
|
144
|
+
</li>
|
|
145
|
+
) : (
|
|
146
|
+
<li
|
|
147
|
+
className={[
|
|
148
|
+
'x-menu-list-item',
|
|
149
|
+
it.disabled ? 'x-menu-item-disabled' : '',
|
|
150
|
+
it.checked !== undefined ? 'x-menu-item-check-item' : '',
|
|
151
|
+
it.checked ? 'x-menu-item-checked' : '',
|
|
152
|
+
active === i ? 'x-menu-item-active' : '',
|
|
153
|
+
]
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.join(' ')}
|
|
156
|
+
key={m.id}
|
|
157
|
+
data-menu-id={m.id}
|
|
158
|
+
style={{ position: 'relative' }}
|
|
159
|
+
onMouseEnter={() => {
|
|
160
|
+
if (it.disabled) return;
|
|
161
|
+
setActive(i);
|
|
162
|
+
if (it.menu) setOpenSub(i); // hover 展开子菜单(对齐 Ext onMouseOver 显示子菜单)
|
|
163
|
+
}}
|
|
164
|
+
onMouseLeave={() => {
|
|
165
|
+
setActive((a) => (a === i ? null : a));
|
|
166
|
+
setOpenSub((s) => (s === i ? null : s)); // 子菜单是 li 后代,移入子菜单不触发 leave
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<a
|
|
170
|
+
href="#"
|
|
171
|
+
className={['x-menu-item', it.menu ? 'x-menu-item-arrow' : ''].filter(Boolean).join(' ')}
|
|
172
|
+
tabIndex={-1}
|
|
173
|
+
unselectable="on"
|
|
174
|
+
onClick={(e) => {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
if (it.disabled) return;
|
|
177
|
+
it.handler?.();
|
|
178
|
+
onItemClick?.(it, i);
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
{it.render ? (
|
|
182
|
+
<span
|
|
183
|
+
onClick={(e) => e.stopPropagation()}
|
|
184
|
+
style={{ display: 'block' }}
|
|
185
|
+
>
|
|
186
|
+
{(typeof it.render === 'function'
|
|
187
|
+
? (it.render as () => React.ReactNode)()
|
|
188
|
+
: it.render)}
|
|
189
|
+
</span>
|
|
190
|
+
) : (
|
|
191
|
+
<>
|
|
192
|
+
{(it.iconCls || it.checked !== undefined) && (
|
|
193
|
+
<img src="/extjs-assets/images/default/s.gif" className={`x-menu-item-icon ${it.iconCls || ''}`} />
|
|
194
|
+
)}
|
|
195
|
+
<span className="x-menu-item-text">{it.text}</span>
|
|
196
|
+
</>
|
|
197
|
+
)}
|
|
198
|
+
</a>
|
|
199
|
+
{it.menu && (
|
|
200
|
+
<Menu
|
|
201
|
+
ref={(h) => {
|
|
202
|
+
if (h) submenuRefs.current.set(m.id, h);
|
|
203
|
+
else submenuRefs.current.delete(m.id);
|
|
204
|
+
}}
|
|
205
|
+
items={it.menu}
|
|
206
|
+
onItemClick={onItemClick}
|
|
207
|
+
style={{
|
|
208
|
+
left: '100%',
|
|
209
|
+
top: 0,
|
|
210
|
+
zIndex: 15001,
|
|
211
|
+
// 始终挂载子菜单(display 控制显隐),使 getSubmenu(id) 在任意时刻、任意层级
|
|
212
|
+
// 都可用——对齐 Ext「item.menu 实例始终存在、可随时 .add()」的标准特性,
|
|
213
|
+
// 无需子菜单处于 hover/展开状态即可对其动态增删(含新增子菜单)。
|
|
214
|
+
display: openSub === i || submenuAlwaysOpen === i ? 'block' : 'none',
|
|
215
|
+
}}
|
|
216
|
+
/>
|
|
217
|
+
)}
|
|
218
|
+
</li>
|
|
219
|
+
);
|
|
220
|
+
})}
|
|
221
|
+
</ul>
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
Menu.displayName = 'Menu';
|
|
226
|
+
|
|
227
|
+
export default Menu;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React, { useState, forwardRef, useImperativeHandle } from 'react';
|
|
2
|
+
import { Window, type WindowHandle } from './Window';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MessageBox —— 保真复刻 Ext.MessageBox(Ext.Msg.show 的对话框外观)。
|
|
6
|
+
*
|
|
7
|
+
* 保真契约(DOM/class 严格对齐 Ext 3.2.1 运行时,由 dump golden 校验):
|
|
8
|
+
* - 外层即 Ext.Window(plain),根追加 x-window-dlg 类;autoHeight(高度由内容撑开)。
|
|
9
|
+
* - header:x-tool-close(closable 时)+ span.x-window-header-text(与 Window 一致)。
|
|
10
|
+
* - body(x-window-body,padding 5px 10px)唯一子节点为 x-dlg-icon(有图标时)包裹:
|
|
11
|
+
* div.ext-mb-icon.ext-mb-<icon>(无图标时省略,x-dlg-icon 类也省略)
|
|
12
|
+
* div.ext-mb-content(有图标时 margin-left:47px)
|
|
13
|
+
* > span.ext-mb-text + br + div.ext-mb-fix-cursor(input.ext-mb-input + textarea.ext-mb-textarea,均 display:none)
|
|
14
|
+
* div.x-progress-wrap.x-hide-display(隐藏,结构占位)
|
|
15
|
+
* div.x-clear
|
|
16
|
+
* - footer:x-window-footer.x-panel-btns > Toolbar(rootClass x-panel-fbar ...,buttonAlign center) 居中按钮。
|
|
17
|
+
* - 末尾:a.x-dlg-focus(Window 已含)。
|
|
18
|
+
*
|
|
19
|
+
* 说明:Ext.MessageBox 本身 modal 遮罩覆盖 document.body;此处仅复刻对话框外观(遮罩不影响对话框像素),
|
|
20
|
+
* 行为用例聚焦对话框内图标/文本/按钮,遮罩不纳入像素对比。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export type MessageBoxIcon = 'question' | 'info' | 'warning' | 'error';
|
|
24
|
+
|
|
25
|
+
export interface MessageBoxButton {
|
|
26
|
+
text: string;
|
|
27
|
+
/** 按钮宽度(像素),对齐 Ext.MessageBox 按钮的 width 配置 */
|
|
28
|
+
width?: number;
|
|
29
|
+
/** 点击回调;返回 false 可阻止对话框自动关闭(对齐 Ext MessageBox 按钮语义) */
|
|
30
|
+
onClick?: () => boolean | void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MessageBoxProps {
|
|
34
|
+
title?: string;
|
|
35
|
+
/** 正文文本 */
|
|
36
|
+
msg: string;
|
|
37
|
+
width?: number;
|
|
38
|
+
icon?: MessageBoxIcon;
|
|
39
|
+
/** 是否显示右上角关闭按钮(默认 true,对齐 Ext MessageBox 默认 closable:true) */
|
|
40
|
+
closable?: boolean;
|
|
41
|
+
/** 底部按钮(按数组顺序渲染,居中)。留空则不渲染 footer */
|
|
42
|
+
buttons?: MessageBoxButton[];
|
|
43
|
+
/** 点击右上角关闭(或 closable)时的回调 */
|
|
44
|
+
onClose?: () => void;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 命令式句柄:运行时改写标题/正文/图标,以及 show/hide(转发底层 Window) */
|
|
49
|
+
export interface MessageBoxHandle {
|
|
50
|
+
/** 改写窗口标题(转发 Window.setTitle) */
|
|
51
|
+
setTitle: (t: string) => void;
|
|
52
|
+
/** 运行时改写正文 */
|
|
53
|
+
setMessage: (m: string) => void;
|
|
54
|
+
/** 运行时改写图标('question' | 'info' | 'warning' | 'error' | undefined) */
|
|
55
|
+
setIcon: (i?: MessageBoxIcon) => void;
|
|
56
|
+
/** 显示对话框(转发 Window.open) */
|
|
57
|
+
show: () => void;
|
|
58
|
+
/** 隐藏对话框(转发 Window.close) */
|
|
59
|
+
hide: () => void;
|
|
60
|
+
/** 当前是否可见 */
|
|
61
|
+
isVisible: () => boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const MessageBox = forwardRef<MessageBoxHandle, MessageBoxProps>(function MessageBox(
|
|
65
|
+
{ title, msg, width = 400, icon, closable = true, buttons, onClose, className },
|
|
66
|
+
ref,
|
|
67
|
+
) {
|
|
68
|
+
// 双轨:msg / icon 由自身 state 管理;title / 可见性转发底层 Window 句柄
|
|
69
|
+
const [dynMsg, setDynMsg] = useState(msg);
|
|
70
|
+
const [dynIcon, setDynIcon] = useState(icon);
|
|
71
|
+
const winRef = React.useRef<WindowHandle>(null);
|
|
72
|
+
|
|
73
|
+
const setTitle = React.useCallback((t: string) => winRef.current?.setTitle(t), []);
|
|
74
|
+
const setMessage = React.useCallback((m: string) => setDynMsg(m), []);
|
|
75
|
+
const setIcon = React.useCallback((i?: MessageBoxIcon) => setDynIcon(i), []);
|
|
76
|
+
const show = React.useCallback(() => winRef.current?.open(), []);
|
|
77
|
+
const hide = React.useCallback(() => winRef.current?.close(), []);
|
|
78
|
+
const isVisible = React.useCallback(() => winRef.current?.isVisible() ?? false, []);
|
|
79
|
+
// 点击 X(closable)时:先关闭对话框(转发 Window.close),再触发用户 onClose 回调,对齐 Ext 行为
|
|
80
|
+
const handleClose = React.useCallback(() => {
|
|
81
|
+
winRef.current?.close();
|
|
82
|
+
onClose?.();
|
|
83
|
+
}, [onClose]);
|
|
84
|
+
|
|
85
|
+
useImperativeHandle(
|
|
86
|
+
ref,
|
|
87
|
+
() => ({ setTitle, setMessage, setIcon, show, hide, isVisible }),
|
|
88
|
+
[setTitle, setMessage, setIcon, show, hide, isVisible],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const rootCls = ['x-window-dlg', className].filter(Boolean).join(' ');
|
|
92
|
+
|
|
93
|
+
const dlgIconCls = dynIcon ? 'x-dlg-icon' : '';
|
|
94
|
+
const iconEl = dynIcon ? <div className={`ext-mb-icon ext-mb-${dynIcon}`} /> : null;
|
|
95
|
+
|
|
96
|
+
const bodyContent = (
|
|
97
|
+
<div className={dlgIconCls}>
|
|
98
|
+
{iconEl}
|
|
99
|
+
<div className="ext-mb-content">
|
|
100
|
+
<span className="ext-mb-text">{dynMsg}</span>
|
|
101
|
+
<br />
|
|
102
|
+
<div className="ext-mb-fix-cursor">
|
|
103
|
+
<input type="text" className="ext-mb-input" style={{ display: 'none' }} />
|
|
104
|
+
<textarea className="ext-mb-textarea" style={{ display: 'none' }} />
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div className="x-progress-wrap x-hide-display" />
|
|
108
|
+
<div className="x-clear" />
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Window
|
|
114
|
+
ref={winRef}
|
|
115
|
+
title={title}
|
|
116
|
+
width={width}
|
|
117
|
+
plain
|
|
118
|
+
autoHeight
|
|
119
|
+
className={rootCls}
|
|
120
|
+
footerAlign="center"
|
|
121
|
+
closable={closable}
|
|
122
|
+
buttons={(buttons ?? []).map((b) => ({
|
|
123
|
+
text: b.text,
|
|
124
|
+
width: b.width,
|
|
125
|
+
// 点击按钮:先执行用户回调;回调未返回 false 则自动关闭对话框(对齐 Ext MessageBox 按钮语义)
|
|
126
|
+
onClick: () => {
|
|
127
|
+
const r = b.onClick?.();
|
|
128
|
+
if (r !== false) winRef.current?.close();
|
|
129
|
+
},
|
|
130
|
+
}))}
|
|
131
|
+
onClose={handleClose}
|
|
132
|
+
>
|
|
133
|
+
{bodyContent}
|
|
134
|
+
</Window>
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export default MessageBox;
|