@furystack/shades-common-components 13.0.1 → 13.2.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 (150) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/esm/components/app-bar.d.ts.map +1 -1
  3. package/esm/components/app-bar.js +12 -3
  4. package/esm/components/app-bar.js.map +1 -1
  5. package/esm/components/avatar.d.ts.map +1 -1
  6. package/esm/components/avatar.js +3 -5
  7. package/esm/components/avatar.js.map +1 -1
  8. package/esm/components/cache-view.d.ts +19 -27
  9. package/esm/components/cache-view.d.ts.map +1 -1
  10. package/esm/components/cache-view.js +2 -20
  11. package/esm/components/cache-view.js.map +1 -1
  12. package/esm/components/cache-view.spec.js +44 -0
  13. package/esm/components/cache-view.spec.js.map +1 -1
  14. package/esm/components/command-palette/command-palette-input.d.ts +1 -2
  15. package/esm/components/command-palette/command-palette-input.d.ts.map +1 -1
  16. package/esm/components/command-palette/command-palette-input.js +14 -36
  17. package/esm/components/command-palette/command-palette-input.js.map +1 -1
  18. package/esm/components/command-palette/command-palette-input.spec.js +14 -116
  19. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -1
  20. package/esm/components/command-palette/index.d.ts.map +1 -1
  21. package/esm/components/command-palette/index.js +3 -0
  22. package/esm/components/command-palette/index.js.map +1 -1
  23. package/esm/components/drawer/index.d.ts.map +1 -1
  24. package/esm/components/drawer/index.js +4 -0
  25. package/esm/components/drawer/index.js.map +1 -1
  26. package/esm/components/drawer/index.spec.js +47 -0
  27. package/esm/components/drawer/index.spec.js.map +1 -1
  28. package/esm/components/noty-list.d.ts.map +1 -1
  29. package/esm/components/noty-list.js +1 -3
  30. package/esm/components/noty-list.js.map +1 -1
  31. package/esm/services/css-variable-theme.d.ts +1 -1
  32. package/esm/services/css-variable-theme.d.ts.map +1 -1
  33. package/esm/services/css-variable-theme.js +5 -5
  34. package/esm/services/css-variable-theme.js.map +1 -1
  35. package/esm/services/css-variable-theme.spec.js +29 -1
  36. package/esm/services/css-variable-theme.spec.js.map +1 -1
  37. package/esm/services/layout-service.d.ts +8 -0
  38. package/esm/services/layout-service.d.ts.map +1 -1
  39. package/esm/services/layout-service.js +16 -0
  40. package/esm/services/layout-service.js.map +1 -1
  41. package/esm/services/layout-service.spec.js +55 -0
  42. package/esm/services/layout-service.spec.js.map +1 -1
  43. package/esm/services/theme-provider-service.d.ts +11 -10
  44. package/esm/services/theme-provider-service.d.ts.map +1 -1
  45. package/esm/services/theme-provider-service.js +3 -2
  46. package/esm/services/theme-provider-service.js.map +1 -1
  47. package/esm/services/theme-provider-service.spec.js +35 -1
  48. package/esm/services/theme-provider-service.spec.js.map +1 -1
  49. package/esm/themes/architect-theme.d.ts +1 -0
  50. package/esm/themes/architect-theme.d.ts.map +1 -1
  51. package/esm/themes/architect-theme.js +1 -0
  52. package/esm/themes/architect-theme.js.map +1 -1
  53. package/esm/themes/auditore-theme.d.ts +1 -0
  54. package/esm/themes/auditore-theme.d.ts.map +1 -1
  55. package/esm/themes/auditore-theme.js +1 -0
  56. package/esm/themes/auditore-theme.js.map +1 -1
  57. package/esm/themes/black-mesa-theme.d.ts +1 -0
  58. package/esm/themes/black-mesa-theme.d.ts.map +1 -1
  59. package/esm/themes/black-mesa-theme.js +1 -0
  60. package/esm/themes/black-mesa-theme.js.map +1 -1
  61. package/esm/themes/default-dark-theme.d.ts +1 -0
  62. package/esm/themes/default-dark-theme.d.ts.map +1 -1
  63. package/esm/themes/default-dark-theme.js +1 -0
  64. package/esm/themes/default-dark-theme.js.map +1 -1
  65. package/esm/themes/default-light-theme.d.ts +1 -0
  66. package/esm/themes/default-light-theme.d.ts.map +1 -1
  67. package/esm/themes/default-light-theme.js +1 -0
  68. package/esm/themes/default-light-theme.js.map +1 -1
  69. package/esm/themes/dragonborn-theme.d.ts +1 -0
  70. package/esm/themes/dragonborn-theme.d.ts.map +1 -1
  71. package/esm/themes/dragonborn-theme.js +1 -0
  72. package/esm/themes/dragonborn-theme.js.map +1 -1
  73. package/esm/themes/hawkins-theme.d.ts +1 -0
  74. package/esm/themes/hawkins-theme.d.ts.map +1 -1
  75. package/esm/themes/hawkins-theme.js +1 -0
  76. package/esm/themes/hawkins-theme.js.map +1 -1
  77. package/esm/themes/jedi-theme.d.ts +1 -0
  78. package/esm/themes/jedi-theme.d.ts.map +1 -1
  79. package/esm/themes/jedi-theme.js +1 -0
  80. package/esm/themes/jedi-theme.js.map +1 -1
  81. package/esm/themes/neon-runner-theme.d.ts +1 -0
  82. package/esm/themes/neon-runner-theme.d.ts.map +1 -1
  83. package/esm/themes/neon-runner-theme.js +1 -0
  84. package/esm/themes/neon-runner-theme.js.map +1 -1
  85. package/esm/themes/plumber-theme.d.ts +1 -0
  86. package/esm/themes/plumber-theme.d.ts.map +1 -1
  87. package/esm/themes/plumber-theme.js +1 -0
  88. package/esm/themes/plumber-theme.js.map +1 -1
  89. package/esm/themes/replicant-theme.d.ts +1 -0
  90. package/esm/themes/replicant-theme.d.ts.map +1 -1
  91. package/esm/themes/replicant-theme.js +1 -0
  92. package/esm/themes/replicant-theme.js.map +1 -1
  93. package/esm/themes/sandworm-theme.d.ts +1 -0
  94. package/esm/themes/sandworm-theme.d.ts.map +1 -1
  95. package/esm/themes/sandworm-theme.js +1 -0
  96. package/esm/themes/sandworm-theme.js.map +1 -1
  97. package/esm/themes/shadow-broker-theme.d.ts +1 -0
  98. package/esm/themes/shadow-broker-theme.d.ts.map +1 -1
  99. package/esm/themes/shadow-broker-theme.js +1 -0
  100. package/esm/themes/shadow-broker-theme.js.map +1 -1
  101. package/esm/themes/sith-theme.d.ts +1 -0
  102. package/esm/themes/sith-theme.d.ts.map +1 -1
  103. package/esm/themes/sith-theme.js +1 -0
  104. package/esm/themes/sith-theme.js.map +1 -1
  105. package/esm/themes/vault-dweller-theme.d.ts +1 -0
  106. package/esm/themes/vault-dweller-theme.d.ts.map +1 -1
  107. package/esm/themes/vault-dweller-theme.js +1 -0
  108. package/esm/themes/vault-dweller-theme.js.map +1 -1
  109. package/esm/themes/wild-hunt-theme.d.ts +1 -0
  110. package/esm/themes/wild-hunt-theme.d.ts.map +1 -1
  111. package/esm/themes/wild-hunt-theme.js +1 -0
  112. package/esm/themes/wild-hunt-theme.js.map +1 -1
  113. package/esm/themes/xenomorph-theme.d.ts +1 -0
  114. package/esm/themes/xenomorph-theme.d.ts.map +1 -1
  115. package/esm/themes/xenomorph-theme.js +1 -0
  116. package/esm/themes/xenomorph-theme.js.map +1 -1
  117. package/package.json +1 -1
  118. package/src/components/app-bar.tsx +12 -3
  119. package/src/components/avatar.tsx +20 -5
  120. package/src/components/cache-view.spec.tsx +63 -0
  121. package/src/components/cache-view.tsx +41 -9
  122. package/src/components/command-palette/command-palette-input.spec.tsx +14 -156
  123. package/src/components/command-palette/command-palette-input.tsx +13 -45
  124. package/src/components/command-palette/index.tsx +4 -0
  125. package/src/components/drawer/index.spec.tsx +64 -0
  126. package/src/components/drawer/index.tsx +5 -0
  127. package/src/components/noty-list.tsx +1 -3
  128. package/src/services/css-variable-theme.spec.ts +43 -1
  129. package/src/services/css-variable-theme.ts +5 -5
  130. package/src/services/layout-service.spec.ts +74 -0
  131. package/src/services/layout-service.ts +18 -0
  132. package/src/services/theme-provider-service.spec.ts +49 -1
  133. package/src/services/theme-provider-service.ts +12 -11
  134. package/src/themes/architect-theme.ts +1 -0
  135. package/src/themes/auditore-theme.ts +1 -0
  136. package/src/themes/black-mesa-theme.ts +1 -0
  137. package/src/themes/default-dark-theme.ts +1 -0
  138. package/src/themes/default-light-theme.ts +1 -0
  139. package/src/themes/dragonborn-theme.ts +1 -0
  140. package/src/themes/hawkins-theme.ts +1 -0
  141. package/src/themes/jedi-theme.ts +1 -0
  142. package/src/themes/neon-runner-theme.ts +1 -0
  143. package/src/themes/plumber-theme.ts +1 -0
  144. package/src/themes/replicant-theme.ts +1 -0
  145. package/src/themes/sandworm-theme.ts +1 -0
  146. package/src/themes/shadow-broker-theme.ts +1 -0
  147. package/src/themes/sith-theme.ts +1 -0
  148. package/src/themes/vault-dweller-theme.ts +1 -0
  149. package/src/themes/wild-hunt-theme.ts +1 -0
  150. package/src/themes/xenomorph-theme.ts +1 -0
@@ -83,6 +83,7 @@ export declare const xenomorphTheme: {
83
83
  wider: string;
84
84
  widest: string;
85
85
  };
86
+ textShadow: string;
86
87
  };
87
88
  transitions: {
88
89
  duration: {
@@ -1 +1 @@
1
- {"version":3,"file":"xenomorph-theme.d.ts","sourceRoot":"","sources":["../../src/themes/xenomorph-theme.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+GV,CAAA"}
1
+ {"version":3,"file":"xenomorph-theme.d.ts","sourceRoot":"","sources":["../../src/themes/xenomorph-theme.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgHV,CAAA"}
@@ -84,6 +84,7 @@ export const xenomorphTheme = {
84
84
  wider: '1.25px',
85
85
  widest: '2px',
86
86
  },
87
+ textShadow: 'none',
87
88
  },
88
89
  transitions: {
89
90
  duration: {
@@ -1 +1 @@
1
- {"version":3,"file":"xenomorph-theme.js","sourceRoot":"","sources":["../../src/themes/xenomorph-theme.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"xenomorph-theme.js","sourceRoot":"","sources":["../../src/themes/xenomorph-theme.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": "13.0.1",
3
+ "version": "13.2.0",
4
4
  "description": "Common UI components for FuryStack Shades",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -5,9 +5,6 @@ export const AppBar = Shade({
5
5
  shadowDomName: 'shade-app-bar',
6
6
  css: {
7
7
  width: '100%',
8
- background: `color-mix(in srgb, ${cssVariableTheme.background.paper} 85%, transparent)`,
9
- backgroundImage: cssVariableTheme.background.paperImage,
10
- backdropFilter: `blur(${cssVariableTheme.effects.blurLg})`,
11
8
  display: 'flex',
12
9
  justifyContent: 'flex-start',
13
10
  alignItems: 'center',
@@ -18,6 +15,18 @@ export const AppBar = Shade({
18
15
  zIndex: '1',
19
16
  fontFamily: cssVariableTheme.typography.fontFamily,
20
17
  color: cssVariableTheme.text.primary,
18
+ // backdrop-filter on the host would create a containing block for position:fixed
19
+ // descendants (per CSS spec), breaking Dropdown overlays inside the AppBar.
20
+ // Using a pseudo-element avoids this while preserving the visual effect.
21
+ '&::before': {
22
+ content: '""',
23
+ position: 'absolute',
24
+ inset: '0',
25
+ zIndex: '-1',
26
+ background: `color-mix(in srgb, ${cssVariableTheme.background.paper} 85%, transparent)`,
27
+ backgroundImage: cssVariableTheme.background.paperImage,
28
+ backdropFilter: `blur(${cssVariableTheme.effects.blurLg})`,
29
+ },
21
30
  '&[data-visible]': {
22
31
  opacity: '1',
23
32
  },
@@ -1,7 +1,6 @@
1
1
  import type { PartialElement } from '@furystack/shades'
2
2
  import { Shade, createComponent } from '@furystack/shades'
3
3
  import { cssVariableTheme } from '../services/css-variable-theme.js'
4
- import { Icon } from './icons/icon.js'
5
4
  import { user as userIcon } from './icons/icon-definitions.js'
6
5
 
7
6
  export type AvatarProps = { avatarUrl: string; fallback?: JSX.Element } & PartialElement<HTMLDivElement>
@@ -37,14 +36,13 @@ export const Avatar = Shade<AvatarProps>({
37
36
  display: 'flex',
38
37
  alignItems: 'center',
39
38
  justifyContent: 'center',
40
- width: '64px',
41
- height: '64px',
39
+ width: '100%',
40
+ height: '100%',
42
41
  borderRadius: cssVariableTheme.shape.borderRadius.full,
43
42
  background: `color-mix(in srgb, ${cssVariableTheme.palette.primary.main} 20%, transparent)`,
44
43
  backdropFilter: 'blur(10px)',
45
44
  textAlign: 'center',
46
45
  userSelect: 'none',
47
- fontSize: '48px',
48
46
  lineHeight: '1',
49
47
  },
50
48
  },
@@ -59,7 +57,24 @@ export const Avatar = Shade<AvatarProps>({
59
57
  if (hasError) {
60
58
  return (
61
59
  <div className="avatar-fallback-container">
62
- <div className="avatar-fallback-icon">{props.fallback || <Icon icon={userIcon} size={48} />}</div>
60
+ <div className="avatar-fallback-icon">
61
+ {props.fallback || (
62
+ <svg
63
+ width="100%"
64
+ height="100%"
65
+ viewBox={userIcon.viewBox ?? '0 0 24 24'}
66
+ fill="none"
67
+ stroke="currentColor"
68
+ stroke-width="2"
69
+ stroke-linecap="round"
70
+ stroke-linejoin="round"
71
+ >
72
+ {userIcon.paths.map((p) => (
73
+ <path d={p.d} />
74
+ ))}
75
+ </svg>
76
+ )}
77
+ </div>
63
78
  </div>
64
79
  )
65
80
  }
@@ -10,6 +10,15 @@ const TestContent = Shade<{ data: CacheWithValue<string> }>({
10
10
  render: ({ props }) => <span className="content-value">{props.data.value}</span>,
11
11
  })
12
12
 
13
+ const TestContentWithLabel = Shade<{ data: CacheWithValue<string>; label: string }>({
14
+ shadowDomName: 'test-cache-content-with-label',
15
+ render: ({ props }) => (
16
+ <span className="content-value">
17
+ {props.label}: {props.data.value}
18
+ </span>
19
+ ),
20
+ })
21
+
13
22
  const renderCacheView = async (
14
23
  cache: Cache<string, [string]>,
15
24
  args: [string],
@@ -210,4 +219,58 @@ describe('CacheView', () => {
210
219
  cache[Symbol.dispose]()
211
220
  })
212
221
  })
222
+
223
+ describe('contentProps', () => {
224
+ it('should forward contentProps to the content component', async () => {
225
+ const cache = new Cache<string, [string]>({ load: async (key) => `Hello ${key}` })
226
+ await cache.get('world')
227
+
228
+ const el = (
229
+ <div>
230
+ <CacheView
231
+ cache={cache}
232
+ args={['world']}
233
+ content={TestContentWithLabel}
234
+ contentProps={{ label: 'Greeting' }}
235
+ />
236
+ </div>
237
+ )
238
+ const cacheView = el.firstElementChild as JSX.Element
239
+ cacheView.updateComponent()
240
+ await flushUpdates()
241
+
242
+ const contentEl = cacheView.querySelector('test-cache-content-with-label') as JSX.Element
243
+ expect(contentEl).not.toBeNull()
244
+ contentEl.updateComponent()
245
+ await flushUpdates()
246
+ const valueEl = contentEl.querySelector('.content-value')
247
+ expect(valueEl?.textContent).toBe('Greeting: Hello world')
248
+ cache[Symbol.dispose]()
249
+ })
250
+
251
+ it('should forward contentProps when cache entry is obsolete', async () => {
252
+ const loadFn = vi.fn(async (key: string) => `Hello ${key}`)
253
+ const cache = new Cache<string, [string]>({ load: loadFn })
254
+ await cache.get('world')
255
+ cache.setObsolete('world')
256
+
257
+ const el = (
258
+ <div>
259
+ <CacheView cache={cache} args={['world']} content={TestContentWithLabel} contentProps={{ label: 'Stale' }} />
260
+ </div>
261
+ )
262
+ const cacheView = el.firstElementChild as JSX.Element
263
+ cacheView.updateComponent()
264
+ await flushUpdates()
265
+
266
+ const contentEl = cacheView.querySelector('test-cache-content-with-label') as JSX.Element
267
+ expect(contentEl).not.toBeNull()
268
+ contentEl.updateComponent()
269
+ await flushUpdates()
270
+ const valueEl = contentEl.querySelector('.content-value')
271
+ expect(valueEl?.textContent).toBe('Stale: Hello world')
272
+ expect(loadFn).toHaveBeenCalledTimes(2)
273
+ cache[Symbol.dispose]()
274
+ })
275
+ })
213
276
  })
@@ -1,6 +1,6 @@
1
1
  import type { Cache, CacheWithValue } from '@furystack/cache'
2
2
  import { hasCacheValue, isFailedCacheResult, isObsoleteCacheResult } from '@furystack/cache'
3
- import type { ShadeComponent } from '@furystack/shades'
3
+ import type { PartialElement, ShadeComponent } from '@furystack/shades'
4
4
  import { Shade, createComponent } from '@furystack/shades'
5
5
 
6
6
  import { cssVariableTheme } from '../services/css-variable-theme.js'
@@ -11,14 +11,19 @@ import { Result } from './result.js'
11
11
  * Props for the CacheView component.
12
12
  * @typeParam TData - The type of data stored in the cache
13
13
  * @typeParam TArgs - The tuple type of arguments used to identify the cache entry
14
+ * @typeParam TContentProps - The full props type of the content component (must include `data`)
14
15
  */
15
- export type CacheViewProps<TData, TArgs extends any[]> = {
16
+ export type CacheViewProps<
17
+ TData,
18
+ TArgs extends any[],
19
+ TContentProps extends { data: CacheWithValue<TData> } = { data: CacheWithValue<TData> },
20
+ > = {
16
21
  /** The cache instance to observe and control */
17
22
  cache: Cache<TData, TArgs>
18
23
  /** The arguments identifying which cache entry to display */
19
24
  args: TArgs
20
- /** Shades component rendered when a value is available (loaded or obsolete). Receives CacheWithValue<TData>. */
21
- content: ShadeComponent<{ data: CacheWithValue<TData> }>
25
+ /** Shades component rendered when a value is available (loaded or obsolete). */
26
+ content: ShadeComponent<TContentProps>
22
27
  /** Optional custom loader element. Default: null (nothing shown when loading). */
23
28
  loader?: JSX.Element
24
29
  /**
@@ -27,7 +32,9 @@ export type CacheViewProps<TData, TArgs extends any[]> = {
27
32
  * If not provided, a default Result + retry Button is shown.
28
33
  */
29
34
  error?: (error: unknown, retry: () => void) => JSX.Element
30
- }
35
+ } & (keyof Omit<TContentProps, 'data' | keyof PartialElement<HTMLElement>> extends never
36
+ ? { contentProps?: never }
37
+ : { contentProps: Omit<TContentProps, 'data' | keyof PartialElement<HTMLElement>> })
31
38
 
32
39
  const getDefaultErrorUi = (error: unknown, retry: () => void): JSX.Element =>
33
40
  (
@@ -55,15 +62,33 @@ const getDefaultErrorUi = (error: unknown, retry: () => void): JSX.Element =>
55
62
  * })
56
63
  *
57
64
  * <CacheView cache={userCache} args={[userId]} content={MyContent} />
65
+ *
66
+ * // With custom content props
67
+ * const MyContentWithLabel = Shade<{ data: CacheWithValue<User>; label: string }>({
68
+ * shadowDomName: 'my-content-with-label',
69
+ * render: ({ props }) => <div>{props.label}: {props.data.value.name}</div>,
70
+ * })
71
+ *
72
+ * <CacheView cache={userCache} args={[userId]} content={MyContentWithLabel} contentProps={{ label: 'User' }} />
58
73
  * ```
59
74
  */
60
- export const CacheView: <TData, TArgs extends any[]>(props: CacheViewProps<TData, TArgs>) => JSX.Element = Shade({
75
+ /** @internal Ungeneric props used by the Shade runtime; the public generic signature is applied via the export cast. */
76
+ type InternalCacheViewProps = {
77
+ cache: Cache<unknown, unknown[]>
78
+ args: unknown[]
79
+ content: ShadeComponent<{ data: CacheWithValue<unknown> }>
80
+ contentProps?: Record<string, unknown>
81
+ loader?: JSX.Element
82
+ error?: (error: unknown, retry: () => void) => JSX.Element
83
+ }
84
+
85
+ export const CacheView = Shade<InternalCacheViewProps>({
61
86
  shadowDomName: 'shade-cache-view',
62
87
  css: {
63
88
  fontFamily: cssVariableTheme.typography.fontFamily,
64
89
  },
65
90
  render: ({ props, useObservable, useState }): JSX.Element | null => {
66
- const { cache, args, content, loader, error } = props
91
+ const { cache, args, content, loader, error, contentProps } = props
67
92
 
68
93
  const argsKey = JSON.stringify(args)
69
94
  const observable = cache.getObservable(...args)
@@ -96,12 +121,19 @@ export const CacheView: <TData, TArgs extends any[]>(props: CacheViewProps<TData
96
121
  } else if (lastReloadedArgsKey !== null) {
97
122
  setLastReloadedArgsKey(null)
98
123
  }
99
- return createComponent(content as ShadeComponent<{ data: CacheWithValue<unknown> }>, {
124
+ return createComponent(content, {
100
125
  data: result,
126
+ ...(contentProps ?? {}),
101
127
  }) as unknown as JSX.Element
102
128
  }
103
129
 
104
130
  // 3. Loading last
105
131
  return loader ?? null
106
132
  },
107
- })
133
+ }) as unknown as <
134
+ TData,
135
+ TArgs extends any[],
136
+ TContentProps extends { data: CacheWithValue<TData> } = { data: CacheWithValue<TData> },
137
+ >(
138
+ props: CacheViewProps<TData, TArgs, TContentProps>,
139
+ ) => JSX.Element
@@ -6,42 +6,14 @@ import { CommandPaletteInput } from './command-palette-input.js'
6
6
  import { CommandPaletteManager } from './command-palette-manager.js'
7
7
 
8
8
  describe('CommandPaletteInput', () => {
9
- let originalAnimate: typeof Element.prototype.animate
10
- let animateCalls: Array<{ keyframes: unknown; options: unknown }>
11
-
12
9
  beforeEach(() => {
13
10
  vi.useFakeTimers()
14
11
  document.body.innerHTML = '<div id="root"></div>'
15
- animateCalls = []
16
- originalAnimate = Element.prototype.animate
17
-
18
- Element.prototype.animate = vi.fn(
19
- (keyframes: Keyframe[] | PropertyIndexedKeyframes | null, options?: number | KeyframeAnimationOptions) => {
20
- animateCalls.push({ keyframes, options })
21
- const mockAnimation = {
22
- onfinish: null as ((event: AnimationPlaybackEvent) => void) | null,
23
- oncancel: null as ((event: AnimationPlaybackEvent) => void) | null,
24
- cancel: vi.fn(),
25
- play: vi.fn(),
26
- pause: vi.fn(),
27
- finish: vi.fn(),
28
- addEventListener: vi.fn(),
29
- removeEventListener: vi.fn(),
30
- }
31
-
32
- setTimeout(() => {
33
- mockAnimation.onfinish?.({} as AnimationPlaybackEvent)
34
- }, 10)
35
-
36
- return mockAnimation as unknown as Animation
37
- },
38
- ) as typeof Element.prototype.animate
39
12
  })
40
13
 
41
14
  afterEach(() => {
42
15
  vi.useRealTimers()
43
16
  document.body.innerHTML = ''
44
- Element.prototype.animate = originalAnimate
45
17
  vi.restoreAllMocks()
46
18
  })
47
19
 
@@ -89,7 +61,7 @@ describe('CommandPaletteInput', () => {
89
61
  })
90
62
  })
91
63
 
92
- it('should start with width 0% when closed', async () => {
64
+ it('should always have width 100%', async () => {
93
65
  await usingAsync(new Injector(), async (injector) => {
94
66
  await usingAsync(createManager(), async (manager) => {
95
67
  manager.isOpened.setValue(false)
@@ -104,15 +76,15 @@ describe('CommandPaletteInput', () => {
104
76
  await flushUpdates()
105
77
 
106
78
  const component = document.querySelector('shades-command-palette-input') as HTMLElement
107
- expect(component.hasAttribute('data-opened')).toBe(false)
79
+ const computedStyle = window.getComputedStyle(component)
80
+ expect(computedStyle.width).toBe('100%')
108
81
  })
109
82
  })
110
83
  })
111
84
 
112
- it('should have width 100% when opened', async () => {
85
+ it('should have overflow hidden style', async () => {
113
86
  await usingAsync(new Injector(), async (injector) => {
114
87
  await usingAsync(createManager(), async (manager) => {
115
- manager.isOpened.setValue(true)
116
88
  const rootElement = document.getElementById('root') as HTMLDivElement
117
89
 
118
90
  initializeShadeRoot({
@@ -124,73 +96,13 @@ describe('CommandPaletteInput', () => {
124
96
  await flushUpdates()
125
97
 
126
98
  const component = document.querySelector('shades-command-palette-input') as HTMLElement
127
- expect(component.hasAttribute('data-opened')).toBe(true)
128
- })
129
- })
130
- })
131
-
132
- it('should animate width when opening', async () => {
133
- await usingAsync(new Injector(), async (injector) => {
134
- await usingAsync(createManager(), async (manager) => {
135
- manager.isOpened.setValue(false)
136
- const rootElement = document.getElementById('root') as HTMLDivElement
137
-
138
- initializeShadeRoot({
139
- injector,
140
- rootElement,
141
- jsxElement: <CommandPaletteInput manager={manager} />,
142
- })
143
-
144
- await flushUpdates()
145
- animateCalls = []
146
-
147
- manager.isOpened.setValue(true)
148
- await flushUpdates()
149
-
150
- const widthAnimation = animateCalls.find(
151
- (call) =>
152
- Array.isArray(call.keyframes) &&
153
- call.keyframes.some((kf: Keyframe) => kf.width === '0%') &&
154
- call.keyframes.some((kf: Keyframe) => kf.width === '100%'),
155
- )
156
-
157
- expect(widthAnimation).toBeDefined()
158
- expect((widthAnimation?.options as KeyframeAnimationOptions)?.duration).toBe(300)
159
- })
160
- })
161
- })
162
-
163
- it('should animate width when closing', async () => {
164
- await usingAsync(new Injector(), async (injector) => {
165
- await usingAsync(createManager(), async (manager) => {
166
- manager.isOpened.setValue(true)
167
- const rootElement = document.getElementById('root') as HTMLDivElement
168
-
169
- initializeShadeRoot({
170
- injector,
171
- rootElement,
172
- jsxElement: <CommandPaletteInput manager={manager} />,
173
- })
174
-
175
- await flushUpdates()
176
- animateCalls = []
177
-
178
- manager.isOpened.setValue(false)
179
- await flushUpdates()
180
-
181
- const widthAnimation = animateCalls.find(
182
- (call) =>
183
- Array.isArray(call.keyframes) &&
184
- call.keyframes.some((kf: Keyframe) => kf.width === '100%') &&
185
- call.keyframes.some((kf: Keyframe) => kf.width === '0%'),
186
- )
187
-
188
- expect(widthAnimation).toBeDefined()
99
+ const computedStyle = window.getComputedStyle(component)
100
+ expect(computedStyle.overflow).toBe('hidden')
189
101
  })
190
102
  })
191
103
  })
192
104
 
193
- it('should clear input value when opening', async () => {
105
+ it('should focus input when opened', async () => {
194
106
  await usingAsync(new Injector(), async (injector) => {
195
107
  await usingAsync(createManager(), async (manager) => {
196
108
  manager.isOpened.setValue(false)
@@ -206,12 +118,12 @@ describe('CommandPaletteInput', () => {
206
118
 
207
119
  const component = document.querySelector('shades-command-palette-input') as HTMLElement
208
120
  const inputElement = component?.querySelector('input') as HTMLInputElement
209
- inputElement.value = 'some text'
121
+ const focusSpy = vi.spyOn(inputElement, 'focus')
210
122
 
211
123
  manager.isOpened.setValue(true)
212
124
  await flushUpdates()
213
125
 
214
- expect(inputElement.value).toBe('')
126
+ expect(focusSpy).toHaveBeenCalled()
215
127
  })
216
128
  })
217
129
  })
@@ -236,35 +148,13 @@ describe('CommandPaletteInput', () => {
236
148
 
237
149
  manager.isOpened.setValue(false)
238
150
  await flushUpdates()
239
- await vi.advanceTimersByTimeAsync(20)
240
- await flushUpdates()
241
151
 
242
152
  expect(inputElement.value).toBe('')
243
153
  })
244
154
  })
245
155
  })
246
156
 
247
- it('should have overflow hidden style', async () => {
248
- await usingAsync(new Injector(), async (injector) => {
249
- await usingAsync(createManager(), async (manager) => {
250
- const rootElement = document.getElementById('root') as HTMLDivElement
251
-
252
- initializeShadeRoot({
253
- injector,
254
- rootElement,
255
- jsxElement: <CommandPaletteInput manager={manager} />,
256
- })
257
-
258
- await flushUpdates()
259
-
260
- const component = document.querySelector('shades-command-palette-input') as HTMLElement
261
- const computedStyle = window.getComputedStyle(component)
262
- expect(computedStyle.overflow).toBe('hidden')
263
- })
264
- })
265
- })
266
-
267
- it('should use cubic-bezier easing for animations', async () => {
157
+ it('should preserve input value when opening', async () => {
268
158
  await usingAsync(new Injector(), async (injector) => {
269
159
  await usingAsync(createManager(), async (manager) => {
270
160
  manager.isOpened.setValue(false)
@@ -277,47 +167,15 @@ describe('CommandPaletteInput', () => {
277
167
  })
278
168
 
279
169
  await flushUpdates()
280
- animateCalls = []
281
170
 
282
- manager.isOpened.setValue(true)
283
- await flushUpdates()
284
-
285
- const widthAnimation = animateCalls.find(
286
- (call) => Array.isArray(call.keyframes) && call.keyframes.some((kf: Keyframe) => 'width' in kf),
287
- )
288
-
289
- expect(widthAnimation).toBeDefined()
290
- expect((widthAnimation?.options as KeyframeAnimationOptions)?.easing).toBe(
291
- 'cubic-bezier(0.595, 0.425, 0.415, 0.845)',
292
- )
293
- })
294
- })
295
- })
296
-
297
- it('should fill animation forwards', async () => {
298
- await usingAsync(new Injector(), async (injector) => {
299
- await usingAsync(createManager(), async (manager) => {
300
- manager.isOpened.setValue(false)
301
- const rootElement = document.getElementById('root') as HTMLDivElement
302
-
303
- initializeShadeRoot({
304
- injector,
305
- rootElement,
306
- jsxElement: <CommandPaletteInput manager={manager} />,
307
- })
308
-
309
- await flushUpdates()
310
- animateCalls = []
171
+ const component = document.querySelector('shades-command-palette-input') as HTMLElement
172
+ const inputElement = component?.querySelector('input') as HTMLInputElement
173
+ inputElement.value = 'some text'
311
174
 
312
175
  manager.isOpened.setValue(true)
313
176
  await flushUpdates()
314
177
 
315
- const widthAnimation = animateCalls.find(
316
- (call) => Array.isArray(call.keyframes) && call.keyframes.some((kf: Keyframe) => 'width' in kf),
317
- )
318
-
319
- expect(widthAnimation).toBeDefined()
320
- expect((widthAnimation?.options as KeyframeAnimationOptions)?.fill).toBe('forwards')
178
+ expect(inputElement.value).toBe('some text')
321
179
  })
322
180
  })
323
181
  })
@@ -1,45 +1,13 @@
1
- import type { RefObject } from '@furystack/shades'
2
1
  import { Shade, createComponent } from '@furystack/shades'
3
2
  import { cssVariableTheme } from '../../services/css-variable-theme.js'
4
- import { promisifyAnimation } from '../../utils/promisify-animation.js'
5
3
  import type { CommandPaletteManager } from './command-palette-manager.js'
6
4
 
7
- const animateOpenState = async (
8
- wrapperRef: RefObject<HTMLDivElement>,
9
- inputRef: RefObject<HTMLInputElement>,
10
- isOpened: boolean,
11
- ) => {
12
- const wrapper = wrapperRef.current
13
- const input = inputRef.current
14
- if (wrapper && input) {
15
- if (isOpened) {
16
- input.value = ''
17
- await promisifyAnimation(wrapper, [{ width: '0%' }, { width: '100%' }], {
18
- duration: 300,
19
- fill: 'forwards',
20
- easing: 'cubic-bezier(0.595, 0.425, 0.415, 0.845)',
21
- })
22
- input.focus()
23
- } else {
24
- await promisifyAnimation(wrapper, [{ width: '100%' }, { width: '0%' }], {
25
- duration: 300,
26
- fill: 'forwards',
27
- easing: 'cubic-bezier(0.595, 0.425, 0.415, 0.845)',
28
- })
29
- input.value = ''
30
- }
31
- }
32
- }
33
-
34
5
  export const CommandPaletteInput = Shade<{ manager: CommandPaletteManager }>({
35
6
  shadowDomName: 'shades-command-palette-input',
36
7
  css: {
37
- width: '0%',
8
+ width: '100%',
38
9
  fontFamily: cssVariableTheme.typography.fontFamily,
39
10
  overflow: 'hidden',
40
- '&[data-opened]': {
41
- width: '100%',
42
- },
43
11
  '& input': {
44
12
  color: cssVariableTheme.text.primary,
45
13
  outline: 'none',
@@ -53,20 +21,20 @@ export const CommandPaletteInput = Shade<{ manager: CommandPaletteManager }>({
53
21
  letterSpacing: '0.01em',
54
22
  },
55
23
  },
56
- render: ({ props, useObservable, useRef, useHostProps }) => {
57
- const { manager } = props
58
- const wrapperRef = useRef<HTMLDivElement>('wrapper')
24
+ render: ({ props, useObservable, useRef }) => {
59
25
  const inputRef = useRef<HTMLInputElement>('input')
60
-
61
- const [isCurrentlyOpened] = useObservable('isOpened', manager.isOpened, {
62
- onChange: (newValue) => void animateOpenState(wrapperRef, inputRef, newValue),
26
+ useObservable('isOpened', props.manager.isOpened, {
27
+ onChange: (isOpened) => {
28
+ if (inputRef.current) {
29
+ if (isOpened) {
30
+ inputRef.current.focus()
31
+ } else {
32
+ inputRef.current.value = ''
33
+ }
34
+ }
35
+ },
63
36
  })
64
- useHostProps({ ...(isCurrentlyOpened ? { 'data-opened': '' } : {}) })
65
37
 
66
- return (
67
- <div ref={wrapperRef} style={{ width: isCurrentlyOpened ? '100%' : '0%', overflow: 'hidden' }}>
68
- <input ref={inputRef} autofocus placeholder="Type to search commands..." />
69
- </div>
70
- )
38
+ return <input ref={inputRef} autofocus placeholder="Type to search commands..." />
71
39
  },
72
40
  })
@@ -85,6 +85,10 @@ export const CommandPalette = Shade<CommandPaletteProps>({
85
85
  )
86
86
  }
87
87
 
88
+ if (!manager.isOpened.getValue()) {
89
+ manager.isOpened.setValue(true)
90
+ }
91
+
88
92
  void manager.getSuggestion({ injector, term: (ev.target as HTMLInputElement).value })
89
93
  }}
90
94
  >