@openvcs/sdk 0.2.17-nightly.20260406.2 → 0.2.18-beta.8
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/README.md +2 -3
- package/lib/build.js +35 -2
- package/lib/fs-utils.js +35 -2
- package/lib/init.js +36 -3
- package/lib/runtime/modal.d.ts +28 -1
- package/lib/runtime/modal.js +59 -1
- package/lib/types/modal.d.ts +34 -1
- package/package.json +2 -2
- package/src/lib/runtime/modal.ts +94 -5
- package/src/lib/types/modal.ts +39 -0
- package/test/modal.test.ts +71 -2
package/README.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# @openvcs/sdk
|
|
2
|
-
|
|
3
|
-
[](https://github.com/Open-VCS/OpenVCS-SDK/actions/workflows/nightly.yml)
|
|
2
|
+
[](https://github.com/Open-VCS/OpenVCS-SDK/actions/workflows/publish.yml)
|
|
4
3
|
[](https://github.com/Open-VCS/OpenVCS-SDK/actions/workflows/ci.yml)
|
|
5
|
-
[](https://github.com/Open-VCS/OpenVCS-SDK/actions/workflows/publish.yml)
|
|
6
5
|
|
|
7
6
|
OpenVCS SDK for npm-based plugin development.
|
|
8
7
|
|
package/lib/build.js
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Copyright © 2025-2026 OpenVCS Contributors
|
|
3
3
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
16
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
17
|
+
}) : function(o, v) {
|
|
18
|
+
o["default"] = v;
|
|
19
|
+
});
|
|
20
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
21
|
+
var ownKeys = function(o) {
|
|
22
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
23
|
+
var ar = [];
|
|
24
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
25
|
+
return ar;
|
|
26
|
+
};
|
|
27
|
+
return ownKeys(o);
|
|
28
|
+
};
|
|
29
|
+
return function (mod) {
|
|
30
|
+
if (mod && mod.__esModule) return mod;
|
|
31
|
+
var result = {};
|
|
32
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
33
|
+
__setModuleDefault(result, mod);
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
4
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
38
|
exports.npmExecutable = npmExecutable;
|
|
6
39
|
exports.shouldUseWindowsShell = shouldUseWindowsShell;
|
|
@@ -15,8 +48,8 @@ exports.generateModuleBootstrap = generateModuleBootstrap;
|
|
|
15
48
|
exports.hasPackageJson = hasPackageJson;
|
|
16
49
|
exports.runCommand = runCommand;
|
|
17
50
|
exports.buildPluginAssets = buildPluginAssets;
|
|
18
|
-
const fs = require("node:fs");
|
|
19
|
-
const path = require("node:path");
|
|
51
|
+
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const path = __importStar(require("node:path"));
|
|
20
53
|
const node_child_process_1 = require("node:child_process");
|
|
21
54
|
const fs_utils_1 = require("./fs-utils");
|
|
22
55
|
const AUTHORED_PLUGIN_MODULE_BASENAME = "plugin.js";
|
package/lib/fs-utils.js
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.isPathInside = isPathInside;
|
|
4
37
|
exports.rejectSymlinksRecursive = rejectSymlinksRecursive;
|
|
5
38
|
exports.ensureDirectory = ensureDirectory;
|
|
6
39
|
exports.copyFileStrict = copyFileStrict;
|
|
7
40
|
exports.copyDirectoryRecursiveStrict = copyDirectoryRecursiveStrict;
|
|
8
|
-
const fs = require("node:fs");
|
|
9
|
-
const path = require("node:path");
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
10
43
|
function isPathInside(rootPath, candidatePath) {
|
|
11
44
|
const relative = path.relative(rootPath, candidatePath);
|
|
12
45
|
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
package/lib/init.js
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.__private = void 0;
|
|
4
37
|
exports.initUsage = initUsage;
|
|
5
38
|
exports.runInitCommand = runInitCommand;
|
|
6
39
|
exports.isUsageError = isUsageError;
|
|
7
|
-
const fs = require("node:fs");
|
|
8
|
-
const path = require("node:path");
|
|
9
|
-
const readline = require("node:readline/promises");
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const readline = __importStar(require("node:readline/promises"));
|
|
10
43
|
const node_process_1 = require("node:process");
|
|
11
44
|
const node_child_process_1 = require("node:child_process");
|
|
12
45
|
const packageJson = require("../package.json");
|
package/lib/runtime/modal.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ModalButtonVariant, ModalContentAlign, ModalInputKind, ModalListRowDefinition, ModalSelectOptionDefinition, PluginModalDefinition } from '../types/modal.js';
|
|
1
|
+
import type { ModalButtonVariant, ModalContentAlign, ModalInputKind, ModalListRowDefinition, ModalSelectOptionDefinition, PluginModalContentItem, PluginModalDefinition } from '../types/modal.js';
|
|
2
2
|
/** Describes the options accepted by `ModalBuilder.button()`. */
|
|
3
3
|
export interface ModalBuilderButtonOptions {
|
|
4
4
|
/** Stores the optional tooltip text. */
|
|
@@ -50,6 +50,27 @@ export interface ModalBuilderListOptions {
|
|
|
50
50
|
/** Stores the list rows. */
|
|
51
51
|
items: ModalListRowDefinition[];
|
|
52
52
|
}
|
|
53
|
+
/** Describes the options accepted by `ModalBuilder.horizontalBox()`. */
|
|
54
|
+
export interface ModalBuilderHorizontalBoxOptions {
|
|
55
|
+
/** Stores the spacing between children. */
|
|
56
|
+
gap?: string;
|
|
57
|
+
/** Stores the alignment hint for the main axis. */
|
|
58
|
+
align?: ModalContentAlign;
|
|
59
|
+
/** Stores whether children may wrap. */
|
|
60
|
+
wrap?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** Describes the options accepted by `ModalBuilder.verticalBox()`. */
|
|
63
|
+
export interface ModalBuilderVerticalBoxOptions {
|
|
64
|
+
/** Stores the spacing between children. */
|
|
65
|
+
gap?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Describes the options accepted by `ModalBuilder.grid()`. */
|
|
68
|
+
export interface ModalBuilderGridOptions {
|
|
69
|
+
/** Stores the CSS grid column template. */
|
|
70
|
+
columns: string;
|
|
71
|
+
/** Stores the spacing between cells. */
|
|
72
|
+
gap?: string;
|
|
73
|
+
}
|
|
53
74
|
/** Builds a structured modal definition with a fluent class API. */
|
|
54
75
|
export declare class ModalBuilder {
|
|
55
76
|
private readonly definition;
|
|
@@ -59,6 +80,12 @@ export declare class ModalBuilder {
|
|
|
59
80
|
text(content: string, options?: ModalBuilderTextOptions): this;
|
|
60
81
|
/** Adds a separator to the modal body. */
|
|
61
82
|
separator(): this;
|
|
83
|
+
/** Adds a horizontal box to the modal body. */
|
|
84
|
+
horizontalBox(content: PluginModalContentItem[], options?: ModalBuilderHorizontalBoxOptions): this;
|
|
85
|
+
/** Adds a vertical box to the modal body. */
|
|
86
|
+
verticalBox(content: PluginModalContentItem[], options?: ModalBuilderVerticalBoxOptions): this;
|
|
87
|
+
/** Adds a grid container to the modal body. */
|
|
88
|
+
grid(content: PluginModalContentItem[], options: ModalBuilderGridOptions): this;
|
|
62
89
|
/** Adds a button to the modal body. */
|
|
63
90
|
button(id: string, content: string, options?: ModalBuilderButtonOptions): this;
|
|
64
91
|
/** Adds a text input to the modal body. */
|
package/lib/runtime/modal.js
CHANGED
|
@@ -28,6 +28,36 @@ class ModalBuilder {
|
|
|
28
28
|
this.definition.content.push({ type: 'separator' });
|
|
29
29
|
return this;
|
|
30
30
|
}
|
|
31
|
+
/** Adds a horizontal box to the modal body. */
|
|
32
|
+
horizontalBox(content, options = {}) {
|
|
33
|
+
this.definition.content.push({
|
|
34
|
+
type: 'horizontal-box',
|
|
35
|
+
content: cloneContent(content),
|
|
36
|
+
...(options.gap ? { gap: options.gap } : {}),
|
|
37
|
+
...(options.align ? { align: options.align } : {}),
|
|
38
|
+
...(options.wrap !== undefined ? { wrap: options.wrap } : {}),
|
|
39
|
+
});
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
/** Adds a vertical box to the modal body. */
|
|
43
|
+
verticalBox(content, options = {}) {
|
|
44
|
+
this.definition.content.push({
|
|
45
|
+
type: 'vertical-box',
|
|
46
|
+
content: cloneContent(content),
|
|
47
|
+
...(options.gap ? { gap: options.gap } : {}),
|
|
48
|
+
});
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
/** Adds a grid container to the modal body. */
|
|
52
|
+
grid(content, options) {
|
|
53
|
+
this.definition.content.push({
|
|
54
|
+
type: 'grid',
|
|
55
|
+
content: cloneContent(content),
|
|
56
|
+
columns: String(options.columns || '').trim(),
|
|
57
|
+
...(options.gap ? { gap: options.gap } : {}),
|
|
58
|
+
});
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
31
61
|
/** Adds a button to the modal body. */
|
|
32
62
|
button(id, content, options = {}) {
|
|
33
63
|
this.definition.content.push({
|
|
@@ -83,7 +113,7 @@ class ModalBuilder {
|
|
|
83
113
|
build() {
|
|
84
114
|
return {
|
|
85
115
|
title: this.definition.title,
|
|
86
|
-
content: this.definition.content
|
|
116
|
+
content: cloneContent(this.definition.content),
|
|
87
117
|
};
|
|
88
118
|
}
|
|
89
119
|
/** Returns the serialized modal payload for a host request. */
|
|
@@ -92,3 +122,31 @@ class ModalBuilder {
|
|
|
92
122
|
}
|
|
93
123
|
}
|
|
94
124
|
exports.ModalBuilder = ModalBuilder;
|
|
125
|
+
/** Clones modal content recursively so nested containers stay isolated. */
|
|
126
|
+
function cloneContent(content) {
|
|
127
|
+
return Array.isArray(content) ? content.map((item) => cloneItem(item)) : [];
|
|
128
|
+
}
|
|
129
|
+
/** Clones one modal content item recursively. */
|
|
130
|
+
function cloneItem(item) {
|
|
131
|
+
if (!item || typeof item !== 'object')
|
|
132
|
+
return item;
|
|
133
|
+
if (item.type === 'horizontal-box') {
|
|
134
|
+
return {
|
|
135
|
+
...item,
|
|
136
|
+
content: cloneContent(item.content),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (item.type === 'vertical-box') {
|
|
140
|
+
return {
|
|
141
|
+
...item,
|
|
142
|
+
content: cloneContent(item.content),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (item.type === 'grid') {
|
|
146
|
+
return {
|
|
147
|
+
...item,
|
|
148
|
+
content: cloneContent(item.content),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return { ...item };
|
|
152
|
+
}
|
package/lib/types/modal.d.ts
CHANGED
|
@@ -35,6 +35,39 @@ export interface ModalSeparatorDefinition {
|
|
|
35
35
|
/** Always stores `separator`. */
|
|
36
36
|
type: 'separator';
|
|
37
37
|
}
|
|
38
|
+
/** Describes one horizontal box rendered inside a modal. */
|
|
39
|
+
export interface ModalHorizontalBoxDefinition {
|
|
40
|
+
/** Always stores `horizontal-box`. */
|
|
41
|
+
type: 'horizontal-box';
|
|
42
|
+
/** Stores the ordered child content. */
|
|
43
|
+
content: PluginModalContentItem[];
|
|
44
|
+
/** Stores the optional spacing between children. */
|
|
45
|
+
gap?: string;
|
|
46
|
+
/** Stores the alignment hint along the main axis. */
|
|
47
|
+
align?: ModalContentAlign;
|
|
48
|
+
/** Stores whether children may wrap onto multiple rows. */
|
|
49
|
+
wrap?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Describes one vertical box rendered inside a modal. */
|
|
52
|
+
export interface ModalVerticalBoxDefinition {
|
|
53
|
+
/** Always stores `vertical-box`. */
|
|
54
|
+
type: 'vertical-box';
|
|
55
|
+
/** Stores the ordered child content. */
|
|
56
|
+
content: PluginModalContentItem[];
|
|
57
|
+
/** Stores the optional spacing between children. */
|
|
58
|
+
gap?: string;
|
|
59
|
+
}
|
|
60
|
+
/** Describes one grid rendered inside a modal. */
|
|
61
|
+
export interface ModalGridDefinition {
|
|
62
|
+
/** Always stores `grid`. */
|
|
63
|
+
type: 'grid';
|
|
64
|
+
/** Stores the ordered child content. */
|
|
65
|
+
content: PluginModalContentItem[];
|
|
66
|
+
/** Stores the CSS grid column template. */
|
|
67
|
+
columns: string;
|
|
68
|
+
/** Stores the optional spacing between cells. */
|
|
69
|
+
gap?: string;
|
|
70
|
+
}
|
|
38
71
|
/** Describes one button rendered inside a modal body. */
|
|
39
72
|
export interface ModalButtonItemDefinition extends ModalButtonDefinition {
|
|
40
73
|
/** Always stores `button`. */
|
|
@@ -119,7 +152,7 @@ export interface ModalListDefinition {
|
|
|
119
152
|
items: ModalListRowDefinition[];
|
|
120
153
|
}
|
|
121
154
|
/** Describes one plugin modal content item. */
|
|
122
|
-
export type PluginModalContentItem = ModalTextDefinition | ModalSeparatorDefinition | ModalButtonItemDefinition | ModalInputDefinition | ModalSelectDefinition | ModalListDefinition;
|
|
155
|
+
export type PluginModalContentItem = ModalTextDefinition | ModalSeparatorDefinition | ModalHorizontalBoxDefinition | ModalVerticalBoxDefinition | ModalGridDefinition | ModalButtonItemDefinition | ModalInputDefinition | ModalSelectDefinition | ModalListDefinition;
|
|
123
156
|
/** Describes the structured payload used to render a plugin modal. */
|
|
124
157
|
export interface PluginModalDefinition {
|
|
125
158
|
/** Stores the modal title. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openvcs/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18-beta.8",
|
|
4
4
|
"description": "OpenVCS SDK CLI for plugin scaffolding and runtime asset builds",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"homepage": "https://openvcs.app/",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^25.3.3",
|
|
43
|
-
"typescript": "^
|
|
43
|
+
"typescript": "^6.0.2"
|
|
44
44
|
},
|
|
45
45
|
"files": [
|
|
46
46
|
"src/",
|
package/src/lib/runtime/modal.ts
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
|
-
ModalButtonDefinition,
|
|
6
5
|
ModalButtonVariant,
|
|
7
6
|
ModalContentAlign,
|
|
8
|
-
|
|
7
|
+
ModalGridDefinition,
|
|
9
8
|
ModalInputKind,
|
|
10
|
-
|
|
9
|
+
ModalHorizontalBoxDefinition,
|
|
11
10
|
ModalListRowDefinition,
|
|
12
|
-
|
|
11
|
+
ModalVerticalBoxDefinition,
|
|
13
12
|
ModalSelectOptionDefinition,
|
|
14
13
|
PluginModalContentItem,
|
|
15
14
|
PluginModalDefinition,
|
|
@@ -71,6 +70,30 @@ export interface ModalBuilderListOptions {
|
|
|
71
70
|
items: ModalListRowDefinition[];
|
|
72
71
|
}
|
|
73
72
|
|
|
73
|
+
/** Describes the options accepted by `ModalBuilder.horizontalBox()`. */
|
|
74
|
+
export interface ModalBuilderHorizontalBoxOptions {
|
|
75
|
+
/** Stores the spacing between children. */
|
|
76
|
+
gap?: string;
|
|
77
|
+
/** Stores the alignment hint for the main axis. */
|
|
78
|
+
align?: ModalContentAlign;
|
|
79
|
+
/** Stores whether children may wrap. */
|
|
80
|
+
wrap?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Describes the options accepted by `ModalBuilder.verticalBox()`. */
|
|
84
|
+
export interface ModalBuilderVerticalBoxOptions {
|
|
85
|
+
/** Stores the spacing between children. */
|
|
86
|
+
gap?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Describes the options accepted by `ModalBuilder.grid()`. */
|
|
90
|
+
export interface ModalBuilderGridOptions {
|
|
91
|
+
/** Stores the CSS grid column template. */
|
|
92
|
+
columns: string;
|
|
93
|
+
/** Stores the spacing between cells. */
|
|
94
|
+
gap?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
74
97
|
/** Builds a structured modal definition with a fluent class API. */
|
|
75
98
|
export class ModalBuilder {
|
|
76
99
|
private readonly definition: PluginModalDefinition;
|
|
@@ -100,6 +123,39 @@ export class ModalBuilder {
|
|
|
100
123
|
return this;
|
|
101
124
|
}
|
|
102
125
|
|
|
126
|
+
/** Adds a horizontal box to the modal body. */
|
|
127
|
+
horizontalBox(content: PluginModalContentItem[], options: ModalBuilderHorizontalBoxOptions = {}): this {
|
|
128
|
+
this.definition.content.push({
|
|
129
|
+
type: 'horizontal-box',
|
|
130
|
+
content: cloneContent(content),
|
|
131
|
+
...(options.gap ? { gap: options.gap } : {}),
|
|
132
|
+
...(options.align ? { align: options.align } : {}),
|
|
133
|
+
...(options.wrap !== undefined ? { wrap: options.wrap } : {}),
|
|
134
|
+
} as ModalHorizontalBoxDefinition);
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Adds a vertical box to the modal body. */
|
|
139
|
+
verticalBox(content: PluginModalContentItem[], options: ModalBuilderVerticalBoxOptions = {}): this {
|
|
140
|
+
this.definition.content.push({
|
|
141
|
+
type: 'vertical-box',
|
|
142
|
+
content: cloneContent(content),
|
|
143
|
+
...(options.gap ? { gap: options.gap } : {}),
|
|
144
|
+
} as ModalVerticalBoxDefinition);
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Adds a grid container to the modal body. */
|
|
149
|
+
grid(content: PluginModalContentItem[], options: ModalBuilderGridOptions): this {
|
|
150
|
+
this.definition.content.push({
|
|
151
|
+
type: 'grid',
|
|
152
|
+
content: cloneContent(content),
|
|
153
|
+
columns: String(options.columns || '').trim(),
|
|
154
|
+
...(options.gap ? { gap: options.gap } : {}),
|
|
155
|
+
} as ModalGridDefinition);
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
103
159
|
/** Adds a button to the modal body. */
|
|
104
160
|
button(id: string, content: string, options: ModalBuilderButtonOptions = {}): this {
|
|
105
161
|
this.definition.content.push({
|
|
@@ -159,7 +215,7 @@ export class ModalBuilder {
|
|
|
159
215
|
build(): PluginModalDefinition {
|
|
160
216
|
return {
|
|
161
217
|
title: this.definition.title,
|
|
162
|
-
content: this.definition.content
|
|
218
|
+
content: cloneContent(this.definition.content),
|
|
163
219
|
};
|
|
164
220
|
}
|
|
165
221
|
|
|
@@ -168,3 +224,36 @@ export class ModalBuilder {
|
|
|
168
224
|
return this.build();
|
|
169
225
|
}
|
|
170
226
|
}
|
|
227
|
+
|
|
228
|
+
/** Clones modal content recursively so nested containers stay isolated. */
|
|
229
|
+
function cloneContent(content: PluginModalContentItem[]): PluginModalContentItem[] {
|
|
230
|
+
return Array.isArray(content) ? content.map((item) => cloneItem(item)) : [];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Clones one modal content item recursively. */
|
|
234
|
+
function cloneItem(item: PluginModalContentItem): PluginModalContentItem {
|
|
235
|
+
if (!item || typeof item !== 'object') return item;
|
|
236
|
+
|
|
237
|
+
if (item.type === 'horizontal-box') {
|
|
238
|
+
return {
|
|
239
|
+
...item,
|
|
240
|
+
content: cloneContent(item.content),
|
|
241
|
+
} as ModalHorizontalBoxDefinition;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (item.type === 'vertical-box') {
|
|
245
|
+
return {
|
|
246
|
+
...item,
|
|
247
|
+
content: cloneContent(item.content),
|
|
248
|
+
} as ModalVerticalBoxDefinition;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (item.type === 'grid') {
|
|
252
|
+
return {
|
|
253
|
+
...item,
|
|
254
|
+
content: cloneContent(item.content),
|
|
255
|
+
} as ModalGridDefinition;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { ...item };
|
|
259
|
+
}
|
package/src/lib/types/modal.ts
CHANGED
|
@@ -44,6 +44,42 @@ export interface ModalSeparatorDefinition {
|
|
|
44
44
|
type: 'separator';
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/** Describes one horizontal box rendered inside a modal. */
|
|
48
|
+
export interface ModalHorizontalBoxDefinition {
|
|
49
|
+
/** Always stores `horizontal-box`. */
|
|
50
|
+
type: 'horizontal-box';
|
|
51
|
+
/** Stores the ordered child content. */
|
|
52
|
+
content: PluginModalContentItem[];
|
|
53
|
+
/** Stores the optional spacing between children. */
|
|
54
|
+
gap?: string;
|
|
55
|
+
/** Stores the alignment hint along the main axis. */
|
|
56
|
+
align?: ModalContentAlign;
|
|
57
|
+
/** Stores whether children may wrap onto multiple rows. */
|
|
58
|
+
wrap?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Describes one vertical box rendered inside a modal. */
|
|
62
|
+
export interface ModalVerticalBoxDefinition {
|
|
63
|
+
/** Always stores `vertical-box`. */
|
|
64
|
+
type: 'vertical-box';
|
|
65
|
+
/** Stores the ordered child content. */
|
|
66
|
+
content: PluginModalContentItem[];
|
|
67
|
+
/** Stores the optional spacing between children. */
|
|
68
|
+
gap?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Describes one grid rendered inside a modal. */
|
|
72
|
+
export interface ModalGridDefinition {
|
|
73
|
+
/** Always stores `grid`. */
|
|
74
|
+
type: 'grid';
|
|
75
|
+
/** Stores the ordered child content. */
|
|
76
|
+
content: PluginModalContentItem[];
|
|
77
|
+
/** Stores the CSS grid column template. */
|
|
78
|
+
columns: string;
|
|
79
|
+
/** Stores the optional spacing between cells. */
|
|
80
|
+
gap?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
47
83
|
/** Describes one button rendered inside a modal body. */
|
|
48
84
|
export interface ModalButtonItemDefinition extends ModalButtonDefinition {
|
|
49
85
|
/** Always stores `button`. */
|
|
@@ -138,6 +174,9 @@ export interface ModalListDefinition {
|
|
|
138
174
|
export type PluginModalContentItem =
|
|
139
175
|
| ModalTextDefinition
|
|
140
176
|
| ModalSeparatorDefinition
|
|
177
|
+
| ModalHorizontalBoxDefinition
|
|
178
|
+
| ModalVerticalBoxDefinition
|
|
179
|
+
| ModalGridDefinition
|
|
141
180
|
| ModalButtonItemDefinition
|
|
142
181
|
| ModalInputDefinition
|
|
143
182
|
| ModalSelectDefinition
|
package/test/modal.test.ts
CHANGED
|
@@ -10,14 +10,83 @@ describe('ModalBuilder', () => {
|
|
|
10
10
|
it('builds a structured modal definition', () => {
|
|
11
11
|
const modal = new ModalBuilder('Manage Submodules')
|
|
12
12
|
.text('Hello, World!')
|
|
13
|
-
.
|
|
13
|
+
.verticalBox([
|
|
14
|
+
{
|
|
15
|
+
type: 'input',
|
|
16
|
+
id: 'path',
|
|
17
|
+
label: 'Submodule Path',
|
|
18
|
+
placeholder: 'libs/example',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'grid',
|
|
22
|
+
columns: 'minmax(0, 1fr) minmax(0, 1fr)',
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: 'input',
|
|
26
|
+
id: 'name',
|
|
27
|
+
label: 'Submodule Name',
|
|
28
|
+
placeholder: 'example',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: 'input',
|
|
32
|
+
id: 'branch',
|
|
33
|
+
label: 'Branch (optional)',
|
|
34
|
+
placeholder: 'main',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
])
|
|
39
|
+
.horizontalBox(
|
|
40
|
+
[
|
|
41
|
+
{ type: 'button', id: 'new-button', content: 'Test button, push me!', align: 'centered' },
|
|
42
|
+
{ type: 'button', id: 'secondary-button', content: 'Refresh' },
|
|
43
|
+
],
|
|
44
|
+
{ align: 'centered', wrap: true },
|
|
45
|
+
)
|
|
14
46
|
.build();
|
|
15
47
|
|
|
16
48
|
assert.deepStrictEqual(modal, {
|
|
17
49
|
title: 'Manage Submodules',
|
|
18
50
|
content: [
|
|
19
51
|
{ type: 'text', content: 'Hello, World!' },
|
|
20
|
-
{
|
|
52
|
+
{
|
|
53
|
+
type: 'vertical-box',
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: 'input',
|
|
57
|
+
id: 'path',
|
|
58
|
+
label: 'Submodule Path',
|
|
59
|
+
placeholder: 'libs/example',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'grid',
|
|
63
|
+
columns: 'minmax(0, 1fr) minmax(0, 1fr)',
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: 'input',
|
|
67
|
+
id: 'name',
|
|
68
|
+
label: 'Submodule Name',
|
|
69
|
+
placeholder: 'example',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'input',
|
|
73
|
+
id: 'branch',
|
|
74
|
+
label: 'Branch (optional)',
|
|
75
|
+
placeholder: 'main',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: 'horizontal-box',
|
|
83
|
+
align: 'centered',
|
|
84
|
+
wrap: true,
|
|
85
|
+
content: [
|
|
86
|
+
{ type: 'button', id: 'new-button', content: 'Test button, push me!', align: 'centered' },
|
|
87
|
+
{ type: 'button', id: 'secondary-button', content: 'Refresh' },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
21
90
|
],
|
|
22
91
|
});
|
|
23
92
|
});
|