@neovici/cosmoz-omnitable 12.0.2 → 12.1.0

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.
@@ -0,0 +1,34 @@
1
+ import { html } from 'lit-html';
2
+
3
+ export const renderHeader = ({
4
+ allSelected,
5
+ onAllCheckboxChange,
6
+ sortAndGroup,
7
+ dataIsValid,
8
+ data,
9
+ columns,
10
+ filters,
11
+ groupOnColumn,
12
+ setFilterState,
13
+ settingsConfig,
14
+ }) => {
15
+ return html` <sort-and-group-provider .value=${sortAndGroup}>
16
+ <div class="header" id="header">
17
+ <input
18
+ class="checkbox all"
19
+ type="checkbox"
20
+ .checked=${allSelected}
21
+ @input=${onAllCheckboxChange}
22
+ ?disabled=${!dataIsValid}
23
+ />
24
+ <cosmoz-omnitable-header-row
25
+ .data=${data}
26
+ .columns=${columns}
27
+ .filters=${filters}
28
+ .groupOnColumn=${groupOnColumn}
29
+ .setFilterState=${setFilterState}
30
+ .settingsConfig=${settingsConfig}
31
+ ></cosmoz-omnitable-header-row>
32
+ </div>
33
+ </sort-and-group-provider>`;
34
+ };
@@ -0,0 +1,62 @@
1
+ /* eslint-disable max-lines-per-function */
2
+ import { _ } from '@neovici/cosmoz-i18next';
3
+ import { html } from 'lit-html';
4
+ import { when } from 'lit-html/directives/when.js';
5
+
6
+ export const renderList = ({
7
+ processedItems,
8
+ dataIsValid,
9
+ filterIsTooStrict,
10
+ loading,
11
+ displayEmptyGroups,
12
+ compareItemsFn,
13
+ setSelectedItems,
14
+ renderItem,
15
+ renderGroup,
16
+ }) => {
17
+ return html` ${when(
18
+ !dataIsValid,
19
+ () => html` <div class="tableContent-empty">
20
+ <slot name="empty-set-message">
21
+ <iron-icon icon="icons:announcement"></iron-icon>
22
+ <div class="tableContent-empty-message">
23
+ <h3>${_('Working set empty')}</h3>
24
+ <p>${_('No data to display')}</p>
25
+ </div>
26
+ </slot>
27
+ </div>`
28
+ )}
29
+ ${when(
30
+ filterIsTooStrict,
31
+ () => html` <div class="tableContent-empty">
32
+ <iron-icon icon="icons:announcement"></iron-icon>
33
+ <div>
34
+ <h3>${_('Filter too strict')}</h3>
35
+ <p>${_('No matches for selection')}</p>
36
+ </div>
37
+ </div>`
38
+ )}
39
+ ${when(
40
+ loading,
41
+ () => html` <div class="tableContent-empty overlay">
42
+ <paper-spinner-lite active></paper-spinner-lite>
43
+ <div>
44
+ <h3>${_('Data set is updating')}</h3>
45
+ </div>
46
+ </div>`
47
+ )}
48
+ <div class="tableContent-scroller" id="scroller">
49
+ <cosmoz-grouped-list
50
+ id="groupedList"
51
+ .data=${processedItems}
52
+ @selected-items-changed=${(event) =>
53
+ setSelectedItems(event.detail.value)}
54
+ .displayEmptyGroups=${
55
+ displayEmptyGroups /* TODO: check if still works */
56
+ }
57
+ .compareItemsFn=${compareItemsFn}
58
+ .renderItem=${renderItem}
59
+ .renderGroup=${renderGroup}
60
+ ></cosmoz-grouped-list>
61
+ </div>`;
62
+ };
@@ -2,10 +2,11 @@ import { useMemo, useState, useRef, useCallback } from 'haunted';
2
2
 
3
3
  import useSavedSettings from './use-saved-settings';
4
4
  import normalize, { sgProps } from './normalize';
5
+ import { useDOMColumns } from '../use-dom-columns';
5
6
 
6
- export default ({ settingsId, initial: ini, ...thru }) => {
7
+ export default ({ settingsId, host }) => {
7
8
  const initial = useMemo(
8
- () => Object.fromEntries(sgProps.map((k) => [k, ini[k]])),
9
+ () => Object.fromEntries(sgProps.map((k) => [k, host[k]])),
9
10
  []
10
11
  ),
11
12
  resetRef = useRef(),
@@ -20,16 +21,17 @@ export default ({ settingsId, initial: ini, ...thru }) => {
20
21
  setSettings,
21
22
  onReset
22
23
  ),
23
- { columns } = thru,
24
+ { enabledColumns } = host,
25
+ columns = useDOMColumns(host, { enabledColumns }),
24
26
  normalizedSettings = useMemo(
25
27
  () =>
26
28
  normalize({
29
+ columns,
27
30
  settings,
28
31
  savedSettings,
29
32
  initial,
30
- ...thru,
31
33
  }),
32
- [settings, savedSettings, ...Object.values(thru)]
34
+ [columns, settings, savedSettings]
33
35
  ),
34
36
  normalizedColumns = useMemo(
35
37
  () =>
@@ -1,48 +1,56 @@
1
- import { useEffect, useState } from 'haunted';
1
+ import { useEffect, useLayoutEffect, useState } from 'haunted';
2
2
  import { memooize } from './memoize';
3
3
 
4
- const
5
- columnSymbol = Symbol('column'),
6
- verifyColumnSetup = columns => {
4
+ const columnSymbol = Symbol('column'),
5
+ verifyColumnSetup = (columns) => {
7
6
  let ok = true;
8
- const columnNames = columns.map(c => c.name);
7
+ const columnNames = columns.map((c) => c.name);
9
8
  // Check if column names are set
10
- columns.forEach(column => {
9
+ columns.forEach((column) => {
11
10
  if (column.name != null) {
12
11
  return;
13
12
  }
14
13
  ok = false;
15
14
  // eslint-disable-next-line no-console
16
- console.error('The name attribute needs to be set on all columns! Missing on column', column);
15
+ console.error(
16
+ 'The name attribute needs to be set on all columns! Missing on column',
17
+ column
18
+ );
17
19
  });
18
20
 
19
- columns.forEach(column => {
20
- if (columnNames.indexOf(column.name) === columnNames.lastIndexOf(column.name)) {
21
+ columns.forEach((column) => {
22
+ if (
23
+ columnNames.indexOf(column.name) ===
24
+ columnNames.lastIndexOf(column.name)
25
+ ) {
21
26
  return;
22
27
  }
23
28
  ok = false;
24
29
  // eslint-disable-next-line no-console
25
- console.error('The name attribute needs to be unique among all columns! Not unique on column', column);
30
+ console.error(
31
+ 'The name attribute needs to be unique among all columns! Not unique on column',
32
+ column
33
+ );
26
34
  });
27
35
  return ok;
28
36
  },
29
-
30
37
  // eslint-disable-next-line max-lines-per-function
31
38
  domColumnsToConfig = (host, { enabledColumns }) => {
32
- const domColumns = host.shadowRoot.querySelector('#columnsSlot')
39
+ const domColumns = host.shadowRoot
40
+ .querySelector('#columnsSlot')
33
41
  .assignedElements({ flatten: true })
34
- .filter(child => child.isOmnitableColumn && !child.hidden);
42
+ .filter((child) => child.isOmnitableColumn && !child.hidden);
35
43
 
36
44
  if (!verifyColumnSetup(domColumns)) {
37
45
  return [];
38
46
  }
39
47
 
40
48
  const columns = Array.isArray(enabledColumns)
41
- ? domColumns.filter(column => enabledColumns.includes(column.name))
42
- : domColumns.filter(column => !column.disabled);
49
+ ? domColumns.filter((column) => enabledColumns.includes(column.name))
50
+ : domColumns.filter((column) => !column.disabled);
43
51
 
44
52
  // eslint-disable-next-line max-lines-per-function
45
- return columns.map(column => ({
53
+ return columns.map((column) => ({
46
54
  name: column.name,
47
55
  title: column.title,
48
56
 
@@ -109,18 +117,20 @@ const
109
117
  ownerTree: column.ownerTree,
110
118
  keyProperty: column.keyProperty,
111
119
 
112
- [columnSymbol]: column
120
+ [columnSymbol]: column,
113
121
  }));
114
122
  },
115
-
116
123
  useDOMColumns = (host, { enabledColumns }) => {
117
- const
118
- [columns, setColumns] = useState(() => domColumnsToConfig(host, { enabledColumns }));
124
+ const [columns, setColumns] = useState([]);
125
+
126
+ useLayoutEffect(() => {
127
+ setColumns(domColumnsToConfig(host, { enabledColumns }));
128
+ }, []);
119
129
 
120
130
  useEffect(() => {
121
- const
122
- slot = host.shadowRoot.querySelector('#columnsSlot'),
123
- handler = () => setColumns(domColumnsToConfig(host, { enabledColumns }));
131
+ const slot = host.shadowRoot.querySelector('#columnsSlot'),
132
+ handler = () =>
133
+ setColumns(domColumnsToConfig(host, { enabledColumns }));
124
134
 
125
135
  handler();
126
136
  slot.addEventListener('slotchange', handler);
@@ -134,8 +144,4 @@ const
134
144
  return columns;
135
145
  };
136
146
 
137
-
138
- export {
139
- columnSymbol,
140
- useDOMColumns
141
- };
147
+ export { columnSymbol, useDOMColumns };
@@ -8,12 +8,14 @@ import { render } from 'lit-html';
8
8
 
9
9
  export const useFastLayout = ({
10
10
  host,
11
+ columns,
11
12
  settings,
12
13
  setSettings,
13
- groupOnColumn,
14
14
  resizeSpeedFactor,
15
+ sortAndGroupOptions,
15
16
  }) => {
16
17
  const canvasWidth = useCanvasWidth(host),
18
+ { groupOnColumn } = sortAndGroupOptions,
17
19
  layout = useLayout({
18
20
  canvasWidth,
19
21
  groupOnColumn,
@@ -23,6 +25,19 @@ export const useFastLayout = ({
23
25
  layoutCss = useMemo(
24
26
  () => toCss(tweenedlayout, settings.columns),
25
27
  [tweenedlayout]
28
+ ),
29
+ collapsedColumns = useMemo(
30
+ () =>
31
+ settings.columns.reduce(
32
+ (acc, column, index) =>
33
+ layout[index] != null ||
34
+ column.name === groupOnColumn?.name ||
35
+ column.disabled
36
+ ? acc
37
+ : [...acc, columns.find((c) => c.name === column.name)],
38
+ []
39
+ ),
40
+ [columns, settings, layout]
26
41
  );
27
42
 
28
43
  useResizableColumns({
@@ -32,7 +47,10 @@ export const useFastLayout = ({
32
47
  setSettings: (update) => setSettings(update(settings)),
33
48
  });
34
49
 
35
- useLayoutEffect(() => render(layoutCss, host.$.layoutStyle), [layoutCss]);
50
+ useLayoutEffect(
51
+ () => render(layoutCss, host.shadowRoot.querySelector('#layoutStyle')),
52
+ [layoutCss]
53
+ );
36
54
 
37
- return layout;
55
+ return { collapsedColumns };
38
56
  };
@@ -0,0 +1,22 @@
1
+ import { defaultPlacement } from '@neovici/cosmoz-dropdown';
2
+ const _defaultPlacement = ['top-right', ...defaultPlacement];
3
+
4
+ export const useFooter = ({
5
+ host,
6
+ ...rest
7
+ }) => {
8
+ const {
9
+ csvFilename = 'omnitable.csv',
10
+ xlsxFilename = 'omnitable.xlsx',
11
+ xlsxSheetname = 'Omnitable',
12
+ topPlacement = _defaultPlacement,
13
+ } = host;
14
+
15
+ return {
16
+ csvFilename,
17
+ xlsxFilename,
18
+ xlsxSheetname,
19
+ topPlacement,
20
+ ...rest,
21
+ };
22
+ };
@@ -0,0 +1,72 @@
1
+ /* eslint-disable max-lines-per-function */
2
+ import { useEffect, useMemo } from 'haunted';
3
+
4
+ export const useHeader = ({
5
+ host,
6
+ selectedItems,
7
+ data,
8
+ sortAndGroupOptions,
9
+ collapsedColumns,
10
+ settings,
11
+ filterFunctions,
12
+ settingS,
13
+ filters,
14
+ ...rest
15
+ }) => {
16
+ const allSelected =
17
+ data && data.length > 0 && selectedItems.length === data.length,
18
+ onAllCheckboxChange = (event) => {
19
+ if (event.target.checked) {
20
+ host.shadowRoot.querySelector('#groupedList').selectAll();
21
+ } else {
22
+ host.shadowRoot.querySelector('#groupedList').deselectAll();
23
+ }
24
+ },
25
+ { groupOnColumn } = sortAndGroupOptions,
26
+ hasHiddenFilter = useMemo(
27
+ () =>
28
+ [
29
+ groupOnColumn,
30
+ ...collapsedColumns,
31
+ ...settings.columns.filter((s) => s.disabled),
32
+ ].some(
33
+ (column) =>
34
+ column && Object.keys(filterFunctions).includes(column.name)
35
+ ),
36
+ [filterFunctions, settings, collapsedColumns]
37
+ ),
38
+ settingsConfig = useMemo(
39
+ () => ({
40
+ ...settingS,
41
+ collapsed: collapsedColumns,
42
+ badge: hasHiddenFilter,
43
+ filters,
44
+ }),
45
+ [settingS, collapsedColumns, hasHiddenFilter, filters]
46
+ );
47
+
48
+ useEffect(() => {
49
+ const el = host.shadowRoot.querySelector('#tableContent'),
50
+ observer = new ResizeObserver((entries) =>
51
+ requestAnimationFrame(() => {
52
+ host.style.setProperty(
53
+ '--ot-height',
54
+ entries[0]?.contentRect.height + 'px'
55
+ );
56
+ })
57
+ );
58
+ observer.observe(el);
59
+ return () => observer.unobserve(el);
60
+ }, []);
61
+
62
+ return {
63
+ allSelected,
64
+ onAllCheckboxChange,
65
+ data,
66
+ settingsConfig,
67
+ filters,
68
+ groupOnColumn,
69
+ sortAndGroup: sortAndGroupOptions.sortAndGroup,
70
+ ...rest,
71
+ };
72
+ };
@@ -0,0 +1,214 @@
1
+ /* eslint-disable max-lines-per-function */
2
+ import { html, useCallback, useEffect, useMemo, useRef } from 'haunted';
3
+ import { indexSymbol } from './utils';
4
+ import { isEmpty } from '@neovici/cosmoz-utils/template';
5
+ import { onItemChange as _onItemChange } from './utils-data';
6
+
7
+ const _getGroupRowClasses = (folded) =>
8
+ folded ? 'groupRow groupRow-folded' : 'groupRow',
9
+ _getFoldIcon = (expanded) => (expanded ? 'expand-less' : 'expand-more'),
10
+ renderItem = ({
11
+ columns,
12
+ collapsedColumns,
13
+ onItemClick,
14
+ onCheckboxChange,
15
+ dataIsValid,
16
+ groupOnColumn,
17
+ onItemChange,
18
+ }) => {
19
+ return (item, index, { selected, expanded, toggleCollapse }) => {
20
+ return html` <div class="item-row-wrapper">
21
+ <div
22
+ ?selected=${selected}
23
+ part="itemRow itemRow-${item[indexSymbol]}"
24
+ .dataIndex=${item[indexSymbol]}
25
+ .dataItem=${item}
26
+ class="itemRow"
27
+ @click=${onItemClick}
28
+ >
29
+ <input
30
+ class="checkbox"
31
+ type="checkbox"
32
+ .checked=${selected}
33
+ .dataItem=${item}
34
+ @input=${onCheckboxChange}
35
+ ?disabled=${!dataIsValid}
36
+ />
37
+ <cosmoz-omnitable-item-row
38
+ .columns=${columns}
39
+ .index=${index}
40
+ .selected=${selected}
41
+ .expanded=${expanded}
42
+ .item=${item}
43
+ .groupOnColumn=${groupOnColumn}
44
+ .onItemChange=${onItemChange}
45
+ >
46
+ </cosmoz-omnitable-item-row>
47
+ <paper-icon-button
48
+ class="expand"
49
+ ?hidden=${isEmpty(collapsedColumns.length)}
50
+ .icon=${_getFoldIcon(expanded)}
51
+ @click=${toggleCollapse}
52
+ ></paper-icon-button>
53
+ </div>
54
+ <cosmoz-omnitable-item-expand
55
+ .columns=${collapsedColumns}
56
+ .item=${item}
57
+ .index=${index}
58
+ ?selected=${selected}
59
+ ?expanded=${expanded}
60
+ .groupOnColumn=${groupOnColumn}
61
+ part="item-expand"
62
+ >
63
+ </cosmoz-omnitable-item-expand>
64
+ </div>`;
65
+ };
66
+ },
67
+ renderGroup = ({ onCheckboxChange, dataIsValid, groupOnColumn }) => {
68
+ return (item, index, { selected, folded, toggleFold }) =>
69
+ html` <div
70
+ class="${_getGroupRowClasses(folded)}"
71
+ part="groupRow groupRow-${item[indexSymbol]}"
72
+ >
73
+ <input
74
+ class="checkbox"
75
+ type="checkbox"
76
+ .checked=${selected}
77
+ .dataItem=${item}
78
+ @input=${onCheckboxChange}
79
+ ?disabled=${!dataIsValid}
80
+ />
81
+ <h3 class="groupRow-label">
82
+ <div><span>${groupOnColumn?.title}</span>: &nbsp;</div>
83
+ <cosmoz-omnitable-group-row
84
+ .column=${groupOnColumn}
85
+ .item=${item.items?.[0]}
86
+ .selected=${selected}
87
+ .folded=${folded}
88
+ .group=${item}
89
+ ></cosmoz-omnitable-group-row>
90
+ </h3>
91
+ <div class="groupRow-badge">${item.items.length}</div>
92
+ <paper-icon-button
93
+ class="fold"
94
+ .icon=${_getFoldIcon(folded)}
95
+ @click=${toggleFold}
96
+ ></paper-icon-button>
97
+ </div>`;
98
+ };
99
+
100
+ export const useList = ({
101
+ host,
102
+ dataIsValid,
103
+ processedItems,
104
+ columns,
105
+ collapsedColumns,
106
+ sortAndGroupOptions,
107
+ ...rest
108
+ }) => {
109
+ const { loading = false, displayEmptyGroups = false, compareItemsFn } = host,
110
+ keyState = useRef({ shiftKey: false, ctrlKey: false }),
111
+ onCheckboxChange = useCallback((event) => {
112
+ const item = event.target.dataItem,
113
+ selected = event.target.checked;
114
+ if (keyState.current.shiftKey) {
115
+ host.shadowRoot
116
+ .querySelector('#groupedList')
117
+ .toggleSelectTo(item, selected);
118
+ } else if (keyState.current.ctrlKey) {
119
+ event.target.checked = true;
120
+ host.shadowRoot.querySelector('#groupedList').selectOnly(item);
121
+ } else {
122
+ host.shadowRoot
123
+ .querySelector('#groupedList')
124
+ .toggleSelect(item, selected);
125
+ }
126
+
127
+ event.preventDefault();
128
+ event.stopPropagation();
129
+ }, []);
130
+
131
+ useEffect(() => {
132
+ const handler = ({ shiftKey, ctrlKey }) => {
133
+ keyState.current = { shiftKey, ctrlKey };
134
+ };
135
+ window.addEventListener('keydown', handler);
136
+ window.addEventListener('keyup', handler);
137
+ return () => {
138
+ window.removeEventListener('keydown', handler);
139
+ window.removeEventListener('keyup', handler);
140
+ };
141
+ }, []);
142
+
143
+ const onItemClick = useCallback((e) => {
144
+ const composedPath = e.composedPath(),
145
+ path = composedPath.slice(0, composedPath.indexOf(e.currentTarget));
146
+
147
+ if (path.find((e) => e.matches?.('a, .checkbox, .expand'))) {
148
+ return;
149
+ }
150
+
151
+ host.dispatchEvent(
152
+ new window.CustomEvent('omnitable-item-click', {
153
+ bubbles: true,
154
+ composed: true,
155
+ detail: {
156
+ item: e.currentTarget.dataItem,
157
+ index: e.currentTarget.dataIndex,
158
+ },
159
+ })
160
+ );
161
+ }, []);
162
+
163
+ useEffect(() => {
164
+ host.shadowRoot.querySelector('#groupedList').scrollTarget =
165
+ host.shadowRoot.querySelector('#scroller');
166
+ }, []);
167
+
168
+ const { groupOnColumn } = sortAndGroupOptions,
169
+ onItemChange = useCallback(
170
+ (column, item) => (value) => _onItemChange(host, column, item, value),
171
+ []
172
+ );
173
+
174
+ return {
175
+ ...rest,
176
+ processedItems,
177
+ dataIsValid,
178
+ filterIsTooStrict: dataIsValid && processedItems.length < 1,
179
+ loading,
180
+ compareItemsFn,
181
+ displayEmptyGroups,
182
+
183
+ renderItem: useMemo(
184
+ () =>
185
+ renderItem({
186
+ columns,
187
+ collapsedColumns,
188
+ onItemClick,
189
+ onCheckboxChange,
190
+ dataIsValid,
191
+ groupOnColumn,
192
+ onItemChange,
193
+ }),
194
+ [
195
+ columns,
196
+ collapsedColumns,
197
+ onItemClick,
198
+ onCheckboxChange,
199
+ dataIsValid,
200
+ groupOnColumn,
201
+ onItemChange,
202
+ ]
203
+ ),
204
+ renderGroup: useMemo(
205
+ () =>
206
+ renderGroup({
207
+ onCheckboxChange,
208
+ dataIsValid,
209
+ groupOnColumn,
210
+ }),
211
+ [onCheckboxChange, dataIsValid, groupOnColumn]
212
+ ),
213
+ };
214
+ };