@signosoft/signpad-js 0.2.3 → 0.3.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/README.md +544 -142
- package/dist/index.d.ts +79 -44
- package/dist/signosoft-signpad.js +790 -779
- package/dist/signosoft-signpad.umd.cjs +39 -39
- package/package.json +2 -12
- package/src/styles/signosoft-signpad.css +43 -5
- package/src/styles/styles-variables.css +29 -9
- package/src/scripts/generate-theme.js +0 -340
package/package.json
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signosoft/signpad-js",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.1",
|
|
5
5
|
"browser": {
|
|
6
|
-
"fs": false
|
|
7
|
-
"path": false,
|
|
8
|
-
"url": false,
|
|
9
|
-
"node:process": false,
|
|
10
|
-
"node:readline/promises": false,
|
|
11
|
-
"chalk": false
|
|
6
|
+
"fs": false
|
|
12
7
|
},
|
|
13
8
|
"type": "module",
|
|
14
9
|
"main": "./dist/signosoft-signpad.umd.cjs",
|
|
@@ -25,12 +20,8 @@
|
|
|
25
20
|
"default": "./dist/angular/fesm2022/signpad-js-angular.mjs"
|
|
26
21
|
}
|
|
27
22
|
},
|
|
28
|
-
"bin": {
|
|
29
|
-
"getSignpadTheme": "src/scripts/generate-theme.js"
|
|
30
|
-
},
|
|
31
23
|
"files": [
|
|
32
24
|
"dist",
|
|
33
|
-
"src/scripts/generate-theme.js",
|
|
34
25
|
"src/styles/styles-variables.css",
|
|
35
26
|
"src/styles/signosoft-signpad.css",
|
|
36
27
|
"docsVideo.gif",
|
|
@@ -47,7 +38,6 @@
|
|
|
47
38
|
"lit": "^3.3.1"
|
|
48
39
|
},
|
|
49
40
|
"devDependencies": {
|
|
50
|
-
"chalk": "^5.6.2",
|
|
51
41
|
"@rollup/plugin-replace": "^6.0.3",
|
|
52
42
|
"@types/node": "^25.0.9",
|
|
53
43
|
"typescript": "~5.9.3",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import "./styles-variables.css";
|
|
1
|
+
@import "./styles-variables.css";
|
|
2
2
|
|
|
3
3
|
html {
|
|
4
4
|
font-family: var(--sign-font-family) !important;
|
|
@@ -19,6 +19,10 @@ html {
|
|
|
19
19
|
background-color: var(--sign-top-bar-bg-base);
|
|
20
20
|
color: var(--sign-top-bar-text-base);
|
|
21
21
|
align-items: center;
|
|
22
|
+
border-top-left-radius: var(--sign-top-bar-border-top-left-radius);
|
|
23
|
+
border-top-right-radius: var(--sign-top-bar-border-top-right-radius);
|
|
24
|
+
border-bottom-left-radius: var(--sign-top-bar-border-bottom-left-radius);
|
|
25
|
+
border-bottom-right-radius: var(--sign-top-bar-border-bottom-right-radius);
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
.signpad-top-left-actions,
|
|
@@ -44,11 +48,39 @@ html {
|
|
|
44
48
|
background-color: var(--sign-canvas-bg-base);
|
|
45
49
|
box-sizing: border-box;
|
|
46
50
|
overflow: hidden;
|
|
51
|
+
border-top-left-radius: var(--sign-canvas-wrapper-border-top-left-radius);
|
|
52
|
+
border-top-right-radius: var(--sign-canvas-wrapper-border-top-right-radius);
|
|
53
|
+
border-bottom-left-radius: var(
|
|
54
|
+
--sign-canvas-wrapper-border-bottom-left-radius
|
|
55
|
+
);
|
|
56
|
+
border-bottom-right-radius: var(
|
|
57
|
+
--sign-canvas-wrapper-border-bottom-right-radius
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.signpad-canvas-container > .signpad-canvas-wrapper:first-child {
|
|
62
|
+
--sign-canvas-wrapper-border-top-left-radius: var(
|
|
63
|
+
--sign-common-border-radius
|
|
64
|
+
);
|
|
65
|
+
--sign-canvas-wrapper-border-top-right-radius: var(
|
|
66
|
+
--sign-common-border-radius
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.signpad-canvas-container > .signpad-canvas-wrapper:last-child {
|
|
71
|
+
--sign-canvas-wrapper-border-bottom-left-radius: var(
|
|
72
|
+
--sign-common-border-radius
|
|
73
|
+
);
|
|
74
|
+
--sign-canvas-wrapper-border-bottom-right-radius: var(
|
|
75
|
+
--sign-common-border-radius
|
|
76
|
+
);
|
|
47
77
|
}
|
|
48
78
|
.signpad-canvas {
|
|
49
79
|
position: absolute;
|
|
50
80
|
width: 100%;
|
|
51
|
-
height: calc(
|
|
81
|
+
height: calc(
|
|
82
|
+
100% - (var(--sign-canvas-height-offset) + var(--sign-line-margin-bottom))
|
|
83
|
+
);
|
|
52
84
|
left: 0;
|
|
53
85
|
top: 0;
|
|
54
86
|
}
|
|
@@ -93,17 +125,20 @@ html {
|
|
|
93
125
|
padding: var(--sign-bottom-bar-padding);
|
|
94
126
|
min-height: var(--sign-bottom-bar-min-height);
|
|
95
127
|
background-color: var(--sign-bottom-bar-bg-base);
|
|
128
|
+
border-top-left-radius: var(--sign-bottom-bar-border-top-left-radius);
|
|
129
|
+
border-top-right-radius: var(--sign-bottom-bar-border-top-right-radius);
|
|
130
|
+
border-bottom-left-radius: var(--sign-bottom-bar-border-bottom-left-radius);
|
|
131
|
+
border-bottom-right-radius: var(--sign-bottom-bar-border-bottom-right-radius);
|
|
96
132
|
}
|
|
97
133
|
.btn {
|
|
98
134
|
font-family: var(--sign-font-family);
|
|
99
135
|
min-height: var(--sign-button-min-height);
|
|
100
136
|
padding: var(--sign-button-padding);
|
|
101
137
|
margin: 0;
|
|
102
|
-
|
|
103
|
-
font-weight: 500;
|
|
138
|
+
font-weight: var(--sign-button-font-weight);
|
|
104
139
|
width: 100%;
|
|
105
140
|
font-size: var(--sign-button-font-size);
|
|
106
|
-
border-radius:
|
|
141
|
+
border-radius: var(--sign-button-border-radius);
|
|
107
142
|
transition:
|
|
108
143
|
background-color 0.3s ease,
|
|
109
144
|
color 0.3s ease,
|
|
@@ -119,14 +154,17 @@ html {
|
|
|
119
154
|
.btn-primary {
|
|
120
155
|
background-color: var(--sign-button-primary-bg-base);
|
|
121
156
|
color: var(--sign-button-primary-text-base);
|
|
157
|
+
border: var(--sign-button-primary-border-base);
|
|
122
158
|
}
|
|
123
159
|
.btn-primary:hover:not(:disabled) {
|
|
124
160
|
background-color: var(--sign-button-primary-bg-hover);
|
|
125
161
|
color: var(--sign-button-primary-text-hover);
|
|
162
|
+
border: var(--sign-button-primary-border-hover);
|
|
126
163
|
}
|
|
127
164
|
.btn-primary:disabled {
|
|
128
165
|
background-color: var(--sign-button-primary-bg-disabled);
|
|
129
166
|
color: var(--sign-button-primary-text-disabled);
|
|
167
|
+
border: var(--sign-button-primary-border-disabled);
|
|
130
168
|
}
|
|
131
169
|
.btn-link {
|
|
132
170
|
background-color: var(--sign-button-link-bg-base);
|
|
@@ -12,25 +12,34 @@
|
|
|
12
12
|
--min-height: 48px;
|
|
13
13
|
--spacing-constraints: 16px 24px;
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
/* CSS Variables */
|
|
17
16
|
--sign-font-family: Arial, Helvetica, sans-serif;
|
|
17
|
+
--sign-common-border-radius: 8px;
|
|
18
18
|
|
|
19
19
|
/* Top Bar */
|
|
20
20
|
--sign-top-bar-bg-base: var(--background-color-10);
|
|
21
21
|
--sign-top-bar-text-base: var(--primary-color-0);
|
|
22
22
|
--sign-top-bar-padding: var(--spacing-constraints);
|
|
23
23
|
--sign-top-bar-min-height: var(--min-height);
|
|
24
|
+
--sign-top-bar-border-top-left-radius: var(--sign-common-border-radius);
|
|
25
|
+
--sign-top-bar-border-top-right-radius: var(--sign-common-border-radius);
|
|
26
|
+
--sign-top-bar-border-bottom-left-radius: 0;
|
|
27
|
+
--sign-top-bar-border-bottom-right-radius: 0;
|
|
24
28
|
|
|
25
29
|
/* Canvas Area */
|
|
26
30
|
--sign-canvas-bg-base: var(--background-color-0);
|
|
31
|
+
--sign-canvas-wrapper-border-top-left-radius: 0;
|
|
32
|
+
--sign-canvas-wrapper-border-top-right-radius: 0;
|
|
33
|
+
--sign-canvas-wrapper-border-bottom-left-radius: 0;
|
|
34
|
+
--sign-canvas-wrapper-border-bottom-right-radius: 0;
|
|
27
35
|
|
|
28
36
|
/* Line */
|
|
29
37
|
--sign-line-height: 22px;
|
|
38
|
+
--sign-line-margin-bottom: 16px;
|
|
30
39
|
--sign-line-border-base: var(--primary-color-10);
|
|
31
40
|
--sign-line-additional-text-color: var(--text-color-0);
|
|
32
|
-
--sign-line-margin: var(--
|
|
33
|
-
--sign-canvas-line-text-font-size:
|
|
41
|
+
--sign-line-margin: 0px 24px var(--sign-line-margin-bottom) 24px;
|
|
42
|
+
--sign-canvas-line-text-font-size: 12px;
|
|
34
43
|
--sign-canvas-height-offset: var(--sign-line-height);
|
|
35
44
|
|
|
36
45
|
/* Bottom Bar */
|
|
@@ -38,28 +47,39 @@
|
|
|
38
47
|
--sign-bottom-bar-padding: var(--spacing-constraints);
|
|
39
48
|
--sign-bottom-bar-min-height: var(--min-height);
|
|
40
49
|
--sign-bottom-bar-gap: 12px;
|
|
50
|
+
--sign-bottom-bar-border-top-left-radius: 0;
|
|
51
|
+
--sign-bottom-bar-border-top-right-radius: 0;
|
|
52
|
+
--sign-bottom-bar-border-bottom-left-radius: var(--sign-common-border-radius);
|
|
53
|
+
--sign-bottom-bar-border-bottom-right-radius: var(
|
|
54
|
+
--sign-common-border-radius
|
|
55
|
+
);
|
|
41
56
|
|
|
42
57
|
/* Button general settings */
|
|
43
|
-
--sign-button-font-size:
|
|
58
|
+
--sign-button-font-size: 16px;
|
|
44
59
|
--sign-button-padding: 14px 16px;
|
|
45
60
|
--sign-button-min-height: var(--min-height);
|
|
61
|
+
--sign-button-border-radius: var(--sign-common-border-radius);
|
|
62
|
+
--sign-button-font-weight: 500;
|
|
46
63
|
|
|
47
64
|
/* Primary Buttons (OK, Clear, Cancel) */
|
|
48
|
-
--sign-button-primary-bg-base:
|
|
65
|
+
--sign-button-primary-bg-base: var(--primary-color-0);
|
|
49
66
|
--sign-button-primary-bg-hover: var(--primary-color-10);
|
|
50
67
|
--sign-button-primary-bg-disabled: var(--grey-color);
|
|
51
68
|
--sign-button-primary-text-base: var(--white-color);
|
|
52
69
|
--sign-button-primary-text-hover: var(--white-color);
|
|
53
70
|
--sign-button-primary-text-disabled: var(--white-color);
|
|
71
|
+
--sign-button-primary-border-base: 1px solid var(--primary-color-0);
|
|
72
|
+
--sign-button-primary-border-hover: 1px solid var(--primary-color-10);
|
|
73
|
+
--sign-button-primary-border-disabled: 1px solid var(--grey-color);
|
|
54
74
|
|
|
55
75
|
/* Link Buttons (Connect signpad) */
|
|
56
76
|
--sign-button-link-bg-base: transparent;
|
|
57
77
|
--sign-button-link-bg-hover: var(--background-color-10);
|
|
58
78
|
--sign-button-link-bg-disabled: transparent;
|
|
59
|
-
--sign-button-link-text-base:
|
|
79
|
+
--sign-button-link-text-base: var(--primary-color-0);
|
|
60
80
|
--sign-button-link-text-hover: var(--primary-color-10);
|
|
61
81
|
--sign-button-link-text-disabled: var(--grey-color);
|
|
62
|
-
--sign-button-link-border-base: 1px solid
|
|
82
|
+
--sign-button-link-border-base: 1px solid var(--primary-color-0);
|
|
63
83
|
--sign-button-link-border-hover: 1px solid var(--primary-color-10);
|
|
64
84
|
--sign-button-link-border-disabled: 1px solid var(--grey-color);
|
|
65
85
|
|
|
@@ -70,7 +90,7 @@
|
|
|
70
90
|
/* Loading Overlay */
|
|
71
91
|
--sign-loading-overlay-bg-color: rgba(255, 255, 255, 0.8);
|
|
72
92
|
--sign-loading-overlay-text-color: var(--text-color-0);
|
|
73
|
-
--sign-loading-overlay-spinner-color:
|
|
93
|
+
--sign-loading-overlay-spinner-color: var(--primary-color-0);
|
|
74
94
|
--sign-loading-overlay-spinner-border-color: rgba(78, 86, 234, 0.1);
|
|
75
95
|
--sign-loading-overlay-spinner-size: 30px;
|
|
76
|
-
}
|
|
96
|
+
}
|
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import { stdin as stdinStream, stdout as stdoutStream } from "node:process";
|
|
6
|
-
import { createInterface } from "node:readline/promises";
|
|
7
|
-
import chalk from "chalk";
|
|
8
|
-
|
|
9
|
-
// Define your brand color using chalk's hex method
|
|
10
|
-
const brandPrimary = chalk.hex("#4e56ea");
|
|
11
|
-
|
|
12
|
-
const args = process.argv.slice(2);
|
|
13
|
-
|
|
14
|
-
const getArg = (name, fallback = undefined) => {
|
|
15
|
-
const idx = args.indexOf(name);
|
|
16
|
-
if (idx === -1) return fallback;
|
|
17
|
-
|
|
18
|
-
// Check if the argument is a flag without a value (e.g., --force)
|
|
19
|
-
if (idx + 1 >= args.length || args[idx + 1].startsWith("--")) {
|
|
20
|
-
return true; // If it's just a flag, return true
|
|
21
|
-
}
|
|
22
|
-
return args[idx + 1];
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// --- Utility function to convert a string to PascalCase for JS identifiers ---
|
|
26
|
-
// This will allow names like 'MyCustomTheme'. It also returns a flag if it made changes.
|
|
27
|
-
const toPascalCase = (str) => {
|
|
28
|
-
let changed = false;
|
|
29
|
-
let result = str;
|
|
30
|
-
|
|
31
|
-
// 1. Remove non-alphanumeric characters (keeping hyphens/underscores for splitting)
|
|
32
|
-
const cleanedAlphaNum = result.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
33
|
-
if (cleanedAlphaNum !== result) changed = true;
|
|
34
|
-
result = cleanedAlphaNum;
|
|
35
|
-
|
|
36
|
-
// 2. Convert after separators (e.g., my-theme -> MyTheme, another_name -> AnotherName)
|
|
37
|
-
const pascalSeparators = result.replace(
|
|
38
|
-
/([_-])([a-zA-Z0-9])/g,
|
|
39
|
-
(_, separator, char) => {
|
|
40
|
-
if (separator) changed = true; // A separator was found and will be replaced
|
|
41
|
-
return char.toUpperCase();
|
|
42
|
-
},
|
|
43
|
-
);
|
|
44
|
-
if (pascalSeparators !== result) changed = true;
|
|
45
|
-
result = pascalSeparators;
|
|
46
|
-
|
|
47
|
-
// 3. prepended with an underscore if it's a number
|
|
48
|
-
if (result.length > 0) {
|
|
49
|
-
const firstChar = result.charAt(0);
|
|
50
|
-
if (/[0-9]/.test(firstChar)) {
|
|
51
|
-
// If starts with a number, prepend underscore
|
|
52
|
-
result = `_${result}`;
|
|
53
|
-
changed = true;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return { value: result, changed: changed };
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// --- Utility function to make a string safe for filenames (preserves initial casing) ---
|
|
61
|
-
// This will allow names like 'My-Custom-Theme.ts' or 'AnotherName.js'
|
|
62
|
-
const toFilenameSafe = (str) => {
|
|
63
|
-
return str
|
|
64
|
-
.replace(/[^a-zA-Z0-9\s-_\.]/g, "") // Keep letters, numbers, spaces, hyphens, underscores, dots
|
|
65
|
-
.replace(/\s+/g, "-") // Replace spaces with single hyphens
|
|
66
|
-
.replace(/([_-])([_-])+/g, "$1") // Replace multiple _ or - with a single one (e.g., __ to _, -- to -)
|
|
67
|
-
.replace(/^-+|-+$/g, ""); // Trim leading/trailing hyphens/underscores
|
|
68
|
-
};
|
|
69
|
-
// -----------------------------------------------------------------------------
|
|
70
|
-
|
|
71
|
-
// Determine the path to the CSS variables file within your library
|
|
72
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
73
|
-
const __dirname = path.dirname(__filename);
|
|
74
|
-
const packageRoot = path.resolve(__dirname, "..");
|
|
75
|
-
|
|
76
|
-
// --- CORRECTED PATH HERE ---
|
|
77
|
-
// Based on your provided library structure, 'styles-variables.css' is inside the 'src' folder.
|
|
78
|
-
const defaultCssPathInLibrary = path.join(
|
|
79
|
-
packageRoot,
|
|
80
|
-
"styles",
|
|
81
|
-
"styles-variables.css",
|
|
82
|
-
);
|
|
83
|
-
// ---------------------------
|
|
84
|
-
|
|
85
|
-
const input = getArg("--in", defaultCssPathInLibrary);
|
|
86
|
-
let out = getArg("--out"); // Required
|
|
87
|
-
let rawNameInput = getArg("--name"); // Capture the user's raw input for --name
|
|
88
|
-
let lang = getArg("--lang", "ts"); // New: Optional, defaults to 'ts'. Can be 'ts' or 'js'.
|
|
89
|
-
|
|
90
|
-
const shouldPrompt = !out || !rawNameInput || (lang !== "ts" && lang !== "js");
|
|
91
|
-
|
|
92
|
-
const selectOption = async (title, options, defaultIndex = 0) => {
|
|
93
|
-
if (!stdinStream.isTTY) {
|
|
94
|
-
return options[defaultIndex].value;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
let index = defaultIndex;
|
|
98
|
-
let linesRendered = 0;
|
|
99
|
-
|
|
100
|
-
const buildLines = () => {
|
|
101
|
-
const lines = [title];
|
|
102
|
-
for (let i = 0; i < options.length; i++) {
|
|
103
|
-
const isActive = i === index;
|
|
104
|
-
const prefix = isActive ? brandPrimary("❯") : " ";
|
|
105
|
-
const label = isActive
|
|
106
|
-
? brandPrimary.bold(options[i].label)
|
|
107
|
-
: options[i].label;
|
|
108
|
-
lines.push(` ${prefix} ${label}`);
|
|
109
|
-
}
|
|
110
|
-
return lines;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const writeLines = (lines, replace = false) => {
|
|
114
|
-
if (replace && linesRendered > 0) {
|
|
115
|
-
stdoutStream.write(`\u001b[${linesRendered}A`);
|
|
116
|
-
}
|
|
117
|
-
for (let i = 0; i < lines.length; i++) {
|
|
118
|
-
stdoutStream.write("\r\u001b[2K");
|
|
119
|
-
stdoutStream.write(lines[i]);
|
|
120
|
-
stdoutStream.write("\n");
|
|
121
|
-
}
|
|
122
|
-
linesRendered = lines.length;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const render = () => writeLines(buildLines(), false);
|
|
126
|
-
const rerender = () => writeLines(buildLines(), true);
|
|
127
|
-
|
|
128
|
-
return new Promise((resolve) => {
|
|
129
|
-
const onData = (buf) => {
|
|
130
|
-
const key = buf.toString();
|
|
131
|
-
if (key === "\u0003") {
|
|
132
|
-
stdinStream.setRawMode(false);
|
|
133
|
-
stdinStream.off("data", onData);
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
if (key === "\u001b[A") {
|
|
137
|
-
index = (index - 1 + options.length) % options.length;
|
|
138
|
-
rerender();
|
|
139
|
-
} else if (key === "\u001b[B") {
|
|
140
|
-
index = (index + 1) % options.length;
|
|
141
|
-
rerender();
|
|
142
|
-
} else if (key === "\r") {
|
|
143
|
-
stdinStream.setRawMode(false);
|
|
144
|
-
stdinStream.off("data", onData);
|
|
145
|
-
stdinStream.pause();
|
|
146
|
-
stdoutStream.write("\u001b[?25h");
|
|
147
|
-
stdoutStream.write("\n");
|
|
148
|
-
resolve(options[index].value);
|
|
149
|
-
} else {
|
|
150
|
-
// Ignore any other input and keep the selector clean
|
|
151
|
-
rerender();
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
stdinStream.setRawMode(true);
|
|
156
|
-
stdinStream.resume();
|
|
157
|
-
stdoutStream.write("\u001b[?25l");
|
|
158
|
-
render();
|
|
159
|
-
stdinStream.on("data", onData);
|
|
160
|
-
});
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
if (shouldPrompt) {
|
|
164
|
-
const rl = createInterface({ input: stdinStream, output: stdoutStream });
|
|
165
|
-
try {
|
|
166
|
-
if (!out) {
|
|
167
|
-
out = (
|
|
168
|
-
await rl.question(
|
|
169
|
-
`Where should be theme generated? (default = current directory):`,
|
|
170
|
-
)
|
|
171
|
-
).trim();
|
|
172
|
-
if (!out) {
|
|
173
|
-
out = process.cwd();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if (!rawNameInput) {
|
|
177
|
-
rawNameInput = (
|
|
178
|
-
await rl.question("Theme name (exported const): ")
|
|
179
|
-
).trim();
|
|
180
|
-
}
|
|
181
|
-
} finally {
|
|
182
|
-
rl.close();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (lang !== "ts" && lang !== "js") {
|
|
186
|
-
lang = "ts";
|
|
187
|
-
}
|
|
188
|
-
lang = await selectOption(
|
|
189
|
-
"Select output language:",
|
|
190
|
-
[
|
|
191
|
-
{ label: "TypeScript (ts)", value: "ts" },
|
|
192
|
-
{ label: "JavaScript (js)", value: "js" },
|
|
193
|
-
],
|
|
194
|
-
lang === "js" ? 1 : 0,
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// --- Validation for required arguments ---
|
|
199
|
-
if (!out) {
|
|
200
|
-
console.error(
|
|
201
|
-
chalk.red("❌ Error:") +
|
|
202
|
-
` The ${brandPrimary("--out")} argument is required. Please specify the output file path or directory (e.g., ${brandPrimary("--out src/app/")} or ${brandPrimary("--out src/app/MyTheme.ts")})`,
|
|
203
|
-
);
|
|
204
|
-
process.exit(1);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (!rawNameInput) {
|
|
208
|
-
console.error(
|
|
209
|
-
chalk.red("❌ Error:") +
|
|
210
|
-
` The ${brandPrimary("--name")} argument is required. Please specify the name for the exported object (e.g., ${brandPrimary("--name MyCustomTheme")})`,
|
|
211
|
-
);
|
|
212
|
-
process.exit(1);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// --- Process the name for both purposes ---
|
|
216
|
-
const { value: pascalCaseConstName, changed: nameWasTransformed } =
|
|
217
|
-
toPascalCase(rawNameInput);
|
|
218
|
-
const safeFilenameBase = toFilenameSafe(rawNameInput); // For constructing filename if --out is a directory
|
|
219
|
-
|
|
220
|
-
// Only display a warning if the name was actually transformed by toPascalCase
|
|
221
|
-
if (nameWasTransformed) {
|
|
222
|
-
console.log(
|
|
223
|
-
chalk.yellow("⚠️ Warning:") +
|
|
224
|
-
` The provided object name '${chalk.cyan(rawNameInput)}' has been transformed to '${brandPrimary.bold(pascalCaseConstName)}' for valid JavaScript variable naming.`,
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Final check for valid JS identifier for the exported const name
|
|
229
|
-
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(pascalCaseConstName)) {
|
|
230
|
-
console.error(
|
|
231
|
-
chalk.red("❌ Error:") +
|
|
232
|
-
` The generated object name '${brandPrimary(pascalCaseConstName)}' is not a valid JavaScript identifier. Please choose a different ${brandPrimary("--name")} that can be converted.`,
|
|
233
|
-
);
|
|
234
|
-
process.exit(1);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Validate --lang argument
|
|
238
|
-
if (lang !== "ts" && lang !== "js") {
|
|
239
|
-
console.error(
|
|
240
|
-
chalk.red("❌ Error:") +
|
|
241
|
-
` Invalid value for ${brandPrimary("--lang")}. It must be either '${chalk.yellow("ts")}' or '${chalk.yellow("js")}'.`,
|
|
242
|
-
);
|
|
243
|
-
process.exit(1);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// --- Determine final output file path and extension ---
|
|
247
|
-
let finalOutputPath;
|
|
248
|
-
let finalExtension;
|
|
249
|
-
|
|
250
|
-
const outProvidedExtension = path.extname(out); // e.g., '.ts', '.js', or ''
|
|
251
|
-
|
|
252
|
-
if (outProvidedExtension) {
|
|
253
|
-
// User provided a full file path with an extension (e.g., "src/app/MyTheme.js")
|
|
254
|
-
finalOutputPath = out;
|
|
255
|
-
finalExtension = outProvidedExtension.substring(1); // Remove the leading dot (e.g., "js")
|
|
256
|
-
} else {
|
|
257
|
-
// User provided a directory path (e.g., "src/app/"), so we construct the filename
|
|
258
|
-
finalExtension = lang; // Use the --lang argument for the extension (e.g., "ts" or "js")
|
|
259
|
-
// Use the filename-safe version of the name for the file name, preserving its original casing intent
|
|
260
|
-
finalOutputPath = path.join(out, `${safeFilenameBase}.${finalExtension}`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// --- Input File Existence Check ---
|
|
264
|
-
if (!fs.existsSync(input)) {
|
|
265
|
-
console.error(
|
|
266
|
-
chalk.red("❌ Error:") +
|
|
267
|
-
` Input file '${chalk.yellow(input)}' not found.
|
|
268
|
-
Please ensure that '${brandPrimary("src/styles-variables.css")}' is included in your ${brandPrimary("package.json")}'s ${brandPrimary("files")} array and is present in the installed library.
|
|
269
|
-
If you want to use a custom file, specify its path using ${brandPrimary("--in <path/to/file.css>")}`,
|
|
270
|
-
);
|
|
271
|
-
process.exit(1);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const cssText = fs.readFileSync(input, "utf8");
|
|
275
|
-
|
|
276
|
-
const lines = cssText.split(/\r?\n/);
|
|
277
|
-
const output = [];
|
|
278
|
-
output.push(`export const ${pascalCaseConstName} = {`); // Use pascalCaseConstName here
|
|
279
|
-
|
|
280
|
-
// Initialize this flag *before* the loop
|
|
281
|
-
let lastItemWasVariable = false; // Moved 'let' to define it outside the loop, if it's not already.
|
|
282
|
-
|
|
283
|
-
for (const rawLine of lines) {
|
|
284
|
-
const line = rawLine.trim();
|
|
285
|
-
if (!line) {
|
|
286
|
-
// If we encounter an empty line in the input, and the last item was a variable,
|
|
287
|
-
// we can use this as an opportunity to add a blank line, similar to how comments do.
|
|
288
|
-
// This helps break up variables if the source CSS itself uses blank lines for grouping.
|
|
289
|
-
if (lastItemWasVariable) {
|
|
290
|
-
output.push("");
|
|
291
|
-
lastItemWasVariable = false; // Reset to avoid multiple blank lines
|
|
292
|
-
}
|
|
293
|
-
continue; // Skip processing actual empty lines in input
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (line.startsWith(":host")) continue; // Assuming you want to ignore this
|
|
297
|
-
if (line === "}") continue; // Assuming you want to ignore this
|
|
298
|
-
|
|
299
|
-
if (line.startsWith("/*") && line.endsWith("*/")) {
|
|
300
|
-
const comment = line.replace(/^\/\*\s*/, "").replace(/\s*\*\/$/, "");
|
|
301
|
-
|
|
302
|
-
// Add a blank line before a grouping comment, if the previous item added was a variable.
|
|
303
|
-
// This creates visual separation between groups.
|
|
304
|
-
if (lastItemWasVariable) {
|
|
305
|
-
output.push("");
|
|
306
|
-
}
|
|
307
|
-
output.push(` // ${comment}`);
|
|
308
|
-
lastItemWasVariable = false; // Reset the flag, as the last item added was a comment
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (line.startsWith("--")) {
|
|
313
|
-
const match = line.match(/^(--[^:]+):\s*(.+?);\s*$/);
|
|
314
|
-
if (!match) {
|
|
315
|
-
console.warn(
|
|
316
|
-
chalk.yellow("⚠️ Warning:") +
|
|
317
|
-
` Line '${chalk.cyan(line)}' looks like a CSS variable but could not be parsed correctly. Skipping.`,
|
|
318
|
-
);
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
const [, varName, value] = match;
|
|
322
|
-
output.push(` "${varName}": "${value.replace(/"/g, '\\"')}",`);
|
|
323
|
-
lastItemWasVariable = true; // Mark that a variable was just added
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
output.push("};");
|
|
329
|
-
output.push("");
|
|
330
|
-
|
|
331
|
-
// Create directories for the final output path
|
|
332
|
-
fs.mkdirSync(path.dirname(finalOutputPath), { recursive: true });
|
|
333
|
-
// Write the file
|
|
334
|
-
fs.writeFileSync(finalOutputPath, output.join("\n"), "utf8");
|
|
335
|
-
|
|
336
|
-
// Success message
|
|
337
|
-
console.log(
|
|
338
|
-
brandPrimary("✨ Success!") +
|
|
339
|
-
` Generated '${chalk.cyan(finalOutputPath)}' from '${chalk.cyan(input)}' as '${brandPrimary.bold(pascalCaseConstName)}' (${finalExtension.toUpperCase()} file).`,
|
|
340
|
-
);
|