@furystack/shades-common-components 3.6.1 → 4.0.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.
Files changed (149) hide show
  1. package/dist/components/app-bar-link.js +18 -20
  2. package/dist/components/app-bar-link.js.map +1 -1
  3. package/dist/components/app-bar.js +7 -6
  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 +32 -28
  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 +4 -8
  20. package/dist/components/data-grid/data-grid.js.map +1 -1
  21. package/dist/components/data-grid/footer.js +8 -13
  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.map +1 -1
  30. package/dist/components/inputs/autocomplete.js +3 -13
  31. package/dist/components/inputs/autocomplete.js.map +1 -1
  32. package/dist/components/inputs/input.js +22 -26
  33. package/dist/components/inputs/input.js.map +1 -1
  34. package/dist/components/inputs/text-area.js +2 -6
  35. package/dist/components/inputs/text-area.js.map +1 -1
  36. package/dist/components/loader.js +9 -5
  37. package/dist/components/loader.js.map +1 -1
  38. package/dist/components/modal.js +7 -17
  39. package/dist/components/modal.js.map +1 -1
  40. package/dist/components/noty-list.js +26 -28
  41. package/dist/components/noty-list.js.map +1 -1
  42. package/dist/components/paper.js +11 -5
  43. package/dist/components/paper.js.map +1 -1
  44. package/dist/components/suggest/index.js +14 -20
  45. package/dist/components/suggest/index.js.map +1 -1
  46. package/dist/components/suggest/suggest-input.js +9 -10
  47. package/dist/components/suggest/suggest-input.js.map +1 -1
  48. package/dist/components/suggest/suggest-manager.js +8 -2
  49. package/dist/components/suggest/suggest-manager.js.map +1 -1
  50. package/dist/components/suggest/suggestion-list.js +27 -30
  51. package/dist/components/suggest/suggestion-list.js.map +1 -1
  52. package/dist/components/tabs.js +32 -36
  53. package/dist/components/tabs.js.map +1 -1
  54. package/dist/components/wizard/index.js +4 -7
  55. package/dist/components/wizard/index.js.map +1 -1
  56. package/dist/services/collection-service.js +68 -56
  57. package/dist/services/collection-service.js.map +1 -1
  58. package/package.json +6 -6
  59. package/src/components/app-bar-link.tsx +27 -30
  60. package/src/components/app-bar.tsx +21 -26
  61. package/src/components/avatar.tsx +40 -36
  62. package/src/components/button.tsx +85 -88
  63. package/src/components/command-palette/command-palette-input.tsx +15 -13
  64. package/src/components/command-palette/command-palette-suggestion-list.tsx +50 -58
  65. package/src/components/command-palette/index.tsx +54 -52
  66. package/src/components/data-grid/body.tsx +10 -22
  67. package/src/components/data-grid/data-grid-row.tsx +64 -63
  68. package/src/components/data-grid/data-grid.tsx +11 -11
  69. package/src/components/data-grid/footer.tsx +13 -16
  70. package/src/components/data-grid/header.tsx +34 -30
  71. package/src/components/data-grid/selection-cell.tsx +9 -19
  72. package/src/components/fab.tsx +21 -23
  73. package/src/components/grid.tsx +1 -1
  74. package/src/components/inputs/autocomplete.tsx +10 -16
  75. package/src/components/inputs/input.tsx +27 -27
  76. package/src/components/inputs/text-area.tsx +3 -11
  77. package/src/components/loader.tsx +20 -5
  78. package/src/components/modal.tsx +9 -17
  79. package/src/components/noty-list.tsx +54 -53
  80. package/src/components/paper.tsx +18 -17
  81. package/src/components/suggest/index.tsx +19 -30
  82. package/src/components/suggest/suggest-input.tsx +22 -15
  83. package/src/components/suggest/suggest-manager.ts +15 -3
  84. package/src/components/suggest/suggestion-list.tsx +90 -85
  85. package/src/components/tabs.tsx +50 -62
  86. package/src/components/wizard/index.tsx +6 -13
  87. package/src/services/collection-service.ts +79 -63
  88. package/tsconfig.json +1 -1
  89. package/tsconfig.tsbuildinfo +1 -1
  90. package/types/components/app-bar-link.d.ts +1 -1
  91. package/types/components/app-bar-link.d.ts.map +1 -1
  92. package/types/components/app-bar.d.ts +1 -1
  93. package/types/components/app-bar.d.ts.map +1 -1
  94. package/types/components/avatar.d.ts +2 -1
  95. package/types/components/avatar.d.ts.map +1 -1
  96. package/types/components/button.d.ts +1 -1
  97. package/types/components/button.d.ts.map +1 -1
  98. package/types/components/command-palette/command-palette-input.d.ts +1 -1
  99. package/types/components/command-palette/command-palette-input.d.ts.map +1 -1
  100. package/types/components/command-palette/command-palette-suggestion-list.d.ts +1 -1
  101. package/types/components/command-palette/command-palette-suggestion-list.d.ts.map +1 -1
  102. package/types/components/command-palette/index.d.ts +1 -5
  103. package/types/components/command-palette/index.d.ts.map +1 -1
  104. package/types/components/data-grid/body.d.ts +1 -5
  105. package/types/components/data-grid/body.d.ts.map +1 -1
  106. package/types/components/data-grid/data-grid-row.d.ts +1 -5
  107. package/types/components/data-grid/data-grid-row.d.ts.map +1 -1
  108. package/types/components/data-grid/data-grid.d.ts +5 -3
  109. package/types/components/data-grid/data-grid.d.ts.map +1 -1
  110. package/types/components/data-grid/footer.d.ts +1 -1
  111. package/types/components/data-grid/footer.d.ts.map +1 -1
  112. package/types/components/data-grid/header.d.ts +1 -1
  113. package/types/components/data-grid/header.d.ts.map +1 -1
  114. package/types/components/data-grid/selection-cell.d.ts +1 -1
  115. package/types/components/data-grid/selection-cell.d.ts.map +1 -1
  116. package/types/components/fab.d.ts +1 -1
  117. package/types/components/fab.d.ts.map +1 -1
  118. package/types/components/grid.d.ts +1 -1
  119. package/types/components/grid.d.ts.map +1 -1
  120. package/types/components/inputs/autocomplete.d.ts +1 -1
  121. package/types/components/inputs/autocomplete.d.ts.map +1 -1
  122. package/types/components/inputs/input.d.ts +1 -1
  123. package/types/components/inputs/input.d.ts.map +1 -1
  124. package/types/components/inputs/text-area.d.ts +1 -4
  125. package/types/components/inputs/text-area.d.ts.map +1 -1
  126. package/types/components/loader.d.ts +9 -1
  127. package/types/components/loader.d.ts.map +1 -1
  128. package/types/components/modal.d.ts +1 -1
  129. package/types/components/modal.d.ts.map +1 -1
  130. package/types/components/noty-list.d.ts +2 -2
  131. package/types/components/noty-list.d.ts.map +1 -1
  132. package/types/components/paper.d.ts +1 -1
  133. package/types/components/paper.d.ts.map +1 -1
  134. package/types/components/skeleton.d.ts +1 -1
  135. package/types/components/skeleton.d.ts.map +1 -1
  136. package/types/components/suggest/index.d.ts +1 -5
  137. package/types/components/suggest/index.d.ts.map +1 -1
  138. package/types/components/suggest/suggest-input.d.ts +1 -1
  139. package/types/components/suggest/suggest-input.d.ts.map +1 -1
  140. package/types/components/suggest/suggest-manager.d.ts +2 -1
  141. package/types/components/suggest/suggest-manager.d.ts.map +1 -1
  142. package/types/components/suggest/suggestion-list.d.ts +1 -1
  143. package/types/components/suggest/suggestion-list.d.ts.map +1 -1
  144. package/types/components/tabs.d.ts +2 -2
  145. package/types/components/tabs.d.ts.map +1 -1
  146. package/types/components/wizard/index.d.ts +2 -2
  147. package/types/components/wizard/index.d.ts.map +1 -1
  148. package/types/services/collection-service.d.ts +4 -0
  149. package/types/services/collection-service.d.ts.map +1 -1
@@ -6,6 +6,7 @@ import { CommandPaletteInput } from './command-palette-input'
6
6
  import { CommandPaletteSuggestionList } from './command-palette-suggestion-list'
7
7
  import type { CommandProvider } from './command-provider'
8
8
  import { ClickAwayService } from '../../services/click-away-service'
9
+ import { ThemeProviderService } from '../../services'
9
10
 
10
11
  export * from './command-palette-input'
11
12
  export * from './command-palette-manager'
@@ -19,23 +20,35 @@ export interface CommandPaletteProps {
19
20
  fullScreenSuggestions?: boolean
20
21
  }
21
22
 
22
- export interface CommandPaletteState {
23
- manager: CommandPaletteManager
24
- }
25
-
26
- export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
23
+ export const CommandPalette = Shade<CommandPaletteProps>({
27
24
  shadowDomName: 'shade-command-palette',
28
- getInitialState: ({ props }) => ({
29
- manager: new CommandPaletteManager(props.commandProviders),
30
- }),
31
- resources: ({ getState, element: rootElement }) => {
32
- const { manager } = getState()
33
- const element = rootElement.querySelector('.input-container') as HTMLDivElement
34
- const clickAwayListener = new ClickAwayService(rootElement, () => manager.isOpened.setValue(false))
35
- return [
36
- manager.isOpened.subscribe((isOpened) => {
37
- const suggestions = rootElement.querySelector('.close-suggestions')
38
- const postControls = rootElement.querySelector('.post-controls')
25
+ render: ({ props, injector, element, useState, useDisposable, useObservable }) => {
26
+ element.style.flexGrow = '1'
27
+ const [manager] = useState('manager', new CommandPaletteManager(props.commandProviders))
28
+ const { theme } = injector.getInstance(ThemeProviderService)
29
+
30
+ useDisposable('clickAwayService', () => new ClickAwayService(element, () => manager.isOpened.setValue(false)))
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
39
52
  if (isOpened) {
40
53
  promisifyAnimation(suggestions, [{ opacity: 0 }, { opacity: 1 }], {
41
54
  duration: 500,
@@ -47,11 +60,15 @@ export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
47
60
  fill: 'forwards',
48
61
  })
49
62
 
50
- promisifyAnimation(element, [{ background: 'transparent' }, { background: 'rgba(128,128,128,0.1)' }], {
51
- duration: 500,
52
- fill: 'forwards',
53
- easing: 'cubic-bezier(0.050, 0.570, 0.840, 1.005)',
54
- })
63
+ promisifyAnimation(
64
+ inputContainer,
65
+ [{ background: 'transparent' }, { background: theme.background.default }],
66
+ {
67
+ duration: 500,
68
+ fill: 'forwards',
69
+ easing: 'cubic-bezier(0.050, 0.570, 0.840, 1.005)',
70
+ },
71
+ )
55
72
  } else {
56
73
  promisifyAnimation(suggestions, [{ opacity: 1 }, { opacity: 0 }], {
57
74
  duration: 500,
@@ -64,34 +81,19 @@ export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
64
81
  delay: 300,
65
82
  })
66
83
 
67
- promisifyAnimation(element, [{ background: 'rgba(128,128,128,0.1)' }, { background: 'transparent' }], {
68
- duration: 300,
69
- fill: 'forwards',
70
- easing: 'cubic-bezier(0.000, 0.245, 0.190, 0.790)',
71
- })
84
+ promisifyAnimation(
85
+ inputContainer,
86
+ [{ background: theme.background.default }, { background: 'transparent' }],
87
+ {
88
+ duration: 300,
89
+ fill: 'forwards',
90
+ easing: 'cubic-bezier(0.000, 0.245, 0.190, 0.790)',
91
+ },
92
+ )
72
93
  }
73
- }),
74
- manager.isLoading.subscribe(async (isLoading) => {
75
- const loader = rootElement.querySelector('.loader-container')
76
- if (isLoading) {
77
- promisifyAnimation(loader, [{ opacity: 0 }, { opacity: 1 }], {
78
- duration: 100,
79
- fill: 'forwards',
80
- })
81
- } else {
82
- promisifyAnimation(loader, [{ opacity: 1 }, { opacity: 0 }], {
83
- duration: 100,
84
- fill: 'forwards',
85
- })
86
- }
87
- }),
88
- clickAwayListener,
89
- manager,
90
- ]
91
- },
92
- render: ({ props, injector, element, getState }) => {
93
- element.style.flexGrow = '1'
94
- const { manager } = getState()
94
+ }
95
+ })
96
+
95
97
  return (
96
98
  <div
97
99
  style={{ display: 'flex', flexDirection: 'column' }}
@@ -135,7 +137,7 @@ export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
135
137
  fontWeight: 'bolder',
136
138
  textShadow: '0 0 1px #aaa',
137
139
  }}
138
- onclick={() => manager.isOpened.setValue(true)}
140
+ onclick={() => setIsOpened(true)}
139
141
  >
140
142
  {props.defaultPrefix}
141
143
  </div>
@@ -146,19 +148,19 @@ export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
146
148
  display: 'flex',
147
149
  alignItems: 'center',
148
150
  justifyContent: 'space-between',
149
- width: manager.isOpened.getValue() ? '50px' : '0px',
151
+ width: isOpenedAtRender ? '50px' : '0px',
150
152
  overflow: 'hidden',
151
153
  }}
152
154
  >
153
155
  <div
154
156
  className="loader-container"
155
- style={{ width: '20px', height: '20px', opacity: manager.isLoading.getValue() ? '1' : '0' }}
157
+ style={{ width: '20px', height: '20px', opacity: isLoadingAtRender ? '1' : '0' }}
156
158
  >
157
159
  <Loader style={{ width: '100%', height: '100%' }} />
158
160
  </div>
159
161
  <div
160
162
  className="close-suggestions"
161
- onclick={() => manager.isOpened.setValue(false)}
163
+ onclick={() => setIsOpened(false)}
162
164
  style={{
163
165
  width: '20px',
164
166
  height: '20px',
@@ -1,6 +1,6 @@
1
1
  import type { CollectionService } from '../../services/collection-service'
2
2
  import type { ChildrenList } from '@furystack/shades'
3
- import { Shade, createComponent, createFragment } from '@furystack/shades'
3
+ import { Shade, createComponent } from '@furystack/shades'
4
4
  import type { DataRowCells } from './data-grid'
5
5
  import { Loader } from '../loader'
6
6
  import { DataGridRow } from './data-grid-row'
@@ -20,29 +20,17 @@ export interface DataGridBodyProps<T> {
20
20
  loaderComponent?: JSX.Element
21
21
  }
22
22
 
23
- export interface DataGridBodyState<T> {
24
- data: T[]
25
- isLoading: boolean
26
- }
27
-
28
- export const DataGridBody: <T>(props: DataGridBodyProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade<
29
- DataGridBodyProps<any>,
30
- DataGridBodyState<any>
23
+ export const DataGridBody: <T>(props: DataGridBodyProps<T>, children: ChildrenList) => JSX.Element<any> = Shade<
24
+ DataGridBodyProps<any>
31
25
  >({
32
- getInitialState: ({ props }) => ({
33
- data: props.service.data.getValue().entries,
34
- isLoading: props.service.isLoading.getValue(),
35
- }),
36
- resources: ({ props, updateState }) => [
37
- props.service.data.subscribe((data) => updateState({ data: data.entries })),
38
- props.service.isLoading.subscribe((isLoading) => updateState({ isLoading })),
39
- ],
40
26
  shadowDomName: 'shade-data-grid-body',
41
- render: ({ getState, props, element }) => {
27
+ render: ({ props, element, useObservable }) => {
42
28
  element.style.display = 'table-row-group'
43
- const state = getState()
44
29
 
45
- if (state.isLoading) {
30
+ const [data] = useObservable('data', props.service.data)
31
+ const [isLoading] = useObservable('isLoading', props.service.isLoading)
32
+
33
+ if (isLoading) {
46
34
  return (
47
35
  <div style={{ display: 'flex', height: '100%', justifyContent: 'center', alignItems: 'center', width: '100%' }}>
48
36
  {/* TODO: Skeleton */}
@@ -51,13 +39,13 @@ export const DataGridBody: <T>(props: DataGridBodyProps<T>, children: ChildrenLi
51
39
  )
52
40
  }
53
41
 
54
- if (!state.data?.length) {
42
+ if (!data?.entries?.length) {
55
43
  return props.emptyComponent || <div> - No Data - </div>
56
44
  }
57
45
 
58
46
  return (
59
47
  <>
60
- {state.data.map((entry) => (
48
+ {data?.entries?.map((entry) => (
61
49
  <DataGridRow<any>
62
50
  columns={props.columns}
63
51
  entry={entry}
@@ -1,5 +1,6 @@
1
1
  import type { ChildrenList } from '@furystack/shades'
2
- import { attachStyles, createComponent, createFragment, Shade } from '@furystack/shades'
2
+ import { attachStyles, createComponent, Shade } from '@furystack/shades'
3
+ import { ThemeProviderService } from '../../services'
3
4
  import type { CollectionService } from '../../services/collection-service'
4
5
  import type { DataRowCells } from './data-grid'
5
6
 
@@ -16,87 +17,88 @@ export interface DataGridRowProps<T> {
16
17
  unselectedRowStyle?: Partial<CSSStyleDeclaration>
17
18
  }
18
19
 
19
- export interface DataGridRowState<T> {
20
- selection?: T[]
21
- focus?: T
22
- }
23
-
24
- export const DataGridRow: <T>(props: DataGridRowProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade<
25
- DataGridRowProps<any>,
26
- DataGridRowState<any>
20
+ export const DataGridRow: <T>(props: DataGridRowProps<T>, children: ChildrenList) => JSX.Element<any> = Shade<
21
+ DataGridRowProps<any>
27
22
  >({
28
- getInitialState: ({ props }) => ({
29
- focus: props.service.focusedEntry.getValue(),
30
- selection: props.service.selection.getValue(),
31
- }),
32
23
  shadowDomName: 'shades-data-grid-row',
33
- resources: ({ props, element }) => [
34
- props.service.focusedEntry.subscribe((newEntry) => {
35
- if (newEntry === props.entry) {
36
- attachStyles(element, {
37
- style: props.focusedRowStyle || {
38
- filter: 'brightness(1.5)',
39
- fontWeight: 'bolder',
40
- },
41
- })
42
24
 
43
- element.classList.add('focused')
25
+ render: ({ props, element, useObservable, injector }) => {
26
+ const { entry, rowComponents, columns, service } = props
44
27
 
45
- const headerHeight = element.closest('table')?.querySelector('th')?.getBoundingClientRect().height || 42
28
+ const { theme } = injector.getInstance(ThemeProviderService)
46
29
 
47
- const parent = element.closest('.shade-grid-wrapper') as HTMLElement
48
- const maxTop = element.offsetTop - headerHeight
49
- const currentTop = parent.scrollTop
50
- if (maxTop < currentTop) {
51
- parent.scrollTo({ top: maxTop, behavior: 'smooth' })
52
- }
53
-
54
- const footerHeight =
55
- element.closest('shade-data-grid')?.querySelector('shade-data-grid-footer')?.getBoundingClientRect().height ||
56
- 42
57
- const visibleMaxTop = parent.clientHeight - footerHeight
58
- const desiredMaxTop = element.offsetTop + element.clientHeight
59
- if (desiredMaxTop > visibleMaxTop) {
60
- parent.scrollTo({ top: desiredMaxTop - visibleMaxTop, behavior: 'smooth' })
61
- }
62
- } else {
63
- element.classList.remove('focused')
64
- attachStyles(element, {
65
- style: props.unfocusedRowStyle || {
66
- filter: 'brightness(1)',
67
- fontWeight: 'inherit',
68
- },
69
- })
70
- }
71
- }, true),
72
- props.service.selection.subscribe((selection) => {
73
- if (selection.includes(props.entry)) {
30
+ const attachSelectedStyles = (selection: any[]) => {
31
+ if (selection.includes(entry)) {
74
32
  element.classList.add('selected')
75
- attachStyles(element, { style: props.selectedRowStyle || { backgroundColor: 'rgba(128,128,128,0.1)' } })
33
+ attachStyles(element, { style: props.selectedRowStyle || { backgroundColor: theme.background.default } })
76
34
  element.setAttribute('aria-selected', 'true')
77
35
  } else {
78
36
  element.classList.remove('selected')
79
37
  attachStyles(element, { style: props.unselectedRowStyle || { backgroundColor: 'transparent' } })
80
38
  element.setAttribute('aria-selected', 'false')
81
39
  }
82
- }, true),
83
- ],
40
+ }
41
+
42
+ const [selection] = useObservable('isSelected', service.selection, attachSelectedStyles, true)
43
+
44
+ attachSelectedStyles(selection)
45
+
46
+ const [focus] = useObservable(
47
+ 'focus',
48
+ service.focusedEntry,
49
+ (newEntry) => {
50
+ if (newEntry === props.entry) {
51
+ attachStyles(element, {
52
+ style: props.focusedRowStyle || {
53
+ boxShadow: `0 0 0 1px ${theme.palette.primary.main}`,
54
+ fontWeight: 'bolder',
55
+ },
56
+ })
57
+
58
+ element.classList.add('focused')
59
+
60
+ const headerHeight = element.closest('table')?.querySelector('th')?.getBoundingClientRect().height || 42
61
+
62
+ const parent = element.closest('.shade-grid-wrapper') as HTMLElement
63
+ const maxTop = element.offsetTop - headerHeight
64
+ const currentTop = parent.scrollTop
65
+ if (maxTop < currentTop) {
66
+ parent.scrollTo({ top: maxTop, behavior: 'smooth' })
67
+ }
68
+
69
+ const footerHeight =
70
+ element.closest('shade-data-grid')?.querySelector('shade-data-grid-footer')?.getBoundingClientRect()
71
+ .height || 42
72
+ const visibleMaxTop = parent.clientHeight - footerHeight
73
+ const desiredMaxTop = element.offsetTop + element.clientHeight
74
+ if (desiredMaxTop > visibleMaxTop) {
75
+ parent.scrollTo({ top: desiredMaxTop - visibleMaxTop, behavior: 'smooth' })
76
+ }
77
+ } else {
78
+ element.classList.remove('focused')
79
+ attachStyles(element, {
80
+ style: props.unfocusedRowStyle || {
81
+ boxShadow: 'none',
82
+ fontWeight: 'inherit',
83
+ },
84
+ })
85
+ }
86
+ },
87
+ true,
88
+ )
84
89
 
85
- render: ({ getState, props, element }) => {
86
- const state = getState()
87
- const { entry, rowComponents, columns } = props
88
90
  element.style.display = 'table-row'
89
91
  element.style.cursor = 'default'
90
92
  element.style.userSelect = 'none'
91
- if (state.selection?.includes(entry)) {
93
+ if (selection?.includes(entry)) {
92
94
  element.setAttribute('aria-selected', 'true')
93
95
  element.classList.add('selected')
94
96
  }
95
97
 
96
- if (state.focus === entry) {
98
+ if (focus === entry) {
97
99
  element.classList.add('focused')
98
100
  }
99
- element.setAttribute('aria-selected', state.selection?.includes(entry).toString() || 'false')
101
+ element.setAttribute('aria-selected', selection?.includes(entry).toString() || 'false')
100
102
 
101
103
  return (
102
104
  <>
@@ -106,9 +108,8 @@ export const DataGridRow: <T>(props: DataGridRowProps<T>, children: ChildrenList
106
108
  onclick={(ev) => props.onRowClick?.(entry, ev)}
107
109
  ondblclick={(ev) => props.onRowDoubleClick?.(entry, ev)}
108
110
  >
109
- {rowComponents?.[column]?.(entry, state) || rowComponents?.default?.(entry, state) || (
110
- <span>{entry[column]}</span>
111
- )}
111
+ {rowComponents?.[column]?.(entry, { selection, focus }) ||
112
+ rowComponents?.default?.(entry, { selection, focus }) || <span>{entry[column]}</span>}
112
113
  </td>
113
114
  ))}
114
115
  </>
@@ -6,13 +6,12 @@ import { DataGridHeader } from './header'
6
6
  import { DataGridBody } from './body'
7
7
  import { DataGridFooter } from './footer'
8
8
  import { ClickAwayService, ThemeProviderService } from '../../services'
9
- import type { DataGridRowState } from './data-grid-row'
10
9
 
11
10
  export type DataHeaderCells<T> = {
12
11
  [TKey in keyof T | 'default']?: (name: keyof T) => JSX.Element
13
12
  }
14
13
  export type DataRowCells<T> = {
15
- [TKey in keyof T | 'default']?: (element: T, state: DataGridRowState<T>) => JSX.Element
14
+ [TKey in keyof T | 'default']?: (element: T, state: { focus?: T; selection: T[] }) => JSX.Element
16
15
  }
17
16
 
18
17
  export interface DataGridProps<T> {
@@ -68,26 +67,27 @@ export interface DataGridProps<T> {
68
67
  loaderComponent?: JSX.Element
69
68
  }
70
69
 
71
- export const DataGrid: <T>(props: DataGridProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade<
70
+ export const DataGrid: <T>(props: DataGridProps<T>, children: ChildrenList) => JSX.Element<any> = Shade<
72
71
  DataGridProps<any>
73
72
  >({
74
73
  shadowDomName: 'shade-data-grid',
75
- resources: ({ element, props }) => {
76
- return [
77
- new ClickAwayService(element, () => {
78
- props.service.hasFocus.setValue(false)
79
- }),
80
- ]
81
- },
82
74
  constructed: ({ props }) => {
83
75
  const listener = (ev: KeyboardEvent) => props.service.handleKeyDown(ev)
84
76
  window.addEventListener('keydown', listener)
85
77
  return () => window.removeEventListener('keydown', listener)
86
78
  },
87
- render: ({ props, injector }) => {
79
+ render: ({ props, injector, useDisposable, element }) => {
88
80
  const tp = injector.getInstance(ThemeProviderService)
89
81
  const { theme } = tp
90
82
 
83
+ useDisposable(
84
+ 'clickAway',
85
+ () =>
86
+ new ClickAwayService(element, () => {
87
+ props.service.hasFocus.setValue(false)
88
+ }),
89
+ )
90
+
91
91
  const headerStyle: Partial<CSSStyleDeclaration> = {
92
92
  backdropFilter: 'blur(12px)',
93
93
  padding: '12px 0',
@@ -1,26 +1,23 @@
1
1
  import { Shade, createComponent } from '@furystack/shades'
2
2
  import { ThemeProviderService } from '../../services'
3
- import type { CollectionService, CollectionData } from '../../services/collection-service'
3
+ import type { CollectionService } from '../../services/collection-service'
4
4
 
5
5
  export const dataGridItemsPerPage = [10, 20, 25, 50, 100, Infinity]
6
6
 
7
- export const DataGridFooter = Shade<{ service: CollectionService<any> }, { data: CollectionData<any> }>({
7
+ export const DataGridFooter = Shade<{ service: CollectionService<any> }>({
8
8
  shadowDomName: 'shade-data-grid-footer',
9
- getInitialState: ({ props }) => ({
10
- data: props.service.data.getValue(),
11
- }),
12
- constructed: ({ props, updateState }) => {
13
- const disposables = [props.service.data.subscribe((data) => updateState({ data }))]
14
-
15
- return () => disposables.forEach((d) => d.dispose())
16
- },
17
- render: ({ props, getState, injector }) => {
18
- const state = getState()
19
- const currentQuerySettings = props.service.querySettings.getValue()
20
- const currentPage = Math.ceil(currentQuerySettings.skip || 0) / (currentQuerySettings.top || 1)
21
- const currentEntriesPerPage = currentQuerySettings.top || Infinity
9
+ render: ({ props, injector, useObservable }) => {
22
10
  const { theme } = injector.getInstance(ThemeProviderService)
23
11
 
12
+ const [currentData] = useObservable('dataUpdater', props.service.data)
13
+
14
+ const [currentQuerySettings] = useObservable('querySettings', props.service.querySettings)
15
+
16
+ const top = currentQuerySettings.top || Infinity
17
+ const skip = currentQuerySettings.skip || 0
18
+ const currentPage = Math.ceil(skip) / (top || 1)
19
+ const currentEntriesPerPage = top
20
+
24
21
  return (
25
22
  <div
26
23
  className="pager"
@@ -47,7 +44,7 @@ export const DataGridFooter = Shade<{ service: CollectionService<any> }, { data:
47
44
  }}
48
45
  >
49
46
  {[
50
- ...new Array(Math.ceil(state.data.count / (props.service.querySettings.getValue().top || Infinity))),
47
+ ...new Array(Math.ceil(currentData.count / (props.service.querySettings.getValue().top || Infinity))),
51
48
  ].map((_val, index) => (
52
49
  <option value={index.toString()} selected={currentPage === index}>
53
50
  {(index + 1).toString()}
@@ -19,37 +19,41 @@ export interface DataGridHeaderState<T> {
19
19
  export const DataGridHeader: <T, K extends keyof T>(
20
20
  props: DataGridHeaderProps<T, K>,
21
21
  children: ChildrenList,
22
- ) => JSX.Element<any, any> = Shade<DataGridHeaderProps<any, any>, DataGridHeaderState<any>>({
22
+ ) => JSX.Element<any> = Shade<DataGridHeaderProps<any, any>>({
23
23
  shadowDomName: 'data-grid-header',
24
- getInitialState: ({ props }) => ({
25
- querySettings: props.collectionService.querySettings.getValue(),
26
- isSearchOpened: false,
27
- updateSearchValue: debounce((value: string) => {
28
- const currentSettings = props.collectionService.querySettings.getValue()
29
- const newSettings: FindOptions<unknown, any> = {
30
- ...currentSettings,
31
- filter: {
32
- ...currentSettings.filter,
33
- [props.field]: { $regex: value },
34
- },
35
- }
36
- props.collectionService.querySettings.setValue(newSettings)
37
- }),
38
- }),
39
- constructed: ({ props, updateState, getState }) => {
40
- const disposable = props.collectionService.querySettings.subscribe((querySettings) =>
41
- updateState({ querySettings }, getState().isSearchOpened),
24
+ // getInitialState: ({ props }) => ({
25
+ // querySettings: props.collectionService.querySettings.getValue(),
26
+ // isSearchOpened: false,
27
+ // updateSearchValue: ,
28
+ // }),
29
+ render: ({ props, element, useState, useObservable }) => {
30
+ const [querySettings, setQuerySettings] = useObservable('querySettings', props.collectionService.querySettings)
31
+ const currentOrder = Object.keys(querySettings.order || {})[0]
32
+ const currentOrderDirection = Object.values(querySettings.order || {})[0]
33
+
34
+ const [isSearchOpened, setIsSearchOpened] = useState('isSearchOpened', false)
35
+ const [updateSearchValue] = useState(
36
+ 'updateSearchValue',
37
+ debounce((value: string) => {
38
+ const currentSettings = props.collectionService.querySettings.getValue()
39
+ const newSettings: FindOptions<unknown, any> = {
40
+ ...currentSettings,
41
+ filter: {
42
+ ...currentSettings.filter,
43
+ [props.field]: { $regex: value },
44
+ },
45
+ }
46
+ props.collectionService.querySettings.setValue(newSettings)
47
+ }),
42
48
  )
43
- return () => disposable.dispose()
44
- },
45
- render: ({ getState, props, updateState, element }) => {
46
- const currentState = getState()
47
- const currentOrder = Object.keys(currentState.querySettings.order || {})[0]
48
- const currentOrderDirection = Object.values(currentState.querySettings.order || {})[0]
49
49
 
50
50
  const filterValue = (props.collectionService.querySettings.getValue().filter as any)?.[props.field]?.$regex || ''
51
51
 
52
- if (currentState.isSearchOpened) {
52
+ useObservable('querySettingsChange', props.collectionService.querySettings, (newSettings) => {
53
+ setQuerySettings(newSettings)
54
+ })
55
+
56
+ if (isSearchOpened) {
53
57
  setTimeout(() => {
54
58
  element.querySelector('input')?.focus()
55
59
  }, 1)
@@ -59,8 +63,8 @@ export const DataGridHeader: <T, K extends keyof T>(
59
63
  value={filterValue}
60
64
  placeholder={props.field}
61
65
  autofocus
62
- onblur={() => updateState({ isSearchOpened: false })}
63
- onkeyup={(ev) => currentState.updateSearchValue((ev.target as HTMLInputElement).value)}
66
+ onblur={() => setIsSearchOpened(false)}
67
+ onkeyup={(ev) => updateSearchValue((ev.target as HTMLInputElement).value)}
64
68
  labelProps={{
65
69
  style: { padding: '0px 2em' },
66
70
  }}
@@ -71,7 +75,7 @@ export const DataGridHeader: <T, K extends keyof T>(
71
75
  return (
72
76
  <div
73
77
  style={{ display: 'flex', width: '100%', height: '100%', justifyContent: 'space-around' }}
74
- onclick={() => updateState({ isSearchOpened: true })}
78
+ onclick={() => setIsSearchOpened(true)}
75
79
  >
76
80
  <div>{props.field}</div>
77
81
  <div className="header-controls" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
@@ -88,7 +92,7 @@ export const DataGridHeader: <T, K extends keyof T>(
88
92
  }
89
93
  newOrder[props.field] = newDirection
90
94
  props.collectionService.querySettings.setValue({
91
- ...currentState.querySettings,
95
+ ...querySettings,
92
96
  order: newOrder,
93
97
  })
94
98
  }}
@@ -3,29 +3,19 @@ import type { CollectionService } from '../../services'
3
3
 
4
4
  export const SelectionCell = Shade<{ entry: any; service: CollectionService<any> }>({
5
5
  shadowDomName: 'shades-data-grid-selection-cell',
6
- resources: ({ props, element }) => [
7
- props.service.selection.subscribe((selection) => {
8
- if (selection.includes(props.entry)) {
9
- ;(element.firstChild as HTMLInputElement).checked = true
10
- } else {
11
- ;(element.firstChild as HTMLInputElement).checked = false
12
- }
13
- }),
14
- ],
15
- render: ({ props }) => {
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)
9
+ })
10
+ const isSelected = selection.includes(props.entry)
11
+
16
12
  return (
17
13
  <input
18
- onchange={(ev) => {
19
- if ((ev.target as HTMLInputElement).checked) {
20
- props.service.selection.setValue([...props.service.selection.getValue(), props.entry])
21
- } else {
22
- props.service.selection.setValue([
23
- ...props.service.selection.getValue().filter((entry) => entry !== props.entry),
24
- ])
25
- }
14
+ onchange={() => {
15
+ props.service.toggleSelection(props.entry)
26
16
  }}
27
17
  type="checkbox"
28
- checked={props.service.selection.getValue().includes(props.entry) ? true : false}
18
+ checked={isSelected}
29
19
  />
30
20
  )
31
21
  },