@signosoft/signpad-js 0.0.1 → 0.2.0

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.
@@ -0,0 +1,100 @@
1
+ # <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/angular/angular-original.svg" alt="Angular" width="30" height="30"> Angular Integration Guide
2
+
3
+ This guide describes how to integrate the `@signosoft/signpad-js` web component into an Angular application.
4
+
5
+ ## 1. Installation
6
+
7
+ Install the core package using npm:
8
+
9
+ ```bash
10
+ npm install @signosoft/signpad-js
11
+ ```
12
+
13
+ ## 2. Create the Bridge Directive
14
+
15
+ Since `signosoft-signpad` is a Web Component, Angular requires a Directive to properly sync configuration changes and provide typed access to the component instance.
16
+
17
+ Create `signosoft-signpad.directive.ts`:
18
+
19
+ ```typescript
20
+ import {
21
+ Directive,
22
+ ElementRef,
23
+ Input,
24
+ OnChanges,
25
+ SimpleChanges,
26
+ } from "@angular/core";
27
+ import type { SignosoftSignpad, SignpadConfig } from "@signosoft/signpad-js";
28
+
29
+ @Directive({
30
+ selector: "signosoft-signpad",
31
+ standalone: true,
32
+ })
33
+ export class SignosoftSignpadDirective implements OnChanges {
34
+ @Input() config?: SignpadConfig;
35
+
36
+ constructor(private host: ElementRef<SignosoftSignpad>) {}
37
+
38
+ // Access the native Web Component instance
39
+ get signpadRef(): SignosoftSignpad {
40
+ return this.host.nativeElement;
41
+ }
42
+
43
+ ngOnChanges(changes: SimpleChanges) {
44
+ const el = this.host.nativeElement as any;
45
+ for (const key of Object.keys(changes)) {
46
+ el[key] = (this as any)[key];
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## 3. Implementation in Component
53
+
54
+ Import the library, the directive, and add the **`CUSTOM_ELEMENTS_SCHEMA`**.
55
+
56
+ ```typescript
57
+ import { Component, ViewChild, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
58
+ import "@signosoft/signpad-js"; // Import the Web Component registration
59
+ import {
60
+ type SignosoftSignpad,
61
+ type SignpadConfig,
62
+ } from "@signosoft/signpad-js";
63
+ import { SignosoftSignpadDirective } from "./directives/signosoft-signpad.directive";
64
+
65
+ @Component({
66
+ selector: "app-root",
67
+ standalone: true,
68
+ imports: [SignosoftSignpadDirective],
69
+ templateUrl: "./app.component.html",
70
+ schemas: [CUSTOM_ELEMENTS_SCHEMA], // Mandatory for custom HTML tags
71
+ })
72
+ export class AppComponent {
73
+ @ViewChild(SignosoftSignpadDirective)
74
+ signpadDirective!: SignosoftSignpadDirective;
75
+
76
+ config: SignpadConfig = {
77
+ licenseKey: "YOUR-LICENSE-KEY",
78
+ // More info in "properties" section
79
+ };
80
+
81
+ // Helper to access methods easily (ok, clear, cancel, etc.)
82
+ public get signpad(): SignosoftSignpad | undefined {
83
+ return this.signpadDirective?.signpadRef;
84
+ }
85
+ }
86
+ ```
87
+
88
+ ## 4. Component Template
89
+
90
+ Use the custom tag in your HTML and bind the configuration object.
91
+
92
+ ```html
93
+ <div>
94
+ <div>
95
+ <signosoft-signpad [config]="config"></signosoft-signpad>
96
+ </div>
97
+ </div>
98
+ ```
99
+
100
+ ### [← Back to Main README](../README.md)
@@ -0,0 +1,64 @@
1
+ # 🍦 Vanilla JS Integration Guide
2
+
3
+ This guide describes how to integrate the `@signosoft/signpad-js` web component into a plain JavaScript project without any frameworks.
4
+
5
+ ## 1. Installation
6
+
7
+ Install the package via npm:
8
+
9
+ ```bash
10
+ npm install @signosoft/signpad-js
11
+ ```
12
+
13
+ Or, if you are not using a bundler, you can include the script directly in your HTML (ensure the path points to the compiled bundle in `node_modules` or a CDN).
14
+
15
+ ## 2. JavaScript Implementation
16
+
17
+ In Vanilla JS, you interact with the component by selecting it from the DOM and assigning the configuration directly to its `config` property.
18
+
19
+ ```javascript
20
+ import "@signosoft/signpad-js";
21
+
22
+ /**
23
+ * INTELLISENSE SUPPORT (Optional)
24
+ * Helps VS Code provide autocomplete for methods and properties.
25
+ * @type {import('@signosoft/signpad-js').SignosoftSignpad}
26
+ */
27
+ const pad = document.querySelector("signosoft-signpad");
28
+
29
+ // 1. Initial Configuration
30
+ pad.config = {
31
+ licenseKey: "YOUR-LICENSE-KEY",
32
+ };
33
+ ```
34
+
35
+ ## 3. HTML Structure
36
+
37
+ Add the custom element tag to your HTML and create the necessary control buttons.
38
+
39
+ ```html
40
+ <!-- index.html -->
41
+ <!DOCTYPE html>
42
+ <html lang="en">
43
+ <head>
44
+ <meta charset="UTF-8" />
45
+ <title>Signosoft Signpad - Vanilla JS</title>
46
+ </head>
47
+ <body>
48
+ <h1>Signosoft Signpad</h1>
49
+ <!-- The Web Component -->
50
+ <signosoft-signpad id="my-signpad"></signosoft-signpad>
51
+ <!-- Main logic script -->
52
+ <script type="module" src="main.js"></script>
53
+ </body>
54
+ </html>
55
+ ```
56
+
57
+ ## 4. Key Concepts
58
+
59
+ - **Direct Property Assignment:** Unlike some frameworks that use attributes, in Plain JS you should assign the configuration directly: `element.config = { ... }`.
60
+ - **Module System:** Ensure your `<script>` tag has `type="module"` to use the `import` statement.
61
+ - **Methods:** All methods like `.connect()`, `.ok()`, and `.clear()` are available directly on the DOM element object.
62
+ - **DOM Events:** The component dispatches standard DOM events (like `sign-ok`, `sign-clear`) which you can listen to using `addEventListener`.
63
+
64
+ ### [← Back to Main README](../README.md)
@@ -0,0 +1,42 @@
1
+ # <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg" alt="React" width="30" height="30"> React - Quick start
2
+
3
+ This guide describes how to integrate the `@signosoft/signpad-js` web component into a React application (Functional Components with Hooks).
4
+
5
+ ## 1. Installation
6
+
7
+ Install the core package using npm:
8
+
9
+ ```bash
10
+ npm install @signosoft/signpad-js
11
+ ```
12
+
13
+ ## 2. Implementation
14
+
15
+ In React, we use the `useRef` hook to interact with the component's methods (like `ok()` or `clear()`) and pass the configuration directly via props.
16
+
17
+ ```tsx
18
+ import { useRef } from "react";
19
+ import "@signosoft/signpad-js"; // Registers the web component
20
+ import type { SignpadConfig } from "@signosoft/signpad-js";
21
+
22
+ function App() {
23
+ // Use ref to access component methods (ok, clear, connect, etc. by signpadRef.current)
24
+ const signpadRef = useRef<any>(null);
25
+
26
+ const config: SignpadConfig = {
27
+ licenseKey: "YOUR-LICENSE-KEY",
28
+ // More info in "properties" section
29
+ };
30
+
31
+ return (
32
+ <div>
33
+ <h1>Signosoft Signpad React Example</h1>
34
+ <signosoft-signpad ref={signpadRef} config={config} />
35
+ </div>
36
+ );
37
+ }
38
+
39
+ export default App;
40
+ ```
41
+
42
+ ### [← Back to Main README](../README.md)
@@ -0,0 +1,67 @@
1
+ # <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/vuejs/vuejs-original.svg" alt="Vue" width="30" height="30"> Vue.js Integration Guide
2
+
3
+ This guide describes how to integrate the `@signosoft/signpad-js` web component into a Vue 3 application using the Composition API (`<script setup>`).
4
+
5
+ ## 1. Installation
6
+
7
+ Install the core package using npm:
8
+
9
+ ```bash
10
+ npm install @signosoft/signpad-js
11
+ ```
12
+
13
+ ## 2. Configure Vue to recognize Custom Elements
14
+
15
+ By default, Vue will try to resolve `signosoft-signpad` as a Vue component and will throw a warning. You need to tell Vue to ignore this tag.
16
+
17
+ **If you are using Vite (`vite.config.ts`):**
18
+
19
+ ```typescript
20
+ import { defineConfig } from "vite";
21
+ import vue from "@vitejs/plugin-vue";
22
+
23
+ export default defineConfig({
24
+ plugins: [
25
+ vue({
26
+ template: {
27
+ compilerOptions: {
28
+ // Treat all tags starting with 'signosoft-' as custom elements
29
+ isCustomElement: (tag) => tag.startsWith("signosoft-"),
30
+ },
31
+ },
32
+ }),
33
+ ],
34
+ });
35
+ ```
36
+
37
+ ## 3. Implementation
38
+
39
+ In Vue 3, we use `ref` to hold the reference to the DOM element and pass the configuration via the `:config` attribute.
40
+
41
+ ```typescript
42
+ <script setup lang="ts">
43
+ import { ref } from "vue";
44
+ import "@signosoft/signpad-js"; // Registers the Web Component
45
+ import type { SignpadConfig, SignosoftSignpad, IPenData } from "@signosoft/signpad-js";
46
+
47
+ // Use ref to access component methods (ok, clear, connect, etc. by signpadRef.value)
48
+ const signpadRef = ref<SignosoftSignpad | null>(null);
49
+
50
+ const signpadConfig: SignpadConfig = {
51
+ licenseKey: "YOUR-LICENSE-KEY",
52
+ // More info in "properties" section
53
+ };
54
+
55
+ </script>
56
+
57
+ <template>
58
+ <div>
59
+ <signosoft-signpad
60
+ ref="signpadRef"
61
+ :config="signpadConfig"
62
+ ></signosoft-signpad>
63
+ </div>
64
+ </template>
65
+ ```
66
+
67
+ ### [← Back to Main README](../README.md)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@signosoft/signpad-js",
3
3
  "private": false,
4
- "version": "0.0.1",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/signosoft-signpad.umd.cjs",
7
7
  "module": "./dist/signosoft-signpad.js",
@@ -11,13 +11,24 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/signosoft-signpad.js",
13
13
  "require": "./dist/signosoft-signpad.umd.cjs"
14
+ },
15
+ "./angular": {
16
+ "types": "./dist/angular/public-api.d.ts",
17
+ "default": "./dist/angular/fesm2022/signpad-js-angular.mjs"
14
18
  }
15
19
  },
20
+ "bin": {
21
+ "getSignpadTheme": "src/scripts/generate-theme.js"
22
+ },
16
23
  "files": [
17
24
  "dist",
25
+ "src/scripts/generate-theme.js",
26
+ "src/styles/styles-variables.css",
27
+ "src/styles/signosoft-signpad.css",
18
28
  "package.json",
19
29
  "README.md",
20
- "LICENSE"
30
+ "LICENSE",
31
+ "framework-docs"
21
32
  ],
22
33
  "scripts": {
23
34
  "dev": "vite",
@@ -25,6 +36,7 @@
25
36
  "preview": "vite preview"
26
37
  },
27
38
  "dependencies": {
39
+ "chalk": "^5.6.2",
28
40
  "lit": "^3.3.1"
29
41
  },
30
42
  "devDependencies": {
@@ -32,7 +44,6 @@
32
44
  "@types/node": "^25.0.9",
33
45
  "typescript": "~5.9.3",
34
46
  "vite": "^7.2.4",
35
- "vite-plugin-dts": "^4.5.4",
36
- "vite-plugin-static-copy": "^3.1.4"
47
+ "vite-plugin-dts": "^4.5.4"
37
48
  }
38
49
  }
@@ -0,0 +1,340 @@
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
+ );