@neovici/cosmoz-autocomplete 8.0.0 → 8.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.
@@ -1,5 +1,6 @@
1
1
  import '@neovici/cosmoz-input';
2
2
  import { Props as Base, RProps } from './use-autocomplete';
3
+ import style from './styles.css';
3
4
  export interface Props<I> extends Base<I> {
4
5
  invalid?: boolean;
5
6
  errorMessage?: string;
@@ -15,4 +16,4 @@ type AProps<I> = Omit<Props<I>, keyof RProps<I>> & RProps<I> & {
15
16
  onInputRef?: (el?: Element) => void;
16
17
  };
17
18
  declare const autocomplete: <I>(props: AProps<I>) => import("lit-html").TemplateResult<1>, Autocomplete: <I>(props: Props<I>) => import("lit-html").TemplateResult<1>, observedAttributes: string[];
18
- export { autocomplete, Autocomplete, observedAttributes };
19
+ export { autocomplete, Autocomplete, observedAttributes, style };
@@ -16,7 +16,7 @@ const inputParts = ['input', 'control', 'label', 'line', 'error']
16
16
  .map((part) => `${part}: input-${part}`)
17
17
  .join();
18
18
  const autocomplete = (props) => {
19
- const { invalid, errorMessage, label, placeholder, disabled, noLabelFloat, alwaysFloatLabel, textual, text, onText, onFocus, onClick, onDeselect, value, limit, showSingle, items$, values$, } = props, host = useHost(), isOne = limit == 1, // eslint-disable-line eqeqeq
19
+ const { invalid, errorMessage, label, placeholder, disabled, noLabelFloat, alwaysFloatLabel, textual, text, onText, onFocus, onClick, onDeselect, value, limit, min, showSingle, items$, values$, } = props, host = useHost(), isOne = limit == 1, // eslint-disable-line eqeqeq
20
20
  isSingle = isOne && value?.[0] != null,
21
21
  // TODO: Refactor with ref or a state callback
22
22
  anchor = useCallback(() => host.shadowRoot.querySelector('#input'), [host, value]), suggestions = until(items$.then((items) => when((!isSingle || showSingle) && items.length, () => listbox({
@@ -28,9 +28,7 @@ const autocomplete = (props) => {
28
28
  useImperativeApi({
29
29
  focus: () => host.shadowRoot?.querySelector('#input')?.focus(),
30
30
  }, []);
31
- return html ` <style>
32
- ${style}
33
- </style>
31
+ return html `
34
32
  <cosmoz-input
35
33
  id="input"
36
34
  part="input"
@@ -56,6 +54,7 @@ const autocomplete = (props) => {
56
54
  <slot name="suffix" slot="suffix"></slot>
57
55
  ${selection({
58
56
  value,
57
+ min,
59
58
  isOne,
60
59
  onDeselect,
61
60
  textual,
@@ -80,7 +79,9 @@ const autocomplete = (props) => {
80
79
  'text-property',
81
80
  'value-property',
82
81
  'limit',
82
+ 'min',
83
83
  'show-single',
84
+ 'preserve-order',
84
85
  'keep-opened',
85
86
  'keep-query',
86
87
  'hide-empty',
@@ -89,4 +90,4 @@ const autocomplete = (props) => {
89
90
  'item-limit',
90
91
  'wrap',
91
92
  ];
92
- export { autocomplete, Autocomplete, observedAttributes };
93
+ export { autocomplete, Autocomplete, observedAttributes, style };
@@ -12,6 +12,7 @@ export default css `
12
12
  min-width: 40px;
13
13
  padding: 0 4px 0 8px;
14
14
  gap: 4px;
15
+ cursor: pointer;
15
16
  }
16
17
  .content {
17
18
  color: var(--cosmoz-autocomplete-chip-color, #424242);
@@ -1,5 +1,5 @@
1
1
  interface Props {
2
- onClear?: () => void;
2
+ onClear?: false | (() => void);
3
3
  disabled?: boolean;
4
4
  }
5
5
  export declare const Chip: ({ onClear, disabled }: Props) => import("lit-html").TemplateResult<1>;
@@ -1,6 +1,7 @@
1
- import { html, component } from '@pionjs/pion';
2
- import { when } from 'lit-html/directives/when.js';
1
+ import { sheet } from '@neovici/cosmoz-utils';
2
+ import { component, html } from '@pionjs/pion';
3
3
  import { ifDefined } from 'lit-html/directives/if-defined.js';
4
+ import { when } from 'lit-html/directives/when.js';
4
5
  import styles from './chip.css';
5
6
  const clear = html `
6
7
  <svg
@@ -27,15 +28,15 @@ const clear = html `
27
28
  </svg>
28
29
  `;
29
30
  export const Chip = ({ onClear, disabled }) => html `
30
- <style>
31
- ${styles}
32
- </style>
33
31
  <div class="content" part="content chip-text"><slot></slot></div>
34
- ${when(!disabled, () => html ` <span class="clear" part="clear chip-clear" @click=${onClear}>
32
+ ${when(onClear && !disabled, () => html ` <span class="clear" part="clear chip-clear" @click=${onClear}>
35
33
  ${clear}
36
34
  </span>`)}
37
35
  `;
38
- customElements.define('cosmoz-autocomplete-chip', component(Chip, { observedAttributes: ['disabled'] }));
36
+ customElements.define('cosmoz-autocomplete-chip', component(Chip, {
37
+ observedAttributes: ['disabled'],
38
+ styleSheets: [sheet(styles)],
39
+ }));
39
40
  export const chip = ({ content, onClear, disabled, hidden, className = 'chip', slot, }) => html `<cosmoz-autocomplete-chip
40
41
  class=${ifDefined(className)}
41
42
  slot=${ifDefined(slot)}
@@ -1,5 +1,6 @@
1
1
  import { component, useCallback } from '@pionjs/pion';
2
- import { Autocomplete, observedAttributes as atts, } from './autocomplete';
2
+ import { Autocomplete, observedAttributes as atts, style, } from './autocomplete';
3
+ import { sheet } from '@neovici/cosmoz-utils';
3
4
  const Standalone = (host) => {
4
5
  const { onChange, onText, ...props } = host;
5
6
  return Autocomplete({
@@ -14,6 +15,6 @@ const Standalone = (host) => {
14
15
  }, [onText]),
15
16
  });
16
17
  };
17
- const observedAttributes = atts;
18
- customElements.define('cosmoz-autocomplete-ui', component(Autocomplete, { observedAttributes }));
19
- customElements.define('cosmoz-autocomplete', component(Standalone, { observedAttributes }));
18
+ const observedAttributes = atts, styleSheets = [sheet(style)];
19
+ customElements.define('cosmoz-autocomplete-ui', component(Autocomplete, { observedAttributes, styleSheets }));
20
+ customElements.define('cosmoz-autocomplete', component(Standalone, { observedAttributes, styleSheets }));
@@ -2,10 +2,11 @@ import type { RProps } from './use-autocomplete';
2
2
  type Deselect<I> = RProps<I>['onDeselect'];
3
3
  interface Props<I> {
4
4
  value: I[];
5
+ min?: number;
5
6
  isOne: boolean;
6
7
  onDeselect: Deselect<I>;
7
8
  textual: (i: I) => string;
8
9
  disabled?: boolean;
9
10
  }
10
- export declare const selection: <I>({ value: values, onDeselect, textual, disabled, }: Props<I>) => import("lit-html").TemplateResult<1>[];
11
+ export declare const selection: <I>({ value: values, min, onDeselect, textual, disabled, }: Props<I>) => import("lit-html").TemplateResult<1>[];
11
12
  export {};
@@ -1,9 +1,9 @@
1
1
  import { html } from 'lit-html';
2
2
  import { chip } from './chip';
3
- export const selection = ({ value: values, onDeselect, textual, disabled, }) => [
3
+ export const selection = ({ value: values, min = 0, onDeselect, textual, disabled, }) => [
4
4
  ...values.filter(Boolean).map((value) => chip({
5
5
  content: textual(value),
6
- onClear: () => onDeselect(value),
6
+ onClear: values.length > min && (() => onDeselect(value)),
7
7
  disabled,
8
8
  slot: 'control',
9
9
  })),
@@ -5,6 +5,7 @@ type Source<I> = (opts: {
5
5
  interface Base<I> {
6
6
  value: I | I[];
7
7
  limit?: number;
8
+ min?: number;
8
9
  keepOpened?: boolean;
9
10
  keepQuery?: boolean;
10
11
  onText: (text: string) => void;
@@ -25,8 +26,9 @@ export interface Props<I> extends Base<I> {
25
26
  hideEmpty?: boolean;
26
27
  disabled?: boolean;
27
28
  onFocus?: (focused?: boolean) => void;
29
+ preserveOrder?: boolean;
28
30
  }
29
- export declare const useAutocomplete: <I>({ value: _value, text, onChange: _onChange, onText: _onText, onSelect, limit, source, textProperty, textual: _textual, valueProperty, external, hideEmpty, keepOpened, keepQuery, ...thru }: Props<I>) => {
31
+ export declare const useAutocomplete: <I>({ value: _value, text, onChange: _onChange, onText: _onText, onSelect, limit, min, source, textProperty, textual: _textual, valueProperty, external, hideEmpty, keepOpened, keepQuery, preserveOrder, ...thru }: Props<I>) => {
30
32
  active: boolean | undefined;
31
33
  query: string;
32
34
  textual: (i: I) => string;
@@ -6,16 +6,16 @@ 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, source, textProperty, textual: _textual, valueProperty, external, hideEmpty, keepOpened, keepQuery, ...thru }) => {
9
+ export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onText: _onText, onSelect, limit, min, source, textProperty, textual: _textual, valueProperty, external, hideEmpty, keepOpened, keepQuery, preserveOrder, ...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);
13
- }, [_onChange]), source$ = useMemo(() => Promise.resolve(typeof source === 'function' ? source({ query, active }) : source), [source, active, query]), value = useMemo(() => array(_value), [_value]), values$ = useMemo(() => source$.then((source) => {
14
- return [
13
+ }, [_onChange]), source$ = useMemo(() => Promise.resolve(typeof source === 'function' ? source({ query, active }) : source), [source, active, query]), value = useMemo(() => array(_value), [_value]), values$ = useMemo(() => source$.then((source) => preserveOrder
14
+ ? normalize(source)
15
+ : [
15
16
  ...value,
16
17
  ...without(value, prop(valueProperty))(normalize(source)),
17
- ];
18
- }), [source$, value, valueProperty]);
18
+ ]), [source$, value, valueProperty]);
19
19
  useKeys({
20
20
  focused,
21
21
  empty,
@@ -34,6 +34,7 @@ export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onTe
34
34
  onChange,
35
35
  value,
36
36
  limit,
37
+ min,
37
38
  keepQuery,
38
39
  keepOpened,
39
40
  setClosed,
@@ -61,13 +62,15 @@ export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onTe
61
62
  }, [onText, text, setClosed]),
62
63
  onSelect: useCallback((newVal) => {
63
64
  meta.onSelect?.(newVal, meta);
64
- const { onChange, onText, limit, value: val, keepQuery, keepOpened, setClosed, } = meta;
65
+ const { onChange, onText, limit, min, value: val, keepQuery, keepOpened, setClosed, } = meta;
65
66
  if (!keepQuery)
66
67
  onText('');
67
68
  if (!keepOpened)
68
69
  setClosed(true);
69
- const value = array(val);
70
- onChange((value.includes(newVal)
70
+ const value = array(val), deselect = value.includes(newVal);
71
+ if (deselect && value.length === min)
72
+ return;
73
+ onChange((deselect
71
74
  ? without(newVal)(value)
72
75
  : [...value, newVal]).slice(-limit));
73
76
  }, [meta]),
@@ -1,11 +1,13 @@
1
- import { html, component, useEffect, useRef, useMemo } from '@pionjs/pion';
2
- import { ref } from 'lit-html/directives/ref.js';
3
1
  import { virtualize, virtualizerRef, } from '@lit-labs/virtualizer/virtualize.js';
2
+ import { sheet } from '@neovici/cosmoz-utils';
4
3
  import { portal } from '@neovici/cosmoz-utils/directives/portal';
5
4
  import { spreadProps } from '@neovici/cosmoz-utils/directives/spread-props';
5
+ import { useStyleSheet } from '@neovici/cosmoz-utils/hooks/use-stylesheet';
6
6
  import { props } from '@neovici/cosmoz-utils/object';
7
- import { useListbox, properties } from './use-listbox';
8
- import { styles } from './style.css';
7
+ import { component, html, useEffect, useMemo, useRef } from '@pionjs/pion';
8
+ import { ref } from 'lit-html/directives/ref.js';
9
+ import style, { styles } from './style.css';
10
+ import { properties, useListbox } from './use-listbox';
9
11
  const Listbox = (props) => {
10
12
  const listRef = useRef(undefined);
11
13
  const { position, items, renderItem, height, itemHeight } = useListbox(props);
@@ -17,23 +19,21 @@ const Listbox = (props) => {
17
19
  return;
18
20
  vl.element(position.index)?.scrollIntoView({ block: 'nearest' });
19
21
  }, [position]);
22
+ useStyleSheet(styles({ ...position, height, itemHeight }));
20
23
  const layout = useMemo(() => ({
21
24
  _itemSize: { height: itemHeight - 0.00001 },
22
25
  }), [itemHeight]);
23
- return html ` <style>
24
- ${styles({ ...position, height, itemHeight })}
25
- </style>
26
- <div class="items" ${ref((el) => (listRef.current = el))}>
27
- <div virtualizer-sizer></div>
28
- ${virtualize({
26
+ return html `<div class="items" ${ref((el) => (listRef.current = el))}>
27
+ <div virtualizer-sizer></div>
28
+ ${virtualize({
29
29
  items,
30
30
  renderItem,
31
31
  scroller: true,
32
32
  layout,
33
33
  })}
34
- </div>`;
34
+ </div>`;
35
35
  };
36
- customElements.define('cosmoz-listbox', component(Listbox));
36
+ customElements.define('cosmoz-listbox', component(Listbox, { styleSheets: [sheet(style)] }));
37
37
  export const listbox = ({ multi, ...thru }) => portal(html `<cosmoz-listbox
38
38
  part="listbox"
39
39
  ?multi=${multi}
@@ -84,8 +84,6 @@ const style = css `
84
84
  `;
85
85
  export default style;
86
86
  export const styles = ({ index, height, itemHeight, }) => css `
87
- ${style}
88
-
89
87
  :host {
90
88
  min-height: ${itemHeight}px;
91
89
  height: ${height}px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-autocomplete",
3
- "version": "8.0.0",
3
+ "version": "8.2.0",
4
4
  "description": "A autocomplete input web component",
5
5
  "keywords": [
6
6
  "lit-html",