@streamscloud/kit 0.44.0 → 0.46.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/core/media/image-helper.js +2 -2
- package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte +2 -7
- package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte.d.ts +1 -7
- package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.d.ts +1 -1
- package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.js +14 -6
- package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.js +17 -5
- package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.js +24 -5
- package/dist/ui/cropper/img-cropper/img-cropper-utils.d.ts +12 -0
- package/dist/ui/cropper/img-cropper/img-cropper-utils.js +55 -14
- package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.d.ts +1 -1
- package/dist/ui/grid-card/cmp.grid-card.svelte +1 -1
- package/dist/ui/grid-card/cmp.grid-card.svelte.d.ts +1 -1
- package/dist/ui/grid-card/fields/cmp.grid-card-button.svelte +4 -1
- package/dist/ui/grid-card/fields/cmp.grid-card-field.svelte +10 -4
- package/dist/ui/grid-card/fields/cmp.grid-card-field.svelte.d.ts +4 -2
- package/dist/ui/grid-card/fields/cmp.grid-card-id-field.svelte +2 -1
- package/dist/ui/grid-card/fields/cmp.grid-card-select-field.svelte +73 -0
- package/dist/ui/grid-card/fields/cmp.grid-card-select-field.svelte.d.ts +53 -0
- package/dist/ui/grid-card/fields/cmp.grid-card-text-field.svelte +5 -3
- package/dist/ui/grid-card/fields/index.d.ts +1 -0
- package/dist/ui/grid-card/fields/index.js +1 -0
- package/dist/ui/grid-card/index.d.ts +1 -1
- package/dist/ui/grid-card/index.js +1 -1
- package/dist/ui/select/_select-trigger.scss +2 -1
- package/dist/ui/select/cmp.input-suggest.svelte +2 -2
- package/dist/ui/select/cmp.input-suggest.svelte.d.ts +1 -1
- package/dist/ui/select/cmp.multiselect-async.svelte +1 -1
- package/dist/ui/select/cmp.multiselect-async.svelte.d.ts +1 -1
- package/dist/ui/select/cmp.multiselect-tree.svelte +1 -1
- package/dist/ui/select/cmp.multiselect-tree.svelte.d.ts +1 -1
- package/dist/ui/select/cmp.multiselect.svelte +1 -1
- package/dist/ui/select/cmp.multiselect.svelte.d.ts +1 -1
- package/dist/ui/select/cmp.singleselect-async.svelte +6 -4
- package/dist/ui/select/cmp.singleselect-async.svelte.d.ts +1 -1
- package/dist/ui/select/cmp.singleselect.svelte +1 -1
- package/dist/ui/select/cmp.singleselect.svelte.d.ts +1 -1
- package/dist/ui/select/multiselect-base.svelte +2 -2
- package/dist/ui/select/multiselect-base.svelte.d.ts +1 -1
- package/dist/ui/select/select-listbox.svelte +3 -0
- package/dist/ui/select/select-listbox.svelte.d.ts +1 -0
- package/dist/ui/table/table-cells/table-by-cell.svelte +5 -4
- package/package.json +1 -1
|
@@ -8,11 +8,11 @@ export class ImageHelper {
|
|
|
8
8
|
};
|
|
9
9
|
static computeRatio = async (src) => {
|
|
10
10
|
const setRatio = (imageElement, resolveFn) => {
|
|
11
|
-
if (!imageElement || !imageElement.
|
|
11
|
+
if (!imageElement || !imageElement.naturalWidth) {
|
|
12
12
|
resolveFn(null);
|
|
13
13
|
}
|
|
14
14
|
else {
|
|
15
|
-
resolveFn({ width: imageElement.
|
|
15
|
+
resolveFn({ width: imageElement.naturalWidth, height: imageElement.naturalHeight });
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
return new Promise((resolve) => {
|
|
@@ -137,13 +137,6 @@ When `showImageShadow` is enabled on the cropper instance, a dark semi-transpare
|
|
|
137
137
|
the canvas bounds so the surrounding area is dimmed. The parent element should set
|
|
138
138
|
`overflow: hidden` to clip the shadow at its boundaries.
|
|
139
139
|
|
|
140
|
-
### Props
|
|
141
|
-
| Prop | Type | Default | Description |
|
|
142
|
-
|---|---|---|---|
|
|
143
|
-
| `src` | `string` | — | Image source URL |
|
|
144
|
-
| `cropper` | `ImgCropper` | — | Cropper controller instance |
|
|
145
|
-
| `showControls` | `boolean` | `true` | Show built-in Apply/Cancel crop controls |
|
|
146
|
-
|
|
147
140
|
### CSS Custom Properties
|
|
148
141
|
None — canvas background, shadow, and sizing are controlled by the `ImgCropper` instance.
|
|
149
142
|
-->
|
|
@@ -161,6 +154,8 @@ None — canvas background, shadow, and sizing are controlled by the `ImgCropper
|
|
|
161
154
|
.img-cropper__canvas {
|
|
162
155
|
width: 100%;
|
|
163
156
|
height: 100%;
|
|
157
|
+
min-width: 0;
|
|
158
|
+
min-height: 0;
|
|
164
159
|
background-color: var(--_fill-color);
|
|
165
160
|
visibility: hidden;
|
|
166
161
|
}
|
|
@@ -2,6 +2,7 @@ import type { ImgCropper } from './img-cropper.svelte';
|
|
|
2
2
|
type Props = {
|
|
3
3
|
src: string;
|
|
4
4
|
cropper: ImgCropper;
|
|
5
|
+
/** Show the built-in Apply/Cancel crop controls @default true */
|
|
5
6
|
showControls?: boolean;
|
|
6
7
|
};
|
|
7
8
|
/**
|
|
@@ -41,13 +42,6 @@ type Props = {
|
|
|
41
42
|
* the canvas bounds so the surrounding area is dimmed. The parent element should set
|
|
42
43
|
* `overflow: hidden` to clip the shadow at its boundaries.
|
|
43
44
|
*
|
|
44
|
-
* ### Props
|
|
45
|
-
* | Prop | Type | Default | Description |
|
|
46
|
-
* |---|---|---|---|
|
|
47
|
-
* | `src` | `string` | — | Image source URL |
|
|
48
|
-
* | `cropper` | `ImgCropper` | — | Cropper controller instance |
|
|
49
|
-
* | `showControls` | `boolean` | `true` | Show built-in Apply/Cancel crop controls |
|
|
50
|
-
*
|
|
51
45
|
* ### CSS Custom Properties
|
|
52
46
|
* None — canvas background, shadow, and sizing are controlled by the `ImgCropper` instance.
|
|
53
47
|
*/
|
|
@@ -22,7 +22,7 @@ export declare abstract class ImgCropperBaseWorker implements ImgCropperWorker {
|
|
|
22
22
|
reset: () => Promise<void>;
|
|
23
23
|
crop: (options?: {
|
|
24
24
|
fillColor?: string;
|
|
25
|
-
}) => Promise<ImgCropperResult>;
|
|
25
|
+
}) => Promise<ImgCropperResult | null>;
|
|
26
26
|
save: (options?: {
|
|
27
27
|
fillColor?: string;
|
|
28
28
|
}) => Promise<ImgCropperResult>;
|
|
@@ -24,7 +24,8 @@ export class ImgCropperBaseWorker {
|
|
|
24
24
|
this._selectHandle = params.selectHandle;
|
|
25
25
|
this.canvasGeometry = { aspectRatio: params.aspectRatio, width: 0, height: 0 };
|
|
26
26
|
const onSelectionChange = (e) => {
|
|
27
|
-
|
|
27
|
+
// CropperSelection emits before committing, so a change the boundary handler cancelled never reaches the selection.
|
|
28
|
+
const { width, height } = e.defaultPrevented ? this._selection : e.detail;
|
|
28
29
|
this.cropBoxVisible = width > 0 && height > 0;
|
|
29
30
|
};
|
|
30
31
|
let handlingBoundary = false;
|
|
@@ -40,8 +41,9 @@ export class ImgCropperBaseWorker {
|
|
|
40
41
|
handlingBoundary = false;
|
|
41
42
|
}
|
|
42
43
|
};
|
|
43
|
-
|
|
44
|
+
// Boundary check first — onSelectionChange reads defaultPrevented, which it must not set itself.
|
|
44
45
|
this._selection.addEventListener('change', onBoundaryCheck);
|
|
46
|
+
this._selection.addEventListener('change', onSelectionChange);
|
|
45
47
|
this.destroy = () => {
|
|
46
48
|
this._selection.removeEventListener('change', onSelectionChange);
|
|
47
49
|
this._selection.removeEventListener('change', onBoundaryCheck);
|
|
@@ -75,6 +77,10 @@ export class ImgCropperBaseWorker {
|
|
|
75
77
|
await this._fitImage();
|
|
76
78
|
};
|
|
77
79
|
crop = async (options) => {
|
|
80
|
+
// An empty selection exports a 0x0 canvas, and assigning its "data:," data URL leaves the cropper with an unloadable source.
|
|
81
|
+
if (this._selection.width <= 0 || this._selection.height <= 0) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
78
84
|
const fillColor = options?.fillColor;
|
|
79
85
|
const imageScale = computeImageScale(this._image.$getTransform());
|
|
80
86
|
const outputSize = computeNaturalOutputSize({ displayWidth: this._selection.width, displayHeight: this._selection.height, imageScale });
|
|
@@ -107,8 +113,10 @@ export class ImgCropperBaseWorker {
|
|
|
107
113
|
// Compute output dimensions before micro-zoom so the result matches
|
|
108
114
|
// the source pixel density, not the display size.
|
|
109
115
|
const imageScale = computeImageScale(this._image.$getTransform());
|
|
110
|
-
|
|
111
|
-
const
|
|
116
|
+
// A zero-sized selection exports a 0x0 canvas, whose toDataURL is the unusable "data:," string.
|
|
117
|
+
const hasCropBox = this._selection.width > 0 && this._selection.height > 0;
|
|
118
|
+
const displayWidth = hasCropBox ? this._selection.width : this._canvas.offsetWidth;
|
|
119
|
+
const displayHeight = hasCropBox ? this._selection.height : this._canvas.offsetHeight;
|
|
112
120
|
const outputSize = computeNaturalOutputSize({ displayWidth, displayHeight, imageScale });
|
|
113
121
|
const toCanvasOptions = { width: outputSize.width, height: outputSize.height, beforeDraw };
|
|
114
122
|
// CropperJS $toCanvas exports only the visible portion of the canvas.
|
|
@@ -116,12 +124,12 @@ export class ImgCropperBaseWorker {
|
|
|
116
124
|
// tightly fits the canvas. A micro-zoom nudges the image slightly past
|
|
117
125
|
// the boundary so the export captures the full area; the zoom is
|
|
118
126
|
// reversed immediately after.
|
|
119
|
-
const needsMicroZoom = !
|
|
127
|
+
const needsMicroZoom = !hasCropBox;
|
|
120
128
|
if (needsMicroZoom) {
|
|
121
129
|
// Forward zoom increases coverage — safe without suspending cover check
|
|
122
130
|
this._image.$zoom(0.003);
|
|
123
131
|
}
|
|
124
|
-
const canvas =
|
|
132
|
+
const canvas = hasCropBox ? await this._selection.$toCanvas(toCanvasOptions) : await this._canvas.$toCanvas(toCanvasOptions);
|
|
125
133
|
if (needsMicroZoom) {
|
|
126
134
|
// Reverse zoom shrinks image — must suspend cover check to avoid clamping
|
|
127
135
|
this._wrapTransformOp(() => this._image.$zoom(-0.003));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ImgCropperBaseWorker } from './img-cropper-base-worker.svelte';
|
|
2
|
-
import { fitInContainer } from './img-cropper-utils';
|
|
2
|
+
import { fitInContainer, resolveCanvasGeometry } from './img-cropper-utils';
|
|
3
3
|
export class ImgCropperContainWorker extends ImgCropperBaseWorker {
|
|
4
4
|
_centerMode = 'contain';
|
|
5
5
|
constructor(params) {
|
|
@@ -24,16 +24,28 @@ export class ImgCropperContainWorker extends ImgCropperBaseWorker {
|
|
|
24
24
|
const fitted = fitInContainer({ ratio: aspectRatio, containerWidth, containerHeight });
|
|
25
25
|
const maxWidth = Math.max(visualWidth, visualHeight * aspectRatio);
|
|
26
26
|
const width = Math.min(fitted.width, maxWidth);
|
|
27
|
-
this.canvasGeometry = { aspectRatio, width, height: width / aspectRatio };
|
|
27
|
+
this.canvasGeometry = resolveCanvasGeometry({ aspectRatio, width, height: width / aspectRatio, maxWidth: fitted.width, maxHeight: containerHeight });
|
|
28
28
|
}
|
|
29
29
|
else if (visualWidth <= containerWidth && visualHeight <= containerHeight) {
|
|
30
|
-
|
|
31
|
-
this.canvasGeometry = {
|
|
30
|
+
const fitted = fitInContainer({ ratio: visualWidth / visualHeight, containerWidth, containerHeight });
|
|
31
|
+
this.canvasGeometry = resolveCanvasGeometry({
|
|
32
|
+
aspectRatio,
|
|
33
|
+
width: visualWidth,
|
|
34
|
+
height: visualHeight,
|
|
35
|
+
maxWidth: fitted.width,
|
|
36
|
+
maxHeight: containerHeight
|
|
37
|
+
});
|
|
32
38
|
}
|
|
33
39
|
else {
|
|
34
40
|
// Free ratio, image too large — scale down to fit
|
|
35
41
|
const fitted = fitInContainer({ ratio: visualWidth / visualHeight, containerWidth, containerHeight });
|
|
36
|
-
this.canvasGeometry = {
|
|
42
|
+
this.canvasGeometry = resolveCanvasGeometry({
|
|
43
|
+
aspectRatio,
|
|
44
|
+
width: fitted.width,
|
|
45
|
+
height: fitted.height,
|
|
46
|
+
maxWidth: fitted.width,
|
|
47
|
+
maxHeight: containerHeight
|
|
48
|
+
});
|
|
37
49
|
}
|
|
38
50
|
};
|
|
39
51
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ImgCropperBaseWorker } from './img-cropper-base-worker.svelte';
|
|
2
|
-
import { fitInContainer, handleCoverCheck } from './img-cropper-utils';
|
|
2
|
+
import { fitInContainer, handleCoverCheck, resolveCanvasGeometry } from './img-cropper-utils';
|
|
3
3
|
export class ImgCropperCoverWorker extends ImgCropperBaseWorker {
|
|
4
4
|
_centerMode = 'cover';
|
|
5
5
|
constructor(params) {
|
|
@@ -53,18 +53,37 @@ export class ImgCropperCoverWorker extends ImgCropperBaseWorker {
|
|
|
53
53
|
// Cap so the image can cover the canvas without upscaling
|
|
54
54
|
const maxWidth = visualWidth > 0 && visualHeight > 0 ? Math.min(visualWidth, visualHeight * aspectRatio) : fitted.width;
|
|
55
55
|
const width = Math.min(fitted.width, maxWidth);
|
|
56
|
-
this.canvasGeometry = { aspectRatio, width, height: width / aspectRatio };
|
|
56
|
+
this.canvasGeometry = resolveCanvasGeometry({ aspectRatio, width, height: width / aspectRatio, maxWidth: fitted.width, maxHeight: containerHeight });
|
|
57
57
|
}
|
|
58
58
|
else if (visualWidth <= 0 || visualHeight <= 0) {
|
|
59
|
-
this.canvasGeometry = {
|
|
59
|
+
this.canvasGeometry = resolveCanvasGeometry({
|
|
60
|
+
aspectRatio,
|
|
61
|
+
width: containerWidth,
|
|
62
|
+
height: containerHeight,
|
|
63
|
+
maxWidth: containerWidth,
|
|
64
|
+
maxHeight: containerHeight
|
|
65
|
+
});
|
|
60
66
|
}
|
|
61
67
|
else if (visualWidth <= containerWidth && visualHeight <= containerHeight) {
|
|
62
|
-
|
|
68
|
+
const fitted = fitInContainer({ ratio: visualWidth / visualHeight, containerWidth, containerHeight });
|
|
69
|
+
this.canvasGeometry = resolveCanvasGeometry({
|
|
70
|
+
aspectRatio,
|
|
71
|
+
width: visualWidth,
|
|
72
|
+
height: visualHeight,
|
|
73
|
+
maxWidth: fitted.width,
|
|
74
|
+
maxHeight: containerHeight
|
|
75
|
+
});
|
|
63
76
|
}
|
|
64
77
|
else {
|
|
65
78
|
// Fit at image AR; per-axis capping collapses an oversized image to the container AR.
|
|
66
79
|
const fitted = fitInContainer({ ratio: visualWidth / visualHeight, containerWidth, containerHeight });
|
|
67
|
-
this.canvasGeometry = {
|
|
80
|
+
this.canvasGeometry = resolveCanvasGeometry({
|
|
81
|
+
aspectRatio,
|
|
82
|
+
width: fitted.width,
|
|
83
|
+
height: fitted.height,
|
|
84
|
+
maxWidth: fitted.width,
|
|
85
|
+
maxHeight: containerHeight
|
|
86
|
+
});
|
|
68
87
|
}
|
|
69
88
|
};
|
|
70
89
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CanvasGeometry } from './img-cropper-worker.svelte';
|
|
1
2
|
import type { CropperCanvas, CropperImage, CropperSelection } from 'cropperjs';
|
|
2
3
|
type Rect = {
|
|
3
4
|
left: number;
|
|
@@ -34,6 +35,17 @@ export declare const fitInContainer: (params: {
|
|
|
34
35
|
width: number;
|
|
35
36
|
height: number;
|
|
36
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Applies the minimum canvas size by scaling the whole rect, bounded by `maxWidth` / `maxHeight`, and rounds to whole pixels.
|
|
40
|
+
* The minimums are soft: a container smaller than them wins, and the returned canvas is smaller than `MIN_CANVAS_WIDTH` / `MIN_CANVAS_HEIGHT`.
|
|
41
|
+
*/
|
|
42
|
+
export declare const resolveCanvasGeometry: (params: {
|
|
43
|
+
aspectRatio: number | null;
|
|
44
|
+
width: number;
|
|
45
|
+
height: number;
|
|
46
|
+
maxWidth: number;
|
|
47
|
+
maxHeight: number;
|
|
48
|
+
}) => CanvasGeometry;
|
|
37
49
|
export declare const handleSelectionBoundary: (params: {
|
|
38
50
|
event: Event;
|
|
39
51
|
selection: CropperSelection;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Base64Helper } from '../../../core/files/base64-helper';
|
|
2
2
|
import { default as mime } from 'mime';
|
|
3
|
+
const COVERAGE_TOLERANCE = 0.5;
|
|
4
|
+
const SHRINK_THRESHOLD = 0.5;
|
|
3
5
|
// Detect MIME type from a URL without fetching the resource.
|
|
4
6
|
// Returns a MIME string for data: URLs, extension-based URLs, or null for blob: / unknown URLs.
|
|
5
7
|
export const detectMimeFromUrl = (url) => {
|
|
@@ -84,35 +86,56 @@ export const handleCoverCheck = (params) => {
|
|
|
84
86
|
right: currentRect.right + (newAABB.right - oldAABB.right),
|
|
85
87
|
bottom: currentRect.bottom + (newAABB.bottom - oldAABB.bottom)
|
|
86
88
|
};
|
|
87
|
-
//
|
|
88
|
-
if (rectCoversRect(proposedRect, canvasRect,
|
|
89
|
+
// Sub-pixel tolerance: a stricter check re-enters on the clamped transform this handler issues itself.
|
|
90
|
+
if (rectCoversRect(proposedRect, canvasRect, COVERAGE_TOLERANCE)) {
|
|
89
91
|
return;
|
|
90
92
|
}
|
|
91
93
|
event.preventDefault();
|
|
92
|
-
// If the proposed image is still large enough to cover the canvas, clamp
|
|
93
|
-
// translation to maintain coverage. Otherwise block the transform entirely.
|
|
94
94
|
const proposedWidth = proposedRect.right - proposedRect.left;
|
|
95
95
|
const proposedHeight = proposedRect.bottom - proposedRect.top;
|
|
96
96
|
const canvasWidth = canvasRect.right - canvasRect.left;
|
|
97
97
|
const canvasHeight = canvasRect.bottom - canvasRect.top;
|
|
98
|
+
const oldAABBWidth = oldAABB.right - oldAABB.left;
|
|
99
|
+
const oldAABBHeight = oldAABB.bottom - oldAABB.top;
|
|
100
|
+
const newAABBWidth = newAABB.right - newAABB.left;
|
|
101
|
+
const newAABBHeight = newAABB.bottom - newAABB.top;
|
|
102
|
+
const shrinksImage = newAABBWidth < oldAABBWidth - SHRINK_THRESHOLD || newAABBHeight < oldAABBHeight - SHRINK_THRESHOLD;
|
|
103
|
+
let [scaleX, skewY, skewX, scaleY] = matrix;
|
|
104
|
+
let clampedRect = proposedRect;
|
|
98
105
|
if (proposedWidth < canvasWidth - 0.5 || proposedHeight < canvasHeight - 0.5) {
|
|
99
|
-
|
|
106
|
+
// Rescaling anything but a shrink would turn a pan, whose proposal is the same size, into an upscale.
|
|
107
|
+
if (!shrinksImage) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const coverScale = Math.max(canvasWidth / proposedWidth, canvasHeight / proposedHeight);
|
|
111
|
+
if (!Number.isFinite(coverScale)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
scaleX *= coverScale;
|
|
115
|
+
skewY *= coverScale;
|
|
116
|
+
skewX *= coverScale;
|
|
117
|
+
scaleY *= coverScale;
|
|
118
|
+
const halfWidth = (proposedWidth * coverScale) / 2;
|
|
119
|
+
const halfHeight = (proposedHeight * coverScale) / 2;
|
|
120
|
+
const centerX = (proposedRect.left + proposedRect.right) / 2;
|
|
121
|
+
const centerY = (proposedRect.top + proposedRect.bottom) / 2;
|
|
122
|
+
clampedRect = { left: centerX - halfWidth, top: centerY - halfHeight, right: centerX + halfWidth, bottom: centerY + halfHeight };
|
|
100
123
|
}
|
|
101
124
|
let clampedTx = matrix[4];
|
|
102
125
|
let clampedTy = matrix[5];
|
|
103
|
-
if (
|
|
104
|
-
clampedTx -=
|
|
126
|
+
if (clampedRect.left > canvasRect.left) {
|
|
127
|
+
clampedTx -= clampedRect.left - canvasRect.left;
|
|
105
128
|
}
|
|
106
|
-
else if (
|
|
107
|
-
clampedTx += canvasRect.right -
|
|
129
|
+
else if (clampedRect.right < canvasRect.right) {
|
|
130
|
+
clampedTx += canvasRect.right - clampedRect.right;
|
|
108
131
|
}
|
|
109
|
-
if (
|
|
110
|
-
clampedTy -=
|
|
132
|
+
if (clampedRect.top > canvasRect.top) {
|
|
133
|
+
clampedTy -= clampedRect.top - canvasRect.top;
|
|
111
134
|
}
|
|
112
|
-
else if (
|
|
113
|
-
clampedTy += canvasRect.bottom -
|
|
135
|
+
else if (clampedRect.bottom < canvasRect.bottom) {
|
|
136
|
+
clampedTy += canvasRect.bottom - clampedRect.bottom;
|
|
114
137
|
}
|
|
115
|
-
image.$setTransform(
|
|
138
|
+
image.$setTransform(scaleX, skewY, skewX, scaleY, clampedTx, clampedTy);
|
|
116
139
|
};
|
|
117
140
|
export const fitInContainer = (params) => {
|
|
118
141
|
const { ratio, containerWidth, containerHeight } = params;
|
|
@@ -121,6 +144,24 @@ export const fitInContainer = (params) => {
|
|
|
121
144
|
}
|
|
122
145
|
return { width: containerHeight * ratio, height: containerHeight };
|
|
123
146
|
};
|
|
147
|
+
// Same limits cropper-canvas declares in its shadow style, where they clamp one axis at a time and break the canvas ratio.
|
|
148
|
+
const MIN_CANVAS_WIDTH = 200;
|
|
149
|
+
const MIN_CANVAS_HEIGHT = 100;
|
|
150
|
+
/**
|
|
151
|
+
* Applies the minimum canvas size by scaling the whole rect, bounded by `maxWidth` / `maxHeight`, and rounds to whole pixels.
|
|
152
|
+
* The minimums are soft: a container smaller than them wins, and the returned canvas is smaller than `MIN_CANVAS_WIDTH` / `MIN_CANVAS_HEIGHT`.
|
|
153
|
+
*/
|
|
154
|
+
export const resolveCanvasGeometry = (params) => {
|
|
155
|
+
const { aspectRatio, width, height, maxWidth, maxHeight } = params;
|
|
156
|
+
if (width <= 0 || height <= 0) {
|
|
157
|
+
return { aspectRatio, width: 0, height: 0 };
|
|
158
|
+
}
|
|
159
|
+
const ratio = width / height;
|
|
160
|
+
const floorScale = Math.max(MIN_CANVAS_WIDTH / width, MIN_CANVAS_HEIGHT / height, 1);
|
|
161
|
+
const boundedWidth = Math.min(width * floorScale, maxWidth, maxHeight * ratio);
|
|
162
|
+
// A zero side reads as "unset" in the template, which falls the canvas back to its CSS size and desyncs it from this geometry.
|
|
163
|
+
return { aspectRatio, width: Math.max(1, Math.round(boundedWidth)), height: Math.max(1, Math.round(boundedWidth / ratio)) };
|
|
164
|
+
};
|
|
124
165
|
export const handleSelectionBoundary = (params) => {
|
|
125
166
|
const { event, selection, canvas } = params;
|
|
126
167
|
const { x, y, width, height } = event.detail;
|
|
@@ -32,7 +32,7 @@ export interface ImgCropperWorker {
|
|
|
32
32
|
reset: () => Promise<void>;
|
|
33
33
|
crop: (options?: {
|
|
34
34
|
fillColor?: string;
|
|
35
|
-
}) => Promise<ImgCropperResult>;
|
|
35
|
+
}) => Promise<ImgCropperResult | null>;
|
|
36
36
|
save: (options?: {
|
|
37
37
|
fillColor?: string;
|
|
38
38
|
}) => Promise<ImgCropperResult>;
|
|
@@ -36,7 +36,7 @@ const onCardKeydown = (e) => {
|
|
|
36
36
|
Media-first grid card primitive for catalog / browser views (ads, products, content lists,
|
|
37
37
|
short-videos). Composable: media in the top slot via `<GridCardMedia>`, key-value rows via
|
|
38
38
|
`<GridCardFields>` containing `<GridCardField>` / `<GridCardTextField>` / `<GridCardIdField>` /
|
|
39
|
-
`<GridCardBadgeField>` / `<GridCardProgressField>` / `<GridCardActionsField>`.
|
|
39
|
+
`<GridCardBadgeField>` / `<GridCardSelectField>` / `<GridCardProgressField>` / `<GridCardActionsField>`.
|
|
40
40
|
|
|
41
41
|
Selection: pass `selected` (and `on.select`) to render a checkbox at top-left. Whole-card
|
|
42
42
|
activation: pass `on.activate` to make the root a keyboard-accessible button (Enter / Space).
|
|
@@ -18,7 +18,7 @@ type Props = {
|
|
|
18
18
|
* Media-first grid card primitive for catalog / browser views (ads, products, content lists,
|
|
19
19
|
* short-videos). Composable: media in the top slot via `<GridCardMedia>`, key-value rows via
|
|
20
20
|
* `<GridCardFields>` containing `<GridCardField>` / `<GridCardTextField>` / `<GridCardIdField>` /
|
|
21
|
-
* `<GridCardBadgeField>` / `<GridCardProgressField>` / `<GridCardActionsField>`.
|
|
21
|
+
* `<GridCardBadgeField>` / `<GridCardSelectField>` / `<GridCardProgressField>` / `<GridCardActionsField>`.
|
|
22
22
|
*
|
|
23
23
|
* Selection: pass `selected` (and `on.select`) to render a checkbox at top-left. Whole-card
|
|
24
24
|
* activation: pass `on.activate` to make the root a keyboard-accessible button (Enter / Space).
|
|
@@ -4,11 +4,14 @@ const handleClick = (e) => {
|
|
|
4
4
|
e.stopPropagation();
|
|
5
5
|
on.click(e);
|
|
6
6
|
};
|
|
7
|
+
// Only the keys GridCard acts on — the card's handler preventDefaults Enter / Space, which would fire activation alongside this button.
|
|
8
|
+
const cardActivationKeys = new Set([' ', 'Enter']);
|
|
7
9
|
</script>
|
|
8
10
|
|
|
9
11
|
{#snippet label()}{text}{/snippet}
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
14
|
+
<span class="grid-card-button" onkeydown={(event) => cardActivationKeys.has(event.key) && event.stopPropagation()}>
|
|
12
15
|
<Button
|
|
13
16
|
type="button"
|
|
14
17
|
variant={variant}
|
|
@@ -12,8 +12,8 @@ export {};
|
|
|
12
12
|
<!--
|
|
13
13
|
@component
|
|
14
14
|
Single label-value row inside a `<GridCardFields>` subgrid. Use directly for custom content,
|
|
15
|
-
or compose via `<GridCardTextField>` / `<GridCardBadgeField>` / `<
|
|
16
|
-
`<GridCardActionsField>` for common patterns. Set `multiline` for top-aligned multi-line
|
|
15
|
+
or compose via `<GridCardTextField>` / `<GridCardBadgeField>` / `<GridCardSelectField>` /
|
|
16
|
+
`<GridCardProgressField>` / `<GridCardActionsField>` for common patterns. Set `multiline` for top-aligned multi-line
|
|
17
17
|
content; set `vertical` to stack the label above the content (escapes the subgrid).
|
|
18
18
|
|
|
19
19
|
### CSS Custom Properties
|
|
@@ -22,12 +22,18 @@ content; set `vertical` to stack the label above the content (escapes the subgri
|
|
|
22
22
|
| `--sc-kit--grid-card-field--background` | Row background | `var(--sc-kit--color--bg--panel)` |
|
|
23
23
|
| `--sc-kit--grid-card-field--padding-block` | Vertical padding | `6px` |
|
|
24
24
|
| `--sc-kit--grid-card-field--padding-inline` | Horizontal padding | `8px` |
|
|
25
|
+
| `--sc-kit--grid-card-field--content--font-size` | Font size of the label and of every field row, `<GridCardIdField>` included | `10px` |
|
|
26
|
+
| `--sc-kit--grid-card-field--content--line-height` | Line height of the label and of the text / select rows. Must stay **unitless** — row heights multiply it by the font size | `var(--sc-kit--leading--tight)` |
|
|
25
27
|
-->
|
|
26
28
|
|
|
27
29
|
<style>.grid-card-field {
|
|
28
30
|
--_gcf--background: var(--sc-kit--grid-card-field--background, var(--sc-kit--color--bg--panel));
|
|
29
31
|
--_gcf--padding-block: var(--sc-kit--grid-card-field--padding-block, 0.375rem);
|
|
30
32
|
--_gcf--padding-inline: var(--sc-kit--grid-card-field--padding-inline, 0.5rem);
|
|
33
|
+
--_gcf--content-font-size: var(--sc-kit--grid-card-field--content--font-size, 0.625rem);
|
|
34
|
+
--_gcf--content-line-height: var(--sc-kit--grid-card-field--content--line-height, var(--sc-kit--leading--tight));
|
|
35
|
+
--_--grid-card-field--content-font-size: var(--_gcf--content-font-size);
|
|
36
|
+
--_--grid-card-field--content-line-height: var(--_gcf--content-line-height);
|
|
31
37
|
display: grid;
|
|
32
38
|
grid-template-columns: subgrid;
|
|
33
39
|
grid-column: 1/-1;
|
|
@@ -44,12 +50,12 @@ content; set `vertical` to stack the label above the content (escapes the subgri
|
|
|
44
50
|
padding: var(--_gcf--padding-block) var(--_gcf--padding-inline);
|
|
45
51
|
}
|
|
46
52
|
.grid-card-field__label {
|
|
47
|
-
font-size:
|
|
53
|
+
font-size: var(--_gcf--content-font-size);
|
|
48
54
|
color: var(--sc-kit--color--text--secondary);
|
|
49
55
|
padding-block: var(--_gcf--padding-block);
|
|
50
56
|
padding-inline-start: var(--_gcf--padding-inline);
|
|
51
57
|
white-space: nowrap;
|
|
52
|
-
line-height: var(--
|
|
58
|
+
line-height: var(--_gcf--content-line-height);
|
|
53
59
|
}
|
|
54
60
|
.grid-card-field__label--vertical {
|
|
55
61
|
padding: 0;
|
|
@@ -7,8 +7,8 @@ type Props = {
|
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
9
|
* Single label-value row inside a `<GridCardFields>` subgrid. Use directly for custom content,
|
|
10
|
-
* or compose via `<GridCardTextField>` / `<GridCardBadgeField>` / `<
|
|
11
|
-
* `<GridCardActionsField>` for common patterns. Set `multiline` for top-aligned multi-line
|
|
10
|
+
* or compose via `<GridCardTextField>` / `<GridCardBadgeField>` / `<GridCardSelectField>` /
|
|
11
|
+
* `<GridCardProgressField>` / `<GridCardActionsField>` for common patterns. Set `multiline` for top-aligned multi-line
|
|
12
12
|
* content; set `vertical` to stack the label above the content (escapes the subgrid).
|
|
13
13
|
*
|
|
14
14
|
* ### CSS Custom Properties
|
|
@@ -17,6 +17,8 @@ type Props = {
|
|
|
17
17
|
* | `--sc-kit--grid-card-field--background` | Row background | `var(--sc-kit--color--bg--panel)` |
|
|
18
18
|
* | `--sc-kit--grid-card-field--padding-block` | Vertical padding | `6px` |
|
|
19
19
|
* | `--sc-kit--grid-card-field--padding-inline` | Horizontal padding | `8px` |
|
|
20
|
+
* | `--sc-kit--grid-card-field--content--font-size` | Font size of the label and of every field row, `<GridCardIdField>` included | `10px` |
|
|
21
|
+
* | `--sc-kit--grid-card-field--content--line-height` | Line height of the label and of the text / select rows. Must stay **unitless** — row heights multiply it by the font size | `var(--sc-kit--leading--tight)` |
|
|
20
22
|
*/
|
|
21
23
|
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
22
24
|
type Cmp = ReturnType<typeof Cmp>;
|
|
@@ -15,6 +15,7 @@ Skips the `<GridCardField>` subgrid wrapping for a tighter look.
|
|
|
15
15
|
-->
|
|
16
16
|
|
|
17
17
|
<style>.grid-card-id-field {
|
|
18
|
+
--_gcif--font-size: var(--sc-kit--grid-card-field--content--font-size, 0.625rem);
|
|
18
19
|
display: flex;
|
|
19
20
|
align-items: center;
|
|
20
21
|
gap: 0.8125rem;
|
|
@@ -23,7 +24,7 @@ Skips the `<GridCardField>` subgrid wrapping for a tighter look.
|
|
|
23
24
|
border-radius: 0.125rem;
|
|
24
25
|
padding-block: 0.375rem;
|
|
25
26
|
padding-inline: 0.5rem;
|
|
26
|
-
font-size:
|
|
27
|
+
font-size: var(--_gcif--font-size);
|
|
27
28
|
}
|
|
28
29
|
.grid-card-id-field__label, .grid-card-id-field__value {
|
|
29
30
|
color: var(--sc-kit--color--text--secondary);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts" module>export {};
|
|
2
|
+
</script>
|
|
3
|
+
|
|
4
|
+
<script lang="ts" generics="TValue">import { Badge } from '../../badge';
|
|
5
|
+
import { Singleselect } from '../../select';
|
|
6
|
+
import GridCardField from './cmp.grid-card-field.svelte';
|
|
7
|
+
const { label, value, options, badge = false, disabled = false, on } = $props();
|
|
8
|
+
const variantOf = (option) => {
|
|
9
|
+
return options.find((candidate) => candidate.value === option.value)?.variant ?? 'neutral';
|
|
10
|
+
};
|
|
11
|
+
// Only the keys GridCard itself acts on: stopping the rest would also block the listbox's document-level Escape handler.
|
|
12
|
+
const cardActivationKeys = new Set([' ', 'Enter']);
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{#snippet badgeSelection({ option }: { option: SelectOption<TValue> })}
|
|
16
|
+
<Badge variant={variantOf(option)} style="soft" size="sm">{option.label}</Badge>
|
|
17
|
+
{/snippet}
|
|
18
|
+
|
|
19
|
+
<GridCardField label={label}>
|
|
20
|
+
<span
|
|
21
|
+
class="grid-card-select-field"
|
|
22
|
+
class:grid-card-select-field--badge={badge}
|
|
23
|
+
role="presentation"
|
|
24
|
+
onclick={(event) => !disabled && event.stopPropagation()}
|
|
25
|
+
onkeydown={(event) => !disabled && cardActivationKeys.has(event.key) && event.stopPropagation()}>
|
|
26
|
+
<Singleselect
|
|
27
|
+
clearable={false}
|
|
28
|
+
value={value}
|
|
29
|
+
options={options}
|
|
30
|
+
size="sm"
|
|
31
|
+
borderless
|
|
32
|
+
searchable={false}
|
|
33
|
+
disabled={disabled}
|
|
34
|
+
aria-label={label}
|
|
35
|
+
selectionSnippet={badge ? badgeSelection : undefined}
|
|
36
|
+
on={{ change: (next) => on?.change?.(next) }} />
|
|
37
|
+
</span>
|
|
38
|
+
</GridCardField>
|
|
39
|
+
|
|
40
|
+
<!--
|
|
41
|
+
@component
|
|
42
|
+
Select row for a value the user picks inside the card — status, visibility, plan. Sizes the
|
|
43
|
+
trigger down to the row's own typography and drops its border, so a plain row lines up with
|
|
44
|
+
`<GridCardTextField>` and a `badge` row renders the same `soft` / `sm` badge as
|
|
45
|
+
`<GridCardBadgeField>`. Per-option badge color comes from `GridCardSelectOption.variant`.
|
|
46
|
+
|
|
47
|
+
### CSS Custom Properties
|
|
48
|
+
| Property | Description | Default |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `--sc-kit--grid-card-select-field--height` | Trigger height | row line box; `20px` with `badge` |
|
|
51
|
+
| `--sc-kit--grid-card-select-field--gap` | Gap between the value and the chevron | `var(--sc-kit--space--1)` |
|
|
52
|
+
| `--sc-kit--grid-card-select-field--chevron--size` | Chevron size | `12px` |
|
|
53
|
+
-->
|
|
54
|
+
|
|
55
|
+
<style>.grid-card-select-field {
|
|
56
|
+
--_gcsf--font-size: var(--_--grid-card-field--content-font-size);
|
|
57
|
+
--_gcsf--line-height: var(--_--grid-card-field--content-line-height);
|
|
58
|
+
--_gcsf--height: var(--sc-kit--grid-card-select-field--height, calc(var(--_gcsf--line-height) * 1em));
|
|
59
|
+
--_gcsf--gap: var(--sc-kit--grid-card-select-field--gap, var(--sc-kit--space--1));
|
|
60
|
+
--_gcsf--chevron-size: var(--sc-kit--grid-card-select-field--chevron--size, 0.75rem);
|
|
61
|
+
--sc-kit--select--trigger--height: var(--_gcsf--height);
|
|
62
|
+
--sc-kit--select--trigger--font-size: var(--_gcsf--font-size);
|
|
63
|
+
--sc-kit--select--trigger--padding-inline: 0;
|
|
64
|
+
--sc-kit--select--trigger--gap: var(--_gcsf--gap);
|
|
65
|
+
--sc-kit--select--trigger--icon--size: var(--_gcsf--chevron-size);
|
|
66
|
+
font-size: var(--_gcsf--font-size);
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
min-width: 0;
|
|
70
|
+
}
|
|
71
|
+
.grid-card-select-field--badge {
|
|
72
|
+
--_gcsf--height: var(--sc-kit--grid-card-select-field--height, 1.25rem);
|
|
73
|
+
}</style>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { BadgeVariant } from '../../badge';
|
|
2
|
+
import type { SelectOption } from '../../select';
|
|
3
|
+
export type GridCardSelectOption<TValue> = SelectOption<TValue> & {
|
|
4
|
+
/** Badge color for this option, used when the field renders `badge`. @default 'neutral' */
|
|
5
|
+
variant?: BadgeVariant;
|
|
6
|
+
};
|
|
7
|
+
declare function $$render<TValue>(): {
|
|
8
|
+
props: {
|
|
9
|
+
label: string;
|
|
10
|
+
value: TValue;
|
|
11
|
+
options: GridCardSelectOption<TValue>[];
|
|
12
|
+
/** Render the selection as a `<Badge>`, matching `<GridCardBadgeField>`. @default false */
|
|
13
|
+
badge?: boolean;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
on?: {
|
|
16
|
+
change?: (value: TValue) => void;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
exports: {};
|
|
20
|
+
bindings: "";
|
|
21
|
+
slots: {};
|
|
22
|
+
events: {};
|
|
23
|
+
};
|
|
24
|
+
declare class __sveltets_Render<TValue> {
|
|
25
|
+
props(): ReturnType<typeof $$render<TValue>>['props'];
|
|
26
|
+
events(): ReturnType<typeof $$render<TValue>>['events'];
|
|
27
|
+
slots(): ReturnType<typeof $$render<TValue>>['slots'];
|
|
28
|
+
bindings(): "";
|
|
29
|
+
exports(): {};
|
|
30
|
+
}
|
|
31
|
+
interface $$IsomorphicComponent {
|
|
32
|
+
new <TValue>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TValue>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TValue>['props']>, ReturnType<__sveltets_Render<TValue>['events']>, ReturnType<__sveltets_Render<TValue>['slots']>> & {
|
|
33
|
+
$$bindings?: ReturnType<__sveltets_Render<TValue>['bindings']>;
|
|
34
|
+
} & ReturnType<__sveltets_Render<TValue>['exports']>;
|
|
35
|
+
<TValue>(internal: unknown, props: ReturnType<__sveltets_Render<TValue>['props']> & {}): ReturnType<__sveltets_Render<TValue>['exports']>;
|
|
36
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Select row for a value the user picks inside the card — status, visibility, plan. Sizes the
|
|
40
|
+
* trigger down to the row's own typography and drops its border, so a plain row lines up with
|
|
41
|
+
* `<GridCardTextField>` and a `badge` row renders the same `soft` / `sm` badge as
|
|
42
|
+
* `<GridCardBadgeField>`. Per-option badge color comes from `GridCardSelectOption.variant`.
|
|
43
|
+
*
|
|
44
|
+
* ### CSS Custom Properties
|
|
45
|
+
* | Property | Description | Default |
|
|
46
|
+
* |---|---|---|
|
|
47
|
+
* | `--sc-kit--grid-card-select-field--height` | Trigger height | row line box; `20px` with `badge` |
|
|
48
|
+
* | `--sc-kit--grid-card-select-field--gap` | Gap between the value and the chevron | `var(--sc-kit--space--1)` |
|
|
49
|
+
* | `--sc-kit--grid-card-select-field--chevron--size` | Chevron size | `12px` |
|
|
50
|
+
*/
|
|
51
|
+
declare const Cmp: $$IsomorphicComponent;
|
|
52
|
+
type Cmp<TValue> = InstanceType<typeof Cmp<TValue>>;
|
|
53
|
+
export default Cmp;
|
|
@@ -29,8 +29,10 @@ overflow) or a snippet for custom markup. `-` placeholder when text is null / un
|
|
|
29
29
|
line-clamp: var(--_max-lines, 1);
|
|
30
30
|
overflow: hidden;
|
|
31
31
|
word-break: break-word;
|
|
32
|
+
--_gctf--font-size: var(--_--grid-card-field--content-font-size);
|
|
33
|
+
--_gctf--line-height: var(--_--grid-card-field--content-line-height);
|
|
32
34
|
color: var(--sc-kit--color--text--primary);
|
|
33
|
-
line-height: var(--
|
|
34
|
-
height: calc(var(--_max-lines, 1) * var(--
|
|
35
|
-
font-size:
|
|
35
|
+
line-height: var(--_gctf--line-height);
|
|
36
|
+
height: calc(var(--_max-lines, 1) * var(--_gctf--line-height) * 1em);
|
|
37
|
+
font-size: var(--_gctf--font-size);
|
|
36
38
|
}</style>
|
|
@@ -5,4 +5,5 @@ export { default as GridCardField } from './cmp.grid-card-field.svelte';
|
|
|
5
5
|
export { default as GridCardFields } from './cmp.grid-card-fields.svelte';
|
|
6
6
|
export { default as GridCardIdField } from './cmp.grid-card-id-field.svelte';
|
|
7
7
|
export { default as GridCardProgressField } from './cmp.grid-card-progress-field.svelte';
|
|
8
|
+
export { default as GridCardSelectField, type GridCardSelectOption } from './cmp.grid-card-select-field.svelte';
|
|
8
9
|
export { default as GridCardTextField } from './cmp.grid-card-text-field.svelte';
|
|
@@ -5,4 +5,5 @@ export { default as GridCardField } from './cmp.grid-card-field.svelte';
|
|
|
5
5
|
export { default as GridCardFields } from './cmp.grid-card-fields.svelte';
|
|
6
6
|
export { default as GridCardIdField } from './cmp.grid-card-id-field.svelte';
|
|
7
7
|
export { default as GridCardProgressField } from './cmp.grid-card-progress-field.svelte';
|
|
8
|
+
export { default as GridCardSelectField } from './cmp.grid-card-select-field.svelte';
|
|
8
9
|
export { default as GridCardTextField } from './cmp.grid-card-text-field.svelte';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export type { AspectRatio, ObjectFit } from './types';
|
|
2
2
|
export { default as GridCard } from './cmp.grid-card.svelte';
|
|
3
3
|
export { default as GridCardMedia, type MediaItem as GridCardMediaItem } from './cmp.grid-card-media.svelte';
|
|
4
|
-
export { GridCardActionsField, GridCardBadgeField, GridCardField, GridCardFields, GridCardIdField, GridCardProgressField, GridCardTextField, type GridCardAction } from './fields';
|
|
4
|
+
export { GridCardActionsField, GridCardBadgeField, GridCardField, GridCardFields, GridCardIdField, GridCardProgressField, GridCardSelectField, GridCardTextField, type GridCardAction, type GridCardSelectOption } from './fields';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default as GridCard } from './cmp.grid-card.svelte';
|
|
2
2
|
export { default as GridCardMedia } from './cmp.grid-card-media.svelte';
|
|
3
|
-
export { GridCardActionsField, GridCardBadgeField, GridCardField, GridCardFields, GridCardIdField, GridCardProgressField, GridCardTextField } from './fields';
|
|
3
|
+
export { GridCardActionsField, GridCardBadgeField, GridCardField, GridCardFields, GridCardIdField, GridCardProgressField, GridCardSelectField, GridCardTextField } from './fields';
|
|
@@ -128,10 +128,11 @@
|
|
|
128
128
|
cursor: text;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// Keeps flex-grow so the trailing chevron stays pinned right when a selectionSnippet replaces the input.
|
|
131
132
|
&--hidden {
|
|
132
133
|
opacity: 0;
|
|
133
134
|
width: 0;
|
|
134
|
-
flex: 0;
|
|
135
|
+
flex: 1 1 0;
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
&--no-search {
|
|
@@ -299,7 +299,7 @@ background, background--disabled, border-color, border-color--hover, border-colo
|
|
|
299
299
|
border-radius, underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
300
300
|
|
|
301
301
|
**Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius, box-shadow,
|
|
302
|
-
max-height, padding}`.
|
|
302
|
+
font-size, max-height, padding}`.
|
|
303
303
|
|
|
304
304
|
**Suggestion rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
305
305
|
padding-inline}`. `--sc-kit--select--option--height` is NOT overridable here — the component pins
|
|
@@ -427,7 +427,7 @@ background--hover}`.
|
|
|
427
427
|
.input-suggest__input--hidden {
|
|
428
428
|
opacity: 0;
|
|
429
429
|
width: 0;
|
|
430
|
-
flex: 0;
|
|
430
|
+
flex: 1 1 0;
|
|
431
431
|
}
|
|
432
432
|
.input-suggest__input--no-search {
|
|
433
433
|
caret-color: transparent;
|
|
@@ -85,7 +85,7 @@ type Props = {
|
|
|
85
85
|
* border-radius, underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
86
86
|
*
|
|
87
87
|
* **Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius, box-shadow,
|
|
88
|
-
* max-height, padding}`.
|
|
88
|
+
* font-size, max-height, padding}`.
|
|
89
89
|
*
|
|
90
90
|
* **Suggestion rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
91
91
|
* padding-inline}`. `--sc-kit--select--option--height` is NOT overridable here — the component pins
|
|
@@ -39,7 +39,7 @@ background--disabled, border-color, border-color--hover, border-color--error, bo
|
|
|
39
39
|
underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
40
40
|
|
|
41
41
|
**Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
42
|
-
box-shadow, max-height, padding}`.
|
|
42
|
+
box-shadow, font-size, max-height, padding}`.
|
|
43
43
|
|
|
44
44
|
**Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
45
45
|
padding-block, padding-inline}`.
|
|
@@ -61,7 +61,7 @@ interface $$IsomorphicComponent {
|
|
|
61
61
|
* underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
62
62
|
*
|
|
63
63
|
* **Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
64
|
-
* box-shadow, max-height, padding}`.
|
|
64
|
+
* box-shadow, font-size, max-height, padding}`.
|
|
65
65
|
*
|
|
66
66
|
* **Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
67
67
|
* padding-block, padding-inline}`.
|
|
@@ -102,7 +102,7 @@ background--disabled, border-color, border-color--hover, border-color--error, bo
|
|
|
102
102
|
underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
103
103
|
|
|
104
104
|
**Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
105
|
-
box-shadow, max-height, padding}`.
|
|
105
|
+
box-shadow, font-size, max-height, padding}`.
|
|
106
106
|
|
|
107
107
|
**Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
108
108
|
padding-block, padding-inline}`.
|
|
@@ -144,7 +144,7 @@ interface $$IsomorphicComponent {
|
|
|
144
144
|
* underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
145
145
|
*
|
|
146
146
|
* **Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
147
|
-
* box-shadow, max-height, padding}`.
|
|
147
|
+
* box-shadow, font-size, max-height, padding}`.
|
|
148
148
|
*
|
|
149
149
|
* **Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
150
150
|
* padding-block, padding-inline}`.
|
|
@@ -54,7 +54,7 @@ background--disabled, border-color, border-color--hover, border-color--error, bo
|
|
|
54
54
|
underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
55
55
|
|
|
56
56
|
**Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
57
|
-
box-shadow, max-height, padding}`.
|
|
57
|
+
box-shadow, font-size, max-height, padding}`.
|
|
58
58
|
|
|
59
59
|
**Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
60
60
|
padding-block, padding-inline}`.
|
|
@@ -59,7 +59,7 @@ interface $$IsomorphicComponent {
|
|
|
59
59
|
* underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
60
60
|
*
|
|
61
61
|
* **Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
62
|
-
* box-shadow, max-height, padding}`.
|
|
62
|
+
* box-shadow, font-size, max-height, padding}`.
|
|
63
63
|
*
|
|
64
64
|
* **Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
65
65
|
* padding-block, padding-inline}`.
|
|
@@ -54,6 +54,8 @@ const core = createSelectCore({
|
|
|
54
54
|
},
|
|
55
55
|
onCreate: (payload) => mode.on?.create?.(payload.query)
|
|
56
56
|
});
|
|
57
|
+
// The `||` is deliberate: a non-searchable select has no query to show, so the selection stays visible while open.
|
|
58
|
+
const selectionVisible = $derived(!core.isOpen || !searchable);
|
|
57
59
|
const listboxId = `singleselect-async-${Math.random().toString(36).slice(2, 9)}`;
|
|
58
60
|
export const open = () => core.openPopover();
|
|
59
61
|
export const close = () => core.closePopover();
|
|
@@ -82,14 +84,14 @@ const showClear = $derived(mode.clearable && hasValue && isInteractive && !core.
|
|
|
82
84
|
<span class="singleselect__icon singleselect__icon--leading"><IconSlot icon={icon} /></span>
|
|
83
85
|
{/if}
|
|
84
86
|
|
|
85
|
-
{#if selectionSnippet && selectedOption &&
|
|
87
|
+
{#if selectionSnippet && selectedOption && selectionVisible}
|
|
86
88
|
<span class="singleselect__selection">{@render selectionSnippet({ option: selectedOption })}</span>
|
|
87
89
|
{/if}
|
|
88
90
|
|
|
89
91
|
<input
|
|
90
92
|
bind:this={inputEl}
|
|
91
93
|
class="singleselect__input"
|
|
92
|
-
class:singleselect__input--hidden={selectionSnippet && selectedOption &&
|
|
94
|
+
class:singleselect__input--hidden={!!selectionSnippet && !!selectedOption && selectionVisible}
|
|
93
95
|
class:singleselect__input--no-search={!searchable}
|
|
94
96
|
type="text"
|
|
95
97
|
role="combobox"
|
|
@@ -161,7 +163,7 @@ background--disabled, border-color, border-color--hover, border-color--error, bo
|
|
|
161
163
|
underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
162
164
|
|
|
163
165
|
**Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
164
|
-
box-shadow, max-height, padding}`.
|
|
166
|
+
box-shadow, font-size, max-height, padding}`.
|
|
165
167
|
|
|
166
168
|
**Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
167
169
|
padding-block, padding-inline}`.
|
|
@@ -294,7 +296,7 @@ background--hover}`.
|
|
|
294
296
|
.singleselect__input--hidden {
|
|
295
297
|
opacity: 0;
|
|
296
298
|
width: 0;
|
|
297
|
-
flex: 0;
|
|
299
|
+
flex: 1 1 0;
|
|
298
300
|
}
|
|
299
301
|
.singleselect__input--no-search {
|
|
300
302
|
caret-color: transparent;
|
|
@@ -89,7 +89,7 @@ interface $$IsomorphicComponent {
|
|
|
89
89
|
* underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
90
90
|
*
|
|
91
91
|
* **Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
92
|
-
* box-shadow, max-height, padding}`.
|
|
92
|
+
* box-shadow, font-size, max-height, padding}`.
|
|
93
93
|
*
|
|
94
94
|
* **Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
95
95
|
* padding-block, padding-inline}`.
|
|
@@ -121,7 +121,7 @@ background--disabled, border-color, border-color--hover, border-color--error, bo
|
|
|
121
121
|
underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
122
122
|
|
|
123
123
|
**Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
124
|
-
box-shadow, max-height, padding}`.
|
|
124
|
+
box-shadow, font-size, max-height, padding}`.
|
|
125
125
|
|
|
126
126
|
**Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
127
127
|
padding-block, padding-inline}`.
|
|
@@ -80,7 +80,7 @@ interface $$IsomorphicComponent {
|
|
|
80
80
|
* underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
81
81
|
*
|
|
82
82
|
* **Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
83
|
-
* box-shadow, max-height, padding}`.
|
|
83
|
+
* box-shadow, font-size, max-height, padding}`.
|
|
84
84
|
*
|
|
85
85
|
* **Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
86
86
|
* padding-block, padding-inline}`.
|
|
@@ -319,7 +319,7 @@ background--disabled, border-color, border-color--hover, border-color--error, bo
|
|
|
319
319
|
underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
320
320
|
|
|
321
321
|
**Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
322
|
-
box-shadow, max-height, padding}`.
|
|
322
|
+
box-shadow, font-size, max-height, padding}`.
|
|
323
323
|
|
|
324
324
|
**Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
325
325
|
padding-block, padding-inline}`.
|
|
@@ -510,7 +510,7 @@ padding-inline, font-size, gap, max-width, remove-color, remove-color--hover}`.
|
|
|
510
510
|
.multiselect-trigger__input--hidden {
|
|
511
511
|
opacity: 0;
|
|
512
512
|
width: 0;
|
|
513
|
-
flex: 0;
|
|
513
|
+
flex: 1 1 0;
|
|
514
514
|
}
|
|
515
515
|
.multiselect-trigger__input--no-search {
|
|
516
516
|
caret-color: transparent;
|
|
@@ -61,7 +61,7 @@ interface $$IsomorphicComponent {
|
|
|
61
61
|
* underline-color, font-size, color, placeholder--color, icon--color, icon--size}`.
|
|
62
62
|
*
|
|
63
63
|
* **Popover panel:** `--sc-kit--select--panel--{background, border-color, border-radius,
|
|
64
|
-
* box-shadow, max-height, padding}`.
|
|
64
|
+
* box-shadow, font-size, max-height, padding}`.
|
|
65
65
|
*
|
|
66
66
|
* **Option rows:** `--sc-kit--select--option--{color, color--selected, background--active,
|
|
67
67
|
* padding-block, padding-inline}`.
|
|
@@ -234,6 +234,7 @@ dismiss events upward. Floating UI handles placement with autoUpdate on scroll/r
|
|
|
234
234
|
| `--sc-kit--select--panel--border-color` | Panel border | `var(--sc-kit--color--border)` |
|
|
235
235
|
| `--sc-kit--select--panel--border-radius` | Panel radius | `var(--sc-kit--radius--md)` |
|
|
236
236
|
| `--sc-kit--select--panel--box-shadow` | Panel shadow | `var(--sc-kit--shadow--lg)` |
|
|
237
|
+
| `--sc-kit--select--panel--font-size` | Option / group / empty-state text size | `var(--sc-kit--font-size--md)` |
|
|
237
238
|
| `--sc-kit--select--panel--max-height` | Panel max height | `18rem` |
|
|
238
239
|
| `--sc-kit--select--panel--padding` | Panel inner padding | `var(--sc-kit--space--1)` |
|
|
239
240
|
| `--sc-kit--select--option--height` | Option row height | `var(--sc-kit--field--height--md)` (32px) |
|
|
@@ -253,6 +254,7 @@ dismiss events upward. Floating UI handles placement with autoUpdate on scroll/r
|
|
|
253
254
|
--_lb--box-shadow: var(--sc-kit--select--panel--box-shadow, var(--sc-kit--shadow--lg));
|
|
254
255
|
--_lb--max-height: var(--sc-kit--select--panel--max-height, 18rem);
|
|
255
256
|
--_lb--padding: var(--sc-kit--select--panel--padding, var(--sc-kit--space--1));
|
|
257
|
+
--_lb--font-size: var(--sc-kit--select--panel--font-size, var(--sc-kit--font-size--md));
|
|
256
258
|
--_lb--row-height: var(--sc-kit--select--option--height, var(--sc-kit--field--height--md));
|
|
257
259
|
--_lb--row-padding-inline: var(--sc-kit--select--option--padding-inline, var(--sc-kit--space--3));
|
|
258
260
|
--_lb--row-background-active: var(--sc-kit--select--option--background--active, var(--sc-kit--color--accent--softer));
|
|
@@ -261,6 +263,7 @@ dismiss events upward. Floating UI handles placement with autoUpdate on scroll/r
|
|
|
261
263
|
--_lb--group-header-color: var(--sc-kit--select--group-header--color, var(--sc-kit--color--text--muted));
|
|
262
264
|
--_lb--group-header-font-size: var(--sc-kit--select--group-header--font-size, var(--sc-kit--font-size--xs));
|
|
263
265
|
--_lb--empty-color: var(--sc-kit--select--empty--color, var(--sc-kit--color--text--muted));
|
|
266
|
+
font-size: var(--_lb--font-size);
|
|
264
267
|
display: flex;
|
|
265
268
|
flex-direction: column;
|
|
266
269
|
gap: 0.125rem;
|
|
@@ -79,6 +79,7 @@ interface $$IsomorphicComponent {
|
|
|
79
79
|
* | `--sc-kit--select--panel--border-color` | Panel border | `var(--sc-kit--color--border)` |
|
|
80
80
|
* | `--sc-kit--select--panel--border-radius` | Panel radius | `var(--sc-kit--radius--md)` |
|
|
81
81
|
* | `--sc-kit--select--panel--box-shadow` | Panel shadow | `var(--sc-kit--shadow--lg)` |
|
|
82
|
+
* | `--sc-kit--select--panel--font-size` | Option / group / empty-state text size | `var(--sc-kit--font-size--md)` |
|
|
82
83
|
* | `--sc-kit--select--panel--max-height` | Panel max height | `18rem` |
|
|
83
84
|
* | `--sc-kit--select--panel--padding` | Panel inner padding | `var(--sc-kit--space--1)` |
|
|
84
85
|
* | `--sc-kit--select--option--height` | Option row height | `var(--sc-kit--field--height--md)` (32px) |
|
|
@@ -100,6 +100,8 @@ const navigateToUser = (event, by) => {
|
|
|
100
100
|
text-transform: lowercase;
|
|
101
101
|
}
|
|
102
102
|
.table-by-cell__by {
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
103
105
|
margin-right: 0.625em;
|
|
104
106
|
}
|
|
105
107
|
.table-by-cell__name {
|
|
@@ -112,12 +114,11 @@ const navigateToUser = (event, by) => {
|
|
|
112
114
|
overflow: hidden;
|
|
113
115
|
}
|
|
114
116
|
.table-by-cell__name--link {
|
|
115
|
-
color:
|
|
116
|
-
text-decoration:
|
|
117
|
-
transition: color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
117
|
+
color: inherit;
|
|
118
|
+
text-decoration: none;
|
|
118
119
|
}
|
|
119
120
|
.table-by-cell__name--link:hover {
|
|
120
|
-
|
|
121
|
+
text-decoration: underline;
|
|
121
122
|
}
|
|
122
123
|
.table-by-cell__by-image {
|
|
123
124
|
display: flex;
|