@streamscloud/kit 0.22.0 → 0.23.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.
- package/dist/ui/fit-box/cmp.fit-box.svelte +46 -0
- package/dist/ui/fit-box/cmp.fit-box.svelte.d.ts +42 -0
- package/dist/ui/fit-box/contain-fit.d.ts +8 -0
- package/dist/ui/fit-box/contain-fit.js +47 -0
- package/dist/ui/fit-box/index.d.ts +2 -0
- package/dist/ui/fit-box/index.js +1 -0
- package/dist/ui/fit-box/types.d.ts +2 -0
- package/dist/ui/fit-box/types.js +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">import { containFit } from './contain-fit';
|
|
2
|
+
const { basis = 'box', allowUpscale = false, alignInline = 'center', alignBlock = 'center', children } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="fit-box">
|
|
6
|
+
<div class="fit-box__scaler" class:fit-box__scaler--natural={basis === 'content'} use:containFit={{ allowUpscale, alignInline, alignBlock }}>
|
|
7
|
+
{@render children()}
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<!--
|
|
12
|
+
@component
|
|
13
|
+
Scales its content as one unit to fit the box like `object-fit: contain` — proportions preserved, bounded by both the box's width and
|
|
14
|
+
height, no crop, no scroll. A generic fit primitive for **cards and media content** (a card, an image/video, a fixed-size widget), not a
|
|
15
|
+
page-layout tool.
|
|
16
|
+
|
|
17
|
+
How it works: the box is `FitBox`'s **parent**, which must have a **definite size** (fixed / `%` / grid-track / aspect-ratio+width — a
|
|
18
|
+
content-driven `height: auto` parent collapses and nothing shows). The content is measured, then transform-scaled by
|
|
19
|
+
`min(boxW / contentW, boxH / contentH)` and positioned; a `ResizeObserver` re-fits on any box or content change.
|
|
20
|
+
|
|
21
|
+
Pick the mode with `basis`:
|
|
22
|
+
- `basis="box"` (default) — fluid, width-driven content (cards, text, single-aspect media). Fills the box width, scales down to fit the
|
|
23
|
+
height. Never enlarges.
|
|
24
|
+
- `basis="content"` — intrinsic content (`<img>` / `<video>`, fixed-size widgets). Keeps the content's natural size and fits both axes;
|
|
25
|
+
set `allowUpscale` to also enlarge it to fill.
|
|
26
|
+
|
|
27
|
+
`alignInline` / `alignBlock` place the content along an axis when fitting leaves free space there. Requires client-side JS (scales via
|
|
28
|
+
`transform`, so `position: fixed` descendants are relative to the box); content with a hard fixed width wider than the box is clipped
|
|
29
|
+
rather than contained.
|
|
30
|
+
-->
|
|
31
|
+
|
|
32
|
+
<style>.fit-box {
|
|
33
|
+
position: relative;
|
|
34
|
+
inline-size: 100%;
|
|
35
|
+
block-size: 100%;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
}
|
|
38
|
+
.fit-box__scaler {
|
|
39
|
+
position: absolute;
|
|
40
|
+
inline-size: 100%;
|
|
41
|
+
max-inline-size: 100%;
|
|
42
|
+
visibility: hidden;
|
|
43
|
+
}
|
|
44
|
+
.fit-box__scaler--natural {
|
|
45
|
+
inline-size: auto;
|
|
46
|
+
}</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { FitAlign, FitBasis } from './types';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
type Props = {
|
|
4
|
+
/**
|
|
5
|
+
* What the content's width is based on before it is scaled to fit:
|
|
6
|
+
* - `'box'` — content is laid out at the **box width** first (use for fluid, width-driven content: a card, a text block, single-aspect
|
|
7
|
+
* media). Height follows from that width; the content is then scaled **down** only if too tall. Never enlarges.
|
|
8
|
+
* - `'content'` — content keeps its **own natural size** (use for intrinsic content: `<img>` / `<video>`, a fixed-size widget). It is
|
|
9
|
+
* scaled to fit **both** axes and may be enlarged when `allowUpscale` is set.
|
|
10
|
+
* @default 'box'
|
|
11
|
+
*/
|
|
12
|
+
basis?: FitBasis;
|
|
13
|
+
/** Allow scaling content up past its natural size to fill the box. Takes effect only with `basis="content"` (`'box'` never enlarges). @default false */
|
|
14
|
+
allowUpscale?: boolean;
|
|
15
|
+
/** Inline-axis (horizontal) placement of the fitted content, visible only when free width remains after fitting. @default 'center' */
|
|
16
|
+
alignInline?: FitAlign;
|
|
17
|
+
/** Block-axis (vertical) placement of the fitted content, visible only when free height remains after fitting. @default 'center' */
|
|
18
|
+
alignBlock?: FitAlign;
|
|
19
|
+
children: Snippet;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Scales its content as one unit to fit the box like `object-fit: contain` — proportions preserved, bounded by both the box's width and
|
|
23
|
+
* height, no crop, no scroll. A generic fit primitive for **cards and media content** (a card, an image/video, a fixed-size widget), not a
|
|
24
|
+
* page-layout tool.
|
|
25
|
+
*
|
|
26
|
+
* How it works: the box is `FitBox`'s **parent**, which must have a **definite size** (fixed / `%` / grid-track / aspect-ratio+width — a
|
|
27
|
+
* content-driven `height: auto` parent collapses and nothing shows). The content is measured, then transform-scaled by
|
|
28
|
+
* `min(boxW / contentW, boxH / contentH)` and positioned; a `ResizeObserver` re-fits on any box or content change.
|
|
29
|
+
*
|
|
30
|
+
* Pick the mode with `basis`:
|
|
31
|
+
* - `basis="box"` (default) — fluid, width-driven content (cards, text, single-aspect media). Fills the box width, scales down to fit the
|
|
32
|
+
* height. Never enlarges.
|
|
33
|
+
* - `basis="content"` — intrinsic content (`<img>` / `<video>`, fixed-size widgets). Keeps the content's natural size and fits both axes;
|
|
34
|
+
* set `allowUpscale` to also enlarge it to fill.
|
|
35
|
+
*
|
|
36
|
+
* `alignInline` / `alignBlock` place the content along an axis when fitting leaves free space there. Requires client-side JS (scales via
|
|
37
|
+
* `transform`, so `position: fixed` descendants are relative to the box); content with a hard fixed width wider than the box is clipped
|
|
38
|
+
* rather than contained.
|
|
39
|
+
*/
|
|
40
|
+
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
41
|
+
type Cmp = ReturnType<typeof Cmp>;
|
|
42
|
+
export default Cmp;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FitAlign } from './types';
|
|
2
|
+
import type { Action } from 'svelte/action';
|
|
3
|
+
export type ContainFitParams = {
|
|
4
|
+
allowUpscale?: boolean;
|
|
5
|
+
alignInline?: FitAlign;
|
|
6
|
+
alignBlock?: FitAlign;
|
|
7
|
+
};
|
|
8
|
+
export declare const containFit: Action<HTMLElement, ContainFitParams | undefined>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const offset = (align, free) => (align === 'start' ? 0 : align === 'end' ? free : free / 2);
|
|
2
|
+
export const containFit = (node, params = {}) => {
|
|
3
|
+
const container = node.parentElement;
|
|
4
|
+
let allowUpscale = params.allowUpscale ?? false;
|
|
5
|
+
let alignInline = params.alignInline ?? 'center';
|
|
6
|
+
let alignBlock = params.alignBlock ?? 'center';
|
|
7
|
+
const apply = () => {
|
|
8
|
+
if (!container) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const boxWidth = container.clientWidth;
|
|
12
|
+
const boxHeight = container.clientHeight;
|
|
13
|
+
const naturalWidth = node.offsetWidth;
|
|
14
|
+
const naturalHeight = node.offsetHeight;
|
|
15
|
+
if (!boxWidth || !boxHeight || !naturalWidth || !naturalHeight) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let scale = Math.min(boxWidth / naturalWidth, boxHeight / naturalHeight);
|
|
19
|
+
if (!allowUpscale) {
|
|
20
|
+
scale = Math.min(scale, 1);
|
|
21
|
+
}
|
|
22
|
+
const left = offset(alignInline, boxWidth - naturalWidth * scale);
|
|
23
|
+
const top = offset(alignBlock, boxHeight - naturalHeight * scale);
|
|
24
|
+
node.style.transformOrigin = '0 0';
|
|
25
|
+
node.style.transform = `scale(${scale})`;
|
|
26
|
+
node.style.left = `${left}px`;
|
|
27
|
+
node.style.top = `${top}px`;
|
|
28
|
+
node.style.visibility = 'visible';
|
|
29
|
+
};
|
|
30
|
+
const observer = new ResizeObserver(apply);
|
|
31
|
+
if (container) {
|
|
32
|
+
observer.observe(container);
|
|
33
|
+
}
|
|
34
|
+
observer.observe(node);
|
|
35
|
+
apply();
|
|
36
|
+
return {
|
|
37
|
+
update(next = {}) {
|
|
38
|
+
allowUpscale = next.allowUpscale ?? false;
|
|
39
|
+
alignInline = next.alignInline ?? 'center';
|
|
40
|
+
alignBlock = next.alignBlock ?? 'center';
|
|
41
|
+
apply();
|
|
42
|
+
},
|
|
43
|
+
destroy() {
|
|
44
|
+
observer.disconnect();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as FitBox } from './cmp.fit-box.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamscloud/kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"author": "StreamsCloud",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -195,6 +195,10 @@
|
|
|
195
195
|
"types": "./dist/ui/file-uploader/index.d.ts",
|
|
196
196
|
"svelte": "./dist/ui/file-uploader/index.js"
|
|
197
197
|
},
|
|
198
|
+
"./ui/fit-box": {
|
|
199
|
+
"types": "./dist/ui/fit-box/index.d.ts",
|
|
200
|
+
"svelte": "./dist/ui/fit-box/index.js"
|
|
201
|
+
},
|
|
198
202
|
"./ui/form-field": {
|
|
199
203
|
"types": "./dist/ui/form-field/index.d.ts",
|
|
200
204
|
"svelte": "./dist/ui/form-field/index.js"
|