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.
Files changed (75) hide show
  1. package/README.md +39 -17
  2. package/angular/components/absolute-layout.component.ts +18 -0
  3. package/angular/components/action-bar.component.ts +16 -0
  4. package/angular/components/action-item.component.ts +22 -0
  5. package/angular/components/activity-indicator.component.ts +32 -0
  6. package/angular/components/button.component.ts +45 -0
  7. package/angular/components/date-picker.component.ts +35 -0
  8. package/angular/components/directives/absolute-position.directive.ts +31 -0
  9. package/angular/components/directives/grid-position.directive.ts +43 -0
  10. package/angular/components/dock-layout.component.ts +46 -0
  11. package/angular/components/flexbox-layout.component.ts +16 -0
  12. package/angular/components/frame.component.ts +9 -0
  13. package/angular/components/grid-layout.component.ts +48 -0
  14. package/angular/components/html-view.component.ts +23 -0
  15. package/angular/components/image-cache-it.component.ts +21 -0
  16. package/angular/components/image.component.ts +22 -0
  17. package/angular/components/index.ts +156 -0
  18. package/angular/components/label.component.ts +27 -0
  19. package/angular/components/list-picker.component.ts +33 -0
  20. package/angular/components/list-view.component.ts +22 -0
  21. package/angular/components/navigation-button.component.ts +36 -0
  22. package/angular/components/page.component.ts +21 -0
  23. package/angular/components/placeholder.component.ts +16 -0
  24. package/angular/components/progress.component.ts +20 -0
  25. package/angular/components/root-layout.component.ts +19 -0
  26. package/angular/components/scroll-view.component.ts +17 -0
  27. package/angular/components/search-bar.component.ts +34 -0
  28. package/angular/components/segmented-bar-item.component.ts +27 -0
  29. package/angular/components/segmented-bar.component.ts +101 -0
  30. package/angular/components/slider.component.ts +41 -0
  31. package/angular/components/stack-layout.component.ts +17 -0
  32. package/angular/components/switch.component.ts +62 -0
  33. package/angular/components/tab-view-item.component.ts +27 -0
  34. package/angular/components/tab-view.component.ts +89 -0
  35. package/angular/components/text-field.component.ts +38 -0
  36. package/angular/components/text-view.component.ts +29 -0
  37. package/angular/components/time-picker.component.ts +27 -0
  38. package/angular/components/web-view.component.ts +25 -0
  39. package/angular/components/wrap-layout.component.ts +17 -0
  40. package/angular/composables/dialogs.ts +54 -0
  41. package/angular/composables/index.ts +6 -0
  42. package/angular/composables/ref.ts +8 -0
  43. package/angular/composables/useActionBar.ts +20 -0
  44. package/angular/composables/useFrame.ts +26 -0
  45. package/angular/composables/usePage.ts +26 -0
  46. package/angular/index.ts +8 -0
  47. package/core/components/ActionBar.vue +1 -1
  48. package/core/components/Button.vue +2 -2
  49. package/core/components/GridLayout.vue +2 -2
  50. package/core/components/HtmlView.vue +2 -2
  51. package/core/components/Image.vue +2 -2
  52. package/core/components/ImageCacheIt.vue +2 -2
  53. package/core/components/Label.vue +2 -2
  54. package/core/components/ListPicker.vue +2 -2
  55. package/core/components/NavigationButton.vue +2 -2
  56. package/core/components/Progress.vue +2 -2
  57. package/core/components/SearchBar.vue +2 -2
  58. package/core/components/Slider.vue +2 -2
  59. package/core/components/Switch.vue +2 -2
  60. package/core/components/TextField.vue +2 -2
  61. package/core/components/TextView.vue +2 -2
  62. package/core/components/WebView.vue +2 -2
  63. package/core/composables/dialogs.ts +5 -5
  64. package/dist/nativescript-web-adapter.es.js +22375 -45
  65. package/dist/nativescript-web-adapter.umd.js +534 -1
  66. package/dist/style.css +1 -1
  67. package/package.json +37 -7
  68. package/tools/cli.cjs +34 -7
  69. package/tools/create-nuxt-platform.cjs +57 -0
  70. package/tools/create-web-platform.cjs +38 -16
  71. package/tools/modules/appPatch.cjs +125 -1
  72. package/tools/modules/templates-nuxt.cjs +63 -0
  73. package/tools/modules/templates.cjs +161 -84
  74. package/tools/modules/transform-nuxt.cjs +59 -0
  75. package/tools/modules/transform.cjs +69 -2
package/README.md CHANGED
@@ -5,24 +5,40 @@
5
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
6
  [![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
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
+ 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
- Temporarily add the local bin to `PATH` to make the `ns-web` command available:
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
- export PATH="$PWD/node_modules/.bin:$PATH"
23
+ npx ns-web vue
18
24
  ```
19
25
 
20
- Compile the web template – install dependencies – start the web project's dev server:
26
+ angular (build)
27
+
28
+ ```bash
29
+ npx ns-web angular
30
+ ```
21
31
 
22
- Web
32
+ auto (build, detects framework from the project)
23
33
 
24
34
  ```bash
25
- ns-web dev
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 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.
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 Vue Project)
116
+ ## Web Side (Generated Web Project)
101
117
 
102
- - Output location: `platforms/web/`
118
+ - Output location:
119
+ - Vue: `platforms/vue/`
120
+ - Angular: `platforms/angular/`
103
121
  - Example structure:
104
122
 
105
123
  ```
106
- platforms/web/
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
- - `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.
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
- - Independent Vite (`platforms/web/vite.config.ts`) runs on port `3005`, using standard Vue HMR and route refresh.
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 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.
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
+