@neovici/cosmoz-autocomplete 11.0.0 → 12.0.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/dist/autocomplete/autocomplete-keybindings.d.ts +31 -0
- package/dist/autocomplete/autocomplete-keybindings.js +14 -0
- package/dist/autocomplete/autocomplete.d.ts +1 -2
- package/dist/autocomplete/autocomplete.js +17 -47
- package/dist/autocomplete/index.js +3 -2
- package/dist/autocomplete/styles.css.js +4 -0
- package/dist/autocomplete/use-autocomplete.d.ts +4 -6
- package/dist/autocomplete/use-autocomplete.js +37 -26
- package/dist/autocomplete/use-overflow.d.ts +2 -2
- package/dist/autocomplete/use-overflow.js +2 -2
- package/dist/excluding/index.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/listbox/index.d.ts +1 -4
- package/dist/listbox/index.js +2 -8
- package/dist/listbox/listbox-keybindings.d.ts +26 -0
- package/dist/listbox/listbox-keybindings.js +23 -0
- package/dist/listbox/style.css.js +2 -32
- package/dist/listbox/use-keyboard.d.ts +1 -1
- package/dist/listbox/use-keyboard.js +20 -31
- package/package.json +5 -3
- package/dist/autocomplete/use-keys.d.ts +0 -9
- package/dist/autocomplete/use-keys.js +0 -23
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Activity } from '@neovici/cosmoz-utils/keybindings';
|
|
2
|
+
export declare const AUTOCOMPLETE_DESELECT_LAST: Activity;
|
|
3
|
+
export declare const autocompleteKeybindings: readonly [readonly [{
|
|
4
|
+
readonly key: "ArrowUp";
|
|
5
|
+
}, [symbol], {
|
|
6
|
+
readonly title: "Navigate up";
|
|
7
|
+
readonly description: "Move to previous listbox item";
|
|
8
|
+
}, {
|
|
9
|
+
readonly allowInEditable: true;
|
|
10
|
+
}], readonly [{
|
|
11
|
+
readonly key: "ArrowDown";
|
|
12
|
+
}, [symbol], {
|
|
13
|
+
readonly title: "Navigate down";
|
|
14
|
+
readonly description: "Move to next listbox item";
|
|
15
|
+
}, {
|
|
16
|
+
readonly allowInEditable: true;
|
|
17
|
+
}], readonly [{
|
|
18
|
+
readonly key: "Enter";
|
|
19
|
+
}, [symbol], {
|
|
20
|
+
readonly title: "Select";
|
|
21
|
+
readonly description: "Select current listbox item";
|
|
22
|
+
}, {
|
|
23
|
+
readonly allowInEditable: true;
|
|
24
|
+
}], readonly [{
|
|
25
|
+
readonly key: "Backspace";
|
|
26
|
+
}, [symbol], {
|
|
27
|
+
readonly title: "Deselect last";
|
|
28
|
+
readonly description: "Remove the last selected item";
|
|
29
|
+
}, {
|
|
30
|
+
readonly allowInEditable: true;
|
|
31
|
+
}]];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { listboxKeybindings } from '../listbox/listbox-keybindings';
|
|
2
|
+
export const AUTOCOMPLETE_DESELECT_LAST = Symbol('autocomplete.deselect.last');
|
|
3
|
+
export const autocompleteKeybindings = [
|
|
4
|
+
...listboxKeybindings,
|
|
5
|
+
[
|
|
6
|
+
{ key: 'Backspace' },
|
|
7
|
+
[AUTOCOMPLETE_DESELECT_LAST],
|
|
8
|
+
{
|
|
9
|
+
title: 'Deselect last',
|
|
10
|
+
description: 'Remove the last selected item',
|
|
11
|
+
},
|
|
12
|
+
{ allowInEditable: true },
|
|
13
|
+
],
|
|
14
|
+
];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import '@neovici/cosmoz-dropdown/cosmoz-dropdown-next';
|
|
2
2
|
import '@neovici/cosmoz-input';
|
|
3
3
|
import { ItemRenderer } from '../listbox/item-renderer';
|
|
4
4
|
import { ChipRenderer } from './selection';
|
|
@@ -18,7 +18,6 @@ export interface Props<I> extends Base<I> {
|
|
|
18
18
|
wrap?: boolean;
|
|
19
19
|
defaultIndex?: number;
|
|
20
20
|
externalSearch?: boolean;
|
|
21
|
-
placement?: Placement;
|
|
22
21
|
itemRenderer?: ItemRenderer<I>;
|
|
23
22
|
chipRenderer?: ChipRenderer<I>;
|
|
24
23
|
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import '@neovici/cosmoz-dropdown/cosmoz-dropdown-next';
|
|
2
2
|
import '@neovici/cosmoz-input';
|
|
3
|
-
import { useHost } from '@neovici/cosmoz-utils/hooks/use-host';
|
|
4
|
-
import { useImperativeApi } from '@neovici/cosmoz-utils/hooks/use-imperative-api';
|
|
5
|
-
import { useEffect } from '@pionjs/pion';
|
|
6
3
|
import { t } from 'i18next';
|
|
7
4
|
import { html } from 'lit-html';
|
|
8
5
|
import { live } from 'lit-html/directives/live.js';
|
|
9
|
-
import { ref } from 'lit-html/directives/ref.js';
|
|
10
6
|
import { until } from 'lit-html/directives/until.js';
|
|
11
7
|
import { when } from 'lit-html/directives/when.js';
|
|
12
8
|
import { listbox } from '../listbox';
|
|
@@ -18,45 +14,25 @@ import { useOverflow } from './use-overflow';
|
|
|
18
14
|
const inputParts = ['input', 'control', 'label', 'line', 'error', 'wrap']
|
|
19
15
|
.map((part) => `${part}: input-${part}`)
|
|
20
16
|
.join();
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
minWidth: `${Math.max(rects.reference.width, rects.floating.width)}px`,
|
|
26
|
-
});
|
|
27
|
-
},
|
|
28
|
-
}),
|
|
29
|
-
...defaultMiddleware,
|
|
30
|
-
];
|
|
31
|
-
const shouldShowDropdown = ({ active, isSingle, showSingle, }) => {
|
|
32
|
-
if (!active)
|
|
17
|
+
const shouldShowDropdown = ({ opened, isSingle, showSingle, hasResultsOrQuery, }) => {
|
|
18
|
+
if (!opened)
|
|
19
|
+
return false;
|
|
20
|
+
if (isSingle && !showSingle)
|
|
33
21
|
return false;
|
|
34
|
-
|
|
35
|
-
return !disallowedSingle;
|
|
22
|
+
return hasResultsOrQuery;
|
|
36
23
|
};
|
|
37
24
|
const autocomplete = (props) => {
|
|
38
|
-
const {
|
|
25
|
+
const { opened, invalid, errorMessage, label, placeholder, disabled, noLabelFloat, alwaysFloatLabel, textual, text, onText, onToggle, onDeselect, value, limit, min, showSingle, items, source$, loading, chipRenderer, } = props, isOne = limit == 1, // eslint-disable-line eqeqeq
|
|
39
26
|
isSingle = isOne && value?.[0] != null;
|
|
40
|
-
const { styles, setReference, setFloating } = useFloating({
|
|
41
|
-
placement,
|
|
42
|
-
middleware,
|
|
43
|
-
});
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
host.addEventListener('focusin', onFocus);
|
|
46
|
-
host.addEventListener('focusout', onFocus);
|
|
47
|
-
return () => {
|
|
48
|
-
host.removeEventListener('focusin', onFocus);
|
|
49
|
-
host.removeEventListener('focusout', onFocus);
|
|
50
|
-
};
|
|
51
|
-
}, [onFocus]);
|
|
52
|
-
useImperativeApi({
|
|
53
|
-
focus: () => host.shadowRoot?.querySelector('#input')?.focus(),
|
|
54
|
-
}, []);
|
|
55
27
|
const hasResultsOrQuery = loading || items.length > 0 || (text != null && text.length > 0);
|
|
56
|
-
return html `<cosmoz-
|
|
28
|
+
return html `<cosmoz-dropdown-next
|
|
29
|
+
open-on-focus
|
|
30
|
+
@dropdown-toggle=${onToggle}
|
|
31
|
+
>
|
|
32
|
+
<cosmoz-input
|
|
33
|
+
slot="button"
|
|
57
34
|
id="input"
|
|
58
35
|
part="input"
|
|
59
|
-
${ref(setReference)}
|
|
60
36
|
.label=${label}
|
|
61
37
|
.placeholder=${isSingle ? undefined : placeholder}
|
|
62
38
|
?no-label-float=${noLabelFloat}
|
|
@@ -67,7 +43,6 @@ const autocomplete = (props) => {
|
|
|
67
43
|
.errorMessage=${until(source$.then(() => errorMessage, (e) => e.message), errorMessage)}
|
|
68
44
|
.value=${live(text)}
|
|
69
45
|
@value-changed=${onText}
|
|
70
|
-
@click=${onClick}
|
|
71
46
|
autocomplete="off"
|
|
72
47
|
exportparts=${inputParts}
|
|
73
48
|
?data-one=${isOne}
|
|
@@ -87,23 +62,18 @@ const autocomplete = (props) => {
|
|
|
87
62
|
</cosmoz-input>
|
|
88
63
|
|
|
89
64
|
${when(shouldShowDropdown({
|
|
90
|
-
|
|
65
|
+
opened,
|
|
91
66
|
isSingle,
|
|
92
67
|
showSingle,
|
|
68
|
+
hasResultsOrQuery,
|
|
93
69
|
}), () => listbox({
|
|
94
70
|
...props,
|
|
95
71
|
items,
|
|
96
72
|
multi: !isOne,
|
|
97
|
-
setFloating,
|
|
98
|
-
styles: {
|
|
99
|
-
...styles,
|
|
100
|
-
// WORKAROUND: hide the listbox if there are no results, don't remove it from DOM
|
|
101
|
-
// TODO: revert https://github.com/Neovici/cosmoz-autocomplete/pull/206 after https://github.com/pionjs/pion/issues/64 is fixed
|
|
102
|
-
display: hasResultsOrQuery ? undefined : 'none',
|
|
103
|
-
},
|
|
104
73
|
}, when(loading, () => html `<cosmoz-autocomplete-skeleton-span></cosmoz-autocomplete-skeleton-span>`, () => when(text != null && text.length > 0 && items.length === 0, () => html `<slot name="no-result">
|
|
105
74
|
<p class="no-result">${t('No results found')}</p>
|
|
106
|
-
</slot>`))))}
|
|
75
|
+
</slot>`))))}
|
|
76
|
+
</cosmoz-dropdown-next>`;
|
|
107
77
|
}, Autocomplete = (props) => {
|
|
108
78
|
const thru = {
|
|
109
79
|
...props,
|
|
@@ -24,5 +24,6 @@ const Standalone = (host) => {
|
|
|
24
24
|
});
|
|
25
25
|
};
|
|
26
26
|
const styleSheets = [sheet(style)];
|
|
27
|
-
|
|
28
|
-
customElements.define('cosmoz-autocomplete', component(
|
|
27
|
+
const shadowRootInit = { mode: 'open', delegatesFocus: true };
|
|
28
|
+
customElements.define('cosmoz-autocomplete-ui', component(Autocomplete, { observedAttributes, styleSheets, shadowRootInit }));
|
|
29
|
+
customElements.define('cosmoz-autocomplete', component(Standalone, { observedAttributes, styleSheets, shadowRootInit }));
|
|
@@ -13,7 +13,7 @@ interface Base<I> {
|
|
|
13
13
|
onSelect: (value: I, meta: Meta<I>) => void;
|
|
14
14
|
}
|
|
15
15
|
interface Meta<I> extends Omit<Base<I>, 'value'> {
|
|
16
|
-
|
|
16
|
+
setOpened: (opened: boolean) => void;
|
|
17
17
|
value: I[];
|
|
18
18
|
}
|
|
19
19
|
export interface Props<I> extends Base<I> {
|
|
@@ -23,21 +23,19 @@ export interface Props<I> extends Base<I> {
|
|
|
23
23
|
textual?: (prop?: string) => (i: I) => string;
|
|
24
24
|
valueProperty?: string;
|
|
25
25
|
disabled?: boolean;
|
|
26
|
-
onFocus?: (focused?: boolean) => void;
|
|
27
26
|
preserveOrder?: boolean;
|
|
28
27
|
defaultIndex?: number;
|
|
29
28
|
externalSearch?: boolean;
|
|
30
29
|
}
|
|
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,
|
|
32
|
-
|
|
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, externalSearch, }: Props<I>) => {
|
|
31
|
+
opened: boolean;
|
|
33
32
|
query: string | undefined;
|
|
34
33
|
textual: (i: I) => string;
|
|
35
34
|
value: I[];
|
|
36
35
|
source$: Promise<I[]>;
|
|
37
36
|
loading: boolean;
|
|
38
37
|
items: I[];
|
|
39
|
-
|
|
40
|
-
onFocus: (e: FocusEvent) => void;
|
|
38
|
+
onToggle: (e: Event) => void;
|
|
41
39
|
onText: (e: InputEvent) => void;
|
|
42
40
|
onSelect: (newVal: I) => void;
|
|
43
41
|
onDeselect: (val: I | I[]) => void;
|
|
@@ -1,34 +1,43 @@
|
|
|
1
|
-
import { useFocus } from '@neovici/cosmoz-dropdown/use-focus';
|
|
2
1
|
import { array, without } from '@neovici/cosmoz-utils/array';
|
|
3
2
|
import { useHost } from '@neovici/cosmoz-utils/hooks/use-host';
|
|
4
3
|
import { useMeta } from '@neovici/cosmoz-utils/hooks/use-meta';
|
|
5
4
|
import { usePromise } from '@neovici/cosmoz-utils/hooks/use-promise';
|
|
5
|
+
import { useActivity } from '@neovici/cosmoz-utils/keybindings';
|
|
6
6
|
import { prop, strProp } from '@neovici/cosmoz-utils/object';
|
|
7
7
|
import { useCallback, useEffect, useMemo, useState } from '@pionjs/pion';
|
|
8
|
-
import {
|
|
8
|
+
import { AUTOCOMPLETE_DESELECT_LAST } from './autocomplete-keybindings';
|
|
9
9
|
import { EMPTY, normalize, notify, search } from './util';
|
|
10
10
|
const useNotify = (host, fn, name) => useCallback((val) => {
|
|
11
11
|
fn?.(val);
|
|
12
12
|
host.dispatchEvent(new CustomEvent(name, { detail: val }));
|
|
13
13
|
}, [fn]);
|
|
14
|
-
export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onText: _onText, onSelect, limit, min, source, textProperty, textual: _textual, valueProperty, keepOpened, keepQuery, preserveOrder, defaultIndex, externalSearch,
|
|
15
|
-
const textual = useMemo(() => (_textual ?? strProp)(textProperty), [_textual, textProperty]),
|
|
16
|
-
|
|
14
|
+
export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onText: _onText, onSelect, limit, min, source, textProperty, textual: _textual, valueProperty, keepOpened, keepQuery, preserveOrder, defaultIndex, externalSearch, }) => {
|
|
15
|
+
const textual = useMemo(() => (_textual ?? strProp)(textProperty), [_textual, textProperty]), host = useHost(),
|
|
16
|
+
// Popover open/close state, synced from cosmoz-dropdown-next's dropdown-toggle event
|
|
17
|
+
[opened, setOpened] = useState(false), empty = !text, query = useMemo(() => text?.trim(), [text]), onText = useNotify(host, _onText, 'text'), onChange = useCallback((val) => {
|
|
18
|
+
_onChange?.(val, () => setOpened(false));
|
|
17
19
|
notify(host, 'value', val);
|
|
18
|
-
}, [_onChange]), [options, setOptions] = useState([]), source$ = useMemo(() => Promise.resolve(typeof source === 'function'
|
|
20
|
+
}, [_onChange]), [options, setOptions] = useState([]), source$ = useMemo(() => Promise.resolve(typeof source === 'function'
|
|
21
|
+
? source({ query, active: opened })
|
|
22
|
+
: source).then(normalize), [source, opened, query]), value = useMemo(() => array(_value), [_value]);
|
|
19
23
|
useEffect(() => source$.then(setOptions), [source$]);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
// Backspace to deselect last chip (when input is empty and multi-select)
|
|
25
|
+
useActivity({
|
|
26
|
+
activity: AUTOCOMPLETE_DESELECT_LAST,
|
|
27
|
+
callback: () => {
|
|
28
|
+
const values = array(value);
|
|
29
|
+
if (values.length > 0) {
|
|
30
|
+
onChange(values.slice(0, -1));
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
check: () => empty && limit !== 1 && host.matches(':focus-within'),
|
|
34
|
+
element: () => host,
|
|
35
|
+
}, []);
|
|
36
|
+
// Clear query when popover closes
|
|
28
37
|
useEffect(() => {
|
|
29
|
-
if (!
|
|
38
|
+
if (!opened && !keepQuery)
|
|
30
39
|
onText('');
|
|
31
|
-
}, [
|
|
40
|
+
}, [opened, keepQuery]);
|
|
32
41
|
const meta = useMeta({
|
|
33
42
|
onText,
|
|
34
43
|
onChange,
|
|
@@ -37,19 +46,19 @@ export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onTe
|
|
|
37
46
|
min,
|
|
38
47
|
keepQuery,
|
|
39
48
|
keepOpened,
|
|
40
|
-
|
|
49
|
+
setOpened,
|
|
41
50
|
onSelect,
|
|
42
51
|
});
|
|
43
52
|
const [, , state] = usePromise(source$);
|
|
44
53
|
return {
|
|
45
|
-
|
|
54
|
+
opened,
|
|
46
55
|
query,
|
|
47
56
|
textual,
|
|
48
57
|
value,
|
|
49
58
|
source$,
|
|
50
59
|
loading: state === 'pending',
|
|
51
60
|
items: useMemo(() => {
|
|
52
|
-
if (!
|
|
61
|
+
if (!opened)
|
|
53
62
|
return EMPTY;
|
|
54
63
|
const items = preserveOrder
|
|
55
64
|
? options
|
|
@@ -57,7 +66,7 @@ export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onTe
|
|
|
57
66
|
return externalSearch ? items : search(items, query, textual);
|
|
58
67
|
}, [
|
|
59
68
|
options,
|
|
60
|
-
|
|
69
|
+
opened,
|
|
61
70
|
query,
|
|
62
71
|
textual,
|
|
63
72
|
empty,
|
|
@@ -66,19 +75,21 @@ export const useAutocomplete = ({ value: _value, text, onChange: _onChange, onTe
|
|
|
66
75
|
valueProperty,
|
|
67
76
|
externalSearch,
|
|
68
77
|
]),
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
// Sync opened state from cosmoz-dropdown-next's composed dropdown-toggle event
|
|
79
|
+
onToggle: useCallback((e) => {
|
|
80
|
+
setOpened(e.newState === 'open');
|
|
81
|
+
}, []),
|
|
71
82
|
onText: useCallback((e) => {
|
|
72
83
|
onText(e.target.value);
|
|
73
|
-
|
|
74
|
-
}, [onText, text,
|
|
84
|
+
setOpened(true);
|
|
85
|
+
}, [onText, text, setOpened]),
|
|
75
86
|
onSelect: useCallback((newVal) => {
|
|
76
87
|
meta.onSelect?.(newVal, meta);
|
|
77
|
-
const { onChange, onText, limit, min, value: val, keepQuery, keepOpened,
|
|
88
|
+
const { onChange, onText, limit, min, value: val, keepQuery, keepOpened, setOpened, } = meta;
|
|
78
89
|
if (!keepQuery)
|
|
79
90
|
onText('');
|
|
80
91
|
if (!keepOpened)
|
|
81
|
-
|
|
92
|
+
setOpened(false);
|
|
82
93
|
const value = array(val), deselect = value.includes(newVal);
|
|
83
94
|
if (deselect && value.length === min)
|
|
84
95
|
return;
|
|
@@ -29,7 +29,7 @@ const overflow = (host) => {
|
|
|
29
29
|
chips[i].hidden = true;
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
-
export const useOverflow = ({ value,
|
|
32
|
+
export const useOverflow = ({ value, opened, wrap, limit, }) => {
|
|
33
33
|
const host = useHost();
|
|
34
34
|
// eslint-disable-next-line eqeqeq
|
|
35
35
|
const enabled = !(wrap || limit == 1);
|
|
@@ -45,5 +45,5 @@ export const useOverflow = ({ value, active, wrap, limit, }) => {
|
|
|
45
45
|
observer.observe(input);
|
|
46
46
|
return () => observer.disconnect();
|
|
47
47
|
}, [enabled]);
|
|
48
|
-
useLayoutEffect(() => (enabled ? doRaf() : undefined), [enabled, width,
|
|
48
|
+
useLayoutEffect(() => (enabled ? doRaf() : undefined), [enabled, width, opened, value]);
|
|
49
49
|
};
|
package/dist/excluding/index.js
CHANGED
|
@@ -55,7 +55,9 @@ const AutocompleteExcluding = (props) => {
|
|
|
55
55
|
chipRenderer: useMemo(() => mkChipRenderer(value, onClear), [value, onClear]),
|
|
56
56
|
});
|
|
57
57
|
};
|
|
58
|
+
const shadowRootInit = { mode: 'open', delegatesFocus: true };
|
|
58
59
|
customElements.define('cosmoz-autocomplete-excluding', component(AutocompleteExcluding, {
|
|
59
60
|
observedAttributes,
|
|
60
61
|
styleSheets: [style, excludingStyle],
|
|
62
|
+
shadowRootInit,
|
|
61
63
|
}));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import './autocomplete/';
|
|
2
|
+
export { AUTOCOMPLETE_DESELECT_LAST, autocompleteKeybindings, } from './autocomplete/autocomplete-keybindings';
|
|
2
3
|
export type { Source } from './autocomplete/use-autocomplete';
|
|
4
|
+
export { LISTBOX_NAVIGATE_DOWN, LISTBOX_NAVIGATE_UP, LISTBOX_SELECT, listboxKeybindings, } from './listbox/listbox-keybindings';
|
package/dist/index.js
CHANGED
package/dist/listbox/index.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { StyleInfo } from 'lit-html/directives/style-map.js';
|
|
2
1
|
import { Props } from './use-listbox';
|
|
3
2
|
interface ListboxProps<I> extends Props<I> {
|
|
4
3
|
multi?: boolean;
|
|
5
|
-
setFloating: (el?: Element) => void;
|
|
6
|
-
styles: StyleInfo;
|
|
7
4
|
}
|
|
8
|
-
export declare const listbox: <I>({ multi,
|
|
5
|
+
export declare const listbox: <I>({ multi, ...thru }: ListboxProps<I>, content: unknown) => import("lit-html").TemplateResult<1>;
|
|
9
6
|
export {};
|
package/dist/listbox/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { virtualize, virtualizerRef, } from '@lit-labs/virtualizer/virtualize.js';
|
|
2
|
-
import { connectable } from '@neovici/cosmoz-utils/connectable';
|
|
3
2
|
import { sheet } from '@neovici/cosmoz-utils';
|
|
4
3
|
import { spreadProps } from '@neovici/cosmoz-utils/directives/spread-props';
|
|
5
4
|
import { noop } from '@neovici/cosmoz-utils/function';
|
|
@@ -7,7 +6,6 @@ import { useStyleSheet } from '@neovici/cosmoz-utils/hooks/use-stylesheet';
|
|
|
7
6
|
import { props } from '@neovici/cosmoz-utils/object';
|
|
8
7
|
import { component, html, useEffect, useRef } from '@pionjs/pion';
|
|
9
8
|
import { ref } from 'lit-html/directives/ref.js';
|
|
10
|
-
import { styleMap } from 'lit-html/directives/style-map.js';
|
|
11
9
|
import style, { styles } from './style.css';
|
|
12
10
|
import { properties, useListbox } from './use-listbox';
|
|
13
11
|
const ignore = noop;
|
|
@@ -51,14 +49,10 @@ const Listbox = (host) => {
|
|
|
51
49
|
</div>
|
|
52
50
|
<slot></slot>`;
|
|
53
51
|
};
|
|
54
|
-
customElements.define('cosmoz-listbox',
|
|
55
|
-
export const listbox = ({ multi,
|
|
56
|
-
style="${styleMap(styles)}"
|
|
57
|
-
@connected="${(e) => e.target.showPopover?.()}"
|
|
58
|
-
popover="manual"
|
|
52
|
+
customElements.define('cosmoz-listbox', component(Listbox, { styleSheets: [sheet(style)] }));
|
|
53
|
+
export const listbox = ({ multi, ...thru }, content) => html `<cosmoz-listbox
|
|
59
54
|
part="listbox"
|
|
60
55
|
?multi=${multi}
|
|
61
|
-
${ref(setFloating)}
|
|
62
56
|
...=${spreadProps(props(properties)(thru))}
|
|
63
57
|
>${content}</cosmoz-listbox
|
|
64
58
|
>`;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Activity } from '@neovici/cosmoz-utils/keybindings';
|
|
2
|
+
export declare const LISTBOX_NAVIGATE_UP: Activity;
|
|
3
|
+
export declare const LISTBOX_NAVIGATE_DOWN: Activity;
|
|
4
|
+
export declare const LISTBOX_SELECT: Activity;
|
|
5
|
+
export declare const listboxKeybindings: readonly [readonly [{
|
|
6
|
+
readonly key: "ArrowUp";
|
|
7
|
+
}, [symbol], {
|
|
8
|
+
readonly title: "Navigate up";
|
|
9
|
+
readonly description: "Move to previous listbox item";
|
|
10
|
+
}, {
|
|
11
|
+
readonly allowInEditable: true;
|
|
12
|
+
}], readonly [{
|
|
13
|
+
readonly key: "ArrowDown";
|
|
14
|
+
}, [symbol], {
|
|
15
|
+
readonly title: "Navigate down";
|
|
16
|
+
readonly description: "Move to next listbox item";
|
|
17
|
+
}, {
|
|
18
|
+
readonly allowInEditable: true;
|
|
19
|
+
}], readonly [{
|
|
20
|
+
readonly key: "Enter";
|
|
21
|
+
}, [symbol], {
|
|
22
|
+
readonly title: "Select";
|
|
23
|
+
readonly description: "Select current listbox item";
|
|
24
|
+
}, {
|
|
25
|
+
readonly allowInEditable: true;
|
|
26
|
+
}]];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const LISTBOX_NAVIGATE_UP = Symbol('listbox.navigate.up');
|
|
2
|
+
export const LISTBOX_NAVIGATE_DOWN = Symbol('listbox.navigate.down');
|
|
3
|
+
export const LISTBOX_SELECT = Symbol('listbox.select');
|
|
4
|
+
export const listboxKeybindings = [
|
|
5
|
+
[
|
|
6
|
+
{ key: 'ArrowUp' },
|
|
7
|
+
[LISTBOX_NAVIGATE_UP],
|
|
8
|
+
{ title: 'Navigate up', description: 'Move to previous listbox item' },
|
|
9
|
+
{ allowInEditable: true },
|
|
10
|
+
],
|
|
11
|
+
[
|
|
12
|
+
{ key: 'ArrowDown' },
|
|
13
|
+
[LISTBOX_NAVIGATE_DOWN],
|
|
14
|
+
{ title: 'Navigate down', description: 'Move to next listbox item' },
|
|
15
|
+
{ allowInEditable: true },
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
{ key: 'Enter' },
|
|
19
|
+
[LISTBOX_SELECT],
|
|
20
|
+
{ title: 'Select', description: 'Select current listbox item' },
|
|
21
|
+
{ allowInEditable: true },
|
|
22
|
+
],
|
|
23
|
+
];
|
|
@@ -7,42 +7,21 @@ const checkSVG =
|
|
|
7
7
|
/* eslint-enable quotes */
|
|
8
8
|
const style = css `
|
|
9
9
|
:host {
|
|
10
|
-
|
|
11
|
-
z-index: 1000;
|
|
10
|
+
display: block;
|
|
12
11
|
font-family: var(--paper-font-subhead_-_font-family, inherit);
|
|
13
12
|
background: var(
|
|
14
13
|
--cosmoz-autocomplete-listbox-bg,
|
|
15
14
|
rgba(255, 255, 255, 0.75)
|
|
16
15
|
);
|
|
17
|
-
min-width: 50px;
|
|
18
16
|
backdrop-filter: blur(16px) saturate(180%);
|
|
19
17
|
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
|
20
18
|
border-radius: 6px;
|
|
21
19
|
box-shadow:
|
|
22
20
|
0 4px 24px 0 rgba(0, 0, 0, 0.18),
|
|
23
21
|
0 1.5px 6px 0 rgba(0, 0, 0, 0.1);
|
|
22
|
+
border: 1px solid rgba(200, 200, 200, 0.25);
|
|
24
23
|
text-transform: var(--cosmoz-autocomplete-listbox-text-transform, initial);
|
|
25
24
|
overflow: hidden;
|
|
26
|
-
transition:
|
|
27
|
-
opacity 150ms ease-in-out,
|
|
28
|
-
transform 100ms ease-in-out;
|
|
29
|
-
}
|
|
30
|
-
:host(:popover-open) {
|
|
31
|
-
box-sizing: border-box;
|
|
32
|
-
display: block;
|
|
33
|
-
margin: 0;
|
|
34
|
-
border: 1px solid rgba(200, 200, 200, 0.25);
|
|
35
|
-
|
|
36
|
-
opacity: 1;
|
|
37
|
-
transform: translateY(0);
|
|
38
|
-
|
|
39
|
-
@starting-style {
|
|
40
|
-
opacity: 0;
|
|
41
|
-
transform: translateY(-50px);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
:host([popover]) {
|
|
45
|
-
padding: 0;
|
|
46
25
|
}
|
|
47
26
|
.items {
|
|
48
27
|
position: relative;
|
|
@@ -106,15 +85,6 @@ const style = css `
|
|
|
106
85
|
padding-left: 33px;
|
|
107
86
|
}
|
|
108
87
|
|
|
109
|
-
.swatch {
|
|
110
|
-
width: 18px;
|
|
111
|
-
height: 18px;
|
|
112
|
-
display: inline-block;
|
|
113
|
-
box-sizing: border-box;
|
|
114
|
-
vertical-align: middle;
|
|
115
|
-
border-radius: 50%;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
88
|
[virtualizer-sizer]:not(.sizer) {
|
|
119
89
|
line-height: 1;
|
|
120
90
|
}
|
|
@@ -1,32 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
case 'Enter':
|
|
22
|
-
e.preventDefault();
|
|
23
|
-
listeners.onEnter();
|
|
24
|
-
break;
|
|
25
|
-
default:
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
document.addEventListener('keydown', handler, true);
|
|
30
|
-
return () => document.removeEventListener('keydown', handler, true);
|
|
31
|
-
}, [listeners]);
|
|
1
|
+
import { useHost } from '@neovici/cosmoz-utils/hooks/use-host';
|
|
2
|
+
import { useActivity } from '@neovici/cosmoz-utils/keybindings';
|
|
3
|
+
import { LISTBOX_NAVIGATE_DOWN, LISTBOX_NAVIGATE_UP, LISTBOX_SELECT, } from './listbox-keybindings';
|
|
4
|
+
export const useKeyboard = ({ onUp, onDown, onEnter }) => {
|
|
5
|
+
const host = useHost();
|
|
6
|
+
useActivity({
|
|
7
|
+
activity: LISTBOX_NAVIGATE_UP,
|
|
8
|
+
callback: onUp,
|
|
9
|
+
element: () => host,
|
|
10
|
+
}, []);
|
|
11
|
+
useActivity({
|
|
12
|
+
activity: LISTBOX_NAVIGATE_DOWN,
|
|
13
|
+
callback: onDown,
|
|
14
|
+
element: () => host,
|
|
15
|
+
}, []);
|
|
16
|
+
useActivity({
|
|
17
|
+
activity: LISTBOX_SELECT,
|
|
18
|
+
callback: onEnter,
|
|
19
|
+
element: () => host,
|
|
20
|
+
}, []);
|
|
32
21
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neovici/cosmoz-autocomplete",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A autocomplete input web component",
|
|
6
6
|
"keywords": [
|
|
@@ -72,13 +72,15 @@
|
|
|
72
72
|
"exports": {
|
|
73
73
|
".": "./dist/index.js",
|
|
74
74
|
"./autocomplete": "./dist/autocomplete/index.js",
|
|
75
|
+
"./keybindings": "./dist/autocomplete/autocomplete-keybindings.js",
|
|
75
76
|
"./listbox": "./dist/listbox/index.js",
|
|
77
|
+
"./listbox/keybindings": "./dist/listbox/listbox-keybindings.js",
|
|
76
78
|
"./item-renderer": "./dist/listbox/item-renderer.js"
|
|
77
79
|
},
|
|
78
80
|
"dependencies": {
|
|
79
81
|
"@lit-labs/virtualizer": "^2.0.0",
|
|
80
|
-
"@neovici/cosmoz-dropdown": "^
|
|
81
|
-
"@neovici/cosmoz-input": "^5.0
|
|
82
|
+
"@neovici/cosmoz-dropdown": "^7.3.0",
|
|
83
|
+
"@neovici/cosmoz-input": "^5.6.0",
|
|
82
84
|
"@neovici/cosmoz-utils": "^6.19.0",
|
|
83
85
|
"@pionjs/pion": "^2.7.1",
|
|
84
86
|
"i18next": "^23.16.8",
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { array } from '@neovici/cosmoz-utils/array';
|
|
2
|
-
import { useMeta } from '@neovici/cosmoz-utils/hooks/use-meta';
|
|
3
|
-
import { useEffect } from '@pionjs/pion';
|
|
4
|
-
export const useKeys = ({ focused, empty, ...rest }) => {
|
|
5
|
-
const enabled = focused && empty && rest.limit !== 1, meta = useMeta(rest);
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
if (!enabled) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
const handler = (e) => {
|
|
11
|
-
if (e.defaultPrevented) {
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
const { key } = e, values = array(meta.value), isOne = meta.limit == 1; //eslint-disable-line eqeqeq
|
|
15
|
-
if (values.length > 0 &&
|
|
16
|
-
(key === 'Backspace' || (isOne && key.length === 1))) {
|
|
17
|
-
return meta.onChange(values.slice(0, -1));
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
document.addEventListener('keydown', handler, true);
|
|
21
|
-
return () => document.removeEventListener('keydown', handler, true);
|
|
22
|
-
}, [enabled, meta]);
|
|
23
|
-
};
|