@neovici/cosmoz-autocomplete 10.2.3 → 10.4.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.
@@ -11,14 +11,15 @@ export interface Props<I> extends Base<I> {
11
11
  noLabelFloat?: boolean;
12
12
  alwaysFloatLabel?: boolean;
13
13
  showSingle?: boolean;
14
- itemHeight?: number;
14
+ itemHeight?: number | 'auto';
15
15
  itemLimit?: number;
16
16
  wrap?: boolean;
17
17
  defaultIndex?: number;
18
+ externalSearch?: boolean;
18
19
  placement?: Placement;
19
20
  }
20
21
  type AProps<I> = Omit<Props<I>, keyof RProps<I>> & RProps<I> & {
21
22
  onInputRef?: (el?: Element) => void;
22
23
  };
23
- declare const autocomplete: <I>(props: AProps<I>) => import("lit-html").TemplateResult<1>, Autocomplete: <I>(props: Props<I>) => import("lit-html").TemplateResult<1>, observedAttributes: readonly ["disabled", "invalid", "no-label-float", "always-float-label", "text-property", "value-property", "limit", "min", "show-single", "preserve-order", "keep-opened", "keep-query", "default-index", "item-height", "item-limit", "wrap"];
24
+ declare const autocomplete: <I>(props: AProps<I>) => import("lit-html").TemplateResult<1>, Autocomplete: <I>(props: Props<I>) => import("lit-html").TemplateResult<1>, observedAttributes: readonly ["disabled", "invalid", "no-label-float", "always-float-label", "text-property", "value-property", "limit", "min", "show-single", "preserve-order", "keep-opened", "keep-query", "default-index", "external-search", "item-height", "item-limit", "wrap"];
24
25
  export { Autocomplete, autocomplete, observedAttributes, style };
@@ -99,6 +99,7 @@ const autocomplete = (props) => {
99
99
  'keep-opened',
100
100
  'keep-query',
101
101
  'default-index',
102
+ 'external-search',
102
103
  'item-height',
103
104
  'item-limit',
104
105
  'wrap',
@@ -17,7 +17,9 @@ export default css `
17
17
  }
18
18
  .content {
19
19
  color: var(--cosmoz-autocomplete-chip-color, #424242);
20
+ font-family: var(--cosmoz-autocomplete-chip-text-font-family, 'Inter', sans-serif);
20
21
  font-size: var(--cosmoz-autocomplete-chip-text-font-size, 12px);
22
+ font-weight: var(--cosmoz-autocomplete-chip-text-font-weight, 400);
21
23
  line-height: var(--cosmoz-autocomplete-chip-text-line-height, 22px);
22
24
  overflow: hidden;
23
25
  text-overflow: ellipsis;
@@ -26,8 +26,9 @@ export interface Props<I> extends Base<I> {
26
26
  onFocus?: (focused?: boolean) => void;
27
27
  preserveOrder?: boolean;
28
28
  defaultIndex?: number;
29
+ externalSearch?: boolean;
29
30
  }
30
- export declare const useAutocomplete: <I>({ value: _value, text, onChange: _onChange, onText: _onText, onSelect, limit, min, source, textProperty, textual: _textual, valueProperty, keepOpened, keepQuery, preserveOrder, defaultIndex, ...thru }: Props<I>) => {
31
+ export declare const useAutocomplete: <I>({ value: _value, text, onChange: _onChange, onText: _onText, onSelect, limit, min, source, textProperty, textual: _textual, valueProperty, keepOpened, keepQuery, preserveOrder, defaultIndex, externalSearch, ...thru }: Props<I>) => {
31
32
  active: boolean | undefined;
32
33
  query: string;
33
34
  textual: (i: I) => string;
@@ -6,7 +6,7 @@ import { useMeta } from '@neovici/cosmoz-utils/hooks/use-meta';
6
6
  import { useFocus } from '@neovici/cosmoz-dropdown/use-focus';
7
7
  import { useKeys } from './use-keys';
8
8
  import { search, normalize, notify, useNotify, EMPTY } from './util';
9
- export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onText: _onText, onSelect, limit, min, source, textProperty, textual: _textual, valueProperty, keepOpened, keepQuery, preserveOrder, defaultIndex, ...thru }) => {
9
+ export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onText: _onText, onSelect, limit, min, source, textProperty, textual: _textual, valueProperty, keepOpened, keepQuery, preserveOrder, defaultIndex, externalSearch, ...thru }) => {
10
10
  const textual = useMemo(() => (_textual ?? strProp)(textProperty), [_textual, textProperty]), { active, focused, onFocus, setClosed } = useFocus(thru), empty = !text, query = useMemo(() => text?.trim(), [text]), host = useHost(), onText = useNotify(host, _onText, 'text'), onChange = useCallback((val) => {
11
11
  _onChange?.(val, () => setClosed(true));
12
12
  notify(host, 'value', val);
@@ -47,7 +47,7 @@ export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onTe
47
47
  const items = preserveOrder
48
48
  ? options
49
49
  : [...value, ...without(value, prop(valueProperty))(options)];
50
- return search(items, query, textual);
50
+ return externalSearch ? items : search(items, query, textual);
51
51
  }, [
52
52
  options,
53
53
  active,
@@ -57,6 +57,7 @@ export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onTe
57
57
  value,
58
58
  preserveOrder,
59
59
  valueProperty,
60
+ externalSearch,
60
61
  ]),
61
62
  onClick: useCallback(() => setClosed(false), []),
62
63
  onFocus,
@@ -2,7 +2,7 @@ import { useEffect } from '@pionjs/pion';
2
2
  import { array } from '@neovici/cosmoz-utils/array';
3
3
  import { useMeta } from '@neovici/cosmoz-utils/hooks/use-meta';
4
4
  export const useKeys = ({ focused, empty, ...rest }) => {
5
- const enabled = focused && empty, meta = useMeta(rest);
5
+ const enabled = focused && empty && rest.limit !== 1, meta = useMeta(rest);
6
6
  useEffect(() => {
7
7
  if (!enabled) {
8
8
  return;
@@ -1,5 +1,5 @@
1
- import { Props } from './use-listbox';
2
1
  import { StyleInfo } from 'lit-html/directives/style-map.js';
2
+ import { Props } from './use-listbox';
3
3
  interface ListboxProps<I> extends Props<I> {
4
4
  multi?: boolean;
5
5
  setFloating: (el?: Element) => void;
@@ -1,29 +1,39 @@
1
1
  import { virtualize, virtualizerRef, } from '@lit-labs/virtualizer/virtualize.js';
2
+ import { connectable } from '@neovici/cosmoz-dropdown/connectable';
2
3
  import { sheet } from '@neovici/cosmoz-utils';
3
4
  import { spreadProps } from '@neovici/cosmoz-utils/directives/spread-props';
4
5
  import { useStyleSheet } from '@neovici/cosmoz-utils/hooks/use-stylesheet';
5
6
  import { props } from '@neovici/cosmoz-utils/object';
6
- import { component, html, useEffect, useMemo, useRef } from '@pionjs/pion';
7
+ import { component, html, useEffect, useRef } from '@pionjs/pion';
7
8
  import { ref } from 'lit-html/directives/ref.js';
9
+ import { styleMap } from 'lit-html/directives/style-map.js';
8
10
  import style, { styles } from './style.css';
9
11
  import { properties, useListbox } from './use-listbox';
10
- import { connectable } from '@neovici/cosmoz-dropdown/connectable';
11
- import { styleMap } from 'lit-html/directives/style-map.js';
12
- const Listbox = (props) => {
12
+ const Listbox = (host) => {
13
13
  const listRef = useRef(undefined);
14
- const { position, items, renderItem, height, itemHeight } = useListbox(props);
14
+ const { position, items, renderItem, height, itemHeight, setItemHeight } = useListbox(host);
15
+ useEffect(() => {
16
+ const vl = listRef.current?.[virtualizerRef];
17
+ if (!vl)
18
+ return;
19
+ vl.layoutComplete.then(() => {
20
+ host.dispatchEvent(new CustomEvent('layout-complete'));
21
+ return setItemHeight(vl['_layout']._metricsCache.averageChildSize);
22
+ });
23
+ }, [items]);
15
24
  useEffect(() => {
16
25
  if (!position.scroll)
17
26
  return;
18
27
  const vl = listRef.current?.[virtualizerRef];
19
- if (!vl?.['_layout'])
28
+ if (!vl)
29
+ return;
30
+ if (!vl?.['_layout']) {
31
+ vl.layoutComplete.then(() => vl.element(position.index)?.scrollIntoView({ block: 'nearest' }));
20
32
  return;
33
+ }
21
34
  vl.element(position.index)?.scrollIntoView({ block: 'nearest' });
22
35
  }, [position]);
23
- useStyleSheet(styles({ ...position, height, itemHeight }));
24
- const layout = useMemo(() => ({
25
- _itemSize: { height: itemHeight - 0.00001 },
26
- }), [itemHeight]);
36
+ useStyleSheet(styles({ ...position, itemHeight, auto: host.itemHeight === 'auto' }));
27
37
  return html `<div
28
38
  class="items"
29
39
  style="min-height: ${height}px"
@@ -34,7 +44,6 @@ const Listbox = (props) => {
34
44
  items,
35
45
  renderItem,
36
46
  scroller: true,
37
- layout,
38
47
  })}
39
48
  </div>
40
49
  <slot></slot>`;
@@ -1,5 +1,7 @@
1
1
  import { TemplateResult } from 'lit-html';
2
- export interface Opts<I> {
2
+ import { Position } from './use-items';
3
+ export interface ItemRendererOpts<I> {
4
+ position: Position;
3
5
  highlight: (i: number) => void;
4
6
  select: (item: I) => void;
5
7
  isSelected: (item: I) => void;
@@ -7,19 +9,7 @@ export interface Opts<I> {
7
9
  textual: (i: I) => string;
8
10
  }
9
11
  export type Render<I> = (content: unknown, item: I, i: number) => unknown;
10
- export type ItemRenderer<I> = (item: I, i: number, opts: Opts<I>) => TemplateResult;
11
- export declare const itemRenderer: <I>(render?: Render<I>) => (item: I, i: number, { highlight, select, textual, query, isSelected, }: {
12
- highlight: (i: number) => void;
13
- select: (item: I) => void;
14
- isSelected: (item: I) => void;
15
- query: string;
16
- textual: (i: I) => string;
17
- }) => TemplateResult;
18
- declare const _default: (item: unknown, i: number, { highlight, select, textual, query, isSelected, }: {
19
- highlight: (i: number) => void;
20
- select: (item: unknown) => void;
21
- isSelected: (item: unknown) => void;
22
- query: string;
23
- textual: (i: unknown) => string;
24
- }) => TemplateResult;
12
+ export type ItemRenderer<I> = (item: I, i: number, opts: ItemRendererOpts<I>) => TemplateResult;
13
+ export declare const itemRenderer: <I>(render?: Render<I>) => (item: I, i: number, { highlight, select, textual, query, isSelected, }: ItemRendererOpts<I>) => TemplateResult;
14
+ declare const _default: (item: unknown, i: number, { highlight, select, textual, query, isSelected, }: ItemRendererOpts<unknown>) => TemplateResult;
25
15
  export default _default;
@@ -3,19 +3,18 @@ import { html } from 'lit-html';
3
3
  import { mark } from './util';
4
4
  export const itemRenderer = (render = identity) => (item, i, { highlight, select, textual = identity, query, isSelected, }) => {
5
5
  const text = textual(item), content = mark(text, query), rendered = render(content, item, i);
6
- return html ` <div
7
- class="item"
8
- role="option"
9
- part="option"
10
- ?aria-selected=${isSelected(item)}
11
- data-index=${i}
12
- @mouseenter=${() => highlight(i)}
13
- @click=${() => select(item)}
14
- @mousedown=${(e) => e.preventDefault()}
15
- title=${text}
16
- >
17
- ${rendered}
18
- </div>
19
- <div class="sizer" virtualizer-sizer>${rendered}</div>`;
6
+ return html `<div
7
+ class="item"
8
+ role="option"
9
+ part="option"
10
+ ?aria-selected=${isSelected(item)}
11
+ data-index=${i}
12
+ @mouseenter=${() => highlight(i)}
13
+ @click=${() => select(item)}
14
+ @mousedown=${(e) => e.preventDefault()}
15
+ title=${text}
16
+ >
17
+ ${rendered}
18
+ </div>`;
20
19
  };
21
20
  export default itemRenderer();
@@ -1,7 +1,7 @@
1
1
  declare const style: string;
2
2
  export default style;
3
- export declare const styles: ({ index, height, itemHeight, }: {
3
+ export declare const styles: ({ index, itemHeight, auto }: {
4
4
  index?: number;
5
- height: number;
6
5
  itemHeight: number;
6
+ auto: boolean;
7
7
  }) => string;
@@ -1,4 +1,5 @@
1
1
  import { tagged as css } from '@neovici/cosmoz-utils';
2
+ import { when } from 'lit-html/directives/when.js';
2
3
  const svg =
3
4
  /* eslint-disable quotes */
4
5
  "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 " +
@@ -9,10 +10,7 @@ const style = css `
9
10
  position: fixed;
10
11
  z-index: 1000;
11
12
  font-family: var(--paper-font-subhead_-_font-family, inherit);
12
- background: var(
13
- --cosmoz-autocomplete-listbox-bg,
14
- rgba(255, 255, 255, 0.85)
15
- );
13
+ background: var(--cosmoz-autocomplete-listbox-bg, rgba(255, 255, 255, 0.2));
16
14
  min-width: 50px;
17
15
  backdrop-filter: blur(16px) saturate(180%);
18
16
  -webkit-backdrop-filter: blur(16px) saturate(180%);
@@ -39,6 +37,7 @@ const style = css `
39
37
  }
40
38
  .item {
41
39
  font-size: var(--cosmoz-autocomplete-listbox-font-size, 13px);
40
+ font-weight: var(--cosmoz-autocomplete-listbox-font-weight, 400);
42
41
  padding: 0 22px;
43
42
  box-sizing: border-box;
44
43
  width: 100%;
@@ -50,20 +49,6 @@ const style = css `
50
49
  overflow: hidden;
51
50
  }
52
51
 
53
- .sizer {
54
- position: relative;
55
- visibility: hidden;
56
- opacity: 0;
57
- pointer-events: none;
58
- z-index: -1;
59
- height: 0;
60
- width: auto;
61
- padding: 0 20px;
62
- overflow: hidden;
63
- max-width: inherit;
64
- font-size: 14px;
65
- }
66
-
67
52
  :host(:not([multi])) .item[aria-selected] {
68
53
  background: var(--cosmoz-listbox-single-selection-color, #dadada);
69
54
  }
@@ -86,9 +71,6 @@ const style = css `
86
71
  /* prettier-ignore */
87
72
  background: url("${svg}") #5881f6 no-repeat 50%;
88
73
  }
89
- :host([multi]) .sizer {
90
- padding-left: 33px;
91
- }
92
74
  .swatch {
93
75
  width: 18px;
94
76
  height: 18px;
@@ -96,23 +78,19 @@ const style = css `
96
78
  box-sizing: border-box;
97
79
  vertical-align: middle;
98
80
  border-radius: 50%;
99
- border: 2px solid #eee;
100
81
  }
101
- [virtualizer-sizer]:not(.sizer) {
82
+ [virtualizer-sizer] {
102
83
  line-height: 1;
103
84
  }
104
85
  `;
105
86
  export default style;
106
- export const styles = ({ index, height, itemHeight, }) => css `
107
- :host {
108
- xmin-height: ${itemHeight}px;
109
- xheight: ${height}px;
110
- }
111
-
112
- .item {
113
- line-height: ${itemHeight}px;
114
- height: ${itemHeight}px;
115
- }
87
+ export const styles = ({ index, itemHeight, auto }) => css `
88
+ ${when(!auto, () => css `
89
+ .item {
90
+ line-height: ${itemHeight}px;
91
+ height: ${itemHeight}px;
92
+ }
93
+ `)}
116
94
 
117
95
  .item[data-index='${index || '0'}'] {
118
96
  background: var(
@@ -0,0 +1 @@
1
+ export declare const useItemHeight: (initialItemHeight: number | "auto") => readonly [number, (v: number) => void];
@@ -0,0 +1,8 @@
1
+ import { useState } from '@pionjs/pion';
2
+ export const useItemHeight = (initialItemHeight) => {
3
+ const auto = initialItemHeight === 'auto', [itemHeight, setItemHeight] = useState(auto ? 40 : initialItemHeight);
4
+ return [
5
+ itemHeight,
6
+ (v) => (auto ? setItemHeight(v) : undefined),
7
+ ];
8
+ };
@@ -9,16 +9,17 @@ export interface Props<I> {
9
9
  query: string;
10
10
  textual: (i: I) => string;
11
11
  itemRenderer?: ItemRenderer<I>;
12
- itemHeight?: number;
12
+ itemHeight?: number | 'auto';
13
13
  itemLimit?: number;
14
14
  }
15
- export declare const useListbox: <I>({ value, valueProperty, items: _items, onSelect, defaultIndex, query, textual, itemRenderer, itemHeight, itemLimit, }: Props<I>) => {
15
+ export declare const useListbox: <I>({ value, valueProperty, items: _items, onSelect, defaultIndex, query, textual, itemRenderer, itemHeight: _itemHeight, itemLimit, }: Props<I>) => {
16
16
  position: import("./use-items").Position;
17
17
  items: I[];
18
18
  height: number;
19
19
  highlight: (index: number) => void;
20
20
  select: (item: I) => void;
21
21
  itemHeight: number;
22
+ setItemHeight: (v: number) => void;
22
23
  renderItem: (item: I, i: number) => import("lit-html").TemplateResult;
23
24
  };
24
25
  export { useRenderItem };
@@ -2,6 +2,7 @@ import { useMemo } from '@pionjs/pion';
2
2
  import { byValue } from './util';
3
3
  import { useItems } from './use-items';
4
4
  import { useRenderItem } from './use-render-item';
5
+ import { useItemHeight } from './use-item-height';
5
6
  export const properties = [
6
7
  'query',
7
8
  'items',
@@ -16,7 +17,7 @@ export const properties = [
16
17
  'valueProperty',
17
18
  'loading',
18
19
  ];
19
- export const useListbox = ({ value, valueProperty, items: _items, onSelect, defaultIndex, query, textual, itemRenderer, itemHeight = 40, itemLimit = 5, }) => {
20
+ export const useListbox = ({ value, valueProperty, items: _items, onSelect, defaultIndex, query, textual, itemRenderer, itemHeight: _itemHeight = 40, itemLimit = 5, }) => {
20
21
  const isSelected = useMemo(() => byValue(value, valueProperty), [value, valueProperty]),
21
22
  // TODO: investigate if we can drop this
22
23
  items = useMemo(() => _items.slice(), [_items, isSelected]), { position, highlight, select } = useItems({
@@ -25,7 +26,7 @@ export const useListbox = ({ value, valueProperty, items: _items, onSelect, defa
25
26
  defaultIndex: isNaN(defaultIndex)
26
27
  ? undefined
27
28
  : Number(defaultIndex),
28
- });
29
+ }), [itemHeight, setItemHeight] = useItemHeight(_itemHeight);
29
30
  return {
30
31
  position,
31
32
  items,
@@ -33,8 +34,10 @@ export const useListbox = ({ value, valueProperty, items: _items, onSelect, defa
33
34
  highlight,
34
35
  select,
35
36
  itemHeight,
37
+ setItemHeight,
36
38
  renderItem: useRenderItem({
37
39
  itemRenderer,
40
+ position,
38
41
  highlight,
39
42
  select,
40
43
  textual,
@@ -1,5 +1,5 @@
1
- import { ItemRenderer, Opts } from './item-renderer';
2
- export declare const useRenderItem: <I>({ itemRenderer, ...meta }: Opts<I> & {
1
+ import { ItemRenderer, ItemRendererOpts } from './item-renderer';
2
+ export declare const useRenderItem: <I>({ itemRenderer, ...meta }: ItemRendererOpts<I> & {
3
3
  itemRenderer?: ItemRenderer<I>;
4
4
  }) => (item: I, i: number) => import("lit-html").TemplateResult;
5
5
  export type { ItemRenderer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-autocomplete",
3
- "version": "10.2.3",
3
+ "version": "10.4.0",
4
4
  "description": "A autocomplete input web component",
5
5
  "keywords": [
6
6
  "lit-html",