deskforge 1.0.0 → 1.0.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/COMPLETION_REPORT.md +426 -0
- package/COMPONENT_API.md +529 -0
- package/DELIVERY_SUMMARY.md +455 -0
- package/IMPLEMENTATION_GUIDE.md +385 -0
- package/IMPORT_GUIDE.md +404 -0
- package/QUICK_REFERENCE.md +352 -0
- package/README.md +245 -31
- package/README_UI_COMPLETE.md +395 -0
- package/TYPE_DEFINITIONS.md +671 -0
- package/UI_GETTING_STARTED.md +523 -0
- package/examples/deskforge-app/App.ts +7 -11
- package/examples/deskforge-app/screens/CounterApp.ts +181 -0
- package/examples/deskforge-app/screens/GalleryApp.ts +267 -0
- package/examples/deskforge-app/screens/NotesApp.ts +376 -0
- package/examples/deskforge-app/screens/SettingsApp.ts +226 -0
- package/examples/deskforge-app/screens/TodoApp.ts +288 -0
- package/examples/deskforge-app/screens/index.ts +50 -0
- package/package.json +1 -1
- package/packages/cli/dist/commands/start.d.ts +7 -1
- package/packages/cli/dist/commands/start.d.ts.map +1 -1
- package/packages/cli/dist/commands/start.js +75 -21
- package/packages/cli/dist/commands/start.js.map +1 -1
- package/packages/cli/src/commands/start.ts +93 -21
- package/packages/cli/test/start.test.js +24 -0
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.d.ts.map +1 -1
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/style/StyleObject.d.ts +115 -0
- package/packages/core/dist/style/StyleObject.d.ts.map +1 -0
- package/packages/core/dist/style/StyleObject.js +6 -0
- package/packages/core/dist/style/StyleObject.js.map +1 -0
- package/packages/core/dist/style/StyleSheet.d.ts +7 -0
- package/packages/core/dist/style/StyleSheet.d.ts.map +1 -0
- package/packages/core/dist/style/StyleSheet.js +13 -0
- package/packages/core/dist/style/StyleSheet.js.map +1 -0
- package/packages/core/dist/style/index.d.ts +3 -0
- package/packages/core/dist/style/index.d.ts.map +1 -0
- package/packages/core/dist/style/index.js +21 -0
- package/packages/core/dist/style/index.js.map +1 -0
- package/packages/core/src/index.ts +2 -1
- package/packages/core/src/style/StyleObject.ts +134 -0
- package/packages/core/src/style/StyleSheet.ts +19 -0
- package/packages/core/src/style/index.ts +5 -0
- package/packages/db/src/db.ts +4 -4
- package/packages/deskforge/src/index.ts +95 -0
- package/packages/file/dist/file.d.ts +1 -1
- package/packages/file/dist/file.d.ts.map +1 -1
- package/packages/file/dist/file.js +75 -3
- package/packages/file/dist/file.js.map +1 -1
- package/packages/file/src/file.ts +96 -4
- package/packages/file/test/file.test.js +23 -0
- package/packages/router/dist/index.d.ts +1 -0
- package/packages/router/dist/index.d.ts.map +1 -1
- package/packages/router/dist/index.js +1 -0
- package/packages/router/dist/index.js.map +1 -1
- package/packages/router/dist/navigation.d.ts +23 -0
- package/packages/router/dist/navigation.d.ts.map +1 -0
- package/packages/router/dist/navigation.js +61 -0
- package/packages/router/dist/navigation.js.map +1 -0
- package/packages/router/src/index.ts +2 -1
- package/packages/router/src/navigation.ts +86 -0
- package/packages/router/test/router.test.js +21 -1
- package/packages/theme/src/theme.ts +23 -0
- package/packages/ui/src/base/Button.ts +67 -0
- package/packages/ui/src/base/Image.ts +59 -0
- package/packages/ui/src/base/Pressable.ts +69 -0
- package/packages/ui/src/base/Text.ts +42 -0
- package/packages/ui/src/base/TextInput.ts +113 -0
- package/packages/ui/src/base/TouchableOpacity.ts +52 -0
- package/packages/ui/src/base/View.ts +43 -0
- package/packages/ui/src/index.ts +40 -25
- package/packages/ui/src/input/CheckBox.ts +69 -0
- package/packages/ui/src/input/Slider.ts +19 -2
- package/packages/ui/src/input/Switch.ts +68 -0
- package/packages/ui/src/layout/KeyboardAvoidingView.ts +65 -0
- package/packages/ui/src/layout/SafeAreaView.ts +58 -0
- package/packages/ui/src/list/FlatList.ts +111 -0
- package/packages/ui/src/list/ScrollView.ts +80 -0
- package/packages/ui/src/list/SectionList.ts +120 -0
- package/packages/ui/src/overlay/ActivityIndicator.ts +70 -0
- package/packages/ui/src/overlay/Modal.ts +28 -6
- package/packages/ui/src/platform/Platform.ts +119 -0
- package/packages/ui/src/platform/StatusBar.ts +63 -0
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
// Read, write, move, copy, watch, pick — all in one clean API.
|
|
5
5
|
// Works on desktop and mobile with platform-appropriate dialogs.
|
|
6
6
|
|
|
7
|
+
import * as fs from 'fs'
|
|
8
|
+
import * as path from 'path'
|
|
9
|
+
|
|
7
10
|
// File types for the picker dialog
|
|
8
11
|
type FileType =
|
|
9
12
|
| 'any'
|
|
@@ -133,12 +136,101 @@ async function copy(
|
|
|
133
136
|
console.log('[DeskForge File] Copy:', from, '→', to)
|
|
134
137
|
}
|
|
135
138
|
|
|
136
|
-
// Renames a file
|
|
139
|
+
// Renames a file and updates local imports that reference it
|
|
137
140
|
async function rename(
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
oldPath: string,
|
|
142
|
+
newPath: string
|
|
140
143
|
): Promise<void> {
|
|
141
|
-
|
|
144
|
+
const resolvedOldPath = path.resolve(oldPath)
|
|
145
|
+
const resolvedNewPath = path.resolve(newPath)
|
|
146
|
+
|
|
147
|
+
if (!fs.existsSync(resolvedOldPath)) {
|
|
148
|
+
throw new Error(`[DeskForge File] Cannot rename missing file: ${resolvedOldPath}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const parentDir = path.dirname(resolvedNewPath)
|
|
152
|
+
fs.mkdirSync(parentDir, { recursive: true })
|
|
153
|
+
fs.renameSync(resolvedOldPath, resolvedNewPath)
|
|
154
|
+
|
|
155
|
+
const workspaceRoot = path.dirname(resolvedOldPath)
|
|
156
|
+
const files = walkFiles(workspaceRoot)
|
|
157
|
+
|
|
158
|
+
for (const file of files) {
|
|
159
|
+
const content = fs.readFileSync(file, 'utf8')
|
|
160
|
+
const updated = rewriteImportReferences(content, file, resolvedOldPath, resolvedNewPath)
|
|
161
|
+
|
|
162
|
+
if (updated !== content) {
|
|
163
|
+
fs.writeFileSync(file, updated)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log('[DeskForge File] Rename:', oldPath, '→', newPath)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function walkFiles(root: string): string[] {
|
|
171
|
+
if (!fs.existsSync(root)) return []
|
|
172
|
+
|
|
173
|
+
const results: string[] = []
|
|
174
|
+
const stack = [root]
|
|
175
|
+
|
|
176
|
+
while (stack.length > 0) {
|
|
177
|
+
const current = stack.pop()
|
|
178
|
+
if (!current) continue
|
|
179
|
+
|
|
180
|
+
const entries = fs.readdirSync(current, { withFileTypes: true })
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
const fullPath = path.join(current, entry.name)
|
|
183
|
+
if (entry.isDirectory()) {
|
|
184
|
+
stack.push(fullPath)
|
|
185
|
+
} else if (entry.isFile() && /\.(ts|tsx|js|jsx|json|md)$/i.test(entry.name)) {
|
|
186
|
+
results.push(fullPath)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return results
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function toPosixPath(filePath: string): string {
|
|
195
|
+
return filePath.split(path.sep).join('/')
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function rewriteImportReferences(
|
|
199
|
+
content: string,
|
|
200
|
+
filePath: string,
|
|
201
|
+
oldPath: string,
|
|
202
|
+
newPath: string
|
|
203
|
+
): string {
|
|
204
|
+
const fileDir = path.dirname(filePath)
|
|
205
|
+
const oldBase = path.basename(oldPath)
|
|
206
|
+
const newBase = path.basename(newPath)
|
|
207
|
+
const oldBaseNoExt = path.basename(oldPath, path.extname(oldPath))
|
|
208
|
+
const newBaseNoExt = path.basename(newPath, path.extname(newPath))
|
|
209
|
+
const oldRelative = toPosixPath(path.relative(fileDir, oldPath))
|
|
210
|
+
const newRelative = toPosixPath(path.relative(fileDir, newPath))
|
|
211
|
+
|
|
212
|
+
const replacements = [
|
|
213
|
+
{ from: oldRelative, to: newRelative },
|
|
214
|
+
{ from: `./${oldBase}`, to: `./${newBase}` },
|
|
215
|
+
{ from: `../${oldBase}`, to: `../${newBase}` },
|
|
216
|
+
{ from: `./${oldBaseNoExt}`, to: `./${newBaseNoExt}` },
|
|
217
|
+
{ from: `../${oldBaseNoExt}`, to: `../${newBaseNoExt}` },
|
|
218
|
+
{ from: oldBase, to: newBase },
|
|
219
|
+
{ from: oldBaseNoExt, to: newBaseNoExt },
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
let updated = content
|
|
223
|
+
for (const { from, to } of replacements) {
|
|
224
|
+
if (from && from !== to) {
|
|
225
|
+
updated = updated.replace(new RegExp(escapeRegExp(from), 'g'), to)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return updated
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function escapeRegExp(value: string): string {
|
|
233
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
142
234
|
}
|
|
143
235
|
|
|
144
236
|
// Deletes a file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const test = require('node:test')
|
|
2
|
+
const assert = require('node:assert/strict')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const os = require('os')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
|
|
7
|
+
const { File } = require('../dist/file.js')
|
|
8
|
+
|
|
9
|
+
test('rename updates local import paths after a file rename', async () => {
|
|
10
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deskforge-file-rename-'))
|
|
11
|
+
const oldFile = path.join(tmpDir, 'oldName.ts')
|
|
12
|
+
const newFile = path.join(tmpDir, 'newName.ts')
|
|
13
|
+
const importer = path.join(tmpDir, 'app.ts')
|
|
14
|
+
|
|
15
|
+
fs.writeFileSync(oldFile, 'export const value = 1\n')
|
|
16
|
+
fs.writeFileSync(importer, "import { value } from './oldName'\nconsole.log(value)\n")
|
|
17
|
+
|
|
18
|
+
await File.rename(oldFile, newFile)
|
|
19
|
+
|
|
20
|
+
assert.ok(fs.existsSync(newFile))
|
|
21
|
+
assert.ok(!fs.existsSync(oldFile))
|
|
22
|
+
assert.match(fs.readFileSync(importer, 'utf8'), /newName/)
|
|
23
|
+
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,OAAO,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,OAAO,CAAA;AACrB,cAAc,cAAc,CAAA"}
|
|
@@ -21,4 +21,5 @@ __exportStar(require("./router"), exports);
|
|
|
21
21
|
__exportStar(require("./file-router"), exports);
|
|
22
22
|
__exportStar(require("./manifest"), exports);
|
|
23
23
|
__exportStar(require("./app"), exports);
|
|
24
|
+
__exportStar(require("./navigation"), exports);
|
|
24
25
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,WAAW;AACX,4CAA4C;AAC5C,2CAAwB;AACxB,2CAAwB;AACxB,gDAA6B;AAC7B,6CAA0B;AAC1B,wCAAqB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,WAAW;AACX,4CAA4C;AAC5C,2CAAwB;AACxB,2CAAwB;AACxB,gDAA6B;AAC7B,6CAA0B;AAC1B,wCAAqB;AACrB,+CAA4B"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DFNode } from './screen';
|
|
2
|
+
import type { ScreenComponent, ScreenConfig, ScreenParams } from './screen';
|
|
3
|
+
export interface NavigationStackEntry {
|
|
4
|
+
name: string;
|
|
5
|
+
params: ScreenParams;
|
|
6
|
+
}
|
|
7
|
+
export interface DeskForgeNavigation {
|
|
8
|
+
navigate: (name: string, params?: ScreenParams) => void;
|
|
9
|
+
goBack: () => void;
|
|
10
|
+
canGoBack: () => boolean;
|
|
11
|
+
currentRoute: () => string | null;
|
|
12
|
+
currentParams: () => ScreenParams;
|
|
13
|
+
getStack: () => NavigationStackEntry[];
|
|
14
|
+
render: () => DFNode | null;
|
|
15
|
+
register: (name: string, component: ScreenComponent | ScreenConfig) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createNavigation(): DeskForgeNavigation;
|
|
18
|
+
export declare const navigation: DeskForgeNavigation;
|
|
19
|
+
export declare function createNavigationScreen(component: ScreenComponent, options?: {
|
|
20
|
+
title?: string;
|
|
21
|
+
layout?: string;
|
|
22
|
+
}): ScreenConfig;
|
|
23
|
+
//# sourceMappingURL=navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAG3E,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,YAAY,CAAA;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,KAAK,IAAI,CAAA;IACvD,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,SAAS,EAAE,MAAM,OAAO,CAAA;IACxB,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IACjC,aAAa,EAAE,MAAM,YAAY,CAAA;IACjC,QAAQ,EAAE,MAAM,oBAAoB,EAAE,CAAA;IACtC,MAAM,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,GAAG,YAAY,KAAK,IAAI,CAAA;CAC5E;AAwDD,wBAAgB,gBAAgB,IAAI,mBAAmB,CAEtD;AAED,eAAO,MAAM,UAAU,qBAAqB,CAAA;AAE5C,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,eAAe,EAC1B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAChD,YAAY,CAEd"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.navigation = void 0;
|
|
4
|
+
exports.createNavigation = createNavigation;
|
|
5
|
+
exports.createNavigationScreen = createNavigationScreen;
|
|
6
|
+
const screen_1 = require("./screen");
|
|
7
|
+
class DeskForgeNavigator {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.routes = new Map();
|
|
10
|
+
this.stack = [];
|
|
11
|
+
this.index = -1;
|
|
12
|
+
}
|
|
13
|
+
navigate(name, params = {}) {
|
|
14
|
+
const route = this.routes.get(name);
|
|
15
|
+
if (!route) {
|
|
16
|
+
console.error(`[DeskForge Navigation] Unknown route: ${name}`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
this.stack = this.stack.slice(0, this.index + 1);
|
|
20
|
+
this.stack.push({ name, params });
|
|
21
|
+
this.index = this.stack.length - 1;
|
|
22
|
+
}
|
|
23
|
+
goBack() {
|
|
24
|
+
if (this.index <= 0)
|
|
25
|
+
return;
|
|
26
|
+
this.index--;
|
|
27
|
+
}
|
|
28
|
+
canGoBack() {
|
|
29
|
+
return this.index > 0;
|
|
30
|
+
}
|
|
31
|
+
currentRoute() {
|
|
32
|
+
return this.stack[this.index]?.name ?? null;
|
|
33
|
+
}
|
|
34
|
+
currentParams() {
|
|
35
|
+
return this.stack[this.index]?.params ?? {};
|
|
36
|
+
}
|
|
37
|
+
getStack() {
|
|
38
|
+
return this.stack.map(entry => ({ ...entry, params: { ...entry.params } }));
|
|
39
|
+
}
|
|
40
|
+
render() {
|
|
41
|
+
const current = this.stack[this.index];
|
|
42
|
+
if (!current)
|
|
43
|
+
return null;
|
|
44
|
+
const route = this.routes.get(current.name);
|
|
45
|
+
if (!route)
|
|
46
|
+
return null;
|
|
47
|
+
const resolved = typeof route === 'function' ? route(current.params) : route.component(current.params);
|
|
48
|
+
return resolved ?? null;
|
|
49
|
+
}
|
|
50
|
+
register(name, component) {
|
|
51
|
+
this.routes.set(name, component);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function createNavigation() {
|
|
55
|
+
return new DeskForgeNavigator();
|
|
56
|
+
}
|
|
57
|
+
exports.navigation = createNavigation();
|
|
58
|
+
function createNavigationScreen(component, options = {}) {
|
|
59
|
+
return (0, screen_1.Screen)(component, options);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=navigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":";;;AA0EA,4CAEC;AAID,wDAKC;AAnFD,qCAAiC;AAkBjC,MAAM,kBAAkB;IAAxB;QACU,WAAM,GAAG,IAAI,GAAG,EAA0C,CAAA;QAC1D,UAAK,GAA2B,EAAE,CAAA;QAClC,UAAK,GAAG,CAAC,CAAC,CAAA;IAiDpB,CAAC;IA/CC,QAAQ,CAAC,IAAY,EAAE,SAAuB,EAAE;QAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAA;YAC9D,OAAM;QACR,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;QAChD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QACjC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IACpC,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC;YAAE,OAAM;QAC3B,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,GAAG,CAAC,CAAA;IACvB,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,IAAI,IAAI,CAAA;IAC7C,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,EAAE,CAAA;IAC7C,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAA;IAC7E,CAAC;IAED,MAAM;QACJ,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACtC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QAEzB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3C,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QAEvB,MAAM,QAAQ,GAAG,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QACtG,OAAO,QAAQ,IAAI,IAAI,CAAA;IACzB,CAAC;IAED,QAAQ,CAAC,IAAY,EAAE,SAAyC;QAC9D,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAClC,CAAC;CACF;AAED,SAAgB,gBAAgB;IAC9B,OAAO,IAAI,kBAAkB,EAAE,CAAA;AACjC,CAAC;AAEY,QAAA,UAAU,GAAG,gBAAgB,EAAE,CAAA;AAE5C,SAAgB,sBAAsB,CACpC,SAA0B,EAC1B,UAA+C,EAAE;IAEjD,OAAO,IAAA,eAAM,EAAC,SAAS,EAAE,OAAO,CAAC,CAAA;AACnC,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { DFNode } from './screen'
|
|
2
|
+
import type { ScreenComponent, ScreenConfig, ScreenParams } from './screen'
|
|
3
|
+
import { Screen } from './screen'
|
|
4
|
+
|
|
5
|
+
export interface NavigationStackEntry {
|
|
6
|
+
name: string
|
|
7
|
+
params: ScreenParams
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface DeskForgeNavigation {
|
|
11
|
+
navigate: (name: string, params?: ScreenParams) => void
|
|
12
|
+
goBack: () => void
|
|
13
|
+
canGoBack: () => boolean
|
|
14
|
+
currentRoute: () => string | null
|
|
15
|
+
currentParams: () => ScreenParams
|
|
16
|
+
getStack: () => NavigationStackEntry[]
|
|
17
|
+
render: () => DFNode | null
|
|
18
|
+
register: (name: string, component: ScreenComponent | ScreenConfig) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class DeskForgeNavigator implements DeskForgeNavigation {
|
|
22
|
+
private routes = new Map<string, ScreenComponent | ScreenConfig>()
|
|
23
|
+
private stack: NavigationStackEntry[] = []
|
|
24
|
+
private index = -1
|
|
25
|
+
|
|
26
|
+
navigate(name: string, params: ScreenParams = {}): void {
|
|
27
|
+
const route = this.routes.get(name)
|
|
28
|
+
if (!route) {
|
|
29
|
+
console.error(`[DeskForge Navigation] Unknown route: ${name}`)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.stack = this.stack.slice(0, this.index + 1)
|
|
34
|
+
this.stack.push({ name, params })
|
|
35
|
+
this.index = this.stack.length - 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
goBack(): void {
|
|
39
|
+
if (this.index <= 0) return
|
|
40
|
+
this.index--
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
canGoBack(): boolean {
|
|
44
|
+
return this.index > 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
currentRoute(): string | null {
|
|
48
|
+
return this.stack[this.index]?.name ?? null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
currentParams(): ScreenParams {
|
|
52
|
+
return this.stack[this.index]?.params ?? {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getStack(): NavigationStackEntry[] {
|
|
56
|
+
return this.stack.map(entry => ({ ...entry, params: { ...entry.params } }))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
render(): DFNode | null {
|
|
60
|
+
const current = this.stack[this.index]
|
|
61
|
+
if (!current) return null
|
|
62
|
+
|
|
63
|
+
const route = this.routes.get(current.name)
|
|
64
|
+
if (!route) return null
|
|
65
|
+
|
|
66
|
+
const resolved = typeof route === 'function' ? route(current.params) : route.component(current.params)
|
|
67
|
+
return resolved ?? null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
register(name: string, component: ScreenComponent | ScreenConfig): void {
|
|
71
|
+
this.routes.set(name, component)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createNavigation(): DeskForgeNavigation {
|
|
76
|
+
return new DeskForgeNavigator()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const navigation = createNavigation()
|
|
80
|
+
|
|
81
|
+
export function createNavigationScreen(
|
|
82
|
+
component: ScreenComponent,
|
|
83
|
+
options: { title?: string; layout?: string } = {}
|
|
84
|
+
): ScreenConfig {
|
|
85
|
+
return Screen(component, options)
|
|
86
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const test = require('node:test')
|
|
2
2
|
const assert = require('node:assert/strict')
|
|
3
3
|
|
|
4
|
-
const { FileRouter, ManifestBuilder } = require('../dist/index.js')
|
|
4
|
+
const { FileRouter, ManifestBuilder, createNavigation } = require('../dist/index.js')
|
|
5
5
|
|
|
6
6
|
function createNode() {
|
|
7
7
|
return {
|
|
@@ -28,3 +28,23 @@ test('FileRouter resolves Expo-style routes and keeps route params', () => {
|
|
|
28
28
|
assert.equal(router.getCurrentPath(), '/note/123')
|
|
29
29
|
assert.deepEqual(router.render()?.props ?? {}, { title: '123' })
|
|
30
30
|
})
|
|
31
|
+
|
|
32
|
+
test('createNavigation supports stack navigation and params', () => {
|
|
33
|
+
const navigation = createNavigation()
|
|
34
|
+
|
|
35
|
+
navigation.register('home', () => createNode())
|
|
36
|
+
navigation.register('details', (params = {}) => ({
|
|
37
|
+
...createNode(),
|
|
38
|
+
props: { noteId: params.noteId ?? 'unknown' },
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
navigation.navigate('home')
|
|
42
|
+
navigation.navigate('details', { noteId: '42' })
|
|
43
|
+
|
|
44
|
+
assert.equal(navigation.currentRoute(), 'details')
|
|
45
|
+
assert.deepEqual(navigation.currentParams(), { noteId: '42' })
|
|
46
|
+
assert.equal(navigation.render()?.props?.noteId, '42')
|
|
47
|
+
navigation.goBack()
|
|
48
|
+
assert.equal(navigation.currentRoute(), 'home')
|
|
49
|
+
assert.equal(navigation.canGoBack(), false)
|
|
50
|
+
})
|
|
@@ -133,8 +133,31 @@ function resolveToken(
|
|
|
133
133
|
return current ?? path
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// Theme object — convenient namespace for theme operations
|
|
137
|
+
const Theme = {
|
|
138
|
+
setTheme,
|
|
139
|
+
getTokens,
|
|
140
|
+
getMode,
|
|
141
|
+
resolveToken,
|
|
142
|
+
onThemeChange,
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Apply theme settings with convenient object syntax
|
|
146
|
+
* @example Theme.apply({ isDark: true })
|
|
147
|
+
* @example Theme.apply({ mode: 'light' })
|
|
148
|
+
*/
|
|
149
|
+
apply(options: { isDark?: boolean; mode?: ThemeMode }): void {
|
|
150
|
+
if (options.isDark !== undefined) {
|
|
151
|
+
setTheme(options.isDark ? 'dark' : 'light')
|
|
152
|
+
} else if (options.mode !== undefined) {
|
|
153
|
+
setTheme(options.mode)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
136
158
|
export type { DFTheme, ThemeMode }
|
|
137
159
|
export {
|
|
160
|
+
Theme,
|
|
138
161
|
themeManager,
|
|
139
162
|
setTheme,
|
|
140
163
|
createTheme,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Component
|
|
3
|
+
*
|
|
4
|
+
* Quick button shorthand component. Combines Text and Pressable for common button pattern.
|
|
5
|
+
* Accepts title, style, and handlers. Returns DFNode.
|
|
6
|
+
*
|
|
7
|
+
* No JSX. No HTML. No XML. Pure functional TypeScript.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createNode } from '@deskforge/core'
|
|
11
|
+
import type { DFNode, StyleObject } from '@deskforge/core'
|
|
12
|
+
import type { PressHandlers } from './Pressable'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Button - Quick button component
|
|
16
|
+
*
|
|
17
|
+
* Convenience component for creating buttons. Renders a Pressable with a Text child.
|
|
18
|
+
* Useful for common button patterns without manually nesting components.
|
|
19
|
+
*
|
|
20
|
+
* @param title - Button text label
|
|
21
|
+
* @param style - StyleObject containing layout and appearance properties
|
|
22
|
+
* @param handlers - PressHandlers object with onPress and other handlers
|
|
23
|
+
* @returns DFNode object with type 'Pressable' (button is a semantic shorthand)
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* Button('Click Me', { backgroundColor: '#0077ff', padding: 12 }, {
|
|
27
|
+
* onPress: () => handleClick()
|
|
28
|
+
* })
|
|
29
|
+
*
|
|
30
|
+
* Button('Submit', {
|
|
31
|
+
* backgroundColor: '#2ecc71',
|
|
32
|
+
* paddingHorizontal: 20,
|
|
33
|
+
* paddingVertical: 10,
|
|
34
|
+
* borderRadius: 4,
|
|
35
|
+
* }, {
|
|
36
|
+
* onPress: () => submitForm()
|
|
37
|
+
* })
|
|
38
|
+
*/
|
|
39
|
+
export function Button(
|
|
40
|
+
title: string,
|
|
41
|
+
style: StyleObject,
|
|
42
|
+
handlers: PressHandlers = {}
|
|
43
|
+
): DFNode {
|
|
44
|
+
return createNode(
|
|
45
|
+
'Pressable',
|
|
46
|
+
{
|
|
47
|
+
style,
|
|
48
|
+
onPress: handlers.onPress,
|
|
49
|
+
onLongPress: handlers.onLongPress,
|
|
50
|
+
onPressIn: handlers.onPressIn,
|
|
51
|
+
onPressOut: handlers.onPressOut,
|
|
52
|
+
disabled: handlers.disabled ?? false,
|
|
53
|
+
} as Record<string, unknown>,
|
|
54
|
+
[
|
|
55
|
+
createNode(
|
|
56
|
+
'Text',
|
|
57
|
+
{
|
|
58
|
+
content: title,
|
|
59
|
+
style: {},
|
|
60
|
+
} as Record<string, unknown>,
|
|
61
|
+
[]
|
|
62
|
+
),
|
|
63
|
+
]
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type { StyleObject, PressHandlers }
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Component
|
|
3
|
+
*
|
|
4
|
+
* Displays images from file paths or URIs with optional styling.
|
|
5
|
+
* Accepts source and optional style object. Returns DFNode.
|
|
6
|
+
*
|
|
7
|
+
* No JSX. No HTML. No XML. Pure functional TypeScript.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createNode } from '@deskforge/core'
|
|
11
|
+
import type { DFNode, StyleObject } from '@deskforge/core'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Image source — can be string path or URI object
|
|
15
|
+
*/
|
|
16
|
+
export type ImageSource = string | { uri: string }
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Image - Image rendering component
|
|
20
|
+
*
|
|
21
|
+
* Renders an image from a file path or URI. Supports local files and remote URLs.
|
|
22
|
+
* Optional style object for sizing, positioning, and appearance.
|
|
23
|
+
*
|
|
24
|
+
* @param source - Image source as file path string or { uri: string } object
|
|
25
|
+
* @param style - Optional StyleObject for sizing and appearance
|
|
26
|
+
* @returns DFNode object with type 'Image'
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* Image('/assets/logo.png', { width: 100, height: 100 })
|
|
30
|
+
*
|
|
31
|
+
* Image({ uri: 'https://example.com/image.png' }, {
|
|
32
|
+
* width: 200,
|
|
33
|
+
* height: 200,
|
|
34
|
+
* borderRadius: 12,
|
|
35
|
+
* })
|
|
36
|
+
*
|
|
37
|
+
* Image('/local/image.jpg', {
|
|
38
|
+
* width: '100%',
|
|
39
|
+
* height: 300,
|
|
40
|
+
* resizeMode: 'cover'
|
|
41
|
+
* })
|
|
42
|
+
*/
|
|
43
|
+
export function Image(
|
|
44
|
+
source: ImageSource,
|
|
45
|
+
style?: StyleObject
|
|
46
|
+
): DFNode {
|
|
47
|
+
const sourceObj = typeof source === 'string' ? { uri: source } : source
|
|
48
|
+
|
|
49
|
+
return createNode(
|
|
50
|
+
'Image',
|
|
51
|
+
{
|
|
52
|
+
source: sourceObj,
|
|
53
|
+
style: style ?? {},
|
|
54
|
+
} as Record<string, unknown>,
|
|
55
|
+
[]
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type { StyleObject }
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pressable Component
|
|
3
|
+
*
|
|
4
|
+
* Tappable container with press event handlers and animations.
|
|
5
|
+
* Accepts style, handlers object, and children array. Returns DFNode.
|
|
6
|
+
*
|
|
7
|
+
* No JSX. No HTML. No XML. Pure functional TypeScript.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createNode } from '@deskforge/core'
|
|
11
|
+
import type { DFNode, StyleObject } from '@deskforge/core'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Press handler configuration
|
|
15
|
+
*
|
|
16
|
+
* Defines all available press events for interactive components.
|
|
17
|
+
*/
|
|
18
|
+
export interface PressHandlers {
|
|
19
|
+
/** Fired on tap or click */
|
|
20
|
+
onPress?: () => void
|
|
21
|
+
/** Fired after 500ms hold */
|
|
22
|
+
onLongPress?: () => void
|
|
23
|
+
/** Fired when press starts */
|
|
24
|
+
onPressIn?: () => void
|
|
25
|
+
/** Fired when press ends */
|
|
26
|
+
onPressOut?: () => void
|
|
27
|
+
/** Prevents all press events when true */
|
|
28
|
+
disabled?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Pressable - Tappable container component
|
|
33
|
+
*
|
|
34
|
+
* Renders a container that responds to press events. Automatically applies
|
|
35
|
+
* press animations and feedback. Children are rendered inside.
|
|
36
|
+
*
|
|
37
|
+
* @param style - StyleObject containing layout and appearance properties
|
|
38
|
+
* @param handlers - PressHandlers object with onPress, onLongPress, onPressIn, onPressOut, disabled
|
|
39
|
+
* @param children - Array of DFNode children to render inside the pressable
|
|
40
|
+
* @returns DFNode object with type 'Pressable'
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* Pressable({ padding: 16, borderRadius: 8 }, {
|
|
44
|
+
* onPress: () => console.log('pressed'),
|
|
45
|
+
* onLongPress: () => console.log('long pressed'),
|
|
46
|
+
* }, [
|
|
47
|
+
* Text('Press me', { color: '#0077ff' })
|
|
48
|
+
* ])
|
|
49
|
+
*/
|
|
50
|
+
export function Pressable(
|
|
51
|
+
style: StyleObject,
|
|
52
|
+
handlers: PressHandlers = {},
|
|
53
|
+
children: DFNode[] = []
|
|
54
|
+
): DFNode {
|
|
55
|
+
return createNode(
|
|
56
|
+
'Pressable',
|
|
57
|
+
{
|
|
58
|
+
style,
|
|
59
|
+
onPress: handlers.onPress,
|
|
60
|
+
onLongPress: handlers.onLongPress,
|
|
61
|
+
onPressIn: handlers.onPressIn,
|
|
62
|
+
onPressOut: handlers.onPressOut,
|
|
63
|
+
disabled: handlers.disabled ?? false,
|
|
64
|
+
} as Record<string, unknown>,
|
|
65
|
+
children
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type { StyleObject }
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Component
|
|
3
|
+
*
|
|
4
|
+
* Renders text content with optional styling.
|
|
5
|
+
* Accepts content string or number and optional style object. Returns DFNode.
|
|
6
|
+
*
|
|
7
|
+
* No JSX. No HTML. No XML. Pure functional TypeScript.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createNode } from '@deskforge/core'
|
|
11
|
+
import type { DFNode, StyleObject } from '@deskforge/core'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Text - Text rendering component
|
|
15
|
+
*
|
|
16
|
+
* Renders text content with optional style object. Content can be string or number.
|
|
17
|
+
* All typography properties from React Native are supported via StyleObject.
|
|
18
|
+
*
|
|
19
|
+
* @param content - Text content to render (string or number)
|
|
20
|
+
* @param style - Optional StyleObject containing typography and appearance properties
|
|
21
|
+
* @returns DFNode object with type 'Text'
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* Text('Hello World', { fontSize: 16, fontWeight: 'bold', color: '#000' })
|
|
25
|
+
* Text(42, { fontSize: 14 })
|
|
26
|
+
* Text('Simple', { color: '#666' })
|
|
27
|
+
*/
|
|
28
|
+
export function Text(
|
|
29
|
+
content: string | number,
|
|
30
|
+
style?: StyleObject
|
|
31
|
+
): DFNode {
|
|
32
|
+
return createNode(
|
|
33
|
+
'Text',
|
|
34
|
+
{
|
|
35
|
+
content: String(content),
|
|
36
|
+
style: style ?? {},
|
|
37
|
+
} as Record<string, unknown>,
|
|
38
|
+
[]
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type { StyleObject }
|