@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
@@ -1,10 +1,11 @@
1
1
  import type { Injector } from '@furystack/inject'
2
2
  import { Injectable } from '@furystack/inject'
3
+ import type { Disposable } from '@furystack/utils'
3
4
  import { debounce, ObservableValue } from '@furystack/utils'
4
5
  import type { SuggestionResult } from './suggestion-result'
5
6
 
6
7
  @Injectable({ lifetime: 'singleton' })
7
- export class SuggestManager<T> {
8
+ export class SuggestManager<T> implements Disposable {
8
9
  public isOpened = new ObservableValue(false)
9
10
  public isLoading = new ObservableValue(false)
10
11
  public term = new ObservableValue('')
@@ -32,6 +33,12 @@ export class SuggestManager<T> {
32
33
  public dispose() {
33
34
  window.removeEventListener('keyup', this.keyPressListener)
34
35
  window.removeEventListener('click', this.clickOutsideListener)
36
+ this.isOpened.dispose()
37
+ this.isLoading.dispose()
38
+ this.term.dispose()
39
+ this.selectedIndex.dispose()
40
+ this.currentSuggestions.dispose()
41
+ this.onSelectSuggestion.dispose()
35
42
  }
36
43
 
37
44
  public selectSuggestion(index: number = this.selectedIndex.getValue()) {
@@ -46,13 +53,18 @@ export class SuggestManager<T> {
46
53
  if (this.lastGetSuggestionOptions?.term === options.term) {
47
54
  return
48
55
  }
56
+ const lastSelectedSuggestion = JSON.stringify(this.currentSuggestions.getValue()[this.selectedIndex.getValue()])
49
57
  this.isLoading.setValue(true)
50
58
  this.lastGetSuggestionOptions = options
51
- this.currentSuggestions.setValue([])
52
- this.selectedIndex.setValue(0)
53
59
  const newEntries = await this.getEntries(options.term)
54
60
  this.isOpened.setValue(true)
55
61
  this.currentSuggestions.setValue(newEntries.map((e) => ({ entry: e, suggestion: this.getSuggestionEntry(e) })))
62
+ this.selectedIndex.setValue(
63
+ Math.max(
64
+ 0,
65
+ this.currentSuggestions.getValue().findIndex((e) => JSON.stringify(e) === lastSelectedSuggestion),
66
+ ),
67
+ )
56
68
  } finally {
57
69
  this.isLoading.setValue(false)
58
70
  }
@@ -1,93 +1,98 @@
1
1
  import type { ChildrenList } from '@furystack/shades'
2
2
  import { Shade, createComponent } from '@furystack/shades'
3
3
  import { promisifyAnimation } from '../../utils/promisify-animation'
4
- import type { SuggestionResult } from './suggestion-result'
5
4
  import type { SuggestManager } from './suggest-manager'
5
+ import { ThemeProviderService } from '../../services'
6
6
 
7
- export const SuggestionList: <T>(
8
- props: { manager: SuggestManager<T> },
9
- children: ChildrenList,
10
- ) => JSX.Element<any, any> = Shade<{ manager: SuggestManager<any> }, { suggestions: SuggestionResult[] }>({
11
- shadowDomName: 'shade-suggest-suggestion-list',
12
- getInitialState: ({ props }) => ({
13
- suggestions: props.manager.currentSuggestions.getValue().map((v) => v.suggestion),
14
- }),
15
- resources: ({ updateState, element, props }) => [
16
- props.manager.currentSuggestions.subscribe((s) => {
17
- updateState({ suggestions: s.map((ss) => ss.suggestion) })
18
- }),
19
- props.manager.isOpened.subscribe(async (isOpened) => {
20
- const container = element.firstElementChild as HTMLDivElement
21
- if (isOpened) {
22
- container.style.zIndex = '1'
23
- container.style.width = `calc(${Math.round(
24
- element.parentElement?.getBoundingClientRect().width || 200,
25
- )}px - 3em)`
26
- await promisifyAnimation(
27
- container,
28
- [
29
- { opacity: 0, transform: 'translate(0, -50px)' },
30
- { opacity: 1, transform: 'translate(0, 0)' },
31
- ],
32
- { fill: 'forwards', duration: 500 },
33
- )
34
- } else {
35
- await promisifyAnimation(
36
- container,
37
- [
38
- { opacity: 1, transform: 'translate(0, 0)' },
39
- { opacity: 0, transform: 'translate(0, -50px)' },
40
- ],
41
- { fill: 'forwards', duration: 200 },
42
- )
43
- container.style.zIndex = '-1'
44
- }
45
- }, true), // TODO: Check initial state
46
- props.manager.selectedIndex.subscribe((idx) => {
47
- ;[...element.querySelectorAll('.suggestion-item')].map((s, i) => {
48
- if (i === idx) {
49
- ;(s as HTMLDivElement).style.background = 'rgba(128,128,128,0.2)'
7
+ export const SuggestionList: <T>(props: { manager: SuggestManager<T> }, children: ChildrenList) => JSX.Element<any> =
8
+ Shade<{ manager: SuggestManager<any> }>({
9
+ shadowDomName: 'shade-suggest-suggestion-list',
10
+ render: ({ element, props, injector, useObservable }) => {
11
+ const { manager } = props
12
+ const { theme } = injector.getInstance(ThemeProviderService)
13
+
14
+ const [suggestions] = useObservable('suggestions', manager.currentSuggestions)
15
+
16
+ const [selectedIndex] = useObservable(
17
+ 'selectedIndex',
18
+ manager.selectedIndex,
19
+ (idx) => {
20
+ ;([...element.querySelectorAll('.suggestion-item')] as HTMLDivElement[]).map((s, i) => {
21
+ if (i === idx) {
22
+ s.style.background = theme.background.paper
23
+ s.style.fontWeight = 'bolder'
24
+ } else {
25
+ s.style.background = theme.background.default
26
+ s.style.fontWeight = 'normal'
27
+ }
28
+ })
29
+ },
30
+ true,
31
+ )
32
+
33
+ const [isListOpened] = useObservable('isOpened', manager.isOpened, async (isOpened) => {
34
+ const container = element.firstElementChild as HTMLDivElement
35
+ if (isOpened) {
36
+ container.style.zIndex = '1'
37
+ container.style.width = `calc(${Math.round(
38
+ element.parentElement?.getBoundingClientRect().width || 200,
39
+ )}px - 3em)`
40
+ await promisifyAnimation(
41
+ container,
42
+ [
43
+ { opacity: 0, transform: 'translate(0, -50px)' },
44
+ { opacity: 1, transform: 'translate(0, 0)' },
45
+ ],
46
+ { fill: 'forwards', duration: 500 },
47
+ )
50
48
  } else {
51
- ;(s as HTMLDivElement).style.background = 'rgba(96,96,96,0.2)'
49
+ await promisifyAnimation(
50
+ container,
51
+ [
52
+ { opacity: 1, transform: 'translate(0, 0)' },
53
+ { opacity: 0, transform: 'translate(0, -50px)' },
54
+ ],
55
+ { fill: 'forwards', duration: 200 },
56
+ )
57
+ container.style.zIndex = '-1'
52
58
  }
53
59
  })
54
- }),
55
- ],
56
- render: ({ element, getState, props }) => {
57
- const { manager } = props
58
- return (
59
- <div
60
- className="suggestion-items-container"
61
- style={{
62
- borderTop: 'none',
63
- position: 'absolute',
64
- borderRadius: '0px 0px 5px 5px',
65
- marginLeft: '14px',
66
- overflow: 'hidden',
67
- zIndex: '1',
68
- left: 'auto',
69
- backgroundColor: 'rgba(8,8,8,0.85)',
70
- boxShadow: '3px 3px 5px rgba(0,0,0,0.3)',
71
- backdropFilter: 'blur(15px)',
72
- width: `calc(${Math.round(element.parentElement?.getBoundingClientRect().width || 200)}px - 3em)`,
73
- }}
74
- >
75
- {getState().suggestions.map((s, i) => (
76
- <div
77
- className="suggestion-item"
78
- onclick={() => {
79
- manager.isOpened.getValue() && manager.selectSuggestion(i)
80
- }}
81
- style={{
82
- padding: '1em',
83
- cursor: 'default',
84
- background: i === manager.selectedIndex.getValue() ? 'rgba(128,128,128,0.2)' : 'rgba(96,96,96,0.2)',
85
- }}
86
- >
87
- {s.element}
88
- </div>
89
- ))}
90
- </div>
91
- )
92
- },
93
- })
60
+
61
+ return (
62
+ <div
63
+ className="suggestion-items-container"
64
+ style={{
65
+ borderTop: 'none',
66
+ position: 'absolute',
67
+ borderRadius: '0px 0px 5px 5px',
68
+ marginLeft: '14px',
69
+ overflow: 'hidden',
70
+ zIndex: '1',
71
+ left: 'auto',
72
+ backgroundColor: theme.background.paper, //'rgba(8,8,8,0.85)',
73
+ color: theme.text.secondary,
74
+ boxShadow: '3px 3px 5px rgba(0,0,0,0.3)',
75
+ backdropFilter: 'blur(15px)',
76
+ width: `calc(${Math.round(element.parentElement?.getBoundingClientRect().width || 200)}px - 3em)`,
77
+ }}
78
+ >
79
+ {suggestions.map((s, i) => (
80
+ <div
81
+ className="suggestion-item"
82
+ onclick={() => {
83
+ isListOpened && manager.selectSuggestion(i)
84
+ }}
85
+ style={{
86
+ padding: '1em',
87
+ cursor: 'default',
88
+ background: i === selectedIndex ? theme.background.paper : theme.background.default,
89
+ fontWeight: i === selectedIndex ? 'bolder' : 'normal',
90
+ }}
91
+ >
92
+ {s.suggestion.element}
93
+ </div>
94
+ ))}
95
+ </div>
96
+ )
97
+ },
98
+ })
@@ -1,85 +1,73 @@
1
- import { Shade, createComponent, LocationService } from '@furystack/shades'
1
+ import { Shade, createComponent, LocationService, attachProps } from '@furystack/shades'
2
2
  import { ThemeProviderService } from '../services'
3
- import { promisifyAnimation } from '../utils/promisify-animation'
4
3
 
5
4
  export interface Tab {
6
5
  header: JSX.Element
7
6
  component: JSX.Element
7
+ hash: string
8
8
  }
9
9
 
10
- export const Tabs = Shade<
11
- {
12
- tabs: Tab[]
13
- containerStyle?: Partial<CSSStyleDeclaration>
14
- style?: Partial<CSSStyleDeclaration>
15
- activeTab?: number
16
- onChange?: (page: number) => void
10
+ const TabHeader = Shade<{ isActive?: boolean; onActivate: () => void }>({
11
+ shadowDomName: 'shade-tab-header',
12
+ render: ({ children, element, injector, props }) => {
13
+ const { theme } = injector.getInstance(ThemeProviderService)
14
+ const { isActive } = props
15
+
16
+ attachProps(element, {
17
+ style: {
18
+ padding: '1em 2.5em',
19
+ cursor: 'pointer',
20
+ transition: 'box-shadow 1s linear',
21
+ fontWeight: isActive ? 'bolder' : 'inherit',
22
+ background: isActive ? theme.background.paper : theme.background.default,
23
+ color: isActive ? theme.text.primary : theme.text.secondary,
24
+ boxShadow: isActive ? `inset 0 -2px 0 ${theme.palette.primary.main}` : 'none',
25
+ },
26
+ onclick: props.onActivate,
27
+ })
28
+ return <>{children}</>
17
29
  },
18
- { activeIndex: number }
19
- >({
30
+ })
31
+
32
+ export const Tabs = Shade<{
33
+ tabs: Tab[]
34
+ containerStyle?: Partial<CSSStyleDeclaration>
35
+ style?: Partial<CSSStyleDeclaration>
36
+ onChange?: (page: number) => void
37
+ }>({
20
38
  shadowDomName: 'shade-tabs',
21
- getInitialState: ({ props }) => ({ activeIndex: props.activeTab || 0 }),
22
- constructed: ({ injector, updateState }) => {
23
- const subscriptions = [
24
- injector.getInstance(LocationService).onLocationChanged.subscribe(() => {
25
- const { hash } = location
26
- if (hash && hash.startsWith('#tab-')) {
27
- const page = parseInt(hash.replace('#tab-', ''), 10)
28
- page && updateState({ activeIndex: page })
29
- }
30
- }, true),
31
- ]
32
- return () => subscriptions.forEach((s) => s.dispose())
33
- },
34
- render: ({ props, getState, updateState, injector }) => {
35
- const themeProvider = injector.getInstance(ThemeProviderService)
36
- const { theme } = themeProvider
39
+ render: ({ props, element, useObservable, injector }) => {
40
+ attachProps(element, {
41
+ style: { width: '100%', height: '100%', display: 'flex', flexDirection: 'column', ...props.containerStyle },
42
+ })
43
+
44
+ const [hash] = useObservable('updateLocation', injector.getInstance(LocationService).onLocationHashChanged)
45
+
46
+ const activeTab = props.tabs.find((t) => t.hash === hash.replace('#', ''))
47
+
37
48
  return (
38
- <div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', ...props.containerStyle }}>
49
+ <>
39
50
  <div
40
51
  className="shade-tabs-header-container"
41
52
  style={{ display: 'inline-flex', borderRadius: '5px 5px 0 0', overflow: 'hidden', flexShrink: '0' }}
42
53
  >
43
- {props.tabs.map((tab, index) => {
44
- const isActive = index === getState().activeIndex
45
- const jsxElement = (
46
- <div
47
- className={`shade-tabs-headers${isActive ? 'active' : ''}`}
48
- style={{
49
- padding: '1em 2.5em',
50
- cursor: 'pointer',
51
- transition: 'box-shadow 1s linear',
52
- fontWeight: isActive ? 'bolder' : 'inherit',
53
- background: isActive ? theme.background.paper : theme.background.default,
54
- color: isActive ? theme.text.primary : theme.text.secondary,
55
- }}
56
- onclick={() => {
57
- props.onChange && props.onChange(index)
58
- window.history.pushState({}, '', `#tab-${index}`)
59
- updateState({ activeIndex: index })
54
+ {props.tabs.map((tab) => {
55
+ const isActive = tab === activeTab
56
+
57
+ return (
58
+ <TabHeader
59
+ isActive={isActive}
60
+ onActivate={() => {
61
+ window.history.pushState({}, '', `#${tab.hash}`)
60
62
  }}
61
63
  >
62
64
  {tab.header}
63
- </div>
65
+ </TabHeader>
64
66
  )
65
-
66
- if (isActive) {
67
- setTimeout(() =>
68
- promisifyAnimation(
69
- jsxElement,
70
- [{ boxShadow: 'none' }, { boxShadow: 'inset 0 -2px 0 rgba(128,128,192,0.9)' }],
71
- { duration: 500, fill: 'forwards', easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' },
72
- ),
73
- )
74
- }
75
-
76
- return jsxElement
77
67
  })}
78
68
  </div>
79
- <div className="shade-tabs-header-content" style={props.style}>
80
- {props.tabs[getState().activeIndex].component}
81
- </div>
82
- </div>
69
+ {activeTab?.component}
70
+ </>
83
71
  )
84
72
  },
85
73
  })
@@ -25,24 +25,17 @@ export interface WizardProps {
25
25
  /**
26
26
  * An array of Shade<WizardStepProps> components
27
27
  */
28
- steps: Array<(props: WizardStepProps, children: ChildrenList) => JSX.Element<any, any>>
28
+ steps: Array<(props: WizardStepProps, children: ChildrenList) => JSX.Element<any>>
29
29
  /**
30
30
  * A callback that will be executed when the wizard is completed
31
31
  */
32
32
  onFinish?: () => void
33
33
  }
34
34
 
35
- interface WizardState {
36
- currentPage: number
37
- }
38
-
39
- export const Wizard = Shade<WizardProps, WizardState>({
35
+ export const Wizard = Shade<WizardProps>({
40
36
  shadowDomName: 'shades-wizard',
41
- getInitialState: () => ({
42
- currentPage: 0,
43
- }),
44
- render: ({ props, getState, updateState }) => {
45
- const { currentPage } = getState()
37
+ render: ({ props, useState }) => {
38
+ const [currentPage, setCurrentPage] = useState('currentPage', 0)
46
39
 
47
40
  const CurrentPage = props.steps[currentPage]
48
41
 
@@ -62,14 +55,14 @@ export const Wizard = Shade<WizardProps, WizardState>({
62
55
  maxPages={props.steps.length}
63
56
  onNext={() => {
64
57
  if (currentPage < props.steps.length - 1) {
65
- updateState({ currentPage: currentPage + 1 })
58
+ setCurrentPage(currentPage + 1)
66
59
  } else {
67
60
  props.onFinish?.()
68
61
  }
69
62
  }}
70
63
  onPrev={() => {
71
64
  if (currentPage > 0) {
72
- updateState({ currentPage: currentPage - 1 })
65
+ setCurrentPage(currentPage - 1)
73
66
  }
74
67
  }}
75
68
  />
@@ -54,6 +54,20 @@ export class CollectionService<T> implements Disposable {
54
54
  this.isLoading.dispose()
55
55
  }
56
56
 
57
+ public isSelected = (entry: T) => this.selection.getValue().includes(entry)
58
+
59
+ public addToSelection = (entry: T) => {
60
+ this.selection.setValue([...this.selection.getValue(), entry])
61
+ }
62
+
63
+ public removeFromSelection = (entry: T) => {
64
+ this.selection.setValue(this.selection.getValue().filter((e) => e !== entry))
65
+ }
66
+
67
+ public toggleSelection = (entry: T) => {
68
+ this.isSelected(entry) ? this.removeFromSelection(entry) : this.addToSelection(entry)
69
+ }
70
+
57
71
  private readonly loadLock = new Semaphore(1)
58
72
 
59
73
  public getEntries: EntryLoader<T>
@@ -81,71 +95,73 @@ export class CollectionService<T> implements Disposable {
81
95
  const focusedEntry = this.focusedEntry.getValue()
82
96
  const searchTerm = this.searchTerm.getValue()
83
97
 
84
- switch (ev.key) {
85
- case ' ':
86
- ev.preventDefault()
87
- focusedEntry &&
88
- this.selection.setValue(
89
- selectedEntries.includes(focusedEntry)
90
- ? selectedEntries.filter((e) => e !== focusedEntry)
91
- : [...selectedEntries, focusedEntry],
92
- )
93
- break
94
- case '*':
95
- this.selection.setValue(entries.filter((e) => !selectedEntries.includes(e)))
96
- break
97
- case '+':
98
- this.selection.setValue(entries)
99
- break
100
- case '-':
101
- this.selection.setValue([])
102
- break
103
- case 'Insert':
104
- focusedEntry &&
105
- (this.selection.getValue().includes(focusedEntry)
106
- ? this.selection.setValue([...this.selection.getValue().filter((e) => e !== focusedEntry)])
107
- : this.selection.setValue([...this.selection.getValue(), focusedEntry]))
108
- this.focusedEntry.setValue(entries[entries.findIndex((e) => e === this.focusedEntry.getValue()) + 1])
109
-
110
- break
111
- case 'ArrowUp':
112
- ev.preventDefault()
113
- this.focusedEntry.setValue(entries[Math.max(0, entries.findIndex((e) => e === focusedEntry) - 1)])
114
- break
115
- case 'ArrowDown':
116
- ev.preventDefault()
117
- this.focusedEntry.setValue(
118
- entries[Math.min(entries.length - 1, entries.findIndex((e) => e === focusedEntry) + 1)],
119
- )
120
- break
121
- case 'Home': {
122
- this.focusedEntry.setValue(entries[0])
123
- break
124
- }
125
- case 'End': {
126
- this.focusedEntry.setValue(entries[entries.length - 1])
127
- break
128
- }
129
- case 'Tab': {
130
- this.hasFocus.setValue(!hasFocus)
131
- break
132
- }
133
- case 'Escape': {
134
- this.searchTerm.setValue('')
135
- this.selection.setValue([])
136
- break
137
- }
138
- default:
139
- if (this.options.searchField && ev.key.length === 1) {
140
- const newSearchExpression = searchTerm + ev.key
141
- const newFocusedEntry = entries.find(
142
- (e) =>
143
- this.options.searchField &&
144
- (e[this.options.searchField] as any)?.toString().startsWith(newSearchExpression),
98
+ if (hasFocus) {
99
+ switch (ev.key) {
100
+ case ' ':
101
+ ev.preventDefault()
102
+ focusedEntry &&
103
+ this.selection.setValue(
104
+ selectedEntries.includes(focusedEntry)
105
+ ? selectedEntries.filter((e) => e !== focusedEntry)
106
+ : [...selectedEntries, focusedEntry],
107
+ )
108
+ break
109
+ case '*':
110
+ this.selection.setValue(entries.filter((e) => !selectedEntries.includes(e)))
111
+ break
112
+ case '+':
113
+ this.selection.setValue(entries)
114
+ break
115
+ case '-':
116
+ this.selection.setValue([])
117
+ break
118
+ case 'Insert':
119
+ focusedEntry &&
120
+ (this.selection.getValue().includes(focusedEntry)
121
+ ? this.selection.setValue([...this.selection.getValue().filter((e) => e !== focusedEntry)])
122
+ : this.selection.setValue([...this.selection.getValue(), focusedEntry]))
123
+ this.focusedEntry.setValue(entries[entries.findIndex((e) => e === this.focusedEntry.getValue()) + 1])
124
+
125
+ break
126
+ case 'ArrowUp':
127
+ ev.preventDefault()
128
+ this.focusedEntry.setValue(entries[Math.max(0, entries.findIndex((e) => e === focusedEntry) - 1)])
129
+ break
130
+ case 'ArrowDown':
131
+ ev.preventDefault()
132
+ this.focusedEntry.setValue(
133
+ entries[Math.min(entries.length - 1, entries.findIndex((e) => e === focusedEntry) + 1)],
145
134
  )
146
- this.focusedEntry.setValue(newFocusedEntry)
147
- this.searchTerm.setValue(newSearchExpression)
135
+ break
136
+ case 'Home': {
137
+ this.focusedEntry.setValue(entries[0])
138
+ break
148
139
  }
140
+ case 'End': {
141
+ this.focusedEntry.setValue(entries[entries.length - 1])
142
+ break
143
+ }
144
+ case 'Tab': {
145
+ this.hasFocus.setValue(!hasFocus)
146
+ break
147
+ }
148
+ case 'Escape': {
149
+ this.searchTerm.setValue('')
150
+ this.selection.setValue([])
151
+ break
152
+ }
153
+ default:
154
+ if (this.options.searchField && ev.key.length === 1) {
155
+ const newSearchExpression = searchTerm + ev.key
156
+ const newFocusedEntry = entries.find(
157
+ (e) =>
158
+ this.options.searchField &&
159
+ (e[this.options.searchField] as any)?.toString().startsWith(newSearchExpression),
160
+ )
161
+ this.focusedEntry.setValue(newFocusedEntry)
162
+ this.searchTerm.setValue(newSearchExpression)
163
+ }
164
+ }
149
165
  }
150
166
  }
151
167
 
package/tsconfig.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "rootDir": "./src",
8
8
  "jsx": "react",
9
9
  "jsxFactory": "createComponent",
10
- "jsxFragmentFactory": "createFragment"
10
+ "jsxFragmentFactory": "createComponent"
11
11
  },
12
12
  "include": ["src"],
13
13
  "references": [{ "path": "../shades" }]