@neovici/cosmoz-autocomplete 3.1.0 → 4.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cosmoz-listbox.js CHANGED
@@ -1,14 +1,93 @@
1
1
  import { html, component, useEffect } from 'haunted';
2
- import { scroll } from 'lit-virtualizer';
2
+ import { virtualize } from '@lit-labs/virtualizer/virtualize.js';
3
+ import { tagged as css } from '@neovici/cosmoz-utils';
3
4
  import { portal } from '@neovici/cosmoz-utils/lib/directives/portal';
5
+ import { spreadProps } from '@neovici/cosmoz-utils/lib/directives/spread-props';
4
6
  import { props } from '@neovici/cosmoz-utils/lib/object';
5
- import { spreadProps } from '@open-wc/lit-helpers';
6
7
  import { useListbox, properties } from './lib/use-listbox';
7
8
  import { scrollIntoViewIfNeeded } from './lib/utils';
8
9
 
9
10
  const svg =
10
11
  '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
12
  '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',
13
+ styles = ({ index, height, itemHeight }) => css`
14
+ :host {
15
+ position: fixed;
16
+ min-height: ${itemHeight}px;
17
+ z-index: 1000;
18
+ height: ${height}px;
19
+ font-family: var(--paper-font-subhead_-_font-family, initial);
20
+ background: #fff;
21
+ min-width: 72px;
22
+ box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14),
23
+ 0 1px 8px 0 rgba(0, 0, 0, 0.12), 0 3px 3px -2px rgba(0, 0, 0, 0.4);
24
+ }
25
+ .items {
26
+ position: relative;
27
+ overflow-y: auto;
28
+ contain: layout paint !important;
29
+ height: 100%;
30
+ }
31
+ .item {
32
+ font-size: 14px;
33
+ line-height: ${itemHeight}px;
34
+ height: ${itemHeight}px;
35
+ padding: 0 20px;
36
+ box-sizing: border-box;
37
+ width: 100%;
38
+ cursor: pointer;
39
+ text-overflow: ellipsis;
40
+ white-space: nowrap;
41
+ transition: background 0.2s;
42
+ color: var(--cosmoz-listbox-color, #101010);
43
+ }
44
+
45
+ .item[data-index='${index}'] {
46
+ background: var(
47
+ --cosmoz-listbox-active-color,
48
+ var(--cosmoz-selection-color, rgba(58, 145, 226, 0.1))
49
+ );
50
+ }
51
+ .sizer {
52
+ position: relative;
53
+ visibility: hidden;
54
+ opacity: 0;
55
+ pointer-events: none;
56
+ z-index: -1;
57
+ height: 0;
58
+ width: auto;
59
+ padding: 0 20px;
60
+ overflow: hidden;
61
+ max-width: inherit;
62
+ font-size: 14px;
63
+ }
64
+
65
+ :host(:not([multi])) .item[aria-selected] {
66
+ background: var(--cosmoz-listbox-single-selection-color, #dadada);
67
+ }
68
+
69
+ :host([multi]) .item {
70
+ padding-left: 0;
71
+ }
72
+ :host([multi]) .item::before {
73
+ content: '';
74
+ font-size: 0;
75
+ padding: 7.5px;
76
+ margin: 0 8px;
77
+ background: #fff;
78
+ border: 1px solid #d6d6d5;
79
+ border-radius: 4px;
80
+ vertical-align: top;
81
+ }
82
+ :host([multi]) .item[aria-selected]::before {
83
+ border-color: #5881f6;
84
+ /* prettier-ignore */
85
+ background: url("${svg}") #5881f6 no-repeat 50%;
86
+ }
87
+ :host([multi]) .sizer {
88
+ padding-left: 33px;
89
+ }
90
+ `,
12
91
  Listbox = (host) => {
13
92
  const { index, items, renderItem, height, itemHeight } = useListbox(host);
14
93
 
@@ -22,86 +101,12 @@ const svg =
22
101
  }, [index]);
23
102
 
24
103
  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
- }
55
-
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
- }
78
-
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
- }
104
+ ${styles({ index, height, itemHeight })}
103
105
  </style>
104
- <div class="items">${scroll({ items, renderItem })}</div>`;
106
+ <div class="items">
107
+ <div virtualizer-sizer></div>
108
+ ${virtualize({ items, renderItem, scroller: true })}
109
+ </div>`;
105
110
  };
106
111
 
107
112
  customElements.define('cosmoz-listbox', component(Listbox));
@@ -40,7 +40,7 @@ export default css`
40
40
  }
41
41
  .chip-clear {
42
42
  margin: 2px 4px 2px -6px;
43
- background-color: var(--cosmoz-autocomplete-chip-clear-bg-color, #81899B);
43
+ background-color: var(--cosmoz-autocomplete-chip-clear-bg-color, #81899b);
44
44
  border-radius: 500px;
45
45
  cursor: pointer;
46
46
  width: 16px;
@@ -48,7 +48,9 @@ export default css`
48
48
  stroke: var(--cosmoz-autocomplete-chip-bg-color, #cbcfdb);
49
49
  display: block;
50
50
  }
51
- .chip-clear svg {
51
+
52
+ .chip-clear svg,
53
+ .badge-clear svg {
52
54
  display: block;
53
55
  transform: translate(3.5px, 3.5px);
54
56
  }
@@ -59,12 +61,25 @@ export default css`
59
61
  font-weight: 500;
60
62
  font-size: 12px;
61
63
  line-height: 22px;
62
- padding: 0 2px;
64
+ padding: 0 4px 0 6px;
65
+ gap: 4px;
63
66
  border-radius: 90px;
64
67
  text-align: center;
65
68
  box-sizing: border-box;
69
+ /* fallback for unsupported min-content FF, Safari */
66
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;
67
78
  cursor: pointer;
79
+ width: 16px;
80
+ height: 16px;
81
+ stroke: var(--cosmoz-autocomplete-chip-bg-color, #cbcfdb);
82
+ display: block;
68
83
  }
69
84
 
70
85
  slot {
@@ -1,6 +1,6 @@
1
1
  import { html, nothing } from 'lit-html'; // eslint-disable-line object-curly-newline
2
- import { live } from 'lit-html/directives/live';
3
- import { until } from 'lit-html/directives/until';
2
+ import { live } from 'lit-html/directives/live.js';
3
+ import { until } from 'lit-html/directives/until.js';
4
4
  import { useCallback } from 'haunted';
5
5
  import { useHost } from '@neovici/cosmoz-utils/lib/hooks/use-host';
6
6
  import { array } from '@neovici/cosmoz-utils/lib/array';
@@ -9,33 +9,75 @@ import { useAutocomplete } from './use-autocomplete';
9
9
  import { listbox } from '../cosmoz-listbox';
10
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,
@@ -56,48 +98,77 @@ const
56
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
- multi: !isOne,
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 };
@@ -23,5 +23,5 @@ export default (
23
23
  @mousedown=${ e => e.preventDefault() }
24
24
  title=${ text }
25
25
  >${ content }</div>
26
- <div class="sizer">${ content }</div>`;
26
+ <div class="sizer" virtualizer-sizer>${ content }</div>`;
27
27
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-autocomplete",
3
- "version": "3.1.0",
3
+ "version": "4.0.0-beta.1",
4
4
  "description": "A autocomplete input web component",
5
5
  "keywords": [
6
6
  "lit-html",
@@ -23,10 +23,9 @@
23
23
  "scripts": {
24
24
  "lint": "eslint --cache --ext .js .",
25
25
  "lint-tsc": "tsc",
26
- "start": "npm run storybook",
26
+ "start": "wds",
27
27
  "test": "wtr --coverage",
28
28
  "test:watch": "wtr --watch",
29
- "storybook": "start-storybook --node-resolve --watch --open",
30
29
  "storybook:build": "build-storybook",
31
30
  "storybook:deploy": "storybook-to-ghpages",
32
31
  "prepare": "husky install"
@@ -56,27 +55,26 @@
56
55
  ]
57
56
  },
58
57
  "dependencies": {
59
- "@neovici/cosmoz-input": "^1.5.0",
60
- "@neovici/cosmoz-utils": "^3.20.0",
61
- "@open-wc/lit-helpers": "^0.3.12",
62
- "haunted": "^4.7.0",
63
- "lit-html": "^1.4.0",
64
- "lit-virtualizer": "^0.4.2",
58
+ "@lit-labs/virtualizer": "^0.7.0",
59
+ "@neovici/cosmoz-dropdown": "^2.0.0-beta.1",
60
+ "@neovici/cosmoz-input": "^2.0.0-beta.1",
61
+ "@neovici/cosmoz-utils": "^4.0.0-beta.2",
62
+ "haunted": "^5.0.0",
63
+ "lit-html": "^2.0.0",
65
64
  "position.js": "^0.3.0"
66
65
  },
67
66
  "devDependencies": {
68
- "@commitlint/cli": "^16.0.0",
69
- "@commitlint/config-conventional": "^16.0.0",
67
+ "@commitlint/cli": "^17.0.0",
68
+ "@commitlint/config-conventional": "^17.0.0",
70
69
  "@neovici/cfg": "^1.13.0",
71
- "@neovici/cosmoz-dropdown": "^1.8.0",
72
- "@open-wc/demoing-storybook": "^2.1.0",
73
- "@open-wc/testing": "^2.5.32",
70
+ "@open-wc/testing": "^3.0.0",
74
71
  "@semantic-release/changelog": "^6.0.0",
75
72
  "@semantic-release/git": "^10.0.0",
76
73
  "@storybook/storybook-deployer": "^2.8.5",
77
- "husky": "^7.0.0",
74
+ "@web/dev-server-storybook": "^0.5.1",
75
+ "husky": "^8.0.0",
78
76
  "semantic-release": "^19.0.0",
79
- "sinon": "^13.0.0",
77
+ "sinon": "^14.0.0",
80
78
  "typescript": "^4.6.0"
81
79
  }
82
80
  }