@streamscloud/kit 0.63.0 → 0.65.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.
Files changed (46) hide show
  1. package/dist/core/local-storage/index.d.ts +1 -1
  2. package/dist/core/local-storage/local-storage-item.d.ts +14 -2
  3. package/dist/core/local-storage/local-storage-item.js +35 -10
  4. package/dist/core/utils/color-helper.d.ts +4 -0
  5. package/dist/core/utils/color-helper.js +10 -0
  6. package/dist/core/utils/price-helper.d.ts +2 -0
  7. package/dist/core/utils/price-helper.js +12 -1
  8. package/dist/ui/cropper/image-editor-dialog/cmp.image-editor-dialog.svelte +17 -51
  9. package/dist/ui/cropper/image-editor-dialog/crop-and-resize.d.ts +4 -16
  10. package/dist/ui/cropper/image-editor-dialog/crop-and-resize.js +1 -14
  11. package/dist/ui/cropper/image-editor-dialog/index.d.ts +8 -7
  12. package/dist/ui/cropper/image-editor-dialog/index.js +8 -7
  13. package/dist/ui/cropper/image-editor-dialog/types.d.ts +1 -1
  14. package/dist/ui/cropper/img-cropper/cmp.img-cropper-toolbar.svelte +149 -181
  15. package/dist/ui/cropper/img-cropper/cmp.img-cropper-toolbar.svelte.d.ts +4 -13
  16. package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte +389 -147
  17. package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte.d.ts +13 -39
  18. package/dist/ui/cropper/img-cropper/frame-geometry.d.ts +82 -0
  19. package/dist/ui/cropper/img-cropper/frame-geometry.js +165 -0
  20. package/dist/ui/cropper/img-cropper/img-cropper-localization.d.ts +2 -3
  21. package/dist/ui/cropper/img-cropper/img-cropper-localization.js +12 -19
  22. package/dist/ui/cropper/img-cropper/img-cropper-toolbar-localization.d.ts +14 -5
  23. package/dist/ui/cropper/img-cropper/img-cropper-toolbar-localization.js +127 -33
  24. package/dist/ui/cropper/img-cropper/img-cropper-utils.d.ts +4 -51
  25. package/dist/ui/cropper/img-cropper/img-cropper-utils.js +35 -190
  26. package/dist/ui/cropper/img-cropper/img-cropper.svelte.d.ts +99 -34
  27. package/dist/ui/cropper/img-cropper/img-cropper.svelte.js +405 -104
  28. package/dist/ui/cropper/img-cropper/index.d.ts +1 -2
  29. package/dist/ui/cropper/img-cropper/index.js +0 -1
  30. package/dist/ui/image/cmp.image.svelte +23 -5
  31. package/dist/ui/image/cmp.image.svelte.d.ts +1 -1
  32. package/dist/ui/page-toolbar/cmp.toolbar-segmented-control.svelte +2 -2
  33. package/dist/ui/page-toolbar/cmp.toolbar-view-controls.svelte +4 -2
  34. package/dist/ui/table/table-columns-manager/cmp.table-columns-manager.svelte +4 -2
  35. package/package.json +1 -3
  36. package/dist/ui/cropper/img-cropper/cmp.img-cropper-controls.svelte +0 -68
  37. package/dist/ui/cropper/img-cropper/cmp.img-cropper-controls.svelte.d.ts +0 -21
  38. package/dist/ui/cropper/img-cropper/cropperjs-elements.d.ts +0 -35
  39. package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.d.ts +0 -40
  40. package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.js +0 -193
  41. package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.d.ts +0 -7
  42. package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.js +0 -51
  43. package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.d.ts +0 -7
  44. package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.js +0 -89
  45. package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.d.ts +0 -42
  46. package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.js +0 -1
@@ -1 +1 @@
1
- export { LocalStorageItem } from './local-storage-item';
1
+ export { LocalStorageItem, type LocalStorageItemOptions } from './local-storage-item';
@@ -1,10 +1,22 @@
1
+ export type LocalStorageItemOptions<T> = {
2
+ /** Rejects a stored value that parses but no longer fits (a stale number, a removed enum member) — `get()` then returns the fallback. */
3
+ validate?: (value: unknown) => value is T;
4
+ };
1
5
  export declare class LocalStorageItem<T, TFallback = null> {
2
6
  readonly key: string;
3
7
  private readonly _fallback;
8
+ private readonly _validate;
4
9
  private constructor();
5
- static of: <T_1>(key: string) => LocalStorageItem<T_1>;
6
- static withDefault: <T_1>(key: string, fallback: T_1) => LocalStorageItem<T_1, T_1>;
10
+ static of: <T_1>(key: string, options?: LocalStorageItemOptions<T_1>) => LocalStorageItem<T_1>;
11
+ static withDefault: <T_1>(key: string, fallback: T_1, options?: LocalStorageItemOptions<T_1>) => LocalStorageItem<T_1, T_1>;
12
+ /**
13
+ * The stored value — or the fallback when nothing is stored, storage is unavailable, the value doesn't parse, or
14
+ * `validate` rejects it (or throws). Without `validate` a parsed value comes back unchecked — a stored `null`, `0` or
15
+ * `false` included.
16
+ */
7
17
  get(): T | TFallback;
18
+ /** Removes the stored value; does nothing when storage is unavailable. */
8
19
  remove(): void;
20
+ /** Stores the value; when storage is unavailable or full it just isn't remembered. */
9
21
  set(value: T): void;
10
22
  }
@@ -1,27 +1,52 @@
1
1
  export class LocalStorageItem {
2
2
  key;
3
3
  _fallback;
4
- constructor(key, fallback) {
4
+ _validate;
5
+ constructor(key, fallback, options) {
5
6
  this.key = key;
6
7
  this._fallback = fallback;
8
+ this._validate = options?.validate ?? null;
7
9
  }
8
- static of = (key) => {
9
- return new LocalStorageItem(key, null);
10
+ static of = (key, options) => {
11
+ return new LocalStorageItem(key, null, options);
10
12
  };
11
- static withDefault = (key, fallback) => {
12
- return new LocalStorageItem(key, fallback);
13
+ static withDefault = (key, fallback, options) => {
14
+ return new LocalStorageItem(key, fallback, options);
13
15
  };
16
+ /**
17
+ * The stored value — or the fallback when nothing is stored, storage is unavailable, the value doesn't parse, or
18
+ * `validate` rejects it (or throws). Without `validate` a parsed value comes back unchecked — a stored `null`, `0` or
19
+ * `false` included.
20
+ */
14
21
  get() {
15
- const stringValue = localStorage.getItem(this.key);
16
- if (!stringValue) {
22
+ try {
23
+ const stringValue = localStorage.getItem(this.key);
24
+ if (!stringValue) {
25
+ return this._fallback;
26
+ }
27
+ const value = JSON.parse(stringValue);
28
+ return !this._validate || this._validate(value) ? value : this._fallback;
29
+ }
30
+ catch {
17
31
  return this._fallback;
18
32
  }
19
- return JSON.parse(stringValue);
20
33
  }
34
+ /** Removes the stored value; does nothing when storage is unavailable. */
21
35
  remove() {
22
- localStorage.removeItem(this.key);
36
+ try {
37
+ localStorage.removeItem(this.key);
38
+ }
39
+ catch {
40
+ // Storage blocked (private mode, sandboxed iframe) — there is nothing to remove.
41
+ }
23
42
  }
43
+ /** Stores the value; when storage is unavailable or full it just isn't remembered. */
24
44
  set(value) {
25
- localStorage.setItem(this.key, JSON.stringify(value));
45
+ try {
46
+ localStorage.setItem(this.key, JSON.stringify(value));
47
+ }
48
+ catch {
49
+ // Storage blocked or over quota — losing a remembered setting beats breaking the caller.
50
+ }
26
51
  }
27
52
  }
@@ -10,4 +10,8 @@ export declare class ColorHelper {
10
10
  * Check whether a color value represents "no color" (empty, transparent, zero alpha).
11
11
  */
12
12
  static isTransparent(value: string | null | undefined): boolean;
13
+ /**
14
+ * Check whether a color value fully hides what is behind it (valid, 100% alpha).
15
+ */
16
+ static isOpaque(value: string | null | undefined): boolean;
13
17
  }
@@ -33,4 +33,14 @@ export class ColorHelper {
33
33
  const parsed = colord(value);
34
34
  return !parsed.isValid() || parsed.alpha() === 0;
35
35
  }
36
+ /**
37
+ * Check whether a color value fully hides what is behind it (valid, 100% alpha).
38
+ */
39
+ static isOpaque(value) {
40
+ if (!value) {
41
+ return false;
42
+ }
43
+ const parsed = colord(value);
44
+ return parsed.isValid() && parsed.alpha() === 1;
45
+ }
36
46
  }
@@ -4,6 +4,8 @@ export type PriceRepresentationOptions = {
4
4
  currencyMode?: CurrencyMode;
5
5
  missingFractionMode?: 'hide' | 'currency-placeholder' | 'zeros' | 'zeros-or-placeholder';
6
6
  locale?: string;
7
+ /** Render a zero amount as a localized "Free" (in the app's `AppLocale`) instead of a number. @default false */
8
+ zeroAsFree?: boolean;
7
9
  };
8
10
  export declare const toPriceRepresentation: (data: {
9
11
  amountMinor: number;
@@ -1,9 +1,20 @@
1
+ import { AppLocale } from '../locale/app-locale.svelte';
2
+ import {} from '../locale/types';
1
3
  import { Utils } from './utils';
4
+ const loc = {
5
+ free: {
6
+ en: 'Free',
7
+ no: 'Gratis'
8
+ }
9
+ };
2
10
  // `amountMinor` is an integer in the currency's smallest unit (e.g. cents) — the canonical money
3
11
  // representation. It is converted to a major amount here via the currency's minor-unit exponent.
4
12
  export const toPriceRepresentation = (data) => {
5
13
  const { amountMinor, currency, options } = data;
6
- const { currencyMode = 'symbol', missingFractionMode = 'zeros-or-placeholder', locale = 'nb-NO' } = options ?? {};
14
+ const { currencyMode = 'symbol', missingFractionMode = 'zeros-or-placeholder', locale = 'nb-NO', zeroAsFree = false } = options ?? {};
15
+ if (zeroAsFree && amountMinor === 0) {
16
+ return loc.free[AppLocale.current];
17
+ }
7
18
  const cfg = currencyDefaults[currency];
8
19
  const amount = amountMinor / 10 ** cfg.minorUnit;
9
20
  // Helper to attach currency per mode
@@ -1,19 +1,16 @@
1
1
  <script lang="ts">import { FileWithBlobDataHelper } from '../../../core/files';
2
2
  import { Dialog, DialogButton, DialogCancelButton } from '../../dialog';
3
3
  import { Toastr } from '../../toast';
4
- import { ImgCropper, ImgCropperControls, ImgCropperToolbar, ImgCropperView } from '../img-cropper';
4
+ import { ImgCropper, ImgCropperToolbar, ImgCropperView } from '../img-cropper';
5
5
  import { ImageEditorDialogLocalization } from './image-editor-dialog-localization';
6
6
  import { untrack } from 'svelte';
7
7
  const { controller, data } = $props();
8
8
  const loc = new ImageEditorDialogLocalization();
9
- const cropper = untrack(() => {
10
- return new ImgCropper({
11
- mode: data.mode ?? 'cover',
12
- aspectRatio: data.aspectRatio,
13
- fillColor: data.fillColor,
14
- showImageShadow: data.showImageShadow ?? true
15
- });
16
- });
9
+ const cropper = untrack(() => new ImgCropper({
10
+ mode: data.mode ?? 'cover',
11
+ aspectRatio: data.aspectRatio,
12
+ fillColor: data.fillColor
13
+ }));
17
14
  const url = $derived(data.url);
18
15
  const save = async () => {
19
16
  try {
@@ -47,24 +44,18 @@ $effect(() => untrack(() => {
47
44
  {loc.title}
48
45
  {/snippet}
49
46
  {#snippet bodySection()}
50
- <div class="image-editor-dialog__body">
51
- <div class="image-editor-dialog__cropper-wrapper">
52
- <ImgCropperView src={url} cropper={cropper} showControls={false} />
53
- </div>
54
- <div class="image-editor-dialog__controls">
55
- <ImgCropperControls cropper={cropper} />
56
- </div>
47
+ <div class="image-editor-dialog__workspace">
48
+ <ImgCropperView src={url} cropper={cropper}>
49
+ {#snippet toolbar()}
50
+ <ImgCropperToolbar cropper={cropper} />
51
+ {/snippet}
52
+ </ImgCropperView>
57
53
  </div>
58
54
  {/snippet}
59
55
  {#snippet footer()}
60
56
  <div class="image-editor-dialog__footer">
61
- <div class="image-editor-dialog__toolbar">
62
- <ImgCropperToolbar cropper={cropper} />
63
- </div>
64
- <div class="image-editor-dialog__buttons">
65
- <DialogCancelButton on={{ click: cancel }}>{loc.cancel}</DialogCancelButton>
66
- <DialogButton on={{ click: save }}>{loc.save}</DialogButton>
67
- </div>
57
+ <DialogCancelButton on={{ click: cancel }}>{loc.cancel}</DialogCancelButton>
58
+ <DialogButton on={{ click: save }}>{loc.save}</DialogButton>
68
59
  </div>
69
60
  {/snippet}
70
61
  </Dialog>
@@ -74,37 +65,12 @@ $effect(() => untrack(() => {
74
65
  --sc-kit--dialog--height: calc(100vh - 2.5rem);
75
66
  display: contents;
76
67
  }
77
- .image-editor-dialog__body {
68
+ .image-editor-dialog__workspace {
78
69
  flex: 1;
79
- position: relative;
80
- background: #dadada;
81
- overflow: hidden;
82
- }
83
- .image-editor-dialog__cropper-wrapper {
84
- position: absolute;
85
- inset: 3rem;
86
- }
87
- .image-editor-dialog__controls {
88
- position: absolute;
89
- bottom: 0.3125rem;
90
- left: 50%;
91
- transform: translateX(-50%);
92
- z-index: 10;
70
+ min-block-size: 0;
93
71
  }
94
72
  .image-editor-dialog__footer {
95
- container-type: inline-size;
96
- display: flex;
97
- flex: 1;
98
- justify-content: space-between;
99
- align-items: center;
100
- }
101
- .image-editor-dialog__toolbar {
102
- flex: 1;
103
- min-width: 0;
104
- }
105
- .image-editor-dialog__buttons {
106
- white-space: nowrap;
107
73
  display: flex;
74
+ justify-content: flex-end;
108
75
  gap: 1.5rem;
109
- margin-left: 1rem;
110
76
  }</style>
@@ -1,11 +1,11 @@
1
1
  import type { FileWithBlobUrl } from '../../../core/files';
2
2
  import type { ImageEditorDynamicAspectRatio } from './types';
3
3
  export type CropAndResizeOptions = {
4
- /** Fixed aspect ratio, or dynamic selector, or omit for free crop. */
4
+ /** Fixed aspect ratio, or dynamic selector, or omit to follow the picture's own. */
5
5
  aspectRatio?: number | ImageEditorDynamicAspectRatio;
6
- /** Crop/scale mode inside the editor canvas. @default 'cover' */
6
+ /** @default 'cover' */
7
7
  mode?: 'contain' | 'cover';
8
- /** Fill color in `contain` mode (transparent letterbox by default). */
8
+ /** Fill color in `contain` mode (transparent by default). */
9
9
  fillColor?: string;
10
10
  /**
11
11
  * After cropping, downscale to fit these limits. `max1` / `max2` are not tied to width /
@@ -18,19 +18,7 @@ export type CropAndResizeOptions = {
18
18
  } | false;
19
19
  };
20
20
  /**
21
- * Opens the kit's image editor dialog, lets the user crop, and (optionally) downscales the
22
- * result before returning. Composable opt-in helper — call it explicitly from a consumer's
23
- * select handler when image cropping is needed before upload.
24
- *
21
+ * Opens the kit's image editor dialog, lets the user crop, and (optionally) downscales the result before returning.
25
22
  * Returns `null` when the user cancels the dialog.
26
- *
27
- * @example
28
- * ```ts
29
- * const cropped = await cropAndResizeImage(file, {
30
- * aspectRatio: 1,
31
- * resize: { max1: 400, max2: 400 }
32
- * });
33
- * if (cropped) store.add(cropped);
34
- * ```
35
23
  */
36
24
  export declare const cropAndResizeImage: (file: File, options?: CropAndResizeOptions) => Promise<FileWithBlobUrl | null>;
@@ -2,20 +2,8 @@ import { FileWithBlobDataHelper, resizeBlob } from '../../../core/files';
2
2
  import { openImageEditorDialog } from './index';
3
3
  const DEFAULT_RESIZE_LIMITS = { max1: 1600, max2: 1000 };
4
4
  /**
5
- * Opens the kit's image editor dialog, lets the user crop, and (optionally) downscales the
6
- * result before returning. Composable opt-in helper — call it explicitly from a consumer's
7
- * select handler when image cropping is needed before upload.
8
- *
5
+ * Opens the kit's image editor dialog, lets the user crop, and (optionally) downscales the result before returning.
9
6
  * Returns `null` when the user cancels the dialog.
10
- *
11
- * @example
12
- * ```ts
13
- * const cropped = await cropAndResizeImage(file, {
14
- * aspectRatio: 1,
15
- * resize: { max1: 400, max2: 400 }
16
- * });
17
- * if (cropped) store.add(cropped);
18
- * ```
19
7
  */
20
8
  export const cropAndResizeImage = async (file, options = {}) => {
21
9
  const sourceUrl = URL.createObjectURL(file);
@@ -35,7 +23,6 @@ export const cropAndResizeImage = async (file, options = {}) => {
35
23
  }
36
24
  const limits = options.resize ?? DEFAULT_RESIZE_LIMITS;
37
25
  const resizedBlob = await resizeBlob(cropped, limits);
38
- // Already-fitting blob? `resizeBlob` returns the same instance — short-circuit.
39
26
  if (resizedBlob === cropped) {
40
27
  return cropped;
41
28
  }
@@ -1,21 +1,22 @@
1
1
  import { type DialogResult } from '../../dialog';
2
2
  import type { ImageEditorDialogOptions, ImageEditorDialogResult } from './types';
3
3
  /**
4
- * Opens a full-screen image editor dialog with crop, rotate, zoom, and reset controls.
4
+ * Opens a full-screen image editor dialog: drag the picture inside a fixed frame, scale it from the corners, zoom,
5
+ * rotate, align, and reset.
5
6
  *
6
7
  * ### Modes
7
- * - **`cover`** (default) — image fills the canvas; crop selects a visible region.
8
- * - **`contain`** — image is fitted inside the canvas with optional fill color.
8
+ * - **`cover`** (default) — the picture always fills the frame; nothing outside the frame is exported.
9
+ * - **`contain`** — the picture is fitted inside the frame with an optional fill color for the remaining space.
9
10
  *
10
11
  * ### Aspect ratio
11
12
  * - `number` — fixed ratio, no popover.
12
- * - `ImageEditorDynamicAspectRatio` — popover with supported ratios. `allowFreeCrop: true`
13
- * adds a free-ratio option; `initial` is optional when free crop is enabled.
14
- * - Omitted — free crop (no ratio constraint on the selection).
13
+ * - `ImageEditorDynamicAspectRatio` — popover with supported ratios. `allowFreeCrop: true` adds the picture's own
14
+ * ratio as a choice; `initial` is optional when it's enabled.
15
+ * - Omitted — the frame follows the picture's own aspect ratio.
15
16
  *
16
17
  * ### Result
17
18
  * - `croppedFile` — `FileWithBlobUrl` with `width`/`height` at natural resolution.
18
- * - `selectedRatio` — active aspect ratio at save time (`null` for free crop).
19
+ * - `selectedRatio` — the active aspect ratio at save time (`null` when following the picture's own).
19
20
  */
20
21
  export declare const openImageEditorDialog: (options: ImageEditorDialogOptions) => Promise<DialogResult<ImageEditorDialogResult>>;
21
22
  export type { CroppedFile, ImageEditorDialogOptions, ImageEditorDialogResult, ImageEditorDynamicAspectRatio } from './types';
@@ -1,21 +1,22 @@
1
1
  import { Dialogs } from '../../dialog';
2
2
  import { default as ImageEditorDialog } from './cmp.image-editor-dialog.svelte';
3
3
  /**
4
- * Opens a full-screen image editor dialog with crop, rotate, zoom, and reset controls.
4
+ * Opens a full-screen image editor dialog: drag the picture inside a fixed frame, scale it from the corners, zoom,
5
+ * rotate, align, and reset.
5
6
  *
6
7
  * ### Modes
7
- * - **`cover`** (default) — image fills the canvas; crop selects a visible region.
8
- * - **`contain`** — image is fitted inside the canvas with optional fill color.
8
+ * - **`cover`** (default) — the picture always fills the frame; nothing outside the frame is exported.
9
+ * - **`contain`** — the picture is fitted inside the frame with an optional fill color for the remaining space.
9
10
  *
10
11
  * ### Aspect ratio
11
12
  * - `number` — fixed ratio, no popover.
12
- * - `ImageEditorDynamicAspectRatio` — popover with supported ratios. `allowFreeCrop: true`
13
- * adds a free-ratio option; `initial` is optional when free crop is enabled.
14
- * - Omitted — free crop (no ratio constraint on the selection).
13
+ * - `ImageEditorDynamicAspectRatio` — popover with supported ratios. `allowFreeCrop: true` adds the picture's own
14
+ * ratio as a choice; `initial` is optional when it's enabled.
15
+ * - Omitted — the frame follows the picture's own aspect ratio.
15
16
  *
16
17
  * ### Result
17
18
  * - `croppedFile` — `FileWithBlobUrl` with `width`/`height` at natural resolution.
18
- * - `selectedRatio` — active aspect ratio at save time (`null` for free crop).
19
+ * - `selectedRatio` — the active aspect ratio at save time (`null` when following the picture's own).
19
20
  */
20
21
  export const openImageEditorDialog = async (options) => {
21
22
  return await Dialogs.open({
@@ -9,9 +9,9 @@ export type ImageEditorDynamicAspectRatio = {
9
9
  allowFreeCrop: true;
10
10
  };
11
11
  export type ImageEditorDialogOptions = {
12
+ /** The picture to edit — same-origin, a `blob:` / `data:` URL, or a server that sends CORS headers; anything else fails to load. */
12
13
  url: string;
13
14
  fillColor?: string;
14
- showImageShadow?: boolean;
15
15
  mode?: 'contain' | 'cover';
16
16
  aspectRatio?: number | ImageEditorDynamicAspectRatio;
17
17
  };