@streamscloud/kit 0.72.0 → 0.74.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.
@@ -1,2 +1,3 @@
1
1
  export { horizontalWheelScroll } from './horizontal-wheel-scroll';
2
2
  export { swallowTouch } from './swallow-touch';
3
+ export { predecodeOnLoad } from './predecode-on-load';
@@ -1,2 +1,3 @@
1
1
  export { horizontalWheelScroll } from './horizontal-wheel-scroll';
2
2
  export { swallowTouch } from './swallow-touch';
3
+ export { predecodeOnLoad } from './predecode-on-load';
@@ -0,0 +1,3 @@
1
+ import type { Action } from 'svelte/action';
2
+ /** Decodes the `<img>` once after every load, so a drag ghost snapshotted right after includes the picture. A broken image is ignored. `false` turns it off. */
3
+ export declare const predecodeOnLoad: Action<HTMLImageElement, boolean | undefined>;
@@ -0,0 +1,31 @@
1
+ /** Decodes the `<img>` once after every load, so a drag ghost snapshotted right after includes the picture. A broken image is ignored. `false` turns it off. */
2
+ export const predecodeOnLoad = (node, enabled = true) => {
3
+ let active = enabled;
4
+ const decode = async () => {
5
+ try {
6
+ await node.decode();
7
+ }
8
+ catch {
9
+ // a broken image has nothing to decode
10
+ }
11
+ };
12
+ const decodeLoaded = () => {
13
+ if (active && node.complete && node.naturalWidth > 0) {
14
+ void decode();
15
+ }
16
+ };
17
+ node.addEventListener('load', decodeLoaded);
18
+ decodeLoaded();
19
+ return {
20
+ update(nextEnabled = true) {
21
+ const turnedOn = nextEnabled && !active;
22
+ active = nextEnabled;
23
+ if (turnedOn) {
24
+ decodeLoaded();
25
+ }
26
+ },
27
+ destroy() {
28
+ node.removeEventListener('load', decodeLoaded);
29
+ }
30
+ };
31
+ };
@@ -8,7 +8,7 @@ import { ProportionalContainer } from '../proportional-container';
8
8
  import { SeekBar } from '../seek-bar';
9
9
  import VideoPlayer from './video-player.svelte';
10
10
  import IconImageOff from '@fluentui/svg-icons/icons/image_off_20_regular.svg?raw';
11
- const { items, aspectRatio = 'vertical', objectFit = 'contain', showSeekBar = true, duration, decoding = 'auto' } = $props();
11
+ const { items, aspectRatio = 'vertical', objectFit = 'contain', showSeekBar = true, duration, predecode = false } = $props();
12
12
  let currentIndex = $state(0);
13
13
  let currentTime = $state(0);
14
14
  let mediaDuration = $state(NaN);
@@ -63,7 +63,7 @@ const onCarouselIndexChanged = (index) => {
63
63
  active={true}
64
64
  on={{ timeUpdate: onPlayerTimeUpdate, durationChange: onPlayerDurationChange, activate: onPlayerActivate }} />
65
65
  {:else}
66
- <Image src={items[0].url} showStubOnError={true} decoding={decoding} />
66
+ <Image src={items[0].url} showStubOnError={true} predecode={predecode} />
67
67
  {/if}
68
68
  </div>
69
69
  {:else}
@@ -78,7 +78,7 @@ const onCarouselIndexChanged = (index) => {
78
78
  active={item === items[currentIndex]}
79
79
  on={{ timeUpdate: onPlayerTimeUpdate, durationChange: onPlayerDurationChange, activate: onPlayerActivate }} />
80
80
  {:else}
81
- <Image src={item.url} showStubOnError={true} decoding={decoding} />
81
+ <Image src={item.url} showStubOnError={true} predecode={predecode} />
82
82
  {/if}
83
83
  </div>
84
84
  {/snippet}
@@ -15,8 +15,8 @@ type Props = {
15
15
  showSeekBar?: boolean;
16
16
  /** Duration in seconds; rendered as a badge overlay at the bottom-right of the media. `≤ 0` / `undefined` renders nothing. */
17
17
  duration?: number;
18
- /** Forwarded to each `Image`. Pass `'sync'` when the card sits in a `DragSource`, or the first drag ghost comes out without the picture. @default 'auto' */
19
- decoding?: 'sync' | 'async' | 'auto';
18
+ /** Forwarded to each `Image`. Set it when the card sits in a `DragSource`, or the first drag ghost comes out without the picture. @default false */
19
+ predecode?: boolean;
20
20
  };
21
21
  /**
22
22
  * Media slot for `GridCard`. Renders an empty placeholder, a single `Image` / `VideoPlayer`, or
@@ -1,7 +1,8 @@
1
- <script lang="ts">import { Icon } from '../icon';
1
+ <script lang="ts">import { predecodeOnLoad } from '../../core/actions';
2
+ import { Icon } from '../icon';
2
3
  import { calculateImageState } from './image-state';
3
4
  import IconImageOff from '@fluentui/svg-icons/icons/image_off_20_regular.svg?raw';
4
- let { src, alt = '', showStubOnError = false, decoding = 'auto', on, stub } = $props();
5
+ let { src, alt = '', showStubOnError = false, predecode = false, on, stub } = $props();
5
6
  let hasError = $state(false);
6
7
  const imageState = $derived(calculateImageState(src, hasError, showStubOnError));
7
8
  // A 0.01x density makes the intrinsic size 100× the natural one, so `contain` sizing always scales the picture
@@ -26,7 +27,7 @@ const onLoadError = () => {
26
27
  srcset={srcset}
27
28
  alt={alt || ''}
28
29
  draggable="false"
29
- decoding={decoding}
30
+ use:predecodeOnLoad={predecode}
30
31
  onload={onLoad}
31
32
  onerror={onLoadError} />
32
33
  {/if}
@@ -5,8 +5,8 @@ type Props = {
5
5
  alt?: string;
6
6
  /** Show a placeholder stub instead of an error icon when image fails to load */
7
7
  showStubOnError?: boolean;
8
- /** `'sync'` paints the picture in the same frame as its box — needed when the element can be dragged, or the first drag ghost comes out empty. @default 'auto' */
9
- decoding?: 'sync' | 'async' | 'auto';
8
+ /** Decodes the picture once right after it loads. Set it when the element can be dragged, or the first drag ghost comes out empty. @default false */
9
+ predecode?: boolean;
10
10
  on?: {
11
11
  /** Fires after load attempt; `true` if successful, `false` on error */
12
12
  load?: (loadedSuccessfully: boolean) => void;
@@ -1,10 +1,11 @@
1
- <script lang="ts">import { Checkbox } from '../checkbox';
1
+ <script lang="ts">import { predecodeOnLoad } from '../../core/actions';
2
+ import { Checkbox } from '../checkbox';
2
3
  import { ToolbarIconButton } from '../page-toolbar';
3
4
  import { PlayIndicator } from '../play-indicator';
4
5
  import { Popover } from '../popover';
5
6
  import { TileCardLocalization } from './tile-card-localization';
6
7
  import IconMore from '@fluentui/svg-icons/icons/more_vertical_20_regular.svg?raw';
7
- const { title, meta, preview, decoding = 'auto', menu, menuLabel, selected = false, checkboxVisibility = 'hover', on } = $props();
8
+ const { title, meta, preview, predecode = false, menu, menuLabel, selected = false, checkboxVisibility = 'hover', on } = $props();
8
9
  const localization = new TileCardLocalization();
9
10
  // Safari paints no frame for preload="metadata" until a seek target is given.
10
11
  const withFirstFrame = (src) => (src.includes('#') ? src : `${src}#t=0.001`);
@@ -19,7 +20,7 @@ const withFirstFrame = (src) => (src.includes('#') ? src : `${src}#t=0.001`);
19
20
  {#if typeof preview === 'function'}
20
21
  {@render preview()}
21
22
  {:else if 'image' in preview}
22
- <img class="tile-card__picture" src={preview.image} alt="" decoding={decoding} />
23
+ <img class="tile-card__picture" src={preview.image} alt="" use:predecodeOnLoad={predecode} />
23
24
  {:else}
24
25
  <video class="tile-card__picture" src={withFirstFrame(preview.video)} poster={preview.poster} preload="metadata" muted playsinline></video>
25
26
  <span class="tile-card__play"><PlayIndicator /></span>
@@ -6,8 +6,8 @@ type Props = {
6
6
  meta?: string;
7
7
  /** Top area: `image` / `video` render a drag-safe picture or frame (contain, never upscaled; a video gets a play glyph); a snippet renders custom content. The area is `pointer-events: none` — interactive content belongs in `menu` or `on.activate`. */
8
8
  preview?: TileCardPreview;
9
- /** Decoding of the `image` preview. Pass `'sync'` when the tile sits in a `DragSource`, or the first drag ghost comes out without the picture — the same goes for an `<img>` in a preview snippet. @default 'auto' */
10
- decoding?: 'sync' | 'async' | 'auto';
9
+ /** Decodes the `image` preview once right after it loads. Set it when the tile sits in a `DragSource`, or the first drag ghost comes out without the picture — an `<img>` in a preview snippet needs `use:predecodeOnLoad` from its owner. @default false */
10
+ predecode?: boolean;
11
11
  /** `PopoverItem`s of the kebab menu at the end of the footer. */
12
12
  menu?: Snippet;
13
13
  /** Accessible name of the kebab menu. @default localized "Actions" */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.72.0",
3
+ "version": "0.74.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",