@neovici/cosmoz-autocomplete 2.15.0 → 2.17.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
@@ -10,7 +10,7 @@ const Listbox = host => {
10
10
  const { index, items, renderItem, height, itemHeight } = useListbox(host);
11
11
 
12
12
  useEffect(() => {
13
- scrollIntoViewIfNeeded(host.shadowRoot.querySelector('[data-index="' + index + '"]'), host);
13
+ scrollIntoViewIfNeeded(host.shadowRoot.querySelector('[data-index="' + index + '"]:not(:hover)'), host);
14
14
  }, [index]);
15
15
 
16
16
  return html`
@@ -150,7 +150,7 @@ const
150
150
  .errorMessage=${ until(values$.then(() => errorMessage, e => e.message), errorMessage) }
151
151
  .value=${ live(text) }
152
152
  @value-changed=${ onText }
153
- @focused-changed=${ onFocus }
153
+ @focusin=${ onFocus } @focusout=${ onFocus }
154
154
  @click=${ onClick }
155
155
  autocomplete="off"
156
156
  exportparts=${ inputParts }
@@ -1,5 +1,5 @@
1
1
  import { html } from 'haunted';
2
- import { mark, identity, prop, array } from './utils';
2
+ import { mark, identity } from './utils';
3
3
  export default (
4
4
  item,
5
5
  i,
@@ -8,17 +8,15 @@ export default (
8
8
  select,
9
9
  textual = identity,
10
10
  query,
11
- value,
12
- valueProperty
11
+ isSelected
13
12
  }
14
13
  ) => {
15
14
  const text = textual(item),
16
- content = mark(text, query),
17
- valuation = prop(valueProperty),
18
- selected = array(value).find(value => valuation(value) === valuation(item));
15
+ content = mark(text, query);
16
+
19
17
  return html`
20
18
  <div class="item"
21
- role="option" part="option" ?aria-selected=${ selected }
19
+ role="option" part="option" ?aria-selected=${ isSelected(item) }
22
20
  data-index=${ i }
23
21
  @mouseenter=${ () => highlight(i) }
24
22
  @click=${ () => select(item) }
package/lib/use-focus.js CHANGED
@@ -1,10 +1,21 @@
1
1
  import { useEffect, useState, useCallback } from 'haunted';
2
2
  import { useMeta } from '@neovici/cosmoz-utils/lib/hooks/use-meta';
3
3
 
4
+ const isFocused = t => t.matches(':focus-within'),
5
+ blurEvs = ['focusout', 'blur'],
6
+ isBlurEvent = type => blurEvs.includes(type);
7
+
4
8
  export const useFocus = ({ disabled, onFocus }) => {
5
- const [{ focused, closed }, setFocus] = useState({ focused: false, closed: false }),
9
+ const [{ focused, closed } = {}, setState] = useState(),
6
10
  active = focused && !disabled,
7
- meta = useMeta({ closed });
11
+ meta = useMeta({ closed, onFocus }),
12
+ setClosed = useCallback(closed => setState(p => ({ ...p, closed })), []),
13
+ onToggle = useCallback(e => {
14
+ const target = e.currentTarget;
15
+ return isFocused(target)
16
+ ? setState(p => ({ focused: true, closed: !p?.closed }))
17
+ : target.focus();
18
+ }, []);
8
19
 
9
20
  useEffect(() => {
10
21
  if (!active) {
@@ -17,10 +28,10 @@ export const useFocus = ({ disabled, onFocus }) => {
17
28
  const { closed } = meta;
18
29
  if (e.key === 'Escape' && !closed) {
19
30
  e.preventDefault();
20
- setFocus(p => ({ ...p, closed: true }));
31
+ setClosed(true);
21
32
  } else if (['ArrowUp', 'Up'].includes(e.key) && closed) {
22
33
  e.preventDefault();
23
- setFocus(p => ({ ...p, closed: false }));
34
+ setClosed(false);
24
35
  }
25
36
  };
26
37
  document.addEventListener('keydown', handler, true);
@@ -29,11 +40,14 @@ export const useFocus = ({ disabled, onFocus }) => {
29
40
 
30
41
  return {
31
42
  active: active && !closed,
32
- setClosed: useCallback(closed => setFocus(p => ({ ...p, closed })), []),
43
+ setClosed,
44
+ onToggle,
33
45
  onFocus: useCallback(e => {
34
- const focused = e.target.focused;
35
- setFocus({ focused, closed: false });
36
- onFocus?.(focused);
37
- }, [onFocus])
46
+ const target = e.currentTarget,
47
+ focused = isFocused(target) || isBlurEvent(e.type)
48
+ && e.relatedTarget?.closest('[data-dropdown]');
49
+ setState({ focused });
50
+ meta.onFocus?.(focused);
51
+ }, [meta])
38
52
  };
39
53
  };
@@ -1,26 +1,11 @@
1
- import { useCallback, useEffect, useState } from 'haunted';
2
- import getPosition from 'position.js';
1
+ import { useCallback, useEffect, useState, useMemo } from 'haunted';
2
+ import { useMeta } from '@neovici/cosmoz-utils/lib/hooks/use-meta';
3
3
  import { useKeyboard } from './use-keyboard';
4
- import { onScrolled } from './on-scrolled';
5
4
  import defaultItemRenderer from './item-renderer';
6
- import { useMeta } from '@neovici/cosmoz-utils/lib/hooks/use-meta';
7
-
8
- const defaultPlacement = ['bottom-left', 'bottom-right', 'bottom', 'top-left', 'top-right', 'top'],
9
- position = (host, anchor, placement = defaultPlacement, confinement) => {
10
- const anchorBounds = anchor.getBoundingClientRect(),
11
- hostBounds = host.getBoundingClientRect(),
12
- { popupOffset: offset } = getPosition(hostBounds, anchorBounds, placement, {
13
- fixed: true,
14
- adjustXY: 'both',
15
- offsetParent: confinement
16
- }),
17
- { style } = host;
18
- style.left = offset.left + 'px';
19
- style.top = offset.top + 'px';
20
- style.minWidth = Math.max(anchorBounds.width, hostBounds.width) + 'px';
21
- },
5
+ import { byValue } from './utils';
6
+ import { usePosition } from './use-position';
22
7
 
23
- useItems = ({
8
+ const useItems = ({
24
9
  items,
25
10
  onSelect,
26
11
  defaultIndex = 0
@@ -54,43 +39,12 @@ const defaultPlacement = ['bottom-left', 'bottom-right', 'bottom', 'top-left', '
54
39
  return useCallback((item, i) => itemRenderer(item, i, info), [info, itemRenderer]);
55
40
  },
56
41
 
57
- usePosition = ({
58
- anchor: anchorage, host, confinement, placement
59
- }) => {
60
- useEffect(() => {
61
- const anchor = typeof anchorage === 'function' ? anchorage() : anchorage;
62
- if (anchor == null) {
63
- return;
64
- }
65
- let rid;
66
- const reposition = () => position(host, anchor, placement, confinement),
67
- ro = new ResizeObserver(reposition);
68
-
69
- ro.observe(host);
70
- ro.observe(anchor);
71
-
72
- const onReposition = () => {
73
- cancelAnimationFrame(rid);
74
- rid = requestAnimationFrame(reposition);
75
- },
76
- offScroll = onScrolled(anchor, onReposition);
77
- window.addEventListener('resize', onReposition, true);
78
-
79
- return () => {
80
- ro.unobserve(host);
81
- ro.unobserve(anchor);
82
- offScroll();
83
- window.removeEventListener('resize', onReposition, true);
84
- cancelAnimationFrame(rid);
85
- };
86
- }, [anchorage, confinement, placement]);
87
- },
88
42
 
89
43
  useListbox = host => {
90
44
  const {
91
45
  value,
92
46
  valueProperty,
93
- items,
47
+ items: _items,
94
48
  onSelect,
95
49
  defaultIndex,
96
50
  query,
@@ -102,6 +56,8 @@ const defaultPlacement = ['bottom-left', 'bottom-right', 'bottom', 'top-left', '
102
56
  confinement,
103
57
  placement
104
58
  } = host,
59
+ isSelected = useMemo(() => byValue(value, valueProperty), [value, valueProperty]),
60
+ items = useMemo(() => _items.slice(), [_items, isSelected]),
105
61
  {
106
62
  index,
107
63
  highlight,
@@ -132,8 +88,7 @@ const defaultPlacement = ['bottom-left', 'bottom-right', 'bottom', 'top-left', '
132
88
  select,
133
89
  textual,
134
90
  query,
135
- value,
136
- valueProperty
91
+ isSelected
137
92
  })
138
93
  };
139
94
  },
@@ -0,0 +1,49 @@
1
+ import { useEffect } from 'haunted';
2
+ import getPosition from 'position.js';
3
+ import { onScrolled } from './on-scrolled';
4
+
5
+ const defaultPlacement = ['bottom-left', 'bottom-right', 'bottom', 'top-left', 'top-right', 'top'],
6
+ position = (host, anchor, placement = defaultPlacement, confinement) => {
7
+ const anchorBounds = anchor.getBoundingClientRect(),
8
+ hostBounds = host.getBoundingClientRect(),
9
+ { popupOffset: offset } = getPosition(hostBounds, anchorBounds, placement, {
10
+ fixed: true,
11
+ adjustXY: 'both',
12
+ offsetParent: confinement
13
+ }),
14
+ { style } = host;
15
+ style.left = offset.left + 'px';
16
+ style.top = offset.top + 'px';
17
+ style.minWidth = Math.max(anchorBounds.width, hostBounds.width) + 'px';
18
+ },
19
+ usePosition = ({ anchor: anchorage, host, confinement, placement }) => {
20
+ useEffect(() => {
21
+ const anchor = typeof anchorage === 'function' ? anchorage() : anchorage;
22
+ if (anchor == null) {
23
+ return;
24
+ }
25
+ let rid;
26
+ const reposition = () => position(host, anchor, placement, confinement),
27
+ ro = new ResizeObserver(reposition);
28
+
29
+ ro.observe(host);
30
+ ro.observe(anchor);
31
+
32
+ const onReposition = () => {
33
+ cancelAnimationFrame(rid);
34
+ rid = requestAnimationFrame(reposition);
35
+ },
36
+ offScroll = onScrolled(anchor, onReposition);
37
+ window.addEventListener('resize', onReposition, true);
38
+
39
+ return () => {
40
+ ro.unobserve(host);
41
+ ro.unobserve(anchor);
42
+ offScroll();
43
+ window.removeEventListener('resize', onReposition, true);
44
+ cancelAnimationFrame(rid);
45
+ };
46
+ }, [anchorage, confinement, placement]);
47
+ };
48
+
49
+ export { usePosition };
package/lib/utils.js CHANGED
@@ -51,8 +51,12 @@ const mark = (text, query) => {
51
51
  return;
52
52
  }
53
53
  el.scrollIntoView();
54
+ },
55
+ byValue = (value, valueProperty) => {
56
+ const valuation = prop(valueProperty);
57
+ return item => array(value).find(value => valuation(value) === valuation(item));
54
58
  };
55
59
 
56
60
  export {
57
- array, identity, mark, prop, search, without, strProp, scrollIntoViewIfNeeded
61
+ array, identity, mark, prop, search, without, strProp, scrollIntoViewIfNeeded, byValue
58
62
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-autocomplete",
3
- "version": "2.15.0",
3
+ "version": "2.17.1",
4
4
  "description": "A autocomplete input web component",
5
5
  "keywords": [
6
6
  "lit-html",
@@ -65,20 +65,19 @@
65
65
  "position.js": "^0.3.0"
66
66
  },
67
67
  "devDependencies": {
68
- "@commitlint/cli": "^12.0.0",
69
- "@commitlint/config-conventional": "^12.0.0",
68
+ "@commitlint/cli": "^13.0.0",
69
+ "@commitlint/config-conventional": "^13.0.0",
70
70
  "@neovici/eslint-config": "^1.2.0",
71
71
  "@open-wc/demoing-storybook": "^2.1.0",
72
72
  "@open-wc/testing": "^2.5.32",
73
- "@semantic-release/changelog": "^5.0.1",
74
- "@semantic-release/git": "^9.0.0",
73
+ "@semantic-release/changelog": "^6.0.0",
74
+ "@semantic-release/git": "^10.0.0",
75
75
  "@storybook/storybook-deployer": "^2.8.5",
76
76
  "@web/test-runner": "^0.13.0",
77
77
  "@web/test-runner-selenium": "^0.5.0",
78
- "@webcomponents/webcomponentsjs": "^2.4.0",
79
78
  "eslint": "^7.0.0",
80
- "husky": "^6.0.0",
81
- "semantic-release": "^17.4.0",
79
+ "husky": "^7.0.0",
80
+ "semantic-release": "^18.0.0",
82
81
  "sinon": "^11.0.0",
83
82
  "typescript": "^4.0.0"
84
83
  }