@bexis2/bexis2-core-ui 0.3.12 → 0.3.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.
Files changed (30) hide show
  1. package/README.md +1 -0
  2. package/dist/components/File/FileUploader.svelte +3 -3
  3. package/dist/components/Table/TableContent.svelte +202 -119
  4. package/dist/components/Table/TableFilter.svelte +146 -102
  5. package/dist/components/Table/TableFilter.svelte.d.ts +2 -3
  6. package/dist/components/Table/TableFilterServer.svelte +274 -0
  7. package/dist/components/Table/TableFilterServer.svelte.d.ts +22 -0
  8. package/dist/components/Table/TablePagination.svelte +72 -39
  9. package/dist/components/Table/TablePaginationServer.svelte +125 -0
  10. package/dist/components/Table/TablePaginationServer.svelte.d.ts +21 -0
  11. package/dist/components/Table/filter.js +40 -78
  12. package/dist/components/Table/shared.d.ts +32 -0
  13. package/dist/components/Table/shared.js +117 -0
  14. package/dist/components/form/DropdownKvP.svelte.d.ts +4 -4
  15. package/dist/components/form/MultiSelect.svelte.d.ts +6 -6
  16. package/dist/models/Enums.d.ts +18 -0
  17. package/dist/models/Enums.js +20 -0
  18. package/dist/models/Models.d.ts +43 -2
  19. package/dist/models/Models.js +28 -1
  20. package/package.json +2 -2
  21. package/src/lib/components/Table/TableContent.svelte +227 -151
  22. package/src/lib/components/Table/TableFilter.svelte +166 -102
  23. package/src/lib/components/Table/TableFilterServer.svelte +310 -0
  24. package/src/lib/components/Table/TablePagination.svelte +75 -39
  25. package/src/lib/components/Table/TablePaginationServer.svelte +133 -0
  26. package/src/lib/components/Table/filter.ts +42 -86
  27. package/src/lib/components/Table/shared.ts +141 -0
  28. package/src/lib/components/file/FileUploader.svelte +3 -3
  29. package/src/lib/models/Enums.ts +22 -0
  30. package/src/lib/models/Models.ts +63 -2
@@ -1,19 +1,18 @@
1
1
  <script lang="ts">
2
2
  import Fa from 'svelte-fa/src/fa.svelte';
3
- import { faFilter } from '@fortawesome/free-solid-svg-icons';
3
+ import { faFilter, faPlus, faXmark } from '@fortawesome/free-solid-svg-icons';
4
4
  import { popup } from '@skeletonlabs/skeleton';
5
5
  import type { PopupSettings } from '@skeletonlabs/skeleton';
6
6
 
7
- export let filterValue;
7
+ import { FilterOptionsEnum } from '$models/Enums';
8
+
8
9
  export let values;
9
10
  export let id;
10
11
  export let tableId;
11
12
  export let toFilterableValueFn: undefined | ((value: any) => any) = undefined;
13
+ export let filterValue;
14
+ export let filters;
12
15
 
13
- let firstOption;
14
- let firstValue;
15
- let secondOption;
16
- let secondValue;
17
16
  // If the filter is applied and the displayed values are filtered
18
17
  let active = false;
19
18
 
@@ -21,84 +20,89 @@
21
20
  const options = {
22
21
  number: [
23
22
  {
24
- value: 'isequal',
23
+ value: FilterOptionsEnum.e,
25
24
  label: 'Is equal to'
26
25
  },
27
26
  {
28
- value: 'isgreaterorequal',
27
+ value: FilterOptionsEnum.gte,
29
28
  label: 'Is greater than or equal to'
30
29
  },
31
30
  {
32
- value: 'isgreater',
31
+ value: FilterOptionsEnum.gt,
33
32
  label: 'Is greater than'
34
33
  },
35
34
  {
36
- value: 'islessorequal',
35
+ value: FilterOptionsEnum.lte,
37
36
  label: 'Is less than or equal to'
38
37
  },
39
38
  {
40
- value: 'isless',
39
+ value: FilterOptionsEnum.lt,
41
40
  label: 'Is less than'
42
41
  },
43
42
  {
44
- value: 'isnotequal',
43
+ value: FilterOptionsEnum.ne,
45
44
  label: 'Is not equal to'
46
45
  }
47
46
  ],
48
47
  string: [
49
48
  {
50
- value: 'contains',
49
+ value: FilterOptionsEnum.c,
51
50
  label: 'Contains'
52
51
  },
53
52
  {
54
- value: 'notcontains',
53
+ value: FilterOptionsEnum.nc,
55
54
  label: 'Does not contain'
56
55
  },
57
56
  {
58
- value: 'isequal',
57
+ value: FilterOptionsEnum.e,
59
58
  label: 'Is equal to'
60
59
  },
61
60
  {
62
- value: 'isnotequal',
61
+ value: FilterOptionsEnum.ne,
63
62
  label: 'Is not equal to'
64
63
  },
65
64
  {
66
- value: 'starts',
65
+ value: FilterOptionsEnum.sw,
67
66
  label: 'Starts with'
68
67
  },
69
68
  {
70
- value: 'ends',
69
+ value: FilterOptionsEnum.ew,
71
70
  label: 'Ends with'
72
71
  }
73
72
  ],
74
73
  date: [
75
74
  {
76
- value: 'ison',
75
+ value: FilterOptionsEnum.o,
77
76
  label: 'Is on'
78
77
  },
79
78
  {
80
- value: 'isstartingfrom',
79
+ value: FilterOptionsEnum.sf,
81
80
  label: 'Is starting from'
82
81
  },
83
82
  {
84
- value: 'isafter',
83
+ value: FilterOptionsEnum.a,
85
84
  label: 'Is after'
86
85
  },
87
86
  {
88
- value: 'isuntil',
87
+ value: FilterOptionsEnum.u,
89
88
  label: 'Is until'
90
89
  },
91
90
  {
92
- value: 'isbefore',
91
+ value: FilterOptionsEnum.b,
93
92
  label: 'Is before'
94
93
  },
95
94
  {
96
- value: 'isnoton',
95
+ value: FilterOptionsEnum.no,
97
96
  label: 'Is not on'
98
97
  }
99
98
  ]
100
99
  };
101
100
 
101
+ let dropdowns: {
102
+ option: FilterOptionsEnum;
103
+ value: string | number | Date | undefined;
104
+ }[] = [];
105
+
102
106
  // Unique ID for the column filter popup
103
107
  const popupId = `${tableId}-${id}`;
104
108
  // Popup config
@@ -122,8 +126,68 @@
122
126
  }
123
127
  }
124
128
  });
129
+
130
+ const optionChangeHandler = (e, index) => {
131
+ delete $filters[id][dropdowns[index].option];
132
+ $filters[id] = { ...$filters[id], [e.target.value]: dropdowns[index].value };
133
+ $filters = $filters;
134
+
135
+ dropdowns[index] = {
136
+ ...dropdowns[index],
137
+ option: e.target.value
138
+ };
139
+ };
140
+
141
+ const valueChangeHandler = (e, index) => {
142
+ dropdowns[index] = {
143
+ ...dropdowns[index],
144
+ value:
145
+ type === 'number'
146
+ ? +e.target.value
147
+ : type === 'date'
148
+ ? new Date(e.target.value)
149
+ : e.target.value
150
+ };
151
+
152
+ $filters = {
153
+ ...$filters,
154
+ [id]: { ...$filters[id], [dropdowns[index].option]: dropdowns[index].value }
155
+ };
156
+ };
157
+
158
+ const addFilter = (option, value) => {
159
+ $filters = { ...$filters, [id]: { ...$filters[id], [option]: value } };
160
+
161
+ dropdowns = [
162
+ ...dropdowns,
163
+ {
164
+ option: option,
165
+ value: undefined
166
+ }
167
+ ];
168
+ };
169
+
170
+ const removeFilter = (option) => {
171
+ dropdowns = dropdowns.filter((dropdown) => dropdown.option !== option);
172
+ delete $filters[id][option];
173
+ $filters = $filters;
174
+ };
175
+
176
+ const clearFilters = () => {
177
+ dropdowns = [];
178
+ $filters[id] = {};
179
+ };
180
+
125
181
  // Determine if the type is date
126
- type = isDate ? 'date' : type;
182
+ $: type = isDate ? 'date' : type;
183
+
184
+ // Filter the unapplied filters
185
+ $: remainingFilters = options[type].filter(
186
+ (option) => !Object.keys($filters[id]).includes(option.value)
187
+ );
188
+
189
+ // Start by adding the default filter
190
+ $: addFilter(options[type][0].value, undefined);
127
191
  </script>
128
192
 
129
193
  <form class="">
@@ -138,98 +202,98 @@
138
202
  </button>
139
203
 
140
204
  <div data-popup={`${popupId}`} id={popupId} class="z-50">
141
- <div class="card p-3 grid gap-2 shadow-lg w-min bg-base-100">
205
+ <div class="card p-3 grid gap-2 shadow-lg w-max bg-base-100">
142
206
  <button
143
207
  class="btn variant-filled-primary btn-sm"
144
208
  type="button"
145
209
  on:click|preventDefault={() => {
146
210
  // Set the defaults when cleared
147
- firstOption = 'isequal';
148
- firstValue = undefined;
149
- secondOption = 'isequal';
150
- secondValue = undefined;
151
-
152
- $filterValue = [firstOption, firstValue, secondOption, secondValue];
211
+ clearFilters();
212
+ addFilter(options[type][0].value, undefined);
213
+ $filterValue = $filters[id];
153
214
  active = false;
154
- }}>Clear Filter</button
215
+ }}>Clear Filters</button
155
216
  >
156
217
 
157
218
  <label for="" class="label normal-case text-sm">Show rows with value that</label>
158
- <div class="grid gap-2 w-full">
159
- <select
160
- class="select border border-primary-500 text-sm p-1"
161
- aria-label="Show rows with value that"
162
- bind:value={firstOption}
163
- on:click|stopPropagation
164
- >
165
- {#each options[type] as option (option)}
166
- <option value={option.value}>{option.label}</option>
167
- {/each}
168
- </select>
169
- {#if type === 'number'}
170
- <input
171
- type="number"
172
- class="input p-1 border border-primary-500"
173
- bind:value={firstValue}
174
- on:click|stopPropagation
175
- />
176
- {:else if type === 'string'}
177
- <input
178
- type="text"
179
- class="input p-1 border border-primary-500"
180
- bind:value={firstValue}
181
- on:click|stopPropagation
182
- />
183
- {:else}
184
- <input
185
- type="date"
186
- class="input p-1 border border-primary-500"
187
- bind:value={firstValue}
188
- on:click|stopPropagation
189
- />
190
- {/if}
219
+ <div class="grid gap-2 overflow-auto">
220
+ {#each dropdowns as dropdown, index (index)}
221
+ <div class="grid gap-2 w-full">
222
+ <div class="flex gap-1 items-center">
223
+ <select
224
+ class="select border border-primary-500 text-sm p-1"
225
+ aria-label="Show rows with value that"
226
+ on:change={(e) => optionChangeHandler(e, index)}
227
+ bind:value={dropdown.option}
228
+ >
229
+ {#each options[type] as option (option)}
230
+ <option
231
+ value={option.value}
232
+ selected={dropdown.option === option.value}
233
+ disabled={Object.keys($filters[id]).includes(option.value) &&
234
+ dropdown.option !== option.value}>{option.label}</option
235
+ >
236
+ {/each}
237
+ </select>
238
+ {#if dropdowns.length > 1}
239
+ <div
240
+ class="btn variant-filled-warning btn-sm h-full"
241
+ on:click|preventDefault={() => removeFilter(dropdown.option)}
242
+ on:keydown|preventDefault={() => removeFilter(dropdown.option)}
243
+ >
244
+ <Fa icon={faXmark} />
245
+ </div>
246
+ {/if}
247
+ </div>
248
+
249
+ {#if type === 'number'}
250
+ <input
251
+ type="number"
252
+ class="input p-1 border border-primary-500"
253
+ on:input={(e) => valueChangeHandler(e, index)}
254
+ bind:value={dropdown.value}
255
+ />
256
+ {:else if type === 'string'}
257
+ <input
258
+ type="text"
259
+ class="input p-1 border border-primary-500"
260
+ on:input={(e) => valueChangeHandler(e, index)}
261
+ bind:value={dropdown.value}
262
+ />
263
+ {:else}
264
+ <input
265
+ type="date"
266
+ class="input p-1 border border-primary-500"
267
+ on:input={(e) => valueChangeHandler(e, index)}
268
+ bind:value={dropdown.value}
269
+ />
270
+ {/if}
271
+ </div>
272
+ {#if index !== dropdowns.length - 1 && dropdowns.length > 1}
273
+ <label for="" class="label normal-case">And</label>
274
+ {/if}
275
+ {/each}
191
276
  </div>
192
- <label for="" class="label normal-case">And</label>
193
- <div class="grid gap-2 w-max">
194
- <select
195
- class="select border border-primary-500 text-sm p-1"
196
- aria-label="Show rows with value that"
197
- bind:value={secondOption}
198
- on:click|stopPropagation
277
+
278
+ {#if remainingFilters.length}
279
+ <div
280
+ class="btn variant-filled-secondary btn-sm cursor-pointer"
281
+ on:click|stopPropagation={() => {
282
+ addFilter(remainingFilters[0].value, undefined);
283
+ }}
284
+ on:keydown|stopPropagation={() => {
285
+ addFilter(remainingFilters[0].value, undefined);
286
+ }}
199
287
  >
200
- {#each options[type] as option (option)}
201
- <option value={option.value}>{option.label}</option>
202
- {/each}
203
- </select>
204
- {#if type === 'number'}
205
- <input
206
- type="number"
207
- class="input p-1 border border-primary-500"
208
- bind:value={secondValue}
209
- on:click|stopPropagation
210
- />
211
- {:else if type === 'string'}
212
- <input
213
- type="text"
214
- class="input p-1 border border-primary-500"
215
- bind:value={secondValue}
216
- on:click|stopPropagation
217
- />
218
- {:else}
219
- <input
220
- type="date"
221
- class="input p-1 border border-primary-500"
222
- bind:value={secondValue}
223
- on:click|stopPropagation
224
- />
225
- {/if}
226
- </div>
288
+ <div class="flex gap-1 items-center"><Fa icon={faPlus} />Add Filter</div>
289
+ </div>
290
+ {/if}
227
291
  <button
228
292
  class="btn variant-filled-primary btn-sm"
229
293
  type="button"
230
294
  on:click|preventDefault={() => {
231
- active = firstValue?.toString().length > 0 || secondValue?.toString().length > 0;
232
- $filterValue = [firstOption, firstValue, secondOption, secondValue];
295
+ $filterValue = $filters[id];
296
+ active = true;
233
297
  }}>Apply</button
234
298
  >
235
299
  </div>
@@ -0,0 +1,310 @@
1
+ <script lang="ts">
2
+ import Fa from 'svelte-fa/src/fa.svelte';
3
+ import { faFilter, faPlus, faXmark } from '@fortawesome/free-solid-svg-icons';
4
+ import { popup } from '@skeletonlabs/skeleton';
5
+ import type { PopupSettings } from '@skeletonlabs/skeleton';
6
+
7
+ import { FilterOptionsEnum } from '$models/Enums';
8
+
9
+ export let values;
10
+ export let id;
11
+ export let tableId;
12
+ export let toFilterableValueFn: undefined | ((value: any) => any) = undefined;
13
+ export let filters;
14
+ export let updateTable;
15
+ export let pageIndex;
16
+
17
+ // If the filter is applied and the displayed values are filtered
18
+ let active = false;
19
+
20
+ // Options for different types of values
21
+ const options = {
22
+ number: [
23
+ {
24
+ value: FilterOptionsEnum.e,
25
+ label: 'Is equal to'
26
+ },
27
+ {
28
+ value: FilterOptionsEnum.gte,
29
+ label: 'Is greater than or equal to'
30
+ },
31
+ {
32
+ value: FilterOptionsEnum.gt,
33
+ label: 'Is greater than'
34
+ },
35
+ {
36
+ value: FilterOptionsEnum.lte,
37
+ label: 'Is less than or equal to'
38
+ },
39
+ {
40
+ value: FilterOptionsEnum.lt,
41
+ label: 'Is less than'
42
+ },
43
+ {
44
+ value: FilterOptionsEnum.ne,
45
+ label: 'Is not equal to'
46
+ }
47
+ ],
48
+ string: [
49
+ {
50
+ value: FilterOptionsEnum.c,
51
+ label: 'Contains'
52
+ },
53
+ {
54
+ value: FilterOptionsEnum.nc,
55
+ label: 'Does not contain'
56
+ },
57
+ {
58
+ value: FilterOptionsEnum.e,
59
+ label: 'Is equal to'
60
+ },
61
+ {
62
+ value: FilterOptionsEnum.ne,
63
+ label: 'Is not equal to'
64
+ },
65
+ {
66
+ value: FilterOptionsEnum.sw,
67
+ label: 'Starts with'
68
+ },
69
+ {
70
+ value: FilterOptionsEnum.ew,
71
+ label: 'Ends with'
72
+ }
73
+ ],
74
+ date: [
75
+ {
76
+ value: FilterOptionsEnum.o,
77
+ label: 'Is on'
78
+ },
79
+ {
80
+ value: FilterOptionsEnum.sf,
81
+ label: 'Is starting from'
82
+ },
83
+ {
84
+ value: FilterOptionsEnum.a,
85
+ label: 'Is after'
86
+ },
87
+ {
88
+ value: FilterOptionsEnum.u,
89
+ label: 'Is until'
90
+ },
91
+ {
92
+ value: FilterOptionsEnum.b,
93
+ label: 'Is before'
94
+ },
95
+ {
96
+ value: FilterOptionsEnum.no,
97
+ label: 'Is not on'
98
+ }
99
+ ]
100
+ };
101
+
102
+ let dropdowns: {
103
+ option: FilterOptionsEnum;
104
+ value: string | number | Date | undefined;
105
+ }[] = [];
106
+
107
+ // Unique ID for the column filter popup
108
+ const popupId = `${tableId}-${id}`;
109
+ // Popup config
110
+ const popupFeatured: PopupSettings = {
111
+ event: 'click',
112
+ target: popupId,
113
+ placement: 'bottom-start'
114
+ };
115
+
116
+ let type: string = 'string';
117
+ let isDate = false;
118
+ // Check the type of the column
119
+ $values.forEach((item) => {
120
+ if (item) {
121
+ type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
122
+
123
+ if (type === 'object') {
124
+ if (item instanceof Date) {
125
+ isDate = true;
126
+ }
127
+ }
128
+ }
129
+ });
130
+
131
+ const optionChangeHandler = (e, index) => {
132
+ delete $filters[id][dropdowns[index].option];
133
+ $filters[id] = { ...$filters[id], [e.target.value]: dropdowns[index].value };
134
+ $filters = $filters;
135
+
136
+ dropdowns[index] = {
137
+ ...dropdowns[index],
138
+ option: e.target.value
139
+ };
140
+ };
141
+
142
+ const valueChangeHandler = (e, index) => {
143
+ dropdowns[index] = {
144
+ ...dropdowns[index],
145
+ value:
146
+ type === 'number'
147
+ ? +e.target.value
148
+ : type === 'date'
149
+ ? new Date(e.target.value)
150
+ : e.target.value
151
+ };
152
+
153
+ $filters = {
154
+ ...$filters,
155
+ [id]: { ...$filters[id], [dropdowns[index].option]: dropdowns[index].value }
156
+ };
157
+ };
158
+
159
+ const addFilter = (option, value) => {
160
+ $filters = { ...$filters, [id]: { ...$filters[id], [option]: value } };
161
+
162
+ dropdowns = [
163
+ ...dropdowns,
164
+ {
165
+ option: option,
166
+ value: undefined
167
+ }
168
+ ];
169
+ };
170
+
171
+ const removeFilter = (option) => {
172
+ dropdowns = dropdowns.filter((dropdown) => dropdown.option !== option);
173
+ delete $filters[id][option];
174
+ $filters = $filters;
175
+ };
176
+
177
+ const clearFilters = () => {
178
+ dropdowns = [];
179
+ $filters[id] = {};
180
+
181
+ $pageIndex = 0;
182
+ updateTable().then(() => {
183
+ active = false;
184
+ });
185
+ };
186
+
187
+ const applyFilters = () => {
188
+ $pageIndex = 0;
189
+ updateTable().then(() => {
190
+ active = true;
191
+ });
192
+ };
193
+
194
+ // Determine if the type is date
195
+ $: type = isDate ? 'date' : type;
196
+
197
+ // Filter the unapplied filters
198
+ $: remainingFilters = options[type].filter(
199
+ (option) => !Object.keys($filters[id]).includes(option.value)
200
+ );
201
+
202
+ // Start by adding the default filter
203
+ $: addFilter(options[type][0].value, undefined);
204
+ </script>
205
+
206
+ <form class="">
207
+ <button
208
+ class:variant-filled-primary={active}
209
+ class="btn w-max p-2"
210
+ type="button"
211
+ use:popup={popupFeatured}
212
+ id="{popupId}-button"
213
+ >
214
+ <Fa icon={faFilter} size="12" />
215
+ </button>
216
+
217
+ <div data-popup={`${popupId}`} id={popupId} class="z-50">
218
+ <div class="card p-3 grid gap-2 shadow-lg w-max bg-base-100">
219
+ <button
220
+ class="btn variant-filled-primary btn-sm"
221
+ type="button"
222
+ on:click|preventDefault={() => {
223
+ // Set the defaults when cleared
224
+ clearFilters();
225
+ addFilter(options[type][0].value, undefined);
226
+ active = false;
227
+ }}>Clear Filters</button
228
+ >
229
+
230
+ <label for="" class="label normal-case text-sm">Show rows with value that</label>
231
+ <div class="grid gap-2 overflow-auto">
232
+ {#each dropdowns as dropdown, index (index)}
233
+ <div class="grid gap-2 w-full">
234
+ <div class="flex gap-1 items-center">
235
+ <select
236
+ class="select border border-primary-500 text-sm p-1"
237
+ aria-label="Show rows with value that"
238
+ on:change={(e) => optionChangeHandler(e, index)}
239
+ bind:value={dropdown.option}
240
+ >
241
+ {#each options[type] as option (option)}
242
+ <option
243
+ value={option.value}
244
+ selected={dropdown.option === option.value}
245
+ disabled={Object.keys($filters[id]).includes(option.value) &&
246
+ dropdown.option !== option.value}>{option.label}</option
247
+ >
248
+ {/each}
249
+ </select>
250
+ {#if dropdowns.length > 1}
251
+ <div
252
+ class="btn variant-filled-warning btn-sm h-full"
253
+ on:click|preventDefault={() => removeFilter(dropdown.option)}
254
+ on:keydown|preventDefault={() => removeFilter(dropdown.option)}
255
+ >
256
+ <Fa icon={faXmark} />
257
+ </div>
258
+ {/if}
259
+ </div>
260
+
261
+ {#if type === 'number'}
262
+ <input
263
+ type="number"
264
+ class="input p-1 border border-primary-500"
265
+ on:input={(e) => valueChangeHandler(e, index)}
266
+ bind:value={dropdown.value}
267
+ />
268
+ {:else if type === 'string'}
269
+ <input
270
+ type="text"
271
+ class="input p-1 border border-primary-500"
272
+ on:input={(e) => valueChangeHandler(e, index)}
273
+ bind:value={dropdown.value}
274
+ />
275
+ {:else}
276
+ <input
277
+ type="date"
278
+ class="input p-1 border border-primary-500"
279
+ on:input={(e) => valueChangeHandler(e, index)}
280
+ bind:value={dropdown.value}
281
+ />
282
+ {/if}
283
+ </div>
284
+ {#if index !== dropdowns.length - 1 && dropdowns.length > 1}
285
+ <label for="" class="label normal-case">And</label>
286
+ {/if}
287
+ {/each}
288
+ </div>
289
+
290
+ {#if remainingFilters.length}
291
+ <div
292
+ class="btn variant-filled-secondary btn-sm cursor-pointer"
293
+ on:click|stopPropagation={() => {
294
+ addFilter(remainingFilters[0].value, undefined);
295
+ }}
296
+ on:keydown|stopPropagation={() => {
297
+ addFilter(remainingFilters[0].value, undefined);
298
+ }}
299
+ >
300
+ <div class="flex gap-1 items-center"><Fa icon={faPlus} />Add Filter</div>
301
+ </div>
302
+ {/if}
303
+ <button
304
+ class="btn variant-filled-primary btn-sm"
305
+ type="button"
306
+ on:click|preventDefault={applyFilters}>Apply</button
307
+ >
308
+ </div>
309
+ </div>
310
+ </form>