nativescript-web-adapter 0.1.2 → 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 +218 -246
- 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 +35 -49
- 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 -377
- package/dist/index.cjs.map +0 -1
- package/dist/index.css +0 -172
- package/dist/index.js +0 -361
- 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/index.types.d.ts +0 -1
- package/dist/types/vue/components/ActionBar.d.ts +0 -5
- package/dist/types/vue/components/ActionItem.d.ts +0 -15
- package/dist/types/vue/components/Button.d.ts +0 -26
- package/dist/types/vue/components/ContentView.d.ts +0 -3
- 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 -26
- 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/ListView.d.ts +0 -24
- package/dist/types/vue/components/Page.d.ts +0 -5
- package/dist/types/vue/components/StackLayout.d.ts +0 -5
- package/dist/types/vue/components/TabView.d.ts +0 -26
- package/dist/types/vue/components/TabViewItem.d.ts +0 -24
- package/dist/types/vue/index.d.ts +0 -18
- package/dist/types/vue/index.types.d.ts +0 -17
- package/dist/types/vue.d.ts +0 -266
- package/dist/vue.cjs +0 -377
- package/dist/vue.cjs.map +0 -1
- package/dist/vue.css +0 -172
- package/dist/vue.js +0 -361
- package/dist/vue.js.map +0 -1
|
@@ -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>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-htmlview" v-if="html" v-html="html"></div>
|
|
3
|
+
<div class="ns-htmlview" v-else><slot /></div>
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script setup lang="ts">
|
|
7
|
+
defineOptions({ name: 'HtmlView' });
|
|
8
|
+
const props = defineProps<{ html?: string }>();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<style scoped>
|
|
12
|
+
.ns-htmlview { display: block; }
|
|
13
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<img class="ns-image" :src="src" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
defineOptions({ name: 'Image' });
|
|
7
|
+
const props = defineProps<{ src: string; stretch?: string }>();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<style scoped>
|
|
11
|
+
.ns-image { display: block; max-width: 100%; height: auto; }
|
|
12
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<img class="ns-imagecacheit" :src="src" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
defineOptions({ name: 'ImageCacheIt' });
|
|
7
|
+
const props = defineProps<{ src: string }>();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<style scoped>
|
|
11
|
+
.ns-imagecacheit { display: block; max-width: 100%; height: auto; }
|
|
12
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span class="ns-label">
|
|
3
|
+
<slot v-if="!text" />
|
|
4
|
+
<template v-else>{{ text }}</template>
|
|
5
|
+
</span>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
defineOptions({ name: 'Label' });
|
|
10
|
+
const props = defineProps<{ text?: string }>();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<style scoped>
|
|
14
|
+
.ns-label { display: inline-block; }
|
|
15
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<select class="ns-listpicker" :value="selectedIndex" @change="onChange">
|
|
3
|
+
<option v-for="(item, i) in items" :key="i" :value="i">{{ item }}</option>
|
|
4
|
+
</select>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
defineOptions({ name: 'ListPicker' });
|
|
9
|
+
const props = defineProps<{ items: string[]; selectedIndex?: number }>();
|
|
10
|
+
const emit = defineEmits<{ (e: 'update:selectedIndex', v: number): void; (e: 'change', v: number): void }>();
|
|
11
|
+
|
|
12
|
+
function onChange(e: Event) {
|
|
13
|
+
const v = Number((e.target as HTMLSelectElement).value);
|
|
14
|
+
emit('update:selectedIndex', v);
|
|
15
|
+
emit('change', v);
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style scoped>
|
|
20
|
+
.ns-listpicker { padding: 6px 8px; }
|
|
21
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ul class="ns-listview"><slot /></ul>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
defineOptions({ name: 'ListView' });
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<style scoped>
|
|
10
|
+
.ns-listview { list-style: none; margin: 0; padding: 0; }
|
|
11
|
+
.ns-listview > li { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.1); }
|
|
12
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button class="ns-navbutton" @click="onClick">
|
|
3
|
+
<slot>{{ text }}</slot>
|
|
4
|
+
</button>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { getCurrentInstance } from 'vue';
|
|
9
|
+
const props = defineProps<{ text?: string }>();
|
|
10
|
+
const emit = defineEmits<{ (e: 'tap', evt: MouseEvent): void }>();
|
|
11
|
+
defineOptions({ name: 'NavigationButton' });
|
|
12
|
+
|
|
13
|
+
function onClick(e: MouseEvent) {
|
|
14
|
+
emit('tap', e);
|
|
15
|
+
// 默认行为:若未监听 tap,则尝试返回上一页
|
|
16
|
+
const inst = getCurrentInstance();
|
|
17
|
+
const hasTapListener = !!(inst?.vnode?.props && (inst.vnode.props as any).onTap);
|
|
18
|
+
if (!hasTapListener) {
|
|
19
|
+
const router = (inst?.appContext?.config?.globalProperties as any)?.$router;
|
|
20
|
+
if (router?.go) router.go(-1);
|
|
21
|
+
else if (window?.history?.back) window.history.back();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<style scoped>
|
|
27
|
+
.ns-navbutton { background: transparent; border: none; color: inherit; cursor: pointer; padding: 8px; }
|
|
28
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-page"><slot /></div>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
defineOptions({ name: 'Page' });
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<style scoped>
|
|
10
|
+
.ns-page {
|
|
11
|
+
top: 0;
|
|
12
|
+
left: 0;
|
|
13
|
+
position: absolute;
|
|
14
|
+
width: 100%;
|
|
15
|
+
min-height: 100%;
|
|
16
|
+
padding: 0px;
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<progress class="ns-progress" :value="value" :max="max"></progress>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
defineOptions({ name: 'Progress' });
|
|
7
|
+
const props = defineProps<{ value?: number; max?: number }>();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<style scoped>
|
|
11
|
+
.ns-progress { width: 100%; }
|
|
12
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-searchbar">
|
|
3
|
+
<input type="search" :value="text" @input="onInput" @keyup.enter="onSubmit" />
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
defineOptions({ name: 'SearchBar' });
|
|
10
|
+
const props = defineProps<{ text?: string }>();
|
|
11
|
+
const emit = defineEmits<{ (e: 'update:text', v: string): void; (e: 'submit', v: string): void }>();
|
|
12
|
+
function onInput(e: Event) {
|
|
13
|
+
emit('update:text', (e.target as HTMLInputElement).value);
|
|
14
|
+
}
|
|
15
|
+
function onSubmit(e: KeyboardEvent) {
|
|
16
|
+
emit('submit', (e.target as HTMLInputElement).value);
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<style scoped>
|
|
21
|
+
.ns-searchbar input { width: 100%; padding: 6px 8px; }
|
|
22
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-segmentedbar">
|
|
3
|
+
<div class="ns-segmentedbar-header">
|
|
4
|
+
<button
|
|
5
|
+
v-for="(t, i) in displayItems"
|
|
6
|
+
:key="i"
|
|
7
|
+
class="ns-segment"
|
|
8
|
+
:class="{ active: i === currentIndex }"
|
|
9
|
+
@click="select(i)"
|
|
10
|
+
>{{ t }}</button>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="ns-segmentedbar-body">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { ref, watch, computed, provide } from 'vue';
|
|
21
|
+
defineOptions({ name: 'SegmentedBar' });
|
|
22
|
+
const props = defineProps<{ items?: string[]; selectedIndex?: number }>();
|
|
23
|
+
const emit = defineEmits<{ (e: 'update:selectedIndex', v: number): void; (e: 'change', v: number): void }>();
|
|
24
|
+
|
|
25
|
+
// Selected index
|
|
26
|
+
const currentIndex = ref(props.selectedIndex ?? 0);
|
|
27
|
+
watch(() => props.selectedIndex, v => { if (typeof v === 'number') currentIndex.value = v; });
|
|
28
|
+
function select(i: number) { currentIndex.value = i; emit('update:selectedIndex', i); emit('change', i); }
|
|
29
|
+
|
|
30
|
+
// Child registration for SegmentedBarItem titles when using child components
|
|
31
|
+
const registeredTitles = ref<string[]>([]);
|
|
32
|
+
function register(title: string): number {
|
|
33
|
+
registeredTitles.value.push(title);
|
|
34
|
+
return registeredTitles.value.length - 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Provide to children so they can register and know selectedIndex
|
|
38
|
+
provide('ns-segmentedbar', { register, selectedIndex: currentIndex });
|
|
39
|
+
|
|
40
|
+
// Items to display: prefer props.items if provided; else use registered child titles
|
|
41
|
+
const displayItems = computed(() => (props.items && props.items.length ? props.items : registeredTitles.value));
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<style scoped>
|
|
45
|
+
.ns-segmentedbar { display: block; color: inherit; }
|
|
46
|
+
.ns-segmentedbar-header { display: flex; gap: 8px; border-bottom: 1px solid rgba(255,255,255,0.3); padding-bottom: 8px; }
|
|
47
|
+
.ns-segment { background: transparent; border: none; color: #fff; padding: 8px 12px; cursor: pointer; opacity: 0.95; }
|
|
48
|
+
.ns-segment.active { opacity: 1; border-bottom: 2px solid currentColor; }
|
|
49
|
+
.ns-segmentedbar-body { padding-top: 12px; }
|
|
50
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-segmentedbar-item" v-show="isActive"><slot /></div>
|
|
3
|
+
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script setup lang="ts">
|
|
7
|
+
import { inject, onMounted, ref, computed } from 'vue';
|
|
8
|
+
defineOptions({ name: 'SegmentedBarItem' });
|
|
9
|
+
const props = defineProps<{ title: string }>();
|
|
10
|
+
const sb = inject<{ register: (t: string) => number; selectedIndex: any } | undefined>('ns-segmentedbar', undefined);
|
|
11
|
+
const index = ref<number>(-1);
|
|
12
|
+
onMounted(() => { index.value = sb ? sb.register(props.title) : -1; });
|
|
13
|
+
const isActive = computed(() => {
|
|
14
|
+
if (!sb || index.value < 0) return true;
|
|
15
|
+
return sb.selectedIndex.value === index.value;
|
|
16
|
+
});
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style scoped>
|
|
20
|
+
.ns-segmentedbar-item { display: block; }
|
|
21
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input class="ns-slider" type="range" :min="min" :max="max" :step="step" :value="value" @input="onInput" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
defineOptions({ name: 'Slider' });
|
|
7
|
+
const props = defineProps<{ value?: number; min?: number; max?: number; step?: number }>();
|
|
8
|
+
const emit = defineEmits<{ (e: 'update:value', v: number): void; (e: 'change', v: number): void }>();
|
|
9
|
+
function onInput(e: Event) {
|
|
10
|
+
const v = Number((e.target as HTMLInputElement).value);
|
|
11
|
+
emit('update:value', v);
|
|
12
|
+
emit('change', v);
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<style scoped>
|
|
17
|
+
.ns-slider { width: 100%; }
|
|
18
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<label class="ns-switch">
|
|
3
|
+
<input type="checkbox" :checked="checked" @change="onChange" />
|
|
4
|
+
<span class="track"><span class="thumb"></span></span>
|
|
5
|
+
</label>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
defineOptions({ name: 'Switch' });
|
|
10
|
+
const props = defineProps<{ checked?: boolean }>();
|
|
11
|
+
const emit = defineEmits<{ (e: 'update:checked', v: boolean): void; (e: 'change', v: boolean): void }>();
|
|
12
|
+
function onChange(e: Event) {
|
|
13
|
+
const v = (e.target as HTMLInputElement).checked;
|
|
14
|
+
emit('update:checked', v);
|
|
15
|
+
emit('change', v);
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style scoped>
|
|
20
|
+
.ns-switch { display: inline-flex; align-items: center; cursor: pointer; }
|
|
21
|
+
.ns-switch input { display: none; }
|
|
22
|
+
.ns-switch .track { width: 40px; height: 22px; background: rgba(255,255,255,0.3); border-radius: 11px; position: relative; transition: background .2s; }
|
|
23
|
+
.ns-switch .thumb { width: 18px; height: 18px; background: rgb(0, 255, 174); border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: left .2s; }
|
|
24
|
+
.ns-switch input:checked + .track { background: currentColor; opacity: .5; }
|
|
25
|
+
.ns-switch input:checked + .track .thumb { left: 20px; }
|
|
26
|
+
</style>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-tabview">
|
|
3
|
+
<div class="ns-tabview-header">
|
|
4
|
+
<button
|
|
5
|
+
v-for="(title, i) in items"
|
|
6
|
+
:key="i"
|
|
7
|
+
class="ns-tabview-tab"
|
|
8
|
+
:class="{ active: i === currentIndex }"
|
|
9
|
+
@click="select(i)"
|
|
10
|
+
>{{ title }}</button>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="ns-tabview-body">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { ref, provide, watch } from 'vue';
|
|
20
|
+
|
|
21
|
+
defineOptions({ name: 'TabView' });
|
|
22
|
+
|
|
23
|
+
const props = defineProps<{ selectedIndex?: number }>();
|
|
24
|
+
const currentIndex = ref(props.selectedIndex ?? 0);
|
|
25
|
+
watch(() => props.selectedIndex, (v) => {
|
|
26
|
+
if (typeof v === 'number') currentIndex.value = v;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const items = ref<string[]>([]);
|
|
30
|
+
function register(title: string) {
|
|
31
|
+
const idx = items.value.length;
|
|
32
|
+
items.value.push(title);
|
|
33
|
+
return idx;
|
|
34
|
+
}
|
|
35
|
+
function select(i: number) {
|
|
36
|
+
currentIndex.value = i;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
provide('ns-tabview', { register, selectedIndex: currentIndex });
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<style scoped>
|
|
43
|
+
.ns-tabview { display: block; }
|
|
44
|
+
.ns-tabview-header { display: flex; gap: 8px; border-bottom: 1px solid rgba(255,255,255,0.2); padding-bottom: 8px; }
|
|
45
|
+
.ns-tabview-tab { background: transparent; border: none; color: inherit; padding: 8px 12px; cursor: pointer; opacity: 0.7; }
|
|
46
|
+
.ns-tabview-tab.active { opacity: 1; border-bottom: 2px solid currentColor; }
|
|
47
|
+
.ns-tabview-body { padding-top: 12px; }
|
|
48
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ns-tabview-item" v-show="isActive">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { inject, onMounted, ref, computed } from 'vue';
|
|
10
|
+
|
|
11
|
+
defineOptions({ name: 'TabViewItem' });
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{ title: string }>();
|
|
14
|
+
const tv = inject<{ register: (t: string) => number; selectedIndex: any } | undefined>('ns-tabview', undefined);
|
|
15
|
+
const index = ref<number>(-1);
|
|
16
|
+
onMounted(() => {
|
|
17
|
+
index.value = tv ? tv.register(props.title) : -1;
|
|
18
|
+
});
|
|
19
|
+
const isActive = computed(() => {
|
|
20
|
+
if (!tv || index.value < 0) return true;
|
|
21
|
+
return tv.selectedIndex.value === index.value;
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style scoped>
|
|
26
|
+
.ns-tabview-item { display: block; }
|
|
27
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input class="ns-textfield" type="text" :value="text" @input="onInput" @keyup.enter="onSubmit" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
defineOptions({ name: 'TextField' });
|
|
7
|
+
const props = defineProps<{ text?: string }>();
|
|
8
|
+
const emit = defineEmits<{ (e: 'update:text', v: string): void; (e: 'submit', v: string): void }>();
|
|
9
|
+
function onInput(e: Event) { emit('update:text', (e.target as HTMLInputElement).value); }
|
|
10
|
+
function onSubmit(e: KeyboardEvent) { emit('submit', (e.target as HTMLInputElement).value); }
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<style scoped>
|
|
14
|
+
.ns-textfield { width: 100%; padding: 6px 8px; }
|
|
15
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<textarea class="ns-textview" :value="text" @input="onInput"></textarea>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
defineOptions({ name: 'TextView' });
|
|
7
|
+
const props = defineProps<{ text?: string }>();
|
|
8
|
+
const emit = defineEmits<{ (e: 'update:text', v: string): void; (e: 'change', v: string): void }>();
|
|
9
|
+
function onInput(e: Event) {
|
|
10
|
+
const v = (e.target as HTMLTextAreaElement).value;
|
|
11
|
+
emit('update:text', v);
|
|
12
|
+
emit('change', v);
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<style scoped>
|
|
17
|
+
.ns-textview { width: 100%; min-height: 80px; padding: 6px 8px; }
|
|
18
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input class="ns-timepicker" type="time" :value="valueStr" @input="onInput" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import { computed } from 'vue';
|
|
7
|
+
defineOptions({ name: 'TimePicker' });
|
|
8
|
+
const props = defineProps<{ time?: string }>();
|
|
9
|
+
const emit = defineEmits<{ (e: 'update:time', v: string): void; (e: 'change', v: string): void }>();
|
|
10
|
+
|
|
11
|
+
const valueStr = computed(() => props.time ?? '');
|
|
12
|
+
function onInput(e: Event) {
|
|
13
|
+
const v = (e.target as HTMLInputElement).value;
|
|
14
|
+
emit('update:time', v);
|
|
15
|
+
emit('change', v);
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style scoped>
|
|
20
|
+
.ns-timepicker { padding: 6px 8px; }
|
|
21
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<iframe class="ns-webview" :src="src" v-if="src"></iframe>
|
|
3
|
+
<div class="ns-webview" v-else><slot /></div>
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script setup lang="ts">
|
|
7
|
+
defineOptions({ name: 'WebView' });
|
|
8
|
+
const props = defineProps<{ src?: string }>();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<style scoped>
|
|
12
|
+
.ns-webview { width: 100%; height: 100%; border: none; }
|
|
13
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export * from './ActionBar.vue';
|
|
2
|
+
export * from './Page.vue';
|
|
3
|
+
export * from './Frame.vue';
|
|
4
|
+
export * from './StackLayout.vue';
|
|
5
|
+
export * from './GridLayout.vue';
|
|
6
|
+
export * from './FlexboxLayout.vue';
|
|
7
|
+
export * from './WrapLayout.vue';
|
|
8
|
+
export * from './ScrollView.vue';
|
|
9
|
+
export * from './Label.vue';
|
|
10
|
+
export * from './Button.vue';
|
|
11
|
+
export * from './Image.vue';
|
|
12
|
+
export * from './HtmlView.vue';
|
|
13
|
+
export * from './ImageCacheIt.vue';
|
|
14
|
+
export * from './TabView.vue';
|
|
15
|
+
export * from './TabViewItem.vue';
|
|
16
|
+
export * from './RootLayout.vue';
|
|
17
|
+
export * from './DockLayout.vue';
|
|
18
|
+
export * from './AbsoluteLayout.vue';
|
|
19
|
+
export * from './ActionItem.vue';
|
|
20
|
+
export * from './NavigationButton.vue';
|
|
21
|
+
export * from './ActivityIndicator.vue';
|
|
22
|
+
export * from './DatePicker.vue';
|
|
23
|
+
export * from './TimePicker.vue';
|
|
24
|
+
export * from './ListPicker.vue';
|
|
25
|
+
export * from './ListView.vue';
|
|
26
|
+
export * from './Placeholder.vue';
|
|
27
|
+
export * from './Progress.vue';
|
|
28
|
+
export * from './SearchBar.vue';
|
|
29
|
+
export * from './SegmentedBar.vue';
|
|
30
|
+
export * from './SegmentedBarItem.vue';
|
|
31
|
+
export * from './Slider.vue';
|
|
32
|
+
export * from './Switch.vue';
|
|
33
|
+
export * from './TextField.vue';
|
|
34
|
+
export * from './TextView.vue';
|
|
35
|
+
export * from './WebView.vue';
|