@ticatec/uniface-element 0.3.9 → 0.3.11
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.
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
export let onfocus: (() => void) | null = null;
|
|
18
18
|
export let onblur: (() => void) | null = null;
|
|
19
19
|
export let textValue: string = '';
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
export const focus = () => {
|
|
22
22
|
editor.focus();
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
export const setFocus = () => {
|
|
26
26
|
setTimeout(() => {
|
|
27
27
|
editor && editor.focus();
|
|
@@ -34,8 +34,27 @@
|
|
|
34
34
|
let currentText: string;
|
|
35
35
|
let isFocused = false;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* 将数字格式化为带千分位的字符串
|
|
39
|
+
* 例:1234567.89 → "1,234,567.89"
|
|
40
|
+
*/
|
|
41
|
+
const formatWithThousands = (num: number, prec: number | null): string => {
|
|
42
|
+
const base = utils.formatNumber(num, prec);
|
|
43
|
+
if (!base) return base;
|
|
44
|
+
const parts = base.split('.');
|
|
45
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
46
|
+
return parts.join('.');
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 剥离千分位逗号,还原为可供 parseFloat 使用的原始字符串
|
|
51
|
+
*/
|
|
52
|
+
const stripThousands = (s: string): string => s.replace(/,/g, '');
|
|
53
|
+
|
|
37
54
|
const handleFocusEvent = (e: FocusEvent) => {
|
|
38
55
|
isFocused = true;
|
|
56
|
+
// 获焦时剥掉千分位逗号,让用户直接编辑原始数字
|
|
57
|
+
textValue = stripThousands(textValue);
|
|
39
58
|
onfocus?.();
|
|
40
59
|
}
|
|
41
60
|
|
|
@@ -43,7 +62,9 @@
|
|
|
43
62
|
isFocused = false;
|
|
44
63
|
onblur?.();
|
|
45
64
|
if (!readonly) {
|
|
46
|
-
|
|
65
|
+
// 解析前先剥逗号(容错)
|
|
66
|
+
const raw = stripThousands(textValue);
|
|
67
|
+
value = parseFloat(raw);
|
|
47
68
|
if (isNaN(value)) {
|
|
48
69
|
value = null;
|
|
49
70
|
}
|
|
@@ -54,7 +75,10 @@
|
|
|
54
75
|
if (max != null && value > max) {
|
|
55
76
|
value = max;
|
|
56
77
|
}
|
|
57
|
-
|
|
78
|
+
// 失焦后格式化为千分位
|
|
79
|
+
textValue = formatWithThousands(value, precision);
|
|
80
|
+
} else {
|
|
81
|
+
textValue = '';
|
|
58
82
|
}
|
|
59
83
|
onchange?.(value);
|
|
60
84
|
} else {
|
|
@@ -63,16 +87,16 @@
|
|
|
63
87
|
}
|
|
64
88
|
|
|
65
89
|
const checkNewInputText = (s: string): boolean => {
|
|
90
|
+
// 编辑过程中 editor.value 不含逗号(获焦时已剥),直接使用
|
|
66
91
|
const currentValue = editor.value;
|
|
67
|
-
const cursorStart = editor.selectionStart;
|
|
68
|
-
const cursorEnd = editor.selectionEnd;
|
|
69
|
-
|
|
70
|
-
if (/[^0-9
|
|
92
|
+
const cursorStart = editor.selectionStart;
|
|
93
|
+
const cursorEnd = editor.selectionEnd;
|
|
94
|
+
const resultingString = currentValue.slice(0, cursorStart!) + s + currentValue.slice(cursorEnd!);
|
|
95
|
+
if (/[^0-9.\-]/.test(resultingString)) {
|
|
71
96
|
return false;
|
|
72
|
-
} else {
|
|
73
|
-
return regex.test(resultingString) &&
|
|
74
|
-
utils.isValidNumber(parseFloat(resultingString), precision, allowNegative, max, min);
|
|
75
97
|
}
|
|
98
|
+
return regex.test(resultingString) &&
|
|
99
|
+
utils.isValidNumber(parseFloat(resultingString), precision, allowNegative, max, min);
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
const handlePaste = (event: ClipboardEvent) => {
|
|
@@ -83,46 +107,47 @@
|
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
86
|
-
//
|
|
110
|
+
// 拦截全角/非 ASCII 字符
|
|
87
111
|
if (/[^\x00-\x7F]/.test(event.key)) {
|
|
88
112
|
event.preventDefault();
|
|
89
113
|
event.stopPropagation();
|
|
90
114
|
return;
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
//
|
|
117
|
+
// 允许功能键
|
|
94
118
|
const allowKeys = ['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
|
95
119
|
if (allowKeys.includes(event.key)) return;
|
|
96
120
|
|
|
97
|
-
//
|
|
98
|
-
if (/[\d
|
|
121
|
+
// 允许数字、小数点、负号
|
|
122
|
+
if (/[\d.\-]/.test(event.key)) return;
|
|
99
123
|
|
|
100
|
-
// Block other invalid characters
|
|
101
124
|
event.preventDefault();
|
|
102
125
|
event.stopPropagation();
|
|
103
126
|
};
|
|
127
|
+
|
|
104
128
|
const handleInput = () => {
|
|
105
|
-
if (composing) return;
|
|
129
|
+
if (composing) return;
|
|
106
130
|
|
|
107
|
-
// Validate if it's a valid number string
|
|
108
131
|
const raw = editor.value;
|
|
132
|
+
|
|
133
|
+
// 拦截中文/全角字符(输入法直接上屏的情况)
|
|
109
134
|
if (/[^\x00-\x7F]/.test(raw)) {
|
|
110
|
-
|
|
111
|
-
editor.value = textValue; // Restore previous state
|
|
135
|
+
editor.value = textValue;
|
|
112
136
|
return;
|
|
113
137
|
}
|
|
138
|
+
|
|
114
139
|
if (regex.test(raw)) {
|
|
115
140
|
const parsed = parseFloat(raw);
|
|
141
|
+
// 仅在数值合法时同步 value,此处不触发格式化
|
|
116
142
|
if (!isNaN(parsed) && utils.isValidNumber(parsed, precision, allowNegative, max, min)) {
|
|
117
143
|
value = parsed;
|
|
118
144
|
}
|
|
119
145
|
}
|
|
120
146
|
|
|
121
|
-
//
|
|
147
|
+
// 编辑过程中只更新原始字符串,禁止在此处对 textValue 做任何格式化
|
|
122
148
|
textValue = raw;
|
|
123
149
|
}
|
|
124
150
|
|
|
125
|
-
|
|
126
151
|
const handleCompositionStart = (event: CompositionEvent) => {
|
|
127
152
|
currentText = editor.value;
|
|
128
153
|
composing = true;
|
|
@@ -131,26 +156,31 @@
|
|
|
131
156
|
const handleCompositionEnd = (event: CompositionEvent) => {
|
|
132
157
|
composing = false;
|
|
133
158
|
if (/[^\x00-\x7F]/.test(editor.value)) {
|
|
134
|
-
editor.value = currentText;
|
|
159
|
+
editor.value = currentText;
|
|
135
160
|
textValue = currentText;
|
|
136
161
|
} else {
|
|
137
|
-
handleInput();
|
|
162
|
+
handleInput();
|
|
138
163
|
}
|
|
139
164
|
};
|
|
140
165
|
|
|
141
|
-
|
|
166
|
+
/**
|
|
167
|
+
* 仅在非焦点状态下根据外部 value 更新显示文本(含千分位)。
|
|
168
|
+
* 焦点状态下绝不重写 textValue,避免干扰用户输入过程。
|
|
169
|
+
*/
|
|
142
170
|
$: if (!isFocused) {
|
|
143
|
-
textValue = value == null ? '' :
|
|
171
|
+
textValue = value == null ? '' : formatWithThousands(value, precision);
|
|
144
172
|
}
|
|
145
173
|
|
|
146
174
|
$: regex = new RegExp(utils.getNumberRegex(precision, allowNegative));
|
|
147
175
|
|
|
148
176
|
</script>
|
|
149
|
-
<input class="number-editor" bind:this={editor} type="text" {readonly} {style} {disabled}
|
|
177
|
+
<input class="number-editor" bind:this={editor} type="text" {readonly} {style} {disabled}
|
|
178
|
+
placeholder={readonly || disabled ? '' : placeholder}
|
|
150
179
|
bind:value={textValue}
|
|
151
180
|
on:focus={handleFocusEvent}
|
|
152
181
|
on:blur={handleBlurEvent}
|
|
153
182
|
on:compositionstart={handleCompositionStart}
|
|
154
183
|
on:input={handleInput}
|
|
155
184
|
on:keydown={handleKeyDown}
|
|
156
|
-
on:compositionend={handleCompositionEnd}
|
|
185
|
+
on:compositionend={handleCompositionEnd}
|
|
186
|
+
on:paste={handlePaste}/>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
|
|
3
3
|
import {DisplayMode} from "../common/DisplayMode";
|
|
4
4
|
import CommonEditor from "../common-editor/CommonEditor.svelte";
|
|
5
5
|
import type {OnChangeHandler} from "../common/OnChangeHandler";
|
|
6
6
|
import NumberInput from "../common-editor/NumberInput.svelte";
|
|
7
|
-
import {tick} from "svelte";
|
|
8
7
|
import utils from "../common/utils";
|
|
9
8
|
|
|
10
9
|
|
|
@@ -40,12 +39,23 @@
|
|
|
40
39
|
editor.setFocus();
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
/**
|
|
43
|
+
* 仅用于 CommonEditor 只读/禁用模式下的展示文本(含千分位)。
|
|
44
|
+
* 与 NumberInput 内部的 textValue 完全解耦,不再干涉编辑状态。
|
|
45
|
+
*/
|
|
46
|
+
const formatWithThousands = (num: number | null, prec: number | null): string => {
|
|
47
|
+
if (num == null) return '';
|
|
48
|
+
const base = utils.formatNumber(num, prec);
|
|
49
|
+
if (!base) return base;
|
|
50
|
+
const parts = base.split('.');
|
|
51
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
52
|
+
return parts.join('.');
|
|
53
|
+
};
|
|
44
54
|
|
|
45
|
-
$:
|
|
55
|
+
$: displayValue = formatWithThousands(value, precision);
|
|
46
56
|
|
|
47
57
|
</script>
|
|
48
|
-
<CommonEditor {displayMode} {style} value={
|
|
58
|
+
<CommonEditor {displayMode} {style} value={displayValue} {suffix} {prefix} {readonly} {variant} {compact} class={className}
|
|
49
59
|
input$class="number-editor"
|
|
50
60
|
hasLeadingIcon={$$slots['leading-icon']!=null} hasTrailingIcon={$$slots['trailing-icon']!=null}
|
|
51
61
|
showActionIcon={removable && !readonly && !disabled && value != null} {clean}>
|
|
@@ -56,7 +66,8 @@
|
|
|
56
66
|
</div>
|
|
57
67
|
{/if}
|
|
58
68
|
</svelte:fragment>
|
|
59
|
-
|
|
69
|
+
<!-- 不再 bind:textValue,让 NumberInput 完全自主管理编辑态文本 -->
|
|
70
|
+
<NumberInput bind:this={editor} style="flex: 1 1 auto" {disabled} {placeholder} bind:value {precision} {allowNegative} {max} {min}
|
|
60
71
|
{readonly} {onchange}/>
|
|
61
72
|
<svelte:fragment slot="trailing-icon">
|
|
62
73
|
{#if $$slots['trailing-icon']}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { DisplayMode } from "./common/DisplayMode.js";
|
|
|
4
4
|
import { DateContext } from "./base-calendar";
|
|
5
5
|
import { ModalResult } from "./message-box";
|
|
6
6
|
import type { LazyLoader } from "./list-box";
|
|
7
|
+
import type { GetRowActions } from "./data-table";
|
|
8
|
+
import type { GetCellStyle } from "./data-table/lib/types";
|
|
9
|
+
import type { GetRowFontStyle, RowFontStyle } from "./data-table/lib/types";
|
|
7
10
|
/**
|
|
8
11
|
* 从选中的Option中获取对于的文字值
|
|
9
12
|
*/
|
|
@@ -11,3 +14,4 @@ type RetrieveOptionText = (item: any) => string;
|
|
|
11
14
|
export { DateContext, ModalResult, DisplayMode };
|
|
12
15
|
export type { RetrieveOptionText, LazyLoader };
|
|
13
16
|
export type { OnChangeHandler, MouseClickHandler };
|
|
17
|
+
export type { GetRowActions, GetCellStyle, GetRowFontStyle, RowFontStyle };
|
package/package.json
CHANGED