@streamscloud/kit 0.26.0 → 0.28.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.
@@ -111,6 +111,9 @@ a `Carousel` of mixed image / video items. Seek-bar renders below the media when
111
111
  (default true); the bar itself is hidden via `visibility: hidden` when the current item isn't
112
112
  playable (preserves card height). Optional `duration` overlay renders bottom-right.
113
113
 
114
+ The play glyph of a playable item is a `PlayIndicator` — size and shadow are tuned with
115
+ `--sc-kit--play-indicator--size` / `--sc-kit--play-indicator--filter`.
116
+
114
117
  ### CSS Custom Properties
115
118
  | Property | Description | Default |
116
119
  |---|---|---|
@@ -22,6 +22,9 @@ type Props = {
22
22
  * (default true); the bar itself is hidden via `visibility: hidden` when the current item isn't
23
23
  * playable (preserves card height). Optional `duration` overlay renders bottom-right.
24
24
  *
25
+ * The play glyph of a playable item is a `PlayIndicator` — size and shadow are tuned with
26
+ * `--sc-kit--play-indicator--size` / `--sc-kit--play-indicator--filter`.
27
+ *
25
28
  * ### CSS Custom Properties
26
29
  * | Property | Description | Default |
27
30
  * |---|---|---|
@@ -0,0 +1,4 @@
1
+ export declare class VideoPlayerLocalization {
2
+ get play(): string;
3
+ get pause(): string;
4
+ }
@@ -0,0 +1,19 @@
1
+ import { AppLocale } from '../../core/locale';
2
+ const loc = {
3
+ play: {
4
+ en: 'Play',
5
+ no: 'Spill av'
6
+ },
7
+ pause: {
8
+ en: 'Pause',
9
+ no: 'Pause'
10
+ }
11
+ };
12
+ export class VideoPlayerLocalization {
13
+ get play() {
14
+ return loc.play[AppLocale.current];
15
+ }
16
+ get pause() {
17
+ return loc.pause[AppLocale.current];
18
+ }
19
+ }
@@ -1,11 +1,11 @@
1
1
  <script lang="ts">import { randomNanoid } from '../../core/utils';
2
- import { Icon } from '../icon';
3
2
  import { PlaybackManager } from '../media-playback';
4
- import IconPause from '@fluentui/svg-icons/icons/pause_20_regular.svg?raw';
5
- import IconPlay from '@fluentui/svg-icons/icons/play_20_regular.svg?raw';
3
+ import { PlayIndicator } from '../play-indicator';
4
+ import { VideoPlayerLocalization } from './video-player-localization';
6
5
  import { untrack } from 'svelte';
7
6
  const { src, poster, active, on } = $props();
8
7
  const id = randomNanoid();
8
+ const localization = new VideoPlayerLocalization();
9
9
  let videoEl = $state(null);
10
10
  let currentTime = $state(0);
11
11
  let duration = $state(NaN);
@@ -132,12 +132,13 @@ export const seekTo = (time) => {
132
132
  type="button"
133
133
  class="video-player__control-button"
134
134
  class:is-playing={isPlaying}
135
+ aria-label={isPlaying ? localization.pause : localization.play}
135
136
  onmousedown={(e) => e.preventDefault()}
136
137
  onclick={(e) => {
137
138
  e.stopPropagation();
138
139
  togglePlay();
139
140
  }}>
140
- <Icon src={isPlaying ? IconPause : IconPlay} color="on-accent" />
141
+ <PlayIndicator glyph={isPlaying ? 'pause' : 'play'} />
141
142
  </button>
142
143
 
143
144
  <div class="video-player__duration">{formatTime(duration - currentTime)}</div>
@@ -148,10 +149,14 @@ export const seekTo = (time) => {
148
149
  Internal video player used by `GridCardMedia` for a single playable media item. Integrates
149
150
  with the global `PlaybackManager` so only one player can play at a time. Exposes `seekTo(t)`
150
151
  via `bind:this` for parent seek-bar sync. Not exported standalone — use `GridCardMedia`.
152
+
153
+ The centred control renders `PlayIndicator`, so its size and shadow are tuned through the
154
+ `--sc-kit--play-indicator--*` variables set anywhere above the card.
151
155
  -->
152
156
 
153
157
  <style>.video-player {
154
158
  --_video-player--object-fit: var(--sc-kit--grid-card--media--object-fit, contain);
159
+ container-type: inline-size;
155
160
  position: relative;
156
161
  width: 100%;
157
162
  height: 100%;
@@ -171,7 +176,6 @@ via `bind:this` for parent seek-bar sync. Not exported standalone — use `GridC
171
176
  border: none;
172
177
  background: none;
173
178
  cursor: pointer;
174
- --sc-kit--icon--filter: var(--sc-kit--filter--icon-overlay);
175
179
  transition: opacity var(--sc-kit--duration--base) var(--sc-kit--ease--default);
176
180
  }
177
181
  .video-player__control-button.is-playing {
@@ -20,6 +20,9 @@ type Props = {
20
20
  * Internal video player used by `GridCardMedia` for a single playable media item. Integrates
21
21
  * with the global `PlaybackManager` so only one player can play at a time. Exposes `seekTo(t)`
22
22
  * via `bind:this` for parent seek-bar sync. Not exported standalone — use `GridCardMedia`.
23
+ *
24
+ * The centred control renders `PlayIndicator`, so its size and shadow are tuned through the
25
+ * `--sc-kit--play-indicator--*` variables set anywhere above the card.
23
26
  */
24
27
  declare const VideoPlayer: import("svelte").Component<Props, {
25
28
  seekTo: (time: number) => void;
@@ -1,7 +1,9 @@
1
- <script lang="ts">import { Icon } from '../icon';
1
+ <script lang="ts">import LayoutControlButton from './layout-control-button.svelte';
2
2
  import { PageLayoutLocalization } from './page-layout-localization';
3
+ import IconDismiss from '@fluentui/svg-icons/icons/dismiss_20_regular.svg?raw';
3
4
  import IconWindow from '@fluentui/svg-icons/icons/window_20_regular.svg?raw';
4
- const { children, title, actions, windowToggleActive = false, scrollable = false, on } = $props();
5
+ import IconWindowMultiple from '@fluentui/svg-icons/icons/window_multiple_20_regular.svg?raw';
6
+ const { children, title, actions, navigationToggle, editorControls, scrollable = false } = $props();
5
7
  const localization = new PageLayoutLocalization();
6
8
  </script>
7
9
 
@@ -18,16 +20,29 @@ const localization = new PageLayoutLocalization();
18
20
  {#if actions}
19
21
  {@render actions()}
20
22
  {/if}
21
- {#if on?.windowToggle}
22
- <button
23
- type="button"
24
- class="page-layout__window-toggle"
25
- class:page-layout__window-toggle--active={windowToggleActive}
26
- onclick={on.windowToggle}
27
- aria-label={localization.toggleNavigation}
28
- aria-pressed={windowToggleActive}>
29
- <Icon src={IconWindow} />
30
- </button>
23
+ {#if navigationToggle || editorControls}
24
+ <div class="page-layout__controls">
25
+ {#if navigationToggle}
26
+ <LayoutControlButton
27
+ icon={IconWindow}
28
+ label={localization.toggleNavigation}
29
+ variant="flat"
30
+ active={navigationToggle.active}
31
+ on={{ click: navigationToggle.onClick }} />
32
+ {/if}
33
+ {#if editorControls}
34
+ <LayoutControlButton
35
+ icon={IconWindowMultiple}
36
+ label={localization.togglePanels}
37
+ active={editorControls.panelsToggle.active}
38
+ on={{ click: editorControls.panelsToggle.onClick }} />
39
+ <LayoutControlButton
40
+ icon={IconDismiss}
41
+ label={localization.close}
42
+ active={editorControls.close.active}
43
+ on={{ click: editorControls.close.onClick }} />
44
+ {/if}
45
+ </div>
31
46
  {/if}
32
47
  </div>
33
48
  </div>
@@ -38,12 +53,14 @@ const localization = new PageLayoutLocalization();
38
53
 
39
54
  <!--
40
55
  @component
41
- PageLayout — top-level window chrome. Renders a fixed-height header (title + actions + optional
42
- window-toggle button) above a flex-column body that typically contains a `PagePanels` row.
56
+ PageLayout — top-level window chrome. Renders a fixed-height header (title + actions + an optional
57
+ group of chrome buttons) above a flex-column body that typically contains a `PagePanels` row.
43
58
 
44
- Pass `on.windowToggle` to surface a navigation-toggle button at the header's right edge wire it to
45
- whatever sidebar / drawer state your app owns. Omit it to hide the button. Set `scrollable` to let
46
- the whole page scroll as one document with sticky panel headers.
59
+ `navigationToggle` surfaces the app-navigation button as a bare glyph; `editorControls` is the fixed
60
+ editor pair panels toggle + close, both required so an editor never ships half the preset, drawn as
61
+ outlined squares. Every entry takes `{ active?, onClick }`, and the group renders in the order
62
+ navigation → panels → close. Set `scrollable` to let the whole page scroll as one document with
63
+ sticky panel headers.
47
64
 
48
65
  ### CSS Custom Properties
49
66
  | Property | Description | Default |
@@ -86,27 +103,10 @@ the whole page scroll as one document with sticky panel headers.
86
103
  align-items: center;
87
104
  gap: 1rem;
88
105
  }
89
- .page-layout__window-toggle {
90
- --sc-kit--icon--size: 1.125rem;
91
- display: inline-flex;
106
+ .page-layout__controls {
107
+ display: flex;
92
108
  align-items: center;
93
- justify-content: center;
94
- width: 1.75rem;
95
- height: 1.75rem;
96
- padding: 0;
97
- background: none;
98
- border: none;
99
- border-radius: var(--sc-kit--radius--sm);
100
- color: var(--sc-kit--color--text--muted);
101
- cursor: pointer;
102
- }
103
- .page-layout__window-toggle:hover {
104
- background: var(--sc-kit--color--bg--hover);
105
- color: var(--sc-kit--color--text--primary);
106
- }
107
- .page-layout__window-toggle--active {
108
- background: var(--sc-kit--color--bg--active);
109
- color: var(--sc-kit--color--text--primary);
109
+ gap: var(--sc-kit--space--2);
110
110
  }
111
111
  .page-layout__body {
112
112
  flex: 1;
@@ -1,23 +1,24 @@
1
+ import type { EditorControls, PageLayoutAction } from './types';
1
2
  import type { Snippet } from 'svelte';
2
3
  type Props = {
3
4
  children: Snippet;
4
5
  title?: string | Snippet;
5
6
  actions?: Snippet;
6
- /** Visual + ARIA state for the window-toggle button. Set `true` when the navigation it controls is hidden / collapsed — the button gets `aria-pressed` and an active background. @default false */
7
- windowToggleActive?: boolean;
7
+ /** Hides / shows the host app's own navigation. When set, a bare glyph button is rendered in the header's control group. */
8
+ navigationToggle?: PageLayoutAction;
9
+ /** Editor chrome — the fixed panels-toggle + close pair. Set it to render both buttons; omit it entirely for a non-editor page. */
10
+ editorControls?: EditorControls;
8
11
  scrollable?: boolean;
9
- on?: {
10
- /** When set, a window-toggle button is rendered at the right edge of the header. */
11
- windowToggle?: () => void;
12
- };
13
12
  };
14
13
  /**
15
- * PageLayout — top-level window chrome. Renders a fixed-height header (title + actions + optional
16
- * window-toggle button) above a flex-column body that typically contains a `PagePanels` row.
14
+ * PageLayout — top-level window chrome. Renders a fixed-height header (title + actions + an optional
15
+ * group of chrome buttons) above a flex-column body that typically contains a `PagePanels` row.
17
16
  *
18
- * Pass `on.windowToggle` to surface a navigation-toggle button at the header's right edge wire it to
19
- * whatever sidebar / drawer state your app owns. Omit it to hide the button. Set `scrollable` to let
20
- * the whole page scroll as one document with sticky panel headers.
17
+ * `navigationToggle` surfaces the app-navigation button as a bare glyph; `editorControls` is the fixed
18
+ * editor pair panels toggle + close, both required so an editor never ships half the preset, drawn as
19
+ * outlined squares. Every entry takes `{ active?, onClick }`, and the group renders in the order
20
+ * navigation → panels → close. Set `scrollable` to let the whole page scroll as one document with
21
+ * sticky panel headers.
21
22
  *
22
23
  * ### CSS Custom Properties
23
24
  * | Property | Description | Default |
@@ -5,4 +5,4 @@ export { default as PagePanels } from './cmp.page-panels.svelte';
5
5
  export { default as PanelCollapseButton } from './cmp.panel-collapse-button.svelte';
6
6
  export { default as PanelContent } from './cmp.panel-content.svelte';
7
7
  export { PanelEntry, PanelState } from './panel-state.svelte';
8
- export type { PanelBorder, PanelResizingSettings } from './types';
8
+ export type { EditorControls, PageLayoutAction, PanelBorder, PanelResizingSettings } from './types';
@@ -0,0 +1,71 @@
1
+ <script lang="ts">import { Icon } from '../icon';
2
+ const { icon, label, variant = 'outlined', active, on } = $props();
3
+ </script>
4
+
5
+ <button
6
+ type="button"
7
+ class="layout-control-button layout-control-button--{variant}"
8
+ class:layout-control-button--active={active}
9
+ aria-label={label}
10
+ aria-pressed={active}
11
+ onclick={on.click}>
12
+ <Icon src={icon} size={variant === 'flat' ? 'control-xl' : 'control-md'} />
13
+ </button>
14
+
15
+ <!--
16
+ @component
17
+ Folder-internal chrome button for the `PageLayout` header. Two variants: `flat` for the
18
+ app-navigation toggle and `outlined` for the editor pair. Rendered by `PageLayout`; not exported.
19
+ -->
20
+ <style>.layout-control-button {
21
+ display: inline-flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ flex-shrink: 0;
25
+ inline-size: var(--_lcb--size);
26
+ block-size: var(--_lcb--size);
27
+ padding: 0;
28
+ color: var(--_lcb--color);
29
+ background: var(--_lcb--background);
30
+ border: 1px solid var(--_lcb--border-color);
31
+ border-radius: var(--_lcb--border-radius);
32
+ cursor: pointer;
33
+ transition: background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), border-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
34
+ }
35
+ .layout-control-button--outlined {
36
+ --_lcb--size: var(--sc-kit--field--height--md);
37
+ --_lcb--border-radius: var(--sc-kit--field--border-radius--md);
38
+ --_lcb--color: var(--sc-kit--color--text--primary);
39
+ --_lcb--color-hover: var(--sc-kit--color--text--primary);
40
+ --_lcb--color-active: var(--sc-kit--color--text--primary);
41
+ --_lcb--background: var(--sc-kit--color--bg--panel);
42
+ --_lcb--background-hover: var(--sc-kit--color--bg--hover);
43
+ --_lcb--background-active: var(--sc-kit--color--bg--active);
44
+ --_lcb--border-color: var(--sc-kit--color--border);
45
+ --_lcb--border-color-active: var(--sc-kit--color--border--strong);
46
+ }
47
+ .layout-control-button--flat {
48
+ --_lcb--size: 1.75rem;
49
+ --_lcb--border-radius: var(--sc-kit--radius--sm);
50
+ --_lcb--color: var(--sc-kit--color--text--muted);
51
+ --_lcb--color-hover: var(--sc-kit--color--text--primary);
52
+ --_lcb--color-active: var(--sc-kit--color--text--primary);
53
+ --_lcb--background: transparent;
54
+ --_lcb--background-hover: var(--sc-kit--color--bg--hover);
55
+ --_lcb--background-active: var(--sc-kit--color--bg--active);
56
+ --_lcb--border-color: transparent;
57
+ --_lcb--border-color-active: transparent;
58
+ }
59
+ .layout-control-button:hover {
60
+ color: var(--_lcb--color-hover);
61
+ background: var(--_lcb--background-hover);
62
+ }
63
+ .layout-control-button:focus-visible {
64
+ outline: 2px solid var(--sc-kit--color--border--focus);
65
+ outline-offset: 2px;
66
+ }
67
+ .layout-control-button--active {
68
+ color: var(--_lcb--color-active);
69
+ background: var(--_lcb--background-active);
70
+ border-color: var(--_lcb--border-color-active);
71
+ }</style>
@@ -0,0 +1,19 @@
1
+ type Props = {
2
+ /** Glyph — a string SVG source. */
3
+ icon: string;
4
+ label: string;
5
+ /** `flat` = bare 20px glyph in a 28px hit area (app-navigation toggle); `outlined` = 32px bordered square with a 16px glyph (editor chrome). @default 'outlined' */
6
+ variant?: 'outlined' | 'flat';
7
+ /** Toggle state. Leave undefined for a plain action button so no `aria-pressed` is emitted. */
8
+ active?: boolean;
9
+ on: {
10
+ click: () => void;
11
+ };
12
+ };
13
+ /**
14
+ * Folder-internal chrome button for the `PageLayout` header. Two variants: `flat` for the
15
+ * app-navigation toggle and `outlined` for the editor pair. Rendered by `PageLayout`; not exported.
16
+ */
17
+ declare const LayoutControlButton: import("svelte").Component<Props, {}, "">;
18
+ type LayoutControlButton = ReturnType<typeof LayoutControlButton>;
19
+ export default LayoutControlButton;
@@ -2,4 +2,6 @@ export declare class PageLayoutLocalization {
2
2
  get resizePanel(): string;
3
3
  get togglePanel(): string;
4
4
  get toggleNavigation(): string;
5
+ get togglePanels(): string;
6
+ get close(): string;
5
7
  }
@@ -11,6 +11,14 @@ const loc = {
11
11
  toggleNavigation: {
12
12
  en: 'Toggle navigation',
13
13
  no: 'Veksle navigasjon'
14
+ },
15
+ togglePanels: {
16
+ en: 'Toggle panels',
17
+ no: 'Veksle paneler'
18
+ },
19
+ close: {
20
+ en: 'Close',
21
+ no: 'Lukk'
14
22
  }
15
23
  };
16
24
  export class PageLayoutLocalization {
@@ -23,4 +31,10 @@ export class PageLayoutLocalization {
23
31
  get toggleNavigation() {
24
32
  return loc.toggleNavigation[AppLocale.current];
25
33
  }
34
+ get togglePanels() {
35
+ return loc.togglePanels[AppLocale.current];
36
+ }
37
+ get close() {
38
+ return loc.close[AppLocale.current];
39
+ }
26
40
  }
@@ -17,3 +17,16 @@ export type PanelResizingSettings = {
17
17
  maxWidth?: number;
18
18
  };
19
19
  export type PanelBorder = 'top' | 'right' | 'bottom' | 'left';
20
+ /**
21
+ * Config for one `PageLayout` header chrome button. Set `active` only for toggles — it drives both
22
+ * `aria-pressed` and the pressed background, and is omitted entirely for plain action buttons.
23
+ */
24
+ export type PageLayoutAction = {
25
+ active?: boolean;
26
+ onClick: () => void;
27
+ };
28
+ /** Editor chrome — a fixed pair, so an editor never ships half of it. */
29
+ export type EditorControls = {
30
+ panelsToggle: PageLayoutAction;
31
+ close: PageLayoutAction;
32
+ };
@@ -1,28 +1,38 @@
1
1
  <script lang="ts">import { Icon } from '../icon';
2
+ import IconPause from '@fluentui/svg-icons/icons/pause_20_regular.svg?raw';
2
3
  import IconPlay from '@fluentui/svg-icons/icons/play_20_regular.svg?raw';
4
+ const { glyph = 'play' } = $props();
3
5
  </script>
4
6
 
5
7
  <div class="play-indicator">
6
- <Icon src={IconPlay} color="on-accent" />
8
+ <Icon src={glyph === 'pause' ? IconPause : IconPlay} color="on-accent" />
7
9
  </div>
8
10
 
9
11
  <!--
10
12
  @component
11
- Decorative play glyph overlaid on a static cover/thumbnail to mark it as video. Not a player: it plays
12
- nothing and is `pointer-events: none`. Matches the built-in play button of kit's `Video`. The parent
13
- positions it (typically absolute-centered) and must be `container-type: inline-size` for the default
14
- container-query size to scale.
13
+ Playback glyph overlaid on a cover / thumbnail or on a player surface. It plays nothing and is
14
+ `pointer-events: none` it only depicts the state its parent owns, so an interactive parent wraps it
15
+ in its own `<button>`. The parent positions it (typically absolute-centered) and must be
16
+ `container-type: inline-size` for the default container-query size to scale.
17
+
18
+ Lower the shadow on very light backgrounds: the default `drop-shadow(1px 1px #000)` reads as a harsh
19
+ outline there — pass a softer filter, or `none`.
15
20
 
16
21
  ### CSS Custom Properties
17
22
  | Property | Description | Default |
18
23
  |---|---|---|
19
- | `--sc-kit--play-indicator--size` | Glyph size; container-query clamp scales it to parent width | `clamp(1.75rem, 12cqi, 3.5rem)` |
24
+ | `--sc-kit--play-indicator--size` | Glyph size; container-query clamp scales it to parent width, reaching the 40px ceiling at 500px | `clamp(1.75rem, 8cqi, 2.5rem)` |
25
+ | `--sc-kit--play-indicator--filter` | Shadow applied to the glyph | `--sc-kit--filter--icon-overlay` |
20
26
  -->
21
27
 
22
28
  <style>.play-indicator {
23
- --_play-indicator--size: var(--sc-kit--play-indicator--size, clamp(1.75rem, 12cqi, 3.5rem));
29
+ --_play-indicator--size: var(
30
+ --sc-kit--play-indicator--size,
31
+ var(--_--play-indicator--size, clamp(1.75rem, 8cqi, 2.5rem))
32
+ );
33
+ --_play-indicator--filter: var(--sc-kit--play-indicator--filter, var(--sc-kit--filter--icon-overlay));
24
34
  display: inline-flex;
25
35
  pointer-events: none;
26
36
  --sc-kit--icon--size: var(--_play-indicator--size);
27
- --sc-kit--icon--filter: var(--sc-kit--filter--icon-overlay);
37
+ --sc-kit--icon--filter: var(--_play-indicator--filter);
28
38
  }</style>
@@ -1,29 +1,22 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
1
+ type Props = {
2
+ /** Which playback state to depict. @default 'play' */
3
+ glyph?: 'play' | 'pause';
4
+ };
14
5
  /**
15
- * Decorative play glyph overlaid on a static cover/thumbnail to mark it as video. Not a player: it plays
16
- * nothing and is `pointer-events: none`. Matches the built-in play button of kit's `Video`. The parent
17
- * positions it (typically absolute-centered) and must be `container-type: inline-size` for the default
18
- * container-query size to scale.
6
+ * Playback glyph overlaid on a cover / thumbnail or on a player surface. It plays nothing and is
7
+ * `pointer-events: none` it only depicts the state its parent owns, so an interactive parent wraps it
8
+ * in its own `<button>`. The parent positions it (typically absolute-centered) and must be
9
+ * `container-type: inline-size` for the default container-query size to scale.
10
+ *
11
+ * Lower the shadow on very light backgrounds: the default `drop-shadow(1px 1px #000)` reads as a harsh
12
+ * outline there — pass a softer filter, or `none`.
19
13
  *
20
14
  * ### CSS Custom Properties
21
15
  * | Property | Description | Default |
22
16
  * |---|---|---|
23
- * | `--sc-kit--play-indicator--size` | Glyph size; container-query clamp scales it to parent width | `clamp(1.75rem, 12cqi, 3.5rem)` |
17
+ * | `--sc-kit--play-indicator--size` | Glyph size; container-query clamp scales it to parent width, reaching the 40px ceiling at 500px | `clamp(1.75rem, 8cqi, 2.5rem)` |
18
+ * | `--sc-kit--play-indicator--filter` | Shadow applied to the glyph | `--sc-kit--filter--icon-overlay` |
24
19
  */
25
- declare const Cmp: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
26
- [evt: string]: CustomEvent<any>;
27
- }, {}, {}, string>;
28
- type Cmp = InstanceType<typeof Cmp>;
20
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
21
+ type Cmp = ReturnType<typeof Cmp>;
29
22
  export default Cmp;
@@ -1,15 +1,14 @@
1
1
  <script lang="ts">import { randomNanoid } from '../../core/utils';
2
2
  import { Icon } from '../icon';
3
3
  import { MediaVolumeManager, PlaybackManager } from '../media-playback';
4
+ import { PlayIndicator } from '../play-indicator';
4
5
  import { SeekBar } from '../seek-bar';
5
6
  import { VideoLocalization } from './video-localization';
6
- import IconPause from '@fluentui/svg-icons/icons/pause_20_regular.svg?raw';
7
- import IconPlay from '@fluentui/svg-icons/icons/play_20_regular.svg?raw';
8
7
  import IconSpeaker from '@fluentui/svg-icons/icons/speaker_2_20_regular.svg?raw';
9
8
  import IconSpeakerMute from '@fluentui/svg-icons/icons/speaker_mute_20_regular.svg?raw';
10
9
  import { untrack } from 'svelte';
11
10
  import { fade } from 'svelte/transition';
12
- let { src, poster, id = randomNanoid(), autoplay = false, loop = false, inert = false, allowPreloading = false, hideSpeaker = false, hidePlayButton = false, intersectionContainer, scrubberPosition = 'bottom', on } = $props();
11
+ let { src, poster, id = randomNanoid(), autoplay = false, loop = false, inert = false, allowPreloading = false, hideSpeaker = false, hidePlayButton = false, intersectionContainer, scrubberPosition = 'bottom', staticControls = false, on } = $props();
13
12
  const localization = new VideoLocalization();
14
13
  let video = $state(null);
15
14
  let videoContainerRef = $state(null);
@@ -243,7 +242,7 @@ const handleSeek = (percent) => {
243
242
  };
244
243
  </script>
245
244
 
246
- <div class="video" role="none" inert={inert} bind:this={videoContainerRef}>
245
+ <div class="video" class:video--static-controls={staticControls} role="none" inert={inert} bind:this={videoContainerRef}>
247
246
  <video
248
247
  class="video__video"
249
248
  class:video__video--not-activated={!everActivated && !!poster}
@@ -273,16 +272,11 @@ const handleSeek = (percent) => {
273
272
  role="none">
274
273
  {#if isVideoPaused && !hidePlayButton}
275
274
  <button type="button" aria-label={localization.play} class="video__playback-button" onclick={togglePlay} onkeydown={() => ({})}>
276
- <Icon src={IconPlay} color="on-accent" />
275
+ <PlayIndicator glyph="play" />
277
276
  </button>
278
277
  {:else if showControlsOnHover && !hidePlayButton}
279
- <button
280
- type="button"
281
- aria-label={localization.pause}
282
- class="video__playback-button video__playback-button--pause"
283
- onclick={togglePlay}
284
- onkeydown={() => ({})}>
285
- <Icon src={IconPause} color="on-accent" />
278
+ <button type="button" aria-label={localization.pause} class="video__playback-button" onclick={togglePlay} onkeydown={() => ({})}>
279
+ <PlayIndicator glyph="pause" />
286
280
  </button>
287
281
  {/if}
288
282
 
@@ -323,13 +317,17 @@ const handleSeek = (percent) => {
323
317
  @component
324
318
  A full-featured video player with custom overlay controls, play/pause, mute, seek bar, poster image, autoplay-on-scroll, and global playback management.
325
319
 
320
+ The root is a `container-type: inline-size` container, so the play / pause glyph scales with the
321
+ player's own width. Pass `staticControls` to drop the container where size containment would break
322
+ the surrounding layout.
323
+
326
324
  ### CSS Custom Properties
327
325
  | Property | Description | Default |
328
326
  |---|---|---|
329
327
  | `--sc-kit--video--background-color` | Player background | `black` |
330
328
  | `--sc-kit--video--border-radius` | Corner rounding | `0` |
331
329
  | `--sc-kit--video--media-fit` | Video object-fit mode | `contain` |
332
- | `--sc-kit--video--playback-button--size` | Size of the custom Play button | `2.5rem` |
330
+ | `--sc-kit--video--playback-button--size` | Pins the play / pause glyph to a fixed size instead of letting `PlayIndicator` scale it with the player width | unset |
333
331
  | `--sc-kit--video--poster--media-fit` | Poster image object-fit mode | `cover` |
334
332
  -->
335
333
 
@@ -337,8 +335,9 @@ A full-featured video player with custom overlay controls, play/pause, mute, see
337
335
  --_video--background-color: var(--sc-kit--video--background-color, #000);
338
336
  --_video--border-radius: var(--sc-kit--video--border-radius, 0);
339
337
  --_video--media-fit: var(--sc-kit--video--media-fit, contain);
340
- --_video--playback-button--size: var(--sc-kit--video--playback-button--size, 2.5rem);
341
338
  --_video--poster--media-fit: var(--sc-kit--video--poster--media-fit, cover);
339
+ --_--play-indicator--size: var(--sc-kit--video--playback-button--size, initial);
340
+ container-type: inline-size;
342
341
  height: 100%;
343
342
  min-height: 100%;
344
343
  max-height: 100%;
@@ -352,20 +351,14 @@ A full-featured video player with custom overlay controls, play/pause, mute, see
352
351
  background: var(--_video--background-color);
353
352
  }
354
353
  .video__playback-button {
355
- --sc-kit--icon--size: var(--_video--playback-button--size);
356
- --sc-kit--icon--filter: var(--sc-kit--filter--icon-overlay);
357
354
  position: absolute;
358
355
  top: 50%;
359
356
  left: 50%;
360
357
  transform: translate(-50%, -50%);
361
358
  }
362
- .video__playback-button--pause {
363
- /* Set 'container-type: inline-size;' to reference container*/
364
- }
365
- @container (width < 576px) {
366
- .video__playback-button--pause {
367
- display: none;
368
- }
359
+ .video--static-controls {
360
+ --_--play-indicator--size: var(--sc-kit--video--playback-button--size, 2.5rem);
361
+ container-type: normal;
369
362
  }
370
363
  .video__mute-button {
371
364
  --sc-kit--icon--size: 1.25rem;
@@ -21,6 +21,12 @@ type Props = {
21
21
  hidePlayButton?: boolean;
22
22
  /** Position the seek bar at the top or bottom of the overlay @default 'bottom' */
23
23
  scrubberPosition?: ScrubberPosition;
24
+ /**
25
+ * Removes `container-type: inline-size` from the root so the play glyph stays a fixed 40px.
26
+ * Use this when size containment would collapse a shrink-to-fit parent to zero width.
27
+ * @default false
28
+ */
29
+ staticControls?: boolean;
24
30
  on?: {
25
31
  started?: (data: {
26
32
  id: string;
@@ -41,13 +47,17 @@ type Props = {
41
47
  /**
42
48
  * A full-featured video player with custom overlay controls, play/pause, mute, seek bar, poster image, autoplay-on-scroll, and global playback management.
43
49
  *
50
+ * The root is a `container-type: inline-size` container, so the play / pause glyph scales with the
51
+ * player's own width. Pass `staticControls` to drop the container where size containment would break
52
+ * the surrounding layout.
53
+ *
44
54
  * ### CSS Custom Properties
45
55
  * | Property | Description | Default |
46
56
  * |---|---|---|
47
57
  * | `--sc-kit--video--background-color` | Player background | `black` |
48
58
  * | `--sc-kit--video--border-radius` | Corner rounding | `0` |
49
59
  * | `--sc-kit--video--media-fit` | Video object-fit mode | `contain` |
50
- * | `--sc-kit--video--playback-button--size` | Size of the custom Play button | `2.5rem` |
60
+ * | `--sc-kit--video--playback-button--size` | Pins the play / pause glyph to a fixed size instead of letting `PlayIndicator` scale it with the player width | unset |
51
61
  * | `--sc-kit--video--poster--media-fit` | Poster image object-fit mode | `cover` |
52
62
  */
53
63
  declare const Cmp: import("svelte").Component<Props, {}, "">;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",