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.
Files changed (88) hide show
  1. package/README.md +220 -168
  2. package/core/components/AbsoluteLayout.vue +11 -0
  3. package/core/components/ActionBar.vue +11 -0
  4. package/core/components/ActionItem.vue +11 -0
  5. package/core/components/ActivityIndicator.vue +15 -0
  6. package/core/components/Button.vue +41 -0
  7. package/core/components/DatePicker.vue +27 -0
  8. package/core/components/DockLayout.vue +23 -0
  9. package/core/components/FlexboxLayout.vue +11 -0
  10. package/core/components/Frame.vue +11 -0
  11. package/core/components/GridLayout.vue +85 -0
  12. package/core/components/HtmlView.vue +13 -0
  13. package/core/components/Image.vue +12 -0
  14. package/core/components/ImageCacheIt.vue +12 -0
  15. package/core/components/Label.vue +15 -0
  16. package/core/components/ListPicker.vue +21 -0
  17. package/core/components/ListView.vue +12 -0
  18. package/core/components/NavigationButton.vue +28 -0
  19. package/core/components/Page.vue +18 -0
  20. package/core/components/Placeholder.vue +11 -0
  21. package/core/components/Progress.vue +12 -0
  22. package/core/components/RootLayout.vue +11 -0
  23. package/core/components/ScrollView.vue +11 -0
  24. package/core/components/SearchBar.vue +22 -0
  25. package/core/components/SegmentedBar.vue +50 -0
  26. package/core/components/SegmentedBarItem.vue +21 -0
  27. package/core/components/Slider.vue +18 -0
  28. package/core/components/StackLayout.vue +11 -0
  29. package/core/components/Switch.vue +26 -0
  30. package/core/components/TabView.vue +48 -0
  31. package/core/components/TabViewItem.vue +27 -0
  32. package/core/components/TextField.vue +15 -0
  33. package/core/components/TextView.vue +18 -0
  34. package/core/components/TimePicker.vue +21 -0
  35. package/core/components/WebView.vue +13 -0
  36. package/core/components/WrapLayout.vue +11 -0
  37. package/core/components/index.js +3 -0
  38. package/core/components/index.ts +35 -0
  39. package/core/composables/dialogs.ts +31 -0
  40. package/core/composables/index.js +3 -0
  41. package/core/composables/index.ts +4 -0
  42. package/core/composables/useActionBar.js +7 -0
  43. package/core/composables/useActionBar.ts +19 -0
  44. package/core/composables/useFrame.js +8 -0
  45. package/core/composables/useFrame.ts +25 -0
  46. package/core/composables/usePage.js +8 -0
  47. package/core/composables/usePage.ts +25 -0
  48. package/core/env.d.ts +7 -0
  49. package/core/index.js +4 -0
  50. package/core/index.ts +85 -0
  51. package/core/types.ts +12 -0
  52. package/dist/nativescript-web-adapter.es.js +83 -0
  53. package/dist/nativescript-web-adapter.umd.js +1 -0
  54. package/dist/style.css +1 -0
  55. package/package.json +34 -46
  56. package/tools/cli.cjs +45 -0
  57. package/tools/create-web-platform.cjs +76 -0
  58. package/tools/create-web-platform.js +196 -0
  59. package/tools/modules/appPatch.cjs +27 -0
  60. package/tools/modules/copy.cjs +84 -0
  61. package/tools/modules/router.cjs +46 -0
  62. package/tools/modules/templates.cjs +130 -0
  63. package/tools/modules/transform.cjs +93 -0
  64. package/dist/core.cjs +0 -3
  65. package/dist/core.cjs.map +0 -1
  66. package/dist/core.js +0 -2
  67. package/dist/core.js.map +0 -1
  68. package/dist/index.cjs +0 -240
  69. package/dist/index.cjs.map +0 -1
  70. package/dist/index.js +0 -229
  71. package/dist/index.js.map +0 -1
  72. package/dist/types/core/index.d.ts +0 -8
  73. package/dist/types/index.d.ts +0 -1
  74. package/dist/types/vue/components/ActionBar.d.ts +0 -5
  75. package/dist/types/vue/components/Button.d.ts +0 -26
  76. package/dist/types/vue/components/FlexboxLayout.d.ts +0 -35
  77. package/dist/types/vue/components/Frame.d.ts +0 -3
  78. package/dist/types/vue/components/GridLayout.d.ts +0 -27
  79. package/dist/types/vue/components/ImageCacheIt.d.ts +0 -23
  80. package/dist/types/vue/components/Label.d.ts +0 -26
  81. package/dist/types/vue/components/Page.d.ts +0 -5
  82. package/dist/types/vue/components/StackLayout.d.ts +0 -5
  83. package/dist/types/vue/index.d.ts +0 -12
  84. package/dist/types/vue.d.ts +0 -169
  85. package/dist/vue.cjs +0 -240
  86. package/dist/vue.cjs.map +0 -1
  87. package/dist/vue.js +0 -229
  88. package/dist/vue.js.map +0 -1
package/README.md CHANGED
@@ -1,190 +1,242 @@
1
1
  # NativeScript for Web
2
2
 
3
- Read this in Chinese: [README_ZH.md](README_ZH.md)
3
+ 中文: [README_ZH.md](README_ZH.md)
4
4
 
5
- Map common NativeScript UI tags to browser-ready Vue 3 components, enabling the same SFC to run on both Native and Web. Inspired by React Native for Web.
5
+ [![npm](https://img.shields.io/npm/v/nativescript-web-adapter.svg?logo=npm&label=npm)](https://www.npmjs.com/package/nativescript-web-adapter)
6
+ [![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
7
 
7
- Ideal for quickly previewing, debugging, or shipping a simplified web version of existing NativeScript Vue projects.
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
- ## Features
10
+ ---
10
11
 
11
- - Same tag names: use `Page`, `ActionBar`, `GridLayout`, `Label`, `Button`, `ImageCacheIt` directly in SFCs.
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
- ## Installation
14
+ Temporarily add the local bin to `PATH` to make the `ns-web` command available:
17
15
 
18
- Install the adapter from npm:
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
- ## Quick Start (Web)
20
+ Compile the web template – install dependencies – start the web project's dev server:
31
21
 
32
- 1) Register the plugin
22
+ Web
33
23
 
34
- ```ts
35
- // src/web/main.ts
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
- 3) Start the dev server
28
+ iOS
70
29
 
71
30
  ```bash
72
- npm run dev:web
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
- ## Component Mapping
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
- - Child placement & span
140
- - Indexing: NativeScript is 0-based; CSS Grid is 1-based. `row=0` → `grid-row-start:1`, `col=0` → `grid-column-start:1`
141
- - Default column: if `col`/`column` is not set, we enforce `grid-column-start:1` to avoid implicit multi-column side-by-side layout
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
- - Keep NativeScript tag parity to reduce migration and dual-edge maintenance costs.
185
- - Use standard web layout primitives (Flex/Grid) for debuggability and extensibility.
186
- - Clear mapping rules: 0→1 index conversion, `*`/`auto`/px track parsing, and alignment/span mapping — minimal surprises.
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
- ## Feedback & Improvements
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
- Try it in your projects and share feedback. If you need more components or refined behaviors (absolute positioning, interactions), add implementations under `src/vue/components` following the existing pattern, and submit a PR.
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
+ <div class="ns-absolute"><slot /></div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ defineOptions({ name: 'AbsoluteLayout' });
7
+ </script>
8
+
9
+ <style scoped>
10
+ .ns-absolute { position: relative; width: 100%; height: 100%; }
11
+ </style>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <header class="ns-actionbar"><slot /></header>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ defineOptions({ name: 'ActionBar' });
7
+ </script>
8
+
9
+ <style scoped>
10
+ .ns-actionbar { background: #1f1140; color: white; padding: 12px; }
11
+ </style>
@@ -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,11 @@
1
+ <template>
2
+ <div class="ns-flex"><slot /></div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ defineOptions({ name: 'FlexboxLayout' });
7
+ </script>
8
+
9
+ <style scoped>
10
+ .ns-flex { display: flex; }
11
+ </style>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <div class="ns-frame"><slot /></div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ defineOptions({ name: 'Frame' });
7
+ </script>
8
+
9
+ <style scoped>
10
+ /* .ns-frame { } */
11
+ </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>