nativescript-web-adapter 0.1.1 → 0.1.3
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 +220 -168
- package/core/components/AbsoluteLayout.vue +11 -0
- package/core/components/ActionBar.vue +11 -0
- package/core/components/ActionItem.vue +11 -0
- package/core/components/ActivityIndicator.vue +15 -0
- package/core/components/Button.vue +41 -0
- package/core/components/DatePicker.vue +27 -0
- package/core/components/DockLayout.vue +23 -0
- package/core/components/FlexboxLayout.vue +11 -0
- package/core/components/Frame.vue +11 -0
- package/core/components/GridLayout.vue +85 -0
- package/core/components/HtmlView.vue +13 -0
- package/core/components/Image.vue +12 -0
- package/core/components/ImageCacheIt.vue +12 -0
- package/core/components/Label.vue +15 -0
- package/core/components/ListPicker.vue +21 -0
- package/core/components/ListView.vue +12 -0
- package/core/components/NavigationButton.vue +28 -0
- package/core/components/Page.vue +18 -0
- package/core/components/Placeholder.vue +11 -0
- package/core/components/Progress.vue +12 -0
- package/core/components/RootLayout.vue +11 -0
- package/core/components/ScrollView.vue +11 -0
- package/core/components/SearchBar.vue +22 -0
- package/core/components/SegmentedBar.vue +50 -0
- package/core/components/SegmentedBarItem.vue +21 -0
- package/core/components/Slider.vue +18 -0
- package/core/components/StackLayout.vue +11 -0
- package/core/components/Switch.vue +26 -0
- package/core/components/TabView.vue +48 -0
- package/core/components/TabViewItem.vue +27 -0
- package/core/components/TextField.vue +15 -0
- package/core/components/TextView.vue +18 -0
- package/core/components/TimePicker.vue +21 -0
- package/core/components/WebView.vue +13 -0
- package/core/components/WrapLayout.vue +11 -0
- package/core/components/index.js +3 -0
- package/core/components/index.ts +35 -0
- package/core/composables/dialogs.ts +31 -0
- package/core/composables/index.js +3 -0
- package/core/composables/index.ts +4 -0
- package/core/composables/useActionBar.js +7 -0
- package/core/composables/useActionBar.ts +19 -0
- package/core/composables/useFrame.js +8 -0
- package/core/composables/useFrame.ts +25 -0
- package/core/composables/usePage.js +8 -0
- package/core/composables/usePage.ts +25 -0
- package/core/env.d.ts +7 -0
- package/core/index.js +4 -0
- package/core/index.ts +85 -0
- package/core/types.ts +12 -0
- package/dist/nativescript-web-adapter.es.js +83 -0
- package/dist/nativescript-web-adapter.umd.js +1 -0
- package/dist/style.css +1 -0
- package/package.json +34 -46
- package/tools/cli.cjs +45 -0
- package/tools/create-web-platform.cjs +76 -0
- package/tools/create-web-platform.js +196 -0
- package/tools/modules/appPatch.cjs +27 -0
- package/tools/modules/copy.cjs +84 -0
- package/tools/modules/router.cjs +46 -0
- package/tools/modules/templates.cjs +130 -0
- package/tools/modules/transform.cjs +93 -0
- package/dist/core.cjs +0 -3
- package/dist/core.cjs.map +0 -1
- package/dist/core.js +0 -2
- package/dist/core.js.map +0 -1
- package/dist/index.cjs +0 -240
- package/dist/index.cjs.map +0 -1
- package/dist/index.js +0 -229
- package/dist/index.js.map +0 -1
- package/dist/types/core/index.d.ts +0 -8
- package/dist/types/index.d.ts +0 -1
- package/dist/types/vue/components/ActionBar.d.ts +0 -5
- package/dist/types/vue/components/Button.d.ts +0 -26
- package/dist/types/vue/components/FlexboxLayout.d.ts +0 -35
- package/dist/types/vue/components/Frame.d.ts +0 -3
- package/dist/types/vue/components/GridLayout.d.ts +0 -27
- package/dist/types/vue/components/ImageCacheIt.d.ts +0 -23
- package/dist/types/vue/components/Label.d.ts +0 -26
- package/dist/types/vue/components/Page.d.ts +0 -5
- package/dist/types/vue/components/StackLayout.d.ts +0 -5
- package/dist/types/vue/index.d.ts +0 -12
- package/dist/types/vue.d.ts +0 -169
- package/dist/vue.cjs +0 -240
- package/dist/vue.cjs.map +0 -1
- package/dist/vue.js +0 -229
- package/dist/vue.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,190 +1,242 @@
|
|
|
1
1
|
# NativeScript for Web
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
中文: [README_ZH.md](README_ZH.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/nativescript-web-adapter)
|
|
6
|
+
[](LICENSE)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
This project is a “web adapter” for NativeScript‑Vue‑Vite. It can transform native app code into a pure Vue Web project that runs in the browser (generated under `platforms/web/`).
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
---
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
- Prop and event mapping: common props/events are translated to DOM (e.g., `tap` → `click`).
|
|
13
|
-
- Lightweight implementation: focuses on frequently used layouts and controls.
|
|
14
|
-
- Pluggable: registered as a Vue plugin; no invasive app changes.
|
|
12
|
+
## Quick Start
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
Temporarily add the local bin to `PATH` to make the `ns-web` command available:
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
npm install nativescript-web-adapter
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
This package expects `vue` as a peer dependency. If not already present:
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
npm install vue@^3.4.0
|
|
16
|
+
```bash
|
|
17
|
+
export PATH="$PWD/node_modules/.bin:$PATH"
|
|
28
18
|
```
|
|
29
19
|
|
|
30
|
-
|
|
20
|
+
Compile the web template – install dependencies – start the web project's dev server:
|
|
31
21
|
|
|
32
|
-
|
|
22
|
+
Web
|
|
33
23
|
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
import { createApp } from 'vue';
|
|
37
|
-
import { NativeScriptWebPlugin } from 'nativescript-web-adapter';
|
|
38
|
-
import Home from '@/components/Home.vue';
|
|
39
|
-
import '@/app.css';
|
|
40
|
-
|
|
41
|
-
const app = createApp(Home);
|
|
42
|
-
app.use(NativeScriptWebPlugin);
|
|
43
|
-
app.mount('#app');
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
2) Vite aliases (map NS deps to the adapter for browser)
|
|
47
|
-
|
|
48
|
-
```ts
|
|
49
|
-
// vite.web.config.ts
|
|
50
|
-
import { defineConfig } from 'vite';
|
|
51
|
-
import vue from '@vitejs/plugin-vue';
|
|
52
|
-
import path from 'path';
|
|
53
|
-
|
|
54
|
-
export default defineConfig({
|
|
55
|
-
plugins: [vue()],
|
|
56
|
-
resolve: {
|
|
57
|
-
alias: {
|
|
58
|
-
'@': path.resolve(__dirname, 'src'),
|
|
59
|
-
'~': path.resolve(__dirname, 'src'),
|
|
60
|
-
// map SFC imports of nativescript-vue to browser vue
|
|
61
|
-
'nativescript-vue': 'vue',
|
|
62
|
-
// map @nativescript/core types/APIs to adapter core (browser-only)
|
|
63
|
-
'@nativescript/core': 'nativescript-web-adapter/core'
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
});
|
|
24
|
+
```bash
|
|
25
|
+
ns-web dev
|
|
67
26
|
```
|
|
68
27
|
|
|
69
|
-
|
|
28
|
+
iOS
|
|
70
29
|
|
|
71
30
|
```bash
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Usage Example
|
|
76
|
-
|
|
77
|
-
```vue
|
|
78
|
-
<template>
|
|
79
|
-
<Frame>
|
|
80
|
-
<Page class="bg-[#251353]">
|
|
81
|
-
<ActionBar>
|
|
82
|
-
<Label text="ViteConf 2025" class="font-bold text-lg" />
|
|
83
|
-
</ActionBar>
|
|
84
|
-
|
|
85
|
-
<GridLayout rows="auto,*">
|
|
86
|
-
<ImageCacheIt
|
|
87
|
-
src="https://example.com/cover.jpg"
|
|
88
|
-
stretch="aspectFill"
|
|
89
|
-
class="align-top"
|
|
90
|
-
style="width:100dvw"
|
|
91
|
-
/>
|
|
92
|
-
|
|
93
|
-
<GridLayout row="1" rows="*,auto,*" class="gradient-purple p-6">
|
|
94
|
-
<Label
|
|
95
|
-
class="text-xl align-middle text-center text-[#77c9fa] font-bold"
|
|
96
|
-
text="Hype Counter: 31485"
|
|
97
|
-
/>
|
|
98
|
-
|
|
99
|
-
<Button
|
|
100
|
-
row="1"
|
|
101
|
-
class="p-6 text-white font-bold border-4 border-[#77c9fa] rounded-lg text-xl gradient-light-purple"
|
|
102
|
-
horizontalAlignment="center"
|
|
103
|
-
>
|
|
104
|
-
Enter Now
|
|
105
|
-
</Button>
|
|
106
|
-
</GridLayout>
|
|
107
|
-
</GridLayout>
|
|
108
|
-
</Page>
|
|
109
|
-
</Frame>
|
|
110
|
-
</template>
|
|
31
|
+
ns run ios
|
|
111
32
|
```
|
|
112
33
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
- `Page` → DOM `main`
|
|
116
|
-
- `ActionBar` → DOM `header`
|
|
117
|
-
- `Frame` → DOM `div`
|
|
118
|
-
- `Label` → DOM `span`
|
|
119
|
-
- `Button` → DOM `button`
|
|
120
|
-
- `StackLayout` → DOM `div` (`display:flex; flex-direction:column`)
|
|
121
|
-
- `FlexboxLayout` → DOM `div` (`display:flex`; supports `flexDirection`, `justifyContent`, `alignItems`)
|
|
122
|
-
- `GridLayout` → DOM `div` (`display:grid`; details below)
|
|
123
|
-
- `ImageCacheIt` → DOM `img` (simple replacement; uses `img` with `stretch` support)
|
|
124
|
-
|
|
125
|
-
## GridLayout: Design & Behavior
|
|
126
|
-
|
|
127
|
-
- Container styles
|
|
128
|
-
- `display: grid`
|
|
129
|
-
- `grid-template-columns`: defaults to single column `1fr` when `columns` is not provided
|
|
130
|
-
- `grid-template-rows`: parsed from `rows` (optional)
|
|
131
|
-
- `grid-auto-flow: row`: auto-stacks vertically by rows
|
|
132
|
-
- `width: 100%`, `height: 100%`
|
|
133
|
-
|
|
134
|
-
- Track parsing (`rows`/`columns`)
|
|
135
|
-
- Comma-separated: e.g., `"auto,*"`, `"*,auto,*"`
|
|
136
|
-
- `*` → `1fr`, `auto` → `auto`, numbers → pixels (e.g., `60` → `60px`)
|
|
137
|
-
- Example: `rows="auto,*"` → `grid-template-rows: auto 1fr`
|
|
34
|
+
Android
|
|
138
35
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
- Span: `rowSpan` → `grid-row-end: span n`; `colSpan` → `grid-column-end: span n`
|
|
143
|
-
|
|
144
|
-
- Alignment
|
|
145
|
-
- Horizontal: `horizontalAlignment` (`left|center|right|stretch`) → `justify-self`
|
|
146
|
-
- Vertical: `verticalAlignment` (`top|center|bottom|stretch`) → `align-self`
|
|
147
|
-
|
|
148
|
-
## Other Props & Events
|
|
149
|
-
|
|
150
|
-
- Events: `tap` → DOM `click` (the adapter emits `tap` on `onClick`)
|
|
151
|
-
- `Label` alignment: `horizontalAlignment="center"` → `text-align:center`
|
|
152
|
-
- `ImageCacheIt`:
|
|
153
|
-
- `stretch="aspectFill"` → `object-fit: cover`
|
|
154
|
-
- `stretch="aspectFit"` → `object-fit: contain`
|
|
155
|
-
- default styles: `width:100%`, `display:block`, `object-position:center`
|
|
156
|
-
|
|
157
|
-
## Layout Tips (Aligning Web with iOS)
|
|
158
|
-
|
|
159
|
-
- Make the outer `main` full viewport height: `main { height: 100vh; }` to match Grid remaining-space behavior.
|
|
160
|
-
- For full-width images: use `style="width:100dvw"` or container `width:100%`; ensure no implicit columns. Optionally set inner `GridLayout` `columns="1*"`.
|
|
161
|
-
- If children appear side-by-side: check if `columns` is declared or `col` is set; by default we fix to column 1.
|
|
162
|
-
|
|
163
|
-
## Compatibility & Limitations
|
|
164
|
-
|
|
165
|
-
- The adapter focuses on common UI/layout features; complex native measurements, animations, and platform APIs are out of scope for the browser.
|
|
166
|
-
- Visual differences (fonts, line height, shadows) exist between iOS/Android and browsers; tweak via Tailwind/CSS.
|
|
167
|
-
- Events follow the DOM model; certain native events are not applicable.
|
|
168
|
-
|
|
169
|
-
## Development & Build
|
|
170
|
-
|
|
171
|
-
- Build the adapter: `npm run build` (outputs `dist/index.js`, `dist/vue.js`, `dist/core.js` and types)
|
|
172
|
-
- Run tests: `npm run test`
|
|
173
|
-
- Demo project in this repo: `npm run dev:web` starts the browser preview.
|
|
174
|
-
|
|
175
|
-
## Package Structure (Adapter Subpackage)
|
|
176
|
-
|
|
177
|
-
- `src/vue/components/*`: implementation of UI components
|
|
178
|
-
- `src/vue/index.ts`: Vue plugin entry (registers all components)
|
|
179
|
-
- `src/core`: minimal types/placeholders for `@nativescript/core` in browser
|
|
180
|
-
- `dist/*`: build outputs and type declarations
|
|
181
|
-
|
|
182
|
-
## Design Rationale
|
|
36
|
+
```bash
|
|
37
|
+
ns run android
|
|
38
|
+
```
|
|
183
39
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
-
|
|
40
|
+
## Table of Contents
|
|
41
|
+
|
|
42
|
+
- Background & Goals
|
|
43
|
+
- Tech Stack & Dependencies
|
|
44
|
+
- Native Side (NativeScript)
|
|
45
|
+
- Web Side (Generated Vue Project)
|
|
46
|
+
- Web Adapter (Generator & Package)
|
|
47
|
+
- Run & Build
|
|
48
|
+
- Transformation & Adaptation Rules (Detailed)
|
|
49
|
+
- Styles & Tailwind Configuration
|
|
50
|
+
- Types & TS Configuration
|
|
51
|
+
- HMR (Hot Module Replacement)
|
|
52
|
+
- FAQ & Limitations
|
|
53
|
+
- Improvements & Upgrade Suggestions
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Background & Goals
|
|
58
|
+
|
|
59
|
+
- Provide a native app example using `nativescript-vue` + `@nativescript/vite`, showcasing development and HMR on iOS/Android.
|
|
60
|
+
- Provide a “web adapter” (local package `nativescript-web-adapter`) that scans and transforms the project's `src/` code to generate a Vue application that runs in the browser, enabling quick preview and collaboration on desktop browsers.
|
|
61
|
+
- The generated Web project lives in `platforms/web/` with its own Vite config, dependencies, and entry files, without affecting the native side.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Tech Stack & Dependencies
|
|
66
|
+
|
|
67
|
+
- Native side (NativeScript):
|
|
68
|
+
- `nativescript-vue@3.0.1` (uses `patch-package` to inject HMR helpers)
|
|
69
|
+
- `@nativescript/core@alpha`, `@nativescript/vite@^0.0.1-alpha.7` (NativeScript v9 prerelease)
|
|
70
|
+
- Example native plugins: `@triniwiz/nativescript-image-cache-it`, `@valor/nativescript-websockets`, `nativescript-inappbrowser`
|
|
71
|
+
- Web side (generated project):
|
|
72
|
+
- `vue@^3.4`, `vue-router@^4.2`
|
|
73
|
+
- `vite@^5`, `@vitejs/plugin-vue@^5`
|
|
74
|
+
- `tailwindcss@^3.4`, `postcss`, `autoprefixer`
|
|
75
|
+
- Adapter package (local):
|
|
76
|
+
- `nativescript-web-adapter/` uses Vite library mode to build UMD/ESM, with built-in generator scripts and a simple CLI.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Native Side (NativeScript)
|
|
81
|
+
|
|
82
|
+
- Entry `src/app.ts`:
|
|
83
|
+
- Starts the native app via `createApp(Home).start()` from `nativescript-vue`.
|
|
84
|
+
- Registers the native element `ImageCacheIt`.
|
|
85
|
+
- On Android, uses `Application.on(Application.launchEvent)` to adjust system status/navigation bar styles.
|
|
86
|
+
- Page `src/components/Home.vue`:
|
|
87
|
+
- Uses NS layouts and components such as `Frame/Page/ActionBar/GridLayout/Label/Button/ImageCacheIt`.
|
|
88
|
+
- Uses `@tap` to open native links (iOS uses `UIApplication.openURL`, Android uses `Intent`).
|
|
89
|
+
- Maintains a simple counter in `mounted/unmounted` to demonstrate HMR and state refresh.
|
|
90
|
+
- Global init `src/globals.ts`:
|
|
91
|
+
- Defines touch animations (iOS: `UIView.animate`, Android: `View.animate`), replaced by a no‑op shim on the Web side.
|
|
92
|
+
- Native Vite config `vite.config.ts`:
|
|
93
|
+
- `export default defineConfig(({ mode }) => mergeConfig(vueConfig({ mode }), {}))` merges the NS official Vite template to support HMR and build.
|
|
94
|
+
- Tailwind & styles:
|
|
95
|
+
- `src/app.css` defines theme colors and gradient classes (`gradient-purple/gradient-light-purple`), and sets the `ActionBar` primary color.
|
|
96
|
+
- `tailwind.config.js` uses `darkMode: ["class", ".ns-dark"]` and disables `preflight` (matching native rendering).
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Web Side (Generated Vue Project)
|
|
101
|
+
|
|
102
|
+
- Output location: `platforms/web/`
|
|
103
|
+
- Example structure:
|
|
187
104
|
|
|
188
|
-
|
|
105
|
+
```
|
|
106
|
+
platforms/web/
|
|
107
|
+
package.json # generated (includes vue + vue-router)
|
|
108
|
+
vite.config.ts # generated (server.port = 3005, strictPort = true)
|
|
109
|
+
index.html
|
|
110
|
+
postcss.config.js
|
|
111
|
+
tailwind.config.js
|
|
112
|
+
src/
|
|
113
|
+
App.vue # root component (<router-view />)
|
|
114
|
+
app.ts # entry (register adapter web components, mount '#app')
|
|
115
|
+
router/index.ts # default route (Home)
|
|
116
|
+
globals.ts # web shim: initGlobals() no-op
|
|
117
|
+
components/
|
|
118
|
+
Home.vue # copied and transformed from native
|
|
119
|
+
websfc/ # adapter web components (ActionBar/Page/Frame)
|
|
120
|
+
composables/
|
|
121
|
+
websfc/ # adapter composables (useActionBar/usePage/useFrame)
|
|
122
|
+
```
|
|
189
123
|
|
|
190
|
-
|
|
124
|
+
- Default dev URL: `http://localhost:3005/` (if the port is taken, it fails directly; set `strictPort` to `false` in `vite.config.ts` to enable fallback).
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Web Adapter (Generator & Package)
|
|
129
|
+
|
|
130
|
+
- Location: `nativescript-web-adapter/`
|
|
131
|
+
- Key files:
|
|
132
|
+
- `tools/create-web-platform.cjs`: generation script (copy, transform, write templates).
|
|
133
|
+
- `core/components/*.vue`: web SFC containers (`ActionBar/Page/Frame`).
|
|
134
|
+
- `core/composables/*.ts`: composables (`useActionBar/usePage/useFrame`).
|
|
135
|
+
- `core/types.ts`: component type aliases and composable type exports.
|
|
136
|
+
- `index.ts`: adapter installer (`install(app)` registers components and attaches `$ns`).
|
|
137
|
+
- `vite.config.ts`: library mode build (`external: ['vue']`, outputs UMD/ESM).
|
|
138
|
+
- CLI: `tools/cli.cjs`
|
|
139
|
+
- Local command: `node nativescript-web-adapter/tools/cli.cjs init`
|
|
140
|
+
- Includes a `create:web` NPM script (for the root project to call).
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Run & Build
|
|
145
|
+
|
|
146
|
+
- Native side (with HMR):
|
|
147
|
+
- `npm run dev:ios`: run Vite (port `5173`) and NS iOS debug in parallel.
|
|
148
|
+
- `npm run dev:android`: run Vite and NS Android debug in parallel.
|
|
149
|
+
- `npm run ios` / `npm run android`: use `ns debug` for debugging builds.
|
|
150
|
+
- Generate and run the Web project:
|
|
151
|
+
- `npm run dev:web`: run the adapter generator, then enter `platforms/web`, install deps, and start the Web Vite dev server.
|
|
152
|
+
- On first run, it creates `platforms/web` along with required templates and configuration.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Transformation & Adaptation Rules (Detailed)
|
|
157
|
+
|
|
158
|
+
The generator’s `transformContent()` currently performs only necessary code‑level replacements and no longer replaces NS tags in SFC templates with native HTML tags; the UI is handled by custom web components.
|
|
159
|
+
|
|
160
|
+
- Import replacements and removals:
|
|
161
|
+
- `from 'nativescript-vue'` → `from 'vue'`
|
|
162
|
+
- Remove `import ... from '@nativescript/core'` (the web build does not load the native runtime)
|
|
163
|
+
- Remove `import ... from '@nativescript/web-adapter'` (the generated web project uses local components directly)
|
|
164
|
+
- Platform declarations and native calls cleanup:
|
|
165
|
+
- Remove common native declarations (e.g., `declare var com;`).
|
|
166
|
+
- Remove `Application.on(...)` and similar platform event registrations (including those with `__ANDROID__` or `Application.launchEvent`).
|
|
167
|
+
- Template retains NS tags:
|
|
168
|
+
- Components kept and registered on the Web: `ActionBar/Page/Frame/StackLayout/GridLayout/FlexboxLayout/WrapLayout/ScrollView/Label/Button/Image/HtmlView/ImageCacheIt`.
|
|
169
|
+
- Special downgrade: `ImageCacheIt` is implemented as a simple `<img>` on the Web (also provided as a web component of the same name for consistent usage).
|
|
170
|
+
- Unified native link opening logic:
|
|
171
|
+
- Replace the example `enterNow(...)` with the browser implementation: `window.open('https://viteconf.amsterdam', '_blank')`.
|
|
172
|
+
- Other cleanup:
|
|
173
|
+
- Remove lines involving `android.*`, `UIApplication`, `NSURL`, `NSDictionary`, `intent`, etc.
|
|
174
|
+
- Remove leftover `else { ... }` native branch blocks.
|
|
175
|
+
- Comment out `registerElement(...)` (not needed on the Web).
|
|
176
|
+
- Special file handling:
|
|
177
|
+
- Replace `globals.ts` with a no‑op shim: `export function initGlobals() { /* web shim: no-op */ }`.
|
|
178
|
+
|
|
179
|
+
> Tip: The current transformation is regex‑driven and covers common patterns. For complex conditional branches, dynamic templates, and advanced native APIs, consider moving to TypeScript/Vue SFC AST‑based transformations to improve accuracy and maintainability.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Styles & Tailwind Configuration
|
|
184
|
+
|
|
185
|
+
- Native side:
|
|
186
|
+
- `src/app.css` uses `@tailwind base/components/utilities` and defines theme styles and gradient backgrounds.
|
|
187
|
+
- `tailwind.config.js`:
|
|
188
|
+
- `content: ["./src/**/*.{css,vue,ts,tsx}"]`
|
|
189
|
+
- `darkMode: ["class", ".ns-dark"]`
|
|
190
|
+
- `corePlugins.preflight: false` (disables browser preset resets)
|
|
191
|
+
- Web side (generated):
|
|
192
|
+
- `tailwind.config.js` scans `./index.html` and `./src/**/*.{vue,js,ts,jsx,tsx}` and uses standard browser presets.
|
|
193
|
+
- `postcss.config.js`: enables `tailwindcss` and `autoprefixer`.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Types & TS Configuration
|
|
198
|
+
|
|
199
|
+
- Root `tsconfig.json`:
|
|
200
|
+
- `strict: true`, target and module set to `esnext`.
|
|
201
|
+
- Path aliases: `~/*`, `@/*` → `src/*`.
|
|
202
|
+
- `vueCompilerOptions.lib = "nativescript-vue"` instructs SFC compilation for the NS environment.
|
|
203
|
+
- Type declarations:
|
|
204
|
+
- Native side `types/shims.vue.d.ts`: points `*.vue` types to `nativescript-vue`’s `DefineComponent`.
|
|
205
|
+
- Adapter side `core/env.d.ts`: points `*.vue` types to `vue`’s `DefineComponent`, consistent with browser compilation.
|
|
206
|
+
- Adapter `core/types.ts`: provides type aliases for `ActionBar/Page/Frame` components and exports state types for `useActionBar/usePage/useFrame`.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## HMR (Hot Module Replacement)
|
|
211
|
+
|
|
212
|
+
- Native side:
|
|
213
|
+
- Uses `@nativescript/vite` with `vite serve -- --env.hmr` to establish an HMR channel, pushing changes to the device.
|
|
214
|
+
- `patches/nativescript-vue+3.0.1.patch` injects within `app.start`: `globalThis.__NS_VUE_APP__ = app`, helping restore state in deep navigation stacks (e.g., when returning during HMR).
|
|
215
|
+
- Web side:
|
|
216
|
+
- Independent Vite (`platforms/web/vite.config.ts`) runs on port `3005`, using standard Vue HMR and route refresh.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## FAQ & Limitations
|
|
221
|
+
|
|
222
|
+
- Native APIs & plugins:
|
|
223
|
+
- Complex native interactions (platform‑specific modules, system services) cannot be automatically converted to the Web. They require manual replacement or browser fallback implementations (e.g., `InAppBrowser` → `window.open` wrapper).
|
|
224
|
+
- Layout & semantic mapping is limited:
|
|
225
|
+
- Basic web components are provided (`ActionBar/Page/Frame/Grid/Stack/Flex/Wrap/Scroll/Label/Button/Image/HtmlView/ImageCacheIt`). More detailed property‑to‑style mappings will be added later (e.g., `flexDirection/row`, grid rows/columns).
|
|
226
|
+
- Regex‑driven transformation:
|
|
227
|
+
- Edge cases may exist for complex code and templates. Gradually moving to AST‑level transformation is recommended.
|
|
228
|
+
- Generator template duplicate writes:
|
|
229
|
+
- The generator currently writes `platforms/web/package.json` twice (with different dependency versions). This can be streamlined into a single write to reduce maintenance cost.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Improvements & Upgrade Suggestions
|
|
234
|
+
|
|
235
|
+
1. Transformation engine upgrade:
|
|
236
|
+
- Replace regex with TypeScript and Vue SFC AST to precisely handle imports, calls, templates, and directive mappings.
|
|
237
|
+
2. Global initialization & animations:
|
|
238
|
+
- Provide optional web animation implementations (CSS transitions/GSAP) for `globals.ts` to keep interaction behavior consistent.
|
|
239
|
+
3. Tests & validation:
|
|
240
|
+
- Add unit tests and e2e tests for transformation rules and template generation to ensure stability across different project structures.
|
|
241
|
+
|
|
242
|
+
---
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button class="ns-actionitem"><slot /></button>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
defineOptions({ name: 'ActionItem' });
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<style scoped>
|
|
10
|
+
.ns-actionitem { background: transparent; border: none; color: inherit; cursor: pointer; padding: 8px; }
|
|
11
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-activity-indicator" :class="{ busy }" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
const props = defineProps<{ busy?: boolean }>();
|
|
7
|
+
const busy = props.busy ?? true;
|
|
8
|
+
defineOptions({ name: 'ActivityIndicator' });
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<style scoped>
|
|
12
|
+
.ns-activity-indicator { width: 20px; height: 20px; border-radius: 50%; border: 2px solid rgba(255,255,255,0.3); border-top-color: currentColor; opacity: 0.8; }
|
|
13
|
+
.ns-activity-indicator.busy { animation: ns-spin 0.8s linear infinite; }
|
|
14
|
+
@keyframes ns-spin { to { transform: rotate(360deg) } }
|
|
15
|
+
</style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button class="ns-button" :style="btnStyle" @click="$emit('tap', $event)"><slot /></button>
|
|
3
|
+
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script setup lang="ts">
|
|
7
|
+
import { computed } from 'vue';
|
|
8
|
+
defineOptions({ name: 'Button' });
|
|
9
|
+
const emit = defineEmits<{ (e: 'tap', evt: MouseEvent): void }>();
|
|
10
|
+
const props = defineProps<{ horizontalAlignment?: string }>();
|
|
11
|
+
|
|
12
|
+
const btnStyle = computed<Record<string, string>>(() => {
|
|
13
|
+
const style: Record<string, string> = {};
|
|
14
|
+
const h = props.horizontalAlignment?.toLowerCase();
|
|
15
|
+
if (h === 'center') {
|
|
16
|
+
style.display = 'block';
|
|
17
|
+
style.width = 'fit-content';
|
|
18
|
+
style.marginLeft = 'auto';
|
|
19
|
+
style.marginRight = 'auto';
|
|
20
|
+
style.alignSelf = 'center';
|
|
21
|
+
style.justifySelf = 'center';
|
|
22
|
+
} else if (h === 'right') {
|
|
23
|
+
style.display = 'block';
|
|
24
|
+
style.width = 'fit-content';
|
|
25
|
+
style.marginLeft = 'auto';
|
|
26
|
+
style.alignSelf = 'flex-end';
|
|
27
|
+
style.justifySelf = 'end';
|
|
28
|
+
} else if (h === 'left') {
|
|
29
|
+
style.display = 'block';
|
|
30
|
+
style.width = 'fit-content';
|
|
31
|
+
style.marginRight = 'auto';
|
|
32
|
+
style.alignSelf = 'flex-start';
|
|
33
|
+
style.justifySelf = 'start';
|
|
34
|
+
}
|
|
35
|
+
return style;
|
|
36
|
+
});
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<style scoped>
|
|
40
|
+
.ns-button { cursor: pointer; }
|
|
41
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input class="ns-datepicker" type="date" :value="valueStr" @input="onInput" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import { computed } from 'vue';
|
|
7
|
+
defineOptions({ name: 'DatePicker' });
|
|
8
|
+
const props = defineProps<{ date?: string | Date }>();
|
|
9
|
+
const emit = defineEmits<{ (e: 'update:date', v: string | Date): void; (e: 'change', v: string | Date): void }>();
|
|
10
|
+
|
|
11
|
+
function pad(n: number) { return n < 10 ? `0${n}` : String(n); }
|
|
12
|
+
const valueStr = computed(() => {
|
|
13
|
+
if (!props.date) return '';
|
|
14
|
+
const d = typeof props.date === 'string' ? new Date(props.date) : props.date;
|
|
15
|
+
if (isNaN(d.getTime())) return '';
|
|
16
|
+
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
|
|
17
|
+
});
|
|
18
|
+
function onInput(e: Event) {
|
|
19
|
+
const v = (e.target as HTMLInputElement).value;
|
|
20
|
+
emit('update:date', v);
|
|
21
|
+
emit('change', v);
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style scoped>
|
|
26
|
+
.ns-datepicker { padding: 6px 8px; }
|
|
27
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-dock">
|
|
3
|
+
<div class="ns-dock-top"><slot name="top" /></div>
|
|
4
|
+
<div class="ns-dock-middle">
|
|
5
|
+
<div class="ns-dock-left"><slot name="left" /></div>
|
|
6
|
+
<div class="ns-dock-center"><slot /></div>
|
|
7
|
+
<div class="ns-dock-right"><slot name="right" /></div>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="ns-dock-bottom"><slot name="bottom" /></div>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
defineOptions({ name: 'DockLayout' });
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<style scoped>
|
|
18
|
+
.ns-dock { display: flex; flex-direction: column; width: 100%; height: 100%; }
|
|
19
|
+
.ns-dock-top, .ns-dock-bottom { flex: 0 0 auto; }
|
|
20
|
+
.ns-dock-middle { display: flex; flex: 1 1 auto; min-height: 0; }
|
|
21
|
+
.ns-dock-left, .ns-dock-right { flex: 0 0 auto; }
|
|
22
|
+
.ns-dock-center { flex: 1 1 auto; min-width: 0; }
|
|
23
|
+
</style>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-grid" :style="gridStyle">
|
|
3
|
+
<template v-for="(child, idx) in renderedChildren" :key="idx">
|
|
4
|
+
<component :is="child" />
|
|
5
|
+
</template>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { computed, useSlots, cloneVNode } from 'vue';
|
|
11
|
+
|
|
12
|
+
defineOptions({ name: 'GridLayout' });
|
|
13
|
+
const props = defineProps<{ rows?: string; columns?: string }>();
|
|
14
|
+
|
|
15
|
+
function parseSegments(input?: string): string | undefined {
|
|
16
|
+
if (!input) return undefined;
|
|
17
|
+
const segToCss = (s: string) => {
|
|
18
|
+
const t = s.trim();
|
|
19
|
+
if (!t) return 'auto';
|
|
20
|
+
if (t === '*') return '1fr';
|
|
21
|
+
const star = t.match(/^(\d+)\*$/);
|
|
22
|
+
if (star) return `${star[1]}fr`;
|
|
23
|
+
if (t.toLowerCase() === 'auto') return 'auto';
|
|
24
|
+
if (/^\d+$/.test(t)) return `${t}px`;
|
|
25
|
+
return t; // passthrough for e.g. '1fr' or '50px'
|
|
26
|
+
};
|
|
27
|
+
return input.split(',').map(segToCss).join(' ');
|
|
28
|
+
}
|
|
29
|
+
const gridStyle = computed<Record<string, string>>(() => {
|
|
30
|
+
const style: Record<string, string> = { display: 'grid' };
|
|
31
|
+
const rows = parseSegments(props.rows);
|
|
32
|
+
const cols = parseSegments(props.columns);
|
|
33
|
+
if (rows) style.gridTemplateRows = rows;
|
|
34
|
+
if (cols) style.gridTemplateColumns = cols;
|
|
35
|
+
return style;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// 根据子元素 row/col/rowSpan/colSpan 设置 CSS Grid 定位
|
|
39
|
+
const slots = useSlots();
|
|
40
|
+
const renderedChildren = computed(() => {
|
|
41
|
+
const nodes = slots.default ? slots.default() : [];
|
|
42
|
+
return nodes.map((node: any) => {
|
|
43
|
+
const props = (node && node.props) ? (node.props as Record<string, any>) : {};
|
|
44
|
+
const parseNum = (v: any) => v === undefined || v === null ? undefined : Number(v);
|
|
45
|
+
const row = parseNum(props.row);
|
|
46
|
+
const col = parseNum(props.col);
|
|
47
|
+
const rowSpan = parseNum(props.rowSpan);
|
|
48
|
+
const colSpan = parseNum(props.colSpan);
|
|
49
|
+
|
|
50
|
+
const style: Record<string, any> = {};
|
|
51
|
+
if (row !== undefined && !Number.isNaN(row)) {
|
|
52
|
+
const start = (row ?? 0) + 1; // CSS Grid 1-indexed
|
|
53
|
+
const span = rowSpan && rowSpan > 0 ? rowSpan : 1;
|
|
54
|
+
style.gridRow = `${start} / span ${span}`;
|
|
55
|
+
}
|
|
56
|
+
if (col !== undefined && !Number.isNaN(col)) {
|
|
57
|
+
const start = (col ?? 0) + 1; // CSS Grid 1-indexed
|
|
58
|
+
const span = colSpan && colSpan > 0 ? colSpan : 1;
|
|
59
|
+
style.gridColumn = `${start} / span ${span}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const existingStyle = props.style as any;
|
|
63
|
+
let mergedStyle: any;
|
|
64
|
+
if (Array.isArray(existingStyle)) mergedStyle = [...existingStyle, style];
|
|
65
|
+
else if (existingStyle && typeof existingStyle === 'object') mergedStyle = { ...existingStyle, ...style };
|
|
66
|
+
else if (typeof existingStyle === 'string') mergedStyle = [existingStyle, style];
|
|
67
|
+
else mergedStyle = style;
|
|
68
|
+
|
|
69
|
+
return cloneVNode(node, { style: mergedStyle });
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<style scoped>
|
|
75
|
+
.ns-grid {
|
|
76
|
+
padding: 0px;
|
|
77
|
+
width: 100%;
|
|
78
|
+
height: 100%;
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
min-width: 0;
|
|
81
|
+
min-height: 0;
|
|
82
|
+
/* flex: 1 1 auto;
|
|
83
|
+
gap: 50px; */
|
|
84
|
+
}
|
|
85
|
+
</style>
|