@streamscloud/kit 0.54.1 → 0.55.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.
@@ -1,28 +1,42 @@
1
1
  <script lang="ts">import { IconSlot } from '../../icon';
2
- let { actions } = $props();
2
+ import PlayerContextMenu from './player-context-menu.svelte';
3
+ let { actions, contextActions = [], contextPosition = 'left-end' } = $props();
3
4
  </script>
4
5
 
5
6
  <div class="mobile-player-buttons">
6
- {#each actions as action (action.icon)}
7
+ {#each actions as action, index (index)}
7
8
  <button type="button" class="mobile-player-buttons__action" disabled={action.disabled} onclick={action.callback}>
8
9
  <span class="mobile-player-buttons__action-icon">
9
10
  <IconSlot icon={action.icon} />
10
11
  </span>
11
12
  </button>
12
13
  {/each}
14
+ {#if contextActions.length}
15
+ <div class="mobile-player-buttons__action mobile-player-buttons__action--menu">
16
+ <PlayerContextMenu actions={contextActions} position={contextPosition} />
17
+ </div>
18
+ {/if}
13
19
  </div>
14
20
 
15
21
  <!--
16
22
  @component
17
23
  A vertically stacked column of player action buttons optimized for mobile touch targets.
24
+ `contextActions` appends an overflow-menu cell below the buttons.
18
25
 
19
- ### Props
20
- | Prop | Type | Description |
26
+ ### CSS Custom Properties
27
+ | Property | Description | Default |
21
28
  |---|---|---|
22
- | `actions` | `PlayerButtonDef[]` | Array of button definitions (icon, callback, disabled state) |
29
+ | `--sc-kit--player-button--color--inactive` | Overflow menu panel background | 60% opacity of `black` |
30
+ | `--sc-kit--player-button--menu--border-color` | Overflow menu panel border | `white` at 15% |
31
+ | `--sc-kit--player-button--menu--item--color` | Overflow menu row text / icon color | `--sc-kit--color--text--on-accent` |
32
+ | `--sc-kit--player-button--menu--item--background--hover` | Overflow menu row hover background | `white` at 12% |
23
33
  -->
24
34
 
25
35
  <style>.mobile-player-buttons {
36
+ --_player-button--color--inactive: var(--sc-kit--player-button--color--inactive, rgb(0 0 0 / 60%));
37
+ --_player-button--menu--border-color: var(--sc-kit--player-button--menu--border-color, rgb(255 255 255 / 15%));
38
+ --_player-button--menu--item-color: var(--sc-kit--player-button--menu--item--color, var(--sc-kit--color--text--on-accent));
39
+ --_player-button--menu--item-background-hover: var(--sc-kit--player-button--menu--item--background--hover, rgb(255 255 255 / 12%));
26
40
  cursor: pointer;
27
41
  display: flex;
28
42
  flex-direction: column;
@@ -30,10 +44,11 @@ A vertically stacked column of player action buttons optimized for mobile touch
30
44
  pointer-events: auto;
31
45
  }
32
46
  .mobile-player-buttons__action {
47
+ --_player-button--cell-padding: var(--sc-kit--space--4);
33
48
  display: flex;
34
49
  justify-content: center;
35
50
  align-items: center;
36
- padding: var(--sc-kit--space--4);
51
+ padding: var(--_player-button--cell-padding);
37
52
  --sc-kit--icon--color: var(--sc-kit--color--text--on-accent);
38
53
  --sc-kit--icon--size: 2rem;
39
54
  --sc-kit--icon--filter: drop-shadow(1px 1px 0 rgba(0, 0, 0, 0.2));
@@ -42,6 +57,15 @@ A vertically stacked column of player action buttons optimized for mobile touch
42
57
  opacity: 0.5;
43
58
  cursor: default;
44
59
  }
60
+ .mobile-player-buttons__action--menu {
61
+ padding: 0;
62
+ min-block-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
63
+ min-inline-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
64
+ --sc-kit--popover--content--background: var(--_player-button--color--inactive);
65
+ --sc-kit--popover--content--border-color: var(--_player-button--menu--border-color);
66
+ --sc-kit--popover-item--color: var(--_player-button--menu--item-color);
67
+ --sc-kit--popover-item--background--hover: var(--_player-button--menu--item-background-hover);
68
+ }
45
69
  .mobile-player-buttons__action-icon {
46
70
  display: block;
47
71
  }</style>
@@ -1,14 +1,23 @@
1
- import type { PlayerButtonDef } from './types';
1
+ import type { PlayerButtonDef, PlayerContextActionDef } from './types';
2
+ import type { Placement } from '@floating-ui/dom';
2
3
  type Props = {
3
4
  actions: PlayerButtonDef[];
5
+ /** Rows of an overflow menu rendered as the final cell — an ellipsis trigger opening a popover. Empty ⇒ no menu cell. @default [] */
6
+ contextActions?: PlayerContextActionDef[];
7
+ /** Floating UI placement of the overflow menu panel @default 'left-end' */
8
+ contextPosition?: Placement;
4
9
  };
5
10
  /**
6
11
  * A vertically stacked column of player action buttons optimized for mobile touch targets.
12
+ * `contextActions` appends an overflow-menu cell below the buttons.
7
13
  *
8
- * ### Props
9
- * | Prop | Type | Description |
14
+ * ### CSS Custom Properties
15
+ * | Property | Description | Default |
10
16
  * |---|---|---|
11
- * | `actions` | `PlayerButtonDef[]` | Array of button definitions (icon, callback, disabled state) |
17
+ * | `--sc-kit--player-button--color--inactive` | Overflow menu panel background | 60% opacity of `black` |
18
+ * | `--sc-kit--player-button--menu--border-color` | Overflow menu panel border | `white` at 15% |
19
+ * | `--sc-kit--player-button--menu--item--color` | Overflow menu row text / icon color | `--sc-kit--color--text--on-accent` |
20
+ * | `--sc-kit--player-button--menu--item--background--hover` | Overflow menu row hover background | `white` at 12% |
12
21
  */
13
22
  declare const Cmp: import("svelte").Component<Props, {}, "">;
14
23
  type Cmp = ReturnType<typeof Cmp>;
@@ -1,23 +1,32 @@
1
1
  <script lang="ts">import { IconSlot } from '../../icon';
2
- let { actions, scaleEffect = false, zoom = 1 } = $props();
2
+ import PlayerContextMenu from './player-context-menu.svelte';
3
+ let { actions, contextActions = [], contextPosition = 'left-end', scaleEffect = false, zoom = 1 } = $props();
4
+ const hasMenu = $derived(contextActions.length > 0);
5
+ const cellCount = $derived(actions.length + (hasMenu ? 1 : 0));
3
6
  </script>
4
7
 
5
- {#if actions.length === 1}
6
- {@const action = actions[0]}
7
- <button
8
- type="button"
9
- class="player-button"
10
- style:zoom={zoom}
11
- class:player-button--scale-effect={scaleEffect}
12
- disabled={action.disabled}
13
- onclick={action.callback}>
14
- <span class="player-button__icon">
15
- <IconSlot icon={action.icon} />
16
- </span>
17
- </button>
18
- {:else if actions.length > 1}
8
+ {#if cellCount === 1}
9
+ {#if hasMenu}
10
+ <div class="player-button player-button--menu" style:zoom={zoom}>
11
+ <PlayerContextMenu actions={contextActions} position={contextPosition} />
12
+ </div>
13
+ {:else}
14
+ {@const action = actions[0]}
15
+ <button
16
+ type="button"
17
+ class="player-button"
18
+ style:zoom={zoom}
19
+ class:player-button--scale-effect={scaleEffect}
20
+ disabled={action.disabled}
21
+ onclick={action.callback}>
22
+ <span class="player-button__icon">
23
+ <IconSlot icon={action.icon} />
24
+ </span>
25
+ </button>
26
+ {/if}
27
+ {:else if cellCount > 1}
19
28
  <div class="player-buttons" style:zoom={zoom}>
20
- {#each actions as action (action.icon)}
29
+ {#each actions as action, index (index)}
21
30
  <button
22
31
  type="button"
23
32
  class="player-buttons__action"
@@ -29,26 +38,40 @@ let { actions, scaleEffect = false, zoom = 1 } = $props();
29
38
  </span>
30
39
  </button>
31
40
  {/each}
41
+ {#if hasMenu}
42
+ <div class="player-buttons__action player-buttons__action--menu">
43
+ <PlayerContextMenu actions={contextActions} position={contextPosition} />
44
+ </div>
45
+ {/if}
32
46
  </div>
33
47
  {/if}
34
48
 
35
49
  <!--
36
50
  @component
37
51
  Desktop player action buttons — renders a single circular button or a vertically stacked pill for multiple actions.
52
+ `contextActions` adds an overflow-menu cell as the last item; it counts towards the single / pill split, so one
53
+ action plus a menu renders as a two-cell pill.
38
54
 
39
55
  ### CSS Custom Properties
40
56
  | Property | Description | Default |
41
57
  |---|---|---|
42
58
  | `--sc-kit--player-button--color` | Button background at full opacity | `dark-500` / `black` at 95% |
43
- | `--sc-kit--player-button--color--inactive` | Button background at rest | 60% opacity of `--color` |
59
+ | `--sc-kit--player-button--color--inactive` | Button background at rest — the overflow menu panel takes the same value | 60% opacity of `--color` |
60
+ | `--sc-kit--player-button--menu--border-color` | Overflow menu panel border | `white` at 15% |
61
+ | `--sc-kit--player-button--menu--item--color` | Overflow menu row text / icon color | `--sc-kit--color--text--on-accent` |
62
+ | `--sc-kit--player-button--menu--item--background--hover` | Overflow menu row hover background | `white` at 12% |
44
63
  -->
45
64
 
46
65
  <style>.player-button {
47
66
  --_player-button--color: var(--sc-kit--player-button--color, rgb(0 0 0 / 95%));
48
67
  --_player-button--color--inactive: var(--sc-kit--player-button--color--inactive, rgb(0 0 0 / 60%));
68
+ --_player-button--menu--border-color: var(--sc-kit--player-button--menu--border-color, rgb(255 255 255 / 15%));
69
+ --_player-button--menu--item-color: var(--sc-kit--player-button--menu--item--color, var(--sc-kit--color--text--on-accent));
70
+ --_player-button--menu--item-background-hover: var(--sc-kit--player-button--menu--item--background--hover, rgb(255 255 255 / 12%));
49
71
  --_player-button--icon-scale: 1;
72
+ --_player-button--cell-padding: 0.625rem;
50
73
  pointer-events: auto;
51
- padding: 0.625rem;
74
+ padding: var(--_player-button--cell-padding);
52
75
  display: flex;
53
76
  justify-content: center;
54
77
  align-items: center;
@@ -69,6 +92,18 @@ Desktop player action buttons — renders a single circular button or a vertical
69
92
  .player-button--scale-effect:hover:not(:disabled) {
70
93
  --_player-button--icon-scale: 1.2;
71
94
  }
95
+ .player-button--menu {
96
+ padding: 0;
97
+ min-block-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
98
+ min-inline-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
99
+ --sc-kit--popover--content--background: var(--_player-button--color--inactive);
100
+ --sc-kit--popover--content--border-color: var(--_player-button--menu--border-color);
101
+ --sc-kit--popover-item--color: var(--_player-button--menu--item-color);
102
+ --sc-kit--popover-item--background--hover: var(--_player-button--menu--item-background-hover);
103
+ }
104
+ .player-button--menu:hover:not(:disabled) {
105
+ background-color: var(--_player-button--color--inactive);
106
+ }
72
107
  .player-button__icon {
73
108
  display: block;
74
109
  transform: scale(var(--_player-button--icon-scale));
@@ -79,7 +114,7 @@ Desktop player action buttons — renders a single circular button or a vertical
79
114
  }
80
115
  @container (width < 576px) {
81
116
  .player-button {
82
- padding: 0.5rem;
117
+ --_player-button--cell-padding: 0.5rem;
83
118
  --sc-kit--icon--size: 1.5rem;
84
119
  }
85
120
  }
@@ -87,6 +122,9 @@ Desktop player action buttons — renders a single circular button or a vertical
87
122
  .player-buttons {
88
123
  --_player-button--color: var(--sc-kit--player-button--color, rgb(0 0 0 / 95%));
89
124
  --_player-button--color--inactive: var(--sc-kit--player-button--color--inactive, rgb(0 0 0 / 60%));
125
+ --_player-button--menu--border-color: var(--sc-kit--player-button--menu--border-color, rgb(255 255 255 / 15%));
126
+ --_player-button--menu--item-color: var(--sc-kit--player-button--menu--item--color, var(--sc-kit--color--text--on-accent));
127
+ --_player-button--menu--item-background-hover: var(--sc-kit--player-button--menu--item--background--hover, rgb(255 255 255 / 12%));
90
128
  cursor: pointer;
91
129
  display: flex;
92
130
  flex-direction: column;
@@ -98,10 +136,11 @@ Desktop player action buttons — renders a single circular button or a vertical
98
136
  }
99
137
  .player-buttons__action {
100
138
  --_player-button--icon-scale: 1;
139
+ --_player-button--cell-padding: 0.5625rem;
101
140
  display: flex;
102
141
  justify-content: center;
103
142
  align-items: center;
104
- padding: 0.5625rem;
143
+ padding: var(--_player-button--cell-padding);
105
144
  border-radius: 1.25rem;
106
145
  color: var(--sc-kit--color--text--on-accent);
107
146
  --sc-kit--icon--color: var(--sc-kit--color--text--on-accent);
@@ -119,6 +158,18 @@ Desktop player action buttons — renders a single circular button or a vertical
119
158
  --_player-button--icon-scale: 1.2;
120
159
  background-color: transparent;
121
160
  }
161
+ .player-buttons__action--menu {
162
+ padding: 0;
163
+ min-block-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
164
+ min-inline-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
165
+ --sc-kit--popover--content--background: var(--_player-button--color--inactive);
166
+ --sc-kit--popover--content--border-color: var(--_player-button--menu--border-color);
167
+ --sc-kit--popover-item--color: var(--_player-button--menu--item-color);
168
+ --sc-kit--popover-item--background--hover: var(--_player-button--menu--item-background-hover);
169
+ }
170
+ .player-buttons__action--menu:hover:not(:disabled) {
171
+ background-color: transparent;
172
+ }
122
173
  .player-buttons__action-icon {
123
174
  display: block;
124
175
  transform: scale(var(--_player-button--icon-scale));
@@ -1,7 +1,12 @@
1
- import type { PlayerButtonDef } from './types';
1
+ import type { PlayerButtonDef, PlayerContextActionDef } from './types';
2
+ import type { Placement } from '@floating-ui/dom';
2
3
  type Props = {
3
4
  /** Array of button definitions (icon, callback, disabled state) */
4
5
  actions: PlayerButtonDef[];
6
+ /** Rows of an overflow menu rendered as the final cell — an ellipsis trigger opening a popover. Empty ⇒ no menu cell. @default [] */
7
+ contextActions?: PlayerContextActionDef[];
8
+ /** Floating UI placement of the overflow menu panel @default 'left-end' */
9
+ contextPosition?: Placement;
5
10
  /** Scale the icon on hover */
6
11
  scaleEffect?: boolean;
7
12
  /** CSS zoom level applied to the button container @default 1 */
@@ -9,12 +14,17 @@ type Props = {
9
14
  };
10
15
  /**
11
16
  * Desktop player action buttons — renders a single circular button or a vertically stacked pill for multiple actions.
17
+ * `contextActions` adds an overflow-menu cell as the last item; it counts towards the single / pill split, so one
18
+ * action plus a menu renders as a two-cell pill.
12
19
  *
13
20
  * ### CSS Custom Properties
14
21
  * | Property | Description | Default |
15
22
  * |---|---|---|
16
23
  * | `--sc-kit--player-button--color` | Button background at full opacity | `dark-500` / `black` at 95% |
17
- * | `--sc-kit--player-button--color--inactive` | Button background at rest | 60% opacity of `--color` |
24
+ * | `--sc-kit--player-button--color--inactive` | Button background at rest — the overflow menu panel takes the same value | 60% opacity of `--color` |
25
+ * | `--sc-kit--player-button--menu--border-color` | Overflow menu panel border | `white` at 15% |
26
+ * | `--sc-kit--player-button--menu--item--color` | Overflow menu row text / icon color | `--sc-kit--color--text--on-accent` |
27
+ * | `--sc-kit--player-button--menu--item--background--hover` | Overflow menu row hover background | `white` at 12% |
18
28
  */
19
29
  declare const Cmp: import("svelte").Component<Props, {}, "">;
20
30
  type Cmp = ReturnType<typeof Cmp>;
@@ -1,25 +1,29 @@
1
1
  <script lang="ts">import { default as MobilePlayerButtons } from './cmp.mobile-player-buttons.svelte';
2
2
  import { default as PlayerButtons } from './cmp.player-buttons.svelte';
3
- let { scaleEffect = false, actions } = $props();
3
+ let { scaleEffect = false, actions, contextActions = [], contextPosition = 'left-end' } = $props();
4
4
  </script>
5
5
 
6
6
  <div class="desktop-controls">
7
- <PlayerButtons actions={actions} scaleEffect={scaleEffect} />
7
+ <PlayerButtons actions={actions} contextActions={contextActions} contextPosition={contextPosition} scaleEffect={scaleEffect} />
8
8
  </div>
9
9
 
10
10
  <div class="mobile-controls">
11
- <MobilePlayerButtons actions={actions} />
11
+ <MobilePlayerButtons actions={actions} contextActions={contextActions} contextPosition={contextPosition} />
12
12
  </div>
13
13
 
14
14
  <!--
15
15
  @component
16
16
  Renders `PlayerButtons` on desktop and `MobilePlayerButtons` on mobile, switching via container query.
17
+ Both branches stay mounted, so `contextActions` builds a menu in each; only the visible one is reachable.
17
18
 
18
- ### Props
19
- | Prop | Type | Default | Description |
20
- |---|---|---|---|
21
- | `actions` | `PlayerButtonDef[]` || Array of button definitions |
22
- | `scaleEffect` | `boolean` | `false` | Whether icons scale on hover (desktop only) |
19
+ ### CSS Custom Properties
20
+ | Property | Description | Default |
21
+ |---|---|---|
22
+ | `--sc-kit--player-button--color` | Button background at full opacity (desktop only the mobile column paints no button background) | `black` at 95% |
23
+ | `--sc-kit--player-button--color--inactive` | Button background at rest the overflow menu panel takes the same value | 60% opacity of `--color` |
24
+ | `--sc-kit--player-button--menu--border-color` | Overflow menu panel border | `white` at 15% |
25
+ | `--sc-kit--player-button--menu--item--color` | Overflow menu row text / icon color | `--sc-kit--color--text--on-accent` |
26
+ | `--sc-kit--player-button--menu--item--background--hover` | Overflow menu row hover background | `white` at 12% |
23
27
  -->
24
28
 
25
29
  <style>.desktop-controls {
@@ -1,16 +1,26 @@
1
- import type { PlayerButtonDef } from './types';
1
+ import type { PlayerButtonDef, PlayerContextActionDef } from './types';
2
+ import type { Placement } from '@floating-ui/dom';
2
3
  type Props = {
3
4
  actions: PlayerButtonDef[];
5
+ /** Rows of an overflow menu rendered as the final cell — an ellipsis trigger opening a popover. Empty ⇒ no menu cell. @default [] */
6
+ contextActions?: PlayerContextActionDef[];
7
+ /** Floating UI placement of the overflow menu panel @default 'left-end' */
8
+ contextPosition?: Placement;
9
+ /** Scale the icon on hover (desktop only) */
4
10
  scaleEffect?: boolean;
5
11
  };
6
12
  /**
7
13
  * Renders `PlayerButtons` on desktop and `MobilePlayerButtons` on mobile, switching via container query.
14
+ * Both branches stay mounted, so `contextActions` builds a menu in each; only the visible one is reachable.
8
15
  *
9
- * ### Props
10
- * | Prop | Type | Default | Description |
11
- * |---|---|---|---|
12
- * | `actions` | `PlayerButtonDef[]` || Array of button definitions |
13
- * | `scaleEffect` | `boolean` | `false` | Whether icons scale on hover (desktop only) |
16
+ * ### CSS Custom Properties
17
+ * | Property | Description | Default |
18
+ * |---|---|---|
19
+ * | `--sc-kit--player-button--color` | Button background at full opacity (desktop only the mobile column paints no button background) | `black` at 95% |
20
+ * | `--sc-kit--player-button--color--inactive` | Button background at rest the overflow menu panel takes the same value | 60% opacity of `--color` |
21
+ * | `--sc-kit--player-button--menu--border-color` | Overflow menu panel border | `white` at 15% |
22
+ * | `--sc-kit--player-button--menu--item--color` | Overflow menu row text / icon color | `--sc-kit--color--text--on-accent` |
23
+ * | `--sc-kit--player-button--menu--item--background--hover` | Overflow menu row hover background | `white` at 12% |
14
24
  */
15
25
  declare const Cmp: import("svelte").Component<Props, {}, "">;
16
26
  type Cmp = ReturnType<typeof Cmp>;
@@ -1,4 +1,4 @@
1
1
  export { default as MobilePlayerButtons } from './cmp.mobile-player-buttons.svelte';
2
2
  export { default as PlayerButtons } from './cmp.player-buttons.svelte';
3
3
  export { default as ResponsivePlayerButtons } from './cmp.responsive-player-buttons.svelte';
4
- export type { PlayerButtonDef } from './types';
4
+ export type { PlayerButtonDef, PlayerContextActionDef } from './types';
@@ -0,0 +1,3 @@
1
+ export declare class PlayerButtonsLocalization {
2
+ get moreActions(): string;
3
+ }
@@ -0,0 +1,12 @@
1
+ import { AppLocale } from '../../../core/locale';
2
+ const loc = {
3
+ moreActions: {
4
+ en: 'More actions',
5
+ no: 'Flere handlinger'
6
+ }
7
+ };
8
+ export class PlayerButtonsLocalization {
9
+ get moreActions() {
10
+ return loc.moreActions[AppLocale.current];
11
+ }
12
+ }
@@ -0,0 +1,20 @@
1
+ <script lang="ts">import { IconSlot } from '../../icon';
2
+ import { IconText } from '../../icon-text';
3
+ import { Popover, PopoverItem } from '../../popover';
4
+ import { PlayerButtonsLocalization } from './player-buttons-localization';
5
+ import IconMoreVertical from '@fluentui/svg-icons/icons/more_vertical_28_regular.svg?raw';
6
+ let { actions, position = 'left-end' } = $props();
7
+ const localization = new PlayerButtonsLocalization();
8
+ </script>
9
+
10
+ <Popover position={position} fixedPosition fillContainer aria-label={localization.moreActions}>
11
+ {#snippet trigger()}
12
+ <IconSlot icon={IconMoreVertical} />
13
+ {/snippet}
14
+
15
+ {#each actions as action (action.label)}
16
+ <PopoverItem disabled={action.disabled} on={{ click: action.callback }}>
17
+ <IconText icon={action.icon} size="sm">{action.label}</IconText>
18
+ </PopoverItem>
19
+ {/each}
20
+ </Popover>
@@ -0,0 +1,9 @@
1
+ import type { PlayerContextActionDef } from './types';
2
+ import type { Placement } from '@floating-ui/dom';
3
+ type Props = {
4
+ actions: PlayerContextActionDef[];
5
+ position?: Placement;
6
+ };
7
+ declare const PlayerContextMenu: import("svelte").Component<Props, {}, "">;
8
+ type PlayerContextMenu = ReturnType<typeof PlayerContextMenu>;
9
+ export default PlayerContextMenu;
@@ -4,3 +4,9 @@ export type PlayerButtonDef = {
4
4
  callback: () => void;
5
5
  disabled?: boolean;
6
6
  };
7
+ export type PlayerContextActionDef = {
8
+ icon: IconProp;
9
+ label: string;
10
+ callback: () => void;
11
+ disabled?: boolean;
12
+ };
@@ -12,11 +12,11 @@ $effect(() => {
12
12
  });
13
13
  </script>
14
14
 
15
- {#if visible}
16
- {#if blocking}
17
- <div class="spinner-overlay" class:spinner-overlay--fixed={position === 'fixed-center'}></div>
18
- {/if}
15
+ {#if blocking}
16
+ <div class="spinner-overlay" class:spinner-overlay--fixed={position === 'fixed-center'} class:spinner-overlay--dimmed={visible}></div>
17
+ {/if}
19
18
 
19
+ {#if visible}
20
20
  <span
21
21
  class="spinner spinner--{size}"
22
22
  class:spinner--accent={color === 'accent'}
@@ -41,7 +41,9 @@ $effect(() => {
41
41
  @component
42
42
  Spinner — indeterminate loading indicator. Single-arc circle rotating at a constant rate. Color preset (`accent` default) resolves to a semantic token; override the public CSS vars on any ancestor for global theming. Pass `label` to expose it to screen readers as a live status region.
43
43
 
44
- Convenience props for the common "load happens, show a centered spinner with optional dimmer" pattern: `position` centers it within the nearest positioned ancestor (`absolute-center`) or the viewport (`fixed-center`); `blocking` adds a semi-transparent overlay; `timeout` delays visibility to avoid flicker for fast operations.
44
+ Convenience props for the common "load happens, show a centered spinner with optional dimmer" pattern: `position` centers it within the nearest positioned ancestor (`absolute-center`) or the viewport (`fixed-center`); `blocking` adds an overlay; `timeout` delays visibility to avoid flicker for fast operations.
45
+
46
+ `blocking` and `timeout` are orthogonal. The overlay mounts with the component and swallows pointer input from the first millisecond regardless of `timeout`; once `timeout` elapses the spinner appears and the overlay fades to its dim background. Pointer input only — a focused control underneath still reacts to the keyboard; wrap the region in `inert` if that matters.
45
47
 
46
48
  For tight inline glyphs inside other kit cmps (Button loading state, async Select), use the internal `_internal/spinner` instead — that one fills its inline box and inherits `currentColor` for stroke.
47
49
 
@@ -58,7 +60,7 @@ For tight inline glyphs inside other kit cmps (Button loading state, async Selec
58
60
  | `--sc-kit--spinner--color-text` | Color for `text` preset | `--sc-kit--color--text--primary` |
59
61
  | `--sc-kit--spinner--color-on-accent` | Color for `on-accent` preset | `--sc-kit--color--text--on-accent` |
60
62
  | `--sc-kit--spinner--duration` | Rotation duration | `1.6s` |
61
- | `--sc-kit--spinner--overlay--background` | Backdrop color when `blocking` is true | `rgba(0, 0, 0, 0.3)` |
63
+ | `--sc-kit--spinner--overlay--background` | Backdrop color the `blocking` overlay fades to once the spinner shows | `rgba(0, 0, 0, 0.3)` |
62
64
  | `--sc-kit--spinner--overlay--z-index` | Backdrop stacking | `--sc-kit--z-index--popover` minus 1 |
63
65
 
64
66
  ### Size presets
@@ -74,12 +76,17 @@ For tight inline glyphs inside other kit cmps (Button loading state, async Selec
74
76
  --_overlay--z-index: var(--sc-kit--spinner--overlay--z-index, calc(var(--sc-kit--z-index--popover) - 1));
75
77
  position: absolute;
76
78
  inset: 0;
77
- background: var(--_overlay--background);
79
+ background-color: var(--_overlay--background);
80
+ opacity: 0;
78
81
  z-index: var(--_overlay--z-index);
82
+ transition: opacity var(--sc-kit--duration--base) var(--sc-kit--ease--default);
79
83
  }
80
84
  .spinner-overlay--fixed {
81
85
  position: fixed;
82
86
  }
87
+ .spinner-overlay--dimmed {
88
+ opacity: 1;
89
+ }
83
90
 
84
91
  .spinner {
85
92
  --_spinner--size: var(--sc-kit--spinner--size, 1.25rem);
@@ -7,15 +7,17 @@ type Props = {
7
7
  label?: string;
8
8
  /** Centering mode. `absolute-center` centers within the closest positioned ancestor; `fixed-center` centers in the viewport. Omit for an inline-flex glyph at the current position. */
9
9
  position?: 'absolute-center' | 'fixed-center';
10
- /** Renders a semi-transparent overlay behind the spinner. Pair with `position` for a full-area dimmer. */
10
+ /** Renders an overlay that swallows pointer input immediately, independent of `timeout`; it stays invisible until `timeout` elapses, then fades to its dim background. Pair with `position` for a full-area dimmer. */
11
11
  blocking?: boolean;
12
- /** Delay in ms before the spinner becomes visible (and the optional overlay renders). Prevents flicker for fast operations. @default 0 */
12
+ /** Delay in ms before the spinner becomes visible (and a `blocking` overlay dims). Prevents flicker for fast operations. @default 0 */
13
13
  timeout?: number;
14
14
  };
15
15
  /**
16
16
  * Spinner — indeterminate loading indicator. Single-arc circle rotating at a constant rate. Color preset (`accent` default) resolves to a semantic token; override the public CSS vars on any ancestor for global theming. Pass `label` to expose it to screen readers as a live status region.
17
17
  *
18
- * Convenience props for the common "load happens, show a centered spinner with optional dimmer" pattern: `position` centers it within the nearest positioned ancestor (`absolute-center`) or the viewport (`fixed-center`); `blocking` adds a semi-transparent overlay; `timeout` delays visibility to avoid flicker for fast operations.
18
+ * Convenience props for the common "load happens, show a centered spinner with optional dimmer" pattern: `position` centers it within the nearest positioned ancestor (`absolute-center`) or the viewport (`fixed-center`); `blocking` adds an overlay; `timeout` delays visibility to avoid flicker for fast operations.
19
+ *
20
+ * `blocking` and `timeout` are orthogonal. The overlay mounts with the component and swallows pointer input from the first millisecond regardless of `timeout`; once `timeout` elapses the spinner appears and the overlay fades to its dim background. Pointer input only — a focused control underneath still reacts to the keyboard; wrap the region in `inert` if that matters.
19
21
  *
20
22
  * For tight inline glyphs inside other kit cmps (Button loading state, async Select), use the internal `_internal/spinner` instead — that one fills its inline box and inherits `currentColor` for stroke.
21
23
  *
@@ -32,7 +34,7 @@ type Props = {
32
34
  * | `--sc-kit--spinner--color-text` | Color for `text` preset | `--sc-kit--color--text--primary` |
33
35
  * | `--sc-kit--spinner--color-on-accent` | Color for `on-accent` preset | `--sc-kit--color--text--on-accent` |
34
36
  * | `--sc-kit--spinner--duration` | Rotation duration | `1.6s` |
35
- * | `--sc-kit--spinner--overlay--background` | Backdrop color when `blocking` is true | `rgba(0, 0, 0, 0.3)` |
37
+ * | `--sc-kit--spinner--overlay--background` | Backdrop color the `blocking` overlay fades to once the spinner shows | `rgba(0, 0, 0, 0.3)` |
36
38
  * | `--sc-kit--spinner--overlay--z-index` | Backdrop stacking | `--sc-kit--z-index--popover` minus 1 |
37
39
  *
38
40
  * ### Size presets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.54.1",
3
+ "version": "0.55.1",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",