@x33025/sveltely 0.1.1 → 0.1.2
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/components/Library/Button/Button.demo.svelte +5 -3
- package/dist/components/Library/Button/Button.demo.svelte.d.ts +1 -0
- package/dist/components/Library/Calendar/Calendar.demo.svelte +2 -14
- package/dist/components/Library/Calendar/Calendar.svelte +54 -50
- package/dist/components/Library/Divider/Divider.svelte +10 -0
- package/dist/components/Library/Divider/Divider.svelte.d.ts +26 -0
- package/dist/components/Library/Divider/index.d.ts +1 -0
- package/dist/components/Library/Divider/index.js +1 -0
- package/dist/components/Library/Dropdown/Dropdown.demo.svelte +10 -3
- package/dist/components/Library/Dropdown/Dropdown.svelte +39 -5
- package/dist/components/Library/Dropdown/index.d.ts +1 -1
- package/dist/components/Library/Dropdown/types.d.ts +4 -1
- package/dist/components/Library/Floating/Floating.svelte +35 -1
- package/dist/components/Library/ForEach/ForEach.svelte +14 -0
- package/dist/components/Library/ForEach/ForEach.svelte.d.ts +28 -0
- package/dist/components/Library/ForEach/index.d.ts +1 -0
- package/dist/components/Library/ForEach/index.js +1 -0
- package/dist/components/Library/Grid/Grid.svelte +74 -0
- package/dist/components/Library/Grid/Grid.svelte.d.ts +13 -0
- package/dist/components/Library/Grid/index.d.ts +1 -0
- package/dist/components/Library/Grid/index.js +1 -0
- package/dist/components/Library/GridItem/GridItem.svelte +65 -0
- package/dist/components/Library/GridItem/GridItem.svelte.d.ts +14 -0
- package/dist/components/Library/GridItem/index.d.ts +1 -0
- package/dist/components/Library/GridItem/index.js +1 -0
- package/dist/components/Library/HStack/HStack.svelte +45 -0
- package/dist/components/Library/HStack/HStack.svelte.d.ts +9 -0
- package/dist/components/Library/HStack/index.d.ts +1 -0
- package/dist/components/Library/HStack/index.js +1 -0
- package/dist/components/Library/Image/Image.demo.svelte +18 -0
- package/dist/components/Library/Image/Image.demo.svelte.d.ts +23 -0
- package/dist/components/Library/Image/Image.svelte +57 -0
- package/dist/components/Library/Image/Image.svelte.d.ts +17 -0
- package/dist/components/Library/Image/ImagePlaceholder.svelte +202 -0
- package/dist/components/Library/Image/ImagePlaceholder.svelte.d.ts +7 -0
- package/dist/components/Library/Image/index.d.ts +1 -0
- package/dist/components/Library/Image/index.js +1 -0
- package/dist/components/Library/ImageMask/BrushPreview.svelte +119 -0
- package/dist/components/Library/ImageMask/BrushPreview.svelte.d.ts +11 -0
- package/dist/components/Library/ImageMask/ImageMask.demo.svelte +117 -0
- package/dist/components/Library/ImageMask/ImageMask.demo.svelte.d.ts +10 -0
- package/dist/components/Library/ImageMask/ImageMask.svelte +46 -0
- package/dist/components/Library/ImageMask/ImageMask.svelte.d.ts +20 -0
- package/dist/components/Library/ImageMask/MaskLayer.svelte +341 -0
- package/dist/components/Library/ImageMask/MaskLayer.svelte.d.ts +12 -0
- package/dist/components/Library/ImageMask/contour.d.ts +11 -0
- package/dist/components/Library/ImageMask/contour.js +152 -0
- package/dist/components/Library/ImageMask/index.d.ts +2 -0
- package/dist/components/Library/ImageMask/index.js +1 -0
- package/dist/components/Library/ImageMask/marchingAnts.d.ts +8 -0
- package/dist/components/Library/ImageMask/marchingAnts.js +29 -0
- package/dist/components/Library/ImageMask/maskSurface.d.ts +5 -0
- package/dist/components/Library/ImageMask/maskSurface.js +94 -0
- package/dist/components/Library/ImageMask/types.d.ts +23 -0
- package/dist/components/Library/ImageMask/types.js +1 -0
- package/dist/components/Library/Label/Label.demo.svelte +28 -0
- package/dist/components/Library/Label/Label.demo.svelte.d.ts +9 -0
- package/dist/components/Library/Label/Label.svelte +177 -0
- package/dist/components/Library/Label/Label.svelte.d.ts +18 -0
- package/dist/components/Library/Label/index.d.ts +1 -0
- package/dist/components/Library/Label/index.js +1 -0
- package/dist/components/Library/NumberField/NumberField.demo.svelte +21 -0
- package/dist/components/Library/NumberField/NumberField.demo.svelte.d.ts +8 -0
- package/dist/components/Library/NumberField/NumberField.svelte +194 -0
- package/dist/components/Library/NumberField/NumberField.svelte.d.ts +21 -0
- package/dist/components/Library/NumberField/index.d.ts +1 -0
- package/dist/components/Library/NumberField/index.js +1 -0
- package/dist/components/Library/ScrollView/ScrollView.svelte +25 -9
- package/dist/components/Library/ScrollView/ScrollView.svelte.d.ts +4 -4
- package/dist/components/Library/Spacer/Spacer.svelte +7 -0
- package/dist/components/Library/Spacer/Spacer.svelte.d.ts +26 -0
- package/dist/components/Library/Spacer/index.d.ts +1 -0
- package/dist/components/Library/Spacer/index.js +1 -0
- package/dist/components/Library/TextField/TextField.demo.svelte +14 -0
- package/dist/components/Library/TextField/TextField.demo.svelte.d.ts +8 -0
- package/dist/components/Library/TextField/TextField.svelte +149 -0
- package/dist/components/Library/TextField/TextField.svelte.d.ts +19 -0
- package/dist/components/Library/TextField/index.d.ts +1 -0
- package/dist/components/Library/TextField/index.js +1 -0
- package/dist/components/Library/VStack/VStack.svelte +45 -0
- package/dist/components/Library/VStack/VStack.svelte.d.ts +9 -0
- package/dist/components/Library/VStack/index.d.ts +1 -0
- package/dist/components/Library/VStack/index.js +1 -0
- package/dist/components/Local/ComponentGrid.svelte +15 -31
- package/dist/components/Local/HeroCard.svelte +26 -36
- package/dist/components/Local/HeroCard.svelte.d.ts +0 -2
- package/dist/index.d.ts +23 -0
- package/dist/index.js +17 -0
- package/dist/style/index.css +28 -17
- package/dist/style/label.d.ts +6 -0
- package/dist/style/label.js +4 -0
- package/dist/style/layout.d.ts +57 -0
- package/dist/style/layout.js +128 -0
- package/dist/style/media.d.ts +12 -0
- package/dist/style/media.js +8 -0
- package/dist/style/scroll.d.ts +7 -0
- package/dist/style/scroll.js +5 -0
- package/dist/style/text-editor.d.ts +34 -0
- package/dist/style/text-editor.js +29 -0
- package/dist/style.css +58 -35
- package/package.json +1 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
function pointKey(point) {
|
|
2
|
+
return `${point.x.toFixed(3)}:${point.y.toFixed(3)}`;
|
|
3
|
+
}
|
|
4
|
+
function distanceToSegmentSquared(point, start, end) {
|
|
5
|
+
const dx = end.x - start.x;
|
|
6
|
+
const dy = end.y - start.y;
|
|
7
|
+
if (dx === 0 && dy === 0)
|
|
8
|
+
return (point.x - start.x) ** 2 + (point.y - start.y) ** 2;
|
|
9
|
+
const t = Math.max(0, Math.min(1, ((point.x - start.x) * dx + (point.y - start.y) * dy) / (dx * dx + dy * dy)));
|
|
10
|
+
const projected = { x: start.x + t * dx, y: start.y + t * dy };
|
|
11
|
+
return (point.x - projected.x) ** 2 + (point.y - projected.y) ** 2;
|
|
12
|
+
}
|
|
13
|
+
function simplifyPoints(points, epsilon) {
|
|
14
|
+
if (points.length <= 2)
|
|
15
|
+
return points;
|
|
16
|
+
const keep = new Array(points.length).fill(false);
|
|
17
|
+
const stack = [[0, points.length - 1]];
|
|
18
|
+
const epsilonSquared = epsilon * epsilon;
|
|
19
|
+
keep[0] = true;
|
|
20
|
+
keep[points.length - 1] = true;
|
|
21
|
+
while (stack.length > 0) {
|
|
22
|
+
const [start, end] = stack.pop();
|
|
23
|
+
let maxDistance = 0;
|
|
24
|
+
let indexToKeep = start;
|
|
25
|
+
for (let index = start + 1; index < end; index += 1) {
|
|
26
|
+
const distance = distanceToSegmentSquared(points[index], points[start], points[end]);
|
|
27
|
+
if (distance > maxDistance) {
|
|
28
|
+
maxDistance = distance;
|
|
29
|
+
indexToKeep = index;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (maxDistance > epsilonSquared) {
|
|
33
|
+
keep[indexToKeep] = true;
|
|
34
|
+
stack.push([start, indexToKeep], [indexToKeep, end]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return points.filter((_, index) => keep[index]);
|
|
38
|
+
}
|
|
39
|
+
export function buildContourPaths({ surfaceCanvas, surfaceContext, viewportWidth, viewportHeight, dpr, step = Math.max(0.25, 1 / Math.max(1, dpr)), simplify = 0.12, threshold = 127 }) {
|
|
40
|
+
const columns = Math.max(2, Math.ceil(viewportWidth / step) + 2);
|
|
41
|
+
const rows = Math.max(2, Math.ceil(viewportHeight / step) + 2);
|
|
42
|
+
const image = surfaceContext.getImageData(0, 0, surfaceCanvas.width, surfaceCanvas.height);
|
|
43
|
+
const alphaAt = (x, y) => {
|
|
44
|
+
const clampedX = Math.min(surfaceCanvas.width - 1, Math.max(0, x));
|
|
45
|
+
const clampedY = Math.min(surfaceCanvas.height - 1, Math.max(0, y));
|
|
46
|
+
const x0 = Math.floor(clampedX);
|
|
47
|
+
const y0 = Math.floor(clampedY);
|
|
48
|
+
const x1 = Math.min(surfaceCanvas.width - 1, x0 + 1);
|
|
49
|
+
const y1 = Math.min(surfaceCanvas.height - 1, y0 + 1);
|
|
50
|
+
const tx = clampedX - x0;
|
|
51
|
+
const ty = clampedY - y0;
|
|
52
|
+
const offset = (sampleX, sampleY) => image.data[(sampleY * surfaceCanvas.width + sampleX) * 4 + 3];
|
|
53
|
+
const top = offset(x0, y0) * (1 - tx) + offset(x1, y0) * tx;
|
|
54
|
+
const bottom = offset(x0, y1) * (1 - tx) + offset(x1, y1) * tx;
|
|
55
|
+
return top * (1 - ty) + bottom * ty;
|
|
56
|
+
};
|
|
57
|
+
const alpha = Array.from({ length: rows }, (_, y) => Array.from({ length: columns }, (_, x) => {
|
|
58
|
+
return alphaAt(x * step * dpr, y * step * dpr);
|
|
59
|
+
}));
|
|
60
|
+
const segments = [];
|
|
61
|
+
const interpolate = (from, to) => {
|
|
62
|
+
if (from === to)
|
|
63
|
+
return 0.5;
|
|
64
|
+
return Math.max(0, Math.min(1, (threshold - from) / (to - from)));
|
|
65
|
+
};
|
|
66
|
+
const edgePoint = (x, y, edge) => {
|
|
67
|
+
const topLeft = alpha[y][x];
|
|
68
|
+
const topRight = alpha[y][x + 1];
|
|
69
|
+
const bottomRight = alpha[y + 1][x + 1];
|
|
70
|
+
const bottomLeft = alpha[y + 1][x];
|
|
71
|
+
if (edge === 'top') {
|
|
72
|
+
const t = interpolate(topLeft, topRight);
|
|
73
|
+
return { x: (x + t) * step, y: y * step };
|
|
74
|
+
}
|
|
75
|
+
if (edge === 'right') {
|
|
76
|
+
const t = interpolate(topRight, bottomRight);
|
|
77
|
+
return { x: (x + 1) * step, y: (y + t) * step };
|
|
78
|
+
}
|
|
79
|
+
if (edge === 'bottom') {
|
|
80
|
+
const t = interpolate(bottomLeft, bottomRight);
|
|
81
|
+
return { x: (x + t) * step, y: (y + 1) * step };
|
|
82
|
+
}
|
|
83
|
+
const t = interpolate(topLeft, bottomLeft);
|
|
84
|
+
return { x: x * step, y: (y + t) * step };
|
|
85
|
+
};
|
|
86
|
+
const addSegment = (x, y, from, to) => segments.push([edgePoint(x, y, from), edgePoint(x, y, to)]);
|
|
87
|
+
for (let y = 0; y < rows - 1; y += 1) {
|
|
88
|
+
for (let x = 0; x < columns - 1; x += 1) {
|
|
89
|
+
const value = (alpha[y][x] >= threshold ? 8 : 0) |
|
|
90
|
+
(alpha[y][x + 1] >= threshold ? 4 : 0) |
|
|
91
|
+
(alpha[y + 1][x + 1] >= threshold ? 2 : 0) |
|
|
92
|
+
(alpha[y + 1][x] >= threshold ? 1 : 0);
|
|
93
|
+
if (value === 1 || value === 14)
|
|
94
|
+
addSegment(x, y, 'left', 'bottom');
|
|
95
|
+
else if (value === 2 || value === 13)
|
|
96
|
+
addSegment(x, y, 'bottom', 'right');
|
|
97
|
+
else if (value === 3 || value === 12)
|
|
98
|
+
addSegment(x, y, 'left', 'right');
|
|
99
|
+
else if (value === 4 || value === 11)
|
|
100
|
+
addSegment(x, y, 'top', 'right');
|
|
101
|
+
else if (value === 6 || value === 9)
|
|
102
|
+
addSegment(x, y, 'top', 'bottom');
|
|
103
|
+
else if (value === 7 || value === 8)
|
|
104
|
+
addSegment(x, y, 'left', 'top');
|
|
105
|
+
else if (value === 5) {
|
|
106
|
+
addSegment(x, y, 'top', 'left');
|
|
107
|
+
addSegment(x, y, 'bottom', 'right');
|
|
108
|
+
}
|
|
109
|
+
else if (value === 10) {
|
|
110
|
+
addSegment(x, y, 'left', 'bottom');
|
|
111
|
+
addSegment(x, y, 'top', 'right');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const adjacency = new Map();
|
|
116
|
+
segments.forEach(([start, end], index) => {
|
|
117
|
+
const startKey = pointKey(start);
|
|
118
|
+
const endKey = pointKey(end);
|
|
119
|
+
adjacency.set(startKey, [...(adjacency.get(startKey) ?? []), index]);
|
|
120
|
+
adjacency.set(endKey, [...(adjacency.get(endKey) ?? []), index]);
|
|
121
|
+
});
|
|
122
|
+
const unused = new Set(segments.map((_, index) => index));
|
|
123
|
+
const contours = [];
|
|
124
|
+
const takeConnectedSegment = (key) => (adjacency.get(key) ?? []).find((index) => unused.has(index));
|
|
125
|
+
while (unused.size > 0) {
|
|
126
|
+
const firstSegmentIndex = unused.values().next().value;
|
|
127
|
+
unused.delete(firstSegmentIndex);
|
|
128
|
+
const [start, end] = segments[firstSegmentIndex];
|
|
129
|
+
const points = [start, end];
|
|
130
|
+
let currentKey = pointKey(end);
|
|
131
|
+
let closed = false;
|
|
132
|
+
while (true) {
|
|
133
|
+
const nextSegmentIndex = takeConnectedSegment(currentKey);
|
|
134
|
+
if (nextSegmentIndex === undefined)
|
|
135
|
+
break;
|
|
136
|
+
unused.delete(nextSegmentIndex);
|
|
137
|
+
const [nextStart, nextEnd] = segments[nextSegmentIndex];
|
|
138
|
+
const nextPoint = pointKey(nextStart) === currentKey ? nextEnd : nextStart;
|
|
139
|
+
const nextKey = pointKey(nextPoint);
|
|
140
|
+
if (nextKey === pointKey(points[0])) {
|
|
141
|
+
closed = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
points.push(nextPoint);
|
|
145
|
+
currentKey = nextKey;
|
|
146
|
+
}
|
|
147
|
+
const prepared = simplifyPoints(points, simplify);
|
|
148
|
+
if (prepared.length > 2)
|
|
149
|
+
contours.push({ points: prepared, closed });
|
|
150
|
+
}
|
|
151
|
+
return contours;
|
|
152
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './ImageMask.svelte';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ContourPath } from './types';
|
|
2
|
+
export declare function drawMarchingAnts({ context, contours, viewportWidth, viewportHeight, dashOffset }: {
|
|
3
|
+
context: CanvasRenderingContext2D | null;
|
|
4
|
+
contours: ContourPath[];
|
|
5
|
+
viewportWidth: number;
|
|
6
|
+
viewportHeight: number;
|
|
7
|
+
dashOffset: number;
|
|
8
|
+
}): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function contourPath(context, contour) {
|
|
2
|
+
const { points, closed } = contour;
|
|
3
|
+
if (points.length === 0)
|
|
4
|
+
return;
|
|
5
|
+
context.beginPath();
|
|
6
|
+
context.moveTo(points[0].x, points[0].y);
|
|
7
|
+
for (const point of points.slice(1)) {
|
|
8
|
+
context.lineTo(point.x, point.y);
|
|
9
|
+
}
|
|
10
|
+
if (closed)
|
|
11
|
+
context.closePath();
|
|
12
|
+
}
|
|
13
|
+
export function drawMarchingAnts({ context, contours, viewportWidth, viewportHeight, dashOffset }) {
|
|
14
|
+
if (!context)
|
|
15
|
+
return;
|
|
16
|
+
context.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
17
|
+
context.save();
|
|
18
|
+
context.lineCap = 'round';
|
|
19
|
+
context.lineJoin = 'round';
|
|
20
|
+
context.lineWidth = 2;
|
|
21
|
+
context.strokeStyle = 'rgb(0 0 0)';
|
|
22
|
+
context.setLineDash([8, 6]);
|
|
23
|
+
context.lineDashOffset = -dashOffset;
|
|
24
|
+
for (const contour of contours) {
|
|
25
|
+
contourPath(context, contour);
|
|
26
|
+
context.stroke();
|
|
27
|
+
}
|
|
28
|
+
context.restore();
|
|
29
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ImageMaskValue, MaskPoint, MaskStroke } from './types';
|
|
2
|
+
export declare function configureMaskContext(context: CanvasRenderingContext2D, dpr: number): void;
|
|
3
|
+
export declare function rebuildMaskSurface(context: CanvasRenderingContext2D | null, strokes: MaskStroke[], width: number, height: number): void;
|
|
4
|
+
export declare function drawMaskStrokeSegment(context: CanvasRenderingContext2D | null, previous: MaskPoint | null, from: MaskPoint, to: MaskPoint, stroke: Pick<MaskStroke, 'tool' | 'brushSize'>): void;
|
|
5
|
+
export declare function exportMaskValue(exportCanvas: HTMLCanvasElement | null, exportContext: CanvasRenderingContext2D | null, surfaceCanvas: HTMLCanvasElement | null, exportSize: number): ImageMaskValue | null;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export function configureMaskContext(context, dpr) {
|
|
2
|
+
context.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
3
|
+
context.lineCap = 'round';
|
|
4
|
+
context.lineJoin = 'round';
|
|
5
|
+
}
|
|
6
|
+
function smoothPath(context, points) {
|
|
7
|
+
if (points.length === 0)
|
|
8
|
+
return;
|
|
9
|
+
if (points.length < 3) {
|
|
10
|
+
context.beginPath();
|
|
11
|
+
context.moveTo(points[0].x, points[0].y);
|
|
12
|
+
for (const point of points.slice(1))
|
|
13
|
+
context.lineTo(point.x, point.y);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
context.beginPath();
|
|
17
|
+
context.moveTo(points[0].x, points[0].y);
|
|
18
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
19
|
+
const current = points[index];
|
|
20
|
+
const next = points[index + 1];
|
|
21
|
+
const previous = points[index - 1] ?? current;
|
|
22
|
+
const afterNext = points[index + 2] ?? next;
|
|
23
|
+
context.bezierCurveTo(current.x + (next.x - previous.x) / 6, current.y + (next.y - previous.y) / 6, next.x - (afterNext.x - current.x) / 6, next.y - (afterNext.y - current.y) / 6, next.x, next.y);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function rebuildMaskSurface(context, strokes, width, height) {
|
|
27
|
+
if (!context)
|
|
28
|
+
return;
|
|
29
|
+
context.clearRect(0, 0, width, height);
|
|
30
|
+
for (const stroke of strokes) {
|
|
31
|
+
smoothPath(context, stroke.points);
|
|
32
|
+
context.lineWidth = stroke.brushSize;
|
|
33
|
+
context.globalCompositeOperation =
|
|
34
|
+
stroke.tool === 'erase' ? 'destination-out' : 'source-over';
|
|
35
|
+
context.strokeStyle = 'rgb(0 0 0)';
|
|
36
|
+
context.stroke();
|
|
37
|
+
}
|
|
38
|
+
context.globalCompositeOperation = 'source-over';
|
|
39
|
+
}
|
|
40
|
+
export function drawMaskStrokeSegment(context, previous, from, to, stroke) {
|
|
41
|
+
if (!context)
|
|
42
|
+
return;
|
|
43
|
+
context.save();
|
|
44
|
+
context.lineCap = 'round';
|
|
45
|
+
context.lineJoin = 'round';
|
|
46
|
+
context.lineWidth = stroke.brushSize;
|
|
47
|
+
context.globalCompositeOperation = stroke.tool === 'erase' ? 'destination-out' : 'source-over';
|
|
48
|
+
context.strokeStyle = 'rgb(0 0 0)';
|
|
49
|
+
if (from.x === to.x && from.y === to.y) {
|
|
50
|
+
context.beginPath();
|
|
51
|
+
context.arc(from.x, from.y, stroke.brushSize / 2, 0, Math.PI * 2);
|
|
52
|
+
context.fillStyle = 'rgb(0 0 0)';
|
|
53
|
+
context.fill();
|
|
54
|
+
context.restore();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const start = previous
|
|
58
|
+
? { x: (previous.x + from.x) / 2, y: (previous.y + from.y) / 2 }
|
|
59
|
+
: from;
|
|
60
|
+
const end = { x: (from.x + to.x) / 2, y: (from.y + to.y) / 2 };
|
|
61
|
+
context.beginPath();
|
|
62
|
+
context.moveTo(start.x, start.y);
|
|
63
|
+
context.quadraticCurveTo(from.x, from.y, end.x, end.y);
|
|
64
|
+
context.stroke();
|
|
65
|
+
context.restore();
|
|
66
|
+
}
|
|
67
|
+
export function exportMaskValue(exportCanvas, exportContext, surfaceCanvas, exportSize) {
|
|
68
|
+
if (!exportCanvas || !exportContext || !surfaceCanvas)
|
|
69
|
+
return null;
|
|
70
|
+
const outputSize = Math.max(1, Math.round(exportSize));
|
|
71
|
+
exportCanvas.width = outputSize;
|
|
72
|
+
exportCanvas.height = outputSize;
|
|
73
|
+
exportContext.save();
|
|
74
|
+
exportContext.setTransform(1, 0, 0, 1, 0, 0);
|
|
75
|
+
exportContext.globalCompositeOperation = 'source-over';
|
|
76
|
+
exportContext.clearRect(0, 0, outputSize, outputSize);
|
|
77
|
+
exportContext.drawImage(surfaceCanvas, 0, 0, outputSize, outputSize);
|
|
78
|
+
exportContext.globalCompositeOperation = 'source-in';
|
|
79
|
+
exportContext.fillStyle = 'rgb(255 255 255)';
|
|
80
|
+
exportContext.fillRect(0, 0, outputSize, outputSize);
|
|
81
|
+
exportContext.globalCompositeOperation = 'destination-over';
|
|
82
|
+
exportContext.fillStyle = 'rgb(0 0 0)';
|
|
83
|
+
exportContext.fillRect(0, 0, outputSize, outputSize);
|
|
84
|
+
exportContext.restore();
|
|
85
|
+
return {
|
|
86
|
+
dataUrl: exportCanvas.toDataURL('image/png'),
|
|
87
|
+
width: outputSize,
|
|
88
|
+
height: outputSize,
|
|
89
|
+
mimeType: 'image/png',
|
|
90
|
+
format: 'black-white',
|
|
91
|
+
maskedColor: 'white',
|
|
92
|
+
unmaskedColor: 'black'
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ImageMaskValue = {
|
|
2
|
+
dataUrl: string;
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
mimeType: 'image/png';
|
|
6
|
+
format: 'black-white';
|
|
7
|
+
maskedColor: 'white';
|
|
8
|
+
unmaskedColor: 'black';
|
|
9
|
+
};
|
|
10
|
+
export type MaskPoint = {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
};
|
|
14
|
+
export type MaskTool = 'paint' | 'erase';
|
|
15
|
+
export type MaskStroke = {
|
|
16
|
+
tool: MaskTool;
|
|
17
|
+
points: MaskPoint[];
|
|
18
|
+
brushSize: number;
|
|
19
|
+
};
|
|
20
|
+
export type ContourPath = {
|
|
21
|
+
points: MaskPoint[];
|
|
22
|
+
closed: boolean;
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export const demo = {
|
|
3
|
+
name: 'Label',
|
|
4
|
+
description: 'Form label wrapper with top and left orientation.',
|
|
5
|
+
columnSpan: 2
|
|
6
|
+
};
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { HashIcon, UserIcon } from '@lucide/svelte';
|
|
11
|
+
import HStack from '../HStack';
|
|
12
|
+
import NumberField from '../NumberField';
|
|
13
|
+
import TextField from '../TextField';
|
|
14
|
+
import Label from './Label.svelte';
|
|
15
|
+
|
|
16
|
+
let name = $state('');
|
|
17
|
+
let amount = $state<number | null>(35);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<HStack gap={1} align="center">
|
|
21
|
+
<Label label="Name" icon={UserIcon} orientation="top">
|
|
22
|
+
<TextField bind:value={name} placeholder="Sveltely" />
|
|
23
|
+
</Label>
|
|
24
|
+
|
|
25
|
+
<Label label="Amount" icon={HashIcon} orientation="left">
|
|
26
|
+
<NumberField bind:value={amount} min={0} max={100} />
|
|
27
|
+
</Label>
|
|
28
|
+
</HStack>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const demo: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
columnSpan: number;
|
|
5
|
+
};
|
|
6
|
+
import Label from './Label.svelte';
|
|
7
|
+
declare const Label: import("svelte").Component<Record<string, never>, {}, "">;
|
|
8
|
+
type Label = ReturnType<typeof Label>;
|
|
9
|
+
export default Label;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component, Snippet } from 'svelte';
|
|
3
|
+
import type { LabelOrientation } from '../../../style/label';
|
|
4
|
+
import { layoutStyle, type LayoutProps } from '../../../style/layout';
|
|
5
|
+
import { surfaceStyle, type StyleProps } from '../../../style/surface';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
label?: string;
|
|
10
|
+
icon?: Component<{ class?: string; size?: number | string }>;
|
|
11
|
+
iconSize?: number | string;
|
|
12
|
+
iconColor?: string;
|
|
13
|
+
orientation?: LabelOrientation;
|
|
14
|
+
} & LayoutProps &
|
|
15
|
+
StyleProps;
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
children,
|
|
19
|
+
label,
|
|
20
|
+
icon,
|
|
21
|
+
iconSize,
|
|
22
|
+
iconColor,
|
|
23
|
+
orientation = 'top',
|
|
24
|
+
size,
|
|
25
|
+
width,
|
|
26
|
+
height,
|
|
27
|
+
minWidth,
|
|
28
|
+
minHeight,
|
|
29
|
+
maxWidth,
|
|
30
|
+
maxHeight,
|
|
31
|
+
grow,
|
|
32
|
+
shrink,
|
|
33
|
+
basis,
|
|
34
|
+
border,
|
|
35
|
+
overflow,
|
|
36
|
+
align,
|
|
37
|
+
justify,
|
|
38
|
+
fontSize,
|
|
39
|
+
paddingX,
|
|
40
|
+
paddingY,
|
|
41
|
+
gap,
|
|
42
|
+
borderRadius,
|
|
43
|
+
inset,
|
|
44
|
+
background,
|
|
45
|
+
borderColor,
|
|
46
|
+
color
|
|
47
|
+
}: Props = $props();
|
|
48
|
+
|
|
49
|
+
const layoutProps = $derived({
|
|
50
|
+
size,
|
|
51
|
+
width,
|
|
52
|
+
height,
|
|
53
|
+
minWidth,
|
|
54
|
+
minHeight,
|
|
55
|
+
maxWidth,
|
|
56
|
+
maxHeight,
|
|
57
|
+
grow,
|
|
58
|
+
shrink,
|
|
59
|
+
basis,
|
|
60
|
+
border,
|
|
61
|
+
overflow,
|
|
62
|
+
align,
|
|
63
|
+
justify
|
|
64
|
+
});
|
|
65
|
+
const styleProps = $derived({
|
|
66
|
+
fontSize,
|
|
67
|
+
paddingX,
|
|
68
|
+
paddingY,
|
|
69
|
+
gap,
|
|
70
|
+
borderRadius,
|
|
71
|
+
inset,
|
|
72
|
+
background,
|
|
73
|
+
borderColor,
|
|
74
|
+
color
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const rootStyle = $derived.by(() =>
|
|
78
|
+
[layoutStyle(layoutProps), surfaceStyle(styleProps, 'label')].filter(Boolean).join(' ')
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const iconStyle = $derived.by(() => {
|
|
82
|
+
const declarations: string[] = [];
|
|
83
|
+
|
|
84
|
+
if (iconColor !== undefined) {
|
|
85
|
+
declarations.push(`color: ${iconColor};`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (iconSize !== undefined) {
|
|
89
|
+
const sizeValue = typeof iconSize === 'number' ? `${iconSize}rem` : iconSize;
|
|
90
|
+
declarations.push(`width: ${sizeValue};`, `height: ${sizeValue};`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return declarations.join(' ');
|
|
94
|
+
});
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<div class="label" data-orientation={orientation} style={rootStyle}>
|
|
98
|
+
{#if icon || label}
|
|
99
|
+
<span class="label-heading">
|
|
100
|
+
{#if icon}
|
|
101
|
+
{@const Icon = icon}
|
|
102
|
+
<span class="label-icon-frame" style={iconStyle}>
|
|
103
|
+
<Icon class="label-icon" />
|
|
104
|
+
</span>
|
|
105
|
+
{/if}
|
|
106
|
+
|
|
107
|
+
{#if label}
|
|
108
|
+
<span class="label-text">{label}</span>
|
|
109
|
+
{/if}
|
|
110
|
+
</span>
|
|
111
|
+
{/if}
|
|
112
|
+
|
|
113
|
+
{#if children}
|
|
114
|
+
<div class="label-content">
|
|
115
|
+
{@render children()}
|
|
116
|
+
</div>
|
|
117
|
+
{/if}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<style>
|
|
121
|
+
.label {
|
|
122
|
+
--label-font-size: var(--sveltely-font-size);
|
|
123
|
+
--label-icon-size: calc(var(--label-font-size) * 1.143);
|
|
124
|
+
display: inline-flex;
|
|
125
|
+
width: 100%;
|
|
126
|
+
min-width: min-content;
|
|
127
|
+
min-height: min-content;
|
|
128
|
+
gap: var(--label-gap, calc(var(--sveltely-gap) * 0.75));
|
|
129
|
+
border-radius: var(--label-border-radius, 0px);
|
|
130
|
+
color: var(--label-color, var(--color-zinc-700));
|
|
131
|
+
font-size: var(--label-font-size);
|
|
132
|
+
padding:
|
|
133
|
+
var(--label-padding-y, 0px)
|
|
134
|
+
var(--label-padding-x, 0px);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.label[data-orientation='top'] {
|
|
138
|
+
flex-direction: column;
|
|
139
|
+
align-items: stretch;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.label[data-orientation='left'] {
|
|
143
|
+
flex-direction: row;
|
|
144
|
+
align-items: center;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.label-heading {
|
|
148
|
+
display: inline-flex;
|
|
149
|
+
min-width: max-content;
|
|
150
|
+
flex-shrink: 0;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: calc(var(--label-gap, var(--sveltely-gap)) * 0.67);
|
|
153
|
+
font-weight: 500;
|
|
154
|
+
line-height: 1.25;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.label-content {
|
|
158
|
+
display: inline-flex;
|
|
159
|
+
min-width: 0;
|
|
160
|
+
flex: 1 1 auto;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.label-icon-frame {
|
|
164
|
+
display: inline-flex;
|
|
165
|
+
width: var(--label-icon-size);
|
|
166
|
+
height: var(--label-icon-size);
|
|
167
|
+
flex-shrink: 0;
|
|
168
|
+
align-items: center;
|
|
169
|
+
justify-content: center;
|
|
170
|
+
color: var(--label-icon-color, currentColor);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.label-icon-frame :global(.label-icon) {
|
|
174
|
+
width: 100%;
|
|
175
|
+
height: 100%;
|
|
176
|
+
}
|
|
177
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Component, Snippet } from 'svelte';
|
|
2
|
+
import type { LabelOrientation } from '../../../style/label';
|
|
3
|
+
import { type LayoutProps } from '../../../style/layout';
|
|
4
|
+
import { type StyleProps } from '../../../style/surface';
|
|
5
|
+
type Props = {
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
label?: string;
|
|
8
|
+
icon?: Component<{
|
|
9
|
+
class?: string;
|
|
10
|
+
size?: number | string;
|
|
11
|
+
}>;
|
|
12
|
+
iconSize?: number | string;
|
|
13
|
+
iconColor?: string;
|
|
14
|
+
orientation?: LabelOrientation;
|
|
15
|
+
} & LayoutProps & StyleProps;
|
|
16
|
+
declare const Label: Component<Props, {}, "">;
|
|
17
|
+
type Label = ReturnType<typeof Label>;
|
|
18
|
+
export default Label;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Label.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Label.svelte';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export const demo = {
|
|
3
|
+
name: 'NumberField',
|
|
4
|
+
description: 'Token-aware numeric input with min, max, and step support.'
|
|
5
|
+
};
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import NumberField from './NumberField.svelte';
|
|
10
|
+
|
|
11
|
+
let value = $state<number | null>(35);
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<NumberField
|
|
15
|
+
bind:value
|
|
16
|
+
min={0}
|
|
17
|
+
max={100}
|
|
18
|
+
step={1}
|
|
19
|
+
placeholder="0"
|
|
20
|
+
help={`Value: ${value ?? 'empty'}`}
|
|
21
|
+
/>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const demo: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
};
|
|
5
|
+
import NumberField from './NumberField.svelte';
|
|
6
|
+
declare const NumberField: import("svelte").Component<Record<string, never>, {}, "">;
|
|
7
|
+
type NumberField = ReturnType<typeof NumberField>;
|
|
8
|
+
export default NumberField;
|