@leeoohoo/ui-apps-devkit 0.1.0 → 0.1.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 (62) hide show
  1. package/README.md +75 -60
  2. package/bin/chatos-uiapp.js +4 -4
  3. package/package.json +26 -20
  4. package/src/cli.js +53 -53
  5. package/src/commands/dev.js +14 -14
  6. package/src/commands/init.js +131 -129
  7. package/src/commands/install.js +47 -46
  8. package/src/commands/pack.js +72 -72
  9. package/src/commands/validate.js +138 -80
  10. package/src/lib/args.js +49 -49
  11. package/src/lib/config.js +29 -29
  12. package/src/lib/fs.js +78 -78
  13. package/src/lib/path-boundary.js +16 -16
  14. package/src/lib/plugin.js +45 -45
  15. package/src/lib/state-constants.js +2 -0
  16. package/src/lib/template.js +172 -168
  17. package/src/sandbox/server.js +1957 -692
  18. package/templates/basic/README.md +78 -54
  19. package/templates/basic/chatos.config.json +5 -5
  20. package/templates/basic/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +214 -181
  21. package/templates/basic/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +75 -74
  22. package/templates/basic/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  23. package/templates/basic/docs/CHATOS_UI_APPS_OVERVIEW.md +112 -107
  24. package/templates/basic/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +242 -227
  25. package/templates/basic/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  26. package/templates/basic/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  27. package/templates/basic/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  28. package/templates/basic/plugin/apps/app/compact.mjs +41 -0
  29. package/templates/basic/plugin/apps/app/index.mjs +287 -263
  30. package/templates/basic/plugin/apps/app/mcp-prompt.en.md +7 -7
  31. package/templates/basic/plugin/apps/app/mcp-prompt.zh.md +7 -7
  32. package/templates/basic/plugin/apps/app/mcp-server.mjs +15 -15
  33. package/templates/basic/plugin/backend/index.mjs +37 -37
  34. package/templates/basic/template.json +7 -7
  35. package/templates/notepad/README.md +55 -24
  36. package/templates/notepad/chatos.config.json +4 -4
  37. package/templates/notepad/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +214 -181
  38. package/templates/notepad/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +75 -74
  39. package/templates/notepad/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  40. package/templates/notepad/docs/CHATOS_UI_APPS_OVERVIEW.md +112 -107
  41. package/templates/notepad/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +242 -227
  42. package/templates/notepad/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  43. package/templates/notepad/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  44. package/templates/notepad/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  45. package/templates/notepad/plugin/apps/app/api.mjs +30 -30
  46. package/templates/notepad/plugin/apps/app/compact.mjs +41 -0
  47. package/templates/notepad/plugin/apps/app/dom.mjs +14 -14
  48. package/templates/notepad/plugin/apps/app/ds-tree.mjs +35 -35
  49. package/templates/notepad/plugin/apps/app/index.mjs +1056 -1056
  50. package/templates/notepad/plugin/apps/app/layers.mjs +338 -338
  51. package/templates/notepad/plugin/apps/app/markdown.mjs +120 -120
  52. package/templates/notepad/plugin/apps/app/mcp-prompt.en.md +22 -22
  53. package/templates/notepad/plugin/apps/app/mcp-prompt.zh.md +22 -22
  54. package/templates/notepad/plugin/apps/app/mcp-server.mjs +206 -199
  55. package/templates/notepad/plugin/apps/app/styles.mjs +355 -355
  56. package/templates/notepad/plugin/apps/app/tags.mjs +21 -21
  57. package/templates/notepad/plugin/apps/app/ui.mjs +280 -280
  58. package/templates/notepad/plugin/backend/index.mjs +99 -99
  59. package/templates/notepad/plugin/plugin.json +23 -23
  60. package/templates/notepad/plugin/shared/notepad-paths.mjs +59 -41
  61. package/templates/notepad/plugin/shared/notepad-store.mjs +765 -765
  62. package/templates/notepad/template.json +8 -8
@@ -1,338 +1,338 @@
1
- function noop() {}
2
-
3
- function safeSetStatus(setStatus, text) {
4
- if (typeof setStatus !== 'function') return;
5
- try {
6
- setStatus(String(text || ''), 'bad');
7
- } catch {
8
- // ignore
9
- }
10
- }
11
-
12
- export function createNotepadLayerManager({ getDisposed, setStatus } = {}) {
13
- const isDisposed = typeof getDisposed === 'function' ? getDisposed : () => false;
14
-
15
- let activeLayer = null;
16
-
17
- const closeActiveLayer = () => {
18
- const layer = activeLayer;
19
- activeLayer = null;
20
- if (!layer) return;
21
- try {
22
- layer.dispose?.();
23
- } catch {
24
- // ignore
25
- }
26
- };
27
-
28
- const showMenu = (x, y, items = []) => {
29
- if (isDisposed()) return;
30
- closeActiveLayer();
31
-
32
- const overlay = document.createElement('div');
33
- overlay.className = 'np-menu-overlay';
34
-
35
- const menu = document.createElement('div');
36
- menu.className = 'np-menu';
37
-
38
- const close = () => {
39
- try {
40
- document.removeEventListener('keydown', onKeyDown, true);
41
- } catch {
42
- // ignore
43
- }
44
- try {
45
- overlay.remove();
46
- } catch {
47
- // ignore
48
- }
49
- if (activeLayer?.overlay === overlay) activeLayer = null;
50
- };
51
-
52
- const onKeyDown = (ev) => {
53
- if (ev?.key !== 'Escape') return;
54
- try {
55
- ev.preventDefault();
56
- } catch {
57
- // ignore
58
- }
59
- close();
60
- };
61
-
62
- overlay.addEventListener('mousedown', (ev) => {
63
- if (ev?.target !== overlay) return;
64
- close();
65
- });
66
- overlay.addEventListener('contextmenu', (ev) => {
67
- try {
68
- ev.preventDefault();
69
- } catch {
70
- // ignore
71
- }
72
- close();
73
- });
74
-
75
- (Array.isArray(items) ? items : []).forEach((item) => {
76
- const label = typeof item?.label === 'string' ? item.label : '';
77
- if (!label) return;
78
- const btn = document.createElement('button');
79
- btn.type = 'button';
80
- btn.className = 'np-menu-item';
81
- btn.textContent = label;
82
- btn.disabled = item?.disabled === true;
83
- btn.dataset.danger = item?.danger === true ? '1' : '0';
84
- btn.addEventListener('click', async () => {
85
- if (btn.disabled) return;
86
- close();
87
- try {
88
- await item?.onClick?.();
89
- } catch (err) {
90
- safeSetStatus(setStatus, `Notes: ${err?.message || String(err)}`);
91
- }
92
- });
93
- menu.appendChild(btn);
94
- });
95
-
96
- overlay.appendChild(menu);
97
- document.body.appendChild(overlay);
98
- document.addEventListener('keydown', onKeyDown, true);
99
-
100
- menu.style.left = `${Math.max(0, Math.floor(x))}px`;
101
- menu.style.top = `${Math.max(0, Math.floor(y))}px`;
102
- try {
103
- const rect = menu.getBoundingClientRect();
104
- const margin = 8;
105
- let left = Math.floor(x);
106
- let top = Math.floor(y);
107
- if (left + rect.width + margin > window.innerWidth) left = Math.max(margin, window.innerWidth - rect.width - margin);
108
- if (top + rect.height + margin > window.innerHeight) top = Math.max(margin, window.innerHeight - rect.height - margin);
109
- menu.style.left = `${left}px`;
110
- menu.style.top = `${top}px`;
111
- } catch {
112
- // ignore
113
- }
114
-
115
- activeLayer = { overlay, dispose: close };
116
- };
117
-
118
- const showDialog = ({
119
- title,
120
- description = '',
121
- fields = [],
122
- confirmText = '确定',
123
- cancelText = '取消',
124
- danger = false,
125
- } = {}) =>
126
- new Promise((resolve) => {
127
- if (isDisposed()) return resolve(null);
128
- closeActiveLayer();
129
-
130
- const overlay = document.createElement('div');
131
- overlay.className = 'np-modal-overlay';
132
- const modal = document.createElement('div');
133
- modal.className = 'np-modal';
134
-
135
- const header = document.createElement('div');
136
- header.className = 'np-modal-header';
137
- header.textContent = typeof title === 'string' && title.trim() ? title.trim() : '提示';
138
-
139
- const body = document.createElement('div');
140
- body.className = 'np-modal-body';
141
-
142
- const desc = document.createElement('div');
143
- desc.className = 'np-modal-desc';
144
- desc.textContent = typeof description === 'string' ? description : '';
145
- if (desc.textContent.trim()) {
146
- body.appendChild(desc);
147
- }
148
-
149
- const errorEl = document.createElement('div');
150
- errorEl.className = 'np-modal-error';
151
- errorEl.textContent = '';
152
-
153
- const inputs = [];
154
- (Array.isArray(fields) ? fields : []).forEach((field) => {
155
- if (!field || typeof field !== 'object') return;
156
- const name = typeof field.name === 'string' ? field.name.trim() : '';
157
- if (!name) return;
158
- const row = document.createElement('div');
159
- row.className = 'np-modal-field';
160
-
161
- const label = document.createElement('div');
162
- label.className = 'np-modal-label';
163
- label.textContent = typeof field.label === 'string' ? field.label : name;
164
- row.appendChild(label);
165
-
166
- let control = null;
167
- const kind = field.kind === 'select' ? 'select' : 'text';
168
- if (kind === 'select') {
169
- const select = document.createElement('select');
170
- select.className = 'np-select';
171
- const options = Array.isArray(field.options) ? field.options : [];
172
- options.forEach((opt) => {
173
- const value = typeof opt?.value === 'string' ? opt.value : '';
174
- const labelText = typeof opt?.label === 'string' ? opt.label : value;
175
- const option = document.createElement('option');
176
- option.value = value;
177
- option.textContent = labelText || value || '(空)';
178
- select.appendChild(option);
179
- });
180
- select.value = typeof field.value === 'string' ? field.value : '';
181
- control = select;
182
- } else {
183
- const input = document.createElement('input');
184
- input.className = 'np-input';
185
- input.type = 'text';
186
- input.placeholder = typeof field.placeholder === 'string' ? field.placeholder : '';
187
- input.value = typeof field.value === 'string' ? field.value : '';
188
- control = input;
189
- }
190
-
191
- row.appendChild(control);
192
- body.appendChild(row);
193
- inputs.push({
194
- name,
195
- required: field.required === true,
196
- control,
197
- label: typeof field.label === 'string' ? field.label : name,
198
- });
199
- });
200
-
201
- if (inputs.length > 0) {
202
- body.appendChild(errorEl);
203
- }
204
-
205
- const actions = document.createElement('div');
206
- actions.className = 'np-modal-actions';
207
-
208
- const btnCancel = document.createElement('button');
209
- btnCancel.type = 'button';
210
- btnCancel.className = 'np-btn';
211
- btnCancel.textContent = cancelText || '取消';
212
-
213
- const btnOk = document.createElement('button');
214
- btnOk.type = 'button';
215
- btnOk.className = 'np-btn';
216
- btnOk.textContent = confirmText || '确定';
217
- btnOk.dataset.variant = danger ? 'danger' : '';
218
-
219
- const cleanup = () => {
220
- try {
221
- document.removeEventListener('keydown', onKeyDown, true);
222
- } catch {
223
- // ignore
224
- }
225
- try {
226
- overlay.remove();
227
- } catch {
228
- // ignore
229
- }
230
- if (activeLayer?.overlay === overlay) activeLayer = null;
231
- };
232
-
233
- const close = (result) => {
234
- cleanup();
235
- resolve(result);
236
- };
237
-
238
- const validateAndClose = () => {
239
- const values = {};
240
- for (const it of inputs) {
241
- const raw = it?.control?.value;
242
- const value = typeof raw === 'string' ? raw.trim() : String(raw ?? '').trim();
243
- if (it.required && !value) {
244
- errorEl.textContent = `请填写:${it.label}`;
245
- try {
246
- it.control?.focus?.();
247
- } catch {
248
- // ignore
249
- }
250
- return;
251
- }
252
- values[it.name] = value;
253
- }
254
- close(values);
255
- };
256
-
257
- const onKeyDown = (ev) => {
258
- if (ev?.key === 'Escape') {
259
- try {
260
- ev.preventDefault();
261
- } catch {
262
- // ignore
263
- }
264
- close(null);
265
- return;
266
- }
267
- if (ev?.key === 'Enter') {
268
- const active = document.activeElement;
269
- const isTextArea = active && active.tagName === 'TEXTAREA';
270
- if (isTextArea) return;
271
- try {
272
- ev.preventDefault();
273
- } catch {
274
- // ignore
275
- }
276
- validateAndClose();
277
- }
278
- };
279
-
280
- overlay.addEventListener('mousedown', (ev) => {
281
- if (ev?.target !== overlay) return;
282
- close(null);
283
- });
284
-
285
- btnOk.addEventListener('click', () => validateAndClose());
286
- btnCancel.addEventListener('click', () => close(null));
287
-
288
- actions.appendChild(btnCancel);
289
- actions.appendChild(btnOk);
290
- modal.appendChild(header);
291
- modal.appendChild(body);
292
- modal.appendChild(actions);
293
- overlay.appendChild(modal);
294
- document.body.appendChild(overlay);
295
- document.addEventListener('keydown', onKeyDown, true);
296
-
297
- activeLayer = { overlay, dispose: () => close(null) };
298
-
299
- const first = inputs[0]?.control;
300
- if (first) {
301
- setTimeout(() => {
302
- try {
303
- first.focus();
304
- } catch {
305
- // ignore
306
- }
307
- }, 0);
308
- } else {
309
- setTimeout(() => {
310
- try {
311
- btnOk.focus();
312
- } catch {
313
- // ignore
314
- }
315
- }, 0);
316
- }
317
- });
318
-
319
- const confirmDialog = async (message, options = {}) => {
320
- const res = await showDialog({
321
- title: options?.title || '确认',
322
- description: typeof message === 'string' ? message : '',
323
- fields: [],
324
- confirmText: options?.confirmText || '确定',
325
- cancelText: options?.cancelText || '取消',
326
- danger: options?.danger === true,
327
- });
328
- return Boolean(res);
329
- };
330
-
331
- return {
332
- closeActiveLayer,
333
- showMenu,
334
- showDialog,
335
- confirmDialog,
336
- };
337
- }
338
-
1
+ function noop() {}
2
+
3
+ function safeSetStatus(setStatus, text) {
4
+ if (typeof setStatus !== 'function') return;
5
+ try {
6
+ setStatus(String(text || ''), 'bad');
7
+ } catch {
8
+ // ignore
9
+ }
10
+ }
11
+
12
+ export function createNotepadLayerManager({ getDisposed, setStatus } = {}) {
13
+ const isDisposed = typeof getDisposed === 'function' ? getDisposed : () => false;
14
+
15
+ let activeLayer = null;
16
+
17
+ const closeActiveLayer = () => {
18
+ const layer = activeLayer;
19
+ activeLayer = null;
20
+ if (!layer) return;
21
+ try {
22
+ layer.dispose?.();
23
+ } catch {
24
+ // ignore
25
+ }
26
+ };
27
+
28
+ const showMenu = (x, y, items = []) => {
29
+ if (isDisposed()) return;
30
+ closeActiveLayer();
31
+
32
+ const overlay = document.createElement('div');
33
+ overlay.className = 'np-menu-overlay';
34
+
35
+ const menu = document.createElement('div');
36
+ menu.className = 'np-menu';
37
+
38
+ const close = () => {
39
+ try {
40
+ document.removeEventListener('keydown', onKeyDown, true);
41
+ } catch {
42
+ // ignore
43
+ }
44
+ try {
45
+ overlay.remove();
46
+ } catch {
47
+ // ignore
48
+ }
49
+ if (activeLayer?.overlay === overlay) activeLayer = null;
50
+ };
51
+
52
+ const onKeyDown = (ev) => {
53
+ if (ev?.key !== 'Escape') return;
54
+ try {
55
+ ev.preventDefault();
56
+ } catch {
57
+ // ignore
58
+ }
59
+ close();
60
+ };
61
+
62
+ overlay.addEventListener('mousedown', (ev) => {
63
+ if (ev?.target !== overlay) return;
64
+ close();
65
+ });
66
+ overlay.addEventListener('contextmenu', (ev) => {
67
+ try {
68
+ ev.preventDefault();
69
+ } catch {
70
+ // ignore
71
+ }
72
+ close();
73
+ });
74
+
75
+ (Array.isArray(items) ? items : []).forEach((item) => {
76
+ const label = typeof item?.label === 'string' ? item.label : '';
77
+ if (!label) return;
78
+ const btn = document.createElement('button');
79
+ btn.type = 'button';
80
+ btn.className = 'np-menu-item';
81
+ btn.textContent = label;
82
+ btn.disabled = item?.disabled === true;
83
+ btn.dataset.danger = item?.danger === true ? '1' : '0';
84
+ btn.addEventListener('click', async () => {
85
+ if (btn.disabled) return;
86
+ close();
87
+ try {
88
+ await item?.onClick?.();
89
+ } catch (err) {
90
+ safeSetStatus(setStatus, `Notes: ${err?.message || String(err)}`);
91
+ }
92
+ });
93
+ menu.appendChild(btn);
94
+ });
95
+
96
+ overlay.appendChild(menu);
97
+ document.body.appendChild(overlay);
98
+ document.addEventListener('keydown', onKeyDown, true);
99
+
100
+ menu.style.left = `${Math.max(0, Math.floor(x))}px`;
101
+ menu.style.top = `${Math.max(0, Math.floor(y))}px`;
102
+ try {
103
+ const rect = menu.getBoundingClientRect();
104
+ const margin = 8;
105
+ let left = Math.floor(x);
106
+ let top = Math.floor(y);
107
+ if (left + rect.width + margin > window.innerWidth) left = Math.max(margin, window.innerWidth - rect.width - margin);
108
+ if (top + rect.height + margin > window.innerHeight) top = Math.max(margin, window.innerHeight - rect.height - margin);
109
+ menu.style.left = `${left}px`;
110
+ menu.style.top = `${top}px`;
111
+ } catch {
112
+ // ignore
113
+ }
114
+
115
+ activeLayer = { overlay, dispose: close };
116
+ };
117
+
118
+ const showDialog = ({
119
+ title,
120
+ description = '',
121
+ fields = [],
122
+ confirmText = '确定',
123
+ cancelText = '取消',
124
+ danger = false,
125
+ } = {}) =>
126
+ new Promise((resolve) => {
127
+ if (isDisposed()) return resolve(null);
128
+ closeActiveLayer();
129
+
130
+ const overlay = document.createElement('div');
131
+ overlay.className = 'np-modal-overlay';
132
+ const modal = document.createElement('div');
133
+ modal.className = 'np-modal';
134
+
135
+ const header = document.createElement('div');
136
+ header.className = 'np-modal-header';
137
+ header.textContent = typeof title === 'string' && title.trim() ? title.trim() : '提示';
138
+
139
+ const body = document.createElement('div');
140
+ body.className = 'np-modal-body';
141
+
142
+ const desc = document.createElement('div');
143
+ desc.className = 'np-modal-desc';
144
+ desc.textContent = typeof description === 'string' ? description : '';
145
+ if (desc.textContent.trim()) {
146
+ body.appendChild(desc);
147
+ }
148
+
149
+ const errorEl = document.createElement('div');
150
+ errorEl.className = 'np-modal-error';
151
+ errorEl.textContent = '';
152
+
153
+ const inputs = [];
154
+ (Array.isArray(fields) ? fields : []).forEach((field) => {
155
+ if (!field || typeof field !== 'object') return;
156
+ const name = typeof field.name === 'string' ? field.name.trim() : '';
157
+ if (!name) return;
158
+ const row = document.createElement('div');
159
+ row.className = 'np-modal-field';
160
+
161
+ const label = document.createElement('div');
162
+ label.className = 'np-modal-label';
163
+ label.textContent = typeof field.label === 'string' ? field.label : name;
164
+ row.appendChild(label);
165
+
166
+ let control = null;
167
+ const kind = field.kind === 'select' ? 'select' : 'text';
168
+ if (kind === 'select') {
169
+ const select = document.createElement('select');
170
+ select.className = 'np-select';
171
+ const options = Array.isArray(field.options) ? field.options : [];
172
+ options.forEach((opt) => {
173
+ const value = typeof opt?.value === 'string' ? opt.value : '';
174
+ const labelText = typeof opt?.label === 'string' ? opt.label : value;
175
+ const option = document.createElement('option');
176
+ option.value = value;
177
+ option.textContent = labelText || value || '(空)';
178
+ select.appendChild(option);
179
+ });
180
+ select.value = typeof field.value === 'string' ? field.value : '';
181
+ control = select;
182
+ } else {
183
+ const input = document.createElement('input');
184
+ input.className = 'np-input';
185
+ input.type = 'text';
186
+ input.placeholder = typeof field.placeholder === 'string' ? field.placeholder : '';
187
+ input.value = typeof field.value === 'string' ? field.value : '';
188
+ control = input;
189
+ }
190
+
191
+ row.appendChild(control);
192
+ body.appendChild(row);
193
+ inputs.push({
194
+ name,
195
+ required: field.required === true,
196
+ control,
197
+ label: typeof field.label === 'string' ? field.label : name,
198
+ });
199
+ });
200
+
201
+ if (inputs.length > 0) {
202
+ body.appendChild(errorEl);
203
+ }
204
+
205
+ const actions = document.createElement('div');
206
+ actions.className = 'np-modal-actions';
207
+
208
+ const btnCancel = document.createElement('button');
209
+ btnCancel.type = 'button';
210
+ btnCancel.className = 'np-btn';
211
+ btnCancel.textContent = cancelText || '取消';
212
+
213
+ const btnOk = document.createElement('button');
214
+ btnOk.type = 'button';
215
+ btnOk.className = 'np-btn';
216
+ btnOk.textContent = confirmText || '确定';
217
+ btnOk.dataset.variant = danger ? 'danger' : '';
218
+
219
+ const cleanup = () => {
220
+ try {
221
+ document.removeEventListener('keydown', onKeyDown, true);
222
+ } catch {
223
+ // ignore
224
+ }
225
+ try {
226
+ overlay.remove();
227
+ } catch {
228
+ // ignore
229
+ }
230
+ if (activeLayer?.overlay === overlay) activeLayer = null;
231
+ };
232
+
233
+ const close = (result) => {
234
+ cleanup();
235
+ resolve(result);
236
+ };
237
+
238
+ const validateAndClose = () => {
239
+ const values = {};
240
+ for (const it of inputs) {
241
+ const raw = it?.control?.value;
242
+ const value = typeof raw === 'string' ? raw.trim() : String(raw ?? '').trim();
243
+ if (it.required && !value) {
244
+ errorEl.textContent = `请填写:${it.label}`;
245
+ try {
246
+ it.control?.focus?.();
247
+ } catch {
248
+ // ignore
249
+ }
250
+ return;
251
+ }
252
+ values[it.name] = value;
253
+ }
254
+ close(values);
255
+ };
256
+
257
+ const onKeyDown = (ev) => {
258
+ if (ev?.key === 'Escape') {
259
+ try {
260
+ ev.preventDefault();
261
+ } catch {
262
+ // ignore
263
+ }
264
+ close(null);
265
+ return;
266
+ }
267
+ if (ev?.key === 'Enter') {
268
+ const active = document.activeElement;
269
+ const isTextArea = active && active.tagName === 'TEXTAREA';
270
+ if (isTextArea) return;
271
+ try {
272
+ ev.preventDefault();
273
+ } catch {
274
+ // ignore
275
+ }
276
+ validateAndClose();
277
+ }
278
+ };
279
+
280
+ overlay.addEventListener('mousedown', (ev) => {
281
+ if (ev?.target !== overlay) return;
282
+ close(null);
283
+ });
284
+
285
+ btnOk.addEventListener('click', () => validateAndClose());
286
+ btnCancel.addEventListener('click', () => close(null));
287
+
288
+ actions.appendChild(btnCancel);
289
+ actions.appendChild(btnOk);
290
+ modal.appendChild(header);
291
+ modal.appendChild(body);
292
+ modal.appendChild(actions);
293
+ overlay.appendChild(modal);
294
+ document.body.appendChild(overlay);
295
+ document.addEventListener('keydown', onKeyDown, true);
296
+
297
+ activeLayer = { overlay, dispose: () => close(null) };
298
+
299
+ const first = inputs[0]?.control;
300
+ if (first) {
301
+ setTimeout(() => {
302
+ try {
303
+ first.focus();
304
+ } catch {
305
+ // ignore
306
+ }
307
+ }, 0);
308
+ } else {
309
+ setTimeout(() => {
310
+ try {
311
+ btnOk.focus();
312
+ } catch {
313
+ // ignore
314
+ }
315
+ }, 0);
316
+ }
317
+ });
318
+
319
+ const confirmDialog = async (message, options = {}) => {
320
+ const res = await showDialog({
321
+ title: options?.title || '确认',
322
+ description: typeof message === 'string' ? message : '',
323
+ fields: [],
324
+ confirmText: options?.confirmText || '确定',
325
+ cancelText: options?.cancelText || '取消',
326
+ danger: options?.danger === true,
327
+ });
328
+ return Boolean(res);
329
+ };
330
+
331
+ return {
332
+ closeActiveLayer,
333
+ showMenu,
334
+ showDialog,
335
+ confirmDialog,
336
+ };
337
+ }
338
+