@uibit/image-comparison 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 UIBit Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # Image Comparison
2
+
3
+ [Interactive Demonstration](https://rawlings.github.io/uibit/components/image-comparison)
4
+
5
+ Image Comparison provides a visual comparison curtain layout. It overlays two images (representing before and after states) and allows users to peel them back horizontally, vertically, diagonally, or radially via an interactive handle slider.
6
+
7
+ ## Value Delivery
8
+
9
+ - **Direct visual contrast** – Enables users to compare subtle differences between two visual states in a single, overlapping interface footprint.
10
+ - **Multiple reveal modes** – Supports horizontal, vertical, diagonal, and radial swipe layouts to fit different aspect ratios and visual orientations.
11
+ - **Full accessibility support** – Out-of-the-box keyboard control (arrow keys, home, end) and ARIA slider semantics ensure comparison functionality is accessible to screen readers and keyboard-only users.
12
+
13
+ ## Ideal Applications
14
+
15
+ - **Photo editing** – Displaying before/after filters, retouches, or color corrections.
16
+ - **Product showcases** – Comparing design upgrades, blueprints, rendering fidelity, or cosmetic changes.
17
+ - **Historical comparisons** – Presenting satellite imagery, architectural renovations, or site transformations over time.
18
+
19
+ ## Further Information
20
+
21
+ Detailed design guidelines, customizable attributes, and integration examples are available on our documentation site.
22
+
23
+ ## Changelog
24
+
25
+ Please see the [Changelog](CHANGELOG.md) for version history.
@@ -0,0 +1,345 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "readme": "",
4
+ "modules": [
5
+ {
6
+ "kind": "javascript-module",
7
+ "path": "src/image-comparison.ts",
8
+ "declarations": [
9
+ {
10
+ "kind": "class",
11
+ "description": "An interactive visual comparison curtain component. Displays before and after states\nand allows users to peel them back horizontally, vertically, diagonally, or radially.",
12
+ "name": "ImageComparison",
13
+ "cssProperties": [
14
+ {
15
+ "description": "Diameter of the handle knob",
16
+ "name": "--uibit-image-comparison-handle-size",
17
+ "default": "2.75rem"
18
+ },
19
+ {
20
+ "description": "Background color of the handle knob",
21
+ "name": "--uibit-image-comparison-handle-bg",
22
+ "default": "#ffffff"
23
+ },
24
+ {
25
+ "description": "Border outline color of the handle",
26
+ "name": "--uibit-image-comparison-handle-border-color",
27
+ "default": "transparent"
28
+ },
29
+ {
30
+ "description": "Color of the arrows/icons inside the handle",
31
+ "name": "--uibit-image-comparison-handle-inner-color",
32
+ "default": "#111111"
33
+ },
34
+ {
35
+ "description": "Separation line border color",
36
+ "name": "--uibit-image-comparison-border-color",
37
+ "default": "#ffffff"
38
+ }
39
+ ],
40
+ "members": [
41
+ {
42
+ "kind": "field",
43
+ "name": "mode",
44
+ "type": {
45
+ "text": "ComparisonMode"
46
+ },
47
+ "default": "'horizontal'",
48
+ "description": "Visual mode of reveal: 'horizontal' | 'vertical' | 'diagonal' | 'radial'",
49
+ "attribute": "mode"
50
+ },
51
+ {
52
+ "kind": "field",
53
+ "name": "progress",
54
+ "type": {
55
+ "text": "number"
56
+ },
57
+ "default": "50",
58
+ "description": "Reveal progress percentage (0 to 100)",
59
+ "attribute": "progress"
60
+ },
61
+ {
62
+ "kind": "field",
63
+ "name": "interactive",
64
+ "type": {
65
+ "text": "boolean"
66
+ },
67
+ "default": "true",
68
+ "description": "Whether progress is interactive via dragging/touching",
69
+ "attribute": "interactive"
70
+ },
71
+ {
72
+ "kind": "field",
73
+ "name": "hoverReveal",
74
+ "type": {
75
+ "text": "boolean"
76
+ },
77
+ "default": "false",
78
+ "description": "If true, pointer movement tracks progress immediately without click-dragging",
79
+ "attribute": "hover-reveal"
80
+ },
81
+ {
82
+ "kind": "field",
83
+ "name": "_isDragging",
84
+ "type": {
85
+ "text": "boolean"
86
+ },
87
+ "privacy": "private",
88
+ "default": "false"
89
+ },
90
+ {
91
+ "kind": "method",
92
+ "name": "_updateAria",
93
+ "privacy": "private"
94
+ },
95
+ {
96
+ "kind": "method",
97
+ "name": "_onKeyDown",
98
+ "privacy": "private",
99
+ "parameters": [
100
+ {
101
+ "name": "e",
102
+ "type": {
103
+ "text": "KeyboardEvent"
104
+ }
105
+ }
106
+ ]
107
+ },
108
+ {
109
+ "kind": "method",
110
+ "name": "_updateProgressFromEvent",
111
+ "privacy": "private",
112
+ "parameters": [
113
+ {
114
+ "name": "e",
115
+ "type": {
116
+ "text": "PointerEvent"
117
+ }
118
+ }
119
+ ]
120
+ },
121
+ {
122
+ "kind": "method",
123
+ "name": "_onPointerDown",
124
+ "privacy": "private",
125
+ "parameters": [
126
+ {
127
+ "name": "e",
128
+ "type": {
129
+ "text": "PointerEvent"
130
+ }
131
+ }
132
+ ]
133
+ },
134
+ {
135
+ "kind": "method",
136
+ "name": "_onPointerMove",
137
+ "privacy": "private",
138
+ "parameters": [
139
+ {
140
+ "name": "e",
141
+ "type": {
142
+ "text": "PointerEvent"
143
+ }
144
+ }
145
+ ]
146
+ },
147
+ {
148
+ "kind": "method",
149
+ "name": "_onPointerUp",
150
+ "privacy": "private",
151
+ "parameters": [
152
+ {
153
+ "name": "e",
154
+ "type": {
155
+ "text": "PointerEvent"
156
+ }
157
+ }
158
+ ]
159
+ },
160
+ {
161
+ "kind": "method",
162
+ "name": "_getClipPath",
163
+ "privacy": "private",
164
+ "return": {
165
+ "type": {
166
+ "text": "string"
167
+ }
168
+ }
169
+ },
170
+ {
171
+ "kind": "method",
172
+ "name": "_getHandleStyle",
173
+ "privacy": "private",
174
+ "return": {
175
+ "type": {
176
+ "text": "string"
177
+ }
178
+ }
179
+ },
180
+ {
181
+ "kind": "method",
182
+ "name": "_getDividerStyle",
183
+ "privacy": "private",
184
+ "return": {
185
+ "type": {
186
+ "text": "string"
187
+ }
188
+ }
189
+ },
190
+ {
191
+ "kind": "method",
192
+ "name": "_renderHandleIcon",
193
+ "privacy": "private"
194
+ }
195
+ ],
196
+ "events": [
197
+ {
198
+ "type": {
199
+ "text": "progress: number"
200
+ },
201
+ "description": "Fired when the curtain progress updates",
202
+ "name": "comparison-progress"
203
+ }
204
+ ],
205
+ "attributes": [
206
+ {
207
+ "name": "mode",
208
+ "type": {
209
+ "text": "ComparisonMode"
210
+ },
211
+ "default": "'horizontal'",
212
+ "description": "Visual mode of reveal: 'horizontal' | 'vertical' | 'diagonal' | 'radial'",
213
+ "fieldName": "mode"
214
+ },
215
+ {
216
+ "name": "progress",
217
+ "type": {
218
+ "text": "number"
219
+ },
220
+ "default": "50",
221
+ "description": "Reveal progress percentage (0 to 100)",
222
+ "fieldName": "progress"
223
+ },
224
+ {
225
+ "name": "interactive",
226
+ "type": {
227
+ "text": "boolean"
228
+ },
229
+ "default": "true",
230
+ "description": "Whether progress is interactive via dragging/touching",
231
+ "fieldName": "interactive"
232
+ },
233
+ {
234
+ "name": "hover-reveal",
235
+ "type": {
236
+ "text": "boolean"
237
+ },
238
+ "default": "false",
239
+ "description": "If true, pointer movement tracks progress immediately without click-dragging",
240
+ "fieldName": "hoverReveal"
241
+ }
242
+ ],
243
+ "superclass": {
244
+ "name": "UIBitElement",
245
+ "package": "@uibit/core"
246
+ },
247
+ "tagName": "uibit-image-comparison",
248
+ "customElement": true
249
+ }
250
+ ],
251
+ "exports": [
252
+ {
253
+ "kind": "js",
254
+ "name": "ImageComparison",
255
+ "declaration": {
256
+ "name": "ImageComparison",
257
+ "module": "src/image-comparison.ts"
258
+ }
259
+ },
260
+ {
261
+ "kind": "custom-element-definition",
262
+ "name": "uibit-image-comparison",
263
+ "declaration": {
264
+ "name": "ImageComparison",
265
+ "module": "src/image-comparison.ts"
266
+ }
267
+ },
268
+ {
269
+ "kind": "js",
270
+ "name": "default",
271
+ "declaration": {
272
+ "name": "ImageComparison",
273
+ "module": "src/image-comparison.ts"
274
+ }
275
+ }
276
+ ]
277
+ },
278
+ {
279
+ "kind": "javascript-module",
280
+ "path": "src/index.ts",
281
+ "declarations": [],
282
+ "exports": [
283
+ {
284
+ "kind": "js",
285
+ "name": "default",
286
+ "declaration": {
287
+ "name": "default",
288
+ "module": "./image-comparison"
289
+ }
290
+ },
291
+ {
292
+ "kind": "js",
293
+ "name": "ImageComparison",
294
+ "declaration": {
295
+ "name": "ImageComparison",
296
+ "module": "./image-comparison"
297
+ }
298
+ },
299
+ {
300
+ "kind": "js",
301
+ "name": "ComparisonMode",
302
+ "declaration": {
303
+ "name": "ComparisonMode",
304
+ "module": "./types"
305
+ }
306
+ },
307
+ {
308
+ "kind": "js",
309
+ "name": "ImageComparisonConfig",
310
+ "declaration": {
311
+ "name": "ImageComparisonConfig",
312
+ "module": "./types"
313
+ }
314
+ }
315
+ ]
316
+ },
317
+ {
318
+ "kind": "javascript-module",
319
+ "path": "src/styles.ts",
320
+ "declarations": [
321
+ {
322
+ "kind": "variable",
323
+ "name": "styles",
324
+ "default": "css` :host { display: block; position: relative; width: 100%; height: 100%; overflow: hidden; user-select: none; outline: none; border-radius: 0.5rem; --uibit-image-comparison-handle-size: 2.25rem; --uibit-image-comparison-handle-bg: #ffffff; --uibit-image-comparison-handle-border-color: #e5e7eb; --uibit-image-comparison-handle-inner-color: #111827; --uibit-image-comparison-border-color: #ffffff; } :host(:focus-visible) { box-shadow: 0 0 0 0.1875rem rgba(0, 0, 0, 0.35); } .container { position: relative; width: 100%; height: 100%; touch-action: none; } .container.dragging, .container.dragging .handle { cursor: grabbing !important; } .layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .layer::slotted(*), .layer slot::slotted(*) { width: 100% !important; height: 100% !important; object-fit: cover; display: block; } .layer-after { z-index: 0; } .layer-before { z-index: 1; pointer-events: none; } .divider { position: absolute; z-index: 2; background-color: var(--uibit-image-comparison-border-color); pointer-events: none; opacity: 0.9; } .divider-horizontal { top: 0; bottom: 0; width: 0.125rem; transform: translateX(-50%); } .divider-vertical { left: 0; right: 0; height: 0.125rem; transform: translateY(-50%); } .handle { position: absolute; z-index: 3; width: var(--uibit-image-comparison-handle-size); height: var(--uibit-image-comparison-handle-size); border-radius: 9999px; background-color: var(--uibit-image-comparison-handle-bg); border: 0.0625rem solid var(--uibit-image-comparison-handle-border-color); box-shadow: none; display: flex; align-items: center; justify-content: center; transform: translate(-50%, -50%); cursor: grab; transition: background-color 150ms ease, border-color 150ms ease, transform 150ms ease; } .handle:hover { background-color: #f3f4f6; border-color: #d1d5db; box-shadow: none; transform: translate(-50%, -50%) scale(1.06); } .handle:active { cursor: grabbing; transform: translate(-50%, -50%) scale(0.95); } .handle-icon { width: 1.125rem; height: 1.125rem; color: var(--uibit-image-comparison-handle-inner-color); display: flex; align-items: center; justify-content: center; } .handle-icon svg { width: 100%; height: 100%; } `"
325
+ }
326
+ ],
327
+ "exports": [
328
+ {
329
+ "kind": "js",
330
+ "name": "styles",
331
+ "declaration": {
332
+ "name": "styles",
333
+ "module": "src/styles.ts"
334
+ }
335
+ }
336
+ ]
337
+ },
338
+ {
339
+ "kind": "javascript-module",
340
+ "path": "src/types.ts",
341
+ "declarations": [],
342
+ "exports": []
343
+ }
344
+ ]
345
+ }
@@ -0,0 +1,45 @@
1
+ import { Component, ChangeDetectionStrategy, ElementRef, input, effect, output, booleanAttribute, numberAttribute } from '@angular/core';
2
+ import '@uibit/image-comparison';
3
+ import type { ImageComparison as HTMLElementClass } from '@uibit/image-comparison';
4
+ import type { ComparisonMode } from '@uibit/image-comparison';
5
+
6
+ @Component({
7
+ selector: 'uibit-image-comparison',
8
+ template: '<ng-content></ng-content>',
9
+ changeDetection: ChangeDetectionStrategy.OnPush,
10
+ standalone: true,
11
+ host: {
12
+ '(comparison-progress)': 'comparisonProgress.emit($event)'
13
+ }
14
+ })
15
+ export class NgxImageComparison {
16
+ constructor(private el: ElementRef<HTMLElementClass>) {
17
+ effect(() => {
18
+ if (this.el.nativeElement) {
19
+ this.el.nativeElement.mode = this.mode();
20
+ }
21
+ });
22
+ effect(() => {
23
+ if (this.el.nativeElement) {
24
+ this.el.nativeElement.progress = this.progress();
25
+ }
26
+ });
27
+ effect(() => {
28
+ if (this.el.nativeElement) {
29
+ this.el.nativeElement.interactive = this.interactive();
30
+ }
31
+ });
32
+ effect(() => {
33
+ if (this.el.nativeElement) {
34
+ this.el.nativeElement.hoverReveal = this.hoverReveal();
35
+ }
36
+ });
37
+ }
38
+
39
+ readonly mode = input<ComparisonMode, any>('horizontal');
40
+ readonly progress = input<number, any>(50, { transform: numberAttribute });
41
+ readonly interactive = input<boolean, any>(true, { transform: booleanAttribute });
42
+ readonly hoverReveal = input<boolean, any>(false, { transform: booleanAttribute });
43
+
44
+ readonly comparisonProgress = output<CustomEvent<progress: number>>();
45
+ }
@@ -0,0 +1,10 @@
1
+ import type { ImageComparison as HTMLElementClass } from '@uibit/image-comparison';
2
+ import '@uibit/image-comparison';
3
+
4
+ declare global {
5
+ namespace astroHTML.JSX {
6
+ interface IntrinsicElements {
7
+ 'uibit-image-comparison': Partial<HTMLElementClass> & astroHTML.JSX.HTMLAttributes;
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,7 @@
1
+ import { defineNuxtPlugin } from '#app';
2
+
3
+ export default defineNuxtPlugin(() => {
4
+ if (process.client) {
5
+ import('@uibit/image-comparison');
6
+ }
7
+ });
@@ -0,0 +1,17 @@
1
+ import type { JSX } from 'preact';
2
+ import type { ImageComparison as HTMLElementClass } from '@uibit/image-comparison';
3
+ import '@uibit/image-comparison';
4
+ import type { ComparisonMode } from '@uibit/image-comparison';
5
+
6
+ declare module 'preact' {
7
+ namespace JSX {
8
+ interface IntrinsicElements {
9
+ 'uibit-image-comparison': JSX.HTMLAttributes<HTMLElementClass> & {
10
+ mode?: ComparisonMode;
11
+ progress?: number;
12
+ interactive?: boolean;
13
+ hoverReveal?: boolean;
14
+ };
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,10 @@
1
+ import { component$, Slot } from '@builder.io/qwik';
2
+ import '@uibit/image-comparison';
3
+
4
+ export const ImageComparison = component$<any>((props) => {
5
+ return (
6
+ <uibit-image-comparison {...props}>
7
+ <Slot />
8
+ </uibit-image-comparison>
9
+ );
10
+ });
@@ -0,0 +1,22 @@
1
+ import type { HTMLAttributes, ClassAttributes } from 'react';
2
+ import type { ImageComparison as HTMLElementClass } from '@uibit/image-comparison';
3
+ import '@uibit/image-comparison';
4
+ import type { ComparisonMode } from '@uibit/image-comparison';
5
+
6
+ declare global {
7
+ namespace React {
8
+ namespace JSX {
9
+ interface IntrinsicElements {
10
+ 'uibit-image-comparison': ClassAttributes<HTMLElementClass> & HTMLAttributes<HTMLElementClass> & {
11
+ children?: React.ReactNode;
12
+ class?: string;
13
+ mode?: ComparisonMode;
14
+ progress?: number;
15
+ interactive?: boolean;
16
+ hoverReveal?: boolean;
17
+ onComparisonProgress?: (event: any) => void;
18
+ };
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,18 @@
1
+ import type { JSX } from 'solid-js';
2
+ import type { ImageComparison as HTMLElementClass } from '@uibit/image-comparison';
3
+ import '@uibit/image-comparison';
4
+ import type { ComparisonMode } from '@uibit/image-comparison';
5
+
6
+ declare module 'solid-js' {
7
+ namespace JSX {
8
+ interface IntrinsicElements {
9
+ 'uibit-image-comparison': Partial<HTMLElementClass> & JSX.HTMLAttributes<HTMLElementClass> & {
10
+ mode?: ComparisonMode;
11
+ progress?: number;
12
+ interactive?: boolean;
13
+ hoverReveal?: boolean;
14
+ "on:comparison-progress"?: (event: CustomEvent) => void;
15
+ };
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,16 @@
1
+ import type { ImageComparison as HTMLElementClass } from '@uibit/image-comparison';
2
+ import '@uibit/image-comparison';
3
+ import type { ComparisonMode } from '@uibit/image-comparison';
4
+
5
+ declare module '@stencil/core' {
6
+ export namespace JSX {
7
+ interface IntrinsicElements {
8
+ 'uibit-image-comparison': HTMLElementClass & {
9
+ mode?: ComparisonMode;
10
+ progress?: number;
11
+ interactive?: boolean;
12
+ hoverReveal?: boolean;
13
+ };
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import '@uibit/image-comparison';
3
+ import type { ImageComparison as HTMLElementClass } from '@uibit/image-comparison';
4
+ import type { ComparisonMode } from '@uibit/image-comparison';
5
+
6
+ let {
7
+ mode = undefined,
8
+ progress = undefined,
9
+ interactive = undefined,
10
+ hoverReveal = undefined,
11
+ children
12
+ } = $props<{
13
+ children?: any;
14
+ mode?: ComparisonMode;
15
+ progress?: number;
16
+ interactive?: boolean;
17
+ hoverReveal?: boolean;
18
+
19
+ }>();
20
+
21
+ let elementRef: HTMLElementClass | null = $state(null);
22
+
23
+ $effect(() => {
24
+ if (elementRef && mode !== undefined) {
25
+ elementRef.mode = mode;
26
+ }
27
+ if (elementRef && progress !== undefined) {
28
+ elementRef.progress = progress;
29
+ }
30
+ if (elementRef && interactive !== undefined) {
31
+ elementRef.interactive = interactive;
32
+ }
33
+ if (elementRef && hoverReveal !== undefined) {
34
+ elementRef.hoverReveal = hoverReveal;
35
+ }
36
+ });
37
+
38
+ </script>
39
+
40
+ <uibit-image-comparison bind:this={elementRef} {...$$restProps}>
41
+
42
+ {#if children}
43
+ {@render children()}
44
+ {:else}
45
+ <slot />
46
+ {/if}
47
+ </uibit-image-comparison>
@@ -0,0 +1,28 @@
1
+ import { defineComponent, h } from 'vue';
2
+ import type { ImageComparison as HTMLElementClass } from '@uibit/image-comparison';
3
+ import '@uibit/image-comparison';
4
+ import type { ComparisonMode } from '@uibit/image-comparison';
5
+
6
+ export const ImageComparison = defineComponent({
7
+ name: 'ImageComparison',
8
+ props: {
9
+ mode: { type: [String, Number, Boolean, Array, Object] as any },
10
+ progress: { type: [String, Number, Boolean, Array, Object] as any },
11
+ interactive: { type: [String, Number, Boolean, Array, Object] as any },
12
+ hoverReveal: { type: [String, Number, Boolean, Array, Object] as any }
13
+ },
14
+ emits: ['comparison-progress'],
15
+ setup(props, { slots, emit }) {
16
+ return () => {
17
+ const eventListeners: Record<string, any> = {};
18
+ eventListeners['onComparison-progress'] = (event: Event) => {
19
+ emit('comparison-progress', event);
20
+ };
21
+
22
+ return h('uibit-image-comparison', {
23
+ ...props,
24
+ ...eventListeners
25
+ }, slots.default?.());
26
+ };
27
+ }
28
+ });
@@ -0,0 +1,46 @@
1
+ import { UIBitElement } from '@uibit/core';
2
+ import type { ComparisonMode } from './types';
3
+ /**
4
+ * An interactive visual comparison curtain component. Displays before and after states
5
+ * and allows users to peel them back horizontally, vertically, diagonally, or radially.
6
+ *
7
+ * @fires { progress: number } comparison-progress - Fired when the curtain progress updates
8
+ *
9
+ * @cssprop [--uibit-image-comparison-handle-size=2.75rem] - Diameter of the handle knob
10
+ * @cssprop [--uibit-image-comparison-handle-bg=#ffffff] - Background color of the handle knob
11
+ * @cssprop [--uibit-image-comparison-handle-border-color=transparent] - Border outline color of the handle
12
+ * @cssprop [--uibit-image-comparison-handle-inner-color=#111111] - Color of the arrows/icons inside the handle
13
+ * @cssprop [--uibit-image-comparison-border-color=#ffffff] - Separation line border color
14
+ */
15
+ export declare class ImageComparison extends UIBitElement {
16
+ static styles: import("lit").CSSResult;
17
+ /** Visual mode of reveal: 'horizontal' | 'vertical' | 'diagonal' | 'radial' */
18
+ mode: ComparisonMode;
19
+ /** Reveal progress percentage (0 to 100) */
20
+ progress: number;
21
+ /** Whether progress is interactive via dragging/touching */
22
+ interactive: boolean;
23
+ /** If true, pointer movement tracks progress immediately without click-dragging */
24
+ hoverReveal: boolean;
25
+ private _isDragging;
26
+ connectedCallback(): void;
27
+ updated(changedProperties: Map<PropertyKey, unknown>): void;
28
+ private _updateAria;
29
+ private _onKeyDown;
30
+ private _updateProgressFromEvent;
31
+ private _onPointerDown;
32
+ private _onPointerMove;
33
+ private _onPointerUp;
34
+ private _getClipPath;
35
+ private _getHandleStyle;
36
+ private _getDividerStyle;
37
+ private _renderHandleIcon;
38
+ render(): import("lit").TemplateResult<1>;
39
+ }
40
+ declare global {
41
+ interface HTMLElementTagNameMap {
42
+ 'uibit-image-comparison': ImageComparison;
43
+ }
44
+ }
45
+ export default ImageComparison;
46
+ //# sourceMappingURL=image-comparison.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-comparison.d.ts","sourceRoot":"","sources":["../src/image-comparison.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,YAAY,EAAE,MAAM,aAAa,CAAC;AAG1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,qBACa,eAAgB,SAAQ,YAAY;IAC/C,MAAM,CAAC,MAAM,0BAAU;IAEvB,+EAA+E;IACnD,IAAI,EAAE,cAAc,CAAgB;IAEhE,4CAA4C;IAChB,QAAQ,SAAM;IAE1C,4DAA4D;IAC/B,WAAW,UAAQ;IAEhD,mFAAmF;IAC3B,WAAW,UAAS;IAEnE,OAAO,CAAC,WAAW,CAAS;IAErC,iBAAiB,SAShB;IAED,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,QAKnD;IAED,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,UAAU;IA8BlB,OAAO,CAAC,wBAAwB;IA6BhC,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,iBAAiB;IAwCzB,MAAM,oCA6CL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,wBAAwB,EAAE,eAAe,CAAC;KAC3C;CACF;eAEc,eAAe"}
@@ -0,0 +1,289 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html } from 'lit';
8
+ import { customElement, UIBitElement } from '@uibit/core';
9
+ import { property, state } from 'lit/decorators.js';
10
+ import { styles } from './styles';
11
+ /**
12
+ * An interactive visual comparison curtain component. Displays before and after states
13
+ * and allows users to peel them back horizontally, vertically, diagonally, or radially.
14
+ *
15
+ * @fires { progress: number } comparison-progress - Fired when the curtain progress updates
16
+ *
17
+ * @cssprop [--uibit-image-comparison-handle-size=2.75rem] - Diameter of the handle knob
18
+ * @cssprop [--uibit-image-comparison-handle-bg=#ffffff] - Background color of the handle knob
19
+ * @cssprop [--uibit-image-comparison-handle-border-color=transparent] - Border outline color of the handle
20
+ * @cssprop [--uibit-image-comparison-handle-inner-color=#111111] - Color of the arrows/icons inside the handle
21
+ * @cssprop [--uibit-image-comparison-border-color=#ffffff] - Separation line border color
22
+ */
23
+ let ImageComparison = class ImageComparison extends UIBitElement {
24
+ constructor() {
25
+ super(...arguments);
26
+ /** Visual mode of reveal: 'horizontal' | 'vertical' | 'diagonal' | 'radial' */
27
+ this.mode = 'horizontal';
28
+ /** Reveal progress percentage (0 to 100) */
29
+ this.progress = 50;
30
+ /** Whether progress is interactive via dragging/touching */
31
+ this.interactive = true;
32
+ /** If true, pointer movement tracks progress immediately without click-dragging */
33
+ this.hoverReveal = false;
34
+ this._isDragging = false;
35
+ }
36
+ static { this.styles = styles; }
37
+ connectedCallback() {
38
+ super.connectedCallback();
39
+ this.setAttribute('tabindex', this.getAttribute('tabindex') || '0');
40
+ this.setAttribute('role', 'slider');
41
+ this.setAttribute('aria-valuemin', '0');
42
+ this.setAttribute('aria-valuemax', '100');
43
+ this._updateAria();
44
+ this.listen(this, 'keydown', this._onKeyDown);
45
+ }
46
+ updated(changedProperties) {
47
+ super.updated(changedProperties);
48
+ if (changedProperties.has('progress')) {
49
+ this._updateAria();
50
+ }
51
+ }
52
+ _updateAria() {
53
+ this.setAttribute('aria-valuenow', String(this.progress));
54
+ this.setAttribute('aria-label', `Visual comparison slider, current progress ${this.progress}%`);
55
+ }
56
+ _onKeyDown(e) {
57
+ if (!this.interactive)
58
+ return;
59
+ let newProgress = this.progress;
60
+ switch (e.key) {
61
+ case 'ArrowLeft':
62
+ case 'ArrowDown':
63
+ newProgress = Math.max(0, this.progress - 5);
64
+ break;
65
+ case 'ArrowRight':
66
+ case 'ArrowUp':
67
+ newProgress = Math.min(100, this.progress + 5);
68
+ break;
69
+ case 'Home':
70
+ newProgress = 0;
71
+ break;
72
+ case 'End':
73
+ newProgress = 100;
74
+ break;
75
+ default:
76
+ return; // Let other keys propagate
77
+ }
78
+ e.preventDefault();
79
+ if (newProgress !== this.progress) {
80
+ this.progress = newProgress;
81
+ this.dispatchCustomEvent('comparison-progress', { progress: this.progress });
82
+ }
83
+ }
84
+ _updateProgressFromEvent(e) {
85
+ const rect = this.getBoundingClientRect();
86
+ let pct = 50;
87
+ if (this.mode === 'horizontal') {
88
+ const x = e.clientX - rect.left;
89
+ pct = (x / rect.width) * 100;
90
+ }
91
+ else if (this.mode === 'vertical') {
92
+ const y = e.clientY - rect.top;
93
+ pct = (y / rect.height) * 100;
94
+ }
95
+ else if (this.mode === 'diagonal') {
96
+ const x = e.clientX - rect.left;
97
+ pct = (x / rect.width) * 100;
98
+ }
99
+ else if (this.mode === 'radial') {
100
+ const x = e.clientX - (rect.left + rect.width / 2);
101
+ const y = e.clientY - (rect.top + rect.height / 2);
102
+ const dist = Math.sqrt(x * x + y * y);
103
+ const maxDist = Math.sqrt(rect.width * rect.width + rect.height * rect.height) / 2;
104
+ pct = (dist / maxDist) * 100;
105
+ }
106
+ pct = Math.max(0, Math.min(100, pct));
107
+ const rounded = Math.round(pct);
108
+ if (rounded !== this.progress) {
109
+ this.progress = rounded;
110
+ this.dispatchCustomEvent('comparison-progress', { progress: this.progress });
111
+ }
112
+ }
113
+ _onPointerDown(e) {
114
+ if (!this.interactive || this.hoverReveal)
115
+ return;
116
+ const container = this.shadowRoot?.querySelector('.container');
117
+ if (container) {
118
+ container.setPointerCapture(e.pointerId);
119
+ }
120
+ this._isDragging = true;
121
+ this._updateProgressFromEvent(e);
122
+ }
123
+ _onPointerMove(e) {
124
+ if (!this.interactive)
125
+ return;
126
+ if (this.hoverReveal || this._isDragging) {
127
+ this._updateProgressFromEvent(e);
128
+ }
129
+ }
130
+ _onPointerUp(e) {
131
+ if (this._isDragging) {
132
+ const container = this.shadowRoot?.querySelector('.container');
133
+ if (container) {
134
+ container.releasePointerCapture(e.pointerId);
135
+ }
136
+ this._isDragging = false;
137
+ }
138
+ }
139
+ _getClipPath() {
140
+ const p = this.progress;
141
+ if (this.mode === 'horizontal') {
142
+ return `inset(0 ${100 - p}% 0 0)`;
143
+ }
144
+ if (this.mode === 'vertical') {
145
+ return `inset(0 0 ${100 - p}% 0)`;
146
+ }
147
+ if (this.mode === 'radial') {
148
+ return `circle(${p}% at 50% 50%)`;
149
+ }
150
+ if (this.mode === 'diagonal') {
151
+ if (p <= 50) {
152
+ return `polygon(0 0, ${p * 2}% 0, 0 ${p * 2}%)`;
153
+ }
154
+ else {
155
+ return `polygon(0 0, 100% 0, 100% ${(p - 50) * 2}%, ${(p - 50) * 2}% 100%, 0 100%)`;
156
+ }
157
+ }
158
+ return 'none';
159
+ }
160
+ _getHandleStyle() {
161
+ const p = this.progress;
162
+ if (this.mode === 'horizontal') {
163
+ return `left: ${p}%; top: 50%;`;
164
+ }
165
+ if (this.mode === 'vertical') {
166
+ return `left: 50%; top: ${p}%;`;
167
+ }
168
+ if (this.mode === 'diagonal') {
169
+ return `left: ${p}%; top: ${p}%;`;
170
+ }
171
+ // Radial placing handle in center
172
+ return `left: 50%; top: 50%;`;
173
+ }
174
+ _getDividerStyle() {
175
+ const p = this.progress;
176
+ if (this.mode === 'horizontal') {
177
+ return `left: ${p}%;`;
178
+ }
179
+ if (this.mode === 'vertical') {
180
+ return `top: ${p}%;`;
181
+ }
182
+ return '';
183
+ }
184
+ _renderHandleIcon() {
185
+ if (this.mode === 'horizontal') {
186
+ return html `
187
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
188
+ <path d="M9 7L4 12L9 17" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
189
+ <path d="M15 7L20 12L15 17" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
190
+ </svg>
191
+ `;
192
+ }
193
+ if (this.mode === 'vertical') {
194
+ return html `
195
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
196
+ <path d="M7 9L12 4L17 9" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
197
+ <path d="M7 15L12 20L17 15" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
198
+ </svg>
199
+ `;
200
+ }
201
+ if (this.mode === 'diagonal') {
202
+ return html `
203
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
204
+ <path d="M7 7L4 4M4 4L9 4M4 4L4 9" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
205
+ <path d="M17 17L20 20M20 20L15 20M20 20L20 15" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
206
+ </svg>
207
+ `;
208
+ }
209
+ // Radial
210
+ return html `
211
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
212
+ <path d="M12 4L12 8" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
213
+ <path d="M12 16L12 20" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
214
+ <path d="M4 12L8 12" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
215
+ <path d="M16 12L20 12" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
216
+ <path d="M6.34 6.34L9.17 9.17" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
217
+ <path d="M14.83 14.83L17.66 17.66" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
218
+ <path d="M17.66 6.34L14.83 9.17" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
219
+ <path d="M9.17 14.83L6.34 17.66" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
220
+ </svg>
221
+ `;
222
+ }
223
+ render() {
224
+ const clipStyle = `clip-path: ${this._getClipPath()};`;
225
+ const handleStyle = this._getHandleStyle();
226
+ const dividerStyle = this._getDividerStyle();
227
+ return html `
228
+ <div
229
+ class="container ${this._isDragging ? 'dragging' : ''}"
230
+ part="container"
231
+ @pointerdown=${this._onPointerDown}
232
+ @pointermove=${this._onPointerMove}
233
+ @pointerup=${this._onPointerUp}
234
+ >
235
+ <div class="layer layer-after" part="after-layer">
236
+ <slot name="after"></slot>
237
+ </div>
238
+
239
+ <div class="layer layer-before" style=${clipStyle} part="before-layer">
240
+ <slot name="before"></slot>
241
+ </div>
242
+
243
+ ${this.mode === 'horizontal' || this.mode === 'vertical'
244
+ ? html `
245
+ <div
246
+ class="divider divider-${this.mode}"
247
+ style=${dividerStyle}
248
+ part="divider"
249
+ ></div>
250
+ `
251
+ : ''}
252
+
253
+ <div
254
+ class="handle"
255
+ style=${handleStyle}
256
+ part="handle"
257
+ role="presentation"
258
+ >
259
+ <slot name="handle">
260
+ <div class="handle-icon">
261
+ ${this._renderHandleIcon()}
262
+ </div>
263
+ </slot>
264
+ </div>
265
+ </div>
266
+ `;
267
+ }
268
+ };
269
+ __decorate([
270
+ property({ type: String })
271
+ ], ImageComparison.prototype, "mode", void 0);
272
+ __decorate([
273
+ property({ type: Number })
274
+ ], ImageComparison.prototype, "progress", void 0);
275
+ __decorate([
276
+ property({ type: Boolean })
277
+ ], ImageComparison.prototype, "interactive", void 0);
278
+ __decorate([
279
+ property({ type: Boolean, attribute: 'hover-reveal' })
280
+ ], ImageComparison.prototype, "hoverReveal", void 0);
281
+ __decorate([
282
+ state()
283
+ ], ImageComparison.prototype, "_isDragging", void 0);
284
+ ImageComparison = __decorate([
285
+ customElement('uibit-image-comparison')
286
+ ], ImageComparison);
287
+ export { ImageComparison };
288
+ export default ImageComparison;
289
+ //# sourceMappingURL=image-comparison.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-comparison.js","sourceRoot":"","sources":["../src/image-comparison.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { default, ImageComparison } from './image-comparison';
2
+ export type { ComparisonMode, ImageComparisonConfig } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC9D,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { default, ImageComparison } from './image-comparison';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const styles: import("lit").CSSResult;
2
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM,yBA2HlB,CAAC"}
package/dist/styles.js ADDED
@@ -0,0 +1,126 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: block;
5
+ position: relative;
6
+ width: 100%;
7
+ height: 100%;
8
+ overflow: hidden;
9
+ user-select: none;
10
+ outline: none;
11
+ border-radius: 0.5rem;
12
+
13
+ --uibit-image-comparison-handle-size: 2.25rem;
14
+ --uibit-image-comparison-handle-bg: #ffffff;
15
+ --uibit-image-comparison-handle-border-color: #e5e7eb;
16
+ --uibit-image-comparison-handle-inner-color: #111827;
17
+ --uibit-image-comparison-border-color: #ffffff;
18
+ }
19
+
20
+ :host(:focus-visible) {
21
+ box-shadow: 0 0 0 0.1875rem rgba(0, 0, 0, 0.35);
22
+ }
23
+
24
+ .container {
25
+ position: relative;
26
+ width: 100%;
27
+ height: 100%;
28
+ touch-action: none;
29
+ }
30
+
31
+ .container.dragging,
32
+ .container.dragging .handle {
33
+ cursor: grabbing !important;
34
+ }
35
+
36
+ .layer {
37
+ position: absolute;
38
+ top: 0;
39
+ left: 0;
40
+ width: 100%;
41
+ height: 100%;
42
+ }
43
+
44
+ .layer::slotted(*),
45
+ .layer slot::slotted(*) {
46
+ width: 100% !important;
47
+ height: 100% !important;
48
+ object-fit: cover;
49
+ display: block;
50
+ }
51
+
52
+ .layer-after {
53
+ z-index: 0;
54
+ }
55
+
56
+ .layer-before {
57
+ z-index: 1;
58
+ pointer-events: none;
59
+ }
60
+
61
+ .divider {
62
+ position: absolute;
63
+ z-index: 2;
64
+ background-color: var(--uibit-image-comparison-border-color);
65
+ pointer-events: none;
66
+ opacity: 0.9;
67
+ }
68
+
69
+ .divider-horizontal {
70
+ top: 0;
71
+ bottom: 0;
72
+ width: 0.125rem;
73
+ transform: translateX(-50%);
74
+ }
75
+
76
+ .divider-vertical {
77
+ left: 0;
78
+ right: 0;
79
+ height: 0.125rem;
80
+ transform: translateY(-50%);
81
+ }
82
+
83
+ .handle {
84
+ position: absolute;
85
+ z-index: 3;
86
+ width: var(--uibit-image-comparison-handle-size);
87
+ height: var(--uibit-image-comparison-handle-size);
88
+ border-radius: 9999px;
89
+ background-color: var(--uibit-image-comparison-handle-bg);
90
+ border: 0.0625rem solid var(--uibit-image-comparison-handle-border-color);
91
+ box-shadow: none;
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ transform: translate(-50%, -50%);
96
+ cursor: grab;
97
+ transition: background-color 150ms ease, border-color 150ms ease, transform 150ms ease;
98
+ }
99
+
100
+ .handle:hover {
101
+ background-color: #f3f4f6;
102
+ border-color: #d1d5db;
103
+ box-shadow: none;
104
+ transform: translate(-50%, -50%) scale(1.06);
105
+ }
106
+
107
+ .handle:active {
108
+ cursor: grabbing;
109
+ transform: translate(-50%, -50%) scale(0.95);
110
+ }
111
+
112
+ .handle-icon {
113
+ width: 1.125rem;
114
+ height: 1.125rem;
115
+ color: var(--uibit-image-comparison-handle-inner-color);
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ }
120
+
121
+ .handle-icon svg {
122
+ width: 100%;
123
+ height: 100%;
124
+ }
125
+ `;
126
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2HxB,CAAC"}
@@ -0,0 +1,8 @@
1
+ export type ComparisonMode = 'horizontal' | 'vertical' | 'diagonal' | 'radial';
2
+ export interface ImageComparisonConfig {
3
+ mode?: ComparisonMode;
4
+ progress?: number;
5
+ interactive?: boolean;
6
+ hoverReveal?: boolean;
7
+ }
8
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE/E,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@uibit/image-comparison",
3
+ "version": "0.1.0",
4
+ "description": "Displays overlapping before/after content layers with interactive diagonal, radial, horizontal, or vertical splits.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./image-comparison.css": "./dist/image-comparison.css",
14
+ "./custom-elements.json": "./custom-elements.json",
15
+ "./react": "./dist/frameworks/react/index.d.ts",
16
+ "./vue": "./dist/frameworks/vue/index.ts",
17
+ "./svelte": "./dist/frameworks/svelte/index.svelte",
18
+ "./angular": "./dist/frameworks/angular/index.ts",
19
+ "./solid": "./dist/frameworks/solid/index.d.ts",
20
+ "./astro": "./dist/frameworks/astro/index.astro",
21
+ "./qwik": "./dist/frameworks/qwik/index.tsx",
22
+ "./nuxt": "./dist/frameworks/nuxt/index.ts",
23
+ "./preact": "./dist/frameworks/preact/index.d.ts",
24
+ "./stencil": "./dist/frameworks/stencil/index.d.ts"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "package.json",
29
+ "README.md",
30
+ "LICENSE",
31
+ "custom-elements.json"
32
+ ],
33
+ "keywords": [
34
+ "comparison",
35
+ "slider",
36
+ "before-after",
37
+ "curtain",
38
+ "diagonal",
39
+ "web-component",
40
+ "lit",
41
+ "effect"
42
+ ],
43
+ "author": "Jonathan Rawlings",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/Rawlings/uibit",
48
+ "directory": "packages/components/image-comparison"
49
+ },
50
+ "dependencies": {
51
+ "@uibit/core": "0.1.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^20.19.43",
55
+ "lit": "^3.3.3",
56
+ "typescript": "7.0.2",
57
+ "@uibit/codegen": "0.1.0"
58
+ },
59
+ "peerDependencies": {
60
+ "lit": "^3.0.0",
61
+ "react": ">=18",
62
+ "vue": ">=3",
63
+ "svelte": ">=4 || ^5",
64
+ "@angular/core": ">=14"
65
+ },
66
+ "customElements": "custom-elements.json",
67
+ "peerDependenciesMeta": {
68
+ "react": {
69
+ "optional": true
70
+ },
71
+ "vue": {
72
+ "optional": true
73
+ },
74
+ "svelte": {
75
+ "optional": true
76
+ },
77
+ "@angular/core": {
78
+ "optional": true
79
+ }
80
+ },
81
+ "uibit": {
82
+ "id": "image-comparison",
83
+ "title": "Image Comparison",
84
+ "category": "Media",
85
+ "tagName": "uibit-image-comparison"
86
+ },
87
+ "scripts": {
88
+ "build": "cem analyze --globs 'src/**/*.ts' --litelement && uibit-codegen --package . && tsc",
89
+ "dev": "concurrently \"cem analyze --globs 'src/**/*.ts' --litelement --watch\" \"tsc --watch\"",
90
+ "analyze": "cem analyze --globs 'src/**/*.ts' --litelement",
91
+ "typecheck": "tsc --noEmit",
92
+ "test": "vitest run -c ../../../vitest.config.ts --passWithNoTests"
93
+ }
94
+ }