@svadmin/lite 0.6.0 → 0.7.0
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 +85 -9
- package/dist/components/LiteDynamicFormList.svelte +169 -0
- package/dist/components/LiteDynamicFormList.svelte.d.ts +45 -0
- package/dist/components/LiteFilterBuilder.svelte +205 -0
- package/dist/components/LiteFilterBuilder.svelte.d.ts +19 -0
- package/dist/components/LiteShowField.svelte +22 -0
- package/dist/components/LiteTransfer.svelte +185 -0
- package/dist/components/LiteTransfer.svelte.d.ts +18 -0
- package/dist/components/fields/LiteAvatarField.svelte +127 -0
- package/dist/components/fields/LiteAvatarField.svelte.d.ts +21 -0
- package/dist/components/fields/LiteCascader.svelte +119 -0
- package/dist/components/fields/LiteCascader.svelte.d.ts +24 -0
- package/dist/components/fields/LiteCodeField.svelte +71 -0
- package/dist/components/fields/LiteCodeField.svelte.d.ts +16 -0
- package/dist/components/fields/LiteCopyField.svelte +74 -0
- package/dist/components/fields/LiteCopyField.svelte.d.ts +18 -0
- package/dist/components/fields/LiteCurrencyField.svelte +126 -0
- package/dist/components/fields/LiteCurrencyField.svelte.d.ts +21 -0
- package/dist/components/fields/LiteDateRangeField.svelte +147 -0
- package/dist/components/fields/LiteDateRangeField.svelte.d.ts +26 -0
- package/dist/components/fields/LitePercentField.svelte +124 -0
- package/dist/components/fields/LitePercentField.svelte.d.ts +17 -0
- package/dist/components/fields/LitePhoneField.svelte +81 -0
- package/dist/components/fields/LitePhoneField.svelte.d.ts +17 -0
- package/dist/components/fields/LiteRatingField.svelte +105 -0
- package/dist/components/fields/LiteRatingField.svelte.d.ts +16 -0
- package/dist/components/fields/LiteTreeSelect.svelte +143 -0
- package/dist/components/fields/LiteTreeSelect.svelte.d.ts +23 -0
- package/dist/components/fields/index.d.ts +12 -0
- package/dist/components/fields/index.js +10 -0
- package/dist/components/pages/LiteListPage.svelte +14 -12
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/lite.css +205 -0
- package/dist/schema-generator.js +15 -4
- package/package.json +4 -3
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type LiteCurrencyTone = 'auto' | 'success' | 'warning' | 'destructive' | 'neutral';
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
field?: FieldDefinition;
|
|
10
|
+
value?: number | string | null;
|
|
11
|
+
currency?: string;
|
|
12
|
+
locale?: string;
|
|
13
|
+
precision?: number;
|
|
14
|
+
compact?: boolean;
|
|
15
|
+
symbol?: string;
|
|
16
|
+
colored?: boolean;
|
|
17
|
+
tone?: LiteCurrencyTone;
|
|
18
|
+
nullLabel?: string;
|
|
19
|
+
tabular?: boolean;
|
|
20
|
+
class?: string;
|
|
21
|
+
error?: string[];
|
|
22
|
+
mode?: 'show' | 'edit' | 'create';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
field,
|
|
27
|
+
value,
|
|
28
|
+
currency = 'USD',
|
|
29
|
+
locale = 'en-US',
|
|
30
|
+
precision = 2,
|
|
31
|
+
compact = false,
|
|
32
|
+
symbol,
|
|
33
|
+
colored = false,
|
|
34
|
+
tone = 'neutral',
|
|
35
|
+
nullLabel = '—',
|
|
36
|
+
tabular = true,
|
|
37
|
+
class: className = '',
|
|
38
|
+
error = [],
|
|
39
|
+
mode = 'show',
|
|
40
|
+
}: Props = $props();
|
|
41
|
+
|
|
42
|
+
const normalizedPrecision = $derived(
|
|
43
|
+
Number.isFinite(precision) ? Math.min(20, Math.max(0, Math.trunc(precision))) : 2
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const numericValue = $derived.by(() => {
|
|
47
|
+
if (value == null) return null;
|
|
48
|
+
if (typeof value === 'string' && value.trim() === '') return null;
|
|
49
|
+
const num = typeof value === 'number' ? value : Number(value);
|
|
50
|
+
return Number.isFinite(num) ? num : null;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const formatted = $derived.by(() => {
|
|
54
|
+
if (numericValue === null) return nullLabel;
|
|
55
|
+
|
|
56
|
+
const opts: Intl.NumberFormatOptions = {
|
|
57
|
+
style: symbol ? 'decimal' : 'currency',
|
|
58
|
+
currency: symbol ? undefined : currency,
|
|
59
|
+
minimumFractionDigits: normalizedPrecision,
|
|
60
|
+
maximumFractionDigits: normalizedPrecision,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (compact) {
|
|
64
|
+
opts.notation = 'compact';
|
|
65
|
+
opts.compactDisplay = 'short';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
let res = new Intl.NumberFormat(locale, opts).format(numericValue);
|
|
70
|
+
if (symbol) {
|
|
71
|
+
res = `${symbol}${res}`;
|
|
72
|
+
}
|
|
73
|
+
return res;
|
|
74
|
+
} catch {
|
|
75
|
+
return String(numericValue);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const resolvedTone = $derived.by(() => {
|
|
80
|
+
if (tone !== 'auto' && tone !== 'neutral') return tone;
|
|
81
|
+
if ((!colored && tone !== 'auto') || numericValue === null) return 'neutral';
|
|
82
|
+
if (numericValue > 0) return 'success';
|
|
83
|
+
if (numericValue < 0) return 'destructive';
|
|
84
|
+
return 'neutral';
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const toneClassMap: Record<LiteCurrencyTone, string> = {
|
|
88
|
+
auto: '',
|
|
89
|
+
success: 'lite-text-success',
|
|
90
|
+
warning: 'lite-text-warning',
|
|
91
|
+
destructive: 'lite-text-danger',
|
|
92
|
+
neutral: '',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const hasError = $derived(error.length > 0);
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
{#if mode === 'show'}
|
|
99
|
+
{#if numericValue === null}
|
|
100
|
+
<span class="lite-text-muted lite-text-sm {className}">{nullLabel}</span>
|
|
101
|
+
{:else}
|
|
102
|
+
<span
|
|
103
|
+
class="lite-currency-field {tabular ? 'lite-font-mono' : ''} {toneClassMap[resolvedTone]} {className}"
|
|
104
|
+
>
|
|
105
|
+
{formatted}
|
|
106
|
+
</span>
|
|
107
|
+
{/if}
|
|
108
|
+
{:else}
|
|
109
|
+
<div>
|
|
110
|
+
<input
|
|
111
|
+
type="number"
|
|
112
|
+
step="any"
|
|
113
|
+
name={field?.key ?? 'currency_amount'}
|
|
114
|
+
id={field?.key ?? 'currency_amount'}
|
|
115
|
+
value={numericValue ?? ''}
|
|
116
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
117
|
+
placeholder={field?.label ?? '0.00'}
|
|
118
|
+
{...field?.required ? { required: true } : {}}
|
|
119
|
+
/>
|
|
120
|
+
{#if hasError}
|
|
121
|
+
{#each error as err, _i (_i)}
|
|
122
|
+
<div class="lite-error-text">{err}</div>
|
|
123
|
+
{/each}
|
|
124
|
+
{/if}
|
|
125
|
+
</div>
|
|
126
|
+
{/if}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type LiteCurrencyTone = 'auto' | 'success' | 'warning' | 'destructive' | 'neutral';
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
interface Props {
|
|
4
|
+
field?: FieldDefinition;
|
|
5
|
+
value?: number | string | null;
|
|
6
|
+
currency?: string;
|
|
7
|
+
locale?: string;
|
|
8
|
+
precision?: number;
|
|
9
|
+
compact?: boolean;
|
|
10
|
+
symbol?: string;
|
|
11
|
+
colored?: boolean;
|
|
12
|
+
tone?: LiteCurrencyTone;
|
|
13
|
+
nullLabel?: string;
|
|
14
|
+
tabular?: boolean;
|
|
15
|
+
class?: string;
|
|
16
|
+
error?: string[];
|
|
17
|
+
mode?: 'show' | 'edit' | 'create';
|
|
18
|
+
}
|
|
19
|
+
declare const LiteCurrencyField: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type LiteCurrencyField = ReturnType<typeof LiteCurrencyField>;
|
|
21
|
+
export default LiteCurrencyField;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type LiteDateRangeFormat = 'date' | 'time' | 'datetime' | 'iso';
|
|
3
|
+
export type LiteDateRangeValue =
|
|
4
|
+
| [string | number | Date | null | undefined, string | number | Date | null | undefined]
|
|
5
|
+
| { start?: string | number | Date | null; end?: string | number | Date | null; from?: string | number | Date | null; to?: string | number | Date | null }
|
|
6
|
+
| null
|
|
7
|
+
| undefined;
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
field?: FieldDefinition;
|
|
15
|
+
value?: LiteDateRangeValue;
|
|
16
|
+
startDate?: string | number | Date | null;
|
|
17
|
+
endDate?: string | number | Date | null;
|
|
18
|
+
separator?: string;
|
|
19
|
+
locale?: string;
|
|
20
|
+
format?: LiteDateRangeFormat;
|
|
21
|
+
options?: Intl.DateTimeFormatOptions;
|
|
22
|
+
nullLabel?: string;
|
|
23
|
+
showIcon?: boolean;
|
|
24
|
+
class?: string;
|
|
25
|
+
error?: string[];
|
|
26
|
+
mode?: 'show' | 'edit' | 'create';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
field,
|
|
31
|
+
value,
|
|
32
|
+
startDate,
|
|
33
|
+
endDate,
|
|
34
|
+
separator = '~',
|
|
35
|
+
locale = 'en-US',
|
|
36
|
+
format = 'date',
|
|
37
|
+
options,
|
|
38
|
+
nullLabel = '—',
|
|
39
|
+
class: className = '',
|
|
40
|
+
error = [],
|
|
41
|
+
mode = 'show',
|
|
42
|
+
}: Props = $props();
|
|
43
|
+
|
|
44
|
+
const start = $derived.by(() => {
|
|
45
|
+
if (startDate !== undefined) return startDate;
|
|
46
|
+
if (Array.isArray(value)) return value[0];
|
|
47
|
+
if (value && typeof value === 'object') {
|
|
48
|
+
const obj = value as Record<string, unknown>;
|
|
49
|
+
return (obj.start ?? obj.from) as string | number | Date | null | undefined;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const end = $derived.by(() => {
|
|
55
|
+
if (endDate !== undefined) return endDate;
|
|
56
|
+
if (Array.isArray(value)) return value[1];
|
|
57
|
+
if (value && typeof value === 'object') {
|
|
58
|
+
const obj = value as Record<string, unknown>;
|
|
59
|
+
return (obj.end ?? obj.to) as string | number | Date | null | undefined;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
function formatDate(val: string | number | Date | null | undefined): string | null {
|
|
65
|
+
if (val == null || val === '') return null;
|
|
66
|
+
const d = val instanceof Date ? val : new Date(val);
|
|
67
|
+
if (isNaN(d.getTime())) return null;
|
|
68
|
+
|
|
69
|
+
if (format === 'iso') return d.toISOString();
|
|
70
|
+
|
|
71
|
+
const defaultOptions: Intl.DateTimeFormatOptions =
|
|
72
|
+
options ??
|
|
73
|
+
(format === 'datetime'
|
|
74
|
+
? { dateStyle: 'medium', timeStyle: 'short' }
|
|
75
|
+
: format === 'time'
|
|
76
|
+
? { timeStyle: 'medium' }
|
|
77
|
+
: { dateStyle: 'medium' });
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
return new Intl.DateTimeFormat(locale, defaultOptions).format(d);
|
|
81
|
+
} catch {
|
|
82
|
+
return d.toLocaleDateString();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toIsoDateInput(val: string | number | Date | null | undefined): string {
|
|
87
|
+
if (val == null || val === '') return '';
|
|
88
|
+
try {
|
|
89
|
+
const d = val instanceof Date ? val : new Date(val);
|
|
90
|
+
if (isNaN(d.getTime())) return '';
|
|
91
|
+
return d.toISOString().split('T')[0] ?? '';
|
|
92
|
+
} catch {
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const startFormatted = $derived(formatDate(start));
|
|
98
|
+
const endFormatted = $derived(formatDate(end));
|
|
99
|
+
|
|
100
|
+
const display = $derived.by(() => {
|
|
101
|
+
if (!startFormatted && !endFormatted) return nullLabel;
|
|
102
|
+
if (startFormatted && endFormatted) return `${startFormatted} ${separator} ${endFormatted}`;
|
|
103
|
+
if (startFormatted) return `${startFormatted} ${separator} ${nullLabel}`;
|
|
104
|
+
return `${nullLabel} ${separator} ${endFormatted}`;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const hasError = $derived(error.length > 0);
|
|
108
|
+
const startKey = $derived(field ? `${field.key}_start` : 'start_date');
|
|
109
|
+
const endKey = $derived(field ? `${field.key}_end` : 'end_date');
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
{#if mode === 'show'}
|
|
113
|
+
<span class="lite-date-range {className}">
|
|
114
|
+
<span>{display}</span>
|
|
115
|
+
</span>
|
|
116
|
+
{:else}
|
|
117
|
+
<div>
|
|
118
|
+
<div class="lite-inline-sm" style="display: flex; align-items: center;">
|
|
119
|
+
<input
|
|
120
|
+
type="date"
|
|
121
|
+
name={startKey}
|
|
122
|
+
id={startKey}
|
|
123
|
+
value={toIsoDateInput(start)}
|
|
124
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
125
|
+
style="flex: 1; min-width: 120px;"
|
|
126
|
+
placeholder="Start date"
|
|
127
|
+
{...field?.required ? { required: true } : {}}
|
|
128
|
+
/>
|
|
129
|
+
<span style="margin: 0 8px; color: #64748b;">{separator}</span>
|
|
130
|
+
<input
|
|
131
|
+
type="date"
|
|
132
|
+
name={endKey}
|
|
133
|
+
id={endKey}
|
|
134
|
+
value={toIsoDateInput(end)}
|
|
135
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
136
|
+
style="flex: 1; min-width: 120px;"
|
|
137
|
+
placeholder="End date"
|
|
138
|
+
{...field?.required ? { required: true } : {}}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
{#if hasError}
|
|
142
|
+
{#each error as err, _i (_i)}
|
|
143
|
+
<div class="lite-error-text">{err}</div>
|
|
144
|
+
{/each}
|
|
145
|
+
{/if}
|
|
146
|
+
</div>
|
|
147
|
+
{/if}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type LiteDateRangeFormat = 'date' | 'time' | 'datetime' | 'iso';
|
|
2
|
+
export type LiteDateRangeValue = [string | number | Date | null | undefined, string | number | Date | null | undefined] | {
|
|
3
|
+
start?: string | number | Date | null;
|
|
4
|
+
end?: string | number | Date | null;
|
|
5
|
+
from?: string | number | Date | null;
|
|
6
|
+
to?: string | number | Date | null;
|
|
7
|
+
} | null | undefined;
|
|
8
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
9
|
+
interface Props {
|
|
10
|
+
field?: FieldDefinition;
|
|
11
|
+
value?: LiteDateRangeValue;
|
|
12
|
+
startDate?: string | number | Date | null;
|
|
13
|
+
endDate?: string | number | Date | null;
|
|
14
|
+
separator?: string;
|
|
15
|
+
locale?: string;
|
|
16
|
+
format?: LiteDateRangeFormat;
|
|
17
|
+
options?: Intl.DateTimeFormatOptions;
|
|
18
|
+
nullLabel?: string;
|
|
19
|
+
showIcon?: boolean;
|
|
20
|
+
class?: string;
|
|
21
|
+
error?: string[];
|
|
22
|
+
mode?: 'show' | 'edit' | 'create';
|
|
23
|
+
}
|
|
24
|
+
declare const LiteDateRangeField: import("svelte").Component<Props, {}, "">;
|
|
25
|
+
type LiteDateRangeField = ReturnType<typeof LiteDateRangeField>;
|
|
26
|
+
export default LiteDateRangeField;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type LitePercentTone = 'auto' | 'success' | 'warning' | 'destructive' | 'info' | 'neutral';
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
field?: FieldDefinition;
|
|
10
|
+
value?: number | string | null;
|
|
11
|
+
precision?: number;
|
|
12
|
+
scale?: '100' | '1';
|
|
13
|
+
showProgress?: boolean;
|
|
14
|
+
tone?: LitePercentTone;
|
|
15
|
+
nullLabel?: string;
|
|
16
|
+
class?: string;
|
|
17
|
+
error?: string[];
|
|
18
|
+
mode?: 'show' | 'edit' | 'create';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
field,
|
|
23
|
+
value,
|
|
24
|
+
precision = 1,
|
|
25
|
+
scale = '100',
|
|
26
|
+
showProgress = false,
|
|
27
|
+
tone = 'neutral',
|
|
28
|
+
nullLabel = '—',
|
|
29
|
+
class: className = '',
|
|
30
|
+
error = [],
|
|
31
|
+
mode = 'show',
|
|
32
|
+
}: Props = $props();
|
|
33
|
+
|
|
34
|
+
const normalizedPrecision = $derived(
|
|
35
|
+
Math.min(20, Math.max(0, Math.trunc(Number.isFinite(precision) ? precision : 1)))
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const numericValue = $derived.by(() => {
|
|
39
|
+
if (value == null) return null;
|
|
40
|
+
if (typeof value === 'string' && value.trim() === '') return null;
|
|
41
|
+
const parsed = typeof value === 'number' ? value : Number(value);
|
|
42
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const percentNumber = $derived.by(() => {
|
|
46
|
+
if (numericValue === null) return null;
|
|
47
|
+
const scaled = scale === '1' ? numericValue * 100 : numericValue;
|
|
48
|
+
return Number.isFinite(scaled) ? scaled : null;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const formatted = $derived(
|
|
52
|
+
percentNumber === null ? nullLabel : `${percentNumber.toFixed(normalizedPrecision)}%`
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const resolvedTone = $derived.by((): LitePercentTone => {
|
|
56
|
+
if (tone !== 'auto') return tone;
|
|
57
|
+
if (percentNumber === null) return 'neutral';
|
|
58
|
+
if (percentNumber >= 80) return 'success';
|
|
59
|
+
if (percentNumber >= 50) return 'warning';
|
|
60
|
+
return 'destructive';
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const toneTextClassMap: Record<LitePercentTone, string> = {
|
|
64
|
+
auto: '',
|
|
65
|
+
success: 'lite-text-success',
|
|
66
|
+
warning: 'lite-text-warning',
|
|
67
|
+
destructive: 'lite-text-danger',
|
|
68
|
+
info: 'lite-text-info',
|
|
69
|
+
neutral: '',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const toneProgressClassMap: Record<LitePercentTone, string> = {
|
|
73
|
+
auto: '',
|
|
74
|
+
success: 'lite-progress-success',
|
|
75
|
+
warning: 'lite-progress-warning',
|
|
76
|
+
destructive: 'lite-progress-danger',
|
|
77
|
+
info: 'lite-progress-info',
|
|
78
|
+
neutral: '',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const clampedProgress = $derived(
|
|
82
|
+
percentNumber === null ? 0 : Math.min(100, Math.max(0, percentNumber))
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const hasError = $derived(error.length > 0);
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
{#if mode === 'show'}
|
|
89
|
+
{#if percentNumber === null}
|
|
90
|
+
<span class="lite-text-muted lite-text-sm {className}">{nullLabel}</span>
|
|
91
|
+
{:else}
|
|
92
|
+
<span class="lite-percent-container {className}">
|
|
93
|
+
<span class="lite-font-mono {toneTextClassMap[resolvedTone]}">
|
|
94
|
+
{formatted}
|
|
95
|
+
</span>
|
|
96
|
+
{#if showProgress}
|
|
97
|
+
<span class="lite-progress-track">
|
|
98
|
+
<span
|
|
99
|
+
class="lite-progress-fill {toneProgressClassMap[resolvedTone]}"
|
|
100
|
+
style="width: {clampedProgress}%;"
|
|
101
|
+
></span>
|
|
102
|
+
</span>
|
|
103
|
+
{/if}
|
|
104
|
+
</span>
|
|
105
|
+
{/if}
|
|
106
|
+
{:else}
|
|
107
|
+
<div>
|
|
108
|
+
<input
|
|
109
|
+
type="number"
|
|
110
|
+
step="any"
|
|
111
|
+
name={field?.key ?? 'percent'}
|
|
112
|
+
id={field?.key ?? 'percent'}
|
|
113
|
+
value={percentNumber ?? ''}
|
|
114
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
115
|
+
placeholder={field?.label ?? '0.0%'}
|
|
116
|
+
{...field?.required ? { required: true } : {}}
|
|
117
|
+
/>
|
|
118
|
+
{#if hasError}
|
|
119
|
+
{#each error as err, _i (_i)}
|
|
120
|
+
<div class="lite-error-text">{err}</div>
|
|
121
|
+
{/each}
|
|
122
|
+
{/if}
|
|
123
|
+
</div>
|
|
124
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type LitePercentTone = 'auto' | 'success' | 'warning' | 'destructive' | 'info' | 'neutral';
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
interface Props {
|
|
4
|
+
field?: FieldDefinition;
|
|
5
|
+
value?: number | string | null;
|
|
6
|
+
precision?: number;
|
|
7
|
+
scale?: '100' | '1';
|
|
8
|
+
showProgress?: boolean;
|
|
9
|
+
tone?: LitePercentTone;
|
|
10
|
+
nullLabel?: string;
|
|
11
|
+
class?: string;
|
|
12
|
+
error?: string[];
|
|
13
|
+
mode?: 'show' | 'edit' | 'create';
|
|
14
|
+
}
|
|
15
|
+
declare const LitePercentField: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type LitePercentField = ReturnType<typeof LitePercentField>;
|
|
17
|
+
export default LitePercentField;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
field?: FieldDefinition;
|
|
6
|
+
value?: string | number | null;
|
|
7
|
+
href?: string;
|
|
8
|
+
showIcon?: boolean;
|
|
9
|
+
clickable?: boolean;
|
|
10
|
+
copyable?: boolean;
|
|
11
|
+
nullLabel?: string;
|
|
12
|
+
oncopy?: (value: string) => void;
|
|
13
|
+
class?: string;
|
|
14
|
+
error?: string[];
|
|
15
|
+
mode?: 'show' | 'edit' | 'create';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
field,
|
|
20
|
+
value,
|
|
21
|
+
href,
|
|
22
|
+
showIcon = true,
|
|
23
|
+
clickable = true,
|
|
24
|
+
nullLabel = '—',
|
|
25
|
+
class: className = '',
|
|
26
|
+
error = [],
|
|
27
|
+
mode = 'show',
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
const stringValue = $derived.by(() => {
|
|
31
|
+
if (value == null || value === '') return null;
|
|
32
|
+
const str = String(value).trim();
|
|
33
|
+
return str.length > 0 ? str : null;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const telHref = $derived.by(() => {
|
|
37
|
+
if (!stringValue) return undefined;
|
|
38
|
+
const candidate = (href ?? stringValue).replace(/^tel:/i, '');
|
|
39
|
+
const normalized = candidate.replace(/[\s().-]/g, '');
|
|
40
|
+
if (!/^\+?\d+$/.test(normalized)) return undefined;
|
|
41
|
+
return `tel:${normalized}`;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const hasError = $derived(error.length > 0);
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
{#if mode === 'show'}
|
|
48
|
+
{#if !stringValue}
|
|
49
|
+
<span class="lite-text-muted lite-text-sm {className}">{nullLabel}</span>
|
|
50
|
+
{:else}
|
|
51
|
+
<span class="lite-phone-container {className}">
|
|
52
|
+
{#if showIcon}
|
|
53
|
+
<span class="lite-phone-icon">📞</span>
|
|
54
|
+
{/if}
|
|
55
|
+
{#if clickable && telHref}
|
|
56
|
+
<a href={telHref} class="lite-phone-link">
|
|
57
|
+
{stringValue}
|
|
58
|
+
</a>
|
|
59
|
+
{:else}
|
|
60
|
+
<span>{stringValue}</span>
|
|
61
|
+
{/if}
|
|
62
|
+
</span>
|
|
63
|
+
{/if}
|
|
64
|
+
{:else}
|
|
65
|
+
<div>
|
|
66
|
+
<input
|
|
67
|
+
type="tel"
|
|
68
|
+
name={field?.key ?? 'phone'}
|
|
69
|
+
id={field?.key ?? 'phone'}
|
|
70
|
+
value={stringValue ?? ''}
|
|
71
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
72
|
+
placeholder={field?.label ?? '+1 (555) 000-0000'}
|
|
73
|
+
{...field?.required ? { required: true } : {}}
|
|
74
|
+
/>
|
|
75
|
+
{#if hasError}
|
|
76
|
+
{#each error as err, _i (_i)}
|
|
77
|
+
<div class="lite-error-text">{err}</div>
|
|
78
|
+
{/each}
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
81
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
2
|
+
interface Props {
|
|
3
|
+
field?: FieldDefinition;
|
|
4
|
+
value?: string | number | null;
|
|
5
|
+
href?: string;
|
|
6
|
+
showIcon?: boolean;
|
|
7
|
+
clickable?: boolean;
|
|
8
|
+
copyable?: boolean;
|
|
9
|
+
nullLabel?: string;
|
|
10
|
+
oncopy?: (value: string) => void;
|
|
11
|
+
class?: string;
|
|
12
|
+
error?: string[];
|
|
13
|
+
mode?: 'show' | 'edit' | 'create';
|
|
14
|
+
}
|
|
15
|
+
declare const LitePhoneField: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type LitePhoneField = ReturnType<typeof LitePhoneField>;
|
|
17
|
+
export default LitePhoneField;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type LiteRatingSize = 'sm' | 'default' | 'lg';
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
field?: FieldDefinition;
|
|
10
|
+
value?: number | string | null;
|
|
11
|
+
max?: number;
|
|
12
|
+
showValue?: boolean;
|
|
13
|
+
size?: LiteRatingSize;
|
|
14
|
+
nullLabel?: string;
|
|
15
|
+
class?: string;
|
|
16
|
+
error?: string[];
|
|
17
|
+
mode?: 'show' | 'edit' | 'create';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
field,
|
|
22
|
+
value,
|
|
23
|
+
max = 5,
|
|
24
|
+
showValue = false,
|
|
25
|
+
size = 'default',
|
|
26
|
+
nullLabel = '—',
|
|
27
|
+
class: className = '',
|
|
28
|
+
error = [],
|
|
29
|
+
mode = 'show',
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
const normalizedMax = $derived(
|
|
33
|
+
Number.isFinite(max) ? Math.min(100, Math.max(1, Math.trunc(max))) : 5
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const numericValue = $derived.by(() => {
|
|
37
|
+
if (value == null) return null;
|
|
38
|
+
if (typeof value === 'string' && value.trim() === '') return null;
|
|
39
|
+
const parsed = typeof value === 'number' ? value : Number(value);
|
|
40
|
+
if (!Number.isFinite(parsed)) return null;
|
|
41
|
+
return Math.max(0, Math.min(normalizedMax, parsed));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const stars = $derived.by(() => {
|
|
45
|
+
if (numericValue === null) return [];
|
|
46
|
+
const result: ('full' | 'half' | 'empty')[] = [];
|
|
47
|
+
for (let index = 1; index <= normalizedMax; index += 1) {
|
|
48
|
+
if (numericValue >= index) {
|
|
49
|
+
result.push('full');
|
|
50
|
+
} else if (numericValue >= index - 0.5) {
|
|
51
|
+
result.push('half');
|
|
52
|
+
} else {
|
|
53
|
+
result.push('empty');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const hasError = $derived(error.length > 0);
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
{#if mode === 'show'}
|
|
63
|
+
{#if numericValue === null}
|
|
64
|
+
<span class="lite-text-muted lite-text-sm {className}">{nullLabel}</span>
|
|
65
|
+
{:else}
|
|
66
|
+
<span class="lite-rating-container {className}">
|
|
67
|
+
<span class="lite-rating lite-rating-{size}" role="img" aria-label={`${numericValue} out of ${normalizedMax}`}>
|
|
68
|
+
{#each stars as starType, idx (idx)}
|
|
69
|
+
{#if starType === 'full'}
|
|
70
|
+
<span class="lite-rating-star lite-rating-star-full">★</span>
|
|
71
|
+
{:else if starType === 'half'}
|
|
72
|
+
<span class="lite-rating-star lite-rating-star-half">★</span>
|
|
73
|
+
{:else}
|
|
74
|
+
<span class="lite-rating-star lite-rating-star-empty">☆</span>
|
|
75
|
+
{/if}
|
|
76
|
+
{/each}
|
|
77
|
+
</span>
|
|
78
|
+
{#if showValue}
|
|
79
|
+
<span class="lite-rating-value lite-font-mono">
|
|
80
|
+
{numericValue}
|
|
81
|
+
</span>
|
|
82
|
+
{/if}
|
|
83
|
+
</span>
|
|
84
|
+
{/if}
|
|
85
|
+
{:else}
|
|
86
|
+
<div>
|
|
87
|
+
<input
|
|
88
|
+
type="number"
|
|
89
|
+
min="0"
|
|
90
|
+
max={normalizedMax}
|
|
91
|
+
step="0.5"
|
|
92
|
+
name={field?.key ?? 'rating'}
|
|
93
|
+
id={field?.key ?? 'rating'}
|
|
94
|
+
value={numericValue ?? ''}
|
|
95
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
96
|
+
placeholder={field?.label ?? `0 - ${normalizedMax}`}
|
|
97
|
+
{...field?.required ? { required: true } : {}}
|
|
98
|
+
/>
|
|
99
|
+
{#if hasError}
|
|
100
|
+
{#each error as err, _i (_i)}
|
|
101
|
+
<div class="lite-error-text">{err}</div>
|
|
102
|
+
{/each}
|
|
103
|
+
{/if}
|
|
104
|
+
</div>
|
|
105
|
+
{/if}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type LiteRatingSize = 'sm' | 'default' | 'lg';
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
interface Props {
|
|
4
|
+
field?: FieldDefinition;
|
|
5
|
+
value?: number | string | null;
|
|
6
|
+
max?: number;
|
|
7
|
+
showValue?: boolean;
|
|
8
|
+
size?: LiteRatingSize;
|
|
9
|
+
nullLabel?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
error?: string[];
|
|
12
|
+
mode?: 'show' | 'edit' | 'create';
|
|
13
|
+
}
|
|
14
|
+
declare const LiteRatingField: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type LiteRatingField = ReturnType<typeof LiteRatingField>;
|
|
16
|
+
export default LiteRatingField;
|