tiddy 0.0.1

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.
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div class="layout-item" v-bind="comProps">
3
+ <SlotNest v-if="slot" v-bind="slot" :ctx-key="formCtxKey" />
4
+ <FormField
5
+ v-for="field in fields"
6
+ :key="getKey(field)"
7
+ v-bind="field"
8
+ :fullProp="`${fullProp || ''}.${field.prop}`"
9
+ />
10
+ </div>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { computed, inject, useAttrs } from 'vue';
15
+ import { getKey } from '../utils';
16
+ import FormField from './form-field.vue';
17
+ import type { LayoutFieldProps, FormContext } from './interface.d';
18
+ import { formCtxKey } from './util';
19
+ import SlotNest from '../slot-nest/slot-nest.vue';
20
+ import { cut, ensureArray } from 'yatter';
21
+
22
+ const props = defineProps<LayoutFieldProps>();
23
+ const formCtx = inject<FormContext>(formCtxKey)!;
24
+ const attrs = useAttrs();
25
+
26
+ const comProps = computed<Record<string, any>>(() => {
27
+ return Object.assign({}, attrs, cut(props, ['fields']));
28
+ });
29
+
30
+ const slot = formCtx.getParentSlots(ensureArray(props.slots))[0];
31
+ </script>
32
+
33
+ <style lang="scss" scoped></style>
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <FormItem v-bind="labelProps" name-space="layout-form-item">
3
+ <FormField v-for="field in subFields" :key="getKey(field)" v-bind="field" />
4
+ </FormItem>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed, useAttrs } from 'vue';
9
+ import { getKey } from '../utils';
10
+ import type { FieldProps, ObjectFieldProps } from './interface';
11
+ import FormField from './form-field.vue';
12
+ import FormItem from './form-item.vue';
13
+ import { pick } from 'yatter';
14
+
15
+ const props = defineProps<ObjectFieldProps>();
16
+
17
+ const labelProps = computed<any>(() => pick(props, [/^label/]));
18
+
19
+ const subFields = computed(() => {
20
+ return props.fields.map(field => {
21
+ const fullProp = computed(() => [props.fullProp, field.prop].filter(Boolean).join('.'));
22
+ return {
23
+ ...field,
24
+ fullProp: fullProp.value,
25
+ };
26
+ });
27
+ });
28
+ </script>
29
+
30
+ <style lang="scss" scoped></style>
@@ -0,0 +1,4 @@
1
+ import type { InjectionKey } from 'vue';
2
+ import type { FormContext } from './interface';
3
+
4
+ export const formCtxKey: InjectionKey<FormContext> = Symbol('formCtxKey');
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <FormItem ref="item" :label="label" :prop="fullProp as any" :rules="rules" v-bind="itemAttrs" class="widget-item">
3
+ <SlotNest
4
+ v-for="psc in preSlots"
5
+ :key="psc.name"
6
+ v-bind="psc"
7
+ :scope="{ value: widgetModel }"
8
+ :ctx-key="formCtxKey"
9
+ ></SlotNest>
10
+ <Widget ref="widget" v-bind="widgetAttrs" v-on="on" v-model="widgetModel" class="widget-comp">
11
+ <template v-for="sc in widgetSlots" :key="sc.name" #[sc.name]="scope">
12
+ <SlotNest v-bind="sc" :scope="scope" :ctx-key="formCtxKey"></SlotNest>
13
+ </template>
14
+ </Widget>
15
+ <SlotNest
16
+ v-for="ssc in sufSlots"
17
+ :key="ssc.name"
18
+ v-bind="ssc"
19
+ :scope="{ value: widgetModel }"
20
+ :ctx-key="formCtxKey"
21
+ ></SlotNest>
22
+ </FormItem>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { computed, inject, useAttrs, useTemplateRef, type ComputedRef } from 'vue';
27
+ import FormItem from './form-item.vue';
28
+ import type { FormContext, WidgetFieldProps } from './interface';
29
+ import { formCtxKey } from './util';
30
+ import { resolveWidget } from './widget';
31
+ import SlotNest from '../slot-nest/slot-nest.vue';
32
+ import { getDeepValue, pick, setDeepValue, toCamelCase, ensureArray, isNullOrUndef } from 'yatter';
33
+
34
+ const props = withDefaults(defineProps<WidgetFieldProps>(), {
35
+ on: () => ({}),
36
+ slots: () => [],
37
+ prependSlots: () => [],
38
+ appendSlots: () => [],
39
+ default: null,
40
+ });
41
+
42
+ const attrs = useAttrs();
43
+ const formCtx = inject<FormContext>(formCtxKey)!;
44
+
45
+ const itemRef = useTemplateRef('item');
46
+ const widgetRef = useTemplateRef('widget');
47
+
48
+ const widgetAttrs = computed(() => {
49
+ const attr4Widget = pick(attrs, [{ from: /^widget/, to: k => toCamelCase(k.slice(6)) }]);
50
+ return { ...attr4Widget, ...props.widget };
51
+ });
52
+
53
+ const itemAttrs = computed(() => {
54
+ const attr4Item = pick(attrs, [{ from: /^item/, to: k => toCamelCase(k.slice(4)) }]);
55
+ const attr = { ...attr4Item, ...props.item, ...pick(props, ['rules', /^label/]) };
56
+ return attr;
57
+ });
58
+
59
+ const Widget = resolveWidget(props.component);
60
+
61
+ const widgetModel = computed({
62
+ get() {
63
+ return getDeepValue(formCtx.model, props.fullProp!);
64
+ },
65
+ set(v) {
66
+ setDeepValue(formCtx.model, props.fullProp!, v);
67
+ },
68
+ });
69
+
70
+ if (!isNullOrUndef(props.default)) {
71
+ widgetModel.value = props.default;
72
+ }
73
+
74
+ const widgetSlots = formCtx.getParentSlots(ensureArray(props.slots));
75
+ const preSlots = formCtx.getParentSlots(ensureArray(props.prependSlots));
76
+ const sufSlots = formCtx.getParentSlots(ensureArray(props.appendSlots));
77
+ </script>
78
+
79
+ <style lang="scss" scoped>
80
+ .widget-item {
81
+ :deep(.el-form-item__content) {
82
+ display: flex;
83
+ .widget-comp {
84
+ flex: 1;
85
+ }
86
+ }
87
+ }
88
+ </style>
@@ -0,0 +1,28 @@
1
+ import { Fragment, type Component } from 'vue';
2
+ import { isString } from 'yatter';
3
+
4
+ const componens: { [k: string]: Component } = {};
5
+
6
+ export function registerWidget(comp: Component): void;
7
+ export function registerWidget(name: string, comp: Component): void;
8
+ export function registerWidget(name: string | Component, comp?: Component) {
9
+ let key = '';
10
+ let component: OrUndef<Component>;
11
+ if (isString(name)) {
12
+ key = name;
13
+ component = comp!;
14
+ } else if (comp) {
15
+ key = comp.name!;
16
+ component = name;
17
+ }
18
+
19
+ if (!key || !component || componens[key]) {
20
+ return;
21
+ }
22
+
23
+ componens[key] = component;
24
+ }
25
+
26
+ export function resolveWidget(name: string | Component) {
27
+ return isString(name) ? (componens[name] ?? Fragment) : name;
28
+ }
@@ -0,0 +1 @@
1
+ export * from './slot-nest.vue';
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <component v-if="comp" :is="comp" v-bind="comProps">
3
+ <template v-for="sc in subSlots" :key="sc.name" #[sc.name]="scope">
4
+ <SlotNest v-bind="sc" :scope="scope" :ctx-key="ctxKey"></SlotNest>
5
+ </template>
6
+ <template v-if="text">{{ getText(scope) }}</template>
7
+ </component>
8
+ <template v-else-if="text">{{ getText(scope) }}</template>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { inject, computed, type InjectionKey, type Component, type Slot } from 'vue';
13
+ import { ensureArray } from 'yatter';
14
+ import type { FormContext } from '../form/interface.d';
15
+ import type { TableContext } from '../table/interface.d';
16
+
17
+ defineOptions({
18
+ name: 'SlotNest',
19
+ });
20
+
21
+ export interface SlotNestProps {
22
+ name: string;
23
+ component?: Component | Slot;
24
+ props?: (scope?: any) => Record<string, any>;
25
+ text?: OrFunction<string>;
26
+ slots?: SlotDef[] | string;
27
+ isSlot?: boolean;
28
+
29
+ ctxKey: InjectionKey<FormContext | TableContext>;
30
+ scope?: any;
31
+ }
32
+
33
+ const props = defineProps<SlotNestProps>();
34
+
35
+ const getProps = computed(() => {
36
+ return props.props ?? ((p: any) => p || {});
37
+ });
38
+
39
+ const comProps = computed(() => getProps.value(props.scope));
40
+
41
+ const parentCtx = inject<FormContext | TableContext>(props.ctxKey)!;
42
+
43
+ const comp = props.component;
44
+
45
+ const subSlots = parentCtx.getParentSlots(ensureArray(props.slots));
46
+
47
+ const getText = typeof props.text === 'string' ? (...args: any) => props.text : props.text!;
48
+ </script>
49
+
50
+ <style lang="scss" scoped></style>
@@ -0,0 +1 @@
1
+ export * from './table.vue';
@@ -0,0 +1,3 @@
1
+ export interface TableContext {
2
+ getParentSlots: GetSlotsFunction;
3
+ }
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <ElTableColumn v-bind="$attrs">
3
+ <template v-if="columns.length">
4
+ <TableCol v-for="col in columns" :key="col.label" v-bind="col" />
5
+ </template>
6
+ <template v-for="sc in columnSlots" :key="sc.name" #[sc.name]="scope">
7
+ <SlotNest v-bind="sc" :scope="scope" :ctx-key="tableCtxKey"></SlotNest>
8
+ </template>
9
+ <template v-if="transform.length" #default="scope">
10
+ {{ filtered(scope) }}
11
+ </template>
12
+ </ElTableColumn>
13
+ </template>
14
+
15
+ <script setup lang="ts" name="TableCol">
16
+ import { ElTableColumn } from 'element-plus';
17
+ import type { SeTableColumnProps } from './table.vue';
18
+ import { getDeepValue, ensureArray } from 'yatter';
19
+ import { inject } from 'vue';
20
+ import { tableCtxKey } from './util';
21
+ import type { TableContext } from './interface';
22
+ import SlotNest from '../slot-nest/slot-nest.vue';
23
+
24
+ type TableCol = Pick<SeTableColumnProps, 'slots' | 'transform' | 'columns'>;
25
+
26
+ const props = withDefaults(defineProps<TableCol>(), {
27
+ slots: () => [],
28
+ transform: () => [],
29
+ columns: () => [],
30
+ });
31
+
32
+ function filtered(scope: any) {
33
+ const { row, column } = scope;
34
+ return props.transform.reduce<any>((res, filter) => filter(res), getDeepValue(row, column.property));
35
+ }
36
+
37
+ const tableCtx = inject<TableContext>(tableCtxKey)!;
38
+
39
+ const columnSlots = tableCtx.getParentSlots(ensureArray(props.slots));
40
+ </script>
41
+
42
+ <style lang="scss" scoped></style>
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <slot name="prepend"></slot>
3
+ <ElTable :data="data" v-bind="$attrs">
4
+ <TableCol v-for="col in columns" :key="col.label" v-bind="col" />
5
+ </ElTable>
6
+ <slot name="append"></slot>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { ElTable, type ElTableColumn } from 'element-plus';
11
+ import TableCol from './table-col.vue';
12
+ import { provide, useSlots } from 'vue';
13
+ import { getSlotsFactory } from '../utils';
14
+ import { tableCtxKey } from './util';
15
+
16
+ defineOptions({
17
+ name: 'SeTable',
18
+ });
19
+
20
+ type TableColumnProps = InstanceType<typeof ElTableColumn>['$props'];
21
+
22
+ export interface SeTableColumnProps extends /* @vue-ignore */ TableColumnProps {
23
+ slots?: OneOrMore<SlotDef>;
24
+ columns?: SeTableColumnProps[];
25
+ transform?: AnyFunction[];
26
+ }
27
+
28
+ export interface SeTableProps<T = unknown> {
29
+ data: T[];
30
+ columns: SeTableColumnProps[];
31
+ }
32
+
33
+ defineProps<SeTableProps>();
34
+
35
+ const slots = useSlots();
36
+
37
+ provide(tableCtxKey, {
38
+ getParentSlots: getSlotsFactory(slots),
39
+ });
40
+ </script>
41
+
42
+ <style lang="scss" scoped></style>
@@ -0,0 +1,4 @@
1
+ import type { InjectionKey } from 'vue';
2
+ import type { TableContext } from './interface';
3
+
4
+ export const tableCtxKey: InjectionKey<TableContext> = Symbol('tableCtxKey');
@@ -0,0 +1,50 @@
1
+ import { type Slots } from 'vue';
2
+ import { isNumber, isObject, isString, uid } from 'yatter';
3
+
4
+ export function getSlotsFactory(slots: Slots) {
5
+ return (names: SlotDef[]) =>
6
+ names.flatMap(name => {
7
+ if (typeof name === 'string') {
8
+ const slot = slots[name];
9
+ if (slot) {
10
+ return [
11
+ {
12
+ name: name.replace(/\w+?_/, ''),
13
+ component: slot,
14
+ slot: true,
15
+ },
16
+ ];
17
+ }
18
+ return [];
19
+ }
20
+ return [name];
21
+ });
22
+ }
23
+
24
+ export function isStringNumber(input: unknown) {
25
+ if (!isString(input)) {
26
+ return false;
27
+ }
28
+ return !Number.isNaN(Number(input));
29
+ }
30
+
31
+ export function addUnit(value?: string | number, defaultUnit = 'px') {
32
+ if (!value) return '';
33
+ if (isNumber(value) || isStringNumber(value)) {
34
+ return `${value}${defaultUnit}`;
35
+ }
36
+ if (isString(value)) {
37
+ return value;
38
+ }
39
+ }
40
+
41
+ const keys = new WeakMap<WeakKey, string>();
42
+ export function getKey(obj: any): string {
43
+ if (!isObject(obj)) {
44
+ return String(obj);
45
+ }
46
+ if (!keys.has(obj)) {
47
+ keys.set(obj, uid());
48
+ }
49
+ return keys.get(obj)!;
50
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { AllowedComponentProps, Component, Raw, Slot } from 'vue';
2
+
3
+ declare global {
4
+ type OneOrMore<T> = T | T[];
5
+ type AnyFunction<T extends unknown[] = unknown[], R = unknown> = (...args: T) => R;
6
+
7
+ type OrNull<T> = T | null;
8
+ type OrUndef<T> = T | undefined;
9
+ type OrNone<T> = OrNull<T> | OrUndef<T>;
10
+ type OrFunction<T> = T | ((...args: any[]) => T);
11
+
12
+ type PropertyKey = string | number | symbol;
13
+ type TestFunction = (...args: unknown[]) => boolean;
14
+
15
+ interface ComponentSlot {
16
+ name: string;
17
+ component?: Component | Slot;
18
+ props?: (scope?: any) => Record<string, any>;
19
+ text?: OrFunction<string>;
20
+ slots?: SlotDef[] | string;
21
+ isSlot?: boolean;
22
+ }
23
+ type SlotDef = ComponentSlot | string;
24
+
25
+ type GetSlotsFunction = (names: SlotDef[]) => ComponentSlot[];
26
+
27
+ type SlotNestProps = SlotNest & ComponentSlot;
28
+ }