@x33025/sveltely 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/components/Library/AnimatedNumber/AnimatedNumber.demo.svelte +1 -1
  2. package/dist/components/Library/AsyncButton/AsyncButton.svelte +42 -16
  3. package/dist/components/Library/Button/Button.demo.svelte +5 -3
  4. package/dist/components/Library/Button/Button.demo.svelte.d.ts +1 -0
  5. package/dist/components/Library/Button/Button.svelte +21 -13
  6. package/dist/components/Library/Calendar/Calendar.demo.svelte +2 -14
  7. package/dist/components/Library/Calendar/Calendar.svelte +69 -65
  8. package/dist/components/Library/Checkbox/Checkbox.svelte +13 -14
  9. package/dist/components/Library/ChipInput/ChipInput.demo.svelte +1 -1
  10. package/dist/components/Library/ChipInput/ChipInput.svelte +7 -4
  11. package/dist/components/Library/Divider/Divider.svelte +10 -0
  12. package/dist/components/Library/Divider/Divider.svelte.d.ts +26 -0
  13. package/dist/components/Library/Divider/index.d.ts +1 -0
  14. package/dist/components/Library/Divider/index.js +1 -0
  15. package/dist/components/Library/Dropdown/Action.svelte +60 -0
  16. package/dist/components/Library/Dropdown/Action.svelte.d.ts +11 -0
  17. package/dist/components/Library/Dropdown/Divider.svelte +5 -0
  18. package/dist/components/Library/Dropdown/Divider.svelte.d.ts +19 -0
  19. package/dist/components/Library/Dropdown/Dropdown.demo.svelte +182 -65
  20. package/dist/components/Library/Dropdown/Dropdown.demo.svelte.d.ts +2 -1
  21. package/dist/components/Library/Dropdown/Dropdown.svelte +95 -250
  22. package/dist/components/Library/Dropdown/Dropdown.svelte.d.ts +17 -16
  23. package/dist/components/Library/Dropdown/Item.svelte +68 -0
  24. package/dist/components/Library/Dropdown/Item.svelte.d.ts +31 -0
  25. package/dist/components/Library/Dropdown/Section.svelte +34 -0
  26. package/dist/components/Library/Dropdown/Section.svelte.d.ts +8 -0
  27. package/dist/components/Library/Dropdown/context.d.ts +34 -0
  28. package/dist/components/Library/Dropdown/context.js +6 -0
  29. package/dist/components/Library/Dropdown/index.d.ts +13 -2
  30. package/dist/components/Library/Dropdown/index.js +11 -1
  31. package/dist/components/Library/Floating/Floating.svelte +44 -7
  32. package/dist/components/Library/ForEach/ForEach.svelte +14 -0
  33. package/dist/components/Library/ForEach/ForEach.svelte.d.ts +28 -0
  34. package/dist/components/Library/ForEach/index.d.ts +1 -0
  35. package/dist/components/Library/ForEach/index.js +1 -0
  36. package/dist/components/Library/Grid/Grid.svelte +74 -0
  37. package/dist/components/Library/Grid/Grid.svelte.d.ts +13 -0
  38. package/dist/components/Library/Grid/index.d.ts +1 -0
  39. package/dist/components/Library/Grid/index.js +1 -0
  40. package/dist/components/Library/GridItem/GridItem.svelte +65 -0
  41. package/dist/components/Library/GridItem/GridItem.svelte.d.ts +14 -0
  42. package/dist/components/Library/GridItem/index.d.ts +1 -0
  43. package/dist/components/Library/GridItem/index.js +1 -0
  44. package/dist/components/Library/HStack/HStack.svelte +45 -0
  45. package/dist/components/Library/HStack/HStack.svelte.d.ts +9 -0
  46. package/dist/components/Library/HStack/index.d.ts +1 -0
  47. package/dist/components/Library/HStack/index.js +1 -0
  48. package/dist/components/Library/Image/Image.demo.svelte +18 -0
  49. package/dist/components/Library/Image/Image.demo.svelte.d.ts +23 -0
  50. package/dist/components/Library/Image/Image.svelte +57 -0
  51. package/dist/components/Library/Image/Image.svelte.d.ts +17 -0
  52. package/dist/components/Library/Image/ImagePlaceholder.svelte +202 -0
  53. package/dist/components/Library/Image/ImagePlaceholder.svelte.d.ts +7 -0
  54. package/dist/components/Library/Image/index.d.ts +1 -0
  55. package/dist/components/Library/Image/index.js +1 -0
  56. package/dist/components/Library/ImageMask/BrushPreview.svelte +119 -0
  57. package/dist/components/Library/ImageMask/BrushPreview.svelte.d.ts +11 -0
  58. package/dist/components/Library/ImageMask/ImageMask.demo.svelte +117 -0
  59. package/dist/components/Library/ImageMask/ImageMask.demo.svelte.d.ts +10 -0
  60. package/dist/components/Library/ImageMask/ImageMask.svelte +46 -0
  61. package/dist/components/Library/ImageMask/ImageMask.svelte.d.ts +20 -0
  62. package/dist/components/Library/ImageMask/MaskLayer.svelte +341 -0
  63. package/dist/components/Library/ImageMask/MaskLayer.svelte.d.ts +12 -0
  64. package/dist/components/Library/ImageMask/contour.d.ts +11 -0
  65. package/dist/components/Library/ImageMask/contour.js +152 -0
  66. package/dist/components/Library/ImageMask/index.d.ts +2 -0
  67. package/dist/components/Library/ImageMask/index.js +1 -0
  68. package/dist/components/Library/ImageMask/marchingAnts.d.ts +8 -0
  69. package/dist/components/Library/ImageMask/marchingAnts.js +29 -0
  70. package/dist/components/Library/ImageMask/maskSurface.d.ts +5 -0
  71. package/dist/components/Library/ImageMask/maskSurface.js +94 -0
  72. package/dist/components/Library/ImageMask/types.d.ts +23 -0
  73. package/dist/components/Library/Label/Label.demo.svelte +28 -0
  74. package/dist/components/Library/Label/Label.demo.svelte.d.ts +9 -0
  75. package/dist/components/Library/Label/Label.svelte +175 -0
  76. package/dist/components/Library/Label/Label.svelte.d.ts +18 -0
  77. package/dist/components/Library/Label/index.d.ts +1 -0
  78. package/dist/components/Library/Label/index.js +1 -0
  79. package/dist/components/Library/NavigationStack/NavigationStack.svelte +17 -7
  80. package/dist/components/Library/NavigationStack/Toolbar.svelte +7 -2
  81. package/dist/components/Library/NumberField/NumberField.demo.svelte +21 -0
  82. package/dist/components/Library/NumberField/NumberField.demo.svelte.d.ts +8 -0
  83. package/dist/components/Library/NumberField/NumberField.svelte +199 -0
  84. package/dist/components/Library/NumberField/NumberField.svelte.d.ts +21 -0
  85. package/dist/components/Library/NumberField/index.d.ts +1 -0
  86. package/dist/components/Library/NumberField/index.js +1 -0
  87. package/dist/components/Library/Pagination/Pagination.svelte +16 -20
  88. package/dist/components/Library/Popover/Popover.demo.svelte +2 -2
  89. package/dist/components/Library/Popover/Popover.svelte +7 -4
  90. package/dist/components/Library/ScrollView/ScrollView.svelte +165 -12
  91. package/dist/components/Library/ScrollView/ScrollView.svelte.d.ts +32 -4
  92. package/dist/components/Library/ScrollView/index.d.ts +1 -0
  93. package/dist/components/Library/{SearchInput/SearchInput.demo.svelte → SearchField/SearchField.demo.svelte} +4 -4
  94. package/dist/components/Library/SearchField/SearchField.demo.svelte.d.ts +8 -0
  95. package/dist/components/Library/{SearchInput/SearchInput.svelte → SearchField/SearchField.svelte} +26 -30
  96. package/dist/components/Library/{SearchInput/SearchInput.svelte.d.ts → SearchField/SearchField.svelte.d.ts} +3 -3
  97. package/dist/components/Library/SearchField/index.d.ts +1 -0
  98. package/dist/components/Library/SearchField/index.js +1 -0
  99. package/dist/components/Library/SegmentedPicker/SegmentedPicker.demo.svelte +1 -1
  100. package/dist/components/Library/SegmentedPicker/SegmentedPicker.svelte +9 -9
  101. package/dist/components/Library/Sheet/Sheet.demo.svelte +1 -1
  102. package/dist/components/Library/Sheet/Sheet.svelte +8 -5
  103. package/dist/components/Library/Slider/Slider.demo.svelte +1 -1
  104. package/dist/components/Library/Slider/Slider.svelte +11 -10
  105. package/dist/components/Library/Spacer/Spacer.svelte +7 -0
  106. package/dist/components/Library/Spacer/Spacer.svelte.d.ts +26 -0
  107. package/dist/components/Library/Spacer/index.d.ts +1 -0
  108. package/dist/components/Library/Spacer/index.js +1 -0
  109. package/dist/components/Library/Spinner/Spinner.demo.svelte +1 -1
  110. package/dist/components/Library/Switch/Switch.svelte +6 -11
  111. package/dist/components/Library/TextField/TextField.demo.svelte +14 -0
  112. package/dist/components/Library/TextField/TextField.demo.svelte.d.ts +8 -0
  113. package/dist/components/Library/TextField/TextField.svelte +154 -0
  114. package/dist/components/Library/TextField/TextField.svelte.d.ts +19 -0
  115. package/dist/components/Library/TextField/index.d.ts +1 -0
  116. package/dist/components/Library/TextField/index.js +1 -0
  117. package/dist/components/Library/{TokenSearchInput/TokenSearchInput.demo.svelte → TokenSearchField/TokenSearchField.demo.svelte} +4 -4
  118. package/dist/components/Library/TokenSearchField/TokenSearchField.demo.svelte.d.ts +9 -0
  119. package/dist/components/Library/{TokenSearchInput/TokenSearchInput.svelte → TokenSearchField/TokenSearchField.svelte} +70 -66
  120. package/dist/components/Library/{TokenSearchInput/TokenSearchInput.svelte.d.ts → TokenSearchField/TokenSearchField.svelte.d.ts} +3 -3
  121. package/dist/components/Library/TokenSearchField/index.d.ts +1 -0
  122. package/dist/components/Library/TokenSearchField/index.js +1 -0
  123. package/dist/components/Library/VStack/VStack.svelte +45 -0
  124. package/dist/components/Library/VStack/VStack.svelte.d.ts +9 -0
  125. package/dist/components/Library/VStack/index.d.ts +1 -0
  126. package/dist/components/Library/VStack/index.js +1 -0
  127. package/dist/components/Library/WheelPicker/WheelColumn.svelte +4 -10
  128. package/dist/components/Library/WheelPicker/WheelPicker.svelte +5 -9
  129. package/dist/components/Local/ComponentGrid.svelte +15 -31
  130. package/dist/components/Local/HeroCard.svelte +30 -38
  131. package/dist/components/Local/HeroCard.svelte.d.ts +0 -2
  132. package/dist/components/Local/StyleControls.svelte +58 -27
  133. package/dist/components/Local/StyleControls.svelte.d.ts +3 -1
  134. package/dist/index.d.ts +26 -2
  135. package/dist/index.js +19 -2
  136. package/dist/style/index.css +35 -20
  137. package/dist/style/label.d.ts +6 -0
  138. package/dist/style/label.js +4 -0
  139. package/dist/style/layout.d.ts +57 -0
  140. package/dist/style/layout.js +128 -0
  141. package/dist/style/media.d.ts +12 -0
  142. package/dist/style/media.js +8 -0
  143. package/dist/style/scroll.d.ts +7 -0
  144. package/dist/style/scroll.js +5 -0
  145. package/dist/style/text-editor.d.ts +34 -0
  146. package/dist/style/text-editor.js +29 -0
  147. package/dist/style.css +112 -58
  148. package/package.json +1 -1
  149. package/dist/components/Library/Dropdown/types.d.ts +0 -27
  150. package/dist/components/Library/SearchInput/SearchInput.demo.svelte.d.ts +0 -8
  151. package/dist/components/Library/SearchInput/index.d.ts +0 -1
  152. package/dist/components/Library/SearchInput/index.js +0 -1
  153. package/dist/components/Library/TokenSearchInput/TokenSearchInput.demo.svelte.d.ts +0 -9
  154. package/dist/components/Library/TokenSearchInput/index.d.ts +0 -1
  155. package/dist/components/Library/TokenSearchInput/index.js +0 -1
  156. /package/dist/components/Library/{Dropdown → ImageMask}/types.js +0 -0
@@ -0,0 +1,202 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+
4
+ let {
5
+ class: className = '',
6
+ seed = 'default'
7
+ }: {
8
+ class?: string;
9
+ seed?: string;
10
+ } = $props();
11
+
12
+ const fps = 24;
13
+ const scale = 3.2;
14
+ const speed = 0.18;
15
+ const contrast = 1.6;
16
+ let canvas: HTMLCanvasElement | null = null;
17
+ let context2d: CanvasRenderingContext2D | null = null;
18
+ let resizeObserver: ResizeObserver | null = null;
19
+ let animationFrame = 0;
20
+ let lastUpdate = 0;
21
+ let startTime = 0;
22
+ let viewportWidth = 1;
23
+ let viewportHeight = 1;
24
+ let devicePixelRatio = 1;
25
+ let canvasPixelWidth = 0;
26
+ let canvasPixelHeight = 0;
27
+
28
+ function hashString(value: string): number {
29
+ let hash = 2_166_136_261;
30
+
31
+ for (let i = 0; i < value.length; i += 1) {
32
+ hash ^= value.charCodeAt(i);
33
+ hash = Math.imul(hash, 16_777_619);
34
+ }
35
+
36
+ return hash >>> 0;
37
+ }
38
+
39
+ const seedValue = $derived(hashString(seed));
40
+ const seedX = $derived(seedValue % 10_000);
41
+ const seedY = $derived((seedValue >>> 8) % 10_000);
42
+ const seedZ = $derived((seedValue >>> 16) % 10_000);
43
+
44
+ function fade(t: number): number {
45
+ return t * t * t * (t * (t * 6 - 15) + 10);
46
+ }
47
+
48
+ function lerp(a: number, b: number, t: number): number {
49
+ return a + (b - a) * t;
50
+ }
51
+
52
+ function clamp01(value: number): number {
53
+ return Math.max(0, Math.min(1, value));
54
+ }
55
+
56
+ function hash3D(x: number, y: number, z: number): number {
57
+ let n = Math.imul(x, 374_761_393) ^ Math.imul(y, 668_265_263) ^ Math.imul(z, 2_147_483_647);
58
+ n = (n ^ (n >>> 13)) >>> 0;
59
+ n = Math.imul(n, 1_274_126_177) >>> 0;
60
+ return ((n ^ (n >>> 16)) >>> 0) / 4_294_967_295;
61
+ }
62
+
63
+ function valueNoise3D(x: number, y: number, z: number): number {
64
+ const x0 = Math.floor(x);
65
+ const y0 = Math.floor(y);
66
+ const z0 = Math.floor(z);
67
+ const x1 = x0 + 1;
68
+ const y1 = y0 + 1;
69
+ const z1 = z0 + 1;
70
+ const sx = fade(x - x0);
71
+ const sy = fade(y - y0);
72
+ const sz = fade(z - z0);
73
+ const z00 = lerp(hash3D(x0, y0, z0), hash3D(x1, y0, z0), sx);
74
+ const z10 = lerp(hash3D(x0, y1, z0), hash3D(x1, y1, z0), sx);
75
+ const z01 = lerp(hash3D(x0, y0, z1), hash3D(x1, y0, z1), sx);
76
+ const z11 = lerp(hash3D(x0, y1, z1), hash3D(x1, y1, z1), sx);
77
+ return lerp(lerp(z00, z10, sy), lerp(z01, z11, sy), sz);
78
+ }
79
+
80
+ function fbm3D(x: number, y: number, z: number): number {
81
+ let total = 0;
82
+ let amplitude = 0.5;
83
+ let frequency = 1;
84
+ let amplitudeSum = 0;
85
+
86
+ for (let i = 0; i < 3; i += 1) {
87
+ total += valueNoise3D(x * frequency, y * frequency, z * frequency) * amplitude;
88
+ amplitudeSum += amplitude;
89
+ amplitude *= 0.5;
90
+ frequency *= 2;
91
+ }
92
+
93
+ return total / amplitudeSum;
94
+ }
95
+
96
+ function updateCanvasDimensions() {
97
+ if (!canvas) return;
98
+ const rect = canvas.parentElement?.getBoundingClientRect() ?? canvas.getBoundingClientRect();
99
+ const dpr = window.devicePixelRatio || 1;
100
+ devicePixelRatio = dpr;
101
+ viewportWidth = Math.max(1, rect.width);
102
+ viewportHeight = Math.max(1, rect.height);
103
+ const nextCanvasPixelWidth = Math.max(1, Math.round(viewportWidth * dpr));
104
+ const nextCanvasPixelHeight = Math.max(1, Math.round(viewportHeight * dpr));
105
+
106
+ if (
107
+ nextCanvasPixelWidth === canvasPixelWidth &&
108
+ nextCanvasPixelHeight === canvasPixelHeight &&
109
+ context2d
110
+ ) {
111
+ return;
112
+ }
113
+
114
+ canvasPixelWidth = nextCanvasPixelWidth;
115
+ canvasPixelHeight = nextCanvasPixelHeight;
116
+ canvas.width = nextCanvasPixelWidth;
117
+ canvas.height = nextCanvasPixelHeight;
118
+ canvas.style.width = `${viewportWidth}px`;
119
+ canvas.style.height = `${viewportHeight}px`;
120
+ context2d = canvas.getContext('2d', { alpha: true });
121
+ context2d?.setTransform(dpr, 0, 0, dpr, 0, 0);
122
+ }
123
+
124
+ function toChannel(value: number, low: number, high: number): number {
125
+ return Math.round(lerp(low, high, clamp01(value)));
126
+ }
127
+
128
+ function drawFrame(time: number) {
129
+ if (!context2d) return;
130
+ context2d.clearRect(0, 0, viewportWidth, viewportHeight);
131
+
132
+ const phase = time * speed;
133
+ const background = context2d.createLinearGradient(0, 0, viewportWidth, viewportHeight);
134
+ background.addColorStop(0, 'rgb(248 248 249)');
135
+ background.addColorStop(0.45, 'rgb(241 241 243)');
136
+ background.addColorStop(1, 'rgb(232 232 235)');
137
+ context2d.fillStyle = background;
138
+ context2d.fillRect(0, 0, viewportWidth, viewportHeight);
139
+
140
+ const columns = Math.max(10, Math.ceil(viewportWidth / 22));
141
+ const rows = Math.max(10, Math.ceil(viewportHeight / 22));
142
+ const cellWidth = viewportWidth / columns;
143
+ const cellHeight = viewportHeight / rows;
144
+
145
+ for (let row = 0; row < rows; row += 1) {
146
+ for (let col = 0; col < columns; col += 1) {
147
+ const nx = col / Math.max(1, columns - 1);
148
+ const ny = row / Math.max(1, rows - 1);
149
+ const noise = fbm3D(
150
+ nx * scale + seedX + Math.cos(phase + ny * 2.4) * 0.22,
151
+ ny * scale + seedY + Math.sin(phase * 1.2 + nx * 2.1) * 0.22,
152
+ phase + seedZ
153
+ );
154
+ const centered = (noise - 0.5) * 2;
155
+ const remapped = Math.tanh(centered * contrast) * 0.5 + 0.5;
156
+ const shimmer = 0.5 + 0.5 * Math.sin(phase * 2.2 + nx * 7 + ny * 5);
157
+ const grayscale = toChannel(remapped * 0.8 + shimmer * 0.2, 148, 248);
158
+ const x = col * cellWidth;
159
+ const y = row * cellHeight;
160
+
161
+ context2d.fillStyle = `rgb(${grayscale} ${grayscale} ${grayscale})`;
162
+ context2d.fillRect(x, y, cellWidth, cellHeight);
163
+ context2d.strokeStyle = 'rgba(255 255 255 / 0.36)';
164
+ context2d.lineWidth = 1 / devicePixelRatio;
165
+ context2d.strokeRect(
166
+ x + 0.5 / devicePixelRatio,
167
+ y + 0.5 / devicePixelRatio,
168
+ Math.max(0, cellWidth - 1 / devicePixelRatio),
169
+ Math.max(0, cellHeight - 1 / devicePixelRatio)
170
+ );
171
+ }
172
+ }
173
+ }
174
+
175
+ function tick(timestamp: number) {
176
+ if (startTime === 0) startTime = timestamp;
177
+ if (timestamp - lastUpdate >= 1000 / fps) {
178
+ drawFrame((timestamp - startTime) / 1000);
179
+ lastUpdate = timestamp;
180
+ }
181
+ animationFrame = requestAnimationFrame(tick);
182
+ }
183
+
184
+ onMount(() => {
185
+ updateCanvasDimensions();
186
+ resizeObserver = new ResizeObserver(() => {
187
+ updateCanvasDimensions();
188
+ drawFrame((performance.now() - startTime) / 1000);
189
+ });
190
+ if (canvas?.parentElement) resizeObserver.observe(canvas.parentElement);
191
+ animationFrame = requestAnimationFrame(tick);
192
+ window.addEventListener('resize', updateCanvasDimensions);
193
+
194
+ return () => {
195
+ cancelAnimationFrame(animationFrame);
196
+ resizeObserver?.disconnect();
197
+ window.removeEventListener('resize', updateCanvasDimensions);
198
+ };
199
+ });
200
+ </script>
201
+
202
+ <canvas bind:this={canvas} class={`block size-full ${className}`}></canvas>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ class?: string;
3
+ seed?: string;
4
+ };
5
+ declare const ImagePlaceholder: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type ImagePlaceholder = ReturnType<typeof ImagePlaceholder>;
7
+ export default ImagePlaceholder;
@@ -0,0 +1 @@
1
+ export { default } from './Image.svelte';
@@ -0,0 +1 @@
1
+ export { default } from './Image.svelte';
@@ -0,0 +1,119 @@
1
+ <script lang="ts">
2
+ import type { MaskPoint, MaskTool } from './types';
3
+
4
+ let {
5
+ point = null,
6
+ visible = false,
7
+ brushSize = 24,
8
+ tool = 'paint',
9
+ sizeLabelVisible = false
10
+ }: {
11
+ point?: MaskPoint | null;
12
+ visible?: boolean;
13
+ brushSize?: number;
14
+ tool?: MaskTool;
15
+ sizeLabelVisible?: boolean;
16
+ } = $props();
17
+
18
+ const brushPreviewStyle = $derived.by(() => {
19
+ if (!point) return '';
20
+ return [
21
+ `width: ${brushSize}px;`,
22
+ `height: ${brushSize}px;`,
23
+ `transform: translate(${point.x - brushSize / 2}px, ${point.y - brushSize / 2}px);`
24
+ ].join(' ');
25
+ });
26
+ const brushSizeLabel = $derived(`${Math.round(brushSize)}px`);
27
+ const brushSizePreviewStyle = $derived(`width: ${brushSize}px; height: ${brushSize}px;`);
28
+ </script>
29
+
30
+ {#if visible && point}
31
+ <div
32
+ class="brush-preview"
33
+ class:brush-preview-erase={tool === 'erase'}
34
+ style={brushPreviewStyle}
35
+ ></div>
36
+ {/if}
37
+
38
+ {#if sizeLabelVisible}
39
+ <div
40
+ class="brush-size-preview"
41
+ class:brush-size-preview-erase={tool === 'erase'}
42
+ aria-hidden="true"
43
+ >
44
+ <div class="brush-size-preview-circle" style={brushSizePreviewStyle}></div>
45
+ <span class="brush-size-preview-label">{brushSizeLabel}</span>
46
+ </div>
47
+ {/if}
48
+
49
+ <style>
50
+ .brush-preview {
51
+ position: absolute;
52
+ top: 0;
53
+ left: 0;
54
+ z-index: 11;
55
+ box-sizing: border-box;
56
+ pointer-events: none;
57
+ border: 1.5px solid rgb(255 255 255 / 0.95);
58
+ border-radius: 9999px;
59
+ background: color-mix(in oklab, var(--sveltely-active-color) 16%, transparent);
60
+ box-shadow:
61
+ 0 0 0 1px color-mix(in oklab, var(--sveltely-active-color) 90%, transparent),
62
+ 0 1px 4px rgb(0 0 0 / 0.22);
63
+ will-change: transform, width, height;
64
+ }
65
+
66
+ .brush-preview-erase {
67
+ background: rgb(255 255 255 / 0.18);
68
+ box-shadow:
69
+ 0 0 0 1px rgb(24 24 27 / 0.8),
70
+ 0 1px 4px rgb(0 0 0 / 0.22);
71
+ }
72
+
73
+ .brush-size-preview {
74
+ position: absolute;
75
+ top: 50%;
76
+ left: 50%;
77
+ z-index: 12;
78
+ display: inline-flex;
79
+ flex-direction: column;
80
+ align-items: center;
81
+ justify-content: center;
82
+ gap: 0.5rem;
83
+ pointer-events: none;
84
+ transform: translate(-50%, -50%);
85
+ }
86
+
87
+ .brush-size-preview-circle {
88
+ box-sizing: border-box;
89
+ border-radius: 9999px;
90
+ border: 1.5px solid rgb(255 255 255 / 0.95);
91
+ background: color-mix(in oklab, var(--sveltely-active-color) 20%, transparent);
92
+ box-shadow:
93
+ 0 0 0 1px color-mix(in oklab, var(--sveltely-active-color) 95%, transparent),
94
+ 0 8px 24px rgb(0 0 0 / 0.24);
95
+ }
96
+
97
+ .brush-size-preview-label {
98
+ border-radius: 9999px;
99
+ background: rgb(255 255 255 / 0.88);
100
+ color: var(--sveltely-primary-color);
101
+ font-size: 0.75rem;
102
+ font-weight: 650;
103
+ line-height: 1;
104
+ padding: 0.25rem 0.375rem;
105
+ box-shadow: 0 1px 6px rgb(0 0 0 / 0.16);
106
+ text-shadow:
107
+ 0 1px 0 rgb(255 255 255 / 0.9),
108
+ 0 -1px 0 rgb(255 255 255 / 0.9),
109
+ 1px 0 0 rgb(255 255 255 / 0.9),
110
+ -1px 0 0 rgb(255 255 255 / 0.9);
111
+ }
112
+
113
+ .brush-size-preview-erase .brush-size-preview-circle {
114
+ background: rgb(255 255 255 / 0.2);
115
+ box-shadow:
116
+ 0 0 0 1px color-mix(in oklab, var(--sveltely-active-color) 85%, transparent),
117
+ 0 8px 24px rgb(0 0 0 / 0.24);
118
+ }
119
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { MaskPoint, MaskTool } from './types';
2
+ type $$ComponentProps = {
3
+ point?: MaskPoint | null;
4
+ visible?: boolean;
5
+ brushSize?: number;
6
+ tool?: MaskTool;
7
+ sizeLabelVisible?: boolean;
8
+ };
9
+ declare const BrushPreview: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type BrushPreview = ReturnType<typeof BrushPreview>;
11
+ export default BrushPreview;
@@ -0,0 +1,117 @@
1
+ <script module lang="ts">
2
+ export const demo = {
3
+ name: 'ImageMask',
4
+ description: 'Image mask editor that exposes the exported mask through bind:mask.',
5
+ columnSpan: 2,
6
+ rowSpan: 2
7
+ };
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import { Brush, Eraser, X } from '@lucide/svelte';
12
+ import Slider from '../Slider';
13
+ import ImageMask from './ImageMask.svelte';
14
+ import type { ImageMaskValue, MaskTool } from './types';
15
+
16
+ let mask = $state<ImageMaskValue | null>(null);
17
+ let brushSize = $state(24);
18
+ let tool = $state<MaskTool>('paint');
19
+ let clearRevision = $state(0);
20
+ </script>
21
+
22
+ <div class="grid items-start gap-3 md:grid-cols-[minmax(0,18rem)_minmax(8rem,1fr)]">
23
+ <ImageMask
24
+ src="https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=700&q=80"
25
+ alt="Red sneaker"
26
+ bind:mask
27
+ bind:brushSize
28
+ bind:tool
29
+ bind:clearRevision
30
+ fit="cover"
31
+ class="aspect-square"
32
+ />
33
+ <div class="vstack gap-2 text-sm text-[var(--sveltely-secondary-color)]">
34
+ <div class="font-medium text-[var(--sveltely-primary-color)]">Mask</div>
35
+ <div class="aspect-square overflow-hidden rounded-md border border-zinc-200 bg-black">
36
+ {#if mask}
37
+ <img src={mask.dataUrl} alt="Exported mask" class="size-full object-contain" />
38
+ {/if}
39
+ </div>
40
+ <label class="vstack gap-2 bg-white p-3">
41
+ <span class="font-medium text-[var(--sveltely-primary-color)]">Brush size {brushSize}px</span>
42
+ <Slider bind:value={brushSize} min={6} max={72} step={1} />
43
+ <div class="image-mask-controls">
44
+ <button
45
+ type="button"
46
+ class:active={tool === 'paint'}
47
+ aria-label="Paint mask"
48
+ onclick={() => {
49
+ tool = 'paint';
50
+ }}
51
+ >
52
+ <Brush size={16} />
53
+ </button>
54
+ <button
55
+ type="button"
56
+ class:active={tool === 'erase'}
57
+ aria-label="Erase mask"
58
+ disabled={!mask}
59
+ onclick={() => {
60
+ tool = 'erase';
61
+ }}
62
+ >
63
+ <Eraser size={16} />
64
+ </button>
65
+ <div class="h-5 w-px bg-zinc-200"></div>
66
+ <button
67
+ type="button"
68
+ aria-label="Clear mask"
69
+ disabled={!mask}
70
+ onclick={() => {
71
+ mask = null;
72
+ clearRevision += 1;
73
+ tool = 'paint';
74
+ }}
75
+ >
76
+ <X size={16} />
77
+ </button>
78
+ </div>
79
+ </label>
80
+ </div>
81
+ </div>
82
+
83
+ <style>
84
+ .image-mask-controls {
85
+ display: inline-flex;
86
+ width: fit-content;
87
+ align-items: center;
88
+ gap: 0.25rem;
89
+ background: var(--sveltely-background-color);
90
+ padding: 0.25rem;
91
+ }
92
+
93
+ .image-mask-controls button {
94
+ display: inline-flex;
95
+ width: 2rem;
96
+ height: 2rem;
97
+ align-items: center;
98
+ justify-content: center;
99
+ border-radius: 9999px;
100
+ color: var(--sveltely-secondary-color);
101
+ transition:
102
+ background-color 150ms,
103
+ color 150ms,
104
+ opacity 150ms;
105
+ }
106
+
107
+ .image-mask-controls button:hover:not(:disabled),
108
+ .image-mask-controls button.active {
109
+ background: var(--sveltely-active-color);
110
+ color: var(--sveltely-background-color);
111
+ }
112
+
113
+ .image-mask-controls button:disabled {
114
+ cursor: not-allowed;
115
+ opacity: 0.4;
116
+ }
117
+ </style>
@@ -0,0 +1,10 @@
1
+ export declare const demo: {
2
+ name: string;
3
+ description: string;
4
+ columnSpan: number;
5
+ rowSpan: number;
6
+ };
7
+ import ImageMask from './ImageMask.svelte';
8
+ declare const ImageMask: import("svelte").Component<Record<string, never>, {}, "">;
9
+ type ImageMask = ReturnType<typeof ImageMask>;
10
+ export default ImageMask;
@@ -0,0 +1,46 @@
1
+ <script lang="ts">
2
+ import Image from '../Image';
3
+ import type { ImageFit, ImageLoading } from '../../../style/media';
4
+ import MaskLayer from './MaskLayer.svelte';
5
+ import type { ImageMaskValue, MaskTool } from './types';
6
+
7
+ let {
8
+ src = null,
9
+ alt = '',
10
+ mask = $bindable<ImageMaskValue | null>(null),
11
+ fit = 'contain',
12
+ busy = false,
13
+ disabled = false,
14
+ placeholderKey = 'default',
15
+ loading = 'lazy',
16
+ brushSize = $bindable(24),
17
+ tool = $bindable<MaskTool>('paint'),
18
+ clearRevision = $bindable(0),
19
+ exportSize = 2048,
20
+ class: className = ''
21
+ }: {
22
+ src?: string | null;
23
+ alt?: string;
24
+ mask?: ImageMaskValue | null;
25
+ fit?: ImageFit;
26
+ busy?: boolean;
27
+ disabled?: boolean;
28
+ placeholderKey?: string;
29
+ loading?: ImageLoading;
30
+ brushSize?: number;
31
+ tool?: MaskTool;
32
+ clearRevision?: number;
33
+ exportSize?: number;
34
+ class?: string;
35
+ } = $props();
36
+
37
+ const canEdit = $derived(Boolean(src) && !busy && !disabled);
38
+ </script>
39
+
40
+ <div class={`image-mask relative ${className}`}>
41
+ <Image {src} {alt} {fit} {busy} {placeholderKey} {loading} class="size-full">
42
+ {#if canEdit}
43
+ <MaskLayer bind:mask {tool} {brushSize} {exportSize} {disabled} {clearRevision} />
44
+ {/if}
45
+ </Image>
46
+ </div>
@@ -0,0 +1,20 @@
1
+ import type { ImageFit, ImageLoading } from '../../../style/media';
2
+ import type { ImageMaskValue, MaskTool } from './types';
3
+ type $$ComponentProps = {
4
+ src?: string | null;
5
+ alt?: string;
6
+ mask?: ImageMaskValue | null;
7
+ fit?: ImageFit;
8
+ busy?: boolean;
9
+ disabled?: boolean;
10
+ placeholderKey?: string;
11
+ loading?: ImageLoading;
12
+ brushSize?: number;
13
+ tool?: MaskTool;
14
+ clearRevision?: number;
15
+ exportSize?: number;
16
+ class?: string;
17
+ };
18
+ declare const ImageMask: import("svelte").Component<$$ComponentProps, {}, "mask" | "brushSize" | "tool" | "clearRevision">;
19
+ type ImageMask = ReturnType<typeof ImageMask>;
20
+ export default ImageMask;