@neovici/cosmoz-autocomplete 9.0.2 → 10.0.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/README.md CHANGED
@@ -1,5 +1,4 @@
1
- cosmoz-autocomplete
2
- ==================
1
+ # cosmoz-autocomplete
3
2
 
4
3
  [![Build Status](https://github.com/Neovici/cosmoz-autocomplete/workflows/Github%20CI/badge.svg)](https://github.com/Neovici/cosmoz-autocomplete/actions?workflow=Github+CI)
5
4
  [![Depfu](https://badges.depfu.com/badges/c887733c2e1b6c70285860279a80fa03/overview.svg)](https://depfu.com/github/Neovici/cosmoz-autocomplete?project_id=9639)
@@ -16,13 +15,14 @@ cosmoz-autocomplete
16
15
  ### Installing
17
16
 
18
17
  Using npm:
18
+
19
19
  ```bash
20
20
  npm install --save @neovici/cosmoz-autocomplete
21
21
  ```
22
22
 
23
23
  ### Usage
24
24
 
25
- ``` javascript
25
+ ```javascript
26
26
  import { html } from 'lit-html';
27
- html`<cosmoz-autocomplete .source=${["A", "B","C", "D"]} />`;
27
+ html`<cosmoz-autocomplete .source=${['A', 'B', 'C', 'D']} />`;
28
28
  ```
@@ -2,6 +2,7 @@ import '@neovici/cosmoz-input';
2
2
  import './skeleton-span';
3
3
  import style from './styles.css';
4
4
  import { Props as Base, RProps } from './use-autocomplete';
5
+ import { Placement } from '@neovici/cosmoz-dropdown/use-floating';
5
6
  export interface Props<I> extends Base<I> {
6
7
  invalid?: boolean;
7
8
  errorMessage?: string;
@@ -14,6 +15,7 @@ export interface Props<I> extends Base<I> {
14
15
  itemLimit?: number;
15
16
  wrap?: boolean;
16
17
  defaultIndex?: number;
18
+ placement?: Placement;
17
19
  }
18
20
  type AProps<I> = Omit<Props<I>, keyof RProps<I>> & RProps<I> & {
19
21
  onInputRef?: (el?: Element) => void;
@@ -2,7 +2,6 @@ import '@neovici/cosmoz-input';
2
2
  import './skeleton-span';
3
3
  import { useHost } from '@neovici/cosmoz-utils/hooks/use-host';
4
4
  import { useImperativeApi } from '@neovici/cosmoz-utils/hooks/use-imperative-api';
5
- import { useCallback } from '@pionjs/pion';
6
5
  import { html, nothing } from 'lit-html'; // eslint-disable-line object-curly-newline
7
6
  import { live } from 'lit-html/directives/live.js';
8
7
  import { until } from 'lit-html/directives/until.js';
@@ -12,21 +11,36 @@ import { selection } from './selection';
12
11
  import style from './styles.css';
13
12
  import { useAutocomplete } from './use-autocomplete';
14
13
  import { useOverflow } from './use-overflow';
14
+ import { ref } from 'lit-html/directives/ref.js';
15
+ import { useFloating, defaultMiddleware, size, } from '@neovici/cosmoz-dropdown/use-floating';
15
16
  const inputParts = ['input', 'control', 'label', 'line', 'error', 'wrap']
16
17
  .map((part) => `${part}: input-${part}`)
17
18
  .join();
18
19
  const blank = () => nothing;
20
+ const middleware = [
21
+ size({
22
+ apply({ rects, elements }) {
23
+ Object.assign(elements.floating.style, {
24
+ minWidth: `${Math.max(rects.reference.width, rects.floating.width)}px`,
25
+ });
26
+ },
27
+ }),
28
+ ...defaultMiddleware,
29
+ ];
19
30
  const autocomplete = (props) => {
20
- const { active, invalid, errorMessage, label, placeholder, disabled, noLabelFloat, alwaysFloatLabel, textual, text, onText, onFocus, onClick, onDeselect, value, limit, min, showSingle, items, source$, } = props, host = useHost(), isOne = limit == 1, // eslint-disable-line eqeqeq
21
- isSingle = isOne && value?.[0] != null,
22
- // TODO: Refactor with ref or a state callback
23
- anchor = useCallback(() => host.shadowRoot.querySelector('#input'), [host, value]);
31
+ const { active, invalid, errorMessage, label, placeholder, disabled, noLabelFloat, alwaysFloatLabel, textual, text, onText, onFocus, onClick, onDeselect, value, limit, min, showSingle, items, source$, placement, } = props, host = useHost(), isOne = limit == 1, // eslint-disable-line eqeqeq
32
+ isSingle = isOne && value?.[0] != null;
33
+ const { styles, setReference, setFloating } = useFloating({
34
+ placement,
35
+ middleware,
36
+ });
24
37
  useImperativeApi({
25
38
  focus: () => host.shadowRoot?.querySelector('#input')?.focus(),
26
39
  }, []);
27
40
  return html `<cosmoz-input
28
41
  id="input"
29
42
  part="input"
43
+ ${ref(setReference)}
30
44
  .label=${label}
31
45
  .placeholder=${isSingle ? undefined : placeholder}
32
46
  ?no-label-float=${noLabelFloat}
@@ -59,9 +73,10 @@ const autocomplete = (props) => {
59
73
 
60
74
  ${when(active && !(isSingle && !showSingle), () => listbox({
61
75
  ...props,
62
- anchor,
63
76
  items,
64
77
  multi: !isOne,
78
+ setFloating,
79
+ styles,
65
80
  }, when(items.length < 5, () => until(source$.then(blank, blank), html `<cosmoz-autocomplete-skeleton-span></cosmoz-autocomplete-skeleton-span>`))))}`;
66
81
  }, Autocomplete = (props) => {
67
82
  const thru = {
@@ -1,11 +1,23 @@
1
1
  import { useCallback } from '@pionjs/pion';
2
+ /**
3
+ * @see https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
4
+ * @see https://medium.com/nerd-for-tech/basics-of-javascript-string-normalize-method-f3241174c2d0
5
+ * @see https://www.codu.co/articles/remove-accents-from-a-javascript-string-skgp1inb
6
+ *
7
+ * @param {string} str string from which to remove accents
8
+ * @returns {string} string w/o accents
9
+ */
10
+ const removeAccents = (str) => {
11
+ return str.normalize('NFD').replace(/[\u0300-\u036f]/gu, '');
12
+ };
2
13
  export const search = (source, query, textual) => {
3
14
  if (!query)
4
15
  return source;
5
- const qry = query.toLowerCase();
16
+ const normalizedQuery = removeAccents(query.toLowerCase());
6
17
  const matches = [];
7
18
  for (const item of source) {
8
- const index = textual(item).toLowerCase().indexOf(qry);
19
+ const normalizedText = removeAccents(textual(item).toLowerCase());
20
+ const index = normalizedText.indexOf(normalizedQuery);
9
21
  if (index < 0) {
10
22
  continue;
11
23
  }
@@ -1,4 +1,9 @@
1
1
  import { Props } from './use-listbox';
2
- export declare const listbox: <I>({ multi, ...thru }: Props<I> & {
2
+ import { StyleInfo } from 'lit-html/directives/style-map.js';
3
+ interface ListboxProps<I> extends Props<I> {
3
4
  multi?: boolean;
4
- }, content: unknown) => import("lit-html").TemplateResult<1>;
5
+ setFloating: (el?: Element) => void;
6
+ styles: StyleInfo;
7
+ }
8
+ export declare const listbox: <I>({ multi, setFloating, styles, ...thru }: ListboxProps<I>, content: unknown) => import("lit-html").TemplateResult<1>;
9
+ export {};
@@ -7,6 +7,8 @@ import { component, html, useEffect, useMemo, useRef } from '@pionjs/pion';
7
7
  import { ref } from 'lit-html/directives/ref.js';
8
8
  import style, { styles } from './style.css';
9
9
  import { properties, useListbox } from './use-listbox';
10
+ import { connectable } from '@neovici/cosmoz-dropdown/connectable';
11
+ import { styleMap } from 'lit-html/directives/style-map.js';
10
12
  const Listbox = (props) => {
11
13
  const listRef = useRef(undefined);
12
14
  const { position, items, renderItem, height, itemHeight } = useListbox(props);
@@ -24,8 +26,8 @@ const Listbox = (props) => {
24
26
  }), [itemHeight]);
25
27
  return html `<div
26
28
  class="items"
27
- ${ref((el) => (listRef.current = el))}
28
29
  style="min-height: ${height}px"
30
+ ${ref((el) => (listRef.current = el))}
29
31
  >
30
32
  <div virtualizer-sizer></div>
31
33
  ${virtualize({
@@ -37,24 +39,14 @@ const Listbox = (props) => {
37
39
  </div>
38
40
  <slot></slot>`;
39
41
  };
40
- const supportsPopover = () => {
41
- // eslint-disable-next-line no-prototype-builtins
42
- return HTMLElement.prototype.hasOwnProperty('popover');
43
- };
44
- const showPopover = (popover) => {
45
- const popoverElement = popover;
46
- if (supportsPopover()) {
47
- requestAnimationFrame(() => {
48
- popoverElement?.showPopover();
49
- });
50
- }
51
- };
52
- customElements.define('cosmoz-listbox', component(Listbox, { styleSheets: [sheet(style)] }));
53
- export const listbox = ({ multi, ...thru }, content) => html `<cosmoz-listbox
54
- ${ref(showPopover)}
42
+ customElements.define('cosmoz-listbox', connectable(component(Listbox, { styleSheets: [sheet(style)] })));
43
+ export const listbox = ({ multi, setFloating, styles, ...thru }, content) => html `<cosmoz-listbox
44
+ style="${styleMap(styles)}"
45
+ @connected="${(e) => e.target.showPopover?.()}"
55
46
  popover="manual"
56
47
  part="listbox"
57
48
  ?multi=${multi}
49
+ ${ref(setFloating)}
58
50
  ...=${spreadProps(props(properties)(thru))}
59
51
  >${content}</cosmoz-listbox
60
52
  >`;
@@ -1,4 +1,3 @@
1
- import { Placement } from '@neovici/cosmoz-dropdown/use-position';
2
1
  import { useRenderItem, ItemRenderer } from './use-render-item';
3
2
  export declare const properties: string[];
4
3
  export interface Props<I> {
@@ -12,11 +11,8 @@ export interface Props<I> {
12
11
  itemRenderer?: ItemRenderer<I>;
13
12
  itemHeight?: number;
14
13
  itemLimit?: number;
15
- anchor?: () => HTMLElement | null;
16
- confinement?: HTMLElement;
17
- placement?: Placement;
18
14
  }
19
- export declare const useListbox: <I>({ value, valueProperty, items: _items, onSelect, defaultIndex, query, textual, itemRenderer, itemHeight, itemLimit, ...thru }: Props<I>) => {
15
+ export declare const useListbox: <I>({ value, valueProperty, items: _items, onSelect, defaultIndex, query, textual, itemRenderer, itemHeight, itemLimit, }: Props<I>) => {
20
16
  position: import("./use-items").Position;
21
17
  items: I[];
22
18
  height: number;
@@ -1,6 +1,4 @@
1
1
  import { useMemo } from '@pionjs/pion';
2
- import { useHost } from '@neovici/cosmoz-utils/hooks/use-host';
3
- import { usePosition } from '@neovici/cosmoz-dropdown/use-position';
4
2
  import { byValue } from './util';
5
3
  import { useItems } from './use-items';
6
4
  import { useRenderItem } from './use-render-item';
@@ -10,8 +8,6 @@ export const properties = [
10
8
  'onSelect',
11
9
  'textual',
12
10
  'anchor',
13
- 'confinement',
14
- 'placement',
15
11
  'itemHeight',
16
12
  'itemLimit',
17
13
  'itemRenderer',
@@ -20,7 +16,7 @@ export const properties = [
20
16
  'valueProperty',
21
17
  'loading',
22
18
  ];
23
- export const useListbox = ({ value, valueProperty, items: _items, onSelect, defaultIndex, query, textual, itemRenderer, itemHeight = 40, itemLimit = 5, ...thru }) => {
19
+ export const useListbox = ({ value, valueProperty, items: _items, onSelect, defaultIndex, query, textual, itemRenderer, itemHeight = 40, itemLimit = 5, }) => {
24
20
  const isSelected = useMemo(() => byValue(value, valueProperty), [value, valueProperty]),
25
21
  // TODO: investigate if we can drop this
26
22
  items = useMemo(() => _items.slice(), [_items, isSelected]), { position, highlight, select } = useItems({
@@ -30,11 +26,6 @@ export const useListbox = ({ value, valueProperty, items: _items, onSelect, defa
30
26
  ? undefined
31
27
  : Number(defaultIndex),
32
28
  });
33
- usePosition({
34
- host: useHost(),
35
- ...thru,
36
- limit: true,
37
- });
38
29
  return {
39
30
  position,
40
31
  items,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-autocomplete",
3
- "version": "9.0.2",
3
+ "version": "10.0.1",
4
4
  "description": "A autocomplete input web component",
5
5
  "keywords": [
6
6
  "lit-html",
@@ -28,6 +28,8 @@
28
28
  "test:watch": "wtr --watch",
29
29
  "e2e": "playwright test",
30
30
  "prepare": "husky",
31
+ "prettier": "prettier --write \"**/*.{js,ts,json,css,md}\"",
32
+ "prettier:check": "prettier --check \"**/*.{js,ts,json,css,md}\"",
31
33
  "storybook:start": "storybook dev -p 8000",
32
34
  "storybook:build": "storybook build",
33
35
  "storybook:deploy": "storybook-to-ghpages",
@@ -71,7 +73,7 @@
71
73
  },
72
74
  "dependencies": {
73
75
  "@lit-labs/virtualizer": "^2.0.0",
74
- "@neovici/cosmoz-dropdown": "^4.4.0",
76
+ "@neovici/cosmoz-dropdown": "^5.0.0",
75
77
  "@neovici/cosmoz-input": "^5.0.2",
76
78
  "@neovici/cosmoz-utils": "^6.0.0",
77
79
  "@pionjs/pion": "^2.7.1",
@@ -91,16 +93,16 @@
91
93
  "@storybook/storybook-deployer": "^2.8.5",
92
94
  "@storybook/web-components": "7.6.17",
93
95
  "@types/mocha": "^10.0.6",
94
- "@types/node": "^20.12.7",
96
+ "@types/node": "^22.10.2",
95
97
  "@web/storybook-builder": "^0.1.9",
96
98
  "@web/storybook-framework-web-components": "^0.1.1",
97
- "esbuild": "^0.21.0",
99
+ "esbuild": "^0.24.0",
98
100
  "http-server": "^14.1.1",
99
101
  "husky": "^9.0.0",
100
102
  "rollup-plugin-esbuild": "^6.1.1",
101
103
  "rollup-plugin-terser": "^7.0.2",
102
104
  "semantic-release": "^24.0.0",
103
- "sinon": "^18.0.0",
105
+ "sinon": "^19.0.0",
104
106
  "storybook": "^7.6.17"
105
107
  },
106
108
  "overrides": {