@neovici/cosmoz-omnitable 8.14.2 → 9.0.1

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.
@@ -72,7 +72,14 @@ const placement = ['bottom-right', ...defaultPlacement],
72
72
  </style>
73
73
  <div class="headline">
74
74
  ${_('Sort and filter')}
75
- <button class="close" @click=${(e) => e.currentTarget?.blur()}>
75
+ <button
76
+ class="close"
77
+ @click=${(e) => {
78
+ const tg = e.currentTarget;
79
+ tg?.focus();
80
+ tg?.blur();
81
+ }}
82
+ >
76
83
  ${close}
77
84
  </button>
78
85
  </div>
@@ -17,14 +17,15 @@ export default css`
17
17
  font-weight: 500;
18
18
  font-size: 16px;
19
19
  line-height: 0.95;
20
- padding: 12px 14px 11px 14px;
20
+ padding: 10px 14px;
21
21
  display: flex;
22
+ align-items: center
22
23
  }
23
24
  .close {
24
25
  border: none;
25
26
  background: none;
26
27
  cursor: pointer;
27
- padding: 0;
28
+ padding: 2.5px 6px;
28
29
  margin-left: auto;
29
30
  }
30
31
 
@@ -56,6 +57,7 @@ export default css`
56
57
  }
57
58
  .heading svg {
58
59
  margin-left: auto;
60
+ margin-right: 4px;
59
61
  }
60
62
  .heading[data-opened] svg {
61
63
  transform: scaleY(-1);
@@ -147,6 +149,7 @@ export default css`
147
149
  padding: 14px;
148
150
  }
149
151
  .sg {
152
+ color: inherit;
150
153
  border: 1px solid rgba(0, 0, 0, 0.2);
151
154
  border-radius: 2px;
152
155
  font-size: 13px;
@@ -120,10 +120,7 @@ const
120
120
  useEffect(() => {
121
121
  const
122
122
  slot = host.shadowRoot.querySelector('#columnsSlot'),
123
- handler = () => {
124
- host.suppressNextScrollReset();
125
- setColumns(domColumnsToConfig(host, { enabledColumns }));
126
- };
123
+ handler = () => setColumns(domColumnsToConfig(host, { enabledColumns }));
127
124
 
128
125
  handler();
129
126
  slot.addEventListener('slotchange', handler);
@@ -1,4 +1,4 @@
1
- import { useEffect, useLayoutEffect, useMemo } from 'haunted';
1
+ import { useLayoutEffect, useMemo } from 'haunted';
2
2
  import { toCss } from './compute-layout';
3
3
  import { useResizableColumns } from './use-resizable-columns';
4
4
  import { useCanvasWidth } from './use-canvas-width';
@@ -6,33 +6,33 @@ import { useTweenArray } from './use-tween-array';
6
6
  import { useLayout } from './use-layout';
7
7
  import { render } from 'lit-html';
8
8
 
9
- export const useFastLayout = ({ host, settings, setSettings, groupOnColumn, resizeSpeedFactor }) => {
10
- const
11
- canvasWidth = useCanvasWidth(host),
12
- layout = useLayout({ canvasWidth, groupOnColumn, config: settings.columns }),
9
+ export const useFastLayout = ({
10
+ host,
11
+ settings,
12
+ setSettings,
13
+ groupOnColumn,
14
+ resizeSpeedFactor,
15
+ }) => {
16
+ const canvasWidth = useCanvasWidth(host),
17
+ layout = useLayout({
18
+ canvasWidth,
19
+ groupOnColumn,
20
+ config: settings.columns,
21
+ }),
13
22
  tweenedlayout = useTweenArray(layout, resizeSpeedFactor),
14
- layoutCss = useMemo(() => toCss(tweenedlayout, settings.columns), [tweenedlayout]);
23
+ layoutCss = useMemo(
24
+ () => toCss(tweenedlayout, settings.columns),
25
+ [tweenedlayout]
26
+ );
15
27
 
16
- useResizableColumns({ host, canvasWidth, layout, setSettings: update => setSettings(update(settings)) });
28
+ useResizableColumns({
29
+ host,
30
+ canvasWidth,
31
+ layout,
32
+ setSettings: (update) => setSettings(update(settings)),
33
+ });
17
34
 
18
35
  useLayoutEffect(() => render(layoutCss, host.$.layoutStyle), [layoutCss]);
19
36
 
20
- // force iron-list to render when the omnitable becomes visible
21
- useEffect(() => {
22
- let lastWidth = 0;
23
-
24
- const
25
- onResize = ([entry]) => {
26
- if (lastWidth === 0) {
27
- requestAnimationFrame(() => host.$.groupedList.$.list._render());
28
- }
29
- lastWidth = entry.contentRect?.width;
30
- },
31
- observer = new ResizeObserver(onResize);
32
-
33
- observer.observe(host);
34
- return () => observer.unobserve(host);
35
- }, []);
36
-
37
37
  return layout;
38
38
  };
@@ -5,6 +5,7 @@ import { useSettings } from './settings';
5
5
  import { useDOMColumns } from './use-dom-columns';
6
6
  import { useSortAndGroupOptions } from './use-sort-and-group-options';
7
7
  import { onItemChange } from './utils-data';
8
+ import { useNotifyProperty } from '@neovici/cosmoz-utils/lib/hooks/use-notify-property'
8
9
 
9
10
  // eslint-disable-next-line max-lines-per-function
10
11
  export const useOmnitable = (host) => {
@@ -19,6 +20,7 @@ export const useOmnitable = (host) => {
19
20
  // TODO: drop filterFunctions
20
21
  {
21
22
  processedItems,
23
+ visibleData,
22
24
  filters,
23
25
  setFilterState,
24
26
  numProcessedItems,
@@ -99,6 +101,8 @@ export const useOmnitable = (host) => {
99
101
  return () => observer.unobserve(el);
100
102
  }, []);
101
103
 
104
+ useNotifyProperty('visibleData', visibleData);
105
+
102
106
  return {
103
107
  ...sortAndGroupOptions,
104
108
 
@@ -3,10 +3,12 @@ import { genericSorter } from './generic-sorter';
3
3
  import { invoke } from './invoke';
4
4
  import { columnSymbol } from './use-dom-columns';
5
5
  import { useHashState } from './use-hash-state';
6
+ import { indexSymbol } from './utils';
6
7
 
7
- const
8
- sortBy = (valueFn, descending) => (a, b) => genericSorter(valueFn(a), valueFn(b)) * (descending ? -1 : 1),
9
- kebab = input => input.replace(/([a-z0-9])([A-Z])/gu, '$1-$2').toLowerCase(),
8
+ const sortBy = (valueFn, descending) => (a, b) =>
9
+ genericSorter(valueFn(a), valueFn(b)) * (descending ? -1 : 1),
10
+ kebab = (input) =>
11
+ input.replace(/([a-z0-9])([A-Z])/gu, '$1-$2').toLowerCase(),
10
12
  notifyChanges = (column, changes) => {
11
13
  if (!column || !changes) {
12
14
  return;
@@ -16,55 +18,93 @@ const
16
18
  column[columnSymbol].__ownChange = true;
17
19
  column[columnSymbol][key] = value;
18
20
  column[columnSymbol].__ownChange = false;
19
- column[columnSymbol].dispatchEvent(new CustomEvent(`${ kebab(key) }-changed`, { bubbles: true, detail: { value }}));
21
+ column[columnSymbol].dispatchEvent(
22
+ new CustomEvent(`${kebab(key)}-changed`, {
23
+ bubbles: true,
24
+ detail: { value },
25
+ })
26
+ );
20
27
  });
21
- };
28
+ },
29
+ assignIndex = (item, index) => Object.assign(item, { [indexSymbol]: index });
22
30
 
23
31
  // eslint-disable-next-line max-lines-per-function
24
- export const useProcessedItems = ({ data, columns, groupOnColumn, groupOnDescending, sortOnColumn, descending, hashParam }) => {
25
- const
26
- write = useCallback(([filter, value]) => {
27
- const column = columns.find(({ name }) => name === filter);
28
- if (column == null) {
29
- return [filter, undefined];
30
- }
31
- return [filter, value.filter && column.serializeFilter(column, value.filter)];
32
- }, [columns]),
33
-
34
- read = useCallback(([filter, value]) => {
35
- const column = columns.find(({ name }) => name === filter);
36
- if (column == null) {
37
- return [filter, undefined];
38
- }
39
-
40
- const state = { filter: column.deserializeFilter(column, value) };
41
- notifyChanges(column, state);
42
- return [filter, state];
43
- }, [columns]),
44
-
45
- [filters, setFilters] = useHashState({}, hashParam, { multi: true, suffix: '-filter--', write, read }),
46
- // TODO: drop extra info from state
47
- setFilterState = useCallback((name, state) => setFilters(filters => {
48
- const newState = invoke(state, filters[name]);
49
-
50
- notifyChanges(columns.find(c => c.name === name), newState);
51
-
52
- return {
53
- ...filters,
54
- [name]: {
55
- ...filters[name],
56
- ...newState
32
+ export const useProcessedItems = ({
33
+ data,
34
+ columns,
35
+ groupOnColumn,
36
+ groupOnDescending,
37
+ sortOnColumn,
38
+ descending,
39
+ hashParam,
40
+ }) => {
41
+ const write = useCallback(
42
+ ([filter, value]) => {
43
+ const column = columns.find(({ name }) => name === filter);
44
+ if (column == null) {
45
+ return [filter, undefined];
46
+ }
47
+ return [
48
+ filter,
49
+ value.filter && column.serializeFilter(column, value.filter),
50
+ ];
51
+ },
52
+ [columns]
53
+ ),
54
+ read = useCallback(
55
+ ([filter, value]) => {
56
+ const column = columns.find(({ name }) => name === filter);
57
+ if (column == null) {
58
+ return [filter, undefined];
57
59
  }
58
- };
59
- }), [columns, setFilters]),
60
60
 
61
- filterValues = useMemo(() => Object.values(filters).map(f => f.filter), [filters]),
61
+ const state = { filter: column.deserializeFilter(column, value) };
62
+ notifyChanges(column, state);
63
+ return [filter, state];
64
+ },
65
+ [columns]
66
+ ),
67
+ [filters, setFilters] = useHashState({}, hashParam, {
68
+ multi: true,
69
+ suffix: '-filter--',
70
+ write,
71
+ read,
72
+ }),
73
+ // TODO: drop extra info from state
74
+ setFilterState = useCallback(
75
+ (name, state) =>
76
+ setFilters((filters) => {
77
+ const newState = invoke(state, filters[name]);
78
+
79
+ notifyChanges(
80
+ columns.find((c) => c.name === name),
81
+ newState
82
+ );
83
+
84
+ return {
85
+ ...filters,
86
+ [name]: {
87
+ ...filters[name],
88
+ ...newState,
89
+ },
90
+ };
91
+ }),
92
+ [columns, setFilters]
93
+ ),
94
+ filterValues = useMemo(
95
+ () => Object.values(filters).map((f) => f.filter),
96
+ [filters]
97
+ ),
62
98
  filterFunctions = useMemo(() => {
63
- return Object.fromEntries(columns
64
- .map(col => [col.name, col.getFilterFn(col, filters[col.name]?.filter)])
65
- .filter(([, fn]) => fn !== undefined));
99
+ return Object.fromEntries(
100
+ columns
101
+ .map((col) => [
102
+ col.name,
103
+ col.getFilterFn(col, filters[col.name]?.filter),
104
+ ])
105
+ .filter(([, fn]) => fn !== undefined)
106
+ );
66
107
  }, [columns, ...filterValues]),
67
-
68
108
  filteredItems = useMemo(() => {
69
109
  if (!Array.isArray(data) || data.length === 0) {
70
110
  return [];
@@ -74,30 +114,50 @@ export const useProcessedItems = ({ data, columns, groupOnColumn, groupOnDescend
74
114
  return data.slice();
75
115
  }
76
116
 
77
- return data.filter(item => Object.values(filterFunctions).every(filterFn => filterFn(item)));
117
+ return data.filter((item) =>
118
+ Object.values(filterFunctions).every((filterFn) => filterFn(item))
119
+ );
78
120
  }, [data, filterFunctions]),
79
-
80
121
  // todo: extract function
122
+ // eslint-disable-next-line max-lines-per-function
81
123
  processedItems = useMemo(() => {
82
- if (!groupOnColumn && sortOnColumn != null && sortOnColumn.sortOn != null) {
83
- return filteredItems.slice().sort(sortBy(a => sortOnColumn.getComparableValue({ ...sortOnColumn, valuePath: sortOnColumn.sortOn }, a), descending));
124
+ if (
125
+ !groupOnColumn &&
126
+ sortOnColumn != null &&
127
+ sortOnColumn.sortOn != null
128
+ ) {
129
+ return filteredItems
130
+ .slice()
131
+ .sort(
132
+ sortBy(
133
+ (a) =>
134
+ sortOnColumn.getComparableValue(
135
+ { ...sortOnColumn, valuePath: sortOnColumn.sortOn },
136
+ a
137
+ ),
138
+ descending
139
+ )
140
+ );
84
141
  }
85
142
 
86
143
  if (groupOnColumn != null && groupOnColumn.groupOn != null) {
87
144
  const groupedResults = filteredItems.reduce((acc, item) => {
88
- const gval = groupOnColumn.getComparableValue({ ...groupOnColumn, valuePath: groupOnColumn.groupOn }, item);
145
+ const gval = groupOnColumn.getComparableValue(
146
+ { ...groupOnColumn, valuePath: groupOnColumn.groupOn },
147
+ item
148
+ );
89
149
 
90
150
  if (gval === undefined) {
91
151
  return acc;
92
152
  }
93
153
 
94
- let group = acc.find(g => g.id === gval);
154
+ let group = acc.find((g) => g.id === gval);
95
155
 
96
156
  if (!group) {
97
157
  group = {
98
158
  id: gval,
99
159
  name: gval,
100
- items: [item]
160
+ items: [item],
101
161
  };
102
162
  return [...acc, group];
103
163
  }
@@ -106,30 +166,74 @@ export const useProcessedItems = ({ data, columns, groupOnColumn, groupOnDescend
106
166
  return acc;
107
167
  }, []);
108
168
 
109
- groupedResults.sort(sortBy(
110
- a => groupOnColumn.getComparableValue({ ...groupOnColumn, valuePath: groupOnColumn.groupOn }, a.items[0]), groupOnDescending));
169
+ groupedResults.sort(
170
+ sortBy(
171
+ (a) =>
172
+ groupOnColumn.getComparableValue(
173
+ { ...groupOnColumn, valuePath: groupOnColumn.groupOn },
174
+ a.items[0]
175
+ ),
176
+ groupOnDescending
177
+ )
178
+ );
111
179
 
112
180
  if (!sortOnColumn) {
113
181
  return groupedResults;
114
182
  }
115
183
 
116
184
  return groupedResults
117
- .filter(group => Array.isArray(group.items))
118
- .map(group => {
119
- group.items.sort(sortBy(a => sortOnColumn.getComparableValue({ ...sortOnColumn, valuePath: sortOnColumn.sortOn }, a), descending));
185
+ .filter((group) => Array.isArray(group.items))
186
+ .map((group) => {
187
+ group.items.sort(
188
+ sortBy(
189
+ (a) =>
190
+ sortOnColumn.getComparableValue(
191
+ { ...sortOnColumn, valuePath: sortOnColumn.sortOn },
192
+ a
193
+ ),
194
+ descending
195
+ )
196
+ );
120
197
  return group;
121
198
  });
122
199
  }
123
200
 
124
201
  return filteredItems;
125
- }, [filteredItems, groupOnColumn, groupOnDescending, sortOnColumn, descending]);
202
+ }, [
203
+ filteredItems,
204
+ groupOnColumn,
205
+ groupOnDescending,
206
+ sortOnColumn,
207
+ descending,
208
+ ]),
209
+ visibleData = useMemo(() => {
210
+ let index = 0,
211
+ groupIndex = 0;
212
+ const result = [];
213
+ processedItems.forEach((item) => {
214
+
215
+ if(Array.isArray(item.items)) {
216
+ assignIndex(item, groupIndex++);
217
+ item.items.forEach(groupItem => {
218
+ assignIndex(groupItem, index++);
219
+ result.push(groupItem);
220
+ })
221
+ return;
222
+ }
223
+
224
+ assignIndex(item, index++);
225
+ return result.push(item);
226
+ }, []);
227
+ return result;
228
+ }, [processedItems]);
126
229
 
127
230
  return {
128
231
  processedItems,
232
+ visibleData,
129
233
  filters,
130
234
  filterFunctions,
131
235
  setFilterState,
132
236
  numProcessedItems: filteredItems.length,
133
- groupsCount: processedItems[0]?.items != null ? processedItems.length : 0
237
+ groupsCount: processedItems[0]?.items != null ? processedItems.length : 0,
134
238
  };
135
239
  };
@@ -1,47 +1,49 @@
1
- import { useEffect, useRef, useState } from 'haunted';
1
+ import { useCallback, useEffect, useRef, useState } from 'haunted';
2
2
 
3
- export const
4
- isCloseEnough = (a = 0, b = 0) => Math.abs(a - b) < 0.1,
3
+ export const isCloseEnough = (a = 0, b = 0) => Math.abs(a - b) < 0.1,
4
+ // eslint-disable-next-line max-lines-per-function
5
5
  useTweenArray = (target, speedFactor = 1.9) => {
6
- const
7
- requestRef = useRef(),
8
- targetRef = useRef(target),
9
- runningRef = useRef(false),
6
+ const state = useRef({
7
+ request: undefined,
8
+ target,
9
+ running: false,
10
+ }),
10
11
  [tween, setTween] = useState(target),
12
+ animate = useCallback(() => {
13
+ state.current.request = requestAnimationFrame(animate);
11
14
 
12
- animate = () => {
13
- requestRef.current = requestAnimationFrame(animate);
14
-
15
- if (!runningRef.current) {
15
+ if (!state.current.running) {
16
16
  return;
17
17
  }
18
18
 
19
- setTween(tween => {
20
- const target = targetRef.current;
19
+ setTween((tween) => {
20
+ const target = state.current.target;
21
21
 
22
22
  if (target.every((t, idx) => tween[idx] === t)) {
23
- runningRef.current = false;
23
+ state.current.running = false;
24
24
  return tween;
25
25
  }
26
26
 
27
27
  return target.map((t, idx) =>
28
28
  isCloseEnough(tween[idx], t)
29
29
  ? t
30
- : (tween[idx] ?? 0) + ((t ?? 0) - (tween[idx] ?? 0)) / speedFactor || 0);
30
+ : (tween[idx] ?? 0) +
31
+ ((t ?? 0) - (tween[idx] ?? 0)) / speedFactor || 0
32
+ );
31
33
  });
32
- };
34
+ }, []);
33
35
 
34
36
  useEffect(() => {
35
- requestRef.current = requestAnimationFrame(animate);
36
- return () => cancelAnimationFrame(requestRef.current);
37
+ state.current.request = requestAnimationFrame(animate);
38
+ return () => cancelAnimationFrame(state.current.request);
37
39
  }, []);
38
40
 
39
41
  useEffect(() => {
40
- if (targetRef.current.length === 0 && target.length > 0) {
42
+ if (state.current.target.length === 0 && target.length > 0) {
41
43
  setTween(target);
42
44
  }
43
- targetRef.current = target;
44
- runningRef.current = true;
45
+ state.current.target = target;
46
+ state.current.running = true;
45
47
  }, [target]);
46
48
 
47
49
  return tween;
package/lib/utils.js ADDED
@@ -0,0 +1,10 @@
1
+ const notifyResize = (target, index) =>
2
+ target.dispatchEvent(
3
+ new CustomEvent('update-item-size', {
4
+ bubbles: true,
5
+ detail: { index },
6
+ })
7
+ ),
8
+ indexSymbol = Symbol('index');
9
+
10
+ export { notifyResize, indexSymbol };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-omnitable",
3
- "version": "8.14.2",
3
+ "version": "9.0.1",
4
4
  "description": "[![Build Status](https://travis-ci.org/Neovici/cosmoz-omnitable.svg?branch=master)](https://travis-ci.org/Neovici/cosmoz-omnitable)",
5
5
  "keywords": [
6
6
  "web-components"
@@ -57,7 +57,7 @@
57
57
  "@neovici/cosmoz-collapse": "^1.1.0",
58
58
  "@neovici/cosmoz-datetime-input": "^3.0.3",
59
59
  "@neovici/cosmoz-dropdown": "^1.5.0",
60
- "@neovici/cosmoz-grouped-list": "^3.2.0",
60
+ "@neovici/cosmoz-grouped-list": "^4.0.7",
61
61
  "@neovici/cosmoz-i18next": "^3.1.1",
62
62
  "@neovici/cosmoz-page-router": "^7.0.0 || ^8.0.0",
63
63
  "@neovici/cosmoz-utils": "^3.19.0",