@furystack/shades-common-components 3.6.1 → 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 (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 +10 -0
  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 +14 -0
  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
@@ -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
  },
@@ -1,30 +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
 
4
5
  export const Fab = Shade<PartialElement<HTMLDivElement>>({
5
6
  shadowDomName: 'shade-fab',
6
- render: ({ props, children }) => {
7
- return (
8
- <div
9
- {...props}
10
- style={{
11
- position: 'fixed',
12
- bottom: '32px',
13
- right: '32px',
14
- background: 'gray',
15
- width: '64px',
16
- height: '64px',
17
- display: 'flex',
18
- justifyContent: 'center',
19
- alignItems: 'center',
20
- borderRadius: '50%',
21
- boxShadow: '2px 2px 4px rgba(0,0,0,0.3)',
22
- cursor: 'pointer',
23
- ...props.style,
24
- }}
25
- >
26
- {children}
27
- </div>
28
- )
7
+ render: ({ props, children, element }) => {
8
+ attachProps(element, {
9
+ ...props,
10
+ style: {
11
+ position: 'fixed',
12
+ bottom: '32px',
13
+ right: '32px',
14
+ background: 'gray',
15
+ width: '64px',
16
+ height: '64px',
17
+ display: 'flex',
18
+ justifyContent: 'center',
19
+ alignItems: 'center',
20
+ borderRadius: '50%',
21
+ boxShadow: '2px 2px 4px rgba(0,0,0,0.3)',
22
+ cursor: 'pointer',
23
+ ...props?.style,
24
+ },
25
+ })
26
+ return <>{children}</>
29
27
  },
30
28
  })
@@ -23,7 +23,7 @@ export type RowCells<T> = {
23
23
  [TKey in keyof T | 'default']?: (element: T) => JSX.Element
24
24
  }
25
25
 
26
- export const Grid: <T>(props: GridProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade({
26
+ export const Grid: <T>(props: GridProps<T>, children: ChildrenList) => JSX.Element<any> = Shade({
27
27
  shadowDomName: 'shade-grid',
28
28
  render: ({ props, injector }) => {
29
29
  const { theme } = injector.getInstance(ThemeProviderService)
@@ -2,27 +2,21 @@ import { Shade, createComponent } from '@furystack/shades'
2
2
  import type { TextInputProps } from './input'
3
3
  import { Input } from './input'
4
4
 
5
- export const Autocomplete = Shade<
6
- { inputProps?: TextInputProps; suggestions: string[]; strict?: boolean; onchange?: (value: string) => void },
7
- { dataListId: string }
8
- >({
9
- getInitialState: () => ({
10
- dataListId: (Math.random() + 1).toString(36).substring(3),
11
- }),
12
- constructed: ({ getState, element }) => {
13
- const { dataListId } = getState()
14
- const input = element.querySelector('input')
15
- if (input) {
16
- input.setAttribute('list', dataListId)
17
- }
18
- },
5
+ export const Autocomplete = Shade<{
6
+ inputProps?: TextInputProps
7
+ suggestions: string[]
8
+ strict?: boolean
9
+ onchange?: (value: string) => void
10
+ }>({
19
11
  shadowDomName: 'shade-autocomplete',
20
- render: ({ props, getState }) => {
21
- const { dataListId } = getState()
12
+ render: ({ props, useState }) => {
13
+ const [dataListId] = useState('dataListId', (Math.random() + 1).toString(36).substring(3))
14
+
22
15
  return (
23
16
  <div>
24
17
  <Input
25
18
  {...props.inputProps}
19
+ list={dataListId as any}
26
20
  onchange={(ev) => {
27
21
  const { value } = ev.target as any
28
22
  if (props.strict) {
@@ -1,5 +1,6 @@
1
1
  import type { PartialElement } from '@furystack/shades'
2
2
  import { Shade, createComponent, attachStyles } from '@furystack/shades'
3
+ import { ObservableValue } from '@furystack/utils'
3
4
  import { ThemeProviderService } from '../..'
4
5
  import type { Palette } from '../../services'
5
6
 
@@ -106,33 +107,32 @@ const getLabelStyle = ({
106
107
  }
107
108
  }
108
109
 
109
- export const Input = Shade<TextInputProps, TextInputState>({
110
+ export const Input = Shade<TextInputProps>({
110
111
  shadowDomName: 'shade-input',
111
- getInitialState: ({ props }) => ({
112
- value: props.value || '',
113
- focused: props.autofocus || false,
114
- validity: { valid: true } as ValidityState,
115
- }),
116
- compareState: ({ newState, element, props, injector }) => {
112
+ render: ({ props, injector, useObservable, element }) => {
117
113
  const themeProvider = injector.getInstance(ThemeProviderService)
118
- const label = element.querySelector('label') as HTMLLabelElement
119
- attachStyles(label, { style: getLabelStyle({ themeProvider, props, state: newState }) })
120
114
 
121
- const helper = element.querySelector<HTMLSpanElement>('span.helperText')
122
- const helperNode = props.getHelperText?.({ state: newState }) || ''
123
- helper?.replaceChildren(helperNode)
115
+ const [state, setState] = useObservable<TextInputState>(
116
+ 'inputState',
117
+ new ObservableValue({
118
+ value: props.value || '',
119
+ focused: props.autofocus || false,
120
+ validity: { valid: true } as ValidityState,
121
+ }),
122
+ (newState) => {
123
+ const label = element.querySelector('label') as HTMLLabelElement
124
+ attachStyles(label, { style: getLabelStyle({ themeProvider, props, state: newState }) })
124
125
 
125
- const startIcon = element.querySelector<HTMLSpanElement>('span.startIcon')
126
- startIcon?.replaceChildren(props.getStartIcon?.({ state: newState }) || '')
127
- const endIcon = element.querySelector<HTMLSpanElement>('span.endIcon')
128
- endIcon?.replaceChildren(props.getEndIcon?.({ state: newState }) || '')
126
+ const helper = element.querySelector<HTMLSpanElement>('span.helperText')
127
+ const helperNode = props.getHelperText?.({ state: newState }) || ''
128
+ helper?.replaceChildren(helperNode)
129
129
 
130
- return false
131
- },
132
- render: ({ props, getState, updateState, injector }) => {
133
- const state = getState()
134
- const { value } = state
135
- const themeProvider = injector.getInstance(ThemeProviderService)
130
+ const startIcon = element.querySelector<HTMLSpanElement>('span.startIcon')
131
+ startIcon?.replaceChildren(props.getStartIcon?.({ state: newState }) || '')
132
+ const endIcon = element.querySelector<HTMLSpanElement>('span.endIcon')
133
+ endIcon?.replaceChildren(props.getEndIcon?.({ state: newState }) || '')
134
+ },
135
+ )
136
136
 
137
137
  return (
138
138
  <label {...props.labelProps} style={getLabelStyle({ props, state, themeProvider })}>
@@ -150,20 +150,20 @@ export const Input = Shade<TextInputProps, TextInputState>({
150
150
  oninvalid={(ev) => {
151
151
  ev.preventDefault()
152
152
  const el = ev.target as HTMLInputElement
153
- updateState({ validity: el.validity })
153
+ setState({ ...state, validity: el.validity })
154
154
  }}
155
155
  onchange={(ev) => {
156
156
  const el = ev.target as HTMLInputElement
157
157
  const newValue = el.value
158
- updateState({ value: newValue, validity: el?.validity })
158
+ setState({ ...state, value: newValue, validity: el?.validity })
159
159
  props.onTextChange?.(newValue)
160
160
  props.onchange && (props.onchange as any)(ev)
161
161
  }}
162
162
  onfocus={() => {
163
- updateState({ focused: true })
163
+ setState({ ...state, focused: true })
164
164
  }}
165
165
  onblur={() => {
166
- updateState({ focused: false })
166
+ setState({ ...state, focused: false })
167
167
  }}
168
168
  {...props}
169
169
  style={{
@@ -180,7 +180,7 @@ export const Input = Shade<TextInputProps, TextInputState>({
180
180
  flexGrow: '1',
181
181
  ...props.style,
182
182
  }}
183
- value={value}
183
+ value={state.value}
184
184
  />
185
185
  {props.getEndIcon ? <span className="endIcon">{props.getEndIcon({ state })}</span> : null}
186
186
  </div>
@@ -10,19 +10,11 @@ export interface TextAreaProps extends PartialElement<HTMLTextAreaElement> {
10
10
  variant?: 'contained' | 'outlined'
11
11
  }
12
12
 
13
- export type TextAreaInputState = {
14
- value?: string
15
- }
16
-
17
- export const TextArea = Shade<TextAreaProps, TextAreaInputState>({
13
+ export const TextArea = Shade<TextAreaProps>({
18
14
  shadowDomName: 'shade-text-area',
19
- getInitialState: ({ props }) => ({
20
- value: props.value,
21
- }),
22
- render: ({ props, element, injector, getState }) => {
15
+ render: ({ props, element, injector }) => {
23
16
  const themeProvider = injector.getInstance(ThemeProviderService)
24
17
  const { theme } = themeProvider
25
- const { value } = getState()
26
18
  const { palette } = theme
27
19
 
28
20
  return (
@@ -105,7 +97,7 @@ export const TextArea = Shade<TextAreaProps, TextAreaInputState>({
105
97
  }
106
98
  }}
107
99
  >
108
- {value}
100
+ {props.value}
109
101
  </div>
110
102
  </label>
111
103
  )