x4js 2.0.11 → 2.0.13
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 +15 -15
- package/lib/README.txt +15 -15
- package/lib/cjs/x4.css +1 -1
- package/lib/cjs/x4.js +1 -1
- package/lib/esm/x4.css +1 -1
- package/lib/esm/x4.mjs +1 -1
- package/lib/src/components/base.scss +25 -26
- package/lib/src/components/boxes/boxes.module.scss +37 -37
- package/lib/src/components/boxes/boxes.ts +129 -125
- package/lib/src/components/breadcrumb/breadcrumb.scss +28 -0
- package/lib/src/components/breadcrumb/breadcrumb.ts +84 -0
- package/lib/src/components/breadcrumb/chevron-right.svg +1 -0
- package/lib/src/components/btngroup/btngroup.module.scss +28 -28
- package/lib/src/components/btngroup/btngroup.ts +119 -101
- package/lib/src/components/button/button.module.scss +154 -153
- package/lib/src/components/button/button.ts +117 -117
- package/lib/src/components/calendar/calendar.module.scss +162 -162
- package/lib/src/components/calendar/calendar.ts +326 -325
- package/lib/src/components/checkbox/check.svg +3 -3
- package/lib/src/components/checkbox/checkbox.module.scss +141 -141
- package/lib/src/components/checkbox/checkbox.ts +125 -124
- package/lib/src/components/colorinput/colorinput.module.scss +64 -64
- package/lib/src/components/colorinput/colorinput.ts +90 -87
- package/lib/src/components/colorpicker/colorpicker.module.scss +132 -132
- package/lib/src/components/colorpicker/colorpicker.ts +481 -476
- package/lib/src/components/combobox/combobox.module.scss +123 -120
- package/lib/src/components/combobox/combobox.ts +192 -190
- package/lib/src/components/combobox/updown.svg +3 -3
- package/lib/src/components/components.ts +34 -0
- package/lib/src/components/dialog/dialog.module.scss +71 -71
- package/lib/src/components/dialog/dialog.ts +94 -92
- package/lib/src/components/form/form.module.scss +34 -34
- package/lib/src/components/form/form.ts +41 -36
- package/lib/src/components/grid/datastore.ts +1298 -0
- package/lib/src/components/grid/gridview.ts +1108 -0
- package/lib/src/components/grid/memdb.ts +325 -0
- package/lib/src/components/header/header.module.scss +39 -39
- package/lib/src/components/header/header.ts +129 -123
- package/lib/src/components/icon/icon.module.scss +29 -29
- package/lib/src/components/icon/icon.ts +136 -134
- package/lib/src/components/image/image.module.scss +20 -20
- package/lib/src/components/image/image.ts +68 -66
- package/lib/src/components/input/input.module.scss +69 -69
- package/lib/src/components/input/input.ts +275 -274
- package/lib/src/components/label/label.module.scss +58 -52
- package/lib/src/components/label/label.ts +64 -55
- package/lib/src/components/link/link.ts +78 -0
- package/lib/src/components/listbox/listbox.module.scss +103 -103
- package/lib/src/components/listbox/listbox.ts +431 -427
- package/lib/src/components/menu/menu.module.scss +107 -107
- package/lib/src/components/menu/menu.ts +171 -168
- package/lib/src/components/messages/messages.module.scss +48 -47
- package/lib/src/components/messages/messages.ts +68 -63
- package/lib/src/components/normalize.scss +386 -386
- package/lib/src/components/notification/notification.module.scss +81 -81
- package/lib/src/components/notification/notification.ts +109 -108
- package/lib/src/components/panel/panel.module.scss +47 -47
- package/lib/src/components/panel/panel.ts +57 -56
- package/lib/src/components/popup/popup.module.scss +43 -43
- package/lib/src/components/popup/popup.ts +396 -395
- package/lib/src/components/progress/progress.module.scss +56 -56
- package/lib/src/components/progress/progress.ts +43 -42
- package/lib/src/components/rating/rating.module.scss +22 -22
- package/lib/src/components/rating/rating.ts +131 -125
- package/lib/src/components/shared.scss +90 -76
- package/lib/src/components/sizers/sizer.module.scss +89 -89
- package/lib/src/components/sizers/sizer.ts +123 -119
- package/lib/src/components/slider/slider.module.scss +70 -70
- package/lib/src/components/slider/slider.ts +147 -142
- package/lib/src/components/switch/switch.module.scss +126 -126
- package/lib/src/components/switch/switch.ts +61 -55
- package/lib/src/components/tabs/tabs.module.scss +46 -46
- package/lib/src/components/tabs/tabs.ts +168 -157
- package/lib/src/components/textarea/textarea.module.scss +59 -59
- package/lib/src/components/textarea/textarea.ts +60 -54
- package/lib/src/components/textedit/textedit.module.scss +113 -113
- package/lib/src/components/textedit/textedit.ts +83 -82
- package/lib/src/components/themes.scss +81 -77
- package/lib/src/components/tooltips/tooltips.scss +50 -50
- package/lib/src/components/tooltips/tooltips.ts +103 -102
- package/lib/src/components/treeview/treeview.module.scss +115 -115
- package/lib/src/components/treeview/treeview.ts +410 -403
- package/lib/src/components/viewport/viewport.module.scss +24 -24
- package/lib/src/components/viewport/viewport.ts +41 -38
- package/lib/src/core/component.ts +1002 -979
- package/lib/src/core/core_application.ts +44 -0
- package/lib/src/core/core_colors.ts +249 -249
- package/lib/src/core/core_dom.ts +471 -471
- package/lib/src/core/core_dragdrop.ts +200 -200
- package/lib/src/core/core_element.ts +97 -97
- package/lib/src/core/core_events.ts +149 -149
- package/lib/src/core/core_i18n.ts +377 -377
- package/lib/src/core/core_router.ts +221 -221
- package/lib/src/core/core_styles.ts +214 -214
- package/lib/src/core/core_svg.ts +550 -550
- package/lib/src/core/core_tools.ts +688 -673
- package/lib/src/demo/assets/radio.svg +3 -3
- package/lib/src/demo/index.html +11 -11
- package/lib/src/demo/main.scss +21 -21
- package/lib/src/demo/main.tsx +323 -323
- package/lib/src/types/scss.d.ts +4 -4
- package/lib/src/types/x4react.d.ts +8 -8
- package/lib/src/x4.scss +18 -18
- package/lib/src/x4.ts +31 -60
- package/lib/styles/x4.css +1 -1
- package/lib/types/x4js.d.ts +100 -49
- package/package.json +2 -3
- package/src/x4.ts +31 -60
- package/lib/output.d.ts +0 -1472
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { EventSource } from '@core/core_events.js';
|
|
2
|
+
|
|
3
|
+
type RecordID = any; // Define RecordID as any
|
|
4
|
+
type FieldType = string | number | boolean | Date;
|
|
5
|
+
|
|
6
|
+
// Define a type for field definitions with additional parameters
|
|
7
|
+
type FieldDef =
|
|
8
|
+
| { type: 'string'; minLength?: number; maxLength?: number; format?: 'email'; required?: boolean }
|
|
9
|
+
| { type: 'number'; min?: number; max?: number; required?: boolean }
|
|
10
|
+
| { type: 'boolean'; required?: boolean };
|
|
11
|
+
|
|
12
|
+
// Define a type for records
|
|
13
|
+
type Record = {
|
|
14
|
+
id: RecordID;
|
|
15
|
+
[key: string]: FieldType;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Model class
|
|
19
|
+
class Model {
|
|
20
|
+
private structure: { [key: string]: FieldDef };
|
|
21
|
+
|
|
22
|
+
constructor(structure: { [key: string]: FieldDef }) {
|
|
23
|
+
this.structure = { ...structure };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Validate data based on the model
|
|
27
|
+
validate(data: { [key: string]: any }): void {
|
|
28
|
+
for (const key in this.structure) {
|
|
29
|
+
const field = this.structure[key];
|
|
30
|
+
const value = data[key];
|
|
31
|
+
|
|
32
|
+
if (field.required && value === undefined) {
|
|
33
|
+
throw new Error(`Field ${key} is required`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if ( value !== undefined && typeof value !== field.type) {
|
|
37
|
+
throw new Error(`Field ${key} has an incorrect type`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (field.type === 'string' && value !== undefined) {
|
|
41
|
+
if (field.minLength !== undefined && value.length < field.minLength) {
|
|
42
|
+
throw new Error(`Field ${key} is shorter than the minimum length of ${field.minLength}`);
|
|
43
|
+
}
|
|
44
|
+
if (field.maxLength !== undefined && value.length > field.maxLength) {
|
|
45
|
+
throw new Error(`Field ${key} is longer than the maximum length of ${field.maxLength}`);
|
|
46
|
+
}
|
|
47
|
+
if (field.format === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
48
|
+
throw new Error(`Field ${key} is not a valid email`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (field.type === 'number' && value !== undefined) {
|
|
53
|
+
if (field.min !== undefined && value < field.min) {
|
|
54
|
+
throw new Error(`Field ${key} is less than the minimum value of ${field.min}`);
|
|
55
|
+
}
|
|
56
|
+
if (field.max !== undefined && value > field.max) {
|
|
57
|
+
throw new Error(`Field ${key} is greater than the maximum value of ${field.max}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Get the structure of the model
|
|
64
|
+
getStructure(): { [key: string]: FieldDef } {
|
|
65
|
+
return this.structure;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create a record based on the model
|
|
69
|
+
createRecord(data: { [key: string]: any }, copy: boolean): Record {
|
|
70
|
+
this.validate(data);
|
|
71
|
+
|
|
72
|
+
const recordData = copy ? { ...data } : data;
|
|
73
|
+
|
|
74
|
+
return recordData as Record;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Get the value of a specific field in a record
|
|
78
|
+
getFieldValue(record: Record, fieldName: string): FieldType | undefined {
|
|
79
|
+
if (fieldName in this.structure) {
|
|
80
|
+
return record[fieldName];
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Field ${fieldName} does not exist in the model`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// MemoryDatabase class
|
|
87
|
+
class MemoryDatabase extends EventSource {
|
|
88
|
+
private records: Record[] = [];
|
|
89
|
+
private model: Model;
|
|
90
|
+
|
|
91
|
+
constructor(model: Model) {
|
|
92
|
+
super();
|
|
93
|
+
this.model = model;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Add a new record to the records array
|
|
97
|
+
addRecord(record: Record): void {
|
|
98
|
+
this.records.push(record);
|
|
99
|
+
this.emit('recordAdded', record);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Get the count of records
|
|
103
|
+
getCount(): number {
|
|
104
|
+
return this.records.length;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Get a record by its index
|
|
108
|
+
getRecordByIndex(index: number): Record | null {
|
|
109
|
+
return this.records[index] || null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get a record by its ID
|
|
113
|
+
getRecordById(id: RecordID): Record | null {
|
|
114
|
+
return this.records.find(record => record.id === id) || null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Get all records
|
|
118
|
+
getAllRecords(): Record[] {
|
|
119
|
+
return this.records;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Remove a record by its ID
|
|
123
|
+
removeRecordById(id: RecordID): void {
|
|
124
|
+
const index = this.records.findIndex(record => record.id === id);
|
|
125
|
+
if (index !== -1) {
|
|
126
|
+
const removedRecord = this.records.splice(index, 1)[0];
|
|
127
|
+
this.emit('recordRemoved', removedRecord);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Remove a record by its index
|
|
132
|
+
removeRecordByIndex(index: number): void {
|
|
133
|
+
if (index >= 0 && index < this.records.length) {
|
|
134
|
+
const removedRecord = this.records.splice(index, 1)[0];
|
|
135
|
+
this.emit('recordRemoved', removedRecord);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// DataView class
|
|
141
|
+
class DataView {
|
|
142
|
+
private database: MemoryDatabase;
|
|
143
|
+
private view: Int32Array = new Int32Array(0);
|
|
144
|
+
private currentFilter: { field: string, valueOrPattern: any } | null = null;
|
|
145
|
+
private currentSort: { field: string, ascending: boolean } | null = null;
|
|
146
|
+
|
|
147
|
+
constructor(database: MemoryDatabase) {
|
|
148
|
+
this.database = database;
|
|
149
|
+
this.database.on('recordAdded', () => this.handleDatabaseChange());
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Initialize the view with all record indexes
|
|
153
|
+
initializeView(): void {
|
|
154
|
+
const recordCount = this.database.getCount();
|
|
155
|
+
this.view = Int32Array.from({ length: recordCount }, (_, i) => i);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Sort the view by a specified field
|
|
159
|
+
sortView(field: string, ascending: boolean = true): void {
|
|
160
|
+
this.currentSort = { field, ascending };
|
|
161
|
+
this.applySort();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private applySort(): void {
|
|
165
|
+
if (this.currentSort) {
|
|
166
|
+
const { field, ascending } = this.currentSort;
|
|
167
|
+
this.view = Int32Array.from(this.view).sort((a, b) => {
|
|
168
|
+
const recordA = this.database.getRecordByIndex(a);
|
|
169
|
+
const recordB = this.database.getRecordByIndex(b);
|
|
170
|
+
|
|
171
|
+
if (recordA && recordB) {
|
|
172
|
+
const valueA = recordA[field];
|
|
173
|
+
const valueB = recordB[field];
|
|
174
|
+
|
|
175
|
+
if (valueA instanceof Date && valueB instanceof Date) {
|
|
176
|
+
return ascending ? valueA.getTime() - valueB.getTime() : valueB.getTime() - valueA.getTime();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (valueA < valueB) {
|
|
180
|
+
return ascending ? -1 : 1;
|
|
181
|
+
}
|
|
182
|
+
if (valueA > valueB) {
|
|
183
|
+
return ascending ? 1 : -1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return 0;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Filter the view by a specified field and value or regex pattern
|
|
192
|
+
filterView(field: string, valueOrPattern: any): void {
|
|
193
|
+
this.currentFilter = { field, valueOrPattern };
|
|
194
|
+
this.applyFilter();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private applyFilter(): void {
|
|
198
|
+
if (this.currentFilter) {
|
|
199
|
+
const { field, valueOrPattern } = this.currentFilter;
|
|
200
|
+
const records = this.database.getAllRecords();
|
|
201
|
+
if (valueOrPattern instanceof RegExp) {
|
|
202
|
+
this.view = Int32Array.from(
|
|
203
|
+
records
|
|
204
|
+
.map((record, index) => ({ record, index }))
|
|
205
|
+
.filter(({ record }) => valueOrPattern.test(record[field] as string))
|
|
206
|
+
.map(({ index }) => index)
|
|
207
|
+
);
|
|
208
|
+
} else {
|
|
209
|
+
this.view = Int32Array.from(
|
|
210
|
+
records
|
|
211
|
+
.map((record, index) => ({ record, index }))
|
|
212
|
+
.filter(({ record }) => record[field] === valueOrPattern)
|
|
213
|
+
.map(({ index }) => index)
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Handle database change event
|
|
220
|
+
private handleDatabaseChange(): void {
|
|
221
|
+
this.initializeView();
|
|
222
|
+
if (this.currentFilter) {
|
|
223
|
+
this.applyFilter();
|
|
224
|
+
}
|
|
225
|
+
if (this.currentSort) {
|
|
226
|
+
this.applySort();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Advanced filtering similar to LokiJS
|
|
231
|
+
advancedFilterView(filter: (record: Record) => boolean): void {
|
|
232
|
+
const records = this.database.getAllRecords();
|
|
233
|
+
this.view = Int32Array.from(
|
|
234
|
+
records
|
|
235
|
+
.map((record, index) => ({ record, index }))
|
|
236
|
+
.filter(({ record }) => filter(record))
|
|
237
|
+
.map(({ index }) => index)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Clear all filters and reset the view to include all records
|
|
242
|
+
clearFilter(): void {
|
|
243
|
+
this.currentFilter = null;
|
|
244
|
+
this.initializeView();
|
|
245
|
+
if (this.currentSort) {
|
|
246
|
+
this.applySort();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Get the records in the view
|
|
251
|
+
getViewRecords(): Record[] {
|
|
252
|
+
return Array.from(this.view).map(index => this.database.getRecordByIndex(index)).filter(record => record !== null) as Record[];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Get the number of records in the view
|
|
256
|
+
getRecordCount(): number {
|
|
257
|
+
return this.view.length;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Get a record by its index in the view
|
|
261
|
+
getRecordByIndex(index: number): Record | null {
|
|
262
|
+
if (index >= 0 && index < this.view.length) {
|
|
263
|
+
return this.database.getRecordByIndex(this.view[index]);
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Get a record by its ID in the view
|
|
269
|
+
getRecordById(id: RecordID): Record | null {
|
|
270
|
+
const recordIndex = this.database.getAllRecords().findIndex(record => record.id === id);
|
|
271
|
+
if (recordIndex !== -1 && this.view.includes(recordIndex)) {
|
|
272
|
+
return this.database.getRecordByIndex(recordIndex);
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// RemoteDataLoader class
|
|
279
|
+
class RemoteDataLoader {
|
|
280
|
+
private database: MemoryDatabase;
|
|
281
|
+
private url: string;
|
|
282
|
+
|
|
283
|
+
constructor(database: MemoryDatabase, url: string) {
|
|
284
|
+
this.database = database;
|
|
285
|
+
this.url = url;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Fetch records from the given URL and add them to the database
|
|
289
|
+
async fetchRecords(): Promise<void> {
|
|
290
|
+
try {
|
|
291
|
+
const response = await fetch(this.url);
|
|
292
|
+
const data = await response.json();
|
|
293
|
+
|
|
294
|
+
if (!Array.isArray(data)) {
|
|
295
|
+
throw new Error('Response is not an array');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
data.forEach(item => {
|
|
299
|
+
const record = this.database.model.createRecord(item, true);
|
|
300
|
+
this.database.addRecord(record);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
this.database.emit('dataLoaded');
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error('Error fetching records:', error);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Example usage
|
|
311
|
+
const userModel = new Model({
|
|
312
|
+
name: { type: 'string', minLength: 3, maxLength: 50, required: true },
|
|
313
|
+
age: { type: 'number', min: 0, max: 120, required: true },
|
|
314
|
+
isActive: { type: 'boolean', required: true },
|
|
315
|
+
email: { type: 'string', format: 'email', required: true }
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const db = new MemoryDatabase(userModel);
|
|
319
|
+
const remoteDataLoader = new RemoteDataLoader(db, 'https://api.example.com/users');
|
|
320
|
+
|
|
321
|
+
remoteDataLoader.fetchRecords().then(() => {
|
|
322
|
+
const dataView = new DataView(db);
|
|
323
|
+
dataView.initializeView();
|
|
324
|
+
console.log(dataView.getViewRecords());
|
|
325
|
+
});
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
@use "../shared.scss";
|
|
2
|
-
|
|
3
|
-
:root {
|
|
4
|
-
--header-background-hover: rgba(100,100,100,0.1);
|
|
5
|
-
--header-sizer-hover: var( --border-hover );
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.x4header {
|
|
9
|
-
@extend .flex;
|
|
10
|
-
width: 100%;
|
|
11
|
-
|
|
12
|
-
overflow: hidden;
|
|
13
|
-
min-height: 2em;
|
|
14
|
-
border-bottom: 1px solid var( --border );
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.cell {
|
|
18
|
-
border-bottom: 1px solid transparent;
|
|
19
|
-
overflow: hidden;
|
|
20
|
-
min-width: 3rem;
|
|
21
|
-
|
|
22
|
-
transition: border-color 0.5s ease;
|
|
23
|
-
padding: 4px;
|
|
24
|
-
|
|
25
|
-
span {
|
|
26
|
-
white-space: nowrap;
|
|
27
|
-
text-overflow: ellipsis;
|
|
28
|
-
overflow: hidden;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
&:hover {
|
|
32
|
-
background-color: var( --header-background-hover );
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.x4csizer:hover {
|
|
36
|
-
border-right: 1px solid var( --header-sizer-hover );
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
1
|
+
@use "../shared.scss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--header-background-hover: rgba(100,100,100,0.1);
|
|
5
|
+
--header-sizer-hover: var( --border-hover );
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.x4header {
|
|
9
|
+
@extend .flex;
|
|
10
|
+
width: 100%;
|
|
11
|
+
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
min-height: 2em;
|
|
14
|
+
border-bottom: 1px solid var( --border );
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
.cell {
|
|
18
|
+
border-bottom: 1px solid transparent;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
min-width: 3rem;
|
|
21
|
+
|
|
22
|
+
transition: border-color 0.5s ease;
|
|
23
|
+
padding: 4px;
|
|
24
|
+
|
|
25
|
+
span {
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
text-overflow: ellipsis;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&:hover {
|
|
32
|
+
background-color: var( --header-background-hover );
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.x4csizer:hover {
|
|
36
|
+
border-right: 1px solid var( --header-sizer-hover );
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
40
|
}
|
|
@@ -1,124 +1,130 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
cell.setInternalData( "width",
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
cell.setInternalData("flex",
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
1
|
+
import { class_ns } from '@core/core_tools.js';
|
|
2
|
+
import { Component, ComponentProps } from '../../core/component.js';
|
|
3
|
+
import { HBox } from '../boxes/boxes.js';
|
|
4
|
+
import { Label } from '../label/label.js';
|
|
5
|
+
import { CSizer } from '../sizers/sizer.js';
|
|
6
|
+
|
|
7
|
+
import "./header.module.scss"
|
|
8
|
+
|
|
9
|
+
interface HeaderItem {
|
|
10
|
+
name: string;
|
|
11
|
+
title: string;
|
|
12
|
+
iconId?: string;
|
|
13
|
+
width?: number; // <0 for flex
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface HeaderProps extends Omit<ComponentProps,"content"> {
|
|
17
|
+
items: HeaderItem[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
@class_ns( "x4" )
|
|
25
|
+
export class Header extends HBox<HeaderProps> {
|
|
26
|
+
|
|
27
|
+
private _els: Component[];
|
|
28
|
+
private _vwp: Component;
|
|
29
|
+
|
|
30
|
+
constructor( props: HeaderProps ) {
|
|
31
|
+
super( props );
|
|
32
|
+
|
|
33
|
+
this._els = props.items?.map( x => {
|
|
34
|
+
const cell = new Label( { cls: "cell", text: x.title, icon: x.iconId } );
|
|
35
|
+
const sizer = new CSizer( "right" );
|
|
36
|
+
|
|
37
|
+
if( x.width>0 ) {
|
|
38
|
+
cell.setStyleValue( "width", x.width+'px' );
|
|
39
|
+
cell.setInternalData( "width", x.width );
|
|
40
|
+
}
|
|
41
|
+
else if( x.width<0 ) {
|
|
42
|
+
cell.setInternalData( "flex", -x.width );
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
cell.setInternalData( "width", 0 );
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
sizer.addDOMEvent( "dblclick", ( e: MouseEvent ) => {
|
|
49
|
+
cell.setInternalData( "flex", 1 );
|
|
50
|
+
this._calc_sizes( );
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
sizer.on( "resize", ( ev ) => {
|
|
54
|
+
//cell.setStyleValue( "flexGrow", "0" );
|
|
55
|
+
cell.setInternalData("flex",0);
|
|
56
|
+
cell.setInternalData("width",ev.size);
|
|
57
|
+
this._calc_sizes( );
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
cell.appendContent( sizer );
|
|
61
|
+
cell.setInternalData( "data", x );
|
|
62
|
+
|
|
63
|
+
return cell;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
this.addDOMEvent( "resized", ( ) => this._on_resize() );
|
|
67
|
+
this.addDOMEvent( "created", ( ) => this._calc_sizes( ) );
|
|
68
|
+
|
|
69
|
+
this._vwp = new HBox( { content: this._els } );
|
|
70
|
+
this.setContent( this._vwp );
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private _calc_sizes( ) {
|
|
74
|
+
|
|
75
|
+
let count = 0;
|
|
76
|
+
let filled = 0;
|
|
77
|
+
|
|
78
|
+
this._els.forEach( c => {
|
|
79
|
+
const flex = c.getInternalData( "flex" );
|
|
80
|
+
if( flex ) {
|
|
81
|
+
count += flex;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
let width = c.getInternalData( "width" );
|
|
85
|
+
if( width==0 ) {
|
|
86
|
+
const rc = c.getBoundingRect( );
|
|
87
|
+
width = Math.ceil( rc.width )+2;
|
|
88
|
+
c.setInternalData( "width", width );
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
filled += width;
|
|
92
|
+
}
|
|
93
|
+
} );
|
|
94
|
+
|
|
95
|
+
const rc = this.getBoundingRect( );
|
|
96
|
+
|
|
97
|
+
let rest = (rc.width-filled);
|
|
98
|
+
const unit = Math.ceil( rest/count );
|
|
99
|
+
|
|
100
|
+
console.log( "filled", filled );
|
|
101
|
+
console.log( "count", count );
|
|
102
|
+
console.log( "rest", rest );
|
|
103
|
+
console.log( "unit", unit );
|
|
104
|
+
|
|
105
|
+
let fullw = 0;
|
|
106
|
+
this._els.forEach( c => {
|
|
107
|
+
let width = 0;
|
|
108
|
+
|
|
109
|
+
const flex = c.getInternalData( "flex" );
|
|
110
|
+
if( flex ) {
|
|
111
|
+
width = Math.min( unit*flex, rest );
|
|
112
|
+
rest -= width;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
width = c.getInternalData( "width" );
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
c.setWidth( width );
|
|
119
|
+
fullw += width;
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
this._vwp.setWidth( fullw );
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private _on_resize( ) {
|
|
126
|
+
this._calc_sizes( );
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
124
130
|
}
|