windowpp 0.1.2 → 0.1.4

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 (30) hide show
  1. package/bin/windowpp.js +4 -1
  2. package/lib/create.js +63 -2
  3. package/package.json +4 -2
  4. package/scripts/publish.js +4 -0
  5. package/scripts/sync-templates.js +238 -0
  6. package/templates/example/CMakeLists.txt +59 -0
  7. package/templates/example/frontend/index.html +20 -0
  8. package/templates/example/frontend/src/API.ts +56 -0
  9. package/templates/example/frontend/src/App.tsx +781 -0
  10. package/templates/example/frontend/src/Layout.tsx +5 -0
  11. package/templates/example/frontend/src/components/ClipboardToast.tsx +23 -0
  12. package/templates/example/frontend/src/components/FileSearch.tsx +936 -0
  13. package/templates/example/frontend/src/components/InfiniteScrollList.tsx +267 -0
  14. package/templates/example/frontend/src/components/index.ts +13 -0
  15. package/templates/example/frontend/src/filedrop.css +421 -0
  16. package/templates/example/frontend/src/index.css +1 -0
  17. package/templates/example/frontend/src/index.tsx +24 -0
  18. package/templates/example/frontend/src/pages/About.tsx +47 -0
  19. package/templates/example/frontend/src/pages/Settings.tsx +37 -0
  20. package/templates/example/frontend/tsconfig.json +20 -0
  21. package/templates/example/frontend/vite.config.ts +27 -0
  22. package/templates/example/main.cpp +224 -0
  23. package/templates/example/package.json +12 -0
  24. package/templates/solid/CMakeLists.txt +4 -1
  25. package/templates/solid/frontend/index.html +16 -0
  26. package/templates/solid/frontend/src/App.tsx +8 -0
  27. package/templates/solid/frontend/src/index.css +1 -0
  28. package/templates/solid/frontend/src/index.tsx +12 -0
  29. package/templates/solid/frontend/tsconfig.json +15 -0
  30. package/templates/solid/frontend/vite.config.ts +3 -1
package/bin/windowpp.js CHANGED
@@ -51,7 +51,10 @@ switch (command) {
51
51
  }
52
52
  const flags = parseFlags(args.slice(2));
53
53
  const { create } = require('../lib/create');
54
- create(name, { template: flags.template, outDir: flags.outDir });
54
+ create(name, { template: flags.template, outDir: flags.outDir }).catch((err) => {
55
+ console.error(err.message || err);
56
+ process.exit(1);
57
+ });
55
58
  break;
56
59
  }
57
60
 
package/lib/create.js CHANGED
@@ -5,8 +5,45 @@
5
5
 
6
6
  const path = require('path');
7
7
  const fs = require('fs');
8
+ const readline = require('readline');
8
9
  const { execSync } = require('child_process');
9
10
 
11
+ // ─── Interactive template picker ─────────────────────────────────────────────
12
+ function listTemplates(cliDir) {
13
+ const templatesDir = path.join(cliDir, 'templates');
14
+ if (!fs.existsSync(templatesDir)) return [];
15
+ return fs.readdirSync(templatesDir, { withFileTypes: true })
16
+ .filter(d => d.isDirectory())
17
+ .map(d => d.name)
18
+ .sort();
19
+ }
20
+
21
+ function promptTemplate(templates) {
22
+ return new Promise((resolve) => {
23
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
24
+
25
+ console.log('\nAvailable templates:');
26
+ templates.forEach((t, i) => console.log(` [${i + 1}] ${t}`));
27
+ console.log();
28
+
29
+ const ask = () => {
30
+ rl.question(`Pick a template [1-${templates.length}] (default: 1): `, (answer) => {
31
+ const trimmed = answer.trim();
32
+ if (trimmed === '') { rl.close(); resolve(templates[0]); return; }
33
+ const idx = parseInt(trimmed, 10) - 1;
34
+ if (idx >= 0 && idx < templates.length) {
35
+ rl.close();
36
+ resolve(templates[idx]);
37
+ } else {
38
+ console.log(` Please enter a number between 1 and ${templates.length}.`);
39
+ ask();
40
+ }
41
+ });
42
+ };
43
+ ask();
44
+ });
45
+ }
46
+
10
47
  const TEXT_EXTENSIONS = new Set([
11
48
  '.cpp', '.h', '.ts', '.tsx', '.js', '.json', '.html',
12
49
  '.css', '.md', '.txt', '.cmake', '.sh', '.env',
@@ -38,9 +75,8 @@ function copyDir(src, dest, tokens) {
38
75
  }
39
76
  }
40
77
 
41
- function create(name, options = {}) {
78
+ async function create(name, options = {}) {
42
79
  const {
43
- template = 'solid',
44
80
  outDir = process.cwd(),
45
81
  installDeps = true,
46
82
  } = options;
@@ -50,6 +86,28 @@ function create(name, options = {}) {
50
86
  process.exit(1);
51
87
  }
52
88
 
89
+ const cliDir = path.resolve(__dirname, '..');
90
+ const templates = listTemplates(cliDir);
91
+
92
+ // Resolve template — prompt if none given or the name doesn't exist yet
93
+ let template = options.template;
94
+ if (!template) {
95
+ if (templates.length === 0) {
96
+ console.error('Error: no templates found in ' + path.join(cliDir, 'templates'));
97
+ process.exit(1);
98
+ }
99
+ if (templates.length === 1) {
100
+ template = templates[0];
101
+ } else {
102
+ template = await promptTemplate(templates);
103
+ }
104
+ }
105
+
106
+ if (!name || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
107
+ console.error('Error: app name must start with a letter and contain only letters, digits, hyphens or underscores.');
108
+ process.exit(1);
109
+ }
110
+
53
111
  const appDir = path.join(outDir, name);
54
112
  if (fs.existsSync(appDir)) {
55
113
  console.error(`Error: directory "${appDir}" already exists.`);
@@ -73,6 +131,9 @@ function create(name, options = {}) {
73
131
  '{{APP_NAME}}': name,
74
132
  '{{APP_TITLE}}': appTitle,
75
133
  '{{CMAKE_TARGET}}': cmakeTarget,
134
+ // Resolved at create-time so vite.config / API.ts can import from the
135
+ // installed framework's src/ tree via the @wpp alias.
136
+ '{{REPO_ROOT}}': path.join(cliDir, 'framework').replace(/\\/g, '/'),
76
137
  };
77
138
 
78
139
  console.log(`\n=== WindowPP Create --- ${name} (template: ${template}) ===\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windowpp",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "WindowPP CLI — build, dev, and scaffold for WindowPP apps",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -16,7 +16,9 @@
16
16
  ],
17
17
  "scripts": {
18
18
  "sync": "node scripts/sync-framework.js",
19
- "prepublishOnly": "node scripts/sync-framework.js"
19
+ "sync:templates": "node scripts/sync-templates.js",
20
+ "sync:all": "node scripts/sync-framework.js && node scripts/sync-templates.js",
21
+ "prepublishOnly": "node scripts/sync-framework.js && node scripts/sync-templates.js"
20
22
  },
21
23
  "engines": {
22
24
  "node": ">=18"
@@ -47,6 +47,10 @@ console.log(`\n=== windowpp publish: ${pkg.version} → ${nextVersion} (${bumpTy
47
47
  console.log('Syncing framework source...');
48
48
  execSync('node scripts/sync-framework.js', { cwd: CLI_DIR, stdio: 'inherit' });
49
49
 
50
+ // ── Sync templates from monorepo apps ────────────────────────────────────────
51
+ console.log('\nSyncing templates...');
52
+ execSync('node scripts/sync-templates.js', { cwd: CLI_DIR, stdio: 'inherit' });
53
+
50
54
  // ── Publish with the new version (without writing it yet) ─────────────────────
51
55
  // Temporarily write the new version so npm publish picks it up, then revert on failure.
52
56
  pkg.version = nextVersion;
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ // cli/scripts/sync-templates.js
3
+ //
4
+ // Syncs app directories from the monorepo into cli/templates/ as reusable
5
+ // project templates. Called manually or as part of the publish flow.
6
+ //
7
+ // Usage:
8
+ // node cli/scripts/sync-templates.js — sync all templates
9
+ // node cli/scripts/sync-templates.js example — sync one by name
10
+
11
+ 'use strict';
12
+
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const SCRIPT_DIR = __dirname;
17
+ const CLI_DIR = path.join(SCRIPT_DIR, '..');
18
+ const REPO_ROOT = path.join(CLI_DIR, '..');
19
+ const TEMPLATES_DIR = path.join(CLI_DIR, 'templates');
20
+ const BASE_TEMPLATE = path.join(TEMPLATES_DIR, 'solid');
21
+
22
+ // ─── Template source configuration ───────────────────────────────────────────
23
+ //
24
+ // Each entry describes one template that can be used with `windowpp create`.
25
+ // To register a new app as a template, add an entry here and run this script.
26
+ //
27
+ // Fields:
28
+ // name — template dir name → cli/templates/<name>/
29
+ // source — source dir relative to repo root
30
+ // appName — concrete npm package name in source (replaced → {{APP_NAME}})
31
+ // cmakeTarget — concrete cmake target in source (replaced → {{CMAKE_TARGET}})
32
+ // appTitle — human-readable title in source (replaced → {{APP_TITLE}})
33
+ // useBase — paths taken from the solid base template instead of source
34
+ // (these need standalone-specific content not in the monorepo app)
35
+ // skip — relative paths / bare file names to skip when copying from source
36
+
37
+ const TEMPLATE_SOURCES = [
38
+ {
39
+ name: 'example',
40
+ source: 'example-app',
41
+ appName: 'windowpp-example',
42
+ cmakeTarget: 'wpp_example',
43
+ appTitle: 'WindowPP Example',
44
+ // Use the standalone-compatible versions from the solid base template
45
+ useBase: [
46
+ 'CMakeLists.txt',
47
+ 'package.json',
48
+ 'frontend/vite.config.ts',
49
+ ],
50
+ // Skip monorepo-specific / generated / lock files
51
+ skip: [
52
+ 'build.js',
53
+ 'dev.js',
54
+ 'frontend/node_modules',
55
+ 'frontend/dist',
56
+ 'frontend/pnpm-lock.yaml',
57
+ 'frontend/package-lock.json',
58
+ 'frontend/.gitignore',
59
+ 'frontend/README.md',
60
+ ],
61
+ },
62
+ // ── To add more templates, copy an entry like this: ───────────────────────
63
+ // {
64
+ // name: 'icup',
65
+ // source: 'ICUP',
66
+ // appName: 'wpp-icup',
67
+ // cmakeTarget: 'wpp_icup',
68
+ // appTitle: 'WindowPP ICUP',
69
+ // useBase: ['CMakeLists.txt', 'package.json', 'frontend/vite.config.ts'],
70
+ // skip: ['build.js', 'dev.js', 'frontend/node_modules', 'frontend/dist'],
71
+ // },
72
+ ];
73
+
74
+ // ─── File extensions that get text/token substitution ────────────────────────
75
+ const TEXT_EXTENSIONS = new Set([
76
+ '.cpp', '.h', '.ts', '.tsx', '.js', '.json', '.html',
77
+ '.css', '.md', '.txt', '.cmake', '.sh', '.env',
78
+ ]);
79
+
80
+ // ─── Build the list of [from, to] string substitution pairs for an entry ─────
81
+ //
82
+ // Transforms applied to all text files:
83
+ //
84
+ // 1. Concrete app name / cmake target / title → template tokens
85
+ //
86
+ // 2. TypeScript relative imports from frontend/src/ depth (3 levels up to src/):
87
+ // '../../../src/Foo' → '@wpp/Foo'
88
+ // Covered by the vite alias: '@wpp' → resolve(REPO_ROOT, 'src')
89
+ //
90
+ // 3. TypeScript relative imports from frontend/ depth (2 levels up):
91
+ // '../../src/Foo' → '@wpp/Foo'
92
+ //
93
+ // 4. C++ #include relative back to src/:
94
+ // "#include "../src/Foo/bar.h"" → "#include "Foo/bar.h""
95
+ // Works because CMakeLists adds target_include_directories(...PRIVATE "${WPP_FRAMEWORK_DIR}/src")
96
+
97
+ function buildTokens(entry) {
98
+ return [
99
+ // ── Name tokens (longer strings first to avoid partial matches) ─────
100
+ [entry.appName, '{{APP_NAME}}'],
101
+ [entry.cmakeTarget, '{{CMAKE_TARGET}}'],
102
+ [entry.appTitle, '{{APP_TITLE}}'],
103
+
104
+ // ── TypeScript/TSX: monorepo-relative imports from frontend/src/ ────
105
+ ["'../../../src/", "'@wpp/"],
106
+ ['"../../../src/', '"@wpp/'],
107
+
108
+ // ── TypeScript/TSX: from frontend/ level ───────────────────────────
109
+ ["'../../src/", "'@wpp/"],
110
+ ['"../../src/', '"@wpp/'],
111
+
112
+ // ── C++ includes: "../src/SubDir/file.h" → "SubDir/file.h" ─────────
113
+ // (CMakeLists adds ${WPP_FRAMEWORK_DIR}/src as a private include dir)
114
+ ['"../src/', '"'],
115
+ ];
116
+ }
117
+
118
+ function applyTokens(content, tokens) {
119
+ for (const [from, to] of tokens) {
120
+ content = content.split(from).join(to);
121
+ }
122
+ return content;
123
+ }
124
+
125
+ // ─── File copy helpers ───────────────────────────────────────────────────────
126
+ function copyFileTransformed(src, dest, tokens) {
127
+ const ext = path.extname(src).toLowerCase();
128
+ if (TEXT_EXTENSIONS.has(ext)) {
129
+ let content = fs.readFileSync(src, 'utf8');
130
+ content = applyTokens(content, tokens);
131
+ fs.writeFileSync(dest, content, 'utf8');
132
+ } else {
133
+ fs.copyFileSync(src, dest);
134
+ }
135
+ }
136
+
137
+ // Copy a directory recursively, respecting a per-directory skip set.
138
+ // relPath tracks the path relative to the template root for skip matching.
139
+ function copySourceDir(src, dest, tokens, skipRelPaths, relPrefix) {
140
+ fs.mkdirSync(dest, { recursive: true });
141
+ for (const dirent of fs.readdirSync(src, { withFileTypes: true })) {
142
+ const relPath = relPrefix ? `${relPrefix}/${dirent.name}` : dirent.name;
143
+ // Skip if matched by bare name OR by relative path
144
+ if (skipRelPaths.has(dirent.name) || skipRelPaths.has(relPath)) continue;
145
+
146
+ const srcPath = path.join(src, dirent.name);
147
+ const destPath = path.join(dest, dirent.name);
148
+
149
+ if (dirent.isDirectory()) {
150
+ copySourceDir(srcPath, destPath, tokens, skipRelPaths, relPath);
151
+ } else {
152
+ copyFileTransformed(srcPath, destPath, tokens);
153
+ }
154
+ }
155
+ }
156
+
157
+ // ─── Sync a single template entry ────────────────────────────────────────────
158
+ function syncTemplate(entry) {
159
+ const srcDir = path.join(REPO_ROOT, entry.source);
160
+ const destDir = path.join(TEMPLATES_DIR, entry.name);
161
+
162
+ if (!fs.existsSync(srcDir)) {
163
+ console.warn(` ⚠ source not found: ${entry.source} — skipping`);
164
+ return 0;
165
+ }
166
+
167
+ // Clear and recreate the destination template directory
168
+ if (fs.existsSync(destDir)) {
169
+ fs.rmSync(destDir, { recursive: true, force: true });
170
+ }
171
+ fs.mkdirSync(destDir, { recursive: true });
172
+
173
+ const tokens = buildTokens(entry);
174
+ const useBaseSet = new Set(entry.useBase ?? []);
175
+ const skipRelPaths = new Set(entry.skip ?? []);
176
+
177
+ // Also skip the "useBase" paths when copying from source
178
+ for (const rel of useBaseSet) skipRelPaths.add(rel);
179
+
180
+ let count = 0;
181
+
182
+ // 1. Overlay "base" files from cli/templates/solid/ (with this entry's tokens)
183
+ for (const rel of useBaseSet) {
184
+ const baseSrc = path.join(BASE_TEMPLATE, rel);
185
+ const baseDest = path.join(destDir, rel);
186
+ if (!fs.existsSync(baseSrc)) {
187
+ console.warn(` ⚠ base file not found: ${rel}`);
188
+ continue;
189
+ }
190
+ fs.mkdirSync(path.dirname(baseDest), { recursive: true });
191
+ copyFileTransformed(baseSrc, baseDest, tokens);
192
+ count++;
193
+ }
194
+
195
+ // 2. Copy everything else from the source app
196
+ copySourceDir(srcDir, destDir, tokens, skipRelPaths, '');
197
+
198
+ // Count total destination files
199
+ count = countFiles(destDir);
200
+ return count;
201
+ }
202
+
203
+ function countFiles(dir) {
204
+ let n = 0;
205
+ for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
206
+ if (dirent.isDirectory()) n += countFiles(path.join(dir, dirent.name));
207
+ else n++;
208
+ }
209
+ return n;
210
+ }
211
+
212
+ // ─── Entry point ─────────────────────────────────────────────────────────────
213
+ function run() {
214
+ const filter = process.argv[2]; // optional: name of one template to sync
215
+ const targets = filter
216
+ ? TEMPLATE_SOURCES.filter(e => e.name === filter)
217
+ : TEMPLATE_SOURCES;
218
+
219
+ if (filter && targets.length === 0) {
220
+ const names = TEMPLATE_SOURCES.map(e => e.name).join(', ');
221
+ console.error(`\nNo template named "${filter}". Available: ${names || '(none)'}\n`);
222
+ process.exit(1);
223
+ }
224
+
225
+ console.log('\n=== windowpp: syncing templates ===\n');
226
+
227
+ let total = 0;
228
+ for (const entry of targets) {
229
+ process.stdout.write(` ${entry.source} → templates/${entry.name}/ ...`);
230
+ const n = syncTemplate(entry);
231
+ process.stdout.write(` ${n} file(s)\n`);
232
+ total += n;
233
+ }
234
+
235
+ console.log(`\n✓ Synced ${targets.length} template(s) (${total} total files)\n`);
236
+ }
237
+
238
+ run();
@@ -0,0 +1,59 @@
1
+ cmake_minimum_required(VERSION 3.16)
2
+ project({{CMAKE_TARGET}} VERSION 0.1.0 LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+
7
+ find_package(Python3 REQUIRED COMPONENTS Interpreter)
8
+
9
+ # ─── WindowPP Framework ───────────────────────────────────────────────────────
10
+ # WPP_FRAMEWORK_DIR is injected by `windowpp build` / `windowpp dev`.
11
+ # To build manually without the CLI:
12
+ # cmake -S . -B build -DWPP_FRAMEWORK_DIR=/path/to/windowpp/framework
13
+ if(NOT DEFINED WPP_FRAMEWORK_DIR)
14
+ message(FATAL_ERROR
15
+ "WPP_FRAMEWORK_DIR is not set.\n"
16
+ " Build via: windowpp build\n"
17
+ " Or pass: -DWPP_FRAMEWORK_DIR=<path/to/windowpp/cli/framework>"
18
+ )
19
+ endif()
20
+
21
+ add_subdirectory("${WPP_FRAMEWORK_DIR}" "${CMAKE_BINARY_DIR}/windowpp_lib")
22
+
23
+ # ─── App Target ───────────────────────────────────────────────────────────────
24
+
25
+ set(GENERATED_DIR ${CMAKE_BINARY_DIR}/generated)
26
+ set(EMBEDDED_ASSETS_CPP ${GENERATED_DIR}/embedded_assets.cpp)
27
+ set(EMBEDDED_ASSETS_H ${GENERATED_DIR}/embedded_assets.h)
28
+
29
+ add_custom_command(
30
+ OUTPUT ${EMBEDDED_ASSETS_CPP} ${EMBEDDED_ASSETS_H}
31
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${GENERATED_DIR}
32
+ COMMAND ${Python3_EXECUTABLE}
33
+ ${WPP_EMBED_SCRIPT}
34
+ ${CMAKE_CURRENT_SOURCE_DIR}/frontend/dist
35
+ ${EMBEDDED_ASSETS_CPP}
36
+ ${EMBEDDED_ASSETS_H}
37
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/frontend/dist/index.html
38
+ COMMENT "Embedding {{APP_TITLE}} frontend assets into C++ byte arrays..."
39
+ VERBATIM
40
+ )
41
+
42
+ set_source_files_properties(
43
+ ${EMBEDDED_ASSETS_CPP}
44
+ ${EMBEDDED_ASSETS_H}
45
+ PROPERTIES GENERATED TRUE
46
+ )
47
+
48
+ add_custom_target({{CMAKE_TARGET}}_frontend_assets ALL
49
+ DEPENDS ${EMBEDDED_ASSETS_CPP} ${EMBEDDED_ASSETS_H}
50
+ )
51
+
52
+ add_executable({{CMAKE_TARGET}} main.cpp ${EMBEDDED_ASSETS_CPP})
53
+ target_include_directories({{CMAKE_TARGET}} PRIVATE
54
+ ${GENERATED_DIR}
55
+ "${WPP_FRAMEWORK_DIR}/src" # bridge headers: AppData/, FileSystem/, Input/, platform/
56
+ )
57
+ target_compile_definitions({{CMAKE_TARGET}} PRIVATE WPP_EMBEDDED_ASSETS)
58
+ target_link_libraries({{CMAKE_TARGET}} PRIVATE windowpp windowpp_deps nlohmann_json::nlohmann_json)
59
+ add_dependencies({{CMAKE_TARGET}} {{CMAKE_TARGET}}_frontend_assets)
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="theme-color" content="#000000" />
7
+ <title>Solid App</title>
8
+ <script>
9
+ // Kill WebView2/browser default "open file" on drag-and-drop
10
+ window.addEventListener('dragover', function(e) { e.preventDefault(); }, false);
11
+ window.addEventListener('drop', function(e) { e.preventDefault(); }, false);
12
+ </script>
13
+ </head>
14
+ <body>
15
+ <noscript>You need to enable JavaScript to run this app.</noscript>
16
+ <div id="root"></div>
17
+
18
+ <script src="/src/index.tsx" type="module"></script>
19
+ </body>
20
+ </html>
@@ -0,0 +1,56 @@
1
+ // ─── AppData ─────────────────────────────────────────────────────────────────
2
+ export type { AppDataEntry, SeedEntry } from '@wpp/AppData/API/AppData';
3
+ export { default as appData } from '@wpp/AppData/API/AppData';
4
+
5
+ // ─── FileSystem ───────────────────────────────────────────────────────────────
6
+ export type {
7
+ FileStat,
8
+ DirEntry,
9
+ ApplicationEntry,
10
+ SearchOptions,
11
+ SearchStreamSummary,
12
+ SearchStreamCallbacks,
13
+ SearchStreamHandle,
14
+ } from '@wpp/FileSystem/API/FileSystem';
15
+ export { default as filesystem } from '@wpp/FileSystem/API/FileSystem';
16
+
17
+ // ─── FileDrop ─────────────────────────────────────────────────────────────────
18
+ export type { FileInfo, DropZone } from '@wpp/filedrop/filedrop';
19
+ export {
20
+ fileDrop,
21
+ formatFileSize,
22
+ createDropZone,
23
+ filterFilesByExtension,
24
+ filterFilesByMimeType,
25
+ isImageFile,
26
+ isVideoFile,
27
+ isAudioFile,
28
+ isTextFile,
29
+ } from '@wpp/filedrop/filedrop';
30
+
31
+ // ─── Input ────────────────────────────────────────────────────────────────────
32
+ export type { InputKeyEvent, ShortcutEvent } from '@wpp/Input/API/Input';
33
+ export { default as input } from '@wpp/Input/API/Input';
34
+
35
+ // ─── App (platform) ──────────────────────────────────────────────────────────
36
+ export type {
37
+ AppInfo,
38
+ Point,
39
+ Size,
40
+ Rect,
41
+ MonitorInfo,
42
+ ClipboardChangedEvent,
43
+ AppEvent,
44
+ } from '@wpp/platform/API/App';
45
+ export { default as appApi } from '@wpp/platform/API/App';
46
+
47
+ // ─── Window (platform) ───────────────────────────────────────────────────────
48
+ export type {
49
+ WindowState,
50
+ WindowInfo,
51
+ ResizeDetail,
52
+ MoveDetail,
53
+ WindowEvent,
54
+ CreateWindowOptions,
55
+ } from '@wpp/platform/API/Window';
56
+ export { default as windowApi } from '@wpp/platform/API/Window';