@ticatec/uniface-element 0.2.0 → 0.2.2
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 +320 -1
- package/README_CN.md +392 -0
- package/dist/accordion/Accordion.svelte +1 -1
- package/dist/action-bar/ActionBar.svelte +7 -1
- package/dist/attachment-files/FileRender.svelte +1 -1
- package/dist/attachment-files/FileUploadBar.svelte +7 -6
- package/dist/attachment-files/FileUploadPanel.svelte +2 -2
- package/dist/base-calendar/DateContext.js +5 -4
- package/dist/base-calendar/YearPickupPanel.svelte +4 -4
- package/dist/card/Card.svelte +1 -1
- package/dist/card/Card.svelte.d.ts +1 -1
- package/dist/cascade-options-select/CascadeOptionSelect.svelte +2 -3
- package/dist/cascade-options-select/CascadeOptionSelect.svelte.d.ts +46 -1
- package/dist/cascade-options-select/CascadePanel.svelte +1 -1
- package/dist/checkbox/CheckBox.svelte +1 -1
- package/dist/color-picker/ColorPicker.svelte +9 -9
- package/dist/color-picker/ColorPicker.svelte.d.ts +0 -2
- package/dist/color-picker/ColorPickerPanel.svelte +11 -15
- package/dist/color-picker/ColorPickerPanel.svelte.d.ts +1 -1
- package/dist/common/CommonPicker.svelte +2 -2
- package/dist/common/CommonPicker.svelte.d.ts +1 -1
- package/dist/common/MouseClickHandler.d.ts +1 -1
- package/dist/common/Popover.svelte +28 -6
- package/dist/common/Popover.svelte.d.ts +9 -3
- package/dist/common/uniface-utils.js +6 -4
- package/dist/common-editor/CommonEditor.svelte +1 -1
- package/dist/common-editor/CommonEditor.svelte.d.ts +1 -1
- package/dist/common-editor/NumberInput.svelte +1 -1
- package/dist/common-editor/NumberInput.svelte.d.ts +1 -1
- package/dist/concise-data-table/ConciseListTable.svelte +7 -2
- package/dist/concise-data-table/ConciseListTable.svelte.d.ts +1 -0
- package/dist/concise-data-table/DataRow.svelte +1 -1
- package/dist/concise-data-table/TableOptions.d.ts +1 -1
- package/dist/data-table/DataTable.svelte +1 -1
- package/dist/data-table/parts/ActionsPanel.svelte +4 -3
- package/dist/data-table/parts/ActionsRow.svelte +3 -3
- package/dist/data-table/parts/ContentPanel.svelte +3 -2
- package/dist/data-table/parts/FixedColumnsPanel.svelte +0 -1
- package/dist/data-table/parts/FixedColumnsPanel.svelte.d.ts +1 -2
- package/dist/data-table/parts/FixedHeaderPanel.svelte +2 -2
- package/dist/data-table/parts/FixedRow.svelte +2 -1
- package/dist/data-table/parts/FixedRow.svelte.d.ts +1 -1
- package/dist/data-table/parts/HrefCell.svelte +23 -2
- package/dist/data-table/parts/PopupMenu.svelte +25 -2
- package/dist/data-table/parts/PopupMenu.svelte.d.ts +8 -1
- package/dist/date-picker/DatePicker.svelte +0 -1
- package/dist/date-picker/DateTimePicker.svelte +3 -3
- package/dist/date-picker/ScrollBar.svelte +4 -7
- package/dist/date-range/DateRangeEditor.svelte +1 -1
- package/dist/dialog/CommonDialog.svelte +8 -5
- package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
- package/dist/dialog/Dialog.svelte +37 -12
- package/dist/dialog/Dialog.svelte.d.ts +4 -3
- package/dist/dialog/DialogBoard.svelte +11 -8
- package/dist/dialog/DialogWrapper.svelte +10 -0
- package/dist/{common-editor/DateInput.svelte.d.ts → dialog/DialogWrapper.svelte.d.ts} +6 -13
- package/dist/dialog/Dialogs.js +1 -1
- package/dist/dialog/ModalResult.d.ts +5 -0
- package/dist/dialog/ModalResult.js +5 -0
- package/dist/drawer/Drawer.svelte +1 -1
- package/dist/form-field/FormField.svelte +2 -1
- package/dist/form-field/FormField.svelte.d.ts +1 -0
- package/dist/form-panel/flex-row-form/FormContainer.svelte +1 -1
- package/dist/form-panel/grid-form/EmptyCell.svelte +8 -7
- package/dist/form-panel/grid-form/EmptyCell.svelte.d.ts +2 -2
- package/dist/form-panel/grid-form/GridField.svelte +1 -1
- package/dist/i18nRes/i18nRes.d.ts +2 -0
- package/dist/i18nRes/i18nRes.js +44 -0
- package/dist/i18nRes/index.d.ts +2 -0
- package/dist/i18nRes/index.js +2 -0
- package/dist/image-files/ImageFilesField.svelte +1 -1
- package/dist/indicator/IndicatorBoard.svelte +1 -1
- package/dist/inline-cell-editor/InvalidEditor.svelte +1 -0
- package/dist/input-options-select/InputOptionsSelect.svelte +6 -6
- package/dist/input-options-select/InputOptionsSelect.svelte.d.ts +1 -1
- package/dist/layout/flex/FlexBlock.svelte +1 -1
- package/dist/lookup-editor/LookupEditor.svelte +1 -1
- package/dist/memo-editor/MemoEditor.svelte +2 -2
- package/dist/message-box/IMessageBox.d.ts +2 -4
- package/dist/message-box/IMessageBox.js +4 -6
- package/dist/message-box/MessageBoxBoard.svelte +1 -3
- package/dist/options-multi-select/OptionsMultiSelect.svelte +1 -1
- package/dist/prompts-text-editor/PromptsTextEditor.svelte +2 -2
- package/dist/prompts-text-editor/PromptsTextEditor.svelte.d.ts +1 -1
- package/dist/property-editor/PropertyEditor.svelte +3 -3
- package/dist/text-editor/PasswordEditor.svelte +1 -1
- package/dist/transfer/Transfer.svelte +17 -12
- package/dist/transfer/Transfer.svelte.d.ts +0 -1
- package/dist/unit-number-editor/UnitNumberEditor.svelte +2 -2
- package/docs/buttons/cn/ActionBar.md +459 -0
- package/docs/buttons/cn/Button.md +228 -0
- package/docs/buttons/cn/IconButton.md +294 -0
- package/docs/buttons/cn/README.md +600 -0
- package/docs/buttons/cn/TextButton.md +163 -0
- package/docs/buttons/en/ActionBar.md +459 -0
- package/docs/buttons/en/Button.md +228 -0
- package/docs/buttons/en/IconButton.md +294 -0
- package/docs/buttons/en/README.md +600 -0
- package/docs/buttons/en/TextButton.md +163 -0
- package/docs/containers/cn/accordion.md +622 -0
- package/docs/containers/cn/box.md +445 -0
- package/docs/containers/cn/card.md +404 -0
- package/docs/containers/cn/page.md +576 -0
- package/docs/containers/cn/tabs.md +686 -0
- package/docs/containers/en/accordion.md +622 -0
- package/docs/containers/en/box.md +445 -0
- package/docs/containers/en/card.md +404 -0
- package/docs/containers/en/page.md +576 -0
- package/docs/containers/en/tabs.md +686 -0
- package/docs/data-display/cn/concise-data-table.md +731 -0
- package/docs/data-display/cn/listbox.md +786 -0
- package/docs/data-display/cn/treeview.md +863 -0
- package/docs/data-display/en/concise-data-table.md +732 -0
- package/docs/data-display/en/listbox.md +786 -0
- package/docs/data-display/en/treeview.md +863 -0
- package/docs/data-table/cn/README.md +596 -0
- package/docs/data-table/en/README.md +596 -0
- package/docs/dialog/cn/README.md +767 -0
- package/docs/dialog/en/README.md +767 -0
- package/docs/form/cn/README.md +847 -0
- package/docs/form/en/README.md +847 -0
- package/docs/form-components/cn/README.md +495 -0
- package/docs/form-components/cn/attachmentfilesfield.md +757 -0
- package/docs/form-components/cn/cascade-options-select.md +374 -0
- package/docs/form-components/cn/checkbox.md +58 -0
- package/docs/form-components/cn/date-picker.md +138 -0
- package/docs/form-components/cn/datetime-picker.md +228 -0
- package/docs/form-components/cn/group-checkbox.md +405 -0
- package/docs/form-components/cn/group-radiobox.md +525 -0
- package/docs/form-components/cn/imagefilesfield.md +651 -0
- package/docs/form-components/cn/input-options-select.md +250 -0
- package/docs/form-components/cn/lookup-editor.md +406 -0
- package/docs/form-components/cn/memoeditor.md +409 -0
- package/docs/form-components/cn/number-editor.md +261 -0
- package/docs/form-components/cn/options-multi-select.md +177 -0
- package/docs/form-components/cn/options-select.md +83 -0
- package/docs/form-components/cn/passwordeditor.md +538 -0
- package/docs/form-components/cn/prompts-text-editor.md +241 -0
- package/docs/form-components/cn/radiobutton.md +84 -0
- package/docs/form-components/cn/switch.md +178 -0
- package/docs/form-components/cn/text-editor.md +142 -0
- package/docs/form-components/cn/time-editor.md +159 -0
- package/docs/form-components/cn/unit-number-editor.md +164 -0
- package/docs/form-components/en/README.md +463 -0
- package/docs/form-components/en/attachmentfilesfield.md +757 -0
- package/docs/form-components/en/cascade-options-select.md +358 -0
- package/docs/form-components/en/checkbox.md +59 -0
- package/docs/form-components/en/date-picker.md +138 -0
- package/docs/form-components/en/datetime-picker.md +228 -0
- package/docs/form-components/en/group-checkbox.md +395 -0
- package/docs/form-components/en/group-radiobox.md +498 -0
- package/docs/form-components/en/imagefilesfield.md +651 -0
- package/docs/form-components/en/input-options-select.md +239 -0
- package/docs/form-components/en/lookup-editor.md +389 -0
- package/docs/form-components/en/memoeditor.md +409 -0
- package/docs/form-components/en/number-editor.md +249 -0
- package/docs/form-components/en/options-multi-select.md +177 -0
- package/docs/form-components/en/options-select.md +83 -0
- package/docs/form-components/en/passwordeditor.md +538 -0
- package/docs/form-components/en/prompts-text-editor.md +241 -0
- package/docs/form-components/en/radiobutton.md +84 -0
- package/docs/form-components/en/switch.md +180 -0
- package/docs/form-components/en/text-editor.md +142 -0
- package/docs/form-components/en/time-editor.md +159 -0
- package/docs/form-components/en/unit-number-editor.md +164 -0
- package/docs/form-layout/cn/criteria-field.md +652 -0
- package/docs/form-layout/cn/flex-form.md +454 -0
- package/docs/form-layout/cn/flex-row-form.md +542 -0
- package/docs/form-layout/cn/form-field.md +923 -0
- package/docs/form-layout/cn/grid-form.md +676 -0
- package/docs/form-layout/en/criteria-field.md +652 -0
- package/docs/form-layout/en/flex-form.md +454 -0
- package/docs/form-layout/en/flex-row-form.md +542 -0
- package/docs/form-layout/en/form-field.md +923 -0
- package/docs/form-layout/en/grid-form.md +676 -0
- package/docs/global/cn/README.md +534 -0
- package/docs/global/en/README.md +534 -0
- package/docs/layout/cn/classiclayout.md +460 -0
- package/docs/layout/cn/headerlayout.md +524 -0
- package/docs/layout/cn/sidebarlayout.md +263 -0
- package/docs/layout/en/classiclayout.md +951 -0
- package/docs/layout/en/headerlayout.md +524 -0
- package/docs/layout/en/sidebarlayout.md +262 -0
- package/docs/misc/cn/README.md +1217 -0
- package/docs/misc/en/README.md +1217 -0
- package/docs/navigation/cn/breadcrumb.md +291 -0
- package/docs/navigation/cn/navigator.md +322 -0
- package/docs/navigation/cn/navigatormenu.md +512 -0
- package/docs/navigation/cn/progressbar.md +199 -0
- package/docs/navigation/cn/progressstepbar.md +299 -0
- package/docs/navigation/en/breadcrumb.md +291 -0
- package/docs/navigation/en/navigator.md +322 -0
- package/docs/navigation/en/navigatormenu.md +512 -0
- package/docs/navigation/en/progressbar.md +199 -0
- package/docs/navigation/en/progressstepbar.md +299 -0
- package/docs/search-filters/cn/daterange.md +1156 -0
- package/docs/search-filters/cn/numberrange.md +688 -0
- package/docs/search-filters/cn/searchbox.md +1582 -0
- package/docs/search-filters/en/daterange.md +1156 -0
- package/docs/search-filters/en/numberrange.md +688 -0
- package/docs/search-filters/en/searchbox.md +1582 -0
- package/package.json +53 -13
- package/dist/common-editor/DateInput.svelte +0 -27
- package/dist/i18n_resources/index.d.ts +0 -3
- package/dist/i18n_resources/index.js +0 -3
- /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.d.ts +0 -0
- /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.js +0 -0
- /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.d.ts +0 -0
- /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.js +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# InputOptionsSelect Component
|
|
2
|
+
|
|
3
|
+
Selection component with input and async search functionality.
|
|
4
|
+
|
|
5
|
+
## Type Definitions
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
type LazyLoader = (keyword: string, pageNo: number) => Promise<{
|
|
9
|
+
list: Array<any>;
|
|
10
|
+
hasMore: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Properties
|
|
15
|
+
|
|
16
|
+
| Property | Type | Default | Description |
|
|
17
|
+
|----------|------|---------|-------------|
|
|
18
|
+
| `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | Appearance variant |
|
|
19
|
+
| `disabled` | `boolean` | `false` | Whether disabled |
|
|
20
|
+
| `readonly` | `boolean` | `false` | Whether read-only |
|
|
21
|
+
| `compact` | `boolean` | `false` | Whether compact mode |
|
|
22
|
+
| `text` | `string` | `''` | Display text |
|
|
23
|
+
| `value` | `any` | `null` | Selected value |
|
|
24
|
+
| `keyField` | `string` | `'code'` | Value field name |
|
|
25
|
+
| `textField` | `string` | `'text'` | Display field name |
|
|
26
|
+
| `menu$height` | `number` | `0` | Dropdown menu height |
|
|
27
|
+
| `placeholder` | `string` | `''` | Placeholder text |
|
|
28
|
+
| `displayMode` | `DisplayMode` | `DisplayMode.Edit` | Display mode |
|
|
29
|
+
| `itemRender` | `any` | `null` | Custom option render component |
|
|
30
|
+
| `lazyLoader` | `LazyLoader` | - | Async data loader |
|
|
31
|
+
| `style` | `string` | `''` | Custom styles |
|
|
32
|
+
| `onChange` | `OnChangeHandler<any>` | - | Value change callback |
|
|
33
|
+
|
|
34
|
+
## Usage Examples
|
|
35
|
+
|
|
36
|
+
```svelte
|
|
37
|
+
<script lang="ts">
|
|
38
|
+
import InputOptionsSelect, { type LazyLoader } from '@ticatec/uniface-element/InputOptionsSelect';
|
|
39
|
+
|
|
40
|
+
interface User {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
email: string;
|
|
44
|
+
department: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface Product {
|
|
48
|
+
code: string;
|
|
49
|
+
text: string;
|
|
50
|
+
category: string;
|
|
51
|
+
price: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let selectedUser: User | null = null;
|
|
55
|
+
let selectedProduct: Product | null = null;
|
|
56
|
+
|
|
57
|
+
// User search
|
|
58
|
+
const searchUsers: LazyLoader = async (keyword: string, pageNo: number) => {
|
|
59
|
+
const response = await fetch(`/api/users/search?q=${encodeURIComponent(keyword)}&page=${pageNo}`);
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
return {
|
|
62
|
+
list: data.users,
|
|
63
|
+
hasMore: data.hasMore
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Product search
|
|
68
|
+
const searchProducts: LazyLoader = async (keyword: string, pageNo: number) => {
|
|
69
|
+
// Simulate API delay
|
|
70
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
71
|
+
|
|
72
|
+
const mockProducts: Product[] = [
|
|
73
|
+
{ code: 'P001', text: 'iPhone 15 Pro', category: 'Phone', price: 999 },
|
|
74
|
+
{ code: 'P002', text: 'MacBook Air M2', category: 'Laptop', price: 1299 },
|
|
75
|
+
{ code: 'P003', text: 'iPad Pro 12.9', category: 'Tablet', price: 899 }
|
|
76
|
+
].filter(p => p.text.toLowerCase().includes(keyword.toLowerCase()));
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
list: mockProducts,
|
|
80
|
+
hasMore: false
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleUserChange = (user: User): void => {
|
|
85
|
+
selectedUser = user;
|
|
86
|
+
console.log('Selected user:', user);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleProductChange = (product: Product): void => {
|
|
90
|
+
selectedProduct = product;
|
|
91
|
+
console.log('Selected product:', product);
|
|
92
|
+
};
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<!-- User search selection -->
|
|
96
|
+
<InputOptionsSelect
|
|
97
|
+
placeholder="Search for users..."
|
|
98
|
+
lazyLoader={searchUsers}
|
|
99
|
+
keyField="id"
|
|
100
|
+
textField="name"
|
|
101
|
+
onChange={handleUserChange}
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
<!-- Product search selection -->
|
|
105
|
+
<InputOptionsSelect
|
|
106
|
+
placeholder="Search for products..."
|
|
107
|
+
lazyLoader={searchProducts}
|
|
108
|
+
keyField="code"
|
|
109
|
+
textField="text"
|
|
110
|
+
onChange={handleProductChange}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Advanced Usage
|
|
115
|
+
|
|
116
|
+
### Custom Option Rendering
|
|
117
|
+
```svelte
|
|
118
|
+
<script lang="ts">
|
|
119
|
+
import UserItemRenderer from './UserItemRenderer.svelte';
|
|
120
|
+
|
|
121
|
+
const searchUsersWithDetails: LazyLoader = async (keyword: string, pageNo: number) => {
|
|
122
|
+
const response = await fetch(`/api/users/search-detailed?q=${keyword}&page=${pageNo}`);
|
|
123
|
+
return response.json();
|
|
124
|
+
};
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
<InputOptionsSelect
|
|
128
|
+
placeholder="Search users"
|
|
129
|
+
lazyLoader={searchUsersWithDetails}
|
|
130
|
+
keyField="id"
|
|
131
|
+
textField="name"
|
|
132
|
+
itemRender={UserItemRenderer}
|
|
133
|
+
onChange={handleUserChange}
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
<!-- UserItemRenderer.svelte -->
|
|
137
|
+
<script lang="ts">
|
|
138
|
+
export let item: User;
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<div class="user-item">
|
|
142
|
+
<div class="user-avatar">
|
|
143
|
+
<img src={item.avatar} alt={item.name} />
|
|
144
|
+
</div>
|
|
145
|
+
<div class="user-info">
|
|
146
|
+
<div class="user-name">{item.name}</div>
|
|
147
|
+
<div class="user-email">{item.email}</div>
|
|
148
|
+
<div class="user-department">{item.department}</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Search with Caching
|
|
154
|
+
```svelte
|
|
155
|
+
<script lang="ts">
|
|
156
|
+
interface SearchCache {
|
|
157
|
+
[key: string]: {
|
|
158
|
+
data: any[];
|
|
159
|
+
timestamp: number;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let searchCache: SearchCache = {};
|
|
164
|
+
const cacheTimeout = 5 * 60 * 1000; // 5 minute cache
|
|
165
|
+
|
|
166
|
+
const cachedSearchLoader: LazyLoader = async (keyword: string, pageNo: number) => {
|
|
167
|
+
const cacheKey = `${keyword}-${pageNo}`;
|
|
168
|
+
const cached = searchCache[cacheKey];
|
|
169
|
+
|
|
170
|
+
// Check cache
|
|
171
|
+
if (cached && Date.now() - cached.timestamp < cacheTimeout) {
|
|
172
|
+
console.log('Using cached data');
|
|
173
|
+
return {
|
|
174
|
+
list: cached.data,
|
|
175
|
+
hasMore: false
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Fetch from API
|
|
180
|
+
const response = await fetch(`/api/search?q=${keyword}&page=${pageNo}`);
|
|
181
|
+
const result = await response.json();
|
|
182
|
+
|
|
183
|
+
// Cache result
|
|
184
|
+
searchCache[cacheKey] = {
|
|
185
|
+
data: result.list,
|
|
186
|
+
timestamp: Date.now()
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return result;
|
|
190
|
+
};
|
|
191
|
+
</script>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Best Practices
|
|
195
|
+
|
|
196
|
+
### 1. Debounced Search
|
|
197
|
+
The component internally implements debouncing with a default delay of 500ms before executing search.
|
|
198
|
+
|
|
199
|
+
### 2. Error Handling
|
|
200
|
+
```svelte
|
|
201
|
+
<script lang="ts">
|
|
202
|
+
const robustSearchLoader: LazyLoader = async (keyword: string, pageNo: number) => {
|
|
203
|
+
try {
|
|
204
|
+
const response = await fetch(`/api/search?q=${keyword}&page=${pageNo}`);
|
|
205
|
+
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
throw new Error(`Search failed: ${response.status}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return await response.json();
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('Search error:', error);
|
|
213
|
+
return {
|
|
214
|
+
list: [],
|
|
215
|
+
hasMore: false
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
</script>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 3. Paginated Loading
|
|
223
|
+
```svelte
|
|
224
|
+
<script lang="ts">
|
|
225
|
+
const paginatedLoader: LazyLoader = async (keyword: string, pageNo: number) => {
|
|
226
|
+
const pageSize = 20;
|
|
227
|
+
const response = await fetch(
|
|
228
|
+
`/api/search?q=${keyword}&page=${pageNo}&size=${pageSize}`
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const data = await response.json();
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
list: data.items,
|
|
235
|
+
hasMore: data.total > pageNo * pageSize
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
</script>
|
|
239
|
+
```
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# LookupEditor Component
|
|
2
|
+
|
|
3
|
+
Lookup editor component that performs complex lookup and selection operations through modal dialogs.
|
|
4
|
+
|
|
5
|
+
## Type Definitions
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
type OnActionHandler = () => void;
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Properties
|
|
12
|
+
|
|
13
|
+
| Property | Type | Default | Description |
|
|
14
|
+
|----------|------|---------|-------------|
|
|
15
|
+
| `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | Appearance variant |
|
|
16
|
+
| `disabled` | `boolean` | `false` | Whether disabled |
|
|
17
|
+
| `readonly` | `boolean` | `false` | Whether read-only |
|
|
18
|
+
| `compact` | `boolean` | `false` | Whether compact mode |
|
|
19
|
+
| `mandatory` | `boolean` | `false` | Whether required |
|
|
20
|
+
| `value` | `any` | `null` | Selected value |
|
|
21
|
+
| `text` | `string` | `''` | Display text |
|
|
22
|
+
| `placeholder` | `string` | `''` | Placeholder text |
|
|
23
|
+
| `displayMode` | `DisplayMode` | `DisplayMode.Edit` | Display mode |
|
|
24
|
+
| `style` | `string` | `''` | Custom styles |
|
|
25
|
+
| `onChange` | `OnChangeHandler<any>` | - | Value change callback |
|
|
26
|
+
| `onAction` | `OnActionHandler` | - | Lookup action callback |
|
|
27
|
+
|
|
28
|
+
## Usage Examples
|
|
29
|
+
|
|
30
|
+
```svelte
|
|
31
|
+
<script lang="ts">
|
|
32
|
+
import LookupEditor from '@ticatec/uniface-element/LookupEditor';
|
|
33
|
+
import CustomerLookupModal from './CustomerLookupModal.svelte';
|
|
34
|
+
import ProductLookupModal from './ProductLookupModal.svelte';
|
|
35
|
+
|
|
36
|
+
interface Customer {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
code: string;
|
|
40
|
+
email: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Product {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
price: number;
|
|
47
|
+
category: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let selectedCustomer: string | null = null;
|
|
51
|
+
let customerText: string = '';
|
|
52
|
+
let selectedProduct: string | null = null;
|
|
53
|
+
let productText: string = '';
|
|
54
|
+
let showCustomerModal: boolean = false;
|
|
55
|
+
let showProductModal: boolean = false;
|
|
56
|
+
|
|
57
|
+
// Customer lookup
|
|
58
|
+
const handleCustomerLookup = (): void => {
|
|
59
|
+
showCustomerModal = true;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handleCustomerSelect = (customer: Customer): void => {
|
|
63
|
+
selectedCustomer = customer.id;
|
|
64
|
+
customerText = `${customer.name} (${customer.code})`;
|
|
65
|
+
showCustomerModal = false;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handleCustomerChange = (value: any): void => {
|
|
69
|
+
selectedCustomer = value;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Product lookup
|
|
73
|
+
const handleProductLookup = (): void => {
|
|
74
|
+
showProductModal = true;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleProductSelect = (product: Product): void => {
|
|
78
|
+
selectedProduct = product.id;
|
|
79
|
+
productText = `${product.name} - $${product.price}`;
|
|
80
|
+
showProductModal = false;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleProductChange = (value: any): void => {
|
|
84
|
+
selectedProduct = value;
|
|
85
|
+
};
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<!-- Customer lookup -->
|
|
89
|
+
<LookupEditor
|
|
90
|
+
placeholder="Please select customer"
|
|
91
|
+
bind:value={selectedCustomer}
|
|
92
|
+
bind:text={customerText}
|
|
93
|
+
onAction={handleCustomerLookup}
|
|
94
|
+
onChange={handleCustomerChange}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<!-- Product lookup -->
|
|
98
|
+
<LookupEditor
|
|
99
|
+
placeholder="Please select product"
|
|
100
|
+
mandatory={true}
|
|
101
|
+
bind:value={selectedProduct}
|
|
102
|
+
bind:text={productText}
|
|
103
|
+
onAction={handleProductLookup}
|
|
104
|
+
onChange={handleProductChange}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
<!-- Customer lookup modal -->
|
|
108
|
+
{#if showCustomerModal}
|
|
109
|
+
<CustomerLookupModal
|
|
110
|
+
onSelect={handleCustomerSelect}
|
|
111
|
+
onClose={() => showCustomerModal = false}
|
|
112
|
+
/>
|
|
113
|
+
{/if}
|
|
114
|
+
|
|
115
|
+
<!-- Product lookup modal -->
|
|
116
|
+
{#if showProductModal}
|
|
117
|
+
<ProductLookupModal
|
|
118
|
+
onSelect={handleProductSelect}
|
|
119
|
+
onClose={() => showProductModal = false}
|
|
120
|
+
/>
|
|
121
|
+
{/if}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Advanced Usage
|
|
125
|
+
|
|
126
|
+
### Multi-step Lookup Flow
|
|
127
|
+
```svelte
|
|
128
|
+
<script lang="ts">
|
|
129
|
+
interface Supplier {
|
|
130
|
+
id: string;
|
|
131
|
+
name: string;
|
|
132
|
+
country: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface SupplierProduct {
|
|
136
|
+
id: string;
|
|
137
|
+
name: string;
|
|
138
|
+
supplier_id: string;
|
|
139
|
+
price: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let selectedSupplier: string | null = null;
|
|
143
|
+
let supplierText: string = '';
|
|
144
|
+
let selectedSupplierProduct: string | null = null;
|
|
145
|
+
let supplierProductText: string = '';
|
|
146
|
+
let showSupplierModal: boolean = false;
|
|
147
|
+
let showSupplierProductModal: boolean = false;
|
|
148
|
+
|
|
149
|
+
const handleSupplierLookup = (): void => {
|
|
150
|
+
showSupplierModal = true;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handleSupplierSelect = (supplier: Supplier): void => {
|
|
154
|
+
selectedSupplier = supplier.id;
|
|
155
|
+
supplierText = `${supplier.name} (${supplier.country})`;
|
|
156
|
+
showSupplierModal = false;
|
|
157
|
+
|
|
158
|
+
// Reset supplier product selection
|
|
159
|
+
selectedSupplierProduct = null;
|
|
160
|
+
supplierProductText = '';
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const handleSupplierProductLookup = (): void => {
|
|
164
|
+
if (!selectedSupplier) {
|
|
165
|
+
alert('Please select supplier first');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
showSupplierProductModal = true;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const handleSupplierProductSelect = (product: SupplierProduct): void => {
|
|
172
|
+
selectedSupplierProduct = product.id;
|
|
173
|
+
supplierProductText = `${product.name} - $${product.price}`;
|
|
174
|
+
showSupplierProductModal = false;
|
|
175
|
+
};
|
|
176
|
+
</script>
|
|
177
|
+
|
|
178
|
+
<div class="lookup-flow">
|
|
179
|
+
<div class="form-field">
|
|
180
|
+
<label>Supplier:</label>
|
|
181
|
+
<LookupEditor
|
|
182
|
+
placeholder="Please select supplier"
|
|
183
|
+
bind:value={selectedSupplier}
|
|
184
|
+
bind:text={supplierText}
|
|
185
|
+
onAction={handleSupplierLookup}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="form-field">
|
|
190
|
+
<label>Supplier Product:</label>
|
|
191
|
+
<LookupEditor
|
|
192
|
+
placeholder="Please select product"
|
|
193
|
+
disabled={!selectedSupplier}
|
|
194
|
+
bind:value={selectedSupplierProduct}
|
|
195
|
+
bind:text={supplierProductText}
|
|
196
|
+
onAction={handleSupplierProductLookup}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Lookup with Caching
|
|
203
|
+
```svelte
|
|
204
|
+
<script lang="ts">
|
|
205
|
+
interface CacheEntry<T> {
|
|
206
|
+
data: T;
|
|
207
|
+
timestamp: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
class LookupCache {
|
|
211
|
+
private cache = new Map<string, CacheEntry<any>>();
|
|
212
|
+
private ttl = 5 * 60 * 1000; // 5 minute cache
|
|
213
|
+
|
|
214
|
+
get<T>(key: string): T | null {
|
|
215
|
+
const entry = this.cache.get(key);
|
|
216
|
+
if (!entry) return null;
|
|
217
|
+
|
|
218
|
+
if (Date.now() - entry.timestamp > this.ttl) {
|
|
219
|
+
this.cache.delete(key);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return entry.data;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
set<T>(key: string, data: T): void {
|
|
227
|
+
this.cache.set(key, {
|
|
228
|
+
data,
|
|
229
|
+
timestamp: Date.now()
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
clear(): void {
|
|
234
|
+
this.cache.clear();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const cache = new LookupCache();
|
|
239
|
+
|
|
240
|
+
let selectedUser: string | null = null;
|
|
241
|
+
let userText: string = '';
|
|
242
|
+
let showUserModal: boolean = false;
|
|
243
|
+
|
|
244
|
+
const handleUserLookup = (): void => {
|
|
245
|
+
// Check cache
|
|
246
|
+
const cachedUsers = cache.get('users');
|
|
247
|
+
if (cachedUsers) {
|
|
248
|
+
console.log('Using cached user data');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
showUserModal = true;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const handleUserSelect = (user: any): void => {
|
|
255
|
+
selectedUser = user.id;
|
|
256
|
+
userText = `${user.name} (${user.email})`;
|
|
257
|
+
|
|
258
|
+
// Cache user data
|
|
259
|
+
cache.set(`user:${user.id}`, user);
|
|
260
|
+
|
|
261
|
+
showUserModal = false;
|
|
262
|
+
};
|
|
263
|
+
</script>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### External API Integration
|
|
267
|
+
```svelte
|
|
268
|
+
<script lang="ts">
|
|
269
|
+
interface ApiResponse<T> {
|
|
270
|
+
data: T[];
|
|
271
|
+
total: number;
|
|
272
|
+
page: number;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
interface ApiUser {
|
|
276
|
+
id: number;
|
|
277
|
+
name: string;
|
|
278
|
+
email: string;
|
|
279
|
+
department: string;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let selectedApiUser: number | null = null;
|
|
283
|
+
let apiUserText: string = '';
|
|
284
|
+
let showApiUserModal: boolean = false;
|
|
285
|
+
let isLoading: boolean = false;
|
|
286
|
+
|
|
287
|
+
const searchUsers = async (query: string, page: number = 1): Promise<ApiResponse<ApiUser>> => {
|
|
288
|
+
const response = await fetch(`/api/users/search?q=${encodeURIComponent(query)}&page=${page}`);
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
throw new Error('Failed to search users');
|
|
291
|
+
}
|
|
292
|
+
return response.json();
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const handleApiUserLookup = (): void => {
|
|
296
|
+
showApiUserModal = true;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const handleApiUserSelect = async (userId: number): Promise<void> => {
|
|
300
|
+
isLoading = true;
|
|
301
|
+
try {
|
|
302
|
+
const response = await fetch(`/api/users/${userId}`);
|
|
303
|
+
const user: ApiUser = await response.json();
|
|
304
|
+
|
|
305
|
+
selectedApiUser = user.id;
|
|
306
|
+
apiUserText = `${user.name} - ${user.department}`;
|
|
307
|
+
showApiUserModal = false;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error('Failed to fetch user details:', error);
|
|
310
|
+
} finally {
|
|
311
|
+
isLoading = false;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
</script>
|
|
315
|
+
|
|
316
|
+
<LookupEditor
|
|
317
|
+
placeholder="Please select API user"
|
|
318
|
+
disabled={isLoading}
|
|
319
|
+
bind:value={selectedApiUser}
|
|
320
|
+
bind:text={apiUserText}
|
|
321
|
+
onAction={handleApiUserLookup}
|
|
322
|
+
/>
|
|
323
|
+
|
|
324
|
+
{#if isLoading}
|
|
325
|
+
<div class="loading-indicator">Loading...</div>
|
|
326
|
+
{/if}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Best Practices
|
|
330
|
+
|
|
331
|
+
### 1. Provide Clear Display Text
|
|
332
|
+
```typescript
|
|
333
|
+
const formatDisplayText = (item: any): string => {
|
|
334
|
+
return `${item.name} (${item.code}) - ${item.category}`;
|
|
335
|
+
};
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### 2. Handle Required Validation
|
|
339
|
+
```svelte
|
|
340
|
+
<script lang="ts">
|
|
341
|
+
let value: string | null = null;
|
|
342
|
+
let error: string = '';
|
|
343
|
+
|
|
344
|
+
const validate = (): boolean => {
|
|
345
|
+
if (!value) {
|
|
346
|
+
error = 'This field is required';
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
error = '';
|
|
350
|
+
return true;
|
|
351
|
+
};
|
|
352
|
+
</script>
|
|
353
|
+
|
|
354
|
+
<LookupEditor
|
|
355
|
+
mandatory={true}
|
|
356
|
+
bind:value={value}
|
|
357
|
+
onAction={handleLookup}
|
|
358
|
+
/>
|
|
359
|
+
{#if error}
|
|
360
|
+
<div class="error-message">{error}</div>
|
|
361
|
+
{/if}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 3. Responsive Modal Design
|
|
365
|
+
```svelte
|
|
366
|
+
<script lang="ts">
|
|
367
|
+
import { onMount } from 'svelte';
|
|
368
|
+
|
|
369
|
+
let isMobile: boolean = false;
|
|
370
|
+
|
|
371
|
+
const checkMobile = (): void => {
|
|
372
|
+
isMobile = window.innerWidth < 768;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
onMount(() => {
|
|
376
|
+
checkMobile();
|
|
377
|
+
window.addEventListener('resize', checkMobile);
|
|
378
|
+
return () => window.removeEventListener('resize', checkMobile);
|
|
379
|
+
});
|
|
380
|
+
</script>
|
|
381
|
+
|
|
382
|
+
{#if showModal}
|
|
383
|
+
<LookupModal
|
|
384
|
+
fullscreen={isMobile}
|
|
385
|
+
onSelect={handleSelect}
|
|
386
|
+
onClose={() => showModal = false}
|
|
387
|
+
/>
|
|
388
|
+
{/if}
|
|
389
|
+
```
|