@leeoohoo/ui-apps-devkit 0.1.0 → 0.1.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.
Files changed (61) hide show
  1. package/README.md +76 -70
  2. package/bin/chatos-uiapp.js +4 -4
  3. package/package.json +25 -22
  4. package/src/cli.js +53 -53
  5. package/src/commands/dev.js +14 -14
  6. package/src/commands/init.js +142 -141
  7. package/src/commands/install.js +55 -55
  8. package/src/commands/pack.js +72 -72
  9. package/src/commands/validate.js +113 -103
  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/template.js +172 -168
  16. package/src/sandbox/server.js +1200 -861
  17. package/templates/basic/README.md +77 -58
  18. package/templates/basic/chatos.config.json +5 -5
  19. package/templates/basic/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +194 -163
  20. package/templates/basic/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +74 -74
  21. package/templates/basic/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  22. package/templates/basic/docs/CHATOS_UI_APPS_OVERVIEW.md +113 -110
  23. package/templates/basic/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +226 -212
  24. package/templates/basic/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  25. package/templates/basic/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  26. package/templates/basic/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  27. package/templates/basic/plugin/apps/app/compact.mjs +41 -0
  28. package/templates/basic/plugin/apps/app/index.mjs +287 -263
  29. package/templates/basic/plugin/apps/app/mcp-prompt.en.md +7 -7
  30. package/templates/basic/plugin/apps/app/mcp-prompt.zh.md +7 -7
  31. package/templates/basic/plugin/apps/app/mcp-server.mjs +15 -15
  32. package/templates/basic/plugin/backend/index.mjs +37 -37
  33. package/templates/basic/template.json +7 -7
  34. package/templates/notepad/README.md +58 -36
  35. package/templates/notepad/chatos.config.json +4 -4
  36. package/templates/notepad/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +194 -163
  37. package/templates/notepad/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +74 -74
  38. package/templates/notepad/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  39. package/templates/notepad/docs/CHATOS_UI_APPS_OVERVIEW.md +113 -110
  40. package/templates/notepad/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +226 -212
  41. package/templates/notepad/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  42. package/templates/notepad/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  43. package/templates/notepad/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  44. package/templates/notepad/plugin/apps/app/api.mjs +30 -30
  45. package/templates/notepad/plugin/apps/app/compact.mjs +41 -0
  46. package/templates/notepad/plugin/apps/app/dom.mjs +14 -14
  47. package/templates/notepad/plugin/apps/app/ds-tree.mjs +35 -35
  48. package/templates/notepad/plugin/apps/app/index.mjs +1056 -1056
  49. package/templates/notepad/plugin/apps/app/layers.mjs +338 -338
  50. package/templates/notepad/plugin/apps/app/markdown.mjs +120 -120
  51. package/templates/notepad/plugin/apps/app/mcp-prompt.en.md +22 -22
  52. package/templates/notepad/plugin/apps/app/mcp-prompt.zh.md +22 -22
  53. package/templates/notepad/plugin/apps/app/mcp-server.mjs +200 -200
  54. package/templates/notepad/plugin/apps/app/styles.mjs +355 -355
  55. package/templates/notepad/plugin/apps/app/tags.mjs +21 -21
  56. package/templates/notepad/plugin/apps/app/ui.mjs +280 -280
  57. package/templates/notepad/plugin/backend/index.mjs +99 -99
  58. package/templates/notepad/plugin/plugin.json +23 -23
  59. package/templates/notepad/plugin/shared/notepad-paths.mjs +62 -62
  60. package/templates/notepad/plugin/shared/notepad-store.mjs +765 -765
  61. 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
+