@neovici/cosmoz-omnitable 8.11.1 → 8.14.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.
package/README.md CHANGED
@@ -2,6 +2,5 @@ cosmoz-omnitable
2
2
  =================
3
3
 
4
4
  [![Build Status](https://github.com/Neovici/cosmoz-omnitable/workflows/Github%20CI/badge.svg)](https://github.com/Neovici/cosmoz-omnitable/actions?workflow=Github+CI)
5
- [![Maintainability](https://api.codeclimate.com/v1/badges/6b16292868f47977eee2/maintainability)](https://codeclimate.com/github/Neovici/cosmoz-omnitable/maintainability)
6
5
  [![codecov](https://codecov.io/gh/Neovici/cosmoz-omnitable/branch/master/graph/badge.svg?token=j46iVMxjcs)](https://codecov.io/gh/Neovici/cosmoz-omnitable)
7
6
  [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
@@ -1,7 +1,6 @@
1
1
  import { html, component } from 'haunted';
2
2
  import { repeat } from 'lit-html/directives/repeat';
3
3
  import './lib/cosmoz-omnitable-resize-nub';
4
- import sort from './lib/cosmoz-omnitable-sort';
5
4
 
6
5
  const /* eslint-disable-next-line max-lines-per-function */
7
6
  renderHeaderRow = ({
@@ -28,7 +27,6 @@ const /* eslint-disable-next-line max-lines-per-function */
28
27
  (state) => setFilterState(column.name, state),
29
28
  column.source(column, data)
30
29
  ),
31
- sort(column.name),
32
30
  ]}
33
31
  </div>`,
34
32
  html`<cosmoz-omnitable-resize-nub
@@ -1,119 +1,80 @@
1
1
  /* eslint-disable max-lines */
2
2
  import { tagged as css } from '@neovici/cosmoz-utils';
3
3
  const checkbox = css`
4
- .checkbox {
5
- box-sizing: border-box;
6
- width: 18px;
7
- height: 18px;
8
- background: transparent;
9
- border-radius: 4px;
10
- appearance: none;
11
- -webkit-appearance: none;
12
- outline: none;
13
- position: relative;
14
- user-select: none;
15
- cursor: pointer;
16
- display: inline-block;
17
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.16) inset;
18
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
19
- vertical-align: middle;
20
- transition: background-color 140ms;
21
- margin: 1px 12px;
22
- flex: none;
23
- }
24
-
25
- .checkbox:checked {
26
- background-color: var(
27
- --cosmoz-omnitable-checkbox-checked-color,
28
- var(--primary-color)
29
- );
30
- box-shadow: none;
31
- }
32
-
33
- .checkbox:checked::before {
34
- content: '';
35
- position: absolute;
36
- box-sizing: content-box;
37
- width: 5px;
38
- height: 10px;
39
- border: 2.4px solid #fff;
40
- border-top: none;
41
- border-left: none;
42
- transform-origin: 5px 10px;
43
- transform: translate(3px) rotate(45deg);
44
- }
45
-
46
- .checkbox::after {
47
- content: '';
48
- display: block;
49
- bottom: -5px;
50
- left: -5px;
51
- right: -5px;
52
- top: -5px;
53
- }
54
-
55
- .checkbox:hover {
56
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 1) inset, 0 0 2px 6px #2021240f;
57
- }
58
-
59
- .checkbox:checked:hover {
60
- box-shadow: 0 0 2px 6px #2021240f;
61
- }
62
-
63
- .checkbox:indeterminate::before {
64
- content: '';
65
- position: absolute;
66
- width: 10px;
67
- height: 2px;
68
- left: 4px;
69
- top: 8px;
70
- background-color: var(
71
- --cosmoz-omnitable-checkbox-checked-color,
72
- var(--primary-color)
73
- );
74
- }
75
- `,
76
- sort = css`
77
- .sort {
78
- display: inline-flex;
79
- width: 10px;
80
- cursor: pointer;
81
- align-items: center;
82
- margin-top: 18px;
83
- overflow: hidden;
84
- flex: none;
85
- background: none;
86
- border: none;
87
- outline: none;
88
- color: inherit;
89
- padding: 0;
90
- }
91
- .sort svg {
92
- display: block;
93
- }
94
- .sort[data-sort] {
95
- color: var(
96
- --cosmoz-omnitable-checkbox-checked-color,
97
- var(--primary-color)
98
- );
99
- }
100
- .sort:not([data-sort='desc']) {
101
- transform: scaleY(-1);
102
- }
103
- :not(:hover) > * > .sort:not([data-sort]) {
104
- display: none;
105
- }
106
- .header-cell {
107
- display: inline-flex;
108
- position: relative;
109
- }
110
- .header-cell :not(.sort) {
111
- min-width: 0;
112
- flex: auto;
113
- }
114
- `;
115
-
116
- export { checkbox, sort };
4
+ .checkbox {
5
+ box-sizing: border-box;
6
+ width: 18px;
7
+ height: 18px;
8
+ background: transparent;
9
+ border-radius: 4px;
10
+ appearance: none;
11
+ -webkit-appearance: none;
12
+ outline: none;
13
+ position: relative;
14
+ user-select: none;
15
+ cursor: pointer;
16
+ display: inline-block;
17
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.16) inset;
18
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
19
+ vertical-align: middle;
20
+ transition: background-color 140ms;
21
+ margin: 1px 12px;
22
+ flex: none;
23
+ }
24
+
25
+ .checkbox:checked {
26
+ background-color: var(
27
+ --cosmoz-omnitable-checkbox-checked-color,
28
+ var(--primary-color)
29
+ );
30
+ box-shadow: none;
31
+ }
32
+
33
+ .checkbox:checked::before {
34
+ content: '';
35
+ position: absolute;
36
+ box-sizing: content-box;
37
+ width: 5px;
38
+ height: 10px;
39
+ border: 2.4px solid #fff;
40
+ border-top: none;
41
+ border-left: none;
42
+ transform-origin: 5px 10px;
43
+ transform: translate(3px) rotate(45deg);
44
+ }
45
+
46
+ .checkbox::after {
47
+ content: '';
48
+ display: block;
49
+ bottom: -5px;
50
+ left: -5px;
51
+ right: -5px;
52
+ top: -5px;
53
+ }
54
+
55
+ .checkbox:hover {
56
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 1) inset, 0 0 2px 6px #2021240f;
57
+ }
58
+
59
+ .checkbox:checked:hover {
60
+ box-shadow: 0 0 2px 6px #2021240f;
61
+ }
62
+
63
+ .checkbox:indeterminate::before {
64
+ content: '';
65
+ position: absolute;
66
+ width: 10px;
67
+ height: 2px;
68
+ left: 4px;
69
+ top: 8px;
70
+ background-color: var(
71
+ --cosmoz-omnitable-checkbox-checked-color,
72
+ var(--primary-color)
73
+ );
74
+ }
75
+ `;
76
+
77
+ export { checkbox };
117
78
 
118
79
  export default `<style>
119
80
  :host {
@@ -387,7 +348,7 @@ export default `<style>
387
348
  }
388
349
 
389
350
  .itemRow[selected] {
390
- background-color: var(--cosmoz-omnitable-selection-color, rgb(195, 212, 248)) !important;
351
+ background-color: var(--cosmoz-omnitable-selection-color, rgb(195, 212, 248));
391
352
  @apply --cosmoz-omnitable-selected-row;
392
353
  }
393
354
 
@@ -520,5 +481,4 @@ export default `<style>
520
481
  .expand:hover, .fold:hover {
521
482
  color: #000;
522
483
  }
523
- ${sort}
524
484
  </style>`;
@@ -15,7 +15,6 @@ import './cosmoz-omnitable-group-row';
15
15
  import './cosmoz-omnitable-columns';
16
16
  import styles from './cosmoz-omnitable-styles';
17
17
 
18
-
19
18
  import { PolymerElement } from '@polymer/polymer/polymer-element';
20
19
  import { html } from '@polymer/polymer/lib/utils/html-tag';
21
20
  import { html as litHtml } from 'lit-html';
@@ -50,7 +49,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
50
49
  <input class="checkbox all" type="checkbox" checked="[[ _allSelected ]]" on-input="_onAllCheckboxChange" disabled$="[[ !_dataIsValid ]]" />
51
50
  <cosmoz-omnitable-header-row
52
51
  data="[[ data ]]"
53
- columns="[[ normalizedColumns ]]"
52
+ columns="[[ columns ]]"
54
53
  filters="[[ filters ]]"
55
54
  group-on-column="[[ groupOnColumn ]]"
56
55
  set-filter-state="[[ setFilterState ]]"
@@ -96,11 +95,11 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
96
95
  >
97
96
  <template slot="templates" data-type="item">
98
97
  <div class="item-row-wrapper">
99
- <div selected$="[[ selected ]]" class="itemRow">
98
+ <div selected$="[[ selected ]]" part="itemRow itemRow-[[ index ]]" data-index$="[[ index ]]" class="itemRow">
100
99
  <input class="checkbox" type="checkbox" checked="[[ selected ]]" on-input="_onCheckboxChange" disabled$="[[ !_dataIsValid ]]" />
101
- <cosmoz-omnitable-item-row columns="[[ normalizedColumns ]]"
100
+ <cosmoz-omnitable-item-row columns="[[ columns ]]"
102
101
  selected="[[ selected ]]" expanded="{{ expanded }}" item="[[ item ]]" group-on-column="[[ groupOnColumn ]]"
103
- on-item-change="[[ onItemChange ]]">
102
+ on-item-change="[[ onItemChange ]]" on-click="onItemClick">
104
103
  </cosmoz-omnitable-item-row>
105
104
  <paper-icon-button
106
105
  class="expand"
@@ -132,7 +131,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
132
131
  </div>
133
132
  </div>
134
133
  <div class="footer">
135
- <div class="footer-controls">
134
+ <div class="footer-controls" part="footer-controls">
136
135
  <cosmoz-autocomplete
137
136
  label="[[ _('Group on', t) ]] [[ _computeSortDirection(groupOnDescending, t) ]]" placeholder="[[ _('No grouping', t) ]]"
138
137
  source="[[ _onCompleteValues(columns, 'groupOn', groupOnColumn) ]]" value="[[ groupOnColumn ]]" limit="1" text-property="title"
@@ -546,6 +545,21 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
546
545
  value && close(); /* eslint-disable-line no-unused-expressions */
547
546
  };
548
547
  }
548
+ onItemClick(e){
549
+ const path = e.composedPath(),
550
+ hasLinks = path.slice(0, path.indexOf(e.currentTarget))
551
+ .find(e => e.matches?.('a'));
552
+
553
+ this.dispatchEvent(new window.CustomEvent('omnitable-item-click', {
554
+ bubbles: true,
555
+ composed: true,
556
+ detail: {
557
+ item: e.model.item,
558
+ index: e.model.index,
559
+ hasLinks
560
+ }
561
+ }));
562
+ }
549
563
  }
550
564
  customElements.define('cosmoz-omnitable', Omnitable);
551
565
 
@@ -2,43 +2,50 @@ import { html } from 'haunted';
2
2
  import { ifDefined } from 'lit-html/directives/if-defined';
3
3
  import { triangle } from '../icons';
4
4
 
5
+ export const render = ({ columns, on, descending, setOn, setDescending }) =>
6
+ columns?.map(
7
+ (c) =>
8
+ html`<button
9
+ class="sg"
10
+ title=${c.title}
11
+ data-on=${ifDefined(
12
+ (c.name === on && (descending ? 'desc' : 'asc')) || undefined
13
+ )}
14
+ @click=${(e) => {
15
+ const on = e.currentTarget?.dataset.on;
16
+ if (!on) {
17
+ setOn(c.name);
18
+ setDescending(false);
19
+ }
20
+ if (on === 'asc') {
21
+ setDescending(true);
22
+ } else if (on === 'desc') {
23
+ setOn();
24
+ setDescending(false);
25
+ }
26
+ }}
27
+ >
28
+ <span>${c.title}</span> ${triangle}
29
+ </button>`
30
+ );
31
+
5
32
  export default () => html`
6
33
  <sort-and-group-consumer
7
- class="groups"
34
+ class="sgs"
8
35
  .render=${({
9
36
  columns,
37
+ groupOn: on,
38
+ setGroupOn: setOn,
10
39
  groupOnDescending: descending,
11
- groupOn,
12
- setGroupOn,
13
- setGroupOnDescending,
14
- } = {}) => {
15
- const items = columns?.filter?.((c) => c['groupOn']);
16
- return items?.map(
17
- (c) =>
18
- html`<button
19
- class="group"
20
- title=${c.title}
21
- data-group=${ifDefined(
22
- (c.name === groupOn && (descending ? 'desc' : 'asc')) || undefined
23
- )}
24
- @click=${(e) => {
25
- const group = e.currentTarget?.dataset.group;
26
- if (!group) {
27
- setGroupOn(c.name);
28
- setGroupOnDescending(false);
29
- }
30
- if (group === 'asc') {
31
- setGroupOnDescending(true);
32
- } else if (group === 'desc') {
33
- setGroupOn();
34
- setGroupOnDescending(false);
35
- }
36
- }}
37
- >
38
- <span>${c.title}</span> ${triangle}
39
- </button>`
40
- );
41
- }}
40
+ setGroupOnDescending: setDescending,
41
+ } = {}) =>
42
+ render({
43
+ columns: columns?.filter?.((c) => c['groupOn']),
44
+ on,
45
+ setOn,
46
+ descending,
47
+ setDescending,
48
+ })}
42
49
  >
43
50
  </sort-and-group-consumer>
44
51
  `;
@@ -4,7 +4,7 @@ import { _ } from '@neovici/cosmoz-i18next';
4
4
  import { isEmpty } from '@neovici/cosmoz-utils/lib/template';
5
5
  import { defaultPlacement } from '@neovici/cosmoz-dropdown';
6
6
  import '@neovici/cosmoz-collapse';
7
- import sort from '../cosmoz-omnitable-sort';
7
+ import sort from './cosmoz-omnitable-sort';
8
8
  import group from './cosmoz-omnitable-group';
9
9
  import style, { dropdown as dropdownStyle } from './style.css';
10
10
  import useSettingsUi from './use-settings-ui';
@@ -44,7 +44,6 @@ const placement = ['bottom-right', ...defaultPlacement],
44
44
  ?has-filter=${!isEmpty(filters[column.name]?.filter)}
45
45
  >${column.title}</label
46
46
  >
47
- ${sort(column.name)}
48
47
  <input
49
48
  class="checkbox"
50
49
  type="checkbox"
@@ -86,8 +85,18 @@ const placement = ['bottom-right', ...defaultPlacement],
86
85
  ${_('Columns')} ${arrow}
87
86
  </div>
88
87
  <cosmoz-collapse ?opened=${opened.columns}>
89
- <div class="list">${settings?.map(renderItem(thru))}</div>
88
+ <div class="list">${settings.columns?.map(renderItem(thru))}</div>
90
89
  </cosmoz-collapse>
90
+
91
+ <div
92
+ class="heading"
93
+ ?data-opened=${opened.sort}
94
+ @click=${() => setOpened((c) => ({ ...c, sort: !c.sort }))}
95
+ >
96
+ ${_('Sort on')} ${arrow}
97
+ </div>
98
+ <cosmoz-collapse ?opened=${opened.sort}> ${sort()} </cosmoz-collapse>
99
+
91
100
  <div
92
101
  class="heading"
93
102
  ?data-opened=${opened.group}
@@ -0,0 +1,23 @@
1
+ import { html } from 'haunted';
2
+ import { render } from './cosmoz-omnitable-group';
3
+
4
+ export default () => html`
5
+ <sort-and-group-consumer
6
+ class="sgs"
7
+ .render=${({
8
+ columns,
9
+ sortOn: on,
10
+ setSortOn: setOn,
11
+ descending,
12
+ setDescending,
13
+ } = {}) =>
14
+ render({
15
+ columns: columns?.filter?.((c) => c['sortOn']),
16
+ on,
17
+ setOn,
18
+ descending,
19
+ setDescending,
20
+ })}
21
+ >
22
+ </sort-and-group-consumer>
23
+ `;
@@ -1,6 +1,15 @@
1
+ /* eslint-disable import/group-exports */
2
+ import { props } from '@neovici/cosmoz-utils/lib/object';
3
+
4
+ export const sgProps = ['sortOn', 'descending', 'groupOn', 'groupOn'];
5
+
1
6
  const byName = (name) => (item) => item.name === name;
2
7
 
3
- export default (columns = [], settings = [], savedSettings = []) => {
8
+ export const normalizeColumns = (
9
+ columns = [],
10
+ settings = [],
11
+ savedSettings = []
12
+ ) => {
4
13
  const cols = columns.filter(
5
14
  (column) =>
6
15
  !settings.some(byName(column.name)) &&
@@ -38,3 +47,28 @@ export default (columns = [], settings = [], savedSettings = []) => {
38
47
  }),
39
48
  ];
40
49
  };
50
+
51
+ export const normalizeStore = (settings, current) => ({
52
+ ...current,
53
+ ...props(sgProps)(settings),
54
+ columns: settings.columns?.map(
55
+ props(['name', 'priority', 'width', 'flex', 'disabled'])
56
+ ),
57
+ });
58
+
59
+ export default ({ columns, settings, savedSettings, initial }) => {
60
+ const init = Object.fromEntries(
61
+ sgProps.flatMap((k) => (initial[k] != null ? [[k, initial[k]]] : []))
62
+ );
63
+
64
+ return {
65
+ ...init,
66
+ ...props(sgProps)(savedSettings),
67
+ ...settings,
68
+ columns: normalizeColumns(
69
+ columns,
70
+ settings?.columns,
71
+ savedSettings?.columns
72
+ ),
73
+ };
74
+ };
@@ -1,5 +1,5 @@
1
1
  import { tagged as css } from '@neovici/cosmoz-utils';
2
- import { checkbox, sort as sortStyle } from '../../cosmoz-omnitable-styles';
2
+ import { checkbox } from '../../cosmoz-omnitable-styles';
3
3
 
4
4
  export default css`
5
5
  :host {
@@ -106,14 +106,6 @@ export default css`
106
106
  margin: 4px 0;
107
107
  }
108
108
 
109
- ${sortStyle}
110
- .sort {
111
- order: initial;
112
- margin: 0;
113
- width: auto;
114
- padding: 8px;
115
- }
116
-
117
109
  .buttons {
118
110
  display: flex;
119
111
  gap: 8px;
@@ -145,7 +137,8 @@ export default css`
145
137
  color: inherit;
146
138
  text-decoration: underline;
147
139
  }
148
- .groups {
140
+ /* sortgroups */
141
+ .sgs {
149
142
  display: grid;
150
143
  column-gap: 7px;
151
144
  row-gap: 8px;
@@ -153,7 +146,7 @@ export default css`
153
146
  grid-template-rows: auto;
154
147
  padding: 14px;
155
148
  }
156
- .group {
149
+ .sg {
157
150
  border: 1px solid rgba(0, 0, 0, 0.2);
158
151
  border-radius: 2px;
159
152
  font-size: 13px;
@@ -166,25 +159,25 @@ export default css`
166
159
  align-items: center;
167
160
  justify-content: center;
168
161
  }
169
- .group span {
162
+ .sg span {
170
163
  overflow: hidden;
171
164
  text-overflow: ellipsis;
172
165
  }
173
- .group[data-group] {
166
+ .sg[data-on] {
174
167
  background: var(
175
168
  --cosmoz-omnitable-checkbox-checked-color,
176
169
  var(--primary-color)
177
170
  );
178
171
  }
179
- .group svg {
172
+ .sg svg {
180
173
  margin-left: 4px;
181
174
  flex: none;
182
175
  vertical-align: middle;
183
176
  }
184
- .group:not([data-group]) svg {
177
+ .sg:not([data-on]) svg {
185
178
  display: none;
186
179
  }
187
- .group:not([data-group='desc']) svg {
180
+ .sg:not([data-on='desc']) svg {
188
181
  transform: scaleY(-1);
189
182
  }
190
183
  `;
@@ -1,22 +1,21 @@
1
1
  import { useCallback, useMemo, useState } from 'haunted';
2
- import { props } from '@neovici/cosmoz-utils/lib/object';
3
-
4
- const storagePrefix = 'omnitable-';
5
-
2
+ import { normalizeStore } from './normalize';
3
+
4
+ const storagePrefix = 'omnitable-',
5
+ read = (settingsId) => {
6
+ if (!settingsId) {
7
+ return [];
8
+ }
9
+ try {
10
+ return JSON.parse(localStorage.getItem(storagePrefix + settingsId)) ?? [];
11
+ } catch (e) {
12
+ return [];
13
+ }
14
+ };
15
+ // eslint-disable-next-line max-lines-per-function
6
16
  export default (settingsId, settings, setSettings) => {
7
- const
8
- [counter, setCounter] = useState(0),
9
- savedSettings = useMemo(() => {
10
- if (!settingsId) {
11
- return [];
12
- }
13
-
14
- try {
15
- return JSON.parse(localStorage.getItem(storagePrefix + settingsId)) ?? [];
16
- } catch (e) {
17
- return [];
18
- }
19
- }, [settingsId, counter]);
17
+ const [counter, setCounter] = useState(0),
18
+ savedSettings = useMemo(() => read(settingsId), [settingsId, counter]);
20
19
 
21
20
  return {
22
21
  settingsId,
@@ -28,28 +27,32 @@ export default (settingsId, settings, setSettings) => {
28
27
  }
29
28
 
30
29
  try {
31
- localStorage.setItem(storagePrefix + settingsId, JSON.stringify(settings.map(props(['name', 'priority', 'width', 'flex', 'disabled']))));
32
- setSettings([]);
33
- setCounter(counter => counter + 1);
30
+ const current = read(settingsId);
31
+ localStorage.setItem(
32
+ storagePrefix + settingsId,
33
+ JSON.stringify(normalizeStore(settings, current))
34
+ );
35
+ setSettings();
36
+ setCounter((counter) => counter + 1);
34
37
  } catch (e) {
35
38
  // eslint-disable-next-line no-console
36
39
  console.error(e);
37
40
  }
38
41
  }, [settings]),
39
42
 
40
- onReset: e => {
41
- setSettings([]);
43
+ onReset: (e) => {
44
+ setSettings();
42
45
 
43
46
  if (e.shiftKey) {
44
47
  try {
45
48
  localStorage.removeItem(storagePrefix + settingsId);
46
- setCounter(counter => counter + 1);
49
+ setCounter((counter) => counter + 1);
47
50
  } catch (err) {
48
51
  // ignore error
49
52
  }
50
53
  }
51
54
  },
52
55
 
53
- hasChanges: settings.length !== 0
56
+ hasChanges: settings != null,
54
57
  };
55
58
  };
@@ -10,7 +10,17 @@ const parseIndex = (str) => {
10
10
  export default (host) => {
11
11
  const { config } = host,
12
12
  { settings, setSettings } = config,
13
- meta = useMeta({ settings, setSettings });
13
+ meta = useMeta({
14
+ settings: settings.columns,
15
+ setSettings: useCallback(
16
+ (columns) =>
17
+ setSettings((cfg) => ({
18
+ ...cfg,
19
+ columns,
20
+ })),
21
+ [setSettings]
22
+ ),
23
+ });
14
24
 
15
25
  return {
16
26
  ...config,
@@ -3,30 +3,38 @@ import { useMemo, useState } from 'haunted';
3
3
  import useSavedSettings from './use-saved-settings';
4
4
  import normalize from './normalize';
5
5
 
6
- export default ({ settingsId, columns }) => {
7
- const [settings, setSettings] = useState([]),
8
- [opened, setOpened] = useState({ columns: true, group: true }),
9
- { savedSettings, ...thru } = useSavedSettings(
6
+ export default ({ settingsId, ...thru }) => {
7
+ const [settings, setSettings] = useState(),
8
+ [opened, setOpened] = useState({ columns: true, sort: true }),
9
+ { savedSettings, ...rest } = useSavedSettings(
10
10
  settingsId,
11
11
  settings,
12
12
  setSettings
13
13
  ),
14
+ { columns } = thru,
14
15
  normalizedSettings = useMemo(
15
- () => normalize(columns, settings, savedSettings),
16
- [columns, settings, savedSettings]
16
+ () =>
17
+ normalize({
18
+ settings,
19
+ savedSettings,
20
+ ...thru,
21
+ }),
22
+ [settings, savedSettings, ...Object.values(thru)]
17
23
  ),
18
24
  normalizedColumns = useMemo(
19
- () => normalizedSettings.map(s => columns.find(c => c.name === s.name)),
20
- [columns, ...normalizedSettings.map(s => s.name)]
25
+ () =>
26
+ normalizedSettings.columns.map((s) =>
27
+ columns.find((c) => c.name === s.name)
28
+ ),
29
+ [columns, ...normalizedSettings.columns.map((s) => s.name)]
21
30
  );
22
31
 
23
32
  return {
24
- ...thru,
33
+ ...rest,
25
34
  opened,
26
35
  setOpened,
27
36
  settings: normalizedSettings,
28
- normalizedColumns,
37
+ columns: normalizedColumns,
29
38
  setSettings,
30
39
  };
31
40
  };
32
-
@@ -9,9 +9,9 @@ import { render } from 'lit-html';
9
9
  export const useFastLayout = ({ host, settings, setSettings, groupOnColumn, resizeSpeedFactor }) => {
10
10
  const
11
11
  canvasWidth = useCanvasWidth(host),
12
- layout = useLayout({ canvasWidth, groupOnColumn, config: settings }),
12
+ layout = useLayout({ canvasWidth, groupOnColumn, config: settings.columns }),
13
13
  tweenedlayout = useTweenArray(layout, resizeSpeedFactor),
14
- layoutCss = useMemo(() => toCss(tweenedlayout, settings), [tweenedlayout]);
14
+ layoutCss = useMemo(() => toCss(tweenedlayout, settings.columns), [tweenedlayout]);
15
15
 
16
16
  useResizableColumns({ host, canvasWidth, layout, setSettings: update => setSettings(update(settings)) });
17
17
 
@@ -8,12 +8,14 @@ import { onItemChange } from './utils-data';
8
8
 
9
9
  // eslint-disable-next-line max-lines-per-function
10
10
  export const useOmnitable = (host) => {
11
- const { enabledColumns, hashParam } = host,
12
- columns = useDOMColumns(host, { enabledColumns }),
13
- sortAndGroupOptions = useSortAndGroupOptions(columns, hashParam, host),
11
+ const { enabledColumns, hashParam, settingsId } = host,
12
+ _columns = useDOMColumns(host, { enabledColumns }),
13
+ settingS = useSettings({ columns: _columns, settingsId, initial: host }),
14
+ { settings, setSettings, columns } = settingS,
15
+ sortAndGroupOptions = useSortAndGroupOptions(columns, hashParam, settings, setSettings),
14
16
  { groupOnColumn, groupOnDescending, sortOnColumn, descending } =
15
17
  sortAndGroupOptions,
16
- { data, resizeSpeedFactor, settingsId } = host,
18
+ { data, resizeSpeedFactor } = host,
17
19
  // TODO: drop filterFunctions
18
20
  {
19
21
  processedItems,
@@ -31,8 +33,6 @@ export const useOmnitable = (host) => {
31
33
  descending,
32
34
  hashParam,
33
35
  }),
34
- settingsOpts = useSettings({ settingsId, columns }),
35
- { settings, setSettings, normalizedColumns } = settingsOpts,
36
36
  layout = useFastLayout({
37
37
  host,
38
38
  settings,
@@ -42,7 +42,7 @@ export const useOmnitable = (host) => {
42
42
  }),
43
43
  collapsedColumns = useMemo(
44
44
  () =>
45
- settings.reduce(
45
+ settings.columns.reduce(
46
46
  (acc, column, index) =>
47
47
  layout[index] != null ||
48
48
  column.name === groupOnColumn?.name ||
@@ -58,7 +58,7 @@ export const useOmnitable = (host) => {
58
58
  [
59
59
  groupOnColumn,
60
60
  ...collapsedColumns,
61
- ...settings.filter((s) => s.disabled),
61
+ ...settings.columns.filter((s) => s.disabled),
62
62
  ].some(
63
63
  (column) =>
64
64
  column && Object.keys(filterFunctions).includes(column.name)
@@ -67,12 +67,12 @@ export const useOmnitable = (host) => {
67
67
  ),
68
68
  settingsConfig = useMemo(
69
69
  () => ({
70
- ...settingsOpts,
70
+ ...settingS,
71
71
  collapsed: collapsedColumns,
72
72
  badge: hasHiddenFilter,
73
73
  filters,
74
74
  }),
75
- [settingsOpts, collapsedColumns, hasHiddenFilter, filters]
75
+ [settingS, collapsedColumns, hasHiddenFilter, filters]
76
76
  );
77
77
 
78
78
  useEffect(() => {
@@ -103,7 +103,6 @@ export const useOmnitable = (host) => {
103
103
  ...sortAndGroupOptions,
104
104
 
105
105
  columns,
106
- normalizedColumns,
107
106
  collapsedColumns,
108
107
 
109
108
  settingsConfig,
@@ -1,49 +1,62 @@
1
1
  import { useEffect, useRef } from 'haunted';
2
2
 
3
- export const useResizableColumns = ({ host, canvasWidth, layout, setSettings }) => {
3
+ export const useResizableColumns = ({
4
+ host,
5
+ canvasWidth,
6
+ layout,
7
+ setSettings,
8
+ }) => {
4
9
  const onColumnResizeRef = useRef();
5
10
 
6
- onColumnResizeRef.current = ev => setSettings(config => {
7
- const
8
- { detail: { newWidth, column }} = ev,
9
- columnIndex = config.findIndex(c => c.name === column.name),
10
- newConfig = [],
11
- maxPriority = config.reduce((p, c) => Math.max(p, c.priority), -Infinity);
12
-
13
- for (let i = 0; i < layout.length; i++) {
14
- newConfig[i] = { ...config[i] };
15
-
16
- // for visible columns to the left of the resized column
17
- if (i < columnIndex && layout[i]) {
18
- // save the current width
19
- newConfig[i].width = layout[i];
20
- // make them fixed width
21
- newConfig[i].flex = 0;
22
- // keep them visible
23
- newConfig[i].priority = maxPriority;
24
- }
25
-
26
- // update the width of the resized column
27
- if (i === columnIndex) {
28
- const
29
- maxNewSize = layout.reduce((acc, cur, i) => {
11
+ onColumnResizeRef.current = (ev) =>
12
+ setSettings((settings) => {
13
+ const config = settings.columns,
14
+ {
15
+ detail: { newWidth, column },
16
+ } = ev,
17
+ columnIndex = config.findIndex((c) => c.name === column.name),
18
+ newConfig = [],
19
+ maxPriority = config.reduce(
20
+ (p, c) => Math.max(p, c.priority),
21
+ -Infinity
22
+ );
23
+
24
+ for (let i = 0; i < layout.length; i++) {
25
+ newConfig[i] = { ...config[i] };
26
+
27
+ // for visible columns to the left of the resized column
28
+ if (i < columnIndex && layout[i]) {
29
+ // save the current width
30
+ newConfig[i].width = layout[i];
31
+ // make them fixed width
32
+ newConfig[i].flex = 0;
33
+ // keep them visible
34
+ newConfig[i].priority = maxPriority;
35
+ }
36
+
37
+ // update the width of the resized column
38
+ if (i === columnIndex) {
39
+ const maxNewSize = layout.reduce((acc, cur, i) => {
30
40
  if (i < columnIndex) {
31
41
  return cur ? acc - cur : acc;
32
42
  }
33
43
  return acc;
34
44
  }, canvasWidth);
35
45
 
36
- newConfig[i].width = Math.min(maxNewSize, Math.max(newWidth, config[i].minWidth));
37
- newConfig[i].flex = 0;
38
- newConfig[i].priority = maxPriority;
46
+ newConfig[i].width = Math.min(
47
+ maxNewSize,
48
+ Math.max(newWidth, config[i].minWidth)
49
+ );
50
+ newConfig[i].flex = 0;
51
+ newConfig[i].priority = maxPriority;
52
+ }
39
53
  }
40
- }
41
54
 
42
- return newConfig;
43
- });
55
+ return { ...settings, columns: newConfig };
56
+ });
44
57
 
45
58
  useEffect(() => {
46
- const handler = ev => onColumnResizeRef.current(ev);
59
+ const handler = (ev) => onColumnResizeRef.current(ev);
47
60
  host.addEventListener('column-resize', handler);
48
61
  return () => host.removeEventListener('column-resize', handler);
49
62
  }, []);
@@ -1,26 +1,46 @@
1
- import { useMemo, createContext, component, useContext } from 'haunted';
1
+ import {
2
+ useMemo,
3
+ createContext,
4
+ component,
5
+ useContext,
6
+ useCallback,
7
+ } from 'haunted';
2
8
  import { useHashState } from './use-hash-state';
3
9
 
4
10
  const parseBool = (bool) => [true, 'true', 1, 'yes', 'on'].includes(bool),
5
- boolParam = (p) => p === '' || (p == null ? undefined : parseBool(p));
11
+ boolParam = (p) => p === '' || (p == null ? undefined : parseBool(p)),
12
+ useSettingsState = (setter, name, setSettings) =>
13
+ useCallback(
14
+ (value) => {
15
+ setter(value);
16
+ setSettings((s) => ({ ...s, [name]: value }));
17
+ },
18
+ [setter, name, setSettings]
19
+ );
6
20
 
7
- export const useSortAndGroupOptions = (columns, hashParam, initial) => {
8
- const [sortOn, setSortOn] = useHashState(initial.sortOn, hashParam, {
21
+ // eslint-disable-next-line max-lines-per-function
22
+ export const useSortAndGroupOptions = (
23
+ columns,
24
+ hashParam,
25
+ settings,
26
+ setSettings
27
+ ) => {
28
+ const [sortOn, setSortOn] = useHashState(settings.sortOn, hashParam, {
9
29
  suffix: '-sortOn',
10
30
  }),
11
31
  [descending, setDescending] = useHashState(
12
- initial.descending,
32
+ settings.descending,
13
33
  hashParam,
14
34
  {
15
35
  suffix: '-descending',
16
36
  read: boolParam,
17
37
  }
18
38
  ),
19
- [groupOn, setGroupOn] = useHashState(initial.groupOn, hashParam, {
39
+ [groupOn, setGroupOn] = useHashState(settings.groupOn, hashParam, {
20
40
  suffix: '-groupOn',
21
41
  }),
22
42
  [groupOnDescending, setGroupOnDescending] = useHashState(
23
- initial.groupOnDescending,
43
+ settings.groupOnDescending,
24
44
  hashParam,
25
45
  { suffix: '-groupOnDescending', read: boolParam }
26
46
  ),
@@ -34,14 +54,22 @@ export const useSortAndGroupOptions = (columns, hashParam, initial) => {
34
54
  ),
35
55
  sortAndGroup_ = {
36
56
  groupOn,
37
- setGroupOn,
57
+ setGroupOn: useSettingsState(setGroupOn, 'groupOn', setSettings),
38
58
  groupOnDescending,
39
- setGroupOnDescending,
59
+ setGroupOnDescending: useSettingsState(
60
+ setGroupOnDescending,
61
+ 'groupOnDescending',
62
+ setSettings
63
+ ),
40
64
 
41
65
  sortOn,
42
- setSortOn,
66
+ setSortOn: useSettingsState(setSortOn, 'sortOn', setSettings),
43
67
  descending,
44
- setDescending,
68
+ setDescending: useSettingsState(
69
+ setDescending,
70
+ 'descending',
71
+ setSettings
72
+ ),
45
73
 
46
74
  columns,
47
75
  },
@@ -56,6 +84,7 @@ export const useSortAndGroupOptions = (columns, hashParam, initial) => {
56
84
  },
57
85
  SortAndGroupContext = createContext();
58
86
 
87
+
59
88
  customElements.define('sort-and-group-provider', SortAndGroupContext.Provider);
60
89
  customElements.define(
61
90
  'sort-and-group-consumer',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-omnitable",
3
- "version": "8.11.1",
3
+ "version": "8.14.0",
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"
@@ -1,37 +0,0 @@
1
- import { html } from 'haunted';
2
- import { ifDefined } from 'lit-html/directives/if-defined';
3
- import { triangle } from './icons';
4
-
5
- export default (column) => html`
6
- <sort-and-group-consumer
7
- style="display: contents"
8
- .render=${({
9
- sortOn,
10
- setSortOn,
11
- descending,
12
- setDescending,
13
- } = {}) => html` <button
14
- class="sort"
15
- data-sort=${ifDefined(
16
- (column === sortOn && (descending ? 'desc' : 'asc')) || undefined
17
- )}
18
- @click=${(e) => {
19
- const sort = e.currentTarget?.dataset.sort;
20
- if (!sort) {
21
- setSortOn(column);
22
- setDescending(false);
23
- }
24
- if (sort === 'asc') {
25
- setDescending(true);
26
- }
27
- if (sort === 'desc') {
28
- setSortOn();
29
- setDescending(false);
30
- }
31
- }}
32
- >
33
- ${triangle}
34
- </button>`}
35
- >
36
- </sort-and-group-consumer>
37
- `;