@neovici/cosmoz-autocomplete 3.0.3 → 3.2.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/cosmoz-listbox.js CHANGED
@@ -6,70 +6,111 @@ import { spreadProps } from '@open-wc/lit-helpers';
6
6
  import { useListbox, properties } from './lib/use-listbox';
7
7
  import { scrollIntoViewIfNeeded } from './lib/utils';
8
8
 
9
- const Listbox = host => {
10
- const { index, items, renderItem, height, itemHeight } = useListbox(host);
9
+ const svg =
10
+ 'data:image/svg+xml,%3Csvg width=\'11\' height=\'8\' viewBox=\'0 0 11 8\' fill=\'none\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath ' +
11
+ 'd=\'M9.5 1L5.20039 7.04766L1.66348 3.46152\' stroke=\'white\' stroke-width=\'1.5\' stroke-linecap=\'round\' stroke-linejoin=\'round\'/%3E%3C/svg%3E',
12
+ Listbox = (host) => {
13
+ const { index, items, renderItem, height, itemHeight } = useListbox(host);
11
14
 
12
- useEffect(() => {
13
- scrollIntoViewIfNeeded(host.shadowRoot.querySelector('[data-index="' + index + '"]:not(:hover)'), host);
14
- }, [index]);
15
+ useEffect(() => {
16
+ scrollIntoViewIfNeeded(
17
+ host.shadowRoot.querySelector(
18
+ '[data-index="' + index + '"]:not(:hover)'
19
+ ),
20
+ host
21
+ );
22
+ }, [index]);
15
23
 
16
- return html`
17
- <style>
18
- :host {
19
- position: fixed;
20
- min-height: ${ itemHeight }px;
21
- z-index: 1000;
22
- height: ${ height }px;
23
- font-family: var(--paper-font-subhead_-_font-family, initial);
24
- background: #fff;
25
- min-width: 72px;
26
- box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14),
27
- 0 1px 8px 0 rgba(0, 0, 0, 0.12),
28
- 0 3px 3px -2px rgba(0, 0, 0, 0.4);
29
- }
30
- .items {
31
- position: relative;
32
- overflow-y: auto;
33
- height: 100%;
34
- }
35
- .item {
36
- font-size: 14px;
37
- line-height: ${ itemHeight }px;
38
- height: ${ itemHeight }px;
39
- padding: 0 20px;
40
- box-sizing: border-box;
41
- width: 100%;
42
- cursor: pointer;
43
- overflow: hidden;
44
- text-overflow: ellipsis;
45
- white-space: nowrap;
46
- transition: background 0.2s;
47
- color: var(--cosmoz-listbox-color, #101010);
48
- }
24
+ return html` <style>
25
+ :host {
26
+ position: fixed;
27
+ min-height: ${itemHeight}px;
28
+ z-index: 1000;
29
+ height: ${height}px;
30
+ font-family: var(--paper-font-subhead_-_font-family, initial);
31
+ background: #fff;
32
+ min-width: 72px;
33
+ box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14),
34
+ 0 1px 8px 0 rgba(0, 0, 0, 0.12), 0 3px 3px -2px rgba(0, 0, 0, 0.4);
35
+ }
36
+ .items {
37
+ position: relative;
38
+ overflow-y: auto;
39
+ height: 100%;
40
+ }
41
+ .item {
42
+ font-size: 14px;
43
+ line-height: ${itemHeight}px;
44
+ height: ${itemHeight}px;
45
+ padding: 0 20px;
46
+ box-sizing: border-box;
47
+ width: 100%;
48
+ cursor: pointer;
49
+ overflow: hidden;
50
+ text-overflow: ellipsis;
51
+ white-space: nowrap;
52
+ transition: background 0.2s;
53
+ color: var(--cosmoz-listbox-color, #101010);
54
+ }
49
55
 
50
- .item[data-index="${ index }"]{
51
- background: var(--cosmoz-listbox-active-color, var(--cosmoz-selection-color, rgba(58, 145, 226, 0.1)));
52
- }
56
+ .item[data-index='${index}'] {
57
+ background: var(
58
+ --cosmoz-listbox-active-color,
59
+ var(--cosmoz-selection-color, rgba(58, 145, 226, 0.1))
60
+ );
61
+ }
62
+ .sizer {
63
+ position: relative;
64
+ visibility: hidden;
65
+ opacity: 0;
66
+ pointer-events: none;
67
+ z-index: -1;
68
+ height: 0;
69
+ width: auto;
70
+ padding: 0 20px;
71
+ overflow: hidden;
72
+ max-width: inherit;
73
+ font-size: 14px;
74
+ }
75
+ .items > :first-child {
76
+ line-height: 1;
77
+ }
53
78
 
54
- .sizer {
55
- position: relative;
56
- visibility: hidden;
57
- opacity:0;
58
- pointer-events: none;
59
- z-index: -1;
60
- height: 0;
61
- width: auto;
62
- padding: 0 20px;
63
- overflow: hidden;
64
- max-width: inherit;
65
- font-size: 14px;
66
- }
67
- .items > :first-child { line-height: 1;}
68
- ${ host.styles || '' }
69
- </style>
70
- <div class="items">${ scroll({ items, renderItem }) }</div>`;
71
- };
79
+ :host(:not([multi])) .item[aria-selected] {
80
+ background: var(--cosmoz-listbox-single-selection-color, #dadada);
81
+ }
82
+
83
+ :host([multi]) .item {
84
+ padding-left: 0;
85
+ }
86
+ :host([multi]) .item::before {
87
+ content: '';
88
+ font-size: 0;
89
+ padding: 7.5px;
90
+ margin: 0 8px;
91
+ background: #fff;
92
+ border: 1px solid #d6d6d5;
93
+ border-radius: 4px;
94
+ vertical-align: top;
95
+ }
96
+ :host([multi]) .item[aria-selected]::before {
97
+ border-color: #5881f6;
98
+ background: url("${svg}") #5881f6 no-repeat 50%;
99
+ }
100
+ :host([multi]) .sizer {
101
+ padding-left: 33px;
102
+ }
103
+ </style>
104
+ <div class="items">${scroll({ items, renderItem })}</div>`;
105
+ };
72
106
 
73
107
  customElements.define('cosmoz-listbox', component(Listbox));
74
108
 
75
- export const listbox = thru => portal(html`<cosmoz-listbox part="listbox" ...=${ spreadProps(props(properties)(thru)) }>`);
109
+ export const listbox = ({ multi, ...thru }) =>
110
+ portal(
111
+ html`<cosmoz-listbox
112
+ part="listbox"
113
+ ?multi=${multi}
114
+ ...=${spreadProps(props(properties)(thru))}
115
+ ></cosmoz-listbox>`
116
+ );
@@ -1,31 +1,4 @@
1
1
  import { tagged as css } from '@neovici/cosmoz-utils';
2
- const svg = 'data:image/svg+xml,%3Csvg width=\'11\' height=\'8\' viewBox=\'0 0 11 8\' fill=\'none\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath '
3
- + 'd=\'M9.5 1L5.20039 7.04766L1.66348 3.46152\' stroke=\'white\' stroke-width=\'1.5\' stroke-linecap=\'round\' stroke-linejoin=\'round\'/%3E%3C/svg%3E';
4
-
5
- export const listboxStyle = css`
6
- .item {
7
- padding-left: 0;
8
- }
9
- .item::before {
10
- content: "";
11
- font-size: 0;
12
- padding: 7.5px;
13
- margin: 0 8px;
14
- background: #fff;
15
- border: 1px solid #d6d6d5;
16
- border-radius: 4px;
17
- vertical-align: top;
18
- }
19
-
20
- .item[aria-selected]::before {
21
- border-color: #5881f6;
22
- background: url("${ svg }") #5881f6 no-repeat 50%;
23
- }
24
-
25
- .sizer {
26
- padding-left: 33px;
27
- }
28
- `;
29
2
 
30
3
  export default css`
31
4
  :host {
@@ -67,7 +40,7 @@ export default css`
67
40
  }
68
41
  .chip-clear {
69
42
  margin: 2px 4px 2px -6px;
70
- background-color: var(--cosmoz-autocomplete-chip-clear-bg-color, #81899B);
43
+ background-color: var(--cosmoz-autocomplete-chip-clear-bg-color, #81899b);
71
44
  border-radius: 500px;
72
45
  cursor: pointer;
73
46
  width: 16px;
@@ -75,7 +48,9 @@ export default css`
75
48
  stroke: var(--cosmoz-autocomplete-chip-bg-color, #cbcfdb);
76
49
  display: block;
77
50
  }
78
- .chip-clear svg {
51
+
52
+ .chip-clear svg,
53
+ .badge-clear svg {
79
54
  display: block;
80
55
  transform: translate(3.5px, 3.5px);
81
56
  }
@@ -86,12 +61,25 @@ export default css`
86
61
  font-weight: 500;
87
62
  font-size: 12px;
88
63
  line-height: 22px;
89
- padding: 0 2px;
64
+ padding: 0 4px 0 6px;
65
+ gap: 4px;
90
66
  border-radius: 90px;
91
67
  text-align: center;
92
68
  box-sizing: border-box;
69
+ /* fallback for unsupported min-content FF, Safari */
93
70
  flex: 0.01 2 30px;
71
+ flex: 0 2 min-content;
72
+ display: flex;
73
+ align-items: center;
74
+ }
75
+ .badge-clear {
76
+ background-color: var(--cosmoz-autocomplete-chip-clear-bg-color, #81899b);
77
+ border-radius: 500px;
94
78
  cursor: pointer;
79
+ width: 16px;
80
+ height: 16px;
81
+ stroke: var(--cosmoz-autocomplete-chip-bg-color, #cbcfdb);
82
+ display: block;
95
83
  }
96
84
 
97
85
  slot {
@@ -7,35 +7,77 @@ import { array } from '@neovici/cosmoz-utils/lib/array';
7
7
  import '@neovici/cosmoz-input';
8
8
  import { useAutocomplete } from './use-autocomplete';
9
9
  import { listbox } from '../cosmoz-listbox';
10
- import style, { listboxStyle } from './autocomplete.css';
10
+ import style from './autocomplete.css';
11
11
 
12
- const
13
- blank = () => nothing,
12
+ const blank = () => nothing,
13
+ clear = html`
14
+ <svg
15
+ width="9"
16
+ height="8"
17
+ viewBox="0 0 9 8"
18
+ fill="none"
19
+ xmlns="http://www.w3.org/2000/svg"
20
+ >
21
+ <line
22
+ x1="7.53033"
23
+ y1="0.994808"
24
+ x2="1.16637"
25
+ y2="7.35877"
26
+ stroke-width="1.5"
27
+ />
28
+ <line
29
+ x1="7.46967"
30
+ y1="7.35882"
31
+ x2="1.10571"
32
+ y2="0.99486"
33
+ stroke-width="1.5"
34
+ />
35
+ </svg>
36
+ `,
14
37
  renderChip = (text, onClear, disabled, isOne) => html`
15
- <div class="chip" part="chip" slot="suffix" title=${ text } ?data-one=${isOne}>
16
- <span class="chip-text" part="chip-text">${ text }</span>
17
- ${ !disabled && html`
18
- <span class="chip-clear" part="chip-clear" @click=${ onClear }>
19
- <svg width="9" height="8" viewBox="0 0 9 8" fill="none" xmlns="http://www.w3.org/2000/svg">
20
- <line x1="7.53033" y1="0.994808" x2="1.16637" y2="7.35877" stroke-width="1.5"/>
21
- <line x1="7.46967" y1="7.35882" x2="1.10571" y2="0.99486" stroke-width="1.5"/>
22
- </svg>
23
- </span>` || nothing }
38
+ <div
39
+ class="chip"
40
+ part="chip"
41
+ slot="suffix"
42
+ title=${text}
43
+ ?data-one=${isOne}
44
+ >
45
+ <span class="chip-text" part="chip-text">${text}</span>
46
+ ${(!disabled &&
47
+ html` <span class="chip-clear" part="chip-clear" @click=${onClear}>
48
+ ${clear}
49
+ </span>`) ||
50
+ nothing}
24
51
  </div>
25
52
  `,
26
- renderBadge = len => len > 0 ? html`<div class="badge" slot="suffix" part="badge">${ len }</div>` : nothing,
53
+ renderBadge = ({ value, onDeselect }) => {
54
+ const len = value?.length;
55
+ return len > 0
56
+ ? html`<div class="badge" slot="suffix" part="badge">
57
+ ${len}
58
+ <span
59
+ class="badge-clear"
60
+ part="chip-clear"
61
+ @click=${() => onDeselect(value)}
62
+ >${clear}</span
63
+ >
64
+ </div>`
65
+ : nothing;
66
+ },
27
67
  renderSelection = (value, isOne, onDeselect, textual, disabled) => {
28
68
  if (isOne || value.length === 1) {
29
69
  const val = value[0];
30
- return val ? renderChip(textual(val), () => onDeselect(val), disabled, isOne) : nothing;
70
+ return val
71
+ ? renderChip(textual(val), () => onDeselect(val), disabled, isOne)
72
+ : nothing;
31
73
  }
32
- return renderBadge(value.length);
74
+ return renderBadge({ value, onDeselect });
33
75
  },
34
- inputParts = ['input', 'label', 'line', 'error'].map(part => `${ part }: input-${ part }`).join(),
35
-
36
- autocomplete = props => {
37
- const
38
- {
76
+ inputParts = ['input', 'label', 'line', 'error']
77
+ .map((part) => `${part}: input-${part}`)
78
+ .join(),
79
+ autocomplete = (props) => {
80
+ const {
39
81
  invalid,
40
82
  errorMessage,
41
83
  label,
@@ -53,51 +95,80 @@ const
53
95
  limit,
54
96
  showSingle,
55
97
  items$,
56
- values$
98
+ values$,
57
99
  } = props,
58
100
  host = useHost(),
59
-
60
101
  isOne = limit == 1, // eslint-disable-line eqeqeq
61
102
  isSingle = isOne && array(value)?.[0] != null,
62
- anchor = useCallback(() => host.shadowRoot.querySelector('#input'), [host, value]),
63
- suggestions = until(items$.then(items => (!isSingle || showSingle) && items.length && listbox({
64
- ...props,
65
- anchor,
66
- items,
67
- styles: !isOne ? listboxStyle : undefined
68
- }) || nothing));
103
+ anchor = useCallback(
104
+ () => host.shadowRoot.querySelector('#input'),
105
+ [host, value]
106
+ ),
107
+ suggestions = until(
108
+ items$.then(
109
+ (items) =>
110
+ ((!isSingle || showSingle) &&
111
+ items.length &&
112
+ listbox({
113
+ ...props,
114
+ anchor,
115
+ items,
116
+ multi: !isOne,
117
+ })) ||
118
+ nothing
119
+ )
120
+ );
69
121
 
70
- return html`
71
- <style>${ style }</style>
122
+ return html` <style>
123
+ ${style}
124
+ </style>
72
125
  <cosmoz-input
73
- id="input" part="input"
74
- .label=${ label }
75
- .placeholder=${ placeholder }
76
- ?no-label-float=${ noLabelFloat }
77
- ?always-float-label=${ isSingle || alwaysFloatLabel }
78
- ?readonly=${ isSingle }
79
- ?disabled=${ disabled }
80
- ?invalid=${ until(values$.then(() => invalid, () => true), invalid) }
81
- .errorMessage=${ until(values$.then(() => errorMessage, e => e.message), errorMessage) }
82
- .value=${ live(text) }
83
- @value-changed=${ onText }
84
- @focusin=${ onFocus } @focusout=${ onFocus }
85
- @click=${ onClick }
126
+ id="input"
127
+ part="input"
128
+ .label=${label}
129
+ .placeholder=${placeholder}
130
+ ?no-label-float=${noLabelFloat}
131
+ ?always-float-label=${isSingle || alwaysFloatLabel}
132
+ ?readonly=${isSingle}
133
+ ?disabled=${disabled}
134
+ ?invalid=${until(
135
+ values$.then(
136
+ () => invalid,
137
+ () => true
138
+ ),
139
+ invalid
140
+ )}
141
+ .errorMessage=${until(
142
+ values$.then(
143
+ () => errorMessage,
144
+ (e) => e.message
145
+ ),
146
+ errorMessage
147
+ )}
148
+ .value=${live(text)}
149
+ @value-changed=${onText}
150
+ @focusin=${onFocus}
151
+ @focusout=${onFocus}
152
+ @click=${onClick}
86
153
  autocomplete="off"
87
- exportparts=${ inputParts }
154
+ exportparts=${inputParts}
88
155
  >
89
156
  <slot name="prefix" slot="prefix"></slot>
90
157
  <slot name="suffix" slot="suffix"></slot>
91
- ${ renderSelection(array(value), isOne, onDeselect, textual, disabled) }
92
- ${ until(values$.then(blank, blank), html`<div slot="suffix" class="spinner"></div>`) }
158
+ ${renderSelection(array(value), isOne, onDeselect, textual, disabled)}
159
+ ${until(
160
+ values$.then(blank, blank),
161
+ html`<div slot="suffix" class="spinner"></div>`
162
+ )}
93
163
  </cosmoz-input>
94
164
 
95
- ${ suggestions }`;
165
+ ${suggestions}`;
96
166
  },
97
- Autocomplete = props => autocomplete({
98
- ...props,
99
- ...useAutocomplete(props)
100
- }),
167
+ Autocomplete = (props) =>
168
+ autocomplete({
169
+ ...props,
170
+ ...useAutocomplete(props),
171
+ }),
101
172
  observedAttributes = [
102
173
  'disabled',
103
174
  'invalid',
@@ -111,11 +182,7 @@ const
111
182
  'hide-empty',
112
183
  'default-index',
113
184
  'item-height',
114
- 'item-limit'
185
+ 'item-limit',
115
186
  ];
116
187
 
117
- export {
118
- autocomplete,
119
- Autocomplete,
120
- observedAttributes
121
- };
188
+ export { autocomplete, Autocomplete, observedAttributes };
@@ -22,6 +22,7 @@ const
22
22
  text,
23
23
  onChange: _onChange,
24
24
  onText: _onText,
25
+ onSelect,
25
26
  limit,
26
27
  source,
27
28
  textProperty,
@@ -58,7 +59,7 @@ const
58
59
  onText
59
60
  });
60
61
 
61
- const meta = useMeta({ onText, onChange, value, limit, keepQuery, setClosed });
62
+ const meta = useMeta({ onText, onChange, value, limit, keepQuery, setClosed, onSelect });
62
63
 
63
64
  return {
64
65
  query,
@@ -79,7 +80,11 @@ const
79
80
  setClosed(false);
80
81
  }, [onText, text, setClosed]),
81
82
  onSelect: useCallback(newVal => {
82
- const { onChange, onText, limit, value: val, keepQuery, keepOpened } = meta;
83
+ const { onSelect} = meta;
84
+ if(onSelect) {
85
+ return onSelect(newVal, meta);
86
+ }
87
+ const { onChange, onText, limit, value: val, keepQuery, keepOpened, setClosed } = meta;
83
88
  if (!keepQuery) {
84
89
  onText('');
85
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-autocomplete",
3
- "version": "3.0.3",
3
+ "version": "3.2.0",
4
4
  "description": "A autocomplete input web component",
5
5
  "keywords": [
6
6
  "lit-html",