@furystack/shades-common-components 5.0.20 → 7.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.
Files changed (66) hide show
  1. package/esm/components/command-palette/command-palette-input.d.ts.map +1 -1
  2. package/esm/components/command-palette/command-palette-input.js +26 -23
  3. package/esm/components/command-palette/command-palette-input.js.map +1 -1
  4. package/esm/components/command-palette/command-palette-suggestion-list.d.ts.map +1 -1
  5. package/esm/components/command-palette/command-palette-suggestion-list.js +33 -29
  6. package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
  7. package/esm/components/command-palette/index.d.ts.map +1 -1
  8. package/esm/components/command-palette/index.js +48 -44
  9. package/esm/components/command-palette/index.js.map +1 -1
  10. package/esm/components/data-grid/data-grid-row.d.ts.map +1 -1
  11. package/esm/components/data-grid/data-grid-row.js +37 -33
  12. package/esm/components/data-grid/data-grid-row.js.map +1 -1
  13. package/esm/components/data-grid/header.d.ts.map +1 -1
  14. package/esm/components/data-grid/header.js +28 -22
  15. package/esm/components/data-grid/header.js.map +1 -1
  16. package/esm/components/data-grid/selection-cell.d.ts.map +1 -1
  17. package/esm/components/data-grid/selection-cell.js +5 -3
  18. package/esm/components/data-grid/selection-cell.js.map +1 -1
  19. package/esm/components/inputs/input.js +1 -1
  20. package/esm/components/inputs/input.js.map +1 -1
  21. package/esm/components/noty-list.d.ts.map +1 -1
  22. package/esm/components/noty-list.js +6 -6
  23. package/esm/components/noty-list.js.map +1 -1
  24. package/esm/components/suggest/index.js +2 -2
  25. package/esm/components/suggest/index.js.map +1 -1
  26. package/esm/components/suggest/suggest-input.d.ts.map +1 -1
  27. package/esm/components/suggest/suggest-input.js +12 -9
  28. package/esm/components/suggest/suggest-input.js.map +1 -1
  29. package/esm/components/suggest/suggest-manager.d.ts +4 -3
  30. package/esm/components/suggest/suggest-manager.d.ts.map +1 -1
  31. package/esm/components/suggest/suggest-manager.js +7 -7
  32. package/esm/components/suggest/suggest-manager.js.map +1 -1
  33. package/esm/components/suggest/suggestion-list.js +32 -27
  34. package/esm/components/suggest/suggestion-list.js.map +1 -1
  35. package/esm/services/collection-service.d.ts.map +1 -1
  36. package/esm/services/collection-service.js +3 -2
  37. package/esm/services/collection-service.js.map +1 -1
  38. package/esm/services/collection-service.spec.d.ts +2 -0
  39. package/esm/services/collection-service.spec.d.ts.map +1 -0
  40. package/esm/services/collection-service.spec.js +28 -0
  41. package/esm/services/collection-service.spec.js.map +1 -0
  42. package/esm/services/noty-service.d.ts +11 -7
  43. package/esm/services/noty-service.d.ts.map +1 -1
  44. package/esm/services/noty-service.js +25 -14
  45. package/esm/services/noty-service.js.map +1 -1
  46. package/esm/services/noty-service.spec.d.ts +2 -0
  47. package/esm/services/noty-service.spec.d.ts.map +1 -0
  48. package/esm/services/noty-service.spec.js +20 -0
  49. package/esm/services/noty-service.spec.js.map +1 -0
  50. package/package.json +6 -6
  51. package/src/components/command-palette/command-palette-input.tsx +26 -28
  52. package/src/components/command-palette/command-palette-suggestion-list.tsx +42 -38
  53. package/src/components/command-palette/index.tsx +55 -51
  54. package/src/components/data-grid/data-grid-row.tsx +6 -7
  55. package/src/components/data-grid/header.tsx +27 -21
  56. package/src/components/data-grid/selection-cell.tsx +4 -2
  57. package/src/components/inputs/input.tsx +1 -1
  58. package/src/components/noty-list.tsx +16 -12
  59. package/src/components/suggest/index.tsx +2 -2
  60. package/src/components/suggest/suggest-input.tsx +4 -6
  61. package/src/components/suggest/suggest-manager.ts +7 -7
  62. package/src/components/suggest/suggestion-list.tsx +32 -32
  63. package/src/services/collection-service.spec.ts +37 -0
  64. package/src/services/collection-service.ts +3 -2
  65. package/src/services/noty-service.spec.ts +26 -0
  66. package/src/services/noty-service.ts +23 -11
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { NotyService } from './noty-service.js';
3
+ import { using } from '@furystack/utils';
4
+ describe('NotyService', () => {
5
+ it('Should add and removea noty', () => {
6
+ using(new NotyService(), (notyService) => {
7
+ expect(notyService.getNotyList()).toEqual([]);
8
+ const exampleNoty = {
9
+ type: 'info',
10
+ title: 'Test',
11
+ body: 'Test',
12
+ };
13
+ notyService.emit('onNotyAdded', exampleNoty);
14
+ expect(notyService.getNotyList()).toEqual([exampleNoty]);
15
+ notyService.emit('onNotyRemoved', exampleNoty);
16
+ expect(notyService.getNotyList()).toEqual([]);
17
+ });
18
+ });
19
+ });
20
+ //# sourceMappingURL=noty-service.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"noty-service.spec.js","sourceRoot":"","sources":["../../src/services/noty-service.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAExC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,KAAK,CAAC,IAAI,WAAW,EAAE,EAAE,CAAC,WAAW,EAAE,EAAE;YACvC,MAAM,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;YAE7C,MAAM,WAAW,GAAc;gBAC7B,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,MAAM;gBACb,IAAI,EAAE,MAAM;aACb,CAAA;YAED,WAAW,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAA;YAE5C,MAAM,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,CAAA;YAExD,WAAW,CAAC,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CAAA;YAE9C,MAAM,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC/C,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@furystack/shades-common-components",
3
- "version": "5.0.20",
3
+ "version": "7.0.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -32,13 +32,13 @@
32
32
  "license": "GPL-2.0",
33
33
  "devDependencies": {
34
34
  "typescript": "^5.4.2",
35
- "vitest": "^1.3.1"
35
+ "vitest": "^1.4.0"
36
36
  },
37
37
  "dependencies": {
38
- "@furystack/core": "^12.0.18",
39
- "@furystack/inject": "^8.1.8",
40
- "@furystack/shades": "^8.1.1",
41
- "@furystack/utils": "^4.0.17",
38
+ "@furystack/core": "^14.0.0",
39
+ "@furystack/inject": "^10.0.0",
40
+ "@furystack/shades": "^10.0.0",
41
+ "@furystack/utils": "^6.0.0",
42
42
  "path-to-regexp": "^6.2.1",
43
43
  "semaphore-async-await": "^1.5.1"
44
44
  },
@@ -3,40 +3,38 @@ import { ThemeProviderService } from '../../services/theme-provider-service.js'
3
3
  import { promisifyAnimation } from '../../utils/promisify-animation.js'
4
4
  import type { CommandPaletteManager } from './command-palette-manager.js'
5
5
 
6
+ const updateComponent = async (element: HTMLElement, isOpened: boolean) => {
7
+ const input = element.firstChild as HTMLInputElement | null
8
+ if (input) {
9
+ if (isOpened) {
10
+ input.value = ''
11
+ await promisifyAnimation(element, [{ width: '0%' }, { width: '100%' }], {
12
+ duration: 300,
13
+ fill: 'forwards',
14
+ easing: 'cubic-bezier(0.595, 0.425, 0.415, 0.845)',
15
+ })
16
+ input.focus()
17
+ } else {
18
+ await promisifyAnimation(element, [{ width: '100%' }, { width: '0%' }], {
19
+ duration: 300,
20
+ fill: 'forwards',
21
+ easing: 'cubic-bezier(0.595, 0.425, 0.415, 0.845)',
22
+ })
23
+ input.value = ''
24
+ }
25
+ }
26
+ }
27
+
6
28
  export const CommandPaletteInput = Shade<{ manager: CommandPaletteManager }>({
7
29
  shadowDomName: 'shades-command-palette-input',
8
30
  render: ({ element, props, injector, useObservable }) => {
9
31
  const { theme } = injector.getInstance(ThemeProviderService)
10
32
  const { manager } = props
11
33
 
12
- useObservable(
13
- 'isOpened',
14
- manager.isOpened,
15
- async (isOpened) => {
16
- const input = element.firstChild as HTMLInputElement | null
17
- if (input) {
18
- if (isOpened) {
19
- input.value = ''
20
- await promisifyAnimation(element, [{ width: '0%' }, { width: '100%' }], {
21
- duration: 300,
22
- fill: 'forwards',
23
- easing: 'cubic-bezier(0.595, 0.425, 0.415, 0.845)',
24
- })
25
- input.focus()
26
- } else {
27
- await promisifyAnimation(element, [{ width: '100%' }, { width: '0%' }], {
28
- duration: 300,
29
- fill: 'forwards',
30
- easing: 'cubic-bezier(0.595, 0.425, 0.415, 0.845)',
31
- })
32
- input.value = ''
33
- }
34
- }
35
- },
36
- true,
37
- )
38
-
39
- element.style.width = manager.isOpened.getValue() ? '100%' : '0%'
34
+ const [isCurrentlyOpened] = useObservable('isOpened', manager.isOpened, {
35
+ onChange: (newValue) => updateComponent(element, newValue),
36
+ })
37
+ element.style.width = isCurrentlyOpened ? '100%' : '0%'
40
38
  element.style.overflow = 'hidden'
41
39
  return (
42
40
  <input
@@ -10,46 +10,50 @@ export const CommandPaletteSuggestionList = Shade<{ manager: CommandPaletteManag
10
10
  const { theme } = injector.getInstance(ThemeProviderService)
11
11
 
12
12
  const [suggestions] = useObservable('suggestions', props.manager.currentSuggestions)
13
- const [selectedIndex] = useObservable('selectedIndex', props.manager.selectedIndex, (idx) => {
14
- ;([...element.querySelectorAll('.suggestion-item')] as HTMLDivElement[]).map((s, i) => {
15
- if (i === idx) {
16
- s.style.background = theme.background.paper
17
- s.style.fontWeight = 'bolder'
18
- } else {
19
- s.style.background = theme.background.default
20
- s.style.fontWeight = 'normal'
21
- }
22
- })
13
+ const [selectedIndex] = useObservable('selectedIndex', props.manager.selectedIndex, {
14
+ onChange: (idx) => {
15
+ ;([...element.querySelectorAll('.suggestion-item')] as HTMLDivElement[]).map((s, i) => {
16
+ if (i === idx) {
17
+ s.style.background = theme.background.paper
18
+ s.style.fontWeight = 'bolder'
19
+ } else {
20
+ s.style.background = theme.background.default
21
+ s.style.fontWeight = 'normal'
22
+ }
23
+ })
24
+ },
23
25
  })
24
26
 
25
- const [isOpenedAtRender] = useObservable('isOpenedAtRender', props.manager.isOpened, async (isOpened) => {
26
- const container = element.firstElementChild as HTMLDivElement
27
- if (isOpened) {
28
- container.style.display = 'initial'
29
- container.style.zIndex = '1'
30
- container.style.width = `calc(${Math.round(
31
- element.parentElement?.getBoundingClientRect().width || 200,
32
- )}px - 3em)`
33
- await promisifyAnimation(
34
- container,
35
- [
36
- { opacity: 0, transform: 'translate(0, -50px)' },
37
- { opacity: 1, transform: 'translate(0, 0)' },
38
- ],
39
- { fill: 'forwards', duration: 500 },
40
- )
41
- } else {
42
- await promisifyAnimation(
43
- container,
44
- [
45
- { opacity: 1, transform: 'translate(0, 0)' },
46
- { opacity: 0, transform: 'translate(0, -50px)' },
47
- ],
48
- { fill: 'forwards', duration: 200 },
49
- )
50
- container.style.zIndex = '-1'
51
- container.style.display = 'none'
52
- }
27
+ const [isOpenedAtRender] = useObservable('isOpenedAtRender', props.manager.isOpened, {
28
+ onChange: async (isOpened) => {
29
+ const container = element.firstElementChild as HTMLDivElement
30
+ if (isOpened) {
31
+ container.style.display = 'initial'
32
+ container.style.zIndex = '1'
33
+ container.style.width = `calc(${Math.round(
34
+ element.parentElement?.getBoundingClientRect().width || 200,
35
+ )}px - 3em)`
36
+ await promisifyAnimation(
37
+ container,
38
+ [
39
+ { opacity: 0, transform: 'translate(0, -50px)' },
40
+ { opacity: 1, transform: 'translate(0, 0)' },
41
+ ],
42
+ { fill: 'forwards', duration: 500 },
43
+ )
44
+ } else {
45
+ await promisifyAnimation(
46
+ container,
47
+ [
48
+ { opacity: 1, transform: 'translate(0, 0)' },
49
+ { opacity: 0, transform: 'translate(0, -50px)' },
50
+ ],
51
+ { fill: 'forwards', duration: 200 },
52
+ )
53
+ container.style.zIndex = '-1'
54
+ container.style.display = 'none'
55
+ }
56
+ },
53
57
  })
54
58
 
55
59
  return (
@@ -29,69 +29,73 @@ export const CommandPalette = Shade<CommandPaletteProps>({
29
29
 
30
30
  useDisposable('clickAwayService', () => new ClickAwayService(element, () => manager.isOpened.setValue(false)))
31
31
 
32
- const [isLoadingAtRender] = useObservable('isLoading', manager.isLoading, async (isLoading) => {
33
- const loader = element.querySelector('.loader-container')
34
- if (isLoading) {
35
- promisifyAnimation(loader, [{ opacity: 0 }, { opacity: 1 }], {
36
- duration: 100,
37
- fill: 'forwards',
38
- })
39
- } else {
40
- promisifyAnimation(loader, [{ opacity: 1 }, { opacity: 0 }], {
41
- duration: 100,
42
- fill: 'forwards',
43
- })
44
- }
45
- })
46
-
47
- const [isOpenedAtRender, setIsOpened] = useObservable('isOpened', manager.isOpened, (isOpened) => {
48
- {
49
- const suggestions = element.querySelector('.close-suggestions')
50
- const postControls = element.querySelector('.post-controls')
51
- const inputContainer = element.querySelector('.input-container') as HTMLDivElement
52
- if (isOpened) {
53
- promisifyAnimation(suggestions, [{ opacity: 0 }, { opacity: 1 }], {
54
- duration: 500,
32
+ const [isLoadingAtRender] = useObservable('isLoading', manager.isLoading, {
33
+ onChange: async (isLoading) => {
34
+ const loader = element.querySelector('.loader-container')
35
+ if (isLoading) {
36
+ promisifyAnimation(loader, [{ opacity: 0 }, { opacity: 1 }], {
37
+ duration: 100,
55
38
  fill: 'forwards',
56
39
  })
57
-
58
- promisifyAnimation(postControls, [{ width: '0px' }, { width: '50px' }], {
40
+ } else {
41
+ promisifyAnimation(loader, [{ opacity: 1 }, { opacity: 0 }], {
59
42
  duration: 100,
60
43
  fill: 'forwards',
61
44
  })
45
+ }
46
+ },
47
+ })
62
48
 
63
- promisifyAnimation(
64
- inputContainer,
65
- [{ background: 'transparent' }, { background: theme.background.default }],
66
- {
49
+ const [isOpenedAtRender, setIsOpened] = useObservable('isOpened', manager.isOpened, {
50
+ onChange: (isOpened) => {
51
+ {
52
+ const suggestions = element.querySelector('.close-suggestions')
53
+ const postControls = element.querySelector('.post-controls')
54
+ const inputContainer = element.querySelector('.input-container') as HTMLDivElement
55
+ if (isOpened) {
56
+ promisifyAnimation(suggestions, [{ opacity: 0 }, { opacity: 1 }], {
67
57
  duration: 500,
68
58
  fill: 'forwards',
69
- easing: 'cubic-bezier(0.050, 0.570, 0.840, 1.005)',
70
- },
71
- )
72
- } else {
73
- promisifyAnimation(suggestions, [{ opacity: 1 }, { opacity: 0 }], {
74
- duration: 500,
75
- fill: 'forwards',
76
- })
59
+ })
77
60
 
78
- promisifyAnimation(postControls, [{ width: '50px' }, { width: '0px' }], {
79
- duration: 500,
80
- fill: 'forwards',
81
- delay: 300,
82
- })
61
+ promisifyAnimation(postControls, [{ width: '0px' }, { width: '50px' }], {
62
+ duration: 100,
63
+ fill: 'forwards',
64
+ })
65
+
66
+ promisifyAnimation(
67
+ inputContainer,
68
+ [{ background: 'transparent' }, { background: theme.background.default }],
69
+ {
70
+ duration: 500,
71
+ fill: 'forwards',
72
+ easing: 'cubic-bezier(0.050, 0.570, 0.840, 1.005)',
73
+ },
74
+ )
75
+ } else {
76
+ promisifyAnimation(suggestions, [{ opacity: 1 }, { opacity: 0 }], {
77
+ duration: 500,
78
+ fill: 'forwards',
79
+ })
83
80
 
84
- promisifyAnimation(
85
- inputContainer,
86
- [{ background: theme.background.default }, { background: 'transparent' }],
87
- {
88
- duration: 300,
81
+ promisifyAnimation(postControls, [{ width: '50px' }, { width: '0px' }], {
82
+ duration: 500,
89
83
  fill: 'forwards',
90
- easing: 'cubic-bezier(0.000, 0.245, 0.190, 0.790)',
91
- },
92
- )
84
+ delay: 300,
85
+ })
86
+
87
+ promisifyAnimation(
88
+ inputContainer,
89
+ [{ background: theme.background.default }, { background: 'transparent' }],
90
+ {
91
+ duration: 300,
92
+ fill: 'forwards',
93
+ easing: 'cubic-bezier(0.000, 0.245, 0.190, 0.790)',
94
+ },
95
+ )
96
+ }
93
97
  }
94
- }
98
+ },
95
99
  })
96
100
 
97
101
  return (
@@ -39,14 +39,14 @@ export const DataGridRow: <T>(props: DataGridRowProps<T>, children: ChildrenList
39
39
  }
40
40
  }
41
41
 
42
- const [selection] = useObservable('isSelected', service.selection, attachSelectedStyles, true)
42
+ // TODO: getLast is elmiminated, do we need it?
43
+ const [selection] = useObservable('isSelected', service.selection, { onChange: attachSelectedStyles })
43
44
 
44
45
  attachSelectedStyles(selection)
45
46
 
46
- const [focus] = useObservable(
47
- 'focus',
48
- service.focusedEntry,
49
- (newEntry) => {
47
+ // TODO: getLast is elmiminated, do we need it?
48
+ const [focus] = useObservable('focus', service.focusedEntry, {
49
+ onChange: (newEntry) => {
50
50
  if (newEntry === props.entry) {
51
51
  attachStyles(element, {
52
52
  style: props.focusedRowStyle || {
@@ -84,8 +84,7 @@ export const DataGridRow: <T>(props: DataGridRowProps<T>, children: ChildrenList
84
84
  })
85
85
  }
86
86
  },
87
- true,
88
- )
87
+ })
89
88
 
90
89
  element.style.display = 'table-row'
91
90
  element.style.cursor = 'default'
@@ -61,14 +61,16 @@ export const OrderButton = Shade<{ collectionService: CollectionService<any>; fi
61
61
  const SearchButton = Shade<{ service: CollectionService<any>; fieldName: string; onclick: () => void }>({
62
62
  shadowDomName: 'data-grid-search-button',
63
63
  render: ({ props, useObservable, element }) => {
64
- const [queryState] = useObservable('currentFilterState', props.service.querySettings, (currentQueryState) => {
65
- const currentValue = (currentQueryState.filter?.[props.fieldName] as any)?.$regex || ''
64
+ const [queryState] = useObservable('currentFilterState', props.service.querySettings, {
65
+ onChange: (currentQueryState) => {
66
+ const currentValue = (currentQueryState.filter?.[props.fieldName] as any)?.$regex || ''
66
67
 
67
- const button = element.querySelector('button') as HTMLInputElement
68
- button.innerHTML = currentValue ? '🔍' : '🔎'
69
- button.style.textShadow = currentValue
70
- ? '1px 1px 20px rgba(235,225,45,0.9), 1px 1px 12px rgba(235,225,45,0.9), 0px 0px 3px rgba(255,200,145,0.6)'
71
- : 'none'
68
+ const button = element.querySelector('button') as HTMLInputElement
69
+ button.innerHTML = currentValue ? '🔍' : '🔎'
70
+ button.style.textShadow = currentValue
71
+ ? '1px 1px 20px rgba(235,225,45,0.9), 1px 1px 12px rgba(235,225,45,0.9), 0px 0px 3px rgba(255,200,145,0.6)'
72
+ : 'none'
73
+ },
72
74
  })
73
75
 
74
76
  const filterValue = (queryState.filter as any)?.[props.fieldName]?.$regex || ''
@@ -100,9 +102,11 @@ const SearchForm = Shade<{
100
102
  render: ({ props, useObservable, element }) => {
101
103
  type SearchSubmitType = { searchValue: string }
102
104
 
103
- const [queryState] = useObservable('currentFilterState', props.service.querySettings, (currentQueryState) => {
104
- const currentValue = (currentQueryState.filter?.[props.fieldName] as any)?.$regex || ''
105
- ;(element.querySelector('input') as HTMLInputElement).value = currentValue
105
+ const [queryState] = useObservable('currentFilterState', props.service.querySettings, {
106
+ onChange: (currentQueryState) => {
107
+ const currentValue = (currentQueryState.filter?.[props.fieldName] as any)?.$regex || ''
108
+ ;(element.querySelector('input') as HTMLInputElement).value = currentValue
109
+ },
106
110
  })
107
111
 
108
112
  return (
@@ -157,17 +161,19 @@ export const DataGridHeader: <T, K extends keyof T>(
157
161
  ) => JSX.Element<any> = Shade<DataGridHeaderProps<any, any>>({
158
162
  shadowDomName: 'data-grid-header',
159
163
  render: ({ props, element, useObservable }) => {
160
- const [, setIsSearchOpened] = useObservable('isSearchOpened', new ObservableValue(false), (newValue) => {
161
- const searchForm = element.querySelector('.search-form') as HTMLElement
162
- const headerContent = element.querySelector('.header-content') as HTMLElement
163
- if (!newValue) {
164
- collapse(searchForm)
165
- expand(headerContent)
166
- } else {
167
- searchForm.style.display = 'flex'
168
- expand(searchForm).then(() => searchForm.querySelector('input')?.focus())
169
- collapse(headerContent)
170
- }
164
+ const [, setIsSearchOpened] = useObservable('isSearchOpened', new ObservableValue(false), {
165
+ onChange: (newValue) => {
166
+ const searchForm = element.querySelector('.search-form') as HTMLElement
167
+ const headerContent = element.querySelector('.header-content') as HTMLElement
168
+ if (!newValue) {
169
+ collapse(searchForm)
170
+ expand(headerContent)
171
+ } else {
172
+ searchForm.style.display = 'flex'
173
+ expand(searchForm).then(() => searchForm.querySelector('input')?.focus())
174
+ collapse(headerContent)
175
+ }
176
+ },
171
177
  })
172
178
  const updateSearchValue = (value?: string) => {
173
179
  const currentSettings = props.collectionService.querySettings.getValue()
@@ -4,8 +4,10 @@ import type { CollectionService } from '../../services/collection-service.js'
4
4
  export const SelectionCell = Shade<{ entry: any; service: CollectionService<any> }>({
5
5
  shadowDomName: 'shades-data-grid-selection-cell',
6
6
  render: ({ props, useObservable, element }) => {
7
- const [selection] = useObservable('selection', props.service.selection, (newSelection) => {
8
- ;(element.querySelector('input') as HTMLInputElement).checked = newSelection.includes(props.entry)
7
+ const [selection] = useObservable('selection', props.service.selection, {
8
+ onChange: (newSelection) => {
9
+ ;(element.querySelector('input') as HTMLInputElement).checked = newSelection.includes(props.entry)
10
+ },
9
11
  })
10
12
  const isSelected = selection.includes(props.entry)
11
13
 
@@ -226,7 +226,7 @@ export const Input = Shade<TextInputProps>({
226
226
  validity: element.querySelector('input')?.validity || ({} as ValidityState),
227
227
  element,
228
228
  }),
229
- updateState,
229
+ { onChange: updateState },
230
230
  )
231
231
 
232
232
  return (
@@ -139,27 +139,31 @@ export const NotyList = Shade({
139
139
  display: 'flex',
140
140
  flexDirection: 'column',
141
141
  },
142
- render: ({ useObservable, injector, element }) => {
142
+ render: ({ useDisposable, injector, element }) => {
143
143
  const notyService = injector.getInstance(NotyService)
144
144
 
145
- const currentNotys = notyService.notys.getValue()
145
+ const currentNotys = notyService.getNotyList()
146
146
 
147
- useObservable('addNoty', notyService.onNotyAdded, (n) =>
148
- element.append(<NotyComponent model={n} onDismiss={() => notyService.removeNoty(n)} />),
147
+ useDisposable('addNoty', () =>
148
+ notyService.subscribe('onNotyAdded', (n) =>
149
+ element.append(<NotyComponent model={n} onDismiss={() => notyService.emit('onNotyRemoved', n)} />),
150
+ ),
149
151
  )
150
152
 
151
- useObservable('removeNoty', notyService.onNotyRemoved, (n) => {
152
- element.querySelectorAll('shade-noty').forEach((e) => {
153
- if ((e as JSX.Element).props.model === n) {
154
- e.remove()
155
- }
156
- })
157
- })
153
+ useDisposable('removeNoty', () =>
154
+ notyService.subscribe('onNotyRemoved', (n) => {
155
+ element.querySelectorAll('shade-noty').forEach((e) => {
156
+ if ((e as JSX.Element).props.model === n) {
157
+ e.remove()
158
+ }
159
+ })
160
+ }),
161
+ )
158
162
 
159
163
  return (
160
164
  <>
161
165
  {currentNotys.map((n) => (
162
- <NotyComponent model={n} onDismiss={() => injector.getInstance(NotyService).removeNoty(n)} />
166
+ <NotyComponent model={n} onDismiss={() => injector.getInstance(NotyService).emit('onNotyRemoved', n)} />
163
167
  ))}
164
168
  </>
165
169
  )
@@ -28,7 +28,7 @@ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX
28
28
  style: {
29
29
  flexGrow: '1',
30
30
  },
31
- render: ({ props, injector, element, useDisposable, useObservable }) => {
31
+ render: ({ props, injector, element, useDisposable }) => {
32
32
  const manager = useDisposable('manager', () => new SuggestManager(props.getEntries, props.getSuggestionEntry))
33
33
  const { theme } = injector.getInstance(ThemeProviderService)
34
34
  const inputContainer = element.querySelector('.input-container') as HTMLDivElement
@@ -85,7 +85,7 @@ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX
85
85
  })
86
86
  }
87
87
  })
88
- useObservable('onSelectSuggestion', manager.onSelectSuggestion, props.onSelectSuggestion)
88
+ useDisposable('onSelectSuggestion', () => manager.subscribe('onSelectSuggestion', props.onSelectSuggestion))
89
89
  return (
90
90
  <div
91
91
  style={{ display: 'flex', flexDirection: 'column' }}
@@ -11,10 +11,9 @@ export const SuggestInput = Shade<{ manager: SuggestManager<any> }>({
11
11
  render: ({ element, props, useObservable, injector }) => {
12
12
  const { theme } = injector.getInstance(ThemeProviderService)
13
13
 
14
- useObservable(
15
- 'isOpened',
16
- props.manager.isOpened,
17
- (isOpened) => {
14
+ // todo: getLast is eliminated, do we need it?
15
+ useObservable('isOpened', props.manager.isOpened, {
16
+ onChange: (isOpened) => {
18
17
  const input = element.firstChild as HTMLInputElement
19
18
  if (isOpened) {
20
19
  input.focus()
@@ -22,8 +21,7 @@ export const SuggestInput = Shade<{ manager: SuggestManager<any> }>({
22
21
  input.value = ''
23
22
  }
24
23
  },
25
- true,
26
- )
24
+ })
27
25
 
28
26
  return (
29
27
  <input
@@ -1,17 +1,16 @@
1
1
  import type { Injector } from '@furystack/inject'
2
2
  import { Injectable } from '@furystack/inject'
3
3
  import type { Disposable } from '@furystack/utils'
4
- import { debounce, ObservableValue } from '@furystack/utils'
4
+ import { debounce, EventHub, ObservableValue } from '@furystack/utils'
5
5
  import type { SuggestionResult } from './suggestion-result.js'
6
6
 
7
7
  @Injectable({ lifetime: 'singleton' })
8
- export class SuggestManager<T> implements Disposable {
8
+ export class SuggestManager<T> extends EventHub<'onSelectSuggestion', { onSelectSuggestion: T }> implements Disposable {
9
9
  public isOpened = new ObservableValue(false)
10
10
  public isLoading = new ObservableValue(false)
11
11
  public term = new ObservableValue('')
12
12
  public selectedIndex = new ObservableValue(0)
13
13
  public currentSuggestions = new ObservableValue<Array<{ entry: T; suggestion: SuggestionResult }>>([])
14
- public onSelectSuggestion = new ObservableValue<T>()
15
14
 
16
15
  public keyPressListener = ((ev: KeyboardEvent) => {
17
16
  if (ev.key === 'Escape') {
@@ -31,20 +30,20 @@ export class SuggestManager<T> implements Disposable {
31
30
  }).bind(this)
32
31
 
33
32
  public dispose() {
34
- window.removeEventListener('keyup', this.keyPressListener)
35
- window.removeEventListener('click', this.clickOutsideListener)
33
+ window.removeEventListener('keyup', this.keyPressListener, true)
34
+ window.removeEventListener('click', this.clickOutsideListener, true)
36
35
  this.isOpened.dispose()
37
36
  this.isLoading.dispose()
38
37
  this.term.dispose()
39
38
  this.selectedIndex.dispose()
40
39
  this.currentSuggestions.dispose()
41
- this.onSelectSuggestion.dispose()
40
+ super.dispose()
42
41
  }
43
42
 
44
43
  public selectSuggestion(index: number = this.selectedIndex.getValue()) {
45
44
  const selectedSuggestion = this.currentSuggestions.getValue()[index]
46
45
  this.isOpened.setValue(false)
47
- this.onSelectSuggestion.setValue(selectedSuggestion.entry)
46
+ this.emit('onSelectSuggestion', selectedSuggestion.entry)
48
47
  }
49
48
 
50
49
  private lastGetSuggestionOptions?: { injector: Injector; term: string }
@@ -74,6 +73,7 @@ export class SuggestManager<T> implements Disposable {
74
73
  private readonly getEntries: (term: string) => Promise<T[]>,
75
74
  private readonly getSuggestionEntry: (entry: T) => SuggestionResult,
76
75
  ) {
76
+ super()
77
77
  window.addEventListener('keyup', this.keyPressListener, true)
78
78
  window.addEventListener('click', this.clickOutsideListener, true)
79
79
  }