@linxin666/dsh-client-ui-task-board 0.2.0 → 0.2.2

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 (37) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +1 -1
  3. package/README.zh.md +1 -1
  4. package/lib/client.js +349 -73
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +161 -31
  7. package/lib/types/client/PluginSettingsCard.d.ts +22 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +2 -2
  9. package/lib/types/client/host-api.d.ts +3 -3
  10. package/lib/types/client/sidebar-entry-core.d.ts +60 -0
  11. package/lib/types/client/sidebar-entry.d.ts +8 -10
  12. package/lib/types/core/controller.d.ts +9 -2
  13. package/lib/types/core/schedule.d.ts +7 -0
  14. package/lib/types/host-ledger.d.ts +10 -0
  15. package/lib/types/host-routes.d.ts +3 -1
  16. package/lib/types/host-runner.d.ts +12 -1
  17. package/lib/types/host-service.d.ts +5 -1
  18. package/lib/types/protocol.d.ts +6 -0
  19. package/package.json +1 -2
  20. package/src/client/PluginSettingsCard.tsx +219 -18
  21. package/src/client/TaskBoardSettingsCard.tsx +10 -10
  22. package/src/client/board/TaskBoard.tsx +5 -5
  23. package/src/client/board/TaskCard.tsx +1 -0
  24. package/src/client/board/TaskDetail.tsx +1 -1
  25. package/src/client/board-mount.tsx +1 -0
  26. package/src/client/host-api.ts +12 -3
  27. package/src/client/settings-card.module.css +90 -1
  28. package/src/client/sidebar-entry-core.ts +208 -0
  29. package/src/client/sidebar-entry.ts +24 -144
  30. package/src/core/controller.ts +20 -3
  31. package/src/core/execution.ts +1 -2
  32. package/src/core/schedule.ts +57 -10
  33. package/src/host-ledger.ts +67 -6
  34. package/src/host-routes.ts +11 -3
  35. package/src/host-runner.ts +24 -6
  36. package/src/host-service.ts +24 -7
  37. package/src/protocol.ts +7 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-client-ui-task-board",
3
3
  "description": "Host-authoritative task board for the DSH Web GUI with real session execution, Host cron scheduling, and optional cross-platform idle-sleep protection; mounted without DSH source changes.",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
@@ -55,7 +55,6 @@
55
55
  "@types/react-dom": "^18.3.5",
56
56
  "@types/node": "^22.20.0",
57
57
  "jsdom": "^25.0.0",
58
- "lightningcss": "1.32.0",
59
58
  "react": "^18.3.1",
60
59
  "react-dom": "^18.3.1",
61
60
  "tsdown": "0.22.2",
@@ -8,7 +8,7 @@
8
8
  * ui-plugin-config PluginCard in a self-contained slice.
9
9
  */
10
10
 
11
- import { useState, type ReactNode } from 'react'
11
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState, type KeyboardEvent, type ReactNode } from 'react'
12
12
  import type { CardShell } from './settings-form.ts'
13
13
  import css from './settings-card.module.css'
14
14
 
@@ -255,6 +255,210 @@ export function ValueField(props: FieldProps & {
255
255
  )
256
256
  }
257
257
 
258
+ const NON_SKIN_BODY_MARKERS = new Set(['dshSkinCenter', 'dshSidebarCollapsed'])
259
+ // 检测使用使用皮肤,用了皮肤退回原生select样式,防止样式冲突。默认外观下使用优化后的select样式。
260
+ function isSkinActive(): boolean {
261
+ const datasetList = Object.keys(document.body.dataset)
262
+ const isActive = datasetList.some(key => key.startsWith('dsh') && !NON_SKIN_BODY_MARKERS.has(key))
263
+ return isActive
264
+ }
265
+
266
+ interface SelectOption {
267
+ value: string
268
+ label: string
269
+ }
270
+
271
+ const SELECT_CLOSE_MS = 100
272
+
273
+ /**
274
+ * The shared dual-mode select control. While an appearance skin is active it
275
+ * renders the legacy native `<select>` untouched, so element-level skin
276
+ * selectors keep working; under the default appearance it renders a
277
+ * self-drawn `role="listbox"` popup whose open/close is transition-animated.
278
+ * Staged cards reach it through BooleanField/ChoiceField; immediate-apply
279
+ * editors (the side-card prefs) bind it directly through onEdit.
280
+ * 双模式下拉框:皮肤激活时用原生 select,默认外观用自绘动画弹层。
281
+ */
282
+ export function SelectField(props: {
283
+ id: string
284
+ options: ReadonlyArray<SelectOption>
285
+ value: string
286
+ disabled: boolean
287
+ invalid: boolean
288
+ onEdit: (text: string) => void
289
+ }) {
290
+ const { id, options, value } = props
291
+ const [open, setOpen] = useState(false)
292
+ const [closing, setClosing] = useState(false)
293
+ const [phase, setPhase] = useState<'initial' | 'open'>('initial')
294
+ const [activeIndex, setActiveIndex] = useState(0)
295
+ const closeTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
296
+ const wrapRef = useRef<HTMLDivElement | null>(null)
297
+ const popupRef = useRef<HTMLDivElement | null>(null)
298
+
299
+ const currentIndex = () => {
300
+ const index = options.findIndex(option => option.value === value)
301
+ return index >= 0 ? index : 0
302
+ }
303
+
304
+ const close = useCallback(() => {
305
+ if (closeTimer.current !== undefined) clearTimeout(closeTimer.current)
306
+ setClosing(true)
307
+ closeTimer.current = setTimeout(() => {
308
+ setClosing(false)
309
+ setOpen(false)
310
+ }, SELECT_CLOSE_MS)
311
+ }, [])
312
+
313
+ const openPopup = () => {
314
+ if (closeTimer.current !== undefined) clearTimeout(closeTimer.current)
315
+ setActiveIndex(currentIndex())
316
+ setPhase('initial')
317
+ setClosing(false)
318
+ setOpen(true)
319
+ }
320
+
321
+ const commit = (index: number) => {
322
+ const option = options[index]
323
+ if (option) props.onEdit(option.value)
324
+ close()
325
+ }
326
+
327
+ const onTriggerClick = () => {
328
+ if (props.disabled) return
329
+ if (open && !closing) close()
330
+ else openPopup()
331
+ }
332
+
333
+ const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
334
+ if (props.disabled) return
335
+ const count = options.length
336
+ switch (event.key) {
337
+ case 'ArrowDown':
338
+ case 'ArrowUp':
339
+ case 'Enter':
340
+ case ' ':
341
+ event.preventDefault()
342
+ if (!open) {
343
+ openPopup()
344
+ } else if (!closing) {
345
+ if (event.key === 'ArrowDown') setActiveIndex(index => (index + 1) % count)
346
+ else if (event.key === 'ArrowUp') setActiveIndex(index => (index - 1 + count) % count)
347
+ else commit(activeIndex)
348
+ }
349
+ break
350
+ case 'Escape':
351
+ if (open) {
352
+ event.preventDefault()
353
+ event.stopPropagation()
354
+ close()
355
+ }
356
+ break
357
+ case 'Tab':
358
+ if (open) close()
359
+ break
360
+ }
361
+ }
362
+
363
+ useEffect(() => () => {
364
+ if (closeTimer.current !== undefined) clearTimeout(closeTimer.current)
365
+ }, [])
366
+ useLayoutEffect(() => {
367
+ if (open && !closing && phase === 'initial') {
368
+ void popupRef.current?.offsetHeight
369
+ setPhase('open')
370
+ }
371
+ }, [open, closing, phase])
372
+
373
+ useEffect(() => {
374
+ if (!open) return
375
+ const onPointerDown = (event: PointerEvent) => {
376
+ const target = event.target
377
+ if (target instanceof Node && !wrapRef.current?.contains(target)) close()
378
+ }
379
+ document.addEventListener('pointerdown', onPointerDown)
380
+ return () => document.removeEventListener('pointerdown', onPointerDown)
381
+ }, [open, close])
382
+
383
+ useEffect(() => {
384
+ if (props.disabled && open) close()
385
+ }, [props.disabled, open, close])
386
+
387
+ // 使用了皮肤则退回原生select样式防止样式冲突。
388
+ if (isSkinActive()) {
389
+ return (
390
+ <select
391
+ id={id}
392
+ className={css.select}
393
+ value={value}
394
+ disabled={props.disabled}
395
+ onChange={(event) => { props.onEdit(event.target.value) }}
396
+ >
397
+ {options.map(option => (
398
+ <option key={option.value} value={option.value}>{option.label}</option>
399
+ ))}
400
+ </select>
401
+ )
402
+ }
403
+
404
+ const label = options.find(option => option.value === value)?.label ?? ''
405
+ const popupClass = closing
406
+ ? `${css.selectPopup} ${css.selectPopupClose}`
407
+ : phase === 'open'
408
+ ? `${css.selectPopup} ${css.selectPopupOpen}`
409
+ : css.selectPopup
410
+ return (
411
+ <div className={css.selectWrap} ref={wrapRef}>
412
+ <button
413
+ type="button"
414
+ id={id}
415
+ className={`${css.select} ${css.selectButton}`}
416
+ disabled={props.disabled}
417
+ aria-haspopup="listbox"
418
+ aria-expanded={open}
419
+ aria-activedescendant={open ? `${id}-o${activeIndex}` : undefined}
420
+ aria-invalid={props.invalid || undefined}
421
+ onClick={onTriggerClick}
422
+ onKeyDown={onKeyDown}
423
+ >
424
+ <span className={css.selectLabel}>{label}</span>
425
+ <svg
426
+ width="14"
427
+ height="14"
428
+ viewBox="0 0 14 14"
429
+ fill="none"
430
+ xmlns="http://www.w3.org/2000/svg"
431
+ className={open ? `${css.selectChevron} ${css.selectChevronOpen}` : css.selectChevron}
432
+ aria-hidden="true"
433
+ >
434
+ <path
435
+ d="M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z"
436
+ fill="currentColor"
437
+ />
438
+ </svg>
439
+ </button>
440
+ {open
441
+ ? (
442
+ <div className={popupClass} role="listbox" ref={popupRef}>
443
+ {options.map((option, index) => (
444
+ <div
445
+ key={option.value}
446
+ id={`${id}-o${index}`}
447
+ role="option"
448
+ aria-selected={option.value === value}
449
+ className={`${css.selectOption}${option.value === value ? ` ${css.selectOptionSelected}` : ''}${index === activeIndex && !closing ? ` ${css.selectOptionActive}` : ''}`}
450
+ onClick={() => { commit(index) }}
451
+ >
452
+ {option.label}
453
+ </div>
454
+ ))}
455
+ </div>
456
+ )
457
+ : null}
458
+ </div>
459
+ )
460
+ }
461
+
258
462
  /** A staged boolean field: 继承 / 开 / 关. */
259
463
  export function BooleanField(props: FieldProps & {
260
464
  /** Copy for the inherit option. */
@@ -284,17 +488,18 @@ export function BooleanField(props: FieldProps & {
284
488
  )
285
489
  : null}
286
490
  </div>
287
- <select
491
+ <SelectField
288
492
  id={props.id}
289
- className={css.select}
493
+ options={[
494
+ { value: '', label: props.inheritLabel },
495
+ { value: 'true', label: props.onLabel },
496
+ { value: 'false', label: props.offLabel },
497
+ ]}
290
498
  value={props.text}
291
499
  disabled={props.disabled}
292
- onChange={(event) => { props.onEdit(event.target.value) }}
293
- >
294
- <option value="">{props.inheritLabel}</option>
295
- <option value="true">{props.onLabel}</option>
296
- <option value="false">{props.offLabel}</option>
297
- </select>
500
+ invalid={props.invalid}
501
+ onEdit={props.onEdit}
502
+ />
298
503
  <p className={css.hint}>{props.hint}</p>
299
504
  </div>
300
505
  )
@@ -327,18 +532,14 @@ export function ChoiceField(props: FieldProps & {
327
532
  )
328
533
  : null}
329
534
  </div>
330
- <select
535
+ <SelectField
331
536
  id={props.id}
332
- className={css.select}
537
+ options={[{ value: '', label: props.inheritLabel }, ...props.choices]}
333
538
  value={props.text}
334
539
  disabled={props.disabled}
335
- onChange={(event) => { props.onEdit(event.target.value) }}
336
- >
337
- <option value="">{props.inheritLabel}</option>
338
- {props.choices.map(choice => (
339
- <option key={choice.value} value={choice.value}>{choice.label}</option>
340
- ))}
341
- </select>
540
+ invalid={props.invalid}
541
+ onEdit={props.onEdit}
542
+ />
342
543
  <p className={props.invalid ? css.invalid : css.hint}>
343
544
  {props.invalid ? props.invalidLabel : props.hint}
344
545
  </p>
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Task-board settings for availability, agent announcement, and optional Host
3
- * idle-sleep protection. Registers into the `settings.plugin.item` slot the
4
- * plugin-configuration section renders, bound to the `task-board` namespace.
3
+ * idle-sleep protection. Registers into the `web-ui.plugin.item` child slot
4
+ * the Web UI plugin group renders, bound to the `task-board` namespace.
5
5
  */
6
6
 
7
7
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
@@ -97,20 +97,20 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
97
97
  const disabled = !state.writable
98
98
  const [power, setPower] = useState<TaskBoardPowerSnapshot | undefined>()
99
99
  useEffect(() => {
100
+ // The SSE channel already carries power on every real change and pushes
101
+ // one frame on subscribe; polling the full /state snapshot every 5 s
102
+ // re-cloned and re-serialized the whole ledger server-side for one field.
100
103
  let live = true
101
- const refresh = async (): Promise<void> => {
104
+ const events = new EventSource('/api/task-board/events')
105
+ events.onmessage = (message: MessageEvent<string>): void => {
102
106
  try {
103
- const response = await fetch('/api/task-board/state', { cache: 'no-store', credentials: 'same-origin' })
104
- if (!response.ok) return
105
- const snapshot = await response.json() as { power?: TaskBoardPowerSnapshot }
106
- if (live) setPower(snapshot.power)
107
+ const frame = JSON.parse(message.data) as { power?: TaskBoardPowerSnapshot }
108
+ if (frame.power !== undefined && live) setPower(frame.power)
107
109
  } catch {
108
110
  // The settings form remains usable while the host status is reconnecting.
109
111
  }
110
112
  }
111
- void refresh()
112
- const timer = setInterval(() => { void refresh() }, 5_000)
113
- return () => { live = false; clearInterval(timer) }
113
+ return () => { live = false; events.close() }
114
114
  }, [])
115
115
  const fieldProps = {
116
116
  overriddenLabel: t('settings.overridden'),
@@ -5,8 +5,8 @@
5
5
  */
6
6
  import { memo, useCallback, useEffect, useState } from 'react'
7
7
  import { selectedTaskOf, type BoardController } from '../../core/controller.ts'
8
- import { COLUMNS, type TaskRecord, type TaskStatus } from '../../core/tasks.ts'
9
- import { t, type TaskBoardKey } from '../locales.ts'
8
+ import { COLUMNS, type TaskRecord } from '../../core/tasks.ts'
9
+ import { t } from '../locales.ts'
10
10
  import css from '../board.module.css'
11
11
  import { NewTaskModal } from './NewTaskModal.tsx'
12
12
  import { STATUS_KEY } from './status-key.ts'
@@ -50,7 +50,7 @@ export function TaskBoard({ controller }: { controller: BoardController }) {
50
50
  const openTask = useCallback((id: string): void => { controller.openTask(id) }, [controller])
51
51
 
52
52
  return (
53
- <div className={css.board} data-dsh-taskboard-board="">
53
+ <div className={css.board} data-dsh-taskboard-board="" data-dsh-plugin="task-board">
54
54
  <header className={css.boardHeader}>
55
55
  <button
56
56
  type="button"
@@ -107,7 +107,7 @@ export function TaskBoard({ controller }: { controller: BoardController }) {
107
107
 
108
108
  <div className={css.columns}>
109
109
  {archiveView ? (
110
- <section className={css.column} data-status="archived">
110
+ <section className={css.column} data-status="archived" data-dsh-part="column">
111
111
  <header className={css.columnHeader}>
112
112
  <h3 className={css.columnTitle}>{t('board.archive')}</h3>
113
113
  <span className={css.columnCount}>{visible.length}</span>
@@ -123,7 +123,7 @@ export function TaskBoard({ controller }: { controller: BoardController }) {
123
123
  COLUMNS.map(column => {
124
124
  const tasks = visible.filter(task => task.status === column.status)
125
125
  return (
126
- <section key={column.status} className={css.column} data-status={column.status}>
126
+ <section key={column.status} className={css.column} data-status={column.status} data-dsh-part="column">
127
127
  <header className={css.columnHeader}>
128
128
  <span className={css.statusDot} data-status={column.status} aria-hidden="true" />
129
129
  <h3 className={css.columnTitle}>{t(STATUS_KEY[column.status])}</h3>
@@ -47,6 +47,7 @@ function TaskCardInner({ task, pending, timeZone, onClick }: { task: TaskRecord;
47
47
  type="button"
48
48
  className={css.card}
49
49
  data-status={archived ? 'archived' : task.status}
50
+ data-dsh-part="card"
50
51
  data-pending={pending || undefined}
51
52
  onClick={onClick}
52
53
  title={task.description !== '' ? task.description : task.title}
@@ -7,7 +7,7 @@
7
7
  import { useEffect, useState } from 'react'
8
8
  import type { BoardController } from '../../core/controller.ts'
9
9
  import { isValidCron } from '../../core/schedule.ts'
10
- import { MANUAL_STATUSES, TASK_PERMISSIONS, type ExecutionRecord, type TaskPermission, type TaskRecord, type TaskStatus } from '../../core/tasks.ts'
10
+ import { MANUAL_STATUSES, TASK_PERMISSIONS, type ExecutionRecord, type TaskPermission, type TaskRecord } from '../../core/tasks.ts'
11
11
  import { t, type TaskBoardKey } from '../locales.ts'
12
12
  import { SCHEDULE_PRESETS } from '../schedule-presets.ts'
13
13
  import css from '../board.module.css'
@@ -49,6 +49,7 @@ export function mountBoard(controller: BoardController): () => void {
49
49
  if (column === undefined) return
50
50
  container = document.createElement('div')
51
51
  container.dataset.dshTaskboardView = ''
52
+ container.dataset.dshPlugin = 'task-board'
52
53
  container.className = css.boardView
53
54
  column.appendChild(container)
54
55
  root = createRoot(container)
@@ -3,6 +3,7 @@ import {
3
3
  TASK_BOARD_API_PREFIX,
4
4
  type TaskBoardAction,
5
5
  type TaskBoardActionEnvelope,
6
+ type TaskBoardEventPayload,
6
7
  type TaskBoardSnapshot,
7
8
  } from '../protocol.ts'
8
9
 
@@ -25,7 +26,7 @@ export interface TaskBoardHostTransport {
25
26
  bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>
26
27
  state(): Promise<TaskBoardSnapshot>
27
28
  action(action: TaskBoardAction): Promise<TaskBoardSnapshot>
28
- subscribe(listener: () => void): () => void
29
+ subscribe(listener: (event?: TaskBoardEventPayload) => void): () => void
29
30
  }
30
31
 
31
32
  export class HttpTaskBoardHostTransport implements TaskBoardHostTransport {
@@ -82,9 +83,17 @@ export class HttpTaskBoardHostTransport implements TaskBoardHostTransport {
82
83
  }
83
84
  }
84
85
 
85
- subscribe(listener: () => void): () => void {
86
+ subscribe(listener: (event?: TaskBoardEventPayload) => void): () => void {
86
87
  const events = new EventSource(`${TASK_BOARD_API_PREFIX}/events`)
87
- events.onmessage = () => { listener() }
88
+ events.onmessage = (message: MessageEvent<string>): void => {
89
+ try {
90
+ const parsed = JSON.parse(message.data) as TaskBoardEventPayload
91
+ if (parsed === null || typeof parsed !== 'object' || typeof parsed.revision !== 'number') throw new Error('invalid event frame')
92
+ listener(parsed)
93
+ } catch {
94
+ listener()
95
+ }
96
+ }
88
97
  const onVisible = (): void => { if (document.visibilityState === 'visible') listener() }
89
98
  document.addEventListener('visibilitychange', onVisible)
90
99
  return () => {
@@ -290,6 +290,92 @@
290
290
  border-color: var(--dsw-alias-label-error);
291
291
  }
292
292
 
293
+ .selectWrap {
294
+ position: relative;
295
+ }
296
+
297
+ .selectButton {
298
+ appearance: none;
299
+ width: 100%;
300
+ text-align: left;
301
+ cursor: pointer;
302
+ align-items: center;
303
+ justify-content: space-between;
304
+ gap: 8px;
305
+ display: flex;
306
+ }
307
+
308
+ .selectLabel {
309
+ min-width: 0;
310
+ overflow: hidden;
311
+ text-overflow: ellipsis;
312
+ white-space: nowrap;
313
+ }
314
+
315
+ .selectChevron {
316
+ color: var(--dsw-alias-label-tertiary);
317
+ flex: none;
318
+ transition: transform 0.16s;
319
+ }
320
+
321
+ .selectChevronOpen {
322
+ transform: rotate(180deg);
323
+ }
324
+
325
+ .selectPopup {
326
+ position: absolute;
327
+ top: calc(100% + 4px);
328
+ left: 0;
329
+ right: 0;
330
+ z-index: 40;
331
+ flex-direction: column;
332
+ max-height: 240px;
333
+ overflow-y: auto;
334
+ border: 1px solid var(--dsw-alias-border-l2);
335
+ border-radius: 8px;
336
+ background: var(--dsw-alias-bg-layer-3);
337
+ box-shadow: 0 8px 24px var(--dsw-alias-bg-mask-2);
338
+ padding: 4px;
339
+ opacity: 0;
340
+ transform: translateY(-4px);
341
+ transition: opacity 0.1s ease, transform 0.1s ease;
342
+ display: flex;
343
+ }
344
+
345
+ .selectPopupOpen {
346
+ opacity: 1;
347
+ transform: none;
348
+ }
349
+
350
+ .selectPopupClose {
351
+ opacity: 0;
352
+ transform: translateY(-4px);
353
+ pointer-events: none;
354
+ }
355
+
356
+ .selectOption {
357
+ border-radius: 6px;
358
+ padding: 6px 10px;
359
+ font-size: 13px;
360
+ line-height: 1.5;
361
+ color: var(--dsw-alias-label-primary);
362
+ cursor: pointer;
363
+ white-space: nowrap;
364
+ overflow: hidden;
365
+ text-overflow: ellipsis;
366
+ }
367
+
368
+ .selectOption:hover,
369
+ .selectOptionActive {
370
+ background: var(--dsw-alias-interactive-bg-hover);
371
+ }
372
+
373
+ .selectOptionSelected {
374
+ color: var(--dsw-alias-brand-primary);
375
+ font-weight: 500;
376
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);
377
+ }
378
+
293
379
  .invalid {
294
380
  color: var(--dsw-alias-label-error);
295
381
  margin: 0;
@@ -310,7 +396,10 @@
310
396
  .chevron,
311
397
  .chevronOpen,
312
398
  .discard,
313
- .save {
399
+ .save,
400
+ .selectChevron,
401
+ .selectChevronOpen,
402
+ .selectPopup {
314
403
  transition: none;
315
404
  }
316
405
  }