@streamscloud/kit 0.56.0 → 0.57.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.
@@ -13,6 +13,8 @@ export declare class KeyedCursorDataLoader<T> {
13
13
  items: (key?: string) => T[];
14
14
  loading: (key?: string) => boolean;
15
15
  initialLoading: (key?: string) => boolean;
16
+ /** The key's last attempt did not deliver a page, whether it returned nothing or threw; cleared when the next attempt for that key starts. */
17
+ failed: (key?: string) => boolean;
16
18
  canLoadMore: (key?: string) => boolean;
17
19
  /** False until the key's first page loaded without failing, so a failed load can be retried; a later failed page never clears it. */
18
20
  isLoaded: (key?: string) => boolean;
@@ -13,6 +13,8 @@ export class KeyedCursorDataLoader {
13
13
  items = (key = '') => this.loaderFor(key).items;
14
14
  loading = (key = '') => this.loaderFor(key).loading;
15
15
  initialLoading = (key = '') => this.loaderFor(key).initialLoading;
16
+ /** The key's last attempt did not deliver a page, whether it returned nothing or threw; cleared when the next attempt for that key starts. */
17
+ failed = (key = '') => this.loaderFor(key).failed;
16
18
  canLoadMore = (key = '') => this.loaderFor(key).continuationToken.canLoadMore;
17
19
  /** False until the key's first page loaded without failing, so a failed load can be retried; a later failed page never clears it. */
18
20
  isLoaded = (key = '') => this._loaded[key] === true;
@@ -5,11 +5,19 @@ let { actions, contextActions = [], contextPosition = 'left-end' } = $props();
5
5
 
6
6
  <div class="mobile-player-buttons">
7
7
  {#each actions as action, index (index)}
8
- <button type="button" class="mobile-player-buttons__action" disabled={action.disabled} onclick={action.callback}>
9
- <span class="mobile-player-buttons__action-icon">
10
- <IconSlot icon={action.icon} />
8
+ {#if action.on}
9
+ <button type="button" class="mobile-player-buttons__action" disabled={action.disabled} aria-label={action.label || undefined} onclick={action.on.click}>
10
+ <span class="mobile-player-buttons__action-icon">
11
+ <IconSlot icon={action.icon} />
12
+ </span>
13
+ </button>
14
+ {:else}
15
+ <span class="mobile-player-buttons__action mobile-player-buttons__action--static" role="img" aria-label={action.label || undefined}>
16
+ <span class="mobile-player-buttons__action-icon">
17
+ <IconSlot icon={action.icon} />
18
+ </span>
11
19
  </span>
12
- </button>
20
+ {/if}
13
21
  {/each}
14
22
  {#if contextActions.length}
15
23
  <div class="mobile-player-buttons__action mobile-player-buttons__action--menu">
@@ -21,7 +29,9 @@ let { actions, contextActions = [], contextPosition = 'left-end' } = $props();
21
29
  <!--
22
30
  @component
23
31
  A vertically stacked column of player action buttons optimized for mobile touch targets.
24
- `contextActions` appends an overflow-menu cell below the buttons.
32
+ `contextActions` appends an overflow-menu cell below the buttons. An action without `on` renders as a static
33
+ plaque — same cell, no pointer feedback, out of the tab order — while `disabled` keeps dimming an action that
34
+ exists but is currently unavailable.
25
35
 
26
36
  ### CSS Custom Properties
27
37
  | Property | Description | Default |
@@ -37,7 +47,6 @@ A vertically stacked column of player action buttons optimized for mobile touch
37
47
  --_player-button--menu--border-color: var(--sc-kit--player-button--menu--border-color, rgb(255 255 255 / 15%));
38
48
  --_player-button--menu--item-color: var(--sc-kit--player-button--menu--item--color, var(--sc-kit--color--text--on-accent));
39
49
  --_player-button--menu--item-background-hover: var(--sc-kit--player-button--menu--item--background--hover, rgb(255 255 255 / 12%));
40
- cursor: pointer;
41
50
  display: flex;
42
51
  flex-direction: column;
43
52
  justify-content: center;
@@ -48,6 +57,7 @@ A vertically stacked column of player action buttons optimized for mobile touch
48
57
  display: flex;
49
58
  justify-content: center;
50
59
  align-items: center;
60
+ cursor: pointer;
51
61
  padding: var(--_player-button--cell-padding);
52
62
  --sc-kit--icon--color: var(--sc-kit--color--text--on-accent);
53
63
  --sc-kit--icon--size: 2rem;
@@ -57,6 +67,9 @@ A vertically stacked column of player action buttons optimized for mobile touch
57
67
  opacity: 0.5;
58
68
  cursor: default;
59
69
  }
70
+ .mobile-player-buttons__action--static {
71
+ cursor: default;
72
+ }
60
73
  .mobile-player-buttons__action--menu {
61
74
  padding: 0;
62
75
  min-block-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
@@ -1,6 +1,7 @@
1
1
  import type { PlayerButtonDef, PlayerContextActionDef } from './types';
2
2
  import type { Placement } from '@floating-ui/dom';
3
3
  type Props = {
4
+ /** Button definitions — one without `on` renders as a static plaque instead of a button. */
4
5
  actions: PlayerButtonDef[];
5
6
  /** Rows of an overflow menu rendered as the final cell — an ellipsis trigger opening a popover. Empty ⇒ no menu cell. @default [] */
6
7
  contextActions?: PlayerContextActionDef[];
@@ -9,7 +10,9 @@ type Props = {
9
10
  };
10
11
  /**
11
12
  * A vertically stacked column of player action buttons optimized for mobile touch targets.
12
- * `contextActions` appends an overflow-menu cell below the buttons.
13
+ * `contextActions` appends an overflow-menu cell below the buttons. An action without `on` renders as a static
14
+ * plaque — same cell, no pointer feedback, out of the tab order — while `disabled` keeps dimming an action that
15
+ * exists but is currently unavailable.
13
16
  *
14
17
  * ### CSS Custom Properties
15
18
  * | Property | Description | Default |
@@ -5,38 +5,63 @@ const hasMenu = $derived(contextActions.length > 0);
5
5
  const cellCount = $derived(actions.length + (hasMenu ? 1 : 0));
6
6
  </script>
7
7
 
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]}
8
+ {#snippet soloCell(action: PlayerButtonDef)}
9
+ {#if action.on}
15
10
  <button
16
11
  type="button"
17
12
  class="player-button"
18
13
  style:zoom={zoom}
19
14
  class:player-button--scale-effect={scaleEffect}
20
15
  disabled={action.disabled}
21
- onclick={action.callback}>
16
+ aria-label={action.label || undefined}
17
+ onclick={action.on.click}>
22
18
  <span class="player-button__icon">
23
19
  <IconSlot icon={action.icon} />
24
20
  </span>
25
21
  </button>
22
+ {:else}
23
+ <span class="player-button player-button--static" style:zoom={zoom} role="img" aria-label={action.label || undefined}>
24
+ <span class="player-button__icon">
25
+ <IconSlot icon={action.icon} />
26
+ </span>
27
+ </span>
28
+ {/if}
29
+ {/snippet}
30
+
31
+ {#snippet pillCell(action: PlayerButtonDef)}
32
+ {#if action.on}
33
+ <button
34
+ type="button"
35
+ class="player-buttons__action"
36
+ class:player-buttons__action--scale-effect={scaleEffect}
37
+ disabled={action.disabled}
38
+ aria-label={action.label || undefined}
39
+ onclick={action.on.click}>
40
+ <span class="player-buttons__action-icon">
41
+ <IconSlot icon={action.icon} />
42
+ </span>
43
+ </button>
44
+ {:else}
45
+ <span class="player-buttons__action player-buttons__action--static" role="img" aria-label={action.label || undefined}>
46
+ <span class="player-buttons__action-icon">
47
+ <IconSlot icon={action.icon} />
48
+ </span>
49
+ </span>
50
+ {/if}
51
+ {/snippet}
52
+
53
+ {#if cellCount === 1}
54
+ {#if hasMenu}
55
+ <div class="player-button player-button--menu" style:zoom={zoom}>
56
+ <PlayerContextMenu actions={contextActions} position={contextPosition} />
57
+ </div>
58
+ {:else}
59
+ {@render soloCell(actions[0])}
26
60
  {/if}
27
61
  {:else if cellCount > 1}
28
62
  <div class="player-buttons" style:zoom={zoom}>
29
63
  {#each actions as action, index (index)}
30
- <button
31
- type="button"
32
- class="player-buttons__action"
33
- class:player-buttons__action--scale-effect={scaleEffect}
34
- disabled={action.disabled}
35
- onclick={action.callback}>
36
- <span class="player-buttons__action-icon">
37
- <IconSlot icon={action.icon} />
38
- </span>
39
- </button>
64
+ {@render pillCell(action)}
40
65
  {/each}
41
66
  {#if hasMenu}
42
67
  <div class="player-buttons__action player-buttons__action--menu">
@@ -50,7 +75,9 @@ const cellCount = $derived(actions.length + (hasMenu ? 1 : 0));
50
75
  @component
51
76
  Desktop player action buttons — renders a single circular button or a vertically stacked pill for multiple actions.
52
77
  `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.
78
+ action plus a menu renders as a two-cell pill. An action without `on` renders as a static plaque — same cell
79
+ chrome, no hover, no scale, out of the tab order — while `disabled` keeps dimming an action that exists but is
80
+ currently unavailable.
54
81
 
55
82
  ### CSS Custom Properties
56
83
  | Property | Description | Default |
@@ -70,6 +97,7 @@ action plus a menu renders as a two-cell pill.
70
97
  --_player-button--menu--item-background-hover: var(--sc-kit--player-button--menu--item--background--hover, rgb(255 255 255 / 12%));
71
98
  --_player-button--icon-scale: 1;
72
99
  --_player-button--cell-padding: 0.625rem;
100
+ --_player-button--background--hover: var(--_player-button--color);
73
101
  pointer-events: auto;
74
102
  padding: var(--_player-button--cell-padding);
75
103
  display: flex;
@@ -83,16 +111,21 @@ action plus a menu renders as a two-cell pill.
83
111
  --sc-kit--icon--size: 1.75rem;
84
112
  }
85
113
  .player-button:hover:not(:disabled) {
86
- background-color: var(--_player-button--color);
114
+ background-color: var(--_player-button--background--hover);
87
115
  }
88
116
  .player-button:disabled {
89
117
  opacity: 0.5;
90
118
  cursor: default;
91
119
  }
120
+ .player-button--static {
121
+ --_player-button--background--hover: var(--_player-button--color--inactive);
122
+ cursor: default;
123
+ }
92
124
  .player-button--scale-effect:hover:not(:disabled) {
93
125
  --_player-button--icon-scale: 1.2;
94
126
  }
95
127
  .player-button--menu {
128
+ --_player-button--background--hover: var(--_player-button--color--inactive);
96
129
  padding: 0;
97
130
  min-block-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
98
131
  min-inline-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
@@ -101,9 +134,6 @@ action plus a menu renders as a two-cell pill.
101
134
  --sc-kit--popover-item--color: var(--_player-button--menu--item-color);
102
135
  --sc-kit--popover-item--background--hover: var(--_player-button--menu--item-background-hover);
103
136
  }
104
- .player-button--menu:hover:not(:disabled) {
105
- background-color: var(--_player-button--color--inactive);
106
- }
107
137
  .player-button__icon {
108
138
  display: block;
109
139
  transform: scale(var(--_player-button--icon-scale));
@@ -125,7 +155,6 @@ action plus a menu renders as a two-cell pill.
125
155
  --_player-button--menu--border-color: var(--sc-kit--player-button--menu--border-color, rgb(255 255 255 / 15%));
126
156
  --_player-button--menu--item-color: var(--sc-kit--player-button--menu--item--color, var(--sc-kit--color--text--on-accent));
127
157
  --_player-button--menu--item-background-hover: var(--sc-kit--player-button--menu--item--background--hover, rgb(255 255 255 / 12%));
128
- cursor: pointer;
129
158
  display: flex;
130
159
  flex-direction: column;
131
160
  justify-content: center;
@@ -137,9 +166,11 @@ action plus a menu renders as a two-cell pill.
137
166
  .player-buttons__action {
138
167
  --_player-button--icon-scale: 1;
139
168
  --_player-button--cell-padding: 0.5625rem;
169
+ --_player-button--background--hover: var(--_player-button--color);
140
170
  display: flex;
141
171
  justify-content: center;
142
172
  align-items: center;
173
+ cursor: pointer;
143
174
  padding: var(--_player-button--cell-padding);
144
175
  border-radius: 1.25rem;
145
176
  color: var(--sc-kit--color--text--on-accent);
@@ -148,17 +179,24 @@ action plus a menu renders as a two-cell pill.
148
179
  transition: background-color 0.5s;
149
180
  }
150
181
  .player-buttons__action:hover:not(:disabled) {
151
- background-color: var(--_player-button--color);
182
+ background-color: var(--_player-button--background--hover);
152
183
  }
153
184
  .player-buttons__action:disabled {
154
185
  opacity: 0.5;
155
186
  cursor: default;
156
187
  }
188
+ .player-buttons__action--static {
189
+ --_player-button--background--hover: transparent;
190
+ cursor: default;
191
+ }
192
+ .player-buttons__action--scale-effect {
193
+ --_player-button--background--hover: transparent;
194
+ }
157
195
  .player-buttons__action--scale-effect:hover:not(:disabled) {
158
196
  --_player-button--icon-scale: 1.2;
159
- background-color: transparent;
160
197
  }
161
198
  .player-buttons__action--menu {
199
+ --_player-button--background--hover: transparent;
162
200
  padding: 0;
163
201
  min-block-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
164
202
  min-inline-size: calc(var(--sc-kit--icon--size) + 2 * var(--_player-button--cell-padding));
@@ -167,9 +205,6 @@ action plus a menu renders as a two-cell pill.
167
205
  --sc-kit--popover-item--color: var(--_player-button--menu--item-color);
168
206
  --sc-kit--popover-item--background--hover: var(--_player-button--menu--item-background-hover);
169
207
  }
170
- .player-buttons__action--menu:hover:not(:disabled) {
171
- background-color: transparent;
172
- }
173
208
  .player-buttons__action-icon {
174
209
  display: block;
175
210
  transform: scale(var(--_player-button--icon-scale));
@@ -1,7 +1,7 @@
1
1
  import type { PlayerButtonDef, PlayerContextActionDef } from './types';
2
2
  import type { Placement } from '@floating-ui/dom';
3
3
  type Props = {
4
- /** Array of button definitions (icon, callback, disabled state) */
4
+ /** Button definitions one without `on` renders as a static plaque instead of a button. */
5
5
  actions: PlayerButtonDef[];
6
6
  /** Rows of an overflow menu rendered as the final cell — an ellipsis trigger opening a popover. Empty ⇒ no menu cell. @default [] */
7
7
  contextActions?: PlayerContextActionDef[];
@@ -15,7 +15,9 @@ type Props = {
15
15
  /**
16
16
  * Desktop player action buttons — renders a single circular button or a vertically stacked pill for multiple actions.
17
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.
18
+ * action plus a menu renders as a two-cell pill. An action without `on` renders as a static plaque — same cell
19
+ * chrome, no hover, no scale, out of the tab order — while `disabled` keeps dimming an action that exists but is
20
+ * currently unavailable.
19
21
  *
20
22
  * ### CSS Custom Properties
21
23
  * | Property | Description | Default |
@@ -13,7 +13,7 @@ const localization = new PlayerButtonsLocalization();
13
13
  {/snippet}
14
14
 
15
15
  {#each actions as action (action.label)}
16
- <PopoverItem disabled={action.disabled} on={{ click: action.callback }}>
16
+ <PopoverItem disabled={action.disabled} on={{ click: action.on?.click }}>
17
17
  <IconText icon={action.icon} size="sm">{action.label}</IconText>
18
18
  </PopoverItem>
19
19
  {/each}
@@ -1,12 +1,20 @@
1
1
  import type { IconProp } from '../../icon';
2
2
  export type PlayerButtonDef = {
3
3
  icon: IconProp;
4
- callback: () => void;
4
+ /** Accessible name — rendered as `aria-label`, never as visible text. */
5
+ label: string;
6
+ /** Omit for a control with no action: it renders as a static plaque with the button's chrome and no pointer feedback, rather than a dimmed button. */
7
+ on?: {
8
+ click: () => void;
9
+ };
5
10
  disabled?: boolean;
6
11
  };
7
12
  export type PlayerContextActionDef = {
8
13
  icon: IconProp;
9
14
  label: string;
10
- callback: () => void;
15
+ /** Omit for a row with no action: it renders as static text that does not highlight and does not dismiss the menu. */
16
+ on?: {
17
+ click: () => void;
18
+ };
11
19
  disabled?: boolean;
12
20
  };
@@ -1,23 +1,33 @@
1
1
  <script lang="ts">import { popoverIgnore } from './popover-ignore';
2
2
  const { disabled = false, keepOpen = false, divider = false, inset = true, on, children } = $props();
3
+ const isStatic = $derived(!on?.click);
4
+ const interactive = $derived(!disabled && !isStatic);
3
5
  const handleClick = () => {
4
- if (disabled) {
6
+ if (!interactive) {
5
7
  return;
6
8
  }
7
9
  on?.click?.();
8
10
  };
11
+ const handleKeydown = (event) => {
12
+ if (event.key !== 'Enter' && event.key !== ' ') {
13
+ return;
14
+ }
15
+ event.preventDefault();
16
+ event.currentTarget.click();
17
+ };
9
18
  </script>
10
19
 
11
20
  <div
12
21
  class="popover-item"
13
22
  class:popover-item--disabled={disabled}
23
+ class:popover-item--static={isStatic}
14
24
  class:popover-item--divider={divider}
15
25
  class:popover-item--inset={inset}
16
- use:popoverIgnore={keepOpen}
26
+ use:popoverIgnore={keepOpen || isStatic}
17
27
  role="menuitem"
18
- tabindex={disabled ? -1 : 0}
28
+ tabindex={interactive ? 0 : -1}
19
29
  onclick={handleClick}
20
- onkeydown={() => undefined}>
30
+ onkeydown={handleKeydown}>
21
31
  <div class="popover-item__content">
22
32
  {@render children()}
23
33
  </div>
@@ -27,7 +37,9 @@ const handleClick = () => {
27
37
  @component
28
38
  PopoverItem — single row inside a Popover's content. Hover / disabled / opt-in divider
29
39
  variants. Set `keepOpen` to prevent the parent popover from closing when clicked (useful
30
- for toggle items inside menus).
40
+ for toggle items inside menus). A row given no `on` is static — full text color, no hover,
41
+ no focus, and a click neither fires nor dismisses the popover; `disabled` stays for a row
42
+ whose action exists but is currently unavailable.
31
43
 
32
44
  ### CSS Custom Properties
33
45
  | Property | Description | Default |
@@ -70,7 +82,7 @@ for toggle items inside menus).
70
82
  cursor: pointer;
71
83
  transition: background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
72
84
  }
73
- .popover-item:hover:not(.popover-item--disabled) {
85
+ .popover-item:hover:not(.popover-item--disabled):not(.popover-item--static) {
74
86
  background: var(--_di--background-hover);
75
87
  }
76
88
  .popover-item:focus-visible {
@@ -81,6 +93,9 @@ for toggle items inside menus).
81
93
  color: var(--_di--color-disabled);
82
94
  cursor: default;
83
95
  }
96
+ .popover-item--static {
97
+ cursor: default;
98
+ }
84
99
  .popover-item--divider {
85
100
  box-shadow: 0 1px 0 0 var(--_di--divider-color);
86
101
  margin-bottom: var(--_di--divider-spacing);
@@ -10,6 +10,7 @@ type Props = {
10
10
  * @default true
11
11
  */
12
12
  inset?: boolean;
13
+ /** Omit for a row with no action: it renders as static text that does not highlight, take focus, or dismiss the popover. */
13
14
  on?: {
14
15
  click?: () => void;
15
16
  };
@@ -18,7 +19,9 @@ type Props = {
18
19
  /**
19
20
  * PopoverItem — single row inside a Popover's content. Hover / disabled / opt-in divider
20
21
  * variants. Set `keepOpen` to prevent the parent popover from closing when clicked (useful
21
- * for toggle items inside menus).
22
+ * for toggle items inside menus). A row given no `on` is static — full text color, no hover,
23
+ * no focus, and a click neither fires nor dismisses the popover; `disabled` stays for a row
24
+ * whose action exists but is currently unavailable.
22
25
  *
23
26
  * ### CSS Custom Properties
24
27
  * | Property | Description | Default |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.56.0",
3
+ "version": "0.57.1",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",