@neovici/cosmoz-omnitable 8.5.2 → 8.7.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.
@@ -1,31 +1,52 @@
1
- /* eslint-disable object-curly-newline */
2
1
  import { html, component } from 'haunted';
3
2
  import { repeat } from 'lit-html/directives/repeat';
4
3
  import './lib/cosmoz-omnitable-resize-nub';
4
+ import sort from './lib/cosmoz-omnitable-sort';
5
5
 
6
- const
7
- renderHeaderRow = ({ data, columns, groupOnColumn, filters, setFilterState }) => {
8
- return repeat(columns, column => column.name, column => [
9
- html`<div
10
- class="cell ${ column.headerCellClass } header-cell"
11
- ?hidden=${ column === groupOnColumn }
12
- title=${ column.title }
13
- name=${ column.name }
14
- >${ column.renderHeader(
15
- column,
16
- filters[column.name] ?? {},
17
- state => setFilterState(column.name, state),
18
- column.source(column, data)
19
- ) }</div>`,
20
- html`<cosmoz-omnitable-resize-nub
21
- .column=${ column }
22
- name=${ column.name }
23
- ></cosmoz-omnitable-resize-nub>`
24
- ]);
25
- },
6
+ const /* eslint-disable-next-line max-lines-per-function */
7
+ renderHeaderRow = ({
8
+ data,
9
+ columns,
10
+ groupOnColumn,
11
+ filters,
12
+ setFilterState,
13
+ }) =>
14
+ repeat(
15
+ columns,
16
+ (column) => column.name,
17
+ (column) => [
18
+ html`<div
19
+ class="cell ${column.headerCellClass} header-cell"
20
+ ?hidden=${column === groupOnColumn}
21
+ title=${column.title}
22
+ name=${column.name}
23
+ >
24
+ ${[
25
+ column.renderHeader(
26
+ column,
27
+ filters[column.name] ?? {},
28
+ (state) => setFilterState(column.name, state),
29
+ column.source(column, data)
30
+ ),
31
+ sort(column.name),
32
+ ]}
33
+ </div>`,
34
+ html`<cosmoz-omnitable-resize-nub
35
+ .column=${column}
36
+ name=${column.name}
37
+ ></cosmoz-omnitable-resize-nub>`,
38
+ ]
39
+ ),
40
+ HeaderRow = ({ content, columns, ...thru }) => [
41
+ columns &&
42
+ renderHeaderRow({
43
+ columns,
44
+ ...thru,
45
+ }),
46
+ content,
47
+ ];
26
48
 
27
- HeaderRow = ({ data, columns, groupOnColumn, content, filters, setFilterState }) => {
28
- return [columns && renderHeaderRow({ data, columns, groupOnColumn, filters, setFilterState }), content];
29
- };
30
-
31
- customElements.define('cosmoz-omnitable-header-row', component(HeaderRow, { useShadowDOM: false }));
49
+ customElements.define(
50
+ 'cosmoz-omnitable-header-row',
51
+ component(HeaderRow, { useShadowDOM: false })
52
+ );
@@ -1,73 +1,119 @@
1
1
  /* eslint-disable max-lines */
2
- const checkbox = `
3
- .checkbox {
4
- box-sizing: border-box;
5
- width: 18px;
6
- height: 18px;
7
- background: transparent;
8
- border-radius: 4px;
9
- appearance: none;
10
- -webkit-appearance: none;
11
- outline: none;
12
- position: relative;
13
- user-select: none;
14
- cursor: pointer;
15
- display: inline-block;
16
- box-shadow: 0 0 0 2px rgba(0,0,0, 0.16) inset;
17
- -webkit-tap-highlight-color: rgba(0,0,0,0);
18
- vertical-align: middle;
19
- transition: background-color 140ms;
20
- margin: 1px 12px;
21
- flex: none;
22
- }
23
-
24
- .checkbox:checked {
25
- background-color: var(--cosmoz-omnitable-checkbox-checked-color, var(--primary-color));
26
- box-shadow: none;
27
- }
2
+ import { tagged as css } from '@neovici/cosmoz-utils';
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[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 * {
111
+ min-width: 0;
112
+ }
113
+ `;
114
+
115
+ export { checkbox, sort };
28
116
 
29
- .checkbox:checked::before {
30
- content: "";
31
- position: absolute;
32
- box-sizing: content-box;
33
- width: 5px;
34
- height: 10px;
35
- border: 2.4px solid #fff;
36
- border-top: none;
37
- border-left: none;
38
- transform-origin: 5px 10px;
39
- transform: translate(3px) rotate(45deg);
40
- }
41
-
42
- .checkbox::after {
43
- content: '';
44
- display: block;
45
- bottom: -5px;
46
- left: -5px;
47
- right: -5px;
48
- top: -5px;
49
- }
50
-
51
- .checkbox:hover {
52
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 1) inset, 0 0 2px 6px #2021240f;
53
- }
54
-
55
- .checkbox:checked:hover {
56
- box-shadow: 0 0 2px 6px #2021240f;
57
- }
58
-
59
- .checkbox:indeterminate::before {
60
- content: "";
61
- position: absolute;
62
- width: 10px;
63
- height: 2px;
64
- left: 4px;
65
- top: 8px;
66
- background-color: var(--cosmoz-omnitable-checkbox-checked-color, var(--primary-color));
67
- }
68
- `;
69
-
70
- export { checkbox };
71
117
  export default `<style>
72
118
  :host {
73
119
  display: flex;
@@ -115,14 +161,12 @@ export default `<style>
115
161
  }
116
162
  .header-cell {
117
163
  --paper-input-container: {
118
- padding-top: 0;
164
+ padding-top: 0;
165
+ padding-bottom: 0;
166
+ --paper-font-caption_-_line-height: 18px;
119
167
  }
120
168
  }
121
169
 
122
- .header-cell > * {
123
- width: 100%;
124
- }
125
-
126
170
  cosmoz-omnitable-header-row {
127
171
  white-space: nowrap;
128
172
  }
@@ -455,10 +499,10 @@ export default `<style>
455
499
  color: rgba(0, 0, 0, 0.54);
456
500
  }
457
501
 
458
- ${ checkbox }
502
+ ${checkbox}
459
503
 
460
504
  .all {
461
- margin-bottom: 14px;
505
+ margin-bottom: 6px;
462
506
  }
463
507
 
464
508
  .expand {
@@ -475,4 +519,5 @@ export default `<style>
475
519
  .expand:hover, .fold:hover {
476
520
  color: #000;
477
521
  }
522
+ ${sort}
478
523
  </style>`;
@@ -28,6 +28,7 @@ import './lib/cosmoz-omnitable-settings';
28
28
  import { saveAsCsvAction } from './lib/save-as-csv-action';
29
29
  import { saveAsXlsxAction } from './lib/save-as-xlsx-action';
30
30
  import { defaultPlacement } from '@neovici/cosmoz-dropdown';
31
+ import { without } from '@neovici/cosmoz-utils/lib/array';
31
32
  /**
32
33
  * @polymer
33
34
  * @customElement
@@ -45,6 +46,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
45
46
  <div id="layoutStyle"></div>
46
47
 
47
48
  <div class="mainContainer">
49
+ <sort-and-group-provider value="[[ sortAndGroup ]]">
48
50
  <div class="header" id="header">
49
51
  <input class="checkbox all" type="checkbox" checked="[[ _allSelected ]]" on-input="_onAllCheckboxChange" disabled$="[[ !_dataIsValid ]]" />
50
52
  <cosmoz-omnitable-header-row
@@ -56,6 +58,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
56
58
  set-filter-state="[[ setFilterState ]]"
57
59
  ></cosmoz-omnitable-header-row>
58
60
  </div>
61
+ </sort-and-group-provider>
59
62
  <div class="tableContent" id="tableContent">
60
63
  <template is="dom-if" if="[[ !_dataIsValid ]]">
61
64
  <div class="tableContent-empty">
@@ -134,7 +137,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
134
137
  label="[[ _('Group on', t) ]] [[ _computeSortDirection(groupOnDescending, t) ]]" placeholder="[[ _('No grouping', t) ]]"
135
138
  source="[[ _onCompleteValues(columns, 'groupOn', groupOnColumn) ]]" value="[[ groupOnColumn ]]" limit="1" text-property="title"
136
139
  always-float-label item-height="48" item-limit="8"
137
- class="footer-control" on-select="[[ _onCompleteChange('groupOn') ]]" default-index="-1" show-single
140
+ class="footer-control" on-change="[[ _onCompleteChange('groupOn') ]]" on-select="[[ _onCompleteSelect ]]" default-index="-1" show-single
138
141
  >
139
142
  <svg slot="suffix" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" width="24" fill="currentColor"><path d="M7 10l5 5 5-5z"></path></svg>
140
143
  </cosmoz-autocomplete>
@@ -142,7 +145,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
142
145
  label="[[ _('Sort on', t) ]] [[ _computeSortDirection(descending, t) ]]" placeholder="[[ _('No sorting', t) ]]"
143
146
  source="[[ _onCompleteValues(columns, 'sortOn', sortOnColumn) ]]" value="[[ sortOnColumn ]]" limit="1" text-property="title"
144
147
  always-float-label item-height="48" item-limit="8"
145
- class="footer-control" on-select="[[ _onCompleteChange('sortOn') ]]" default-index="-1" show-single
148
+ class="footer-control" on-change="[[ _onCompleteChange('sortOn') ]]" on-select="[[ _onCompleteSelect ]]" default-index="-1" show-single
146
149
  >
147
150
  <svg slot="suffix" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" width="24" fill="currentColor"><path d="M7 10l5 5 5-5z"></path></svg>
148
151
  </cosmoz-autocomplete>
@@ -520,8 +523,13 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
520
523
  return columns?.filter?.(c => c[type]).sort((a, b) => ((b === value) >> 0) - ((a === value) >> 0));
521
524
  }
522
525
 
526
+ _onCompleteSelect(newVal, {value, onChange, onText, limit}) {
527
+ onText('');
528
+ onChange([...without(newVal)(value), newVal].slice(-limit));
529
+ }
530
+
523
531
  _onCompleteChange(type) {
524
- return (val, {setClosed}) => {
532
+ return (val, close) => {
525
533
  const value = (val[0] ?? val)?.name ?? '',
526
534
  setter = type === 'groupOn' ? this.setGroupOn : this.setSortOn,
527
535
  directionSetter = type === 'groupOn' ? this.setGroupOnDescending : this.setDescending;
@@ -535,7 +543,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
535
543
  return value;
536
544
  });
537
545
 
538
- value && setClosed(true); /* eslint-disable-line no-unused-expressions */
546
+ value && close(); /* eslint-disable-line no-unused-expressions */
539
547
  };
540
548
  }
541
549
 
@@ -1,24 +1,26 @@
1
1
  import { html, component, useCallback } from 'haunted';
2
2
  import { useMeta } from '@neovici/cosmoz-utils/lib/hooks/use-meta';
3
- import { checkbox } from '../cosmoz-omnitable-styles';
3
+ import { checkbox, sort as sortStyle } from '../cosmoz-omnitable-styles';
4
4
  import { nothing } from 'lit-html';
5
5
  import { isEmpty } from '@neovici/cosmoz-utils/lib/template';
6
6
  import { defaultPlacement } from '@neovici/cosmoz-dropdown';
7
+ import sort from './cosmoz-omnitable-sort';
8
+
7
9
  const placement = ['bottom-right', ...defaultPlacement],
8
10
  settingsStyles = `
9
11
  :host {
10
12
  box-sizing: border-box;
11
- padding: 12px;
12
13
  display: flex;
13
14
  flex-direction: column;
14
15
  max-height: 42vh;
15
16
  outline: none;
17
+ padding-top: 14px;
16
18
  }
17
19
  .list {
18
20
  overflow-y: auto;
19
21
  flex: 1;
20
- padding-top: 2px;
21
- min-width: 225px;
22
+ padding: 2px 14px;
23
+ min-width: 232px;
22
24
  }
23
25
  .item {
24
26
  display: flex;
@@ -32,7 +34,7 @@ const placement = ['bottom-right', ...defaultPlacement],
32
34
  .item.dragover {
33
35
  box-shadow: 0 -2px 0 0 currentColor;
34
36
  }
35
- .sort {
37
+ .pull {
36
38
  border: none;
37
39
  padding: 0;
38
40
  font-size: 0;
@@ -41,6 +43,7 @@ const placement = ['bottom-right', ...defaultPlacement],
41
43
  background: transparent;
42
44
  cursor: move;
43
45
  margin-right: 12px;
46
+ color: #c4c4c4;
44
47
  }
45
48
  .title {
46
49
  flex: 1;
@@ -48,12 +51,25 @@ const placement = ['bottom-right', ...defaultPlacement],
48
51
  .title[has-filter] {
49
52
  font-weight: bold;
50
53
  }
51
- ${ checkbox }
54
+ ${checkbox}
55
+ .checkbox {
56
+ margin: 4px 0;
57
+ }
58
+
59
+ ${sortStyle}
60
+ .sort {
61
+ order: initial;
62
+ margin: 0;
63
+ width: auto;
64
+ padding: 8px;
65
+ }
52
66
 
53
67
  .buttons {
54
68
  display: flex;
69
+ gap: 8px;
55
70
  margin-top: 10px;
56
- gap: 7px;
71
+ padding: 12px 14px;
72
+ box-shadow: inset 0px 1px 0px rgba(0, 0, 0, 0.15);
57
73
  }
58
74
  .button {
59
75
  border: none;
@@ -62,32 +78,32 @@ const placement = ['bottom-right', ...defaultPlacement],
62
78
  color: var(--cosmoz-button-color, #fff);
63
79
  font-family: inherit;
64
80
  font-size: 13px;
65
- font-weight: 500;
81
+ font-weight: 600;
66
82
  line-height: 26px;
67
- height: 26px;
68
83
  border-radius: 4px;
69
- padding: 0 8px;
84
+ padding: 8px;
70
85
  flex: 1;
71
86
  }
72
- .button:hover {
73
- background: var(--cosmoz-button-hover-bg-color, #3A3F44);
87
+ .button:not(.reset):hover {
88
+ background: var(--cosmoz-button-hover-bg-color, #3A3F44);
74
89
  }
75
90
  .button[disabled] {
76
- opacity: 0.7;
91
+ opacity: 0.2;
77
92
  pointer-events: none;
78
93
  }
94
+ .reset {
95
+ background: transparent;
96
+ color: inherit;
97
+ text-decoration: underline;
98
+ }
79
99
  `,
80
-
81
- parseIndex = str => {
100
+ parseIndex = (str) => {
82
101
  const idx = parseInt(str, 10);
83
102
  return isFinite(idx) ? idx : undefined;
84
103
  },
85
-
86
104
  // eslint-disable-next-line max-lines-per-function
87
- useSettings = host => {
88
-
89
- const
90
- { settings, onSettings } = host,
105
+ useSettings = (host) => {
106
+ const { settings, onSettings } = host,
91
107
  meta = useMeta({ settings, onSettings });
92
108
 
93
109
  return {
@@ -101,30 +117,40 @@ const placement = ['bottom-right', ...defaultPlacement],
101
117
 
102
118
  filters: host.filters,
103
119
 
104
- onDown: useCallback(e => {
105
- if (!e.target.closest('.sort')) {
106
- return;
107
- }
120
+ onDown: useCallback(
121
+ (e) => {
122
+ if (!e.target.closest('.pull')) {
123
+ return;
124
+ }
108
125
 
109
- meta.handle = e.currentTarget;
110
- }, [meta]),
126
+ meta.handle = e.currentTarget;
127
+ },
128
+ [meta]
129
+ ),
111
130
 
112
- onDragStart: useCallback(e => {
113
- const { target } = e,
114
- index = parseIndex(target.dataset.index);
131
+ onDragStart: useCallback(
132
+ (e) => {
133
+ const { target } = e,
134
+ index = parseIndex(target.dataset.index);
115
135
 
116
- if (!meta.handle?.contains(target) || index == null) {
117
- return e.preventDefault();
118
- }
136
+ if (!meta.handle?.contains(target) || index == null) {
137
+ return e.preventDefault();
138
+ }
119
139
 
120
- meta.handle = null;
121
- e.dataTransfer.effectAllowed = 'move';
122
- e.dataTransfer.setData('omnitable/sort-index', index);
123
- setTimeout(() => target.classList.add('drag'), 0);
124
- target.addEventListener('dragend', e => e.target.classList.remove('drag'), { once: true });
125
- }, [meta]),
140
+ meta.handle = null;
141
+ e.dataTransfer.effectAllowed = 'move';
142
+ e.dataTransfer.setData('omnitable/sort-index', index);
143
+ setTimeout(() => target.classList.add('drag'), 0);
144
+ target.addEventListener(
145
+ 'dragend',
146
+ (e) => e.target.classList.remove('drag'),
147
+ { once: true }
148
+ );
149
+ },
150
+ [meta]
151
+ ),
126
152
 
127
- onDragEnter: useCallback(e => {
153
+ onDragEnter: useCallback((e) => {
128
154
  const ctg = e.currentTarget;
129
155
  if (ctg !== e.target) {
130
156
  return;
@@ -135,12 +161,12 @@ const placement = ['bottom-right', ...defaultPlacement],
135
161
  ctg.classList.add('dragover');
136
162
  }, []),
137
163
 
138
- onDragOver: useCallback(e => {
164
+ onDragOver: useCallback((e) => {
139
165
  e.preventDefault();
140
166
  e.currentTarget.classList.add('dragover');
141
167
  }, []),
142
168
 
143
- onDragLeave: useCallback(e => {
169
+ onDragLeave: useCallback((e) => {
144
170
  const ctg = e.currentTarget;
145
171
  if (ctg.contains(e.relatedTarget)) {
146
172
  return;
@@ -149,77 +175,153 @@ const placement = ['bottom-right', ...defaultPlacement],
149
175
  ctg.classList.remove('dragover');
150
176
  }, []),
151
177
 
152
- onDrop: useCallback(e => {
153
- const from = parseIndex(e.dataTransfer.getData('omnitable/sort-index')),
154
- to = parseIndex(e.currentTarget.dataset.index),
155
- { settings, onSettings } = meta;
178
+ onDrop: useCallback(
179
+ (e) => {
180
+ const from = parseIndex(
181
+ e.dataTransfer.getData('omnitable/sort-index')
182
+ ),
183
+ to = parseIndex(e.currentTarget.dataset.index),
184
+ { settings, onSettings } = meta;
156
185
 
157
- e.currentTarget.classList.remove('dragover');
158
- e.preventDefault();
186
+ e.currentTarget.classList.remove('dragover');
187
+ e.preventDefault();
159
188
 
160
- const newSettings = settings.slice();
161
- newSettings.splice(to + (from >= to ? 0 : -1), 0, newSettings.splice(from, 1)[0]);
162
- onSettings(newSettings);
163
- }, [meta]),
164
-
165
- onToggle: useCallback(e => {
166
- const checked = e.target.checked,
167
- idx = parseIndex(e.target.closest('[data-index]')?.dataset.index),
168
- { settings, onSettings } = meta,
169
- newSettings = settings.slice(),
170
- setting = settings[idx],
171
- priority = e.target.windeterminate ? settings.reduce((acc, s) => Math.max(acc, s.priority), 0) + 1 : setting.priority;
172
-
173
- newSettings.splice(idx, 1, { ...settings[idx], disabled: !checked, priority });
174
- onSettings(newSettings);
175
- }, [meta])
176
- };
177
- },
189
+ const newSettings = settings.slice();
190
+ newSettings.splice(
191
+ to + (from >= to ? 0 : -1),
192
+ 0,
193
+ newSettings.splice(from, 1)[0]
194
+ );
195
+ onSettings(newSettings);
196
+ },
197
+ [meta]
198
+ ),
178
199
 
179
- renderItem = ({ onDragStart, onDragEnter, onDragOver, onDragLeave, onDrop, onDown, onToggle, collapsed, filters }) => (column, i) => {
180
- const indeterminate = collapsed?.includes(column.name),
181
- checked = !column.disabled && !indeterminate;
182
- return html`
183
- <div class="item" data-index=${ i } @mousedown=${ onDown } draggable="true"
184
- @dragstart=${ onDragStart } @dragenter=${ onDragEnter } @dragover=${ onDragOver } @dragleave=${ onDragLeave }
185
- @drop=${ onDrop }
186
- >
187
- <button class="sort" mousedown="">
188
- <svg viewBox="0 0 24 24" width="24"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"></path></svg>
189
- </button>
190
- <label class="title" ?has-filter=${ !isEmpty(filters[column.name]?.filter) }>${ column.title }</label>
191
- <input class="checkbox" type="checkbox" .checked=${ checked } @click=${ onToggle }
192
- .indeterminate=${ indeterminate } .windeterminate=${ indeterminate }
193
- >
194
- </div>`;
195
- },
200
+ onToggle: useCallback(
201
+ (e) => {
202
+ const checked = e.target.checked,
203
+ idx = parseIndex(e.target.closest('[data-index]')?.dataset.index),
204
+ { settings, onSettings } = meta,
205
+ newSettings = settings.slice(),
206
+ setting = settings[idx],
207
+ priority = e.target.windeterminate
208
+ ? settings.reduce((acc, s) => Math.max(acc, s.priority), 0) + 1
209
+ : setting.priority;
196
210
 
197
- SettingsUI = host => {
198
- const { settings, settingsId, onSave, onReset, hasChanges, ...thru } = useSettings(host);
211
+ newSettings.splice(idx, 1, {
212
+ ...settings[idx],
213
+ disabled: !checked,
214
+ priority,
215
+ });
216
+ onSettings(newSettings);
217
+ },
218
+ [meta]
219
+ ),
220
+ };
221
+ },
222
+ renderItem =
223
+ // eslint-disable-next-line max-lines-per-function
224
+ ({
225
+ onDragStart,
226
+ onDragEnter,
227
+ onDragOver,
228
+ onDragLeave,
229
+ onDrop,
230
+ onDown,
231
+ onToggle,
232
+ collapsed,
233
+ filters,
234
+ }) =>
235
+ (column, i) => {
236
+ const indeterminate = collapsed?.includes(column.name),
237
+ checked = !column.disabled && !indeterminate;
238
+ return html` <div
239
+ class="item"
240
+ data-index=${i}
241
+ @mousedown=${onDown}
242
+ draggable="true"
243
+ @dragstart=${onDragStart}
244
+ @dragenter=${onDragEnter}
245
+ @dragover=${onDragOver}
246
+ @dragleave=${onDragLeave}
247
+ @drop=${onDrop}
248
+ >
249
+ <button class="pull">
250
+ <svg
251
+ width="16"
252
+ height="6"
253
+ viewBox="0 0 16 6"
254
+ fill="currentColor"
255
+ xmlns="http://www.w3.org/2000/svg"
256
+ >
257
+ <rect width="16" height="2" />
258
+ <rect y="4" width="16" height="2" />
259
+ </svg>
260
+ </button>
261
+ <label
262
+ class="title"
263
+ ?has-filter=${!isEmpty(filters[column.name]?.filter)}
264
+ >${column.title}</label
265
+ >
266
+ ${sort(column.name)}
267
+ <input
268
+ class="checkbox"
269
+ type="checkbox"
270
+ .checked=${checked}
271
+ @click=${onToggle}
272
+ .indeterminate=${indeterminate}
273
+ .windeterminate=${indeterminate}
274
+ />
275
+ </div>`;
276
+ },
277
+ SettingsUI = (host) => {
278
+ const { settings, settingsId, onSave, onReset, hasChanges, ...thru } =
279
+ useSettings(host);
199
280
  return [
200
281
  html`
201
- <style>${ settingsStyles }</style>
202
- <div class="list">${ settings?.map(renderItem(thru)) }</div>
282
+ <style>
283
+ ${settingsStyles}
284
+ </style>
285
+ <div class="list">${settings?.map(renderItem(thru))}</div>
203
286
  `,
204
287
  settingsId
205
288
  ? html`<div class="buttons">
206
- <button class="button" @click=${ onSave } ?disabled=${ !hasChanges }>Save</button>
207
- <button class="button" @click=${ onReset } ?disabled=${ !hasChanges }>Reset</button>
208
- </div>`
209
- : nothing
289
+ <button
290
+ class="button reset"
291
+ @click=${onReset}
292
+ ?disabled=${!hasChanges}
293
+ >
294
+ Reset
295
+ </button>
296
+ <button class="button" @click=${onSave} ?disabled=${!hasChanges}>
297
+ Save
298
+ </button>
299
+ </div>`
300
+ : nothing,
210
301
  ];
211
302
  },
212
-
213
- Settings = ({ anchor, settings, onSettings, collapsed, settingsId, hasChanges, onSave, onReset, filters, badge }) => html`
303
+ // eslint-disable-next-line max-lines-per-function
304
+ Settings = ({
305
+ anchor,
306
+ settings,
307
+ onSettings,
308
+ collapsed,
309
+ settingsId,
310
+ hasChanges,
311
+ onSave,
312
+ onReset,
313
+ filters,
314
+ badge,
315
+ }) => html`
214
316
  <style>
215
317
  :host {
216
318
  display: contents;
217
319
  color: var(--cosmoz-omnitable-settings-color, #101010);
218
- --cosmoz-dropdown-box-shadow: 0 3px 4px 0 rgb(0 0 0 / 14%), 0 1px 8px 0 rgb(0 0 0 / 12%), 0 3px 3px -2px rgb(0 0 0 / 40%);
320
+ --cosmoz-dropdown-box-shadow: 0 3px 4px 0 rgb(0 0 0 / 14%),
321
+ 0 1px 8px 0 rgb(0 0 0 / 12%), 0 3px 3px -2px rgb(0 0 0 / 40%);
219
322
  }
220
323
  cosmoz-dropdown::part(button) {
221
324
  --cosmoz-dropdown-button-size: 24px;
222
- margin-bottom: 10px;
223
325
  padding: 0;
224
326
  background: transparent;
225
327
  color: inherit;
@@ -231,19 +333,34 @@ const placement = ['bottom-right', ...defaultPlacement],
231
333
  position: absolute;
232
334
  top: 1px;
233
335
  right: -4px;
234
- background-color: var(--cosmoz-omnitable-checkbox-checked-color, var(--primary-color));
336
+ background-color: var(
337
+ --cosmoz-omnitable-checkbox-checked-color,
338
+ var(--primary-color)
339
+ );
235
340
  width: 8px;
236
341
  height: 8px;
237
342
  border-radius: 100%;
238
343
  }
239
344
  </style>
240
- <cosmoz-dropdown .render=${ () => html`<cosmoz-omnitable-settings-ui
241
- .anchor=${ anchor } .settings=${ settings } .onSettings=${ onSettings } .collapsed=${ collapsed }
242
- .settingsId=${ settingsId } .hasChanges=${ hasChanges } .onSave=${ onSave } .onReset=${ onReset }
243
- .filters=${ filters }></cosmoz-omnitable-settings-ui>` } .placement=${ placement }>
244
- <svg viewBox="0 0 24 24" width="24" slot="button" fill="currentColor"><path d="M10 18h5V5h-5v13zm-6 0h5V5H4v13zM16 5v13h5V5h-5z"></path></svg>
245
- ${ badge ? html`<div class="badge" slot="button"></div>` : nothing }
345
+ <cosmoz-dropdown
346
+ .render=${() => html`<cosmoz-omnitable-settings-ui
347
+ .anchor=${anchor}
348
+ .settings=${settings}
349
+ .onSettings=${onSettings}
350
+ .collapsed=${collapsed}
351
+ .settingsId=${settingsId}
352
+ .hasChanges=${hasChanges}
353
+ .onSave=${onSave}
354
+ .onReset=${onReset}
355
+ .filters=${filters}
356
+ ></cosmoz-omnitable-settings-ui>`}
357
+ .placement=${placement}
358
+ >
359
+ <svg viewBox="0 0 24 24" width="24" slot="button" fill="currentColor">
360
+ <path d="M10 18h5V5h-5v13zm-6 0h5V5H4v13zM16 5v13h5V5h-5z"></path>
361
+ </svg>
362
+ ${badge ? html`<div class="badge" slot="button"></div>` : nothing}
246
363
  </cosmoz-dropdown>
247
- `;
364
+ `;
248
365
  customElements.define('cosmoz-omnitable-settings', component(Settings));
249
366
  customElements.define('cosmoz-omnitable-settings-ui', component(SettingsUI));
@@ -0,0 +1,47 @@
1
+ import { html } from 'haunted';
2
+ import { ifDefined } from 'lit-html/directives/if-defined';
3
+
4
+ const sortIcon = html`<svg
5
+ width="8"
6
+ height="6"
7
+ viewBox="0 0 8 6"
8
+ fill="currentColor"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ >
11
+ <path d="M0.5,0.5h7c0.4,0,0.6,0.4,0.4,0.7L4.4,5.3c-0.2,0.2-0.5,0.2-0.7,0L0.1,1.2
12
+ C-0.1,0.9,0.1,0.5,0.5,0.5z" />
13
+ </svg> `;
14
+
15
+ export default (column) => html`
16
+ <sort-and-group-consumer
17
+ style="display: contents"
18
+ .render=${({
19
+ sortOn,
20
+ setSortOn,
21
+ descending,
22
+ setDescending,
23
+ } = {}) => html` <button
24
+ class="sort"
25
+ data-sort=${ifDefined(
26
+ (column === sortOn && (descending ? 'desc' : 'asc')) || undefined
27
+ )}
28
+ @click=${(e) => {
29
+ const sort = e.currentTarget?.dataset.sort;
30
+ if (!sort) {
31
+ setSortOn(column);
32
+ setDescending(false);
33
+ }
34
+ if (sort === 'asc') {
35
+ setDescending(true);
36
+ }
37
+ if (sort === 'desc') {
38
+ setSortOn();
39
+ setDescending(false);
40
+ }
41
+ }}
42
+ >
43
+ ${sortIcon}
44
+ </button>`}
45
+ >
46
+ </sort-and-group-consumer>
47
+ `;
@@ -1,45 +1,63 @@
1
- import { useMemo } from 'haunted';
1
+ import { useMemo, createContext, component, useContext } from 'haunted';
2
2
  import { useHashState } from './use-hash-state';
3
3
 
4
4
  const parseBool = (bool) => [true, 'true', 1, 'yes', 'on'].includes(bool),
5
5
  boolParam = (p) => p === '' || (p == null ? undefined : parseBool(p));
6
6
 
7
7
  export const useSortAndGroupOptions = (columns, hashParam, initial) => {
8
- const [sortOn, setSortOn] = useHashState(initial.sortOn, hashParam, {
9
- suffix: '-sortOn',
10
- }),
11
- [descending, setDescending] = useHashState(initial.descending, hashParam, {
12
- suffix: '-descending',
13
- read: boolParam,
14
- }),
15
- [groupOn, setGroupOn] = useHashState(initial.groupOn, hashParam, {
16
- suffix: '-groupOn',
17
- }),
18
- [groupOnDescending, setGroupOnDescending] = useHashState(
19
- initial.groupOnDescending,
20
- hashParam,
21
- { suffix: '-groupOnDescending', read: boolParam }
22
- ),
23
- sortOnColumn = useMemo(
24
- () => columns.find((column) => column.name === sortOn),
25
- [columns, sortOn]
26
- ),
27
- groupOnColumn = useMemo(
28
- () => columns.find((column) => column.name === groupOn),
29
- [columns, groupOn]
30
- );
8
+ const [sortOn, setSortOn] = useHashState(initial.sortOn, hashParam, {
9
+ suffix: '-sortOn',
10
+ }),
11
+ [descending, setDescending] = useHashState(
12
+ initial.descending,
13
+ hashParam,
14
+ {
15
+ suffix: '-descending',
16
+ read: boolParam,
17
+ }
18
+ ),
19
+ [groupOn, setGroupOn] = useHashState(initial.groupOn, hashParam, {
20
+ suffix: '-groupOn',
21
+ }),
22
+ [groupOnDescending, setGroupOnDescending] = useHashState(
23
+ initial.groupOnDescending,
24
+ hashParam,
25
+ { suffix: '-groupOnDescending', read: boolParam }
26
+ ),
27
+ sortOnColumn = useMemo(
28
+ () => columns.find((column) => column.name === sortOn),
29
+ [columns, sortOn]
30
+ ),
31
+ groupOnColumn = useMemo(
32
+ () => columns.find((column) => column.name === groupOn),
33
+ [columns, groupOn]
34
+ ),
35
+ sortAndGroup_ = {
36
+ groupOn,
37
+ setGroupOn,
38
+ groupOnDescending,
39
+ setGroupOnDescending,
31
40
 
32
- return {
33
- groupOn,
34
- setGroupOn,
35
- groupOnColumn,
36
- groupOnDescending,
37
- setGroupOnDescending,
41
+ sortOn,
42
+ setSortOn,
43
+ descending,
44
+ setDescending,
45
+ },
46
+ sortAndGroup = useMemo(() => sortAndGroup_, Object.values(sortAndGroup_));
38
47
 
39
- sortOn,
40
- setSortOn,
41
- sortOnColumn,
42
- descending,
43
- setDescending,
44
- };
45
- };
48
+ return {
49
+ ...sortAndGroup,
50
+ sortAndGroup,
51
+ groupOnColumn,
52
+ sortOnColumn,
53
+ };
54
+ },
55
+ SortAndGroupContext = createContext();
56
+
57
+ customElements.define('sort-and-group-provider', SortAndGroupContext.Provider);
58
+ customElements.define(
59
+ 'sort-and-group-consumer',
60
+ component(({ render }) => render(useContext(SortAndGroupContext)), {
61
+ useShadowDOM: false,
62
+ })
63
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-omnitable",
3
- "version": "8.5.2",
3
+ "version": "8.7.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"
@@ -80,8 +80,6 @@
80
80
  "@neovici/cosmoz-viewinfo": "^3.1.3",
81
81
  "@neovici/eslint-config": "^1.3.3",
82
82
  "@open-wc/testing": "^2.5.28",
83
- "@polymer/iron-component-page": "^4.0.0",
84
- "@polymer/iron-demo-helpers": "^3.0.0",
85
83
  "@polymer/iron-test-helpers": "^3.0.0",
86
84
  "@polymer/paper-button": "^3.0.1",
87
85
  "@polymer/paper-item": "^3.0.1",