nativescript-web-adapter 0.1.3 → 0.1.6
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 +39 -17
- package/angular/components/absolute-layout.component.ts +18 -0
- package/angular/components/action-bar.component.ts +16 -0
- package/angular/components/action-item.component.ts +22 -0
- package/angular/components/activity-indicator.component.ts +32 -0
- package/angular/components/button.component.ts +45 -0
- package/angular/components/date-picker.component.ts +35 -0
- package/angular/components/directives/absolute-position.directive.ts +31 -0
- package/angular/components/directives/grid-position.directive.ts +43 -0
- package/angular/components/dock-layout.component.ts +46 -0
- package/angular/components/flexbox-layout.component.ts +16 -0
- package/angular/components/frame.component.ts +9 -0
- package/angular/components/grid-layout.component.ts +48 -0
- package/angular/components/html-view.component.ts +23 -0
- package/angular/components/image-cache-it.component.ts +21 -0
- package/angular/components/image.component.ts +22 -0
- package/angular/components/index.ts +156 -0
- package/angular/components/label.component.ts +27 -0
- package/angular/components/list-picker.component.ts +33 -0
- package/angular/components/list-view.component.ts +22 -0
- package/angular/components/navigation-button.component.ts +36 -0
- package/angular/components/page.component.ts +21 -0
- package/angular/components/placeholder.component.ts +16 -0
- package/angular/components/progress.component.ts +20 -0
- package/angular/components/root-layout.component.ts +19 -0
- package/angular/components/scroll-view.component.ts +17 -0
- package/angular/components/search-bar.component.ts +34 -0
- package/angular/components/segmented-bar-item.component.ts +27 -0
- package/angular/components/segmented-bar.component.ts +101 -0
- package/angular/components/slider.component.ts +41 -0
- package/angular/components/stack-layout.component.ts +17 -0
- package/angular/components/switch.component.ts +62 -0
- package/angular/components/tab-view-item.component.ts +27 -0
- package/angular/components/tab-view.component.ts +89 -0
- package/angular/components/text-field.component.ts +38 -0
- package/angular/components/text-view.component.ts +29 -0
- package/angular/components/time-picker.component.ts +27 -0
- package/angular/components/web-view.component.ts +25 -0
- package/angular/components/wrap-layout.component.ts +17 -0
- package/angular/composables/dialogs.ts +54 -0
- package/angular/composables/index.ts +6 -0
- package/angular/composables/ref.ts +8 -0
- package/angular/composables/useActionBar.ts +20 -0
- package/angular/composables/useFrame.ts +26 -0
- package/angular/composables/usePage.ts +26 -0
- package/angular/index.ts +8 -0
- package/core/components/ActionBar.vue +1 -1
- package/core/components/Button.vue +2 -2
- package/core/components/GridLayout.vue +2 -2
- package/core/components/HtmlView.vue +2 -2
- package/core/components/Image.vue +2 -2
- package/core/components/ImageCacheIt.vue +2 -2
- package/core/components/Label.vue +2 -2
- package/core/components/ListPicker.vue +2 -2
- package/core/components/NavigationButton.vue +2 -2
- package/core/components/Progress.vue +2 -2
- package/core/components/SearchBar.vue +2 -2
- package/core/components/Slider.vue +2 -2
- package/core/components/Switch.vue +2 -2
- package/core/components/TextField.vue +2 -2
- package/core/components/TextView.vue +2 -2
- package/core/components/WebView.vue +2 -2
- package/core/composables/dialogs.ts +5 -5
- package/dist/nativescript-web-adapter.es.js +22375 -45
- package/dist/nativescript-web-adapter.umd.js +534 -1
- package/dist/style.css +1 -1
- package/package.json +37 -7
- package/tools/cli.cjs +34 -7
- package/tools/create-nuxt-platform.cjs +57 -0
- package/tools/create-web-platform.cjs +38 -16
- package/tools/modules/appPatch.cjs +125 -1
- package/tools/modules/templates-nuxt.cjs +63 -0
- package/tools/modules/templates.cjs +161 -84
- package/tools/modules/transform-nuxt.cjs +59 -0
- package/tools/modules/transform.cjs +69 -2
package/README.md
CHANGED
|
@@ -5,24 +5,40 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/nativescript-web-adapter)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
This project is a “web adapter” for NativeScript
|
|
8
|
+
This project is a “web adapter” for NativeScript apps. It can transform native app code into a Web project that runs in the browser (generated under `platforms/<framework>/`).
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## Quick Start
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
```bash
|
|
15
|
+
npm install nativescript-web-adapter
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Generate the web template – install dependencies – run the web project:
|
|
19
|
+
|
|
20
|
+
vue (dev server)
|
|
15
21
|
|
|
16
22
|
```bash
|
|
17
|
-
|
|
23
|
+
npx ns-web vue
|
|
18
24
|
```
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
angular (build)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx ns-web angular
|
|
30
|
+
```
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
auto (build, detects framework from the project)
|
|
23
33
|
|
|
24
34
|
```bash
|
|
25
|
-
ns-web
|
|
35
|
+
npx ns-web build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
nuxt
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx ns-web nuxt
|
|
26
42
|
```
|
|
27
43
|
|
|
28
44
|
iOS
|
|
@@ -57,8 +73,8 @@ ns run android
|
|
|
57
73
|
## Background & Goals
|
|
58
74
|
|
|
59
75
|
- 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
|
|
61
|
-
- The generated Web project lives in `platforms
|
|
76
|
+
- Provide a “web adapter” (local package `nativescript-web-adapter`) that scans and transforms the project's `src/` code to generate a Web application that runs in the browser (Vue or Angular), enabling quick preview and collaboration on desktop browsers.
|
|
77
|
+
- The generated Web project lives in `platforms/<framework>/` with its own Vite config, dependencies, and entry files, without affecting the native side.
|
|
62
78
|
|
|
63
79
|
---
|
|
64
80
|
|
|
@@ -97,13 +113,15 @@ ns run android
|
|
|
97
113
|
|
|
98
114
|
---
|
|
99
115
|
|
|
100
|
-
## Web Side (Generated
|
|
116
|
+
## Web Side (Generated Web Project)
|
|
101
117
|
|
|
102
|
-
- Output location:
|
|
118
|
+
- Output location:
|
|
119
|
+
- Vue: `platforms/vue/`
|
|
120
|
+
- Angular: `platforms/angular/`
|
|
103
121
|
- Example structure:
|
|
104
122
|
|
|
105
123
|
```
|
|
106
|
-
platforms/
|
|
124
|
+
platforms/vue/
|
|
107
125
|
package.json # generated (includes vue + vue-router)
|
|
108
126
|
vite.config.ts # generated (server.port = 3005, strictPort = true)
|
|
109
127
|
index.html
|
|
@@ -121,6 +139,8 @@ platforms/web/
|
|
|
121
139
|
websfc/ # adapter composables (useActionBar/usePage/useFrame)
|
|
122
140
|
```
|
|
123
141
|
|
|
142
|
+
Angular platform uses `platforms/angular/` and keeps the same `src/components/websfc` + `src/composables/websfc` structure, but the entry is `src/main.ts` and the HTML root is `<ns-app></ns-app>`.
|
|
143
|
+
|
|
124
144
|
- 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
145
|
|
|
126
146
|
---
|
|
@@ -148,8 +168,9 @@ platforms/web/
|
|
|
148
168
|
- `npm run dev:android`: run Vite and NS Android debug in parallel.
|
|
149
169
|
- `npm run ios` / `npm run android`: use `ns debug` for debugging builds.
|
|
150
170
|
- Generate and run the Web project:
|
|
151
|
-
-
|
|
152
|
-
-
|
|
171
|
+
- Vue dev server: `npx ns-web vue`
|
|
172
|
+
- Angular build: `npx ns-web angular`
|
|
173
|
+
- Auto build (detects framework): `npx ns-web build`
|
|
153
174
|
|
|
154
175
|
---
|
|
155
176
|
|
|
@@ -213,7 +234,8 @@ The generator’s `transformContent()` currently performs only necessary code‑
|
|
|
213
234
|
- Uses `@nativescript/vite` with `vite serve -- --env.hmr` to establish an HMR channel, pushing changes to the device.
|
|
214
235
|
- `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
236
|
- Web side:
|
|
216
|
-
-
|
|
237
|
+
- Vue Vite (`platforms/vue/vite.config.ts`) runs on port `3005`.
|
|
238
|
+
- Angular Vite (`platforms/angular/vite.config.ts`) runs on port `3006`.
|
|
217
239
|
|
|
218
240
|
---
|
|
219
241
|
|
|
@@ -225,8 +247,8 @@ The generator’s `transformContent()` currently performs only necessary code‑
|
|
|
225
247
|
- 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
248
|
- Regex‑driven transformation:
|
|
227
249
|
- Edge cases may exist for complex code and templates. Gradually moving to AST‑level transformation is recommended.
|
|
228
|
-
- Generator
|
|
229
|
-
- The generator
|
|
250
|
+
- Generator templates:
|
|
251
|
+
- The generator writes framework-specific Vite templates and copies adapter components/composables into `src/components/websfc` and `src/composables/websfc`.
|
|
230
252
|
|
|
231
253
|
---
|
|
232
254
|
|
|
@@ -239,4 +261,4 @@ The generator’s `transformContent()` currently performs only necessary code‑
|
|
|
239
261
|
3. Tests & validation:
|
|
240
262
|
- Add unit tests and e2e tests for transformation rules and template generation to ensure stability across different project structures.
|
|
241
263
|
|
|
242
|
-
---
|
|
264
|
+
---
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'AbsoluteLayout, absolutelayout',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<div class="ns-absolute"><ng-content></ng-content></div>`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-absolute {
|
|
11
|
+
position: relative;
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: 100%;
|
|
14
|
+
}
|
|
15
|
+
`,
|
|
16
|
+
],
|
|
17
|
+
})
|
|
18
|
+
export class AbsoluteLayoutComponent {}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'ActionBar, actionbar',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<header class="ns-actionbar"><ng-content></ng-content></header>`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-actionbar {
|
|
11
|
+
padding: 12px;
|
|
12
|
+
}
|
|
13
|
+
`,
|
|
14
|
+
],
|
|
15
|
+
})
|
|
16
|
+
export class ActionBarComponent {}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'ActionItem, actionitem',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<button class="ns-actionitem" (click)="tap.emit($event)"><ng-content></ng-content></button>`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-actionitem {
|
|
11
|
+
background: transparent;
|
|
12
|
+
border: none;
|
|
13
|
+
color: inherit;
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
padding: 8px;
|
|
16
|
+
}
|
|
17
|
+
`,
|
|
18
|
+
],
|
|
19
|
+
})
|
|
20
|
+
export class ActionItemComponent {
|
|
21
|
+
@Output() tap = new EventEmitter<MouseEvent>();
|
|
22
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'ActivityIndicator, activityindicator',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<div class="ns-activity-indicator" [class.busy]="busy"></div>`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-activity-indicator {
|
|
11
|
+
width: 20px;
|
|
12
|
+
height: 20px;
|
|
13
|
+
border-radius: 50%;
|
|
14
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
15
|
+
border-top-color: currentColor;
|
|
16
|
+
opacity: 0.8;
|
|
17
|
+
}
|
|
18
|
+
.ns-activity-indicator.busy {
|
|
19
|
+
animation: ns-spin 0.8s linear infinite;
|
|
20
|
+
}
|
|
21
|
+
@keyframes ns-spin {
|
|
22
|
+
to {
|
|
23
|
+
transform: rotate(360deg);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`,
|
|
27
|
+
],
|
|
28
|
+
})
|
|
29
|
+
export class ActivityIndicatorComponent {
|
|
30
|
+
@Input() busy = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'Button, button[nsButton], ns-button',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<button class="ns-button" [ngStyle]="btnStyle" (click)="tap.emit($event)"><ng-content></ng-content></button>`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-button {
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
}
|
|
13
|
+
`,
|
|
14
|
+
],
|
|
15
|
+
})
|
|
16
|
+
export class ButtonComponent {
|
|
17
|
+
@Input() horizontalAlignment?: string;
|
|
18
|
+
@Output() tap = new EventEmitter<MouseEvent>();
|
|
19
|
+
|
|
20
|
+
get btnStyle(): Record<string, string> {
|
|
21
|
+
const style: Record<string, string> = {};
|
|
22
|
+
const h = this.horizontalAlignment?.toLowerCase();
|
|
23
|
+
if (h === 'center') {
|
|
24
|
+
style.display = 'block';
|
|
25
|
+
style.width = 'fit-content';
|
|
26
|
+
style.marginLeft = 'auto';
|
|
27
|
+
style.marginRight = 'auto';
|
|
28
|
+
style.alignSelf = 'center';
|
|
29
|
+
style.justifySelf = 'center';
|
|
30
|
+
} else if (h === 'right') {
|
|
31
|
+
style.display = 'block';
|
|
32
|
+
style.width = 'fit-content';
|
|
33
|
+
style.marginLeft = 'auto';
|
|
34
|
+
style.alignSelf = 'flex-end';
|
|
35
|
+
style.justifySelf = 'end';
|
|
36
|
+
} else if (h === 'left') {
|
|
37
|
+
style.display = 'block';
|
|
38
|
+
style.width = 'fit-content';
|
|
39
|
+
style.marginRight = 'auto';
|
|
40
|
+
style.alignSelf = 'flex-start';
|
|
41
|
+
style.justifySelf = 'start';
|
|
42
|
+
}
|
|
43
|
+
return style;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'DatePicker, datepicker',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<input class="ns-datepicker" type="date" [value]="valueStr" (input)="onInput($event)" />`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-datepicker {
|
|
11
|
+
padding: 6px 8px;
|
|
12
|
+
}
|
|
13
|
+
`,
|
|
14
|
+
],
|
|
15
|
+
})
|
|
16
|
+
export class DatePickerComponent {
|
|
17
|
+
@Input() date?: string | Date;
|
|
18
|
+
@Output() dateChange = new EventEmitter<string | Date>();
|
|
19
|
+
@Output() change = new EventEmitter<string | Date>();
|
|
20
|
+
|
|
21
|
+
get valueStr(): string {
|
|
22
|
+
if (!this.date) return '';
|
|
23
|
+
const d = typeof this.date === 'string' ? new Date(this.date) : this.date;
|
|
24
|
+
if (Number.isNaN(d.getTime())) return '';
|
|
25
|
+
const pad = (n: number) => (n < 10 ? `0${n}` : String(n));
|
|
26
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onInput(e: Event) {
|
|
30
|
+
const v = (e.target as HTMLInputElement).value;
|
|
31
|
+
this.dateChange.emit(v);
|
|
32
|
+
this.change.emit(v);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Directive, ElementRef, Input, OnChanges, Renderer2 } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Directive({
|
|
4
|
+
selector: '[left],[top]',
|
|
5
|
+
standalone: true,
|
|
6
|
+
})
|
|
7
|
+
export class NsAbsolutePositionDirective implements OnChanges {
|
|
8
|
+
@Input() left?: number | string;
|
|
9
|
+
@Input() top?: number | string;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly el: ElementRef<HTMLElement>,
|
|
13
|
+
private readonly renderer: Renderer2
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
ngOnChanges(): void {
|
|
17
|
+
const host = this.el.nativeElement;
|
|
18
|
+
this.renderer.setStyle(host, 'position', 'absolute');
|
|
19
|
+
if (this.left !== undefined) this.renderer.setStyle(host, 'left', this.toCssPx(this.left));
|
|
20
|
+
if (this.top !== undefined) this.renderer.setStyle(host, 'top', this.toCssPx(this.top));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private toCssPx(v: number | string): string {
|
|
24
|
+
if (typeof v === 'number') return `${v}px`;
|
|
25
|
+
const t = v.trim();
|
|
26
|
+
if (!t) return '0px';
|
|
27
|
+
if (/^-?\d+(\.\d+)?$/.test(t)) return `${t}px`;
|
|
28
|
+
return t;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Directive, ElementRef, Input, OnChanges, Renderer2 } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Directive({
|
|
4
|
+
selector: '[row],[col],[rowSpan],[colSpan]',
|
|
5
|
+
standalone: true,
|
|
6
|
+
})
|
|
7
|
+
export class NsGridPositionDirective implements OnChanges {
|
|
8
|
+
@Input() row?: number | string;
|
|
9
|
+
@Input() col?: number | string;
|
|
10
|
+
@Input() rowSpan?: number | string;
|
|
11
|
+
@Input() colSpan?: number | string;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly el: ElementRef<HTMLElement>,
|
|
15
|
+
private readonly renderer: Renderer2
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
ngOnChanges(): void {
|
|
19
|
+
const host = this.el.nativeElement;
|
|
20
|
+
const row = this.toNumberOrUndefined(this.row);
|
|
21
|
+
const col = this.toNumberOrUndefined(this.col);
|
|
22
|
+
const rowSpan = this.toNumberOrUndefined(this.rowSpan);
|
|
23
|
+
const colSpan = this.toNumberOrUndefined(this.colSpan);
|
|
24
|
+
|
|
25
|
+
if (row !== undefined) {
|
|
26
|
+
const start = row + 1;
|
|
27
|
+
const span = rowSpan && rowSpan > 0 ? rowSpan : 1;
|
|
28
|
+
this.renderer.setStyle(host, 'grid-row', `${start} / span ${span}`);
|
|
29
|
+
}
|
|
30
|
+
if (col !== undefined) {
|
|
31
|
+
const start = col + 1;
|
|
32
|
+
const span = colSpan && colSpan > 0 ? colSpan : 1;
|
|
33
|
+
this.renderer.setStyle(host, 'grid-column', `${start} / span ${span}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private toNumberOrUndefined(v: number | string | undefined): number | undefined {
|
|
38
|
+
if (v === undefined || v === null) return undefined;
|
|
39
|
+
const n = typeof v === 'number' ? v : Number(v);
|
|
40
|
+
return Number.isFinite(n) ? n : undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'DockLayout, docklayout',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `
|
|
8
|
+
<div class="ns-dock">
|
|
9
|
+
<div class="ns-dock-top"><ng-content select="[dock='top'],[dock='Top'],[dock='TOP']"></ng-content></div>
|
|
10
|
+
<div class="ns-dock-middle">
|
|
11
|
+
<div class="ns-dock-left"><ng-content select="[dock='left'],[dock='Left'],[dock='LEFT']"></ng-content></div>
|
|
12
|
+
<div class="ns-dock-center"><ng-content></ng-content></div>
|
|
13
|
+
<div class="ns-dock-right"><ng-content select="[dock='right'],[dock='Right'],[dock='RIGHT']"></ng-content></div>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="ns-dock-bottom"><ng-content select="[dock='bottom'],[dock='Bottom'],[dock='BOTTOM']"></ng-content></div>
|
|
16
|
+
</div>
|
|
17
|
+
`,
|
|
18
|
+
styles: [
|
|
19
|
+
`
|
|
20
|
+
.ns-dock {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
}
|
|
26
|
+
.ns-dock-top,
|
|
27
|
+
.ns-dock-bottom {
|
|
28
|
+
flex: 0 0 auto;
|
|
29
|
+
}
|
|
30
|
+
.ns-dock-middle {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex: 1 1 auto;
|
|
33
|
+
min-height: 0;
|
|
34
|
+
}
|
|
35
|
+
.ns-dock-left,
|
|
36
|
+
.ns-dock-right {
|
|
37
|
+
flex: 0 0 auto;
|
|
38
|
+
}
|
|
39
|
+
.ns-dock-center {
|
|
40
|
+
flex: 1 1 auto;
|
|
41
|
+
min-width: 0;
|
|
42
|
+
}
|
|
43
|
+
`,
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
export class DockLayoutComponent {}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'FlexboxLayout, flexboxlayout',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<div class="ns-flex"><ng-content></ng-content></div>`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-flex {
|
|
11
|
+
display: flex;
|
|
12
|
+
}
|
|
13
|
+
`,
|
|
14
|
+
],
|
|
15
|
+
})
|
|
16
|
+
export class FlexboxLayoutComponent {}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'Frame, frame',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<div class="ns-frame"><ng-content></ng-content></div>`,
|
|
8
|
+
})
|
|
9
|
+
export class FrameComponent {}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'GridLayout, gridlayout',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<div class="ns-grid" [ngStyle]="gridStyle"><ng-content></ng-content></div>`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-grid {
|
|
11
|
+
padding: 0px;
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: 100%;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
min-height: 0;
|
|
17
|
+
}
|
|
18
|
+
`,
|
|
19
|
+
],
|
|
20
|
+
})
|
|
21
|
+
export class GridLayoutComponent {
|
|
22
|
+
@Input() rows?: string;
|
|
23
|
+
@Input() columns?: string;
|
|
24
|
+
|
|
25
|
+
get gridStyle(): Record<string, string> {
|
|
26
|
+
const style: Record<string, string> = { display: 'grid' };
|
|
27
|
+
const rows = this.parseSegments(this.rows);
|
|
28
|
+
const cols = this.parseSegments(this.columns);
|
|
29
|
+
if (rows) style.gridTemplateRows = rows;
|
|
30
|
+
if (cols) style.gridTemplateColumns = cols;
|
|
31
|
+
return style;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private parseSegments(input?: string): string | undefined {
|
|
35
|
+
if (!input) return undefined;
|
|
36
|
+
const segToCss = (s: string) => {
|
|
37
|
+
const t = s.trim();
|
|
38
|
+
if (!t) return 'auto';
|
|
39
|
+
if (t === '*') return '1fr';
|
|
40
|
+
const star = t.match(/^(\d+)\*$/);
|
|
41
|
+
if (star) return `${star[1]}fr`;
|
|
42
|
+
if (t.toLowerCase() === 'auto') return 'auto';
|
|
43
|
+
if (/^\d+$/.test(t)) return `${t}px`;
|
|
44
|
+
return t;
|
|
45
|
+
};
|
|
46
|
+
return input.split(',').map(segToCss).join(' ');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'HtmlView, htmlview',
|
|
6
|
+
standalone: true,
|
|
7
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8
|
+
imports: [CommonModule],
|
|
9
|
+
template: `
|
|
10
|
+
<div class="ns-htmlview" *ngIf="html; else projected" [innerHTML]="html"></div>
|
|
11
|
+
<ng-template #projected><div class="ns-htmlview"><ng-content></ng-content></div></ng-template>
|
|
12
|
+
`,
|
|
13
|
+
styles: [
|
|
14
|
+
`
|
|
15
|
+
.ns-htmlview {
|
|
16
|
+
display: block;
|
|
17
|
+
}
|
|
18
|
+
`,
|
|
19
|
+
],
|
|
20
|
+
})
|
|
21
|
+
export class HtmlViewComponent {
|
|
22
|
+
@Input() html?: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'ImageCacheIt, imagecacheit',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<img class="ns-imagecacheit" [src]="src" />`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-imagecacheit {
|
|
11
|
+
display: block;
|
|
12
|
+
max-width: 100%;
|
|
13
|
+
height: auto;
|
|
14
|
+
}
|
|
15
|
+
`,
|
|
16
|
+
],
|
|
17
|
+
})
|
|
18
|
+
export class ImageCacheItComponent {
|
|
19
|
+
@Input({ required: true }) src!: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'Image, image',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `<img class="ns-image" [src]="src" />`,
|
|
8
|
+
styles: [
|
|
9
|
+
`
|
|
10
|
+
.ns-image {
|
|
11
|
+
display: block;
|
|
12
|
+
max-width: 100%;
|
|
13
|
+
height: auto;
|
|
14
|
+
}
|
|
15
|
+
`,
|
|
16
|
+
],
|
|
17
|
+
})
|
|
18
|
+
export class ImageComponent {
|
|
19
|
+
@Input({ required: true }) src!: string;
|
|
20
|
+
@Input() stretch?: string;
|
|
21
|
+
}
|
|
22
|
+
|