@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
- value = parseFloat(textValue);
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
- textValue = utils.formatNumber(value, precision);
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; // cursor start position
68
- const cursorEnd = editor.selectionEnd; // cursor end position
69
- let resultingString = currentValue.slice(0, cursorStart) + s + currentValue.slice(cursorEnd);
70
- if (/[^0-9\.\-]/.test(resultingString)) {
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
- // Check for full-width characters or non-ASCII characters
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
- // Allow special keys like ctrl
117
+ // 允许功能键
94
118
  const allowKeys = ['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
95
119
  if (allowKeys.includes(event.key)) return;
96
120
 
97
- // Allow numbers, decimal points, negative signs (can customize based on precision)
98
- if (/[\d\.\-]/.test(event.key)) return;
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; // Ignore during composition (e.g., pinyin input)
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
- // Contains Chinese, Japanese, Korean, full-width characters, emojis, etc.
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
- // When invalid, don't update value, only update textValue (allow user to continue typing)
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; // Restore previous content
159
+ editor.value = currentText;
135
160
  textValue = currentText;
136
161
  } else {
137
- handleInput(); // Normal input
162
+ handleInput();
138
163
  }
139
164
  };
140
165
 
141
-
166
+ /**
167
+ * 仅在非焦点状态下根据外部 value 更新显示文本(含千分位)。
168
+ * 焦点状态下绝不重写 textValue,避免干扰用户输入过程。
169
+ */
142
170
  $: if (!isFocused) {
143
- textValue = value == null ? '' : utils.formatNumber(value, precision);
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} placeholder={readonly || disabled ? '' : placeholder}
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} on:paste={handlePaste}/>
185
+ on:compositionend={handleCompositionEnd}
186
+ on:paste={handlePaste}/>
@@ -1,10 +1,9 @@
1
- <script lang="ts">
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
- //let textValue: string = utils.formatNumber(value, precision);
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
- $: textValue = utils.formatNumber(value, precision);
55
+ $: displayValue = formatWithThousands(value, precision);
46
56
 
47
57
  </script>
48
- <CommonEditor {displayMode} {style} value={textValue} {suffix} {prefix} {readonly} {variant} {compact} class={className}
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
- <NumberInput bind:this={editor} bind:textValue style="flex: 1 1 auto" {disabled} {placeholder} bind:value {precision} {allowNegative} {max} {min}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ticatec/uniface-element",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "A comprehensive UI component library for Svelte applications with rich form controls, data tables, layouts and interactive elements",
5
5
  "keywords": [
6
6
  "svelte",