@furystack/shades-common-components 3.0.4 → 3.1.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 (62) hide show
  1. package/dist/components/app-bar-link.js +39 -0
  2. package/dist/components/app-bar-link.js.map +1 -0
  3. package/dist/components/app-bar.js +1 -2
  4. package/dist/components/app-bar.js.map +1 -1
  5. package/dist/components/data-grid/body.js +6 -26
  6. package/dist/components/data-grid/body.js.map +1 -1
  7. package/dist/components/data-grid/data-grid-row.js +62 -0
  8. package/dist/components/data-grid/data-grid-row.js.map +1 -0
  9. package/dist/components/data-grid/data-grid.js +16 -17
  10. package/dist/components/data-grid/data-grid.js.map +1 -1
  11. package/dist/components/data-grid/footer.js +8 -6
  12. package/dist/components/data-grid/footer.js.map +1 -1
  13. package/dist/components/data-grid/header.js +1 -1
  14. package/dist/components/data-grid/header.js.map +1 -1
  15. package/dist/components/data-grid/index.js +1 -0
  16. package/dist/components/data-grid/index.js.map +1 -1
  17. package/dist/components/data-grid/selection-cell.js +32 -0
  18. package/dist/components/data-grid/selection-cell.js.map +1 -0
  19. package/dist/components/grid.js +1 -3
  20. package/dist/components/grid.js.map +1 -1
  21. package/dist/components/index.js +1 -0
  22. package/dist/components/index.js.map +1 -1
  23. package/dist/components/paper.js +1 -1
  24. package/dist/components/paper.js.map +1 -1
  25. package/dist/services/collection-service.js +80 -2
  26. package/dist/services/collection-service.js.map +1 -1
  27. package/package.json +7 -6
  28. package/src/components/app-bar-link.tsx +44 -0
  29. package/src/components/app-bar.tsx +1 -2
  30. package/src/components/data-grid/body.tsx +15 -40
  31. package/src/components/data-grid/data-grid-row.tsx +88 -0
  32. package/src/components/data-grid/data-grid.tsx +25 -32
  33. package/src/components/data-grid/footer.tsx +22 -20
  34. package/src/components/data-grid/header.tsx +1 -1
  35. package/src/components/data-grid/index.tsx +1 -0
  36. package/src/components/data-grid/selection-cell.tsx +32 -0
  37. package/src/components/grid.tsx +1 -1
  38. package/src/components/index.ts +1 -0
  39. package/src/components/paper.tsx +1 -1
  40. package/src/services/collection-service.ts +88 -2
  41. package/tsconfig.json +2 -1
  42. package/tsconfig.tsbuildinfo +1 -1
  43. package/types/components/animations.d.ts +2 -2
  44. package/types/components/animations.d.ts.map +1 -1
  45. package/types/components/app-bar-link.d.ts +3 -0
  46. package/types/components/app-bar-link.d.ts.map +1 -0
  47. package/types/components/app-bar.d.ts.map +1 -1
  48. package/types/components/data-grid/body.d.ts +0 -2
  49. package/types/components/data-grid/body.d.ts.map +1 -1
  50. package/types/components/data-grid/data-grid-row.d.ts +15 -0
  51. package/types/components/data-grid/data-grid-row.d.ts.map +1 -0
  52. package/types/components/data-grid/data-grid.d.ts +4 -9
  53. package/types/components/data-grid/data-grid.d.ts.map +1 -1
  54. package/types/components/data-grid/footer.d.ts.map +1 -1
  55. package/types/components/data-grid/index.d.ts +1 -0
  56. package/types/components/data-grid/index.d.ts.map +1 -1
  57. package/types/components/data-grid/selection-cell.d.ts +6 -0
  58. package/types/components/data-grid/selection-cell.d.ts.map +1 -0
  59. package/types/components/index.d.ts +1 -0
  60. package/types/components/index.d.ts.map +1 -1
  61. package/types/services/collection-service.d.ts +4 -1
  62. package/types/services/collection-service.d.ts.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"collection-service.js","sourceRoot":"","sources":["../../src/services/collection-service.ts"],"names":[],"mappings":";;;;AACA,0FAA6C;AAC7C,4CAAwE;AAWxE,MAAa,iBAAiB;IAwB5B,YAAY,KAAqB,EAAE,eAA+C;QAhBjE,aAAQ,GAAG,IAAI,+BAAS,CAAC,CAAC,CAAC,CAAA;QAIrC,SAAI,GAAG,IAAI,uBAAe,CAAoB,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAA;QAExE,UAAK,GAAG,IAAI,uBAAe,CAAsB,SAAS,CAAC,CAAA;QAE3D,cAAS,GAAG,IAAI,uBAAe,CAAU,KAAK,CAAC,CAAA;QAI/C,UAAK,GAAG,IAAI,uBAAe,EAAiB,CAAA;QAE5C,cAAS,GAAG,IAAI,uBAAe,CAAM,EAAE,CAAC,CAAA;QAG7C,IAAI,CAAC,aAAa,GAAG,IAAI,uBAAe,CAAiC,eAAe,CAAC,CAAA;QACzF,IAAI,CAAC,UAAU,GAAG,IAAA,gBAAQ,EAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YAC3C,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAA;YAC7B,IAAI;gBACF,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;gBAC7B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAA;gBACnC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;gBAC1B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;gBAC9B,OAAO,MAAM,CAAA;aACd;YAAC,OAAO,KAAK,EAAE;gBACd,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBAC1B,MAAM,KAAK,CAAA;aACZ;oBAAS;gBACR,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAA;gBACvB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;aAC/B;QACH,CAAC,EAAE,GAAG,CAAC,CAAA;QACP,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;IACnE,CAAC;IA1CM,OAAO;QACZ,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAA;QAC5B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAA;QACnB,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;QACpB,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAA;IAC1B,CAAC;CAsCF;AA5CD,8CA4CC"}
1
+ {"version":3,"file":"collection-service.js","sourceRoot":"","sources":["../../src/services/collection-service.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@furystack/shades-common-components",
3
- "version": "3.0.4",
3
+ "version": "3.1.0",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "FuryStack",
@@ -17,13 +17,14 @@
17
17
  "author": "gallay.lajos@gmail.com",
18
18
  "license": "GPL-2.0",
19
19
  "devDependencies": {
20
- "typescript": "^4.6.4"
20
+ "typescript": "4.7.4"
21
21
  },
22
22
  "dependencies": {
23
- "@furystack/core": "^11.0.3",
24
- "@furystack/inject": "^6.0.3",
25
- "@furystack/shades": "^5.0.4",
26
- "@furystack/utils": "^3.0.3",
23
+ "@furystack/core": "^11.0.4",
24
+ "@furystack/inject": "^7.0.0",
25
+ "@furystack/shades": "^6.0.0",
26
+ "@furystack/utils": "^3.0.4",
27
+ "path-to-regexp": "^6.2.1",
27
28
  "semaphore-async-await": "^1.5.1",
28
29
  "tslib": "^2.4.0"
29
30
  },
@@ -0,0 +1,44 @@
1
+ import { createComponent, LocationService, RouteLink, RouteLinkProps, Shade } from '@furystack/shades'
2
+ import { match } from 'path-to-regexp'
3
+ import { ThemeProviderService } from '../services'
4
+
5
+ export const AppBarLink = Shade<RouteLinkProps, { isActive: boolean }>({
6
+ getInitialState: () => ({ isActive: false }),
7
+ resources: ({ injector, props, updateState, element, getState }) => {
8
+ const updateColor = () => {
9
+ const { isActive } = getState()
10
+ const themeProviderService = injector.getInstance(ThemeProviderService)
11
+ const theme = themeProviderService.theme.getValue()
12
+ const el = element.querySelector('a') as HTMLElement
13
+ const backgroundColor = isActive ? theme.button.active : 'rgba(128, 128, 128, 0.05)'
14
+
15
+ el.style.backgroundColor = backgroundColor
16
+ el.style.color = themeProviderService.getTextColor(backgroundColor)
17
+ }
18
+ return [
19
+ injector.getInstance(LocationService).onLocationChanged.subscribe(async (currentUrl) => {
20
+ const isActive = !!match(props.href as string)(currentUrl)
21
+ updateState({ isActive }, false)
22
+ updateColor()
23
+ }, true),
24
+ ]
25
+ },
26
+ shadowDomName: 'shade-app-bar-link',
27
+ render: ({ children, props }) => {
28
+ return (
29
+ <RouteLink
30
+ style={{
31
+ display: 'flex',
32
+ height: '100%',
33
+ textDecoration: 'none',
34
+ alignItems: 'center',
35
+ padding: '0 8px',
36
+ ...props.style,
37
+ }}
38
+ {...props}
39
+ >
40
+ {children}
41
+ </RouteLink>
42
+ )
43
+ },
44
+ })
@@ -11,7 +11,7 @@ export const AppBar = Shade({
11
11
  constructed: ({ element }) => {
12
12
  const container = element.children[0] as HTMLElement
13
13
  requestAnimationFrame(() => {
14
- container.style.padding = '8px 8px'
14
+ // container.style.padding = '8px 8px'
15
15
  container.style.opacity = '1'
16
16
  })
17
17
  },
@@ -28,7 +28,6 @@ export const AppBar = Shade({
28
28
  boxShadow: '0 0 12px rgba(0,0,0,0.6)',
29
29
  transition:
30
30
  'opacity .35s cubic-bezier(0.550, 0.085, 0.680, 0.530), padding .2s cubic-bezier(0.550, 0.085, 0.680, 0.530)',
31
- padding: '6px 16px',
32
31
  opacity: '0',
33
32
  position: 'fixed',
34
33
  zIndex: '1',
@@ -1,7 +1,8 @@
1
1
  import { CollectionService } from '../../services/collection-service'
2
- import { ChildrenList, Shade, createComponent } from '@furystack/shades'
2
+ import { ChildrenList, Shade, createComponent, createFragment } from '@furystack/shades'
3
3
  import { DataRowCells } from './data-grid'
4
4
  import { Loader } from '../loader'
5
+ import { DataGridRow } from './data-grid-row'
5
6
 
6
7
  export interface DataGridBodyProps<T> {
7
8
  service: CollectionService<T>
@@ -13,8 +14,6 @@ export interface DataGridBodyProps<T> {
13
14
 
14
15
  export interface DataGridBodyState<T> {
15
16
  data: T[]
16
- selection: T[]
17
- focus: T | undefined
18
17
  isLoading: boolean
19
18
  }
20
19
 
@@ -24,19 +23,12 @@ export const DataGridBody: <T>(props: DataGridBodyProps<T>, children: ChildrenLi
24
23
  >({
25
24
  getInitialState: ({ props }) => ({
26
25
  data: props.service.data.getValue().entries,
27
- selection: props.service.selection.getValue(),
28
- focus: props.service.focus.getValue(),
29
26
  isLoading: props.service.isLoading.getValue(),
30
27
  }),
31
- constructed: ({ props, updateState }) => {
32
- const disposables = [
33
- props.service.data.subscribe((data) => updateState({ data: data.entries })),
34
- props.service.focus.subscribe((focus) => updateState({ focus })),
35
- props.service.selection.subscribe((selection) => updateState({ selection })),
36
- props.service.isLoading.subscribe((isLoading) => updateState({ isLoading })),
37
- ]
38
- return () => disposables.map((d) => d.dispose())
39
- },
28
+ resources: ({ props, updateState }) => [
29
+ props.service.data.subscribe((data) => updateState({ data: data.entries })),
30
+ props.service.isLoading.subscribe((isLoading) => updateState({ isLoading })),
31
+ ],
40
32
  shadowDomName: 'shade-data-grid-body',
41
33
  render: ({ getState, props, element }) => {
42
34
  element.style.display = 'table-row-group'
@@ -45,6 +37,7 @@ export const DataGridBody: <T>(props: DataGridBodyProps<T>, children: ChildrenLi
45
37
  if (state.isLoading) {
46
38
  return (
47
39
  <div style={{ display: 'flex', height: '100%', justifyContent: 'center', alignItems: 'center', width: '100%' }}>
40
+ {/* TODO: Skeleton */}
48
41
  <Loader style={{ height: '128px', width: '128px' }} />
49
42
  </div>
50
43
  )
@@ -55,34 +48,16 @@ export const DataGridBody: <T>(props: DataGridBodyProps<T>, children: ChildrenLi
55
48
  }
56
49
 
57
50
  return (
58
- <div style={{ display: 'contents' }}>
51
+ <>
59
52
  {state.data.map((entry) => (
60
- <tr
61
- style={{
62
- background: state.selection.includes(entry) ? 'rgba(128,128,128,0.3)' : 'transparent',
63
- filter: state.focus === entry ? 'brightness(1.5)' : 'brightness(1)',
64
- cursor: 'default',
65
- boxShadow: '2px 1px 0px rgba(255,255,255,0.07)',
66
- fontSize: '0.8em',
67
- }}
68
- onclick={() => {
69
- if (getState().focus !== entry) {
70
- props.service.focus.setValue(entry)
71
- props.service.selection.setValue([entry])
72
- }
73
- }}
74
- ondblclick={() => props.onDoubleClick?.(entry)}
75
- >
76
- {props.columns.map((column: any) => (
77
- <td style={{ padding: '0.5em', ...props.style }}>
78
- {props.rowComponents?.[column]?.(entry, state) || props.rowComponents?.default?.(entry, state) || (
79
- <span>{entry[column]}</span>
80
- )}
81
- </td>
82
- ))}
83
- </tr>
53
+ <DataGridRow<any>
54
+ columns={props.columns}
55
+ entry={entry}
56
+ service={props.service}
57
+ rowComponents={props.rowComponents}
58
+ ></DataGridRow>
84
59
  ))}
85
- </div>
60
+ </>
86
61
  )
87
62
  },
88
63
  })
@@ -0,0 +1,88 @@
1
+ import { ChildrenList, createComponent, createFragment, Shade } from '@furystack/shades'
2
+ import { CollectionService } from '../../services/collection-service'
3
+ import { DataRowCells } from './data-grid'
4
+
5
+ export interface DataGridRowProps<T> {
6
+ entry: T
7
+ columns: Array<keyof T>
8
+ service: CollectionService<T>
9
+ rowComponents?: DataRowCells<T>
10
+ }
11
+
12
+ export interface DataGridRowState<T> {
13
+ selection?: T[]
14
+ focus?: T
15
+ }
16
+
17
+ export const DataGridRow: <T>(props: DataGridRowProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade<
18
+ DataGridRowProps<any>,
19
+ DataGridRowState<any>
20
+ >({
21
+ getInitialState: ({ props }) => ({
22
+ focus: props.service.focusedEntry.getValue(),
23
+ selection: props.service.selection.getValue(),
24
+ }),
25
+ shadowDomName: 'shades-data-grid-row',
26
+ resources: ({ props, element }) => [
27
+ props.service.focusedEntry.subscribe((newEntry) => {
28
+ if (newEntry === props.entry) {
29
+ element.style.filter = 'brightness(1.5)'
30
+ element.style.fontWeight = 'bolder'
31
+
32
+ const headerHeight = element.closest('table')?.querySelector('th')?.getBoundingClientRect().height || 42
33
+
34
+ const parent = element.closest('.shade-grid-wrapper') as HTMLElement
35
+ const maxTop = element.offsetTop - headerHeight
36
+ const currentTop = parent.scrollTop
37
+ if (maxTop < currentTop) {
38
+ parent.scrollTo({ top: maxTop, behavior: 'smooth' })
39
+ }
40
+
41
+ const footerHeight =
42
+ element.closest('shade-data-grid')?.querySelector('shade-data-grid-footer')?.getBoundingClientRect().height ||
43
+ 42
44
+ const visibleMaxTop = parent.clientHeight - footerHeight // parent.getBoundingClientRect().height - footerHeight - headerHeight
45
+ const desiredMaxTop = element.offsetTop + element.clientHeight
46
+ if (desiredMaxTop > visibleMaxTop) {
47
+ parent.scrollTo({ top: desiredMaxTop - visibleMaxTop, behavior: 'smooth' })
48
+ }
49
+
50
+ // ;(element as any).scrollIntoView({ inline: 'nearest', block: 'nearest', behavior: 'smooth' })
51
+ } else {
52
+ element.style.filter = 'brightness(1)'
53
+ element.style.fontWeight = 'inherit'
54
+ }
55
+ }),
56
+ props.service.selection.subscribe((selection) => {
57
+ if (selection.includes(props.entry)) {
58
+ element.style.background = 'rgba(128,128,128,0.1)'
59
+ } else {
60
+ element.style.background = 'none'
61
+ }
62
+ }),
63
+ ],
64
+
65
+ render: ({ getState, props, element }) => {
66
+ const state = getState()
67
+ const { entry, rowComponents, columns } = props
68
+
69
+ element.style.display = 'table-row'
70
+ element.style.cursor = 'default'
71
+ element.style.userSelect = 'none'
72
+
73
+ element.onclick = () => {
74
+ props.service.focusedEntry.setValue(props.entry)
75
+ }
76
+ return (
77
+ <>
78
+ {columns.map((column) => (
79
+ <td style={{ padding: '0.5em' }}>
80
+ {rowComponents?.[column]?.(entry, state) || rowComponents?.default?.(entry, state) || (
81
+ <span>{entry[column]}</span>
82
+ )}
83
+ </td>
84
+ ))}
85
+ </>
86
+ )
87
+ },
88
+ })
@@ -1,17 +1,17 @@
1
1
  import { ChildrenList, createComponent, Shade } from '@furystack/shades'
2
2
  import { CollectionService } from '../../services/collection-service'
3
3
  import { GridProps } from '../grid'
4
- import { colors } from '../styles'
5
4
  import { DataGridHeader } from './header'
6
- import { DataGridBody, DataGridBodyState } from './body'
5
+ import { DataGridBody } from './body'
7
6
  import { DataGridFooter } from './footer'
8
- import { ThemeProviderService } from '../../services'
7
+ import { ClickAwayService, ThemeProviderService } from '../../services'
8
+ import { DataGridRowState } from './data-grid-row'
9
9
 
10
10
  export type DataHeaderCells<T> = {
11
- [TKey in keyof T | 'default']?: (name: keyof T, state: DataGridState) => JSX.Element
11
+ [TKey in keyof T | 'default']?: (name: keyof T) => JSX.Element
12
12
  }
13
13
  export type DataRowCells<T> = {
14
- [TKey in keyof T | 'default']?: (element: T, state: DataGridBodyState<T>) => JSX.Element
14
+ [TKey in keyof T | 'default']?: (element: T, state: DataGridRowState<T>) => JSX.Element
15
15
  }
16
16
 
17
17
  export interface DataGridProps<T> {
@@ -20,25 +20,16 @@ export interface DataGridProps<T> {
20
20
  service: CollectionService<T>
21
21
  headerComponents: DataHeaderCells<T>
22
22
  rowComponents: DataRowCells<T>
23
- onFocusChange?: (entry?: T) => void
24
- onSelectionChange?: (selection: T[]) => void
25
- onDoubleClick?: (entry: T) => void
26
- }
27
-
28
- export interface DataGridState {
29
- error?: unknown
23
+ autofocus?: boolean
30
24
  }
31
25
 
32
26
  export const DataGrid: <T>(props: DataGridProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade<
33
- DataGridProps<any>,
34
- DataGridState
27
+ DataGridProps<any>
35
28
  >({
36
29
  shadowDomName: 'shade-data-grid',
37
- getInitialState: () => ({}),
38
- constructed: ({ props, updateState, injector, element }) => {
30
+ resources: ({ injector, element, props }) => {
39
31
  const tp = injector.getInstance(ThemeProviderService)
40
- const subscriptions = [
41
- props.service.error.subscribe((error) => updateState({ error })),
32
+ return [
42
33
  tp.theme.subscribe((t) => {
43
34
  const headers = element.querySelectorAll('th')
44
35
  const { r, g, b } = tp.getRgbFromColorString(t.background.paper)
@@ -47,18 +38,19 @@ export const DataGrid: <T>(props: DataGridProps<T>, children: ChildrenList) => J
47
38
  header.style.backgroundColor = `rgba(${r}, ${g}, ${b}, 0.3)`
48
39
  })
49
40
  }),
50
- props.service.focus.subscribe((f) => props.onFocusChange?.(f)),
51
- props.service.selection.subscribe((f) => props.onSelectionChange?.(f)),
41
+ new ClickAwayService(element, () => {
42
+ props.service.hasFocus.setValue(false)
43
+ }),
52
44
  ]
53
- return () => Promise.all(subscriptions.map((s) => s.dispose()))
54
45
  },
55
- render: ({ props, getState, injector }) => {
46
+ constructed: ({ props }) => {
47
+ window.addEventListener('keydown', (ev) => {
48
+ props.service.handleKeyDown(ev)
49
+ })
50
+ },
51
+ render: ({ props, injector }) => {
56
52
  const tp = injector.getInstance(ThemeProviderService)
57
53
  const theme = tp.theme.getValue()
58
- const state = getState()
59
- if (state.error) {
60
- return <div style={{ color: colors.error.main }}>{JSON.stringify(state.error)}</div>
61
- }
62
54
 
63
55
  const { r, g, b } = tp.getRgbFromColorString(theme.background.paper)
64
56
  const headerStyle: Partial<CSSStyleDeclaration> = {
@@ -86,17 +78,19 @@ export const DataGrid: <T>(props: DataGridProps<T>, children: ChildrenList) => J
86
78
  overflow: 'auto',
87
79
  zIndex: '1',
88
80
  }}
81
+ onclick={() => {
82
+ props.service.hasFocus.setValue(true)
83
+ }}
89
84
  >
90
- <table style={{ width: '100%', height: 'calc(100% - 4em)', position: 'relative' }}>
85
+ <table style={{ width: '100%', maxHeight: 'calc(100% - 4em)', position: 'relative' }}>
91
86
  <thead>
92
87
  <tr>
93
88
  {props.columns.map((column: any) => {
94
89
  return (
95
90
  <th style={headerStyle}>
96
- {props.headerComponents?.[column]?.(column, state) ||
97
- props.headerComponents?.default?.(column, state) || (
98
- <DataGridHeader<any, typeof column> field={column} collectionService={props.service} />
99
- )}
91
+ {props.headerComponents?.[column]?.(column) || props.headerComponents?.default?.(column) || (
92
+ <DataGridHeader<any, typeof column> field={column} collectionService={props.service} />
93
+ )}
100
94
  </th>
101
95
  )
102
96
  })}
@@ -107,7 +101,6 @@ export const DataGrid: <T>(props: DataGridProps<T>, children: ChildrenList) => J
107
101
  service={props.service}
108
102
  rowComponents={props.rowComponents}
109
103
  style={props.styles?.cell}
110
- onDoubleClick={props.onDoubleClick}
111
104
  />
112
105
  </table>
113
106
  <DataGridFooter service={props.service} />
@@ -2,7 +2,7 @@ import { Shade, createComponent } from '@furystack/shades'
2
2
  import { ThemeProviderService } from '../../services'
3
3
  import { CollectionService, CollectionData } from '../../services/collection-service'
4
4
 
5
- export const dataGridItemsPerPage = [10, 20, 25, 50, 100]
5
+ export const dataGridItemsPerPage = [10, 20, 25, 50, 100, Infinity]
6
6
 
7
7
  export const DataGridFooter = Shade<{ service: CollectionService<any> }, { data: CollectionData<any> }>({
8
8
  shadowDomName: 'shade-data-grid-footer',
@@ -26,13 +26,14 @@ export const DataGridFooter = Shade<{ service: CollectionService<any> }, { data:
26
26
  const state = getState()
27
27
  const currentQuerySettings = props.service.querySettings.getValue()
28
28
  const currentPage = Math.ceil(currentQuerySettings.skip || 0) / (currentQuerySettings.top || 1)
29
+ const currentEntriesPerPage = currentQuerySettings.top || Infinity
29
30
  const theme = injector.getInstance(ThemeProviderService).theme.getValue()
30
31
 
31
32
  return (
32
33
  <div
33
34
  className="pager"
34
35
  style={{
35
- background: theme.background.paper,
36
+ backdropFilter: 'blur(10px)',
36
37
  color: theme.text.secondary,
37
38
  position: 'sticky',
38
39
  bottom: '0',
@@ -42,27 +43,28 @@ export const DataGridFooter = Shade<{ service: CollectionService<any> }, { data:
42
43
  alignItems: 'center',
43
44
  }}
44
45
  >
45
- <div>
46
- Goto page
47
- <select
48
- style={{ margin: '0 1em' }}
49
- onchange={(ev) => {
50
- const value = parseInt((ev.target as any).value, 10)
51
- const currentQuery = props.service.querySettings.getValue()
52
- props.service.querySettings.setValue({ ...currentQuery, skip: (currentQuery.top || 0) * value })
53
- }}
54
- >
55
- {[...new Array(Math.ceil(state.data.count / (props.service.querySettings.getValue().top || Infinity)))].map(
56
- (_val, index) => (
46
+ {currentEntriesPerPage !== Infinity && (
47
+ <div>
48
+ Goto page
49
+ <select
50
+ style={{ margin: '0 1em' }}
51
+ onchange={(ev) => {
52
+ const value = parseInt((ev.target as any).value, 10)
53
+ const currentQuery = props.service.querySettings.getValue()
54
+ props.service.querySettings.setValue({ ...currentQuery, skip: (currentQuery.top || 0) * value })
55
+ }}
56
+ >
57
+ {[
58
+ ...new Array(Math.ceil(state.data.count / (props.service.querySettings.getValue().top || Infinity))),
59
+ ].map((_val, index) => (
57
60
  <option value={index.toString()} selected={currentPage === index}>
58
61
  {(index + 1).toString()}
59
62
  </option>
60
- ),
61
- )}
62
- </select>
63
- </div>
63
+ ))}
64
+ </select>
65
+ </div>
66
+ )}
64
67
  <div>
65
- {' '}
66
68
  Show
67
69
  <select
68
70
  style={{ margin: '0 1em' }}
@@ -76,7 +78,7 @@ export const DataGridFooter = Shade<{ service: CollectionService<any> }, { data:
76
78
  }}
77
79
  >
78
80
  {dataGridItemsPerPage.map((no) => (
79
- <option value={no.toString()} selected={no === currentQuerySettings.top}>
81
+ <option value={no.toString()} selected={no === currentEntriesPerPage}>
80
82
  {no.toString()}
81
83
  </option>
82
84
  ))}
@@ -29,7 +29,7 @@ export const DataGridHeader: <T, K extends keyof T>(
29
29
  ...currentSettings,
30
30
  filter: {
31
31
  ...currentSettings.filter,
32
- [props.field]: value ? { $regex: value } : undefined,
32
+ [props.field]: { $regex: value },
33
33
  },
34
34
  }
35
35
  props.collectionService.querySettings.setValue(newSettings)
@@ -1 +1,2 @@
1
1
  export * from './data-grid'
2
+ export * from './selection-cell'
@@ -0,0 +1,32 @@
1
+ import { createComponent, Shade } from '@furystack/shades'
2
+ import { CollectionService } from '../../services'
3
+
4
+ export const SelectionCell = Shade<{ entry: any; service: CollectionService<any> }>({
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 }) => {
16
+ return (
17
+ <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
+ }
26
+ }}
27
+ type="checkbox"
28
+ checked={props.service.selection.getValue().includes(props.entry) ? true : false}
29
+ />
30
+ )
31
+ },
32
+ })
@@ -64,7 +64,7 @@ export const Grid: <T>(props: GridProps<T>, children: ChildrenList) => JSX.Eleme
64
64
  <th style={headerStyle}>
65
65
  {props.headerComponents?.[column]?.(column) || props.headerComponents?.default?.(column) || (
66
66
  <span>{column}</span>
67
- )}{' '}
67
+ )}
68
68
  </th>
69
69
  )
70
70
  })}
@@ -1,5 +1,6 @@
1
1
  export * from './animations'
2
2
  export * from './app-bar'
3
+ export * from './app-bar-link'
3
4
  export * from './autocomplete'
4
5
  export * from './avatar'
5
6
  export * from './button'
@@ -24,7 +24,7 @@ export const Paper = Shade<PartialElement<HTMLDivElement> & { elevation?: 1 | 2
24
24
  color: themeProvider.theme.getValue().text.secondary,
25
25
  margin: '8px',
26
26
  padding: '6px 16px',
27
- ...(props ? props.style : {}),
27
+ ...props?.style,
28
28
  }}
29
29
  >
30
30
  {children}
@@ -31,19 +31,105 @@ export class CollectionService<T> implements Disposable {
31
31
 
32
32
  public querySettings: ObservableValue<FindOptions<T, Array<keyof T>>>
33
33
 
34
- public focus = new ObservableValue<T | undefined>()
34
+ public focusedEntry = new ObservableValue<T | undefined>()
35
35
 
36
36
  public selection = new ObservableValue<T[]>([])
37
37
 
38
+ public searchTerm = new ObservableValue('')
39
+
40
+ public hasFocus = new ObservableValue(false)
41
+
42
+ public handleKeyDown(ev: KeyboardEvent) {
43
+ const { entries } = this.data.getValue()
44
+ const hasFocus = this.hasFocus.getValue()
45
+ const selectedEntries = this.selection.getValue()
46
+ const focusedEntry = this.focusedEntry.getValue()
47
+ const searchTerm = this.searchTerm.getValue()
48
+
49
+ switch (ev.key) {
50
+ case ' ':
51
+ ev.preventDefault()
52
+ focusedEntry &&
53
+ this.selection.setValue(
54
+ selectedEntries.includes(focusedEntry)
55
+ ? selectedEntries.filter((e) => e !== focusedEntry)
56
+ : [...selectedEntries, focusedEntry],
57
+ )
58
+ break
59
+ case '*':
60
+ this.selection.setValue(entries.filter((e) => !selectedEntries.includes(e)))
61
+ break
62
+ case '+':
63
+ this.selection.setValue(entries)
64
+ break
65
+ case '-':
66
+ this.selection.setValue([])
67
+ break
68
+ case 'Insert':
69
+ focusedEntry &&
70
+ (this.selection.getValue().includes(focusedEntry)
71
+ ? this.selection.setValue([...this.selection.getValue().filter((e) => e !== focusedEntry)])
72
+ : this.selection.setValue([...this.selection.getValue(), focusedEntry]))
73
+ this.focusedEntry.setValue(entries[entries.findIndex((e) => e === this.focusedEntry.getValue()) + 1])
74
+
75
+ break
76
+ case 'ArrowUp':
77
+ ev.preventDefault()
78
+ this.focusedEntry.setValue(entries[Math.max(0, entries.findIndex((e) => e === focusedEntry) - 1)])
79
+ break
80
+ case 'ArrowDown':
81
+ ev.preventDefault()
82
+ this.focusedEntry.setValue(
83
+ entries[Math.min(entries.length - 1, entries.findIndex((e) => e === focusedEntry) + 1)],
84
+ )
85
+ break
86
+ case 'Home': {
87
+ this.focusedEntry.setValue(entries[0])
88
+ break
89
+ }
90
+ case 'End': {
91
+ this.focusedEntry.setValue(entries[entries.length - 1])
92
+ break
93
+ }
94
+ case 'Enter': {
95
+ // this.activate(focusedEntry)
96
+ break
97
+ }
98
+ case 'Backspace': {
99
+ // this.currentWorkDir.goUp()
100
+ break
101
+ }
102
+ case 'Tab': {
103
+ this.hasFocus.setValue(!hasFocus)
104
+ break
105
+ }
106
+ case 'Escape': {
107
+ this.searchTerm.setValue('')
108
+ this.selection.setValue([])
109
+ break
110
+ }
111
+ default:
112
+ if (ev.key.length === 1 && new RegExp(/[a-zA-z0-9]/).test(ev.key)) {
113
+ const newSearchExpression = searchTerm + ev.key
114
+ // TODO: implement search
115
+ // const newFocusedEntry = entries.find((e) => e.name.startsWith(newSearchExpression))
116
+ // this.focus.setValue(newFocusedEntry)
117
+ this.searchTerm.setValue(newSearchExpression)
118
+ } else {
119
+ console.log(`Handler for '${ev.key}' not registered`)
120
+ }
121
+ }
122
+ }
123
+
38
124
  constructor(fetch: EntryLoader<T>, defaultSettings: FindOptions<T, Array<keyof T>>) {
39
125
  this.querySettings = new ObservableValue<FindOptions<T, Array<keyof T>>>(defaultSettings)
40
126
  this.getEntries = debounce(async (options) => {
41
127
  await this.loadLock.acquire()
42
128
  try {
129
+ this.error.setValue(undefined)
43
130
  this.isLoading.setValue(true)
44
131
  const result = await fetch(options)
45
132
  this.data.setValue(result)
46
- this.error.setValue(undefined)
47
133
  return result
48
134
  } catch (error) {
49
135
  this.error.setValue(error)