@kws3/ui 1.8.0 → 1.8.3
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/CHANGELOG.mdx +15 -0
- package/controls/NumberInput.svelte +93 -41
- package/datagrid/GridView/GridRow.svelte +16 -7
- package/datagrid/GridView/GridView.svelte +14 -2
- package/datagrid/TileView/TileView.svelte +12 -0
- package/datagrid/TileView/TileViewItem.svelte +11 -5
- package/forms/select/MultiSelect.svelte +1 -1
- package/helpers/ScrollableList.svelte +231 -0
- package/index.js +1 -0
- package/package.json +2 -3
- package/sliding-panes/SlidingPane.svelte +13 -20
- package/styles/Grid.scss +1 -0
- package/utils/fuzzysearch.js +20 -0
- package/utils/resizeObserver.js +24 -0
package/CHANGELOG.mdx
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## 1.8.3
|
|
2
|
+
- Allow `clickableRows` and `bulk_actions` to work at the same time on `GridView`
|
|
3
|
+
- Various bugfixes on `GridRow`
|
|
4
|
+
- New `visualActivationOnClick` prop for `GridView` and `TileView`
|
|
5
|
+
- Change the way click activation works on `GridView` and `TileView` rows. Now only one row can be activated at a time
|
|
6
|
+
|
|
7
|
+
## 1.8.2
|
|
8
|
+
- Usability fixes for `NumberInput`
|
|
9
|
+
- New `input_only`, `force_integer`, `style` and `class` props for `NumberInput`
|
|
10
|
+
- Forward `focus`, `blur` input events for `NumberInput`
|
|
11
|
+
- Use custom version of `fuzzysearch` for `SearchableSelect` and `MultiSelect`
|
|
12
|
+
|
|
13
|
+
## 1.8.1
|
|
14
|
+
- New `ScrollableList` component
|
|
15
|
+
|
|
1
16
|
## 1.8.0
|
|
2
17
|
- `Modal`, `CardModal` and `ActionSheet` components now play an outro transition instead of abruptly disappearing.
|
|
3
18
|
- Usability fixes for `SearchableSelect` and `MultiSelect`.
|
|
@@ -20,52 +20,84 @@ This will be overridden if `min` is higher, or `max` is lower, Default: `0`
|
|
|
20
20
|
@param {string} [plus_icon="plus"] - Name of the icon that is to be displayed in the plus button, Default: `"plus"`
|
|
21
21
|
@param {''|'success'|'primary'|'warning'|'info'|'danger'|'dark'|'light'} [plus_icon_color="success"] - Color of the Plus Icon, Default: `"success"`
|
|
22
22
|
@param {''|'success'|'primary'|'warning'|'info'|'danger'|'dark'|'light'} [plus_button_color=""] - Color of the Plus Button, Default: `""`
|
|
23
|
+
@param {boolean} [input_only=false] - Show input without controls, Default: `false`
|
|
24
|
+
@param {boolean} [force_integer=false] - Prevent decimal numbers such as `1.5`, Default: `false`
|
|
25
|
+
@param {string} [style=""] - Inline CSS for component, Default: `""`
|
|
26
|
+
@param {string} [class=""] - CSS classes for component, Default: `""`
|
|
23
27
|
|
|
24
28
|
### Events
|
|
25
29
|
- `change` - Triggered when value changes
|
|
30
|
+
- `blur`
|
|
31
|
+
- `focus`
|
|
26
32
|
|
|
27
33
|
-->
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
{#if input_only}
|
|
35
|
+
<input
|
|
36
|
+
{style}
|
|
37
|
+
data-testid="input"
|
|
38
|
+
class="input has-text-centered {klass} is-{size} is-{value < min ||
|
|
39
|
+
value > max
|
|
40
|
+
? 'danger'
|
|
41
|
+
: ''}"
|
|
42
|
+
type="number"
|
|
43
|
+
min
|
|
44
|
+
max
|
|
45
|
+
{step}
|
|
46
|
+
{disabled}
|
|
47
|
+
readonly={!typeable}
|
|
48
|
+
bind:value
|
|
49
|
+
on:blur={isBlurred}
|
|
50
|
+
on:blur
|
|
51
|
+
on:focus={isFocused}
|
|
52
|
+
on:focus />
|
|
53
|
+
{:else}
|
|
54
|
+
<div class="field has-addons {klass}" {style}>
|
|
55
|
+
<div class="control">
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
class="button is-{size} is-{minus_button_color}"
|
|
59
|
+
style="box-shadow:none;"
|
|
60
|
+
on:click={count(-1)}
|
|
61
|
+
disabled={disabled || value <= min}>
|
|
62
|
+
<Icon
|
|
63
|
+
icon={minus_icon}
|
|
64
|
+
size="small"
|
|
65
|
+
class="has-text-{minus_icon_color}" />
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="control is-{fullwidth ? 'expanded' : 'narrow'}">
|
|
69
|
+
<input
|
|
70
|
+
data-testid="input"
|
|
71
|
+
class="input has-text-centered is-{size} is-{value < min || value > max
|
|
72
|
+
? 'danger'
|
|
73
|
+
: ''}"
|
|
74
|
+
type="number"
|
|
75
|
+
min
|
|
76
|
+
max
|
|
77
|
+
{step}
|
|
78
|
+
{disabled}
|
|
79
|
+
readonly={!typeable}
|
|
80
|
+
bind:value
|
|
81
|
+
on:blur={isBlurred}
|
|
82
|
+
on:blur
|
|
83
|
+
on:focus={isFocused}
|
|
84
|
+
on:focus />
|
|
85
|
+
</div>
|
|
86
|
+
<div class="control">
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
class="button is-{size} is-{plus_button_color}"
|
|
90
|
+
style="box-shadow:none;"
|
|
91
|
+
on:click|preventDefault={count(+1)}
|
|
92
|
+
disabled={disabled || value >= max}>
|
|
93
|
+
<Icon
|
|
94
|
+
icon={plus_icon}
|
|
95
|
+
size="small"
|
|
96
|
+
class="has-text-{plus_icon_color}" />
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
41
99
|
</div>
|
|
42
|
-
|
|
43
|
-
<input
|
|
44
|
-
data-testid="input"
|
|
45
|
-
class="input has-text-centered is-{size} is-{value < min || value > max
|
|
46
|
-
? 'danger'
|
|
47
|
-
: ''}"
|
|
48
|
-
type="number"
|
|
49
|
-
min
|
|
50
|
-
max
|
|
51
|
-
step
|
|
52
|
-
{disabled}
|
|
53
|
-
readonly={!typeable}
|
|
54
|
-
bind:value
|
|
55
|
-
on:blur={isBlurred()}
|
|
56
|
-
on:focus={isFocused()} />
|
|
57
|
-
</div>
|
|
58
|
-
<div class="control">
|
|
59
|
-
<button
|
|
60
|
-
type="button"
|
|
61
|
-
class="button is-{size} is-{plus_button_color}"
|
|
62
|
-
style="box-shadow:none;"
|
|
63
|
-
on:click|preventDefault={count(+1)}
|
|
64
|
-
disabled={disabled || value >= max}>
|
|
65
|
-
<Icon icon={plus_icon} size="small" class="has-text-{plus_icon_color}" />
|
|
66
|
-
</button>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
100
|
+
{/if}
|
|
69
101
|
|
|
70
102
|
<style>
|
|
71
103
|
input[type="number"]::-webkit-inner-spin-button,
|
|
@@ -158,7 +190,25 @@ This will be overridden if `min` is higher, or `max` is lower, Default: `0`
|
|
|
158
190
|
* Color of the Plus Button
|
|
159
191
|
* @type {''|'success'|'primary'|'warning'|'info'|'danger'|'dark'|'light'}
|
|
160
192
|
*/
|
|
161
|
-
plus_button_color = ""
|
|
193
|
+
plus_button_color = "",
|
|
194
|
+
/**
|
|
195
|
+
* Show input without controls
|
|
196
|
+
*/
|
|
197
|
+
input_only = false,
|
|
198
|
+
/**
|
|
199
|
+
* Prevent decimal numbers such as `1.5`
|
|
200
|
+
*/
|
|
201
|
+
force_integer = false,
|
|
202
|
+
/**
|
|
203
|
+
* Inline CSS for component
|
|
204
|
+
*/
|
|
205
|
+
style = "";
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* CSS classes for component
|
|
209
|
+
*/
|
|
210
|
+
let klass = "";
|
|
211
|
+
export { klass as class };
|
|
162
212
|
|
|
163
213
|
let _has_focus = false,
|
|
164
214
|
_old_value = null;
|
|
@@ -189,6 +239,8 @@ This will be overridden if `min` is higher, or `max` is lower, Default: `0`
|
|
|
189
239
|
|
|
190
240
|
if (typeof value == "undefined" || value === null) value = min;
|
|
191
241
|
|
|
242
|
+
if (force_integer) value = Math.floor(Number(value));
|
|
243
|
+
|
|
192
244
|
if (value < min) value = min;
|
|
193
245
|
if (value > max) value = max;
|
|
194
246
|
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
@param {number} [row_index=0] - Row index value, Default: `0`
|
|
6
6
|
@param {object} [row={}] - Contains all the column values in a row, Default: `{}`
|
|
7
|
-
@param {boolean} [
|
|
7
|
+
@param {boolean} [visualActivationOnClick=true] - Determines whether clickable rows activate visually on click, Default: `true`
|
|
8
|
+
@param {object} [activatedId=null] - Unique id of row that is activated, Default: `null`
|
|
8
9
|
@param {object} [isVisible={}] - Determines whether column is visible or not, Default: `{}`
|
|
9
10
|
@param {boolean} [clickableRows=false] - Determines whether the row is clickable or not, Default: `false`
|
|
10
11
|
@param {object} [transforms={}] - Contains all custom values for each columns, Default: `{}`
|
|
@@ -29,10 +30,14 @@
|
|
|
29
30
|
<tr
|
|
30
31
|
in:fly={{ x: 20, delay: 25 * row_index }}
|
|
31
32
|
on:click|stopPropagation={rowClick}
|
|
32
|
-
class:is-selected={
|
|
33
|
+
class:is-selected={activated && visualActivationOnClick}
|
|
33
34
|
class:is-checked={checked}>
|
|
34
35
|
{#if bulk_actions}
|
|
35
|
-
<td
|
|
36
|
+
<td
|
|
37
|
+
style="vertical-align:middle;"
|
|
38
|
+
on:click={(e) => {
|
|
39
|
+
clickableRows && e.stopImmediatePropagation();
|
|
40
|
+
}}>
|
|
36
41
|
<Checkbox
|
|
37
42
|
size={selectCheckboxSize}
|
|
38
43
|
color={selectCheckboxColor}
|
|
@@ -58,7 +63,7 @@
|
|
|
58
63
|
{:else}
|
|
59
64
|
<tr
|
|
60
65
|
on:click|stopPropagation={rowClick}
|
|
61
|
-
class:is-selected={
|
|
66
|
+
class:is-selected={activated && visualActivationOnClick}
|
|
62
67
|
class:is-checked={checked}>
|
|
63
68
|
{#if bulk_actions}
|
|
64
69
|
<td style="vertical-align:middle;">
|
|
@@ -103,9 +108,13 @@
|
|
|
103
108
|
*/
|
|
104
109
|
row = {},
|
|
105
110
|
/**
|
|
106
|
-
* Determines whether
|
|
111
|
+
* Determines whether clickable rows activate visually on click
|
|
112
|
+
*/
|
|
113
|
+
visualActivationOnClick = true,
|
|
114
|
+
/**
|
|
115
|
+
* Unique id of row that is activated
|
|
107
116
|
*/
|
|
108
|
-
|
|
117
|
+
activatedId = null,
|
|
109
118
|
/**
|
|
110
119
|
* Determines whether column is visible or not
|
|
111
120
|
*/
|
|
@@ -161,6 +170,7 @@
|
|
|
161
170
|
};
|
|
162
171
|
|
|
163
172
|
$: selectedIds, setCheckedValue();
|
|
173
|
+
$: activated = activatedId === row.id;
|
|
164
174
|
|
|
165
175
|
function setCheckedValue() {
|
|
166
176
|
checked = false;
|
|
@@ -175,7 +185,6 @@
|
|
|
175
185
|
|
|
176
186
|
function rowClick() {
|
|
177
187
|
if (clickableRows) {
|
|
178
|
-
rowActive = true;
|
|
179
188
|
fire("rowClick", { row });
|
|
180
189
|
}
|
|
181
190
|
}
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
@param {string} [iteration_key="id"] - Iteration key, Default: `"id"`
|
|
6
6
|
@param {array} [data=[]] - Contains all the results that needs to be displayed, Default: `[]`
|
|
7
7
|
@param {object} [columns={}] - Table column names. {db_field_name: column_name}, Default: `{}`
|
|
8
|
-
@param {boolean} [transition=false] - Determines if a
|
|
8
|
+
@param {boolean} [transition=false] - Determines if a transition effect is used, Default: `false`
|
|
9
9
|
@param {boolean} [is_striped=true] - Determines whether to use alternating row shading in the table view, Default: `true`
|
|
10
|
+
@param {boolean} [visualActivationOnClick=true] - Determines whether clickable rows activate visually on click, Default: `true`
|
|
10
11
|
@param {boolean} [clickableRows=false] - Determines whether rows are clickable or not, Default: `false`
|
|
12
|
+
@param {object} [activatedId=null] - Unique id of row that is activated, Default: `null`
|
|
11
13
|
@param {boolean} [bulk_actions=false] - Determines if selecting multiple rows and doing multiple actions is allowed, Default: `false`
|
|
12
14
|
@param {boolean} [selectAll=false] - Determines if all rows are selected, Default: `false`
|
|
13
15
|
@param {array} [selectedIds=[]] - List of unique IDs of all the selected rows, Default: `[]`
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
{transition}
|
|
65
67
|
{column_keys}
|
|
66
68
|
{clickableRows}
|
|
69
|
+
{visualActivationOnClick}
|
|
67
70
|
{isVisible}
|
|
68
71
|
{transforms}
|
|
69
72
|
{classNames}
|
|
@@ -71,6 +74,7 @@
|
|
|
71
74
|
{cellComponent}
|
|
72
75
|
{row}
|
|
73
76
|
{bulk_actions}
|
|
77
|
+
{activatedId}
|
|
74
78
|
{selectedIds}
|
|
75
79
|
{selectCheckboxColor}
|
|
76
80
|
{selectCheckboxSize}
|
|
@@ -100,7 +104,7 @@
|
|
|
100
104
|
*/
|
|
101
105
|
columns = {},
|
|
102
106
|
/**
|
|
103
|
-
* Determines if a
|
|
107
|
+
* Determines if a transition effect is used
|
|
104
108
|
*/
|
|
105
109
|
transition = false,
|
|
106
110
|
/**
|
|
@@ -108,10 +112,18 @@
|
|
|
108
112
|
* @link https://bulma.io/documentation/elements/table/#modifiers
|
|
109
113
|
*/
|
|
110
114
|
is_striped = true,
|
|
115
|
+
/**
|
|
116
|
+
* Determines whether clickable rows activate visually on click
|
|
117
|
+
*/
|
|
118
|
+
visualActivationOnClick = true,
|
|
111
119
|
/**
|
|
112
120
|
* Determines whether rows are clickable or not
|
|
113
121
|
*/
|
|
114
122
|
clickableRows = false,
|
|
123
|
+
/**
|
|
124
|
+
* Unique id of row that is activated
|
|
125
|
+
*/
|
|
126
|
+
activatedId = null,
|
|
115
127
|
/**
|
|
116
128
|
* Determines if selecting multiple rows and doing multiple actions is allowed
|
|
117
129
|
*/
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
@param {object} [tileItemComponent=null] - Contains a custom component, Default: `null`
|
|
9
9
|
@param {number} [per_row=3] - Sets how many items to display in a row, Default: `3`
|
|
10
10
|
@param {object} [columns={}] - Column names for the displayed table {db_field_name: column_name}, Default: `{}`
|
|
11
|
+
@param {boolean} [visualActivationOnClick=true] - Determines whether clickable rows activate visually on click, Default: `true`
|
|
11
12
|
@param {boolean} [clickableRows=false] - Determines whether rows are clickable or not, Default: `false`
|
|
13
|
+
@param {object} [activatedId=null] - Unique id of row that is activated, Default: `null`
|
|
12
14
|
@param {object} [valueTransformers={}] - Contains all custom values for each column, Default: `{}`
|
|
13
15
|
@param {object} [classTransformers={}] - CSS class names for each column, Default: `{}`
|
|
14
16
|
@param {object} [styleTransformers={}] - CSS styles for each column, Default: `{}`
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
on:_forwardEvent
|
|
32
34
|
{row_index}
|
|
33
35
|
{column_keys}
|
|
36
|
+
{visualActivationOnClick}
|
|
34
37
|
{clickableRows}
|
|
35
38
|
{isVisible}
|
|
36
39
|
{transforms}
|
|
@@ -38,6 +41,7 @@
|
|
|
38
41
|
{styles}
|
|
39
42
|
{row}
|
|
40
43
|
{bulk_actions}
|
|
44
|
+
{activatedId}
|
|
41
45
|
{selectedIds}
|
|
42
46
|
{selectCheckboxColor}
|
|
43
47
|
{selectCheckboxSize}
|
|
@@ -75,10 +79,18 @@
|
|
|
75
79
|
* Column names for the displayed table {db_field_name: column_name}
|
|
76
80
|
*/
|
|
77
81
|
columns = {},
|
|
82
|
+
/**
|
|
83
|
+
* Determines whether clickable rows activate visually on click
|
|
84
|
+
*/
|
|
85
|
+
visualActivationOnClick = true,
|
|
78
86
|
/**
|
|
79
87
|
* Determines whether rows are clickable or not
|
|
80
88
|
*/
|
|
81
89
|
clickableRows = false,
|
|
90
|
+
/**
|
|
91
|
+
* Unique id of row that is activated
|
|
92
|
+
*/
|
|
93
|
+
activatedId = null,
|
|
82
94
|
/**
|
|
83
95
|
* Contains all custom values for each column
|
|
84
96
|
*/
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
@param {object} [row={}] - List of all values in a row, Default: `{}`
|
|
6
|
-
@param {boolean} [
|
|
6
|
+
@param {boolean} [visualActivationOnClick=true] - Determines whether clickable rows activate visually on click, Default: `true`
|
|
7
|
+
@param {object} [activatedId=null] - Unique id of row that is activated, Default: `null`
|
|
7
8
|
@param {boolean} [clickableRows=false] - Determines whether the row is clickable or not, Default: `false`
|
|
8
9
|
@param {function} [isVisible()] - Returns whether a column can be visible or not
|
|
9
10
|
@param {function} [transforms()] - Returns column custom value
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
|
|
17
18
|
-->
|
|
18
19
|
<div
|
|
19
|
-
class:is-selected={
|
|
20
|
+
class:is-selected={activated && visualActivationOnClick}
|
|
20
21
|
class="box {clickableRows ? 'is-hoverable' : ''}"
|
|
21
22
|
on:click|stopPropagation={rowClick}>
|
|
22
23
|
{#each column_keys as column}
|
|
@@ -39,9 +40,13 @@
|
|
|
39
40
|
*/
|
|
40
41
|
export let row = {},
|
|
41
42
|
/**
|
|
42
|
-
* Determines whether
|
|
43
|
+
* Determines whether clickable rows activate visually on click
|
|
43
44
|
*/
|
|
44
|
-
|
|
45
|
+
visualActivationOnClick = true,
|
|
46
|
+
/**
|
|
47
|
+
* Unique id of row that is activated
|
|
48
|
+
*/
|
|
49
|
+
activatedId = null,
|
|
45
50
|
/**
|
|
46
51
|
* Determines whether the row is clickable or not
|
|
47
52
|
*/
|
|
@@ -67,9 +72,10 @@
|
|
|
67
72
|
*/
|
|
68
73
|
column_keys = [];
|
|
69
74
|
|
|
75
|
+
$: activated = activatedId === row.id;
|
|
76
|
+
|
|
70
77
|
function rowClick() {
|
|
71
78
|
if (clickableRows) {
|
|
72
|
-
rowActive = true;
|
|
73
79
|
/**
|
|
74
80
|
* Fires an event when a row is clicked
|
|
75
81
|
*/
|
|
@@ -160,7 +160,7 @@ Default value: `<span>{option[search_key] || option}</span>`
|
|
|
160
160
|
import { debounce } from "@kws3/ui/utils";
|
|
161
161
|
import { createEventDispatcher, onMount, tick } from "svelte";
|
|
162
162
|
import { createPopper } from "@popperjs/core";
|
|
163
|
-
import fuzzysearch from "fuzzysearch";
|
|
163
|
+
import fuzzysearch from "@kws3/ui/utils/fuzzysearch";
|
|
164
164
|
|
|
165
165
|
const sameWidthPopperModifier = {
|
|
166
166
|
name: "sameWidth",
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@param {array} [items=[]] - Array of items, Default: `[]`
|
|
6
|
+
@param {string} [height="100%"] - Height of the wrapper, CSS String, Default: `"100%"`
|
|
7
|
+
@param {number | null} [item_height=null] - Height of each list item. If not set, height will be calculated automatically based on each item's offsetHeight, Default: `null`
|
|
8
|
+
@param {number} [start=0] - First item index rendered inside viewport - readonly, Default: `0`
|
|
9
|
+
@param {number} [end=0] - Last item index rendered inside viewport - readonly, Default: `0`
|
|
10
|
+
@param {number} [end_threshold=10] - `end` event will be fired when the list reaches this many items before the end of the list., Default: `10`
|
|
11
|
+
@param {string} [style=""] - Inline CSS for scroller container, Default: `""`
|
|
12
|
+
@param {string} [class=""] - CSS classes for scroller container, Default: `""`
|
|
13
|
+
|
|
14
|
+
### Events
|
|
15
|
+
- `end` - Fired when the list reaches `end_threshold` items before the end of the list.
|
|
16
|
+
|
|
17
|
+
### Slots
|
|
18
|
+
- `<slot name="default" {item} {index} />` - Default slot for list view items
|
|
19
|
+
- `<slot name="loader" />` - Optional slot to display a loading graphic at the bottom of the list
|
|
20
|
+
while more items are loading
|
|
21
|
+
|
|
22
|
+
-->
|
|
23
|
+
{#if hasResizeObserver}
|
|
24
|
+
<div
|
|
25
|
+
bind:this={viewport}
|
|
26
|
+
class="kws-scrollable-list with-resize-observer {klass}"
|
|
27
|
+
on:scroll={handle_scroll}
|
|
28
|
+
style="height:{height};{style}"
|
|
29
|
+
use:resizeObserver
|
|
30
|
+
on:resize={resize}>
|
|
31
|
+
<div
|
|
32
|
+
bind:this={contents}
|
|
33
|
+
style="padding-top: {top}px; padding-bottom: {bottom}px;">
|
|
34
|
+
{#each visible as item (item.index)}
|
|
35
|
+
<div class="row">
|
|
36
|
+
<!--Default slot for list view items-->
|
|
37
|
+
<slot item={item.data} index={item.index} />
|
|
38
|
+
</div>
|
|
39
|
+
{/each}
|
|
40
|
+
<!--Optional slot to display a loading graphic at the bottom of the list
|
|
41
|
+
while more items are loading-->
|
|
42
|
+
<slot name="loader" />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
{:else}
|
|
46
|
+
<div
|
|
47
|
+
bind:this={viewport}
|
|
48
|
+
class="kws-scrollable-list {klass}"
|
|
49
|
+
on:scroll={handle_scroll}
|
|
50
|
+
style="height:{height};{style}"
|
|
51
|
+
bind:offsetHeight={viewport_height}>
|
|
52
|
+
<div
|
|
53
|
+
bind:this={contents}
|
|
54
|
+
style="padding-top: {top}px; padding-bottom: {bottom}px;">
|
|
55
|
+
{#each visible as item (item.index)}
|
|
56
|
+
<div class="row">
|
|
57
|
+
<!--Default slot for list view items-->
|
|
58
|
+
<slot item={item.data} index={item.index} />
|
|
59
|
+
</div>
|
|
60
|
+
{/each}
|
|
61
|
+
<!--Optional slot to display a loading graphic at the bottom of the list
|
|
62
|
+
while more items are loading-->
|
|
63
|
+
<slot name="loader" />
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
67
|
+
|
|
68
|
+
<style>
|
|
69
|
+
.kws-scrollable-list {
|
|
70
|
+
overflow: auto;
|
|
71
|
+
-webkit-overflow-scrolling: touch;
|
|
72
|
+
position: relative;
|
|
73
|
+
height: 100%;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
76
|
+
|
|
77
|
+
<script>
|
|
78
|
+
import { onMount, tick } from "svelte";
|
|
79
|
+
import { createEventDispatcher } from "svelte";
|
|
80
|
+
import {
|
|
81
|
+
resizeObserver,
|
|
82
|
+
hasResizeObserver,
|
|
83
|
+
} from "@kws3/ui/utils/resizeObserver";
|
|
84
|
+
|
|
85
|
+
const fire = createEventDispatcher();
|
|
86
|
+
/**
|
|
87
|
+
* Array of items
|
|
88
|
+
*/
|
|
89
|
+
export let items = [],
|
|
90
|
+
/**
|
|
91
|
+
* Height of the wrapper, CSS String
|
|
92
|
+
*/
|
|
93
|
+
height = "100%",
|
|
94
|
+
/**
|
|
95
|
+
* Height of each list item. If not set, height will be calculated automatically based on each item's offsetHeight
|
|
96
|
+
* @type {number | null}
|
|
97
|
+
*/
|
|
98
|
+
item_height = null,
|
|
99
|
+
/**
|
|
100
|
+
* First item index rendered inside viewport - readonly
|
|
101
|
+
*/
|
|
102
|
+
start = 0,
|
|
103
|
+
/**
|
|
104
|
+
* Last item index rendered inside viewport - readonly
|
|
105
|
+
*/
|
|
106
|
+
end = 0,
|
|
107
|
+
/**
|
|
108
|
+
* `end` event will be fired when the list reaches this many items before the end of the list.
|
|
109
|
+
*/
|
|
110
|
+
end_threshold = 10,
|
|
111
|
+
/**
|
|
112
|
+
* Inline CSS for scroller container
|
|
113
|
+
*/
|
|
114
|
+
style = "";
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* CSS classes for scroller container
|
|
118
|
+
*/
|
|
119
|
+
let klass = "";
|
|
120
|
+
export { klass as class };
|
|
121
|
+
|
|
122
|
+
// local state
|
|
123
|
+
let height_map = [],
|
|
124
|
+
rows,
|
|
125
|
+
viewport,
|
|
126
|
+
contents,
|
|
127
|
+
viewport_height = 0,
|
|
128
|
+
visible,
|
|
129
|
+
mounted,
|
|
130
|
+
top = 0,
|
|
131
|
+
bottom = 0,
|
|
132
|
+
average_height,
|
|
133
|
+
items_count = 0;
|
|
134
|
+
|
|
135
|
+
$: visible = items.slice(start, end).map((data, i) => {
|
|
136
|
+
return { index: i + start, data };
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// whenever `items` changes, invalidate the current heightmap
|
|
140
|
+
$: items, viewport_height, item_height, mounted, refresh();
|
|
141
|
+
|
|
142
|
+
async function refresh() {
|
|
143
|
+
if (!mounted) return;
|
|
144
|
+
const scrollTop = viewport.scrollTop;
|
|
145
|
+
await tick(); // wait until the DOM is up to date
|
|
146
|
+
let content_height = top - scrollTop;
|
|
147
|
+
let i = start;
|
|
148
|
+
while (content_height < viewport_height && i < items.length) {
|
|
149
|
+
let row = rows[i - start];
|
|
150
|
+
if (!row) {
|
|
151
|
+
end = i + 1;
|
|
152
|
+
await tick(); // render the newly visible row
|
|
153
|
+
row = rows[i - start];
|
|
154
|
+
}
|
|
155
|
+
const row_height = (height_map[i] =
|
|
156
|
+
item_height || (row ? row.offsetHeight : 0));
|
|
157
|
+
content_height += row_height;
|
|
158
|
+
i += 1;
|
|
159
|
+
}
|
|
160
|
+
end = i;
|
|
161
|
+
const remaining = items.length - end;
|
|
162
|
+
average_height = (top + content_height) / end;
|
|
163
|
+
bottom = remaining * average_height;
|
|
164
|
+
height_map.length = items.length;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function handle_scroll() {
|
|
168
|
+
const scrollTop = viewport.scrollTop;
|
|
169
|
+
const old_start = start;
|
|
170
|
+
for (let v = 0; v < rows.length; v += 1) {
|
|
171
|
+
height_map[start + v] = item_height || rows[v].offsetHeight;
|
|
172
|
+
}
|
|
173
|
+
let i = 0;
|
|
174
|
+
let y = 0;
|
|
175
|
+
while (i < items.length) {
|
|
176
|
+
const row_height = height_map[i] || average_height;
|
|
177
|
+
if (y + row_height > scrollTop) {
|
|
178
|
+
start = i;
|
|
179
|
+
top = y;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
y += row_height;
|
|
183
|
+
i += 1;
|
|
184
|
+
}
|
|
185
|
+
while (i < items.length) {
|
|
186
|
+
y += height_map[i] || average_height;
|
|
187
|
+
i += 1;
|
|
188
|
+
if (y > scrollTop + viewport_height) break;
|
|
189
|
+
}
|
|
190
|
+
end = i;
|
|
191
|
+
const remaining = items.length - end;
|
|
192
|
+
average_height = y / end;
|
|
193
|
+
while (i < items.length) height_map[i++] = average_height;
|
|
194
|
+
bottom = remaining * average_height;
|
|
195
|
+
// prevent jumping if we scrolled up into unknown territory
|
|
196
|
+
if (start < old_start) {
|
|
197
|
+
await tick();
|
|
198
|
+
let expected_height = 0;
|
|
199
|
+
let actual_height = 0;
|
|
200
|
+
for (let i = start; i < old_start; i += 1) {
|
|
201
|
+
if (rows[i - start]) {
|
|
202
|
+
expected_height += height_map[i];
|
|
203
|
+
actual_height += item_height || rows[i - start].offsetHeight;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const d = actual_height - expected_height;
|
|
207
|
+
viewport.scrollTo(0, scrollTop + d);
|
|
208
|
+
}
|
|
209
|
+
// fire on:end event if we scrolled past the end of the list
|
|
210
|
+
if (end > items.length - end_threshold) {
|
|
211
|
+
if (items_count !== items.length) {
|
|
212
|
+
items_count = items.length;
|
|
213
|
+
await tick();
|
|
214
|
+
/**
|
|
215
|
+
* Fired when the list reaches `end_threshold` items before the end of the list.
|
|
216
|
+
*/
|
|
217
|
+
fire("end", { start, end });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const resize = () => {
|
|
223
|
+
viewport_height = viewport.offsetHeight;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// trigger initial refresh
|
|
227
|
+
onMount(() => {
|
|
228
|
+
rows = contents.getElementsByClassName("row");
|
|
229
|
+
mounted = true;
|
|
230
|
+
});
|
|
231
|
+
</script>
|
package/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { default as TimelineItem } from "./helpers/Timeline/TimelineItem.svelte"
|
|
|
17
17
|
export { default as TimelineHeader } from "./helpers/Timeline/TimelineHeader.svelte";
|
|
18
18
|
export { default as Nl2br } from "./helpers/Nl2br.svelte";
|
|
19
19
|
export { default as ClipboardCopier } from "./helpers/ClipboardCopier.svelte";
|
|
20
|
+
export { default as ScrollableList } from "./helpers/ScrollableList.svelte";
|
|
20
21
|
export { alert, confirm, prompt, default as Dialog } from "./helpers/Dialog";
|
|
21
22
|
export {
|
|
22
23
|
Notifications,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kws3/ui",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
4
4
|
"description": "UI components for use with Svelte v3 applications.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,10 +25,9 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"apexcharts": "3.33.2",
|
|
27
27
|
"flatpickr": "^4.5.2",
|
|
28
|
-
"fuzzysearch": "^1.0.3",
|
|
29
28
|
"svelte-portal": "^2.1.2",
|
|
30
29
|
"text-mask-core": "^5.1.2",
|
|
31
30
|
"tippy.js": "^6.3.1"
|
|
32
31
|
},
|
|
33
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "b211043d95c9b0f3e4184624ccd84b0a99687e50"
|
|
34
33
|
}
|
|
@@ -25,6 +25,8 @@ This will work only when `track_height` is set to `true`
|
|
|
25
25
|
: ''} {h_center ? 'h-centered' : ''} {active ? 'is-active' : ''} {klass}"
|
|
26
26
|
{style}>
|
|
27
27
|
<div
|
|
28
|
+
use:resizeObserver
|
|
29
|
+
on:resize={debouncedFireSizeChange}
|
|
28
30
|
bind:this={slideInner}
|
|
29
31
|
class="sliding-pane-inner {v_center ? 'v-centered' : ''} {h_center
|
|
30
32
|
? 'h-centered'
|
|
@@ -52,6 +54,10 @@ This will work only when `track_height` is set to `true`
|
|
|
52
54
|
<script>
|
|
53
55
|
import { onMount, createEventDispatcher } from "svelte";
|
|
54
56
|
import { debounce } from "@kws3/ui/utils";
|
|
57
|
+
import {
|
|
58
|
+
resizeObserver,
|
|
59
|
+
hasResizeObserver,
|
|
60
|
+
} from "@kws3/ui/utils/resizeObserver";
|
|
55
61
|
|
|
56
62
|
const fire = createEventDispatcher();
|
|
57
63
|
|
|
@@ -76,8 +82,7 @@ This will work only when `track_height` is set to `true`
|
|
|
76
82
|
*/
|
|
77
83
|
track_height = true;
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
let _height, slideInner, Observer;
|
|
85
|
+
let _height, slideInner;
|
|
81
86
|
|
|
82
87
|
/**
|
|
83
88
|
* CSS classes for the panel
|
|
@@ -91,18 +96,22 @@ This will work only when `track_height` is set to `true`
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
const max_retries_for_render = 10;
|
|
100
|
+
let try_count = 0;
|
|
94
101
|
function pollForRender() {
|
|
95
102
|
if (slideInner && typeof slideInner != "undefined") {
|
|
96
103
|
init();
|
|
97
104
|
} else {
|
|
98
105
|
setTimeout(() => {
|
|
99
|
-
|
|
106
|
+
try_count++;
|
|
107
|
+
if (try_count < max_retries_for_render) {
|
|
108
|
+
pollForRender();
|
|
109
|
+
}
|
|
100
110
|
}, 50);
|
|
101
111
|
}
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
function init() {
|
|
105
|
-
setupResizeObserver();
|
|
106
115
|
fireSizeChange();
|
|
107
116
|
}
|
|
108
117
|
|
|
@@ -129,23 +138,7 @@ This will work only when `track_height` is set to `true`
|
|
|
129
138
|
|
|
130
139
|
const debouncedFireSizeChange = debounce(fireSizeChange, 150, false);
|
|
131
140
|
|
|
132
|
-
const setupResizeObserver = () => {
|
|
133
|
-
if (hasResizeObserver) {
|
|
134
|
-
if (!slideInner || typeof slideInner == "undefined") {
|
|
135
|
-
pollForRender();
|
|
136
|
-
} else {
|
|
137
|
-
Observer = new ResizeObserver(() => {
|
|
138
|
-
debouncedFireSizeChange();
|
|
139
|
-
});
|
|
140
|
-
Observer.observe(slideInner);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
141
|
onMount(() => {
|
|
146
142
|
pollForRender();
|
|
147
|
-
return () => {
|
|
148
|
-
Observer && Observer.disconnect();
|
|
149
|
-
};
|
|
150
143
|
});
|
|
151
144
|
</script>
|
package/styles/Grid.scss
CHANGED
|
@@ -51,6 +51,7 @@ $kws-gridview-checked-row-background: $primary-light !default;
|
|
|
51
51
|
background-color: $kws-gridview-checked-row-background !important;
|
|
52
52
|
td {
|
|
53
53
|
background-color: $kws-gridview-checked-row-background !important;
|
|
54
|
+
color: findColorInvert($kws-gridview-checked-row-background) !important;
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default function fuzzysearch(needle, haystack) {
|
|
2
|
+
var tlen = haystack.length;
|
|
3
|
+
var qlen = needle.length;
|
|
4
|
+
if (qlen > tlen) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
if (qlen === tlen) {
|
|
8
|
+
return needle === haystack;
|
|
9
|
+
}
|
|
10
|
+
outer: for (var i = 0, j = 0; i < qlen; i++) {
|
|
11
|
+
var nch = needle.charCodeAt(i);
|
|
12
|
+
while (j < tlen) {
|
|
13
|
+
if (haystack.charCodeAt(j++) === nch) {
|
|
14
|
+
continue outer;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const hasResizeObserver = typeof window.ResizeObserver != "undefined";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Usage: `<div use:resizeObserver on:resize={resizeHandler}>`
|
|
5
|
+
* @param {HTMLElement} node
|
|
6
|
+
* @returns {Object}
|
|
7
|
+
*/
|
|
8
|
+
export function resizeObserver(node) {
|
|
9
|
+
let ro;
|
|
10
|
+
if (hasResizeObserver) {
|
|
11
|
+
ro = new ResizeObserver(() => {
|
|
12
|
+
const e = new CustomEvent("resize", { bubbles: false });
|
|
13
|
+
node.dispatchEvent(e);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
ro.observe(node);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
destroy() {
|
|
21
|
+
hasResizeObserver && ro.disconnect();
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|