@furystack/shades-common-components 3.6.0 → 4.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 (165) hide show
  1. package/dist/components/app-bar-link.js +20 -24
  2. package/dist/components/app-bar-link.js.map +1 -1
  3. package/dist/components/app-bar.js +9 -12
  4. package/dist/components/app-bar.js.map +1 -1
  5. package/dist/components/avatar.js +20 -18
  6. package/dist/components/avatar.js.map +1 -1
  7. package/dist/components/button.js +51 -37
  8. package/dist/components/button.js.map +1 -1
  9. package/dist/components/command-palette/command-palette-input.js +24 -29
  10. package/dist/components/command-palette/command-palette-input.js.map +1 -1
  11. package/dist/components/command-palette/command-palette-suggestion-list.js +38 -47
  12. package/dist/components/command-palette/command-palette-suggestion-list.js.map +1 -1
  13. package/dist/components/command-palette/index.js +34 -40
  14. package/dist/components/command-palette/index.js.map +1 -1
  15. package/dist/components/data-grid/body.js +6 -13
  16. package/dist/components/data-grid/body.js.map +1 -1
  17. package/dist/components/data-grid/data-grid-row.js +29 -31
  18. package/dist/components/data-grid/data-grid-row.js.map +1 -1
  19. package/dist/components/data-grid/data-grid.js +6 -21
  20. package/dist/components/data-grid/data-grid.js.map +1 -1
  21. package/dist/components/data-grid/footer.js +9 -21
  22. package/dist/components/data-grid/footer.js.map +1 -1
  23. package/dist/components/data-grid/header.js +19 -18
  24. package/dist/components/data-grid/header.js.map +1 -1
  25. package/dist/components/data-grid/selection-cell.js +9 -23
  26. package/dist/components/data-grid/selection-cell.js.map +1 -1
  27. package/dist/components/fab.js +10 -5
  28. package/dist/components/fab.js.map +1 -1
  29. package/dist/components/grid.js +1 -10
  30. package/dist/components/grid.js.map +1 -1
  31. package/dist/components/inputs/autocomplete.js +3 -13
  32. package/dist/components/inputs/autocomplete.js.map +1 -1
  33. package/dist/components/inputs/input.js +31 -39
  34. package/dist/components/inputs/input.js.map +1 -1
  35. package/dist/components/inputs/text-area.js +3 -11
  36. package/dist/components/inputs/text-area.js.map +1 -1
  37. package/dist/components/loader.js +8 -14
  38. package/dist/components/loader.js.map +1 -1
  39. package/dist/components/modal.js +7 -17
  40. package/dist/components/modal.js.map +1 -1
  41. package/dist/components/noty-list.js +27 -29
  42. package/dist/components/noty-list.js.map +1 -1
  43. package/dist/components/paper.js +13 -17
  44. package/dist/components/paper.js.map +1 -1
  45. package/dist/components/suggest/index.js +14 -20
  46. package/dist/components/suggest/index.js.map +1 -1
  47. package/dist/components/suggest/suggest-input.js +9 -10
  48. package/dist/components/suggest/suggest-input.js.map +1 -1
  49. package/dist/components/suggest/suggest-manager.js +8 -2
  50. package/dist/components/suggest/suggest-manager.js.map +1 -1
  51. package/dist/components/suggest/suggestion-list.js +27 -30
  52. package/dist/components/suggest/suggestion-list.js.map +1 -1
  53. package/dist/components/tabs.js +32 -44
  54. package/dist/components/tabs.js.map +1 -1
  55. package/dist/components/wizard/index.js +4 -7
  56. package/dist/components/wizard/index.js.map +1 -1
  57. package/dist/services/collection-service.js +10 -0
  58. package/dist/services/collection-service.js.map +1 -1
  59. package/dist/services/default-variable-theme.js +80 -0
  60. package/dist/services/default-variable-theme.js.map +1 -0
  61. package/dist/services/index.js +1 -0
  62. package/dist/services/index.js.map +1 -1
  63. package/dist/services/theme-provider-service.js +9 -7
  64. package/dist/services/theme-provider-service.js.map +1 -1
  65. package/package.json +6 -6
  66. package/src/components/app-bar-link.tsx +29 -35
  67. package/src/components/app-bar.tsx +22 -30
  68. package/src/components/avatar.tsx +40 -36
  69. package/src/components/button.tsx +91 -81
  70. package/src/components/command-palette/command-palette-input.tsx +15 -13
  71. package/src/components/command-palette/command-palette-suggestion-list.tsx +50 -58
  72. package/src/components/command-palette/index.tsx +54 -52
  73. package/src/components/data-grid/body.tsx +10 -22
  74. package/src/components/data-grid/data-grid-row.tsx +64 -63
  75. package/src/components/data-grid/data-grid.tsx +13 -24
  76. package/src/components/data-grid/footer.tsx +12 -23
  77. package/src/components/data-grid/header.tsx +34 -30
  78. package/src/components/data-grid/selection-cell.tsx +9 -19
  79. package/src/components/fab.tsx +21 -23
  80. package/src/components/grid.tsx +2 -11
  81. package/src/components/inputs/autocomplete.tsx +10 -16
  82. package/src/components/inputs/input.tsx +37 -42
  83. package/src/components/inputs/text-area.tsx +4 -18
  84. package/src/components/loader.tsx +19 -13
  85. package/src/components/modal.tsx +9 -17
  86. package/src/components/noty-list.tsx +55 -54
  87. package/src/components/paper.tsx +19 -26
  88. package/src/components/suggest/index.tsx +19 -30
  89. package/src/components/suggest/suggest-input.tsx +22 -15
  90. package/src/components/suggest/suggest-manager.ts +15 -3
  91. package/src/components/suggest/suggestion-list.tsx +90 -85
  92. package/src/components/tabs.tsx +50 -70
  93. package/src/components/wizard/index.tsx +6 -13
  94. package/src/services/collection-service.ts +14 -0
  95. package/src/services/default-variable-theme.ts +83 -0
  96. package/src/services/index.ts +1 -0
  97. package/src/services/theme-provider-service.ts +11 -8
  98. package/tsconfig.json +1 -1
  99. package/tsconfig.tsbuildinfo +1 -1
  100. package/types/components/app-bar-link.d.ts +1 -1
  101. package/types/components/app-bar-link.d.ts.map +1 -1
  102. package/types/components/app-bar.d.ts +1 -1
  103. package/types/components/app-bar.d.ts.map +1 -1
  104. package/types/components/avatar.d.ts +2 -1
  105. package/types/components/avatar.d.ts.map +1 -1
  106. package/types/components/button.d.ts +1 -1
  107. package/types/components/button.d.ts.map +1 -1
  108. package/types/components/command-palette/command-palette-input.d.ts +1 -1
  109. package/types/components/command-palette/command-palette-input.d.ts.map +1 -1
  110. package/types/components/command-palette/command-palette-suggestion-list.d.ts +1 -1
  111. package/types/components/command-palette/command-palette-suggestion-list.d.ts.map +1 -1
  112. package/types/components/command-palette/index.d.ts +1 -5
  113. package/types/components/command-palette/index.d.ts.map +1 -1
  114. package/types/components/data-grid/body.d.ts +1 -5
  115. package/types/components/data-grid/body.d.ts.map +1 -1
  116. package/types/components/data-grid/data-grid-row.d.ts +1 -5
  117. package/types/components/data-grid/data-grid-row.d.ts.map +1 -1
  118. package/types/components/data-grid/data-grid.d.ts +5 -3
  119. package/types/components/data-grid/data-grid.d.ts.map +1 -1
  120. package/types/components/data-grid/footer.d.ts +1 -1
  121. package/types/components/data-grid/footer.d.ts.map +1 -1
  122. package/types/components/data-grid/header.d.ts +1 -1
  123. package/types/components/data-grid/header.d.ts.map +1 -1
  124. package/types/components/data-grid/selection-cell.d.ts +1 -1
  125. package/types/components/data-grid/selection-cell.d.ts.map +1 -1
  126. package/types/components/fab.d.ts +1 -1
  127. package/types/components/fab.d.ts.map +1 -1
  128. package/types/components/grid.d.ts +1 -1
  129. package/types/components/grid.d.ts.map +1 -1
  130. package/types/components/inputs/autocomplete.d.ts +1 -1
  131. package/types/components/inputs/autocomplete.d.ts.map +1 -1
  132. package/types/components/inputs/input.d.ts +2 -3
  133. package/types/components/inputs/input.d.ts.map +1 -1
  134. package/types/components/inputs/text-area.d.ts +1 -6
  135. package/types/components/inputs/text-area.d.ts.map +1 -1
  136. package/types/components/loader.d.ts +9 -1
  137. package/types/components/loader.d.ts.map +1 -1
  138. package/types/components/modal.d.ts +1 -1
  139. package/types/components/modal.d.ts.map +1 -1
  140. package/types/components/noty-list.d.ts +2 -2
  141. package/types/components/noty-list.d.ts.map +1 -1
  142. package/types/components/paper.d.ts +1 -1
  143. package/types/components/paper.d.ts.map +1 -1
  144. package/types/components/skeleton.d.ts +1 -1
  145. package/types/components/skeleton.d.ts.map +1 -1
  146. package/types/components/suggest/index.d.ts +1 -5
  147. package/types/components/suggest/index.d.ts.map +1 -1
  148. package/types/components/suggest/suggest-input.d.ts +1 -1
  149. package/types/components/suggest/suggest-input.d.ts.map +1 -1
  150. package/types/components/suggest/suggest-manager.d.ts +2 -1
  151. package/types/components/suggest/suggest-manager.d.ts.map +1 -1
  152. package/types/components/suggest/suggestion-list.d.ts +1 -1
  153. package/types/components/suggest/suggestion-list.d.ts.map +1 -1
  154. package/types/components/tabs.d.ts +2 -2
  155. package/types/components/tabs.d.ts.map +1 -1
  156. package/types/components/wizard/index.d.ts +2 -2
  157. package/types/components/wizard/index.d.ts.map +1 -1
  158. package/types/services/collection-service.d.ts +4 -0
  159. package/types/services/collection-service.d.ts.map +1 -1
  160. package/types/services/default-variable-theme.d.ts +7 -0
  161. package/types/services/default-variable-theme.d.ts.map +1 -0
  162. package/types/services/index.d.ts +1 -0
  163. package/types/services/index.d.ts.map +1 -1
  164. package/types/services/theme-provider-service.d.ts +2 -3
  165. package/types/services/theme-provider-service.d.ts.map +1 -1
@@ -1,4 +1,4 @@
1
- import { createComponent, Shade } from '@furystack/shades'
1
+ import { attachProps, createComponent, Shade } from '@furystack/shades'
2
2
  import type { NotyModel } from '../services/noty-service'
3
3
  import { NotyService } from '../services/noty-service'
4
4
  import { ThemeProviderService } from '../services/theme-provider-service'
@@ -24,10 +24,9 @@ export const NotyComponent = Shade<{ model: NotyModel; onDismiss: () => void }>(
24
24
  shadowDomName: 'shade-noty',
25
25
  constructed: ({ element }) => {
26
26
  setTimeout(() => {
27
- const container = element.querySelector('div')
28
- const height = container?.scrollHeight || 80
27
+ const height = element.scrollHeight || 80
29
28
  promisifyAnimation(
30
- container,
29
+ element,
31
30
  [
32
31
  { opacity: '0', height: '0px' },
33
32
  { opacity: '1', height: `${height}px` },
@@ -42,16 +41,15 @@ export const NotyComponent = Shade<{ model: NotyModel; onDismiss: () => void }>(
42
41
  },
43
42
  render: ({ props, injector, element }) => {
44
43
  const themeProvider = injector.getInstance(ThemeProviderService)
45
- const colors = themeProvider.theme.getValue().palette[props.model.type]
44
+ const colors = themeProvider.theme.palette[props.model.type]
46
45
  const headerTextColor = themeProvider.getTextColor(colors.dark)
47
46
  const textColor = themeProvider.getTextColor(colors.main)
48
47
 
49
48
  const removeSelf = async () => {
50
- const container = element.querySelector('div')
51
49
  await promisifyAnimation(
52
- container,
50
+ element,
53
51
  [
54
- { opacity: '1', height: `${container?.scrollHeight || 0}px`, margin: '8px 8px' },
52
+ { opacity: '1', height: `${element?.scrollHeight || 0}px`, margin: '8px 8px' },
55
53
  { opacity: '0', height: '0px', margin: '0px 8px' },
56
54
  ],
57
55
  {
@@ -68,22 +66,24 @@ export const NotyComponent = Shade<{ model: NotyModel; onDismiss: () => void }>(
68
66
  setTimeout(removeSelf, timeout)
69
67
  }
70
68
 
69
+ attachProps(element, {
70
+ className: `noty ${props.model.type}`,
71
+ style: {
72
+ width: '300px',
73
+ display: 'flex',
74
+ flexDirection: 'column',
75
+ height: '0px',
76
+ backgroundColor: colors.main,
77
+ color: textColor,
78
+ margin: '8px',
79
+ overflow: 'hidden',
80
+ borderRadius: '6px',
81
+ boxShadow: '1px 3px 6px rgba(0,0,0,0.3)',
82
+ },
83
+ })
84
+
71
85
  return (
72
- <div
73
- className={`noty ${props.model.type}`}
74
- style={{
75
- width: '300px',
76
- display: 'flex',
77
- flexDirection: 'column',
78
- height: '0px',
79
- backgroundColor: colors.main,
80
- color: textColor,
81
- margin: '8px',
82
- overflow: 'hidden',
83
- borderRadius: '6px',
84
- boxShadow: '1px 3px 6px rgba(0,0,0,0.3)',
85
- }}
86
- >
86
+ <>
87
87
  <div
88
88
  style={{
89
89
  display: 'flex',
@@ -119,45 +119,46 @@ export const NotyComponent = Shade<{ model: NotyModel; onDismiss: () => void }>(
119
119
  </Button>
120
120
  </div>
121
121
  <div style={{ padding: '16px 16px' }}>{props.model.body}</div>
122
- </div>
122
+ </>
123
123
  )
124
124
  },
125
125
  })
126
126
 
127
- export const NotyList = Shade<unknown, { currentNotys: NotyModel[] }>({
128
- getInitialState: ({ injector }) => ({ currentNotys: injector.getInstance(NotyService).notys.getValue() }),
129
- constructed: ({ injector, element }) => {
130
- const notyService = injector.getInstance(NotyService)
131
- const observables = [
132
- notyService.onNotyAdded.subscribe((n) => {
133
- element.querySelector('div')?.append(<NotyComponent model={n} onDismiss={() => notyService.removeNoty(n)} />)
134
- }),
135
- notyService.onNotyRemoved.subscribe((n) => {
136
- element.querySelectorAll('shade-noty').forEach((e) => {
137
- if ((e as JSX.Element).props.model === n) {
138
- e.remove()
139
- }
140
- })
141
- }),
142
- ]
143
- return () => observables.forEach((o) => o.dispose())
144
- },
127
+ export const NotyList = Shade({
145
128
  shadowDomName: 'shade-noty-list',
146
- render: ({ getState, injector }) => {
129
+ render: ({ useObservable, injector, element }) => {
130
+ const notyService = injector.getInstance(NotyService)
131
+
132
+ attachProps(element, {
133
+ style: {
134
+ position: 'fixed',
135
+ bottom: '1em',
136
+ right: '1em',
137
+ display: 'flex',
138
+ flexDirection: 'column',
139
+ },
140
+ })
141
+
142
+ const currentNotys = notyService.notys.getValue()
143
+
144
+ useObservable('addNoty', notyService.onNotyAdded, (n) =>
145
+ element.append(<NotyComponent model={n} onDismiss={() => notyService.removeNoty(n)} />),
146
+ )
147
+
148
+ useObservable('removeNoty', notyService.onNotyRemoved, (n) => {
149
+ element.querySelectorAll('shade-noty').forEach((e) => {
150
+ if ((e as JSX.Element).props.model === n) {
151
+ e.remove()
152
+ }
153
+ })
154
+ })
155
+
147
156
  return (
148
- <div
149
- style={{
150
- position: 'fixed',
151
- bottom: '1em',
152
- right: '1em',
153
- display: 'flex',
154
- flexDirection: 'column',
155
- }}
156
- >
157
- {getState().currentNotys.map((n) => (
157
+ <>
158
+ {currentNotys.map((n) => (
158
159
  <NotyComponent model={n} onDismiss={() => injector.getInstance(NotyService).removeNoty(n)} />
159
160
  ))}
160
- </div>
161
+ </>
161
162
  )
162
163
  },
163
164
  })
@@ -1,35 +1,28 @@
1
1
  import type { PartialElement } from '@furystack/shades'
2
+ import { attachProps } from '@furystack/shades'
2
3
  import { Shade, createComponent } from '@furystack/shades'
3
4
  import { ThemeProviderService } from '../services/theme-provider-service'
4
5
 
5
6
  export const Paper = Shade<PartialElement<HTMLDivElement> & { elevation?: 1 | 2 | 3 }>({
6
7
  shadowDomName: 'shade-paper',
7
- resources: ({ injector, element }) => {
8
- const themeProvider = injector.getInstance(ThemeProviderService)
9
- return [
10
- themeProvider.theme.subscribe((newTheme) => {
11
- ;(element.firstChild as HTMLDivElement).style.background = newTheme.background.paper
12
- ;(element.firstChild as HTMLDivElement).style.color = themeProvider.theme.getValue().text.secondary
13
- }),
14
- ]
15
- },
16
- render: ({ injector, props, children }) => {
8
+
9
+ render: ({ injector, props, children, element }) => {
17
10
  const themeProvider = injector.getInstance(ThemeProviderService)
18
- return (
19
- <div
20
- {...props}
21
- style={{
22
- borderRadius: '3px',
23
- boxShadow: props.elevation ? `1px ${props.elevation}px ${props.elevation}px rgba(0,0,0,0.3)` : '',
24
- backgroundColor: themeProvider.theme.getValue().background.paper,
25
- color: themeProvider.theme.getValue().text.secondary,
26
- margin: '8px',
27
- padding: '6px 16px',
28
- ...props?.style,
29
- }}
30
- >
31
- {children}
32
- </div>
33
- )
11
+ const { elevation = 1 } = props
12
+
13
+ attachProps(element, {
14
+ ...props,
15
+ style: {
16
+ borderRadius: '3px',
17
+ boxShadow: elevation ? `1px ${elevation}px ${elevation}px rgba(0,0,0,0.3)` : '',
18
+ backgroundColor: themeProvider.theme.background.paper,
19
+ color: themeProvider.theme.text.secondary,
20
+ margin: '8px',
21
+ padding: '6px 16px',
22
+ ...props?.style,
23
+ },
24
+ })
25
+
26
+ return <>{children}</>
34
27
  },
35
28
  })
@@ -6,6 +6,7 @@ import { SuggestManager } from './suggest-manager'
6
6
  import type { SuggestionResult } from './suggestion-result'
7
7
  import { SuggestInput } from './suggest-input'
8
8
  import { SuggestionList } from './suggestion-list'
9
+ import { ThemeProviderService } from '../../services'
9
10
 
10
11
  export * from './suggest-input'
11
12
  export * from './suggest-manager'
@@ -20,25 +21,19 @@ export interface SuggestProps<T> {
20
21
  style?: Partial<CSSStyleDeclaration>
21
22
  }
22
23
 
23
- export interface SuggestState<T> {
24
- manager: SuggestManager<T>
25
- }
26
-
27
- export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade<
28
- SuggestProps<any>,
29
- SuggestState<any>
24
+ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX.Element<any> = Shade<
25
+ SuggestProps<any>
30
26
  >({
31
27
  shadowDomName: 'shade-suggest',
32
- getInitialState: ({ props }) => ({
33
- manager: new SuggestManager(props.getEntries, props.getSuggestionEntry),
34
- }),
35
- constructed: ({ element: el, getState, props }) => {
36
- const { manager } = getState()
37
- const element = el.querySelector('.input-container') as HTMLDivElement
38
- manager.element = el
28
+ render: ({ props, injector, element, useDisposable, useObservable }) => {
29
+ element.style.flexGrow = '1'
30
+ const manager = useDisposable('manager', () => new SuggestManager(props.getEntries, props.getSuggestionEntry))
31
+ const { theme } = injector.getInstance(ThemeProviderService)
32
+ const inputContainer = element.querySelector('.input-container') as HTMLDivElement
33
+ manager.element = element
39
34
  manager.isOpened.subscribe((isOpened) => {
40
- const suggestions = el.querySelector('.close-suggestions')
41
- const postControls = el.querySelector('.post-controls')
35
+ const suggestions = element.querySelector('.close-suggestions')
36
+ const postControls = element.querySelector('.post-controls')
42
37
  if (isOpened) {
43
38
  promisifyAnimation(suggestions, [{ opacity: 0 }, { opacity: 1 }], {
44
39
  duration: 500,
@@ -50,7 +45,7 @@ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX
50
45
  fill: 'forwards',
51
46
  })
52
47
 
53
- promisifyAnimation(element, [{ background: 'transparent' }, { background: 'rgba(128,128,128,0.1)' }], {
48
+ promisifyAnimation(inputContainer, [{ background: 'transparent' }, { background: theme.background.default }], {
54
49
  duration: 500,
55
50
  fill: 'forwards',
56
51
  easing: 'cubic-bezier(0.050, 0.570, 0.840, 1.005)',
@@ -67,7 +62,7 @@ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX
67
62
  delay: 300,
68
63
  })
69
64
 
70
- promisifyAnimation(element, [{ background: 'rgba(128,128,128,0.1)' }, { background: 'transparent' }], {
65
+ promisifyAnimation(inputContainer, [{ background: theme.background.default }, { background: 'transparent' }], {
71
66
  duration: 300,
72
67
  fill: 'forwards',
73
68
  easing: 'cubic-bezier(0.000, 0.245, 0.190, 0.790)',
@@ -75,7 +70,7 @@ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX
75
70
  }
76
71
  })
77
72
  manager.isLoading.subscribe(async (isLoading) => {
78
- const loader = el.querySelector('.loader-container')
73
+ const loader = element.querySelector('shade-loader')
79
74
  if (isLoading) {
80
75
  promisifyAnimation(loader, [{ opacity: 0 }, { opacity: 1 }], {
81
76
  duration: 100,
@@ -88,12 +83,7 @@ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX
88
83
  })
89
84
  }
90
85
  })
91
- manager.onSelectSuggestion.subscribe((value) => props.onSelectSuggestion(value))
92
- return () => manager.dispose()
93
- },
94
- render: ({ props, injector, element, getState }) => {
95
- element.style.flexGrow = '1'
96
- const { manager } = getState()
86
+ useObservable('onSelectSuggestion', manager.onSelectSuggestion, props.onSelectSuggestion)
97
87
  return (
98
88
  <div
99
89
  style={{ display: 'flex', flexDirection: 'column' }}
@@ -153,12 +143,11 @@ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX
153
143
  overflow: 'hidden',
154
144
  }}
155
145
  >
156
- <div
157
- className="loader-container"
146
+ <Loader
158
147
  style={{ width: '20px', height: '20px', opacity: manager.isLoading.getValue() ? '1' : '0' }}
159
- >
160
- <Loader style={{ width: '100%', height: '100%' }} />
161
- </div>
148
+ delay={0}
149
+ borderWidth={4}
150
+ />
162
151
  <div
163
152
  className="close-suggestions"
164
153
  onclick={() => manager.isOpened.setValue(false)}
@@ -1,27 +1,34 @@
1
1
  import { Shade, createComponent } from '@furystack/shades'
2
+ import { ThemeProviderService } from '../../services'
2
3
  import type { SuggestManager } from './suggest-manager'
3
4
 
4
- export const SuggestInput = Shade<{ manager: SuggestManager<any> }, { isOpened: boolean }>({
5
- getInitialState: ({ props }) => ({ isOpened: props.manager.isOpened.getValue() }),
6
- resources: ({ element, props }) => [
7
- props.manager.isOpened.subscribe(async (isOpened) => {
8
- const input = element.firstChild as HTMLInputElement
9
- if (isOpened) {
10
- input.focus()
11
- } else {
12
- input.value = ''
13
- }
14
- }),
15
- ],
5
+ export const SuggestInput = Shade<{ manager: SuggestManager<any> }>({
16
6
  shadowDomName: 'shades-suggest-input',
17
- render: ({ element }) => {
18
- element.style.width = '100%' //manager.isOpened.getValue() ? '100%' : '0%'
7
+ render: ({ element, props, useObservable, injector }) => {
8
+ const { theme } = injector.getInstance(ThemeProviderService)
9
+
10
+ element.style.width = '100%'
19
11
  element.style.overflow = 'hidden'
12
+
13
+ useObservable(
14
+ 'isOpened',
15
+ props.manager.isOpened,
16
+ (isOpened) => {
17
+ const input = element.firstChild as HTMLInputElement
18
+ if (isOpened) {
19
+ input.focus()
20
+ } else {
21
+ input.value = ''
22
+ }
23
+ },
24
+ true,
25
+ )
26
+
20
27
  return (
21
28
  <input
22
29
  autofocus
23
30
  style={{
24
- color: 'white',
31
+ color: theme.text.primary,
25
32
  outline: 'none',
26
33
  padding: '1em',
27
34
  background: 'transparent',
@@ -1,10 +1,11 @@
1
1
  import type { Injector } from '@furystack/inject'
2
2
  import { Injectable } from '@furystack/inject'
3
+ import type { Disposable } from '@furystack/utils'
3
4
  import { debounce, ObservableValue } from '@furystack/utils'
4
5
  import type { SuggestionResult } from './suggestion-result'
5
6
 
6
7
  @Injectable({ lifetime: 'singleton' })
7
- export class SuggestManager<T> {
8
+ export class SuggestManager<T> implements Disposable {
8
9
  public isOpened = new ObservableValue(false)
9
10
  public isLoading = new ObservableValue(false)
10
11
  public term = new ObservableValue('')
@@ -32,6 +33,12 @@ export class SuggestManager<T> {
32
33
  public dispose() {
33
34
  window.removeEventListener('keyup', this.keyPressListener)
34
35
  window.removeEventListener('click', this.clickOutsideListener)
36
+ this.isOpened.dispose()
37
+ this.isLoading.dispose()
38
+ this.term.dispose()
39
+ this.selectedIndex.dispose()
40
+ this.currentSuggestions.dispose()
41
+ this.onSelectSuggestion.dispose()
35
42
  }
36
43
 
37
44
  public selectSuggestion(index: number = this.selectedIndex.getValue()) {
@@ -46,13 +53,18 @@ export class SuggestManager<T> {
46
53
  if (this.lastGetSuggestionOptions?.term === options.term) {
47
54
  return
48
55
  }
56
+ const lastSelectedSuggestion = JSON.stringify(this.currentSuggestions.getValue()[this.selectedIndex.getValue()])
49
57
  this.isLoading.setValue(true)
50
58
  this.lastGetSuggestionOptions = options
51
- this.currentSuggestions.setValue([])
52
- this.selectedIndex.setValue(0)
53
59
  const newEntries = await this.getEntries(options.term)
54
60
  this.isOpened.setValue(true)
55
61
  this.currentSuggestions.setValue(newEntries.map((e) => ({ entry: e, suggestion: this.getSuggestionEntry(e) })))
62
+ this.selectedIndex.setValue(
63
+ Math.max(
64
+ 0,
65
+ this.currentSuggestions.getValue().findIndex((e) => JSON.stringify(e) === lastSelectedSuggestion),
66
+ ),
67
+ )
56
68
  } finally {
57
69
  this.isLoading.setValue(false)
58
70
  }
@@ -1,93 +1,98 @@
1
1
  import type { ChildrenList } from '@furystack/shades'
2
2
  import { Shade, createComponent } from '@furystack/shades'
3
3
  import { promisifyAnimation } from '../../utils/promisify-animation'
4
- import type { SuggestionResult } from './suggestion-result'
5
4
  import type { SuggestManager } from './suggest-manager'
5
+ import { ThemeProviderService } from '../../services'
6
6
 
7
- export const SuggestionList: <T>(
8
- props: { manager: SuggestManager<T> },
9
- children: ChildrenList,
10
- ) => JSX.Element<any, any> = Shade<{ manager: SuggestManager<any> }, { suggestions: SuggestionResult[] }>({
11
- shadowDomName: 'shade-suggest-suggestion-list',
12
- getInitialState: ({ props }) => ({
13
- suggestions: props.manager.currentSuggestions.getValue().map((v) => v.suggestion),
14
- }),
15
- resources: ({ updateState, element, props }) => [
16
- props.manager.currentSuggestions.subscribe((s) => {
17
- updateState({ suggestions: s.map((ss) => ss.suggestion) })
18
- }),
19
- props.manager.isOpened.subscribe(async (isOpened) => {
20
- const container = element.firstElementChild as HTMLDivElement
21
- if (isOpened) {
22
- container.style.zIndex = '1'
23
- container.style.width = `calc(${Math.round(
24
- element.parentElement?.getBoundingClientRect().width || 200,
25
- )}px - 3em)`
26
- await promisifyAnimation(
27
- container,
28
- [
29
- { opacity: 0, transform: 'translate(0, -50px)' },
30
- { opacity: 1, transform: 'translate(0, 0)' },
31
- ],
32
- { fill: 'forwards', duration: 500 },
33
- )
34
- } else {
35
- await promisifyAnimation(
36
- container,
37
- [
38
- { opacity: 1, transform: 'translate(0, 0)' },
39
- { opacity: 0, transform: 'translate(0, -50px)' },
40
- ],
41
- { fill: 'forwards', duration: 200 },
42
- )
43
- container.style.zIndex = '-1'
44
- }
45
- }, true), // TODO: Check initial state
46
- props.manager.selectedIndex.subscribe((idx) => {
47
- ;[...element.querySelectorAll('.suggestion-item')].map((s, i) => {
48
- if (i === idx) {
49
- ;(s as HTMLDivElement).style.background = 'rgba(128,128,128,0.2)'
7
+ export const SuggestionList: <T>(props: { manager: SuggestManager<T> }, children: ChildrenList) => JSX.Element<any> =
8
+ Shade<{ manager: SuggestManager<any> }>({
9
+ shadowDomName: 'shade-suggest-suggestion-list',
10
+ render: ({ element, props, injector, useObservable }) => {
11
+ const { manager } = props
12
+ const { theme } = injector.getInstance(ThemeProviderService)
13
+
14
+ const [suggestions] = useObservable('suggestions', manager.currentSuggestions)
15
+
16
+ const [selectedIndex] = useObservable(
17
+ 'selectedIndex',
18
+ manager.selectedIndex,
19
+ (idx) => {
20
+ ;([...element.querySelectorAll('.suggestion-item')] as HTMLDivElement[]).map((s, i) => {
21
+ if (i === idx) {
22
+ s.style.background = theme.background.paper
23
+ s.style.fontWeight = 'bolder'
24
+ } else {
25
+ s.style.background = theme.background.default
26
+ s.style.fontWeight = 'normal'
27
+ }
28
+ })
29
+ },
30
+ true,
31
+ )
32
+
33
+ const [isListOpened] = useObservable('isOpened', manager.isOpened, async (isOpened) => {
34
+ const container = element.firstElementChild as HTMLDivElement
35
+ if (isOpened) {
36
+ container.style.zIndex = '1'
37
+ container.style.width = `calc(${Math.round(
38
+ element.parentElement?.getBoundingClientRect().width || 200,
39
+ )}px - 3em)`
40
+ await promisifyAnimation(
41
+ container,
42
+ [
43
+ { opacity: 0, transform: 'translate(0, -50px)' },
44
+ { opacity: 1, transform: 'translate(0, 0)' },
45
+ ],
46
+ { fill: 'forwards', duration: 500 },
47
+ )
50
48
  } else {
51
- ;(s as HTMLDivElement).style.background = 'rgba(96,96,96,0.2)'
49
+ await promisifyAnimation(
50
+ container,
51
+ [
52
+ { opacity: 1, transform: 'translate(0, 0)' },
53
+ { opacity: 0, transform: 'translate(0, -50px)' },
54
+ ],
55
+ { fill: 'forwards', duration: 200 },
56
+ )
57
+ container.style.zIndex = '-1'
52
58
  }
53
59
  })
54
- }),
55
- ],
56
- render: ({ element, getState, props }) => {
57
- const { manager } = props
58
- return (
59
- <div
60
- className="suggestion-items-container"
61
- style={{
62
- borderTop: 'none',
63
- position: 'absolute',
64
- borderRadius: '0px 0px 5px 5px',
65
- marginLeft: '14px',
66
- overflow: 'hidden',
67
- zIndex: '1',
68
- left: 'auto',
69
- backgroundColor: 'rgba(8,8,8,0.85)',
70
- boxShadow: '3px 3px 5px rgba(0,0,0,0.3)',
71
- backdropFilter: 'blur(15px)',
72
- width: `calc(${Math.round(element.parentElement?.getBoundingClientRect().width || 200)}px - 3em)`,
73
- }}
74
- >
75
- {getState().suggestions.map((s, i) => (
76
- <div
77
- className="suggestion-item"
78
- onclick={() => {
79
- manager.isOpened.getValue() && manager.selectSuggestion(i)
80
- }}
81
- style={{
82
- padding: '1em',
83
- cursor: 'default',
84
- background: i === manager.selectedIndex.getValue() ? 'rgba(128,128,128,0.2)' : 'rgba(96,96,96,0.2)',
85
- }}
86
- >
87
- {s.element}
88
- </div>
89
- ))}
90
- </div>
91
- )
92
- },
93
- })
60
+
61
+ return (
62
+ <div
63
+ className="suggestion-items-container"
64
+ style={{
65
+ borderTop: 'none',
66
+ position: 'absolute',
67
+ borderRadius: '0px 0px 5px 5px',
68
+ marginLeft: '14px',
69
+ overflow: 'hidden',
70
+ zIndex: '1',
71
+ left: 'auto',
72
+ backgroundColor: theme.background.paper, //'rgba(8,8,8,0.85)',
73
+ color: theme.text.secondary,
74
+ boxShadow: '3px 3px 5px rgba(0,0,0,0.3)',
75
+ backdropFilter: 'blur(15px)',
76
+ width: `calc(${Math.round(element.parentElement?.getBoundingClientRect().width || 200)}px - 3em)`,
77
+ }}
78
+ >
79
+ {suggestions.map((s, i) => (
80
+ <div
81
+ className="suggestion-item"
82
+ onclick={() => {
83
+ isListOpened && manager.selectSuggestion(i)
84
+ }}
85
+ style={{
86
+ padding: '1em',
87
+ cursor: 'default',
88
+ background: i === selectedIndex ? theme.background.paper : theme.background.default,
89
+ fontWeight: i === selectedIndex ? 'bolder' : 'normal',
90
+ }}
91
+ >
92
+ {s.suggestion.element}
93
+ </div>
94
+ ))}
95
+ </div>
96
+ )
97
+ },
98
+ })