@finsweet/webflow-apps-utils 1.0.23 → 1.0.25

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.
@@ -50,28 +50,6 @@
50
50
  id
51
51
  }: Props = $props();
52
52
 
53
- // Remove local inputValue state
54
- // let inputValue = $state(color);
55
-
56
- // Helper: normalize hex to 6-digit uppercase
57
- // function normalizeHex(value: string): string {
58
- // if (/^#[A-Fa-f0-9]{3}$/.test(value)) {
59
- // return (
60
- // '#' +
61
- // value
62
- // .slice(1)
63
- // .split('')
64
- // .map((c) => c + c)
65
- // .join('')
66
- // .toUpperCase()
67
- // );
68
- // }
69
- // if (/^#[A-Fa-f0-9]{6}$/.test(value)) {
70
- // return value.toUpperCase();
71
- // }
72
- // return value;
73
- // }
74
-
75
53
  function isValidColor(value: string): boolean {
76
54
  const hexRegex = /^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/;
77
55
  return hexRegex.test(value.startsWith('#') ? value.slice(1) : value);
@@ -89,8 +67,11 @@
89
67
  .join('');
90
68
  }
91
69
 
92
- // Remove alpha channel if present (8-digit hex)
70
+ // Extract alpha channel if present (8-digit hex)
71
+ let alpha = 100;
93
72
  if (normalizedHex.length === 8) {
73
+ const alphaHex = normalizedHex.substring(6, 8);
74
+ alpha = Math.round((parseInt(alphaHex, 16) / 255) * 100);
94
75
  normalizedHex = normalizedHex.substring(0, 6);
95
76
  }
96
77
 
@@ -203,15 +184,24 @@
203
184
 
204
185
  function handleFullColorChange(fullColor: ColorObject) {
205
186
  oncolorchange?.(fullColor);
206
- const normalizedValue = normalizeHex(fullColor.hex);
207
- color = normalizedValue;
187
+ // Preserve the alpha channel by using the full hex value with alpha
188
+ // The fullColor.hex already includes the alpha channel if alpha < 100
189
+ color = fullColor.hex;
190
+ }
191
+
192
+ function handleDragEnd() {
193
+ // Signal the tooltip to ignore the next click event
194
+ // This prevents the tooltip from closing when dragging and releasing outside
195
+ tooltipRef?.ignoreNextClickEvent?.();
208
196
  }
209
197
 
210
198
  let showColorSelect = $state(defaultShowColorSelect);
199
+ let tooltipRef: { ignoreNextClickEvent?: () => void } | undefined = $state();
211
200
  </script>
212
201
 
213
202
  <div class="color-picker">
214
203
  <Tooltip
204
+ bind:this={tooltipRef}
215
205
  listener="click"
216
206
  listenerout="click"
217
207
  showArrow={false}
@@ -231,7 +221,7 @@
231
221
  {/snippet}
232
222
  {#snippet tooltip()}
233
223
  {#if showColorSelect}
234
- <ColorSelect bind:color oncolorchange={handleFullColorChange} />
224
+ <ColorSelect bind:color oncolorchange={handleFullColorChange} ondragend={handleDragEnd} />
235
225
  {/if}
236
226
  {/snippet}
237
227
  </Tooltip>
@@ -271,6 +261,11 @@
271
261
  0px 4px 4px -4px rgba(0, 0, 0, 0.17) inset,
272
262
  0px 3px 3px -3px rgba(0, 0, 0, 0.17) inset,
273
263
  0px 1px 1px -1px rgba(0, 0, 0, 0.13) inset;
264
+
265
+ user-select: none;
266
+ -webkit-user-select: none;
267
+ -moz-user-select: none;
268
+ -ms-user-select: none;
274
269
  }
275
270
 
276
271
  .color-picker__swatch {
@@ -12,9 +12,14 @@
12
12
  alpha: number;
13
13
  }
14
14
 
15
- let { color = $bindable('#fff'), oncolorchange } = $props<{
15
+ let {
16
+ color = $bindable('#fff'),
17
+ oncolorchange,
18
+ ondragend
19
+ } = $props<{
16
20
  color?: string;
17
21
  oncolorchange?: (fullColor: ColorObject) => void;
22
+ ondragend?: () => void;
18
23
  }>();
19
24
 
20
25
  function setColor(newColor: string) {
@@ -108,6 +113,26 @@
108
113
  return normalizeHex(colorInput);
109
114
  }
110
115
 
116
+ // Helper function to extract alpha value from hex color
117
+ function extractAlphaFromHex(hexColor: string): number {
118
+ if (hexColor.startsWith('#') && hexColor.length === 9) {
119
+ // 8-digit hex with alpha channel (#RRGGBBAA)
120
+ const alphaHex = hexColor.slice(7, 9);
121
+ const alphaValue = parseInt(alphaHex, 16);
122
+ return Math.round((alphaValue / 255) * 100);
123
+ }
124
+ return 100; // Default to 100% opacity if no alpha channel
125
+ }
126
+
127
+ // Helper function to get hex without alpha channel
128
+ function getHexWithoutAlpha(hexColor: string): string {
129
+ if (hexColor.startsWith('#') && hexColor.length === 9) {
130
+ // Remove alpha channel from 8-digit hex
131
+ return hexColor.slice(0, 7);
132
+ }
133
+ return hexColor;
134
+ }
135
+
111
136
  // Color conversion utilities
112
137
  function hsbToRgb(h: number, s: number, b: number): [number, number, number] {
113
138
  h = h / 360;
@@ -377,6 +402,7 @@
377
402
  let hexInputValue = $state('');
378
403
  let isHexEditing = $state(false);
379
404
  let prevHexValue = $state('');
405
+ let isInitialized = $state(false);
380
406
 
381
407
  function handleHexFocus(event: FocusEvent) {
382
408
  prevHexValue = hexValue;
@@ -489,6 +515,8 @@
489
515
  if (isDragging) {
490
516
  isDragging = false;
491
517
  dragTarget = null;
518
+ // Signal that a drag operation just ended
519
+ ondragend?.();
492
520
  }
493
521
 
494
522
  // Reset drag detection state
@@ -508,10 +536,18 @@
508
536
 
509
537
  // Watch for prop changes - FIXED: Better color normalization
510
538
  $effect(() => {
539
+ // Skip effect during initial mount to prevent overriding alpha
540
+ if (!isInitialized) return;
541
+
511
542
  const normalized = normalizeColorToHex(color);
512
- if (normalized !== hexValue) {
513
- hexValue = normalized;
514
- const [h, s, b] = hexToHsb(normalized);
543
+ // Extract alpha value from the original color if it has an alpha channel
544
+ const alphaFromColor = extractAlphaFromHex(color);
545
+
546
+ // Use hex without alpha for internal processing
547
+ const hexWithoutAlpha = getHexWithoutAlpha(normalized);
548
+ if (hexWithoutAlpha !== hexValue) {
549
+ hexValue = hexWithoutAlpha;
550
+ const [h, s, b] = hexToHsb(hexValue);
515
551
  hue = h;
516
552
  saturation = s;
517
553
  brightness = b;
@@ -519,6 +555,11 @@
519
555
  // Force position update after state changes
520
556
  setTimeout(() => updateColorPickerPosition(), 0);
521
557
  }
558
+
559
+ // Only update alpha if it's different from current alpha
560
+ if (alphaFromColor !== alpha) {
561
+ alpha = alphaFromColor;
562
+ }
522
563
  });
523
564
 
524
565
  let currentColor = $derived(
@@ -527,14 +568,19 @@
527
568
 
528
569
  onMount(() => {
529
570
  const normalized = normalizeColorToHex(color);
530
- hexValue = normalized;
531
- const [h, s, b] = hexToHsb(normalized);
571
+ // Extract alpha value from the original color if it has an alpha channel
572
+ const alphaFromColor = extractAlphaFromHex(color);
573
+ alpha = alphaFromColor;
574
+
575
+ // Use hex without alpha for internal processing
576
+ hexValue = getHexWithoutAlpha(normalized);
577
+ const [h, s, b] = hexToHsb(hexValue);
532
578
  hue = h;
533
579
  saturation = s;
534
580
  brightness = b;
535
581
  [rgbRed, rgbGreen, rgbBlue] = hsbToRgb(hue, saturation, brightness);
536
582
 
537
- console.log('ColorSelect mounted', { color });
583
+ console.log('ColorSelect mounted', { color, alpha: alphaFromColor });
538
584
  updateColor();
539
585
  // Delay position update to ensure DOM is ready
540
586
 
@@ -543,6 +589,9 @@
543
589
  document.addEventListener('mousemove', handleMouseMove);
544
590
  document.addEventListener('mouseup', handleMouseUp);
545
591
 
592
+ // Mark as initialized after setting up the initial state
593
+ isInitialized = true;
594
+
546
595
  return () => {
547
596
  document.removeEventListener('mousemove', handleMouseMove);
548
597
  document.removeEventListener('mouseup', handleMouseUp);
@@ -24,6 +24,7 @@ interface ColorObject {
24
24
  type $$ComponentProps = {
25
25
  color?: string;
26
26
  oncolorchange?: (fullColor: ColorObject) => void;
27
+ ondragend?: () => void;
27
28
  };
28
29
  declare const ColorSelect: import("svelte").Component<$$ComponentProps, {}, "color">;
29
30
  type ColorSelect = ReturnType<typeof ColorSelect>;
@@ -64,6 +64,7 @@
64
64
  let arrowElement: HTMLDivElement | undefined = $state();
65
65
  let observer: MutationObserver | null = null;
66
66
  let documentClickListener: ((event: MouseEvent) => void) | null = null;
67
+ let ignoreNextClick = $state(false);
67
68
  const tooltipId = `tooltip-${uuidv4()}`;
68
69
 
69
70
  /**
@@ -214,6 +215,12 @@
214
215
 
215
216
  // Store reference to the click handler for cleanup
216
217
  documentClickListener = (event: MouseEvent) => {
218
+ // Ignore clicks that happen immediately after a drag operation
219
+ if (ignoreNextClick) {
220
+ ignoreNextClick = false;
221
+ return;
222
+ }
223
+
217
224
  if (
218
225
  tooltipElement &&
219
226
  toggle &&
@@ -263,6 +270,9 @@
263
270
 
264
271
  export const show = () => tooltipInstance?.showTooltip();
265
272
  export const hide = () => tooltipInstance?.hideTooltip();
273
+ export const ignoreNextClickEvent = () => {
274
+ ignoreNextClick = true;
275
+ };
266
276
 
267
277
  // Svelte 5 effect for hidden prop
268
278
  $effect(() => {
@@ -3,6 +3,7 @@ import type { TooltipProps } from './types';
3
3
  declare const Tooltip: Component<TooltipProps, {
4
4
  show: () => void | undefined;
5
5
  hide: () => void | undefined;
6
+ ignoreNextClickEvent: () => void;
6
7
  }, "hidden" | "isActive">;
7
8
  type Tooltip = ReturnType<typeof Tooltip>;
8
9
  export default Tooltip;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finsweet/webflow-apps-utils",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "Shared utilities for Webflow apps",
5
5
  "homepage": "https://github.com/finsweet/webflow-apps-utils",
6
6
  "repository": {