@widgetic/editor 4.0.10 → 4.0.11
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/README.md +118 -0
- package/dist/components/WidgetPreview.svelte +65 -27
- package/dist/components/WidgetPreview.svelte.d.ts +8 -0
- package/dist/components/editors/CompositionEditorApp.svelte +508 -0
- package/dist/components/editors/CompositionEditorApp.svelte.d.ts +75 -0
- package/dist/components/editors/PropsEditor.svelte +193 -792
- package/dist/components/editors/PropsEditor.svelte.d.ts +7 -0
- package/dist/components/ic/BrowserInputController.svelte +21 -2
- package/dist/components/ic/ColorPickerInputController.svelte +24 -6
- package/dist/components/ic/DateFieldInputController.svelte +7 -3
- package/dist/components/ic/DatePickerInputController.svelte +10 -4
- package/dist/components/ic/DropdownInputController.svelte +11 -4
- package/dist/components/ic/FontInputController.svelte +68 -49
- package/dist/components/ic/InputControllerHeader.svelte +11 -8
- package/dist/components/ic/NumberInputController.svelte +4 -2
- package/dist/components/ic/PositionInputController.svelte +14 -11
- package/dist/components/ic/RangeCalendarInputController.svelte +10 -4
- package/dist/components/ic/RoundedCornersInputController.svelte +17 -9
- package/dist/components/ic/TextAreaInputController.svelte +20 -7
- package/dist/components/ic/TextAreaPreviewInputController.svelte +19 -69
- package/dist/components/ic/TimeInputController.svelte +9 -3
- package/dist/components/ic/VectorInputController.svelte +80 -54
- package/dist/components/ic/shared/ItemGrid.svelte +5 -1
- package/dist/composition/compositionApi.d.ts +95 -0
- package/dist/composition/compositionApi.js +289 -0
- package/dist/composition/compositionEditorController.d.ts +254 -0
- package/dist/composition/compositionEditorController.js +734 -0
- package/dist/composition/compositionHelpers.d.ts +72 -0
- package/dist/composition/compositionHelpers.js +403 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/mediaLibraryContext.d.ts +10 -0
- package/dist/mediaLibraryContext.js +30 -0
- package/dist/utils/FontManager.d.ts +21 -5
- package/dist/utils/FontManager.js +62 -67
- package/dist/utils/VectorManager.d.ts +0 -1
- package/dist/utils/VectorManager.js +10 -12
- package/dist/utils/editorStorage.d.ts +23 -0
- package/dist/utils/editorStorage.js +95 -0
- package/dist/utils/fontUtils.js +5 -3
- package/dist/utils/htmlSafety.d.ts +21 -0
- package/dist/utils/htmlSafety.js +38 -0
- package/dist/utils/itemReorder.d.ts +90 -0
- package/dist/utils/itemReorder.js +354 -0
- package/dist/utils/recentColors.js +4 -2
- package/dist/utils/richTextPreview.d.ts +38 -0
- package/dist/utils/richTextPreview.js +207 -0
- package/dist/utils/scrollActivity.d.ts +14 -0
- package/dist/utils/scrollActivity.js +63 -0
- package/dist/utils/shadowDom.d.ts +67 -0
- package/dist/utils/shadowDom.js +211 -0
- package/dist/utils/vectorUtils.js +5 -3
- package/dist/utils/visibilityCondition.d.ts +40 -0
- package/dist/utils/visibilityCondition.js +535 -0
- package/package.json +6 -5
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
|
|
36
36
|
// Component config
|
|
37
37
|
import type { InputControllerConfig } from './index';
|
|
38
|
+
|
|
39
|
+
// The cursor of a drag is shown on the module's element, not on the body of the customer's page
|
|
40
|
+
import { lockPointerStyle } from '../../utils/shadowDom';
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
43
|
* Core state and configuration props
|
|
@@ -58,6 +61,8 @@
|
|
|
58
61
|
let joystickHandle: HTMLDivElement;
|
|
59
62
|
let isDragging = false;
|
|
60
63
|
let isHovering = false;
|
|
64
|
+
// Puts back the cursor the page had before the drag (does nothing when no drag is going on)
|
|
65
|
+
let releasePointerStyle: () => void = () => {};
|
|
61
66
|
|
|
62
67
|
/**
|
|
63
68
|
* Reactive position state
|
|
@@ -125,8 +130,9 @@
|
|
|
125
130
|
updatePosition(event.clientX, event.clientY);
|
|
126
131
|
document.addEventListener('mousemove', handleMouseMove);
|
|
127
132
|
document.addEventListener('mouseup', handleMouseUp);
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
// A second press before the first one was let go must not leave the first lock behind
|
|
134
|
+
releasePointerStyle();
|
|
135
|
+
releasePointerStyle = lockPointerStyle(joystickContainer, 'grabbing');
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
function handleMouseMove(event: MouseEvent) {
|
|
@@ -139,9 +145,7 @@
|
|
|
139
145
|
onDragStateChange(false);
|
|
140
146
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
141
147
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
142
|
-
|
|
143
|
-
document.body.classList.remove('position-dragging');
|
|
144
|
-
document.body.style.userSelect = '';
|
|
148
|
+
releasePointerStyle();
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
/**
|
|
@@ -177,13 +181,12 @@
|
|
|
177
181
|
});
|
|
178
182
|
|
|
179
183
|
onDestroy(() => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
document.body.style.userSelect = '';
|
|
184
|
+
// Only undoes what this drag did (it used to clear the cursor and the text selection of the whole page)
|
|
185
|
+
releasePointerStyle();
|
|
183
186
|
});
|
|
184
187
|
</script>
|
|
185
188
|
|
|
186
|
-
<div class="position-input-controller widgetic-colors">
|
|
189
|
+
<div class="position-input-controller widgetic-colors" class:position-input-controller-dragging={isDragging}>
|
|
187
190
|
<!-- Joystick UI -->
|
|
188
191
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
189
192
|
<div
|
|
@@ -228,8 +231,8 @@
|
|
|
228
231
|
</div>
|
|
229
232
|
|
|
230
233
|
<style>
|
|
231
|
-
/*
|
|
232
|
-
|
|
234
|
+
/* While dragging, everything inside the controller shows the grabbing cursor (this used to be a class on the page's body) */
|
|
235
|
+
.position-input-controller-dragging :global(*) {
|
|
233
236
|
cursor: grabbing !important;
|
|
234
237
|
user-select: none !important;
|
|
235
238
|
-webkit-user-select: none !important;
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
<script lang="ts">
|
|
47
47
|
// Svelte imports
|
|
48
48
|
import { onMount, onDestroy } from 'svelte';
|
|
49
|
+
import { listenForScroll } from '../../utils/shadowDom';
|
|
49
50
|
|
|
50
51
|
// Shadcn UI Components
|
|
51
52
|
import { Button, RangeCalendar } from '@widgetic/design-system/components';
|
|
@@ -177,19 +178,24 @@
|
|
|
177
178
|
closeDropdown();
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
//
|
|
181
|
+
// The control's own element, which says which tree (the page or a module's shadow root) to listen for scrolling in
|
|
182
|
+
let controllerElement: HTMLDivElement | undefined;
|
|
183
|
+
let stopListeningForScroll: () => void = () => {};
|
|
184
|
+
|
|
185
|
+
// Setup scroll event listener on mount (the page and, inside a module, the module's own panels: a scroll never
|
|
186
|
+
// leaves a shadow root, so a listener on the window alone does not hear about those)
|
|
181
187
|
onMount(() => {
|
|
182
|
-
|
|
188
|
+
stopListeningForScroll = listenForScroll(controllerElement, handleScroll);
|
|
183
189
|
});
|
|
184
190
|
|
|
185
191
|
// Cleanup scroll event listener on destroy
|
|
186
192
|
onDestroy(() => {
|
|
187
|
-
|
|
193
|
+
stopListeningForScroll();
|
|
188
194
|
});
|
|
189
195
|
</script>
|
|
190
196
|
|
|
191
197
|
<!-- Range Calendar Container -->
|
|
192
|
-
<div class="flex flex-col gap-2 widgetic-colors">
|
|
198
|
+
<div bind:this={controllerElement} class="flex flex-col gap-2 widgetic-colors">
|
|
193
199
|
<div class="flex items-center w-full relative">
|
|
194
200
|
<div class="flex-1 min-w-0">
|
|
195
201
|
<InputControllerHeader {label} helpText={helpText} />
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import Lock from '../icons/locked.svelte';
|
|
29
29
|
import Unlock from '../icons/unlocked.svelte';
|
|
30
30
|
import type { InputControllerConfig } from './index';
|
|
31
|
+
import { activeElementIn } from '../../utils/shadowDom';
|
|
31
32
|
|
|
32
33
|
// Constants
|
|
33
34
|
const MIN_RADIUS = 0;
|
|
@@ -63,19 +64,22 @@
|
|
|
63
64
|
|
|
64
65
|
if (isLocked) {
|
|
65
66
|
// When locked, update all corners to the same value
|
|
66
|
-
|
|
67
|
+
value = {
|
|
67
68
|
topLeft: newValue,
|
|
68
69
|
topRight: newValue,
|
|
69
70
|
bottomRight: newValue,
|
|
70
71
|
bottomLeft: newValue
|
|
71
|
-
}
|
|
72
|
+
};
|
|
72
73
|
} else {
|
|
73
74
|
// When unlocked, update only the specified corner
|
|
74
|
-
|
|
75
|
+
value = {
|
|
75
76
|
...value,
|
|
76
77
|
[corner]: newValue
|
|
77
|
-
}
|
|
78
|
+
};
|
|
78
79
|
}
|
|
80
|
+
// The fields show the change at once, in every corner it reached: the parent hands the value back only when it
|
|
81
|
+
// builds this control again, and until then the corners that were changed together kept showing the old number
|
|
82
|
+
onChange(value);
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
// Handler for input changes
|
|
@@ -167,9 +171,12 @@
|
|
|
167
171
|
|
|
168
172
|
// Reset focused corner if we're not focusing another input immediately
|
|
169
173
|
setTimeout(() => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
// The focused element of the tree this control is in: inside a module `document.activeElement` is the module's
|
|
175
|
+
// host element, which is never one of the corners
|
|
176
|
+
const focusedElement = activeElementIn(input);
|
|
177
|
+
if (!focusedElement ||
|
|
178
|
+
!(focusedElement instanceof HTMLInputElement) ||
|
|
179
|
+
!Object.values(inputRefs).includes(focusedElement)) {
|
|
173
180
|
focusedCorner = null;
|
|
174
181
|
}
|
|
175
182
|
}, 0);
|
|
@@ -182,12 +189,13 @@
|
|
|
182
189
|
// If locking, normalize all values to the first focused corner or top-left
|
|
183
190
|
if (isLocked && focusedCorner) {
|
|
184
191
|
const referenceValue = value[focusedCorner];
|
|
185
|
-
|
|
192
|
+
value = {
|
|
186
193
|
topLeft: referenceValue,
|
|
187
194
|
topRight: referenceValue,
|
|
188
195
|
bottomRight: referenceValue,
|
|
189
196
|
bottomLeft: referenceValue
|
|
190
|
-
}
|
|
197
|
+
};
|
|
198
|
+
onChange(value);
|
|
191
199
|
}
|
|
192
200
|
}
|
|
193
201
|
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
// Add local ColorPickerInputController import
|
|
72
72
|
import ColorPickerInputController from './ColorPickerInputController.svelte';
|
|
73
73
|
|
|
74
|
+
// Looking things up and listening for scrolling in the tree the editor is in (a module has a shadow root)
|
|
75
|
+
import { isEventInside, listenForScroll, queryAllInTreeOf, queryInTreeOf } from '../../utils/shadowDom';
|
|
76
|
+
|
|
74
77
|
// Add this constant for the decoration type
|
|
75
78
|
const TRANSITION_DURATION = 250;
|
|
76
79
|
|
|
@@ -533,7 +536,9 @@
|
|
|
533
536
|
});
|
|
534
537
|
|
|
535
538
|
updateActiveFormats();
|
|
539
|
+
// Choosing from either menu (text style or list style) closes it: the list style menu used to stay open.
|
|
536
540
|
textStyleDropdownOpen = false;
|
|
541
|
+
listStyleDropdownOpen = false;
|
|
537
542
|
} catch (error) {
|
|
538
543
|
console.error('Error changing text style:', error);
|
|
539
544
|
}
|
|
@@ -843,8 +848,11 @@
|
|
|
843
848
|
if (isDestroyed) return;
|
|
844
849
|
const handleClickOutside = (event: MouseEvent) => {
|
|
845
850
|
if (isDestroyed) return;
|
|
846
|
-
|
|
847
|
-
|
|
851
|
+
// Looked for in the tree this editor is in (inside a module, `document` has none of the editor's elements and may
|
|
852
|
+
// have a popover of the customer's page), and checked with the event's path (the target the document sees is the
|
|
853
|
+
// module's host)
|
|
854
|
+
const colorSelectModals = queryAllInTreeOf(toolbarRef, '[data-radix-popper-content-wrapper]');
|
|
855
|
+
const isClickingColorSelect = colorSelectModals.some((colorSelectModal) => isEventInside(event, colorSelectModal));
|
|
848
856
|
|
|
849
857
|
// Don't do anything if clicking within the color select modal
|
|
850
858
|
if (isClickingColorSelect) {
|
|
@@ -941,9 +949,11 @@
|
|
|
941
949
|
function handleEditorBlur(relatedTarget: Node | null) {
|
|
942
950
|
if (!editorInstance) return;
|
|
943
951
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const
|
|
952
|
+
// Looked for in the tree the editor is in: `document` does not see inside a module's shadow root, and may have a
|
|
953
|
+
// dialog of the customer's page
|
|
954
|
+
const colorSelectModal = queryInTreeOf(toolbarRef, '[data-radix-popper-content-wrapper]');
|
|
955
|
+
const popoverContent = queryInTreeOf(toolbarRef, '[role="dialog"]');
|
|
956
|
+
const commandMenu = queryInTreeOf(toolbarRef, '[cmdk-root]');
|
|
947
957
|
|
|
948
958
|
const isClickingInside = toolbarRef?.contains(relatedTarget) ||
|
|
949
959
|
editorInstance.view.dom.contains(relatedTarget as Node) ||
|
|
@@ -1030,12 +1040,15 @@
|
|
|
1030
1040
|
}
|
|
1031
1041
|
|
|
1032
1042
|
// Add to lifecycle management
|
|
1043
|
+
// (listens to the page and, inside a module, to the module's own panels too: a scroll never leaves a shadow root)
|
|
1044
|
+
let stopListeningForScroll: () => void = () => {};
|
|
1045
|
+
|
|
1033
1046
|
onMount(() => {
|
|
1034
|
-
|
|
1047
|
+
stopListeningForScroll = listenForScroll(toolbarRef, handleScroll);
|
|
1035
1048
|
});
|
|
1036
1049
|
|
|
1037
1050
|
onDestroy(() => {
|
|
1038
|
-
|
|
1051
|
+
stopListeningForScroll();
|
|
1039
1052
|
});
|
|
1040
1053
|
|
|
1041
1054
|
// Add cleanup function
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
import { onMount, onDestroy } from 'svelte';
|
|
20
20
|
|
|
21
21
|
import { TextAreaInputController } from './index';
|
|
22
|
+
import { createPreviewHtml } from '../../utils/richTextPreview';
|
|
23
|
+
import { escapeHtml } from '../../utils/htmlSafety';
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* Component props
|
|
@@ -42,7 +44,7 @@
|
|
|
42
44
|
$: isShowingPlaceholder = !value && placeholder;
|
|
43
45
|
|
|
44
46
|
// Create a preview version of the text that preserves formatting
|
|
45
|
-
$: previewText = value ? createPreviewText(value) : placeholder;
|
|
47
|
+
$: previewText = value ? createPreviewText(value) : escapeHtml(placeholder);
|
|
46
48
|
|
|
47
49
|
// Add this to track the current color
|
|
48
50
|
let currentColor = '#1a1a1a'; // Default color
|
|
@@ -53,6 +55,10 @@
|
|
|
53
55
|
// Add this to store the background rect ref
|
|
54
56
|
let backgroundRectRef: HTMLDivElement;
|
|
55
57
|
|
|
58
|
+
// The box the text editor is shown in. The editor is found through it: a search of the whole `document` finds nothing
|
|
59
|
+
// inside a module's shadow root, and finds the customer's own text editor (and focuses it) when the page has one.
|
|
60
|
+
let portalContentRef: HTMLDivElement | undefined;
|
|
61
|
+
|
|
56
62
|
// Add these near the top with other state declarations
|
|
57
63
|
let isClosing = false;
|
|
58
64
|
|
|
@@ -60,73 +66,16 @@
|
|
|
60
66
|
* Methods
|
|
61
67
|
*/
|
|
62
68
|
function createPreviewText(text: string) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const parser = new DOMParser();
|
|
73
|
-
const doc = parser.parseFromString(html, 'text/html');
|
|
74
|
-
|
|
75
|
-
function processNode(node: Node): string {
|
|
76
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
77
|
-
return node.textContent || '';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
81
|
-
const element = node as Element;
|
|
82
|
-
let tag = element.tagName.toLowerCase();
|
|
83
|
-
const attributes: Record<string, string> = {};
|
|
84
|
-
const children = Array.from(node.childNodes)
|
|
85
|
-
.map(child => processNode(child))
|
|
86
|
-
.join('');
|
|
87
|
-
|
|
88
|
-
// Flatten block wrappers so marks render inside the compact clamp box
|
|
89
|
-
if (tag === 'p' || tag === 'div' || tag === 'br') {
|
|
90
|
-
return tag === 'br' ? ' ' : `${children} `;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const style = element.getAttribute('style');
|
|
94
|
-
if (style) {
|
|
95
|
-
const colorMatch = style.match(/color:\s*([^;]+)/);
|
|
96
|
-
if (colorMatch) {
|
|
97
|
-
attributes.style = `--color: ${colorMatch[1]}; ${style}`;
|
|
98
|
-
} else {
|
|
99
|
-
attributes.style = style;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const textStyle = element.getAttribute('data-text-style');
|
|
104
|
-
if (textStyle) {
|
|
105
|
-
attributes['data-text-style'] = textStyle;
|
|
106
|
-
attributes.class = (attributes.class || '') + ' preserve-color';
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (tag.match(/^h[1-4]$/)) {
|
|
110
|
-
tag = 'span';
|
|
111
|
-
attributes.class = (attributes.class || '') + ' tiptap-heading';
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let startTag = `<${tag}`;
|
|
115
|
-
for (const [key, attrValue] of Object.entries(attributes)) {
|
|
116
|
-
startTag += ` ${key}="${attrValue.trim()}"`;
|
|
117
|
-
}
|
|
118
|
-
startTag += '>';
|
|
119
|
-
|
|
120
|
-
return `${startTag}${children}</${tag}>`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return '';
|
|
69
|
+
// The text is rich text somebody typed or pasted, and this editor can run inside a customer's page: the preview is
|
|
70
|
+
// not a copy of that HTML. It is built by a renderer that escapes every text and keeps only the tags and styles on
|
|
71
|
+
// its lists (see richTextPreview.ts). Block wrappers (p, div, br) are flattened there, so marks render inside the
|
|
72
|
+
// compact clamp box.
|
|
73
|
+
try {
|
|
74
|
+
return createPreviewHtml(text);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error('[TextAreaPreview] Could not build the preview of the text:', error);
|
|
77
|
+
return escapeHtml(text);
|
|
124
78
|
}
|
|
125
|
-
|
|
126
|
-
return Array.from(doc.body.childNodes)
|
|
127
|
-
.map(node => processNode(node))
|
|
128
|
-
.join('')
|
|
129
|
-
.trim();
|
|
130
79
|
}
|
|
131
80
|
|
|
132
81
|
function handleFullEditorChange(html: string) {
|
|
@@ -141,7 +90,7 @@
|
|
|
141
90
|
|
|
142
91
|
$: if (isShowingTextArea) {
|
|
143
92
|
setTimeout(() => {
|
|
144
|
-
const editor =
|
|
93
|
+
const editor = portalContentRef?.querySelector('.ProseMirror');
|
|
145
94
|
if (editor instanceof HTMLElement) {
|
|
146
95
|
editor.focus();
|
|
147
96
|
}
|
|
@@ -213,7 +162,7 @@
|
|
|
213
162
|
isClosing = true;
|
|
214
163
|
|
|
215
164
|
// Add animation end handler
|
|
216
|
-
const content =
|
|
165
|
+
const content = portalContentRef;
|
|
217
166
|
const overlay = content?.previousElementSibling as HTMLDivElement;
|
|
218
167
|
|
|
219
168
|
const handleAnimationEnd = () => {
|
|
@@ -280,6 +229,7 @@
|
|
|
280
229
|
></div>
|
|
281
230
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
282
231
|
<div
|
|
232
|
+
bind:this={portalContentRef}
|
|
283
233
|
class="text-area-portal-content absolute mt-4 mx-6 mb-6 inset-0 {isClosing ? 'scale-out-animation' : 'scale-in-animation'}"
|
|
284
234
|
style="z-index: 51; transform: translateZ(0); backface-visibility: hidden;"
|
|
285
235
|
on:mouseenter={handlePortalMouseEnter}
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import { Portal } from '@widgetic/design-system/components';
|
|
30
30
|
import InputControllerHeader from './InputControllerHeader.svelte';
|
|
31
31
|
import type { InputControllerConfig } from './index';
|
|
32
|
+
import { isEventInside, listenForScroll } from '../../utils/shadowDom';
|
|
32
33
|
|
|
33
34
|
export let label: TimeInputControllerProps['label'];
|
|
34
35
|
export let value: TimeInputControllerProps['value'] = "00:00";
|
|
@@ -256,7 +257,9 @@
|
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
function handleClickOutside(event: MouseEvent) {
|
|
259
|
-
|
|
260
|
+
// Seen from the document, a click inside a shadow root has the module's host as its target, so the click's path
|
|
261
|
+
// is what says whether it was inside the dropdown
|
|
262
|
+
if (dropdownContainer && !isEventInside(event, dropdownContainer)) {
|
|
260
263
|
closeDropdown();
|
|
261
264
|
}
|
|
262
265
|
}
|
|
@@ -290,11 +293,14 @@
|
|
|
290
293
|
}
|
|
291
294
|
}
|
|
292
295
|
|
|
296
|
+
// Stops the scroll listener (it listens to the page and, in a module, to the module's own panels)
|
|
297
|
+
let stopListeningForScroll: () => void = () => {};
|
|
298
|
+
|
|
293
299
|
// Update onMount to add scroll listener with capture phase
|
|
294
300
|
onMount(() => {
|
|
295
301
|
parseTimeFromValue(value);
|
|
296
302
|
updateValue();
|
|
297
|
-
|
|
303
|
+
stopListeningForScroll = listenForScroll(clockContainer, handleScroll);
|
|
298
304
|
});
|
|
299
305
|
|
|
300
306
|
// Update onDestroy to remove scroll listener
|
|
@@ -302,7 +308,7 @@
|
|
|
302
308
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
303
309
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
304
310
|
document.removeEventListener('click', handleClickOutside);
|
|
305
|
-
|
|
311
|
+
stopListeningForScroll();
|
|
306
312
|
});
|
|
307
313
|
|
|
308
314
|
// Watch for external value changes
|