@widgetic/editor 4.0.10 → 4.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.
Files changed (84) hide show
  1. package/README.md +242 -0
  2. package/dist/components/WidgetPreview.svelte +123 -86
  3. package/dist/components/WidgetPreview.svelte.d.ts +8 -0
  4. package/dist/components/color-select/ColorSelect.svelte +4 -4
  5. package/dist/components/editors/CompositionEditorApp.svelte +562 -0
  6. package/dist/components/editors/CompositionEditorApp.svelte.d.ts +95 -0
  7. package/dist/components/editors/EmbedEditor.svelte +33 -22
  8. package/dist/components/editors/EmbedEditor.svelte.d.ts +1 -1
  9. package/dist/components/editors/PropsEditor.svelte +267 -850
  10. package/dist/components/editors/PropsEditor.svelte.d.ts +10 -3
  11. package/dist/components/ic/AngleInputController.svelte +1 -1
  12. package/dist/components/ic/BrowserInputController.svelte +58 -31
  13. package/dist/components/ic/ColorPickerInputController.svelte +55 -21
  14. package/dist/components/ic/DateFieldInputController.svelte +34 -14
  15. package/dist/components/ic/DatePickerInputController.svelte +37 -21
  16. package/dist/components/ic/DropdownInputController.svelte +21 -9
  17. package/dist/components/ic/DropdownInputController.svelte.d.ts +1 -1
  18. package/dist/components/ic/FixedPositionInputController.svelte +12 -6
  19. package/dist/components/ic/FontInputController.svelte +100 -76
  20. package/dist/components/ic/InputControllerHeader.svelte +16 -9
  21. package/dist/components/ic/NumberControls.svelte +4 -4
  22. package/dist/components/ic/NumberInputController.svelte +6 -4
  23. package/dist/components/ic/PositionInputController.svelte +23 -15
  24. package/dist/components/ic/RangeCalendarInputController.svelte +36 -20
  25. package/dist/components/ic/RoundedCornersInputController.svelte +39 -26
  26. package/dist/components/ic/SliderInputController.svelte +7 -2
  27. package/dist/components/ic/SwitchInputController.svelte +12 -3
  28. package/dist/components/ic/TagsInputController.svelte +19 -4
  29. package/dist/components/ic/TextAreaInputController.svelte +79 -51
  30. package/dist/components/ic/TextAreaPreviewInputController.svelte +27 -72
  31. package/dist/components/ic/TextInputController.svelte +17 -1
  32. package/dist/components/ic/TimeInputController.svelte +11 -4
  33. package/dist/components/ic/VectorInputController.svelte +101 -70
  34. package/dist/components/ic/shared/ItemGrid.svelte +12 -4
  35. package/dist/components/ic/shared/RecentItemsSection.svelte +16 -11
  36. package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -1
  37. package/dist/composition/compositionApi.d.ts +95 -0
  38. package/dist/composition/compositionApi.js +289 -0
  39. package/dist/composition/compositionEditorController.d.ts +263 -0
  40. package/dist/composition/compositionEditorController.js +746 -0
  41. package/dist/composition/compositionHelpers.d.ts +74 -0
  42. package/dist/composition/compositionHelpers.js +405 -0
  43. package/dist/i18n/index.d.ts +4 -0
  44. package/dist/i18n/index.js +4 -0
  45. package/dist/i18n/labels.d.ts +15 -0
  46. package/dist/i18n/labels.js +57 -0
  47. package/dist/i18n/language.d.ts +228 -0
  48. package/dist/i18n/language.js +34 -0
  49. package/dist/i18n/languageKit.d.ts +54 -0
  50. package/dist/i18n/languageKit.js +102 -0
  51. package/dist/i18n/messages.d.ts +241 -0
  52. package/dist/i18n/messages.js +253 -0
  53. package/dist/i18n/translate.d.ts +6 -0
  54. package/dist/i18n/translate.js +8 -0
  55. package/dist/index.d.ts +4 -0
  56. package/dist/index.js +8 -0
  57. package/dist/mediaLibraryContext.d.ts +10 -0
  58. package/dist/mediaLibraryContext.js +30 -0
  59. package/dist/utils/FontManager.d.ts +21 -5
  60. package/dist/utils/FontManager.js +62 -67
  61. package/dist/utils/VectorManager.d.ts +0 -1
  62. package/dist/utils/VectorManager.js +10 -12
  63. package/dist/utils/dockPlacement.d.ts +12 -0
  64. package/dist/utils/dockPlacement.js +13 -0
  65. package/dist/utils/editorStorage.d.ts +23 -0
  66. package/dist/utils/editorStorage.js +95 -0
  67. package/dist/utils/fieldDirection.d.ts +13 -0
  68. package/dist/utils/fieldDirection.js +25 -0
  69. package/dist/utils/fontUtils.js +5 -3
  70. package/dist/utils/htmlSafety.d.ts +21 -0
  71. package/dist/utils/htmlSafety.js +38 -0
  72. package/dist/utils/itemReorder.d.ts +90 -0
  73. package/dist/utils/itemReorder.js +354 -0
  74. package/dist/utils/recentColors.js +4 -2
  75. package/dist/utils/richTextPreview.d.ts +38 -0
  76. package/dist/utils/richTextPreview.js +207 -0
  77. package/dist/utils/scrollActivity.d.ts +14 -0
  78. package/dist/utils/scrollActivity.js +63 -0
  79. package/dist/utils/shadowDom.d.ts +67 -0
  80. package/dist/utils/shadowDom.js +211 -0
  81. package/dist/utils/vectorUtils.js +5 -3
  82. package/dist/utils/visibilityCondition.d.ts +40 -0
  83. package/dist/utils/visibilityCondition.js +535 -0
  84. package/package.json +7 -6
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Reordering a list of items by dragging a handle.
3
+ *
4
+ * This replaces @shopify/draggable, which cannot work inside a shadow root (the editor runs in one when it is a
5
+ * module on a customer's page):
6
+ * - it listens on the `document`, where every event that comes from inside a shadow root has the shadow host as its
7
+ * target, so it never finds the list it was given and no drag ever starts;
8
+ * - the events it makes itself do not leave the shadow root, so its own listeners on the `document` never hear them;
9
+ * - it adds classes to the page's `<body>`, switches text selection off on it, appends its floating copy of the item
10
+ * to it, and leaves a screen reader region in it that stays after the editor is gone.
11
+ *
12
+ * This one listens on the list itself, keeps hold of the pointer with pointer capture (so the drag carries on over the
13
+ * preview frame and outside the editor), works out where an item would be dropped from where the items are on the
14
+ * screen, and leaves the page it runs in alone. It never moves the items itself: the list changes only when the
15
+ * caller changes the data the list is drawn from, in `onReorder`.
16
+ */
17
+ /** How far (px) the pointer must move from where it was pressed before the press counts as a drag and not a click. */
18
+ export declare const DRAG_START_DISTANCE_PX = 4;
19
+ /** The height (px) of the copy of the item that follows the pointer. */
20
+ export declare const MIRROR_HEIGHT_PX = 56;
21
+ /** How close (px) to the top or bottom edge of the scrolling area the pointer must be for the area to start scrolling. */
22
+ export declare const EDGE_SCROLL_ZONE_PX = 56;
23
+ /** The most (px) the scrolling area moves in one frame, reached when the pointer is at or beyond the edge. */
24
+ export declare const EDGE_SCROLL_MAX_STEP_PX = 18;
25
+ /** The classes put on the list and its items while one is dragged; the editor's stylesheet draws them. */
26
+ export declare const REORDER_CLASSES: {
27
+ /** On the list for as long as an item is being dragged. */
28
+ readonly list: "is-reordering";
29
+ /** On the item being dragged. */
30
+ readonly source: "is-reorder-source";
31
+ /** On the item the dragged one would be dropped in front of. */
32
+ readonly dropBefore: "is-reorder-drop-before";
33
+ /** On the last item, when the dragged one would be dropped behind it. */
34
+ readonly dropAfter: "is-reorder-drop-after";
35
+ /** On the floating copy of the dragged item. */
36
+ readonly mirror: "reorder-mirror";
37
+ };
38
+ /** The top and height of an item on the screen. */
39
+ export interface VerticalBox {
40
+ top: number;
41
+ height: number;
42
+ }
43
+ /**
44
+ * Where a dragged item ends up when it is dropped with the pointer at `pointerY`: its index in the list once it has
45
+ * been taken out and put back. `otherItems` are the boxes of the items on the screen without the dragged one. The
46
+ * dragged item goes after every item whose middle is above the pointer.
47
+ */
48
+ export declare function dropIndexForPointer(otherItems: readonly VerticalBox[], pointerY: number): number;
49
+ /**
50
+ * How far (px) to scroll the area that holds the list in this frame: upwards (negative) when the pointer is in the
51
+ * top `zone` of the area or above it, downwards (positive) in the bottom `zone` or below it, faster the nearer the
52
+ * edge, and 0 anywhere else.
53
+ */
54
+ export declare function edgeScrollStep(areaTop: number, areaBottom: number, pointerY: number, zone?: number, maxStep?: number): number;
55
+ /**
56
+ * A copy of `items` with the item at `oldIndex` moved to `newIndex` (its index in the copy). When either index is not
57
+ * a place in the list, the copy keeps the original order.
58
+ */
59
+ export declare function moveListItem<ItemType>(items: readonly ItemType[], oldIndex: number, newIndex: number): ItemType[];
60
+ /** What `reorderableList` needs to know about the list. */
61
+ export interface ItemReorderOptions {
62
+ /** CSS selector of one item. Items are direct children of the list. */
63
+ itemSelector: string;
64
+ /** CSS selector of the part of an item that starts a drag when it is pressed. */
65
+ handleSelector: string;
66
+ /** The drag began (the pointer moved far enough from where it was pressed). The list is not changed yet. */
67
+ onDragStart?: (drag: {
68
+ item: HTMLElement;
69
+ index: number;
70
+ }) => void;
71
+ /** The item was dropped at another place in the list. Change the data the list is drawn from here. */
72
+ onReorder: (move: {
73
+ item: HTMLElement;
74
+ oldIndex: number;
75
+ newIndex: number;
76
+ }) => void;
77
+ /** The drag is over: dropped (`reordered` tells if the order changed), cancelled with Escape, or the list went away. */
78
+ onDragEnd?: (outcome: {
79
+ reordered: boolean;
80
+ }) => void;
81
+ }
82
+ /**
83
+ * Svelte action that makes the direct children of a list reorderable by dragging their handle:
84
+ * `<div use:reorderableList={{ itemSelector, handleSelector, onReorder }}>`.
85
+ */
86
+ export declare function reorderableList(list: HTMLElement, initialOptions: ItemReorderOptions): {
87
+ /** Svelte calls this when the options change; the callbacks of the new options are the ones used from then on. */
88
+ update(nextOptions: ItemReorderOptions): void;
89
+ destroy(): void;
90
+ };
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Reordering a list of items by dragging a handle.
3
+ *
4
+ * This replaces @shopify/draggable, which cannot work inside a shadow root (the editor runs in one when it is a
5
+ * module on a customer's page):
6
+ * - it listens on the `document`, where every event that comes from inside a shadow root has the shadow host as its
7
+ * target, so it never finds the list it was given and no drag ever starts;
8
+ * - the events it makes itself do not leave the shadow root, so its own listeners on the `document` never hear them;
9
+ * - it adds classes to the page's `<body>`, switches text selection off on it, appends its floating copy of the item
10
+ * to it, and leaves a screen reader region in it that stays after the editor is gone.
11
+ *
12
+ * This one listens on the list itself, keeps hold of the pointer with pointer capture (so the drag carries on over the
13
+ * preview frame and outside the editor), works out where an item would be dropped from where the items are on the
14
+ * screen, and leaves the page it runs in alone. It never moves the items itself: the list changes only when the
15
+ * caller changes the data the list is drawn from, in `onReorder`.
16
+ */
17
+ import { acquirePortalHost, releasePortalHost } from './shadowDom';
18
+ /** How far (px) the pointer must move from where it was pressed before the press counts as a drag and not a click. */
19
+ export const DRAG_START_DISTANCE_PX = 4;
20
+ /** The height (px) of the copy of the item that follows the pointer. */
21
+ export const MIRROR_HEIGHT_PX = 56;
22
+ /** How close (px) to the top or bottom edge of the scrolling area the pointer must be for the area to start scrolling. */
23
+ export const EDGE_SCROLL_ZONE_PX = 56;
24
+ /** The most (px) the scrolling area moves in one frame, reached when the pointer is at or beyond the edge. */
25
+ export const EDGE_SCROLL_MAX_STEP_PX = 18;
26
+ /** How long (ms) a click right after a drag is ignored, so letting go of the handle does not also open the item. */
27
+ const CLICK_AFTER_DRAG_GRACE_MS = 50;
28
+ /** The id of the layer, in the popover layer of the tree the list is in, that the floating copy of the item is drawn in. */
29
+ const MIRROR_LAYER_ID = 'widgetic-reorder-mirror-layer';
30
+ /** The classes put on the list and its items while one is dragged; the editor's stylesheet draws them. */
31
+ export const REORDER_CLASSES = {
32
+ /** On the list for as long as an item is being dragged. */
33
+ list: 'is-reordering',
34
+ /** On the item being dragged. */
35
+ source: 'is-reorder-source',
36
+ /** On the item the dragged one would be dropped in front of. */
37
+ dropBefore: 'is-reorder-drop-before',
38
+ /** On the last item, when the dragged one would be dropped behind it. */
39
+ dropAfter: 'is-reorder-drop-after',
40
+ /** On the floating copy of the dragged item. */
41
+ mirror: 'reorder-mirror'
42
+ };
43
+ /**
44
+ * Where a dragged item ends up when it is dropped with the pointer at `pointerY`: its index in the list once it has
45
+ * been taken out and put back. `otherItems` are the boxes of the items on the screen without the dragged one. The
46
+ * dragged item goes after every item whose middle is above the pointer.
47
+ */
48
+ export function dropIndexForPointer(otherItems, pointerY) {
49
+ let dropIndex = 0;
50
+ for (const box of otherItems) {
51
+ if (box.top + box.height / 2 < pointerY)
52
+ dropIndex += 1;
53
+ }
54
+ return dropIndex;
55
+ }
56
+ /**
57
+ * How far (px) to scroll the area that holds the list in this frame: upwards (negative) when the pointer is in the
58
+ * top `zone` of the area or above it, downwards (positive) in the bottom `zone` or below it, faster the nearer the
59
+ * edge, and 0 anywhere else.
60
+ */
61
+ export function edgeScrollStep(areaTop, areaBottom, pointerY, zone = EDGE_SCROLL_ZONE_PX, maxStep = EDGE_SCROLL_MAX_STEP_PX) {
62
+ if (!(zone > 0))
63
+ return 0;
64
+ const depthIntoTopZone = areaTop + zone - pointerY;
65
+ if (depthIntoTopZone > 0)
66
+ return -Math.round(maxStep * Math.min(1, depthIntoTopZone / zone)) || 0;
67
+ const depthIntoBottomZone = pointerY - (areaBottom - zone);
68
+ if (depthIntoBottomZone > 0)
69
+ return Math.round(maxStep * Math.min(1, depthIntoBottomZone / zone)) || 0;
70
+ return 0;
71
+ }
72
+ /**
73
+ * A copy of `items` with the item at `oldIndex` moved to `newIndex` (its index in the copy). When either index is not
74
+ * a place in the list, the copy keeps the original order.
75
+ */
76
+ export function moveListItem(items, oldIndex, newIndex) {
77
+ const reordered = [...items];
78
+ const isPlaceInList = (index) => Number.isInteger(index) && index >= 0 && index < items.length;
79
+ if (!isPlaceInList(oldIndex) || !isPlaceInList(newIndex))
80
+ return reordered;
81
+ const [movedItem] = reordered.splice(oldIndex, 1);
82
+ reordered.splice(newIndex, 0, movedItem);
83
+ return reordered;
84
+ }
85
+ /** The nearest ancestor that scrolls vertically, looking out of shadow roots too; `null` when the page itself scrolls. */
86
+ function findScrollingAncestor(start) {
87
+ let current = start.parentNode;
88
+ while (current) {
89
+ if (current instanceof HTMLElement) {
90
+ const { overflowY } = getComputedStyle(current);
91
+ if ((overflowY === 'auto' || overflowY === 'scroll') && current.scrollHeight > current.clientHeight) {
92
+ return current;
93
+ }
94
+ }
95
+ current = current.parentNode ?? (current instanceof ShadowRoot ? current.host : null);
96
+ }
97
+ return null;
98
+ }
99
+ /**
100
+ * Svelte action that makes the direct children of a list reorderable by dragging their handle:
101
+ * `<div use:reorderableList={{ itemSelector, handleSelector, onReorder }}>`.
102
+ */
103
+ export function reorderableList(list, initialOptions) {
104
+ let options = initialOptions;
105
+ let press = null;
106
+ const view = list.ownerDocument.defaultView ?? window;
107
+ /** Runs a callback of the caller; one that throws must not leave a drag half done. */
108
+ function runCallback(name, callback) {
109
+ try {
110
+ callback();
111
+ }
112
+ catch (error) {
113
+ console.error(`[itemReorder] The ${name} callback failed:`, error);
114
+ }
115
+ }
116
+ function currentItems() {
117
+ return Array.from(list.children).filter((child) => child instanceof HTMLElement && child.matches(options.itemSelector));
118
+ }
119
+ /** The copy of the item that follows the pointer, drawn in the popover layer so no ancestor can clip it. */
120
+ function createMirror(current, itemBox) {
121
+ const mirrorLayer = acquirePortalHost(list, MIRROR_LAYER_ID);
122
+ if (!mirrorLayer)
123
+ return;
124
+ const mirror = current.item.cloneNode(true);
125
+ mirror.classList.add(REORDER_CLASSES.mirror, 'widgetic-colors', 'widgetic-radius');
126
+ // The copy is only a picture: no ids that clash with the real item's, nothing to focus or press.
127
+ mirror.removeAttribute('data-id');
128
+ mirror.querySelectorAll('[id]').forEach((element) => element.removeAttribute('id'));
129
+ mirror.setAttribute('aria-hidden', 'true');
130
+ mirror.setAttribute('inert', '');
131
+ Object.assign(mirror.style, {
132
+ position: 'fixed',
133
+ left: '0',
134
+ top: '0',
135
+ width: `${itemBox.width}px`,
136
+ height: `${MIRROR_HEIGHT_PX}px`,
137
+ overflow: 'hidden',
138
+ pointerEvents: 'none',
139
+ zIndex: '1000'
140
+ });
141
+ mirrorLayer.appendChild(mirror);
142
+ current.mirror = mirror;
143
+ current.mirrorLayer = mirrorLayer;
144
+ }
145
+ function positionMirror(current) {
146
+ if (!current.mirror)
147
+ return;
148
+ const left = current.lastX - current.grabOffsetX;
149
+ const top = current.lastY - MIRROR_HEIGHT_PX / 2;
150
+ current.mirror.style.transform = `translate3d(${Math.round(left)}px, ${Math.round(top)}px, 0)`;
151
+ }
152
+ /** Works out where the item would be dropped, and draws a line there. */
153
+ function updateDropTarget(current) {
154
+ const items = currentItems();
155
+ const otherItems = items.filter((item) => item !== current.item);
156
+ const boxes = otherItems.map((item) => {
157
+ const { top, height } = item.getBoundingClientRect();
158
+ return { top, height };
159
+ });
160
+ current.dropIndex = dropIndexForPointer(boxes, current.lastY);
161
+ for (const item of items) {
162
+ item.classList.remove(REORDER_CLASSES.dropBefore, REORDER_CLASSES.dropAfter);
163
+ }
164
+ if (otherItems.length === 0 || current.dropIndex === current.sourceIndex)
165
+ return;
166
+ if (current.dropIndex < otherItems.length) {
167
+ otherItems[current.dropIndex].classList.add(REORDER_CLASSES.dropBefore);
168
+ }
169
+ else {
170
+ otherItems[otherItems.length - 1].classList.add(REORDER_CLASSES.dropAfter);
171
+ }
172
+ }
173
+ /** Scrolls the area that holds the list while the pointer is near its top or bottom edge. Runs every frame of a drag. */
174
+ function scrollNearEdges() {
175
+ const current = press;
176
+ if (!current?.dragging)
177
+ return;
178
+ try {
179
+ const area = current.scroller
180
+ ? current.scroller.getBoundingClientRect()
181
+ : { top: 0, bottom: view.innerHeight };
182
+ const step = edgeScrollStep(area.top, area.bottom, current.lastY);
183
+ if (step !== 0) {
184
+ if (current.scroller)
185
+ current.scroller.scrollTop += step;
186
+ else
187
+ view.scrollBy(0, step);
188
+ updateDropTarget(current);
189
+ }
190
+ }
191
+ catch (error) {
192
+ console.warn('[itemReorder] Could not scroll while dragging:', error);
193
+ }
194
+ current.scrollFrame = view.requestAnimationFrame(scrollNearEdges);
195
+ }
196
+ function beginDragging(current) {
197
+ current.dragging = true;
198
+ current.sourceIndex = currentItems().indexOf(current.item);
199
+ current.dropIndex = current.sourceIndex;
200
+ const itemBox = current.item.getBoundingClientRect();
201
+ current.grabOffsetX = Math.min(Math.max(current.startX - itemBox.left, 0), itemBox.width);
202
+ current.scroller = findScrollingAncestor(list);
203
+ // The copy is made first, so it does not carry the "this is the one being dragged" class.
204
+ createMirror(current, itemBox);
205
+ list.classList.add(REORDER_CLASSES.list);
206
+ current.item.classList.add(REORDER_CLASSES.source);
207
+ runCallback('onDragStart', () => options.onDragStart?.({ item: current.item, index: current.sourceIndex }));
208
+ current.scrollFrame = view.requestAnimationFrame(scrollNearEdges);
209
+ }
210
+ /** Lets a click right after a drag pass unseen: it would open or close the item the handle belongs to. */
211
+ function ignoreClickAfterDrag() {
212
+ const ignore = (event) => {
213
+ event.stopPropagation();
214
+ event.preventDefault();
215
+ };
216
+ list.addEventListener('click', ignore, { capture: true, once: true });
217
+ view.setTimeout(() => list.removeEventListener('click', ignore, { capture: true }), CLICK_AFTER_DRAG_GRACE_MS);
218
+ }
219
+ /** Ends the press, whether the item was dropped or the drag was cancelled, and puts everything back the way it was. */
220
+ function endPress(outcome) {
221
+ const current = press;
222
+ if (!current)
223
+ return;
224
+ // First of all: whatever happens below, this press is over and nothing can end it a second time.
225
+ press = null;
226
+ view.removeEventListener('pointermove', handlePointerMove);
227
+ view.removeEventListener('pointerup', handlePointerUp);
228
+ view.removeEventListener('pointercancel', handlePointerCancel);
229
+ view.removeEventListener('keydown', handleKeyDown, true);
230
+ if (current.scrollFrame !== null)
231
+ view.cancelAnimationFrame(current.scrollFrame);
232
+ try {
233
+ current.handle.releasePointerCapture(current.pointerId);
234
+ }
235
+ catch {
236
+ // The pointer was not captured any more (the handle left the page, or the browser let go): nothing to release.
237
+ }
238
+ if (!current.dragging)
239
+ return;
240
+ list.classList.remove(REORDER_CLASSES.list);
241
+ current.item.classList.remove(REORDER_CLASSES.source);
242
+ for (const item of currentItems()) {
243
+ item.classList.remove(REORDER_CLASSES.dropBefore, REORDER_CLASSES.dropAfter);
244
+ }
245
+ current.mirror?.remove();
246
+ releasePortalHost(current.mirrorLayer);
247
+ const hasMoved = outcome === 'dropped' && current.dropIndex !== current.sourceIndex;
248
+ ignoreClickAfterDrag();
249
+ if (hasMoved) {
250
+ runCallback('onReorder', () => options.onReorder({
251
+ item: current.item,
252
+ oldIndex: current.sourceIndex,
253
+ newIndex: current.dropIndex
254
+ }));
255
+ }
256
+ runCallback('onDragEnd', () => options.onDragEnd?.({ reordered: hasMoved }));
257
+ }
258
+ function handlePointerDown(event) {
259
+ try {
260
+ // One press at a time: a second finger or a second button does not start another drag.
261
+ if (press || event.button !== 0 || !event.isPrimary)
262
+ return;
263
+ const target = event.target;
264
+ if (!(target instanceof Element))
265
+ return;
266
+ const handle = target.closest(options.handleSelector);
267
+ if (!(handle instanceof HTMLElement) || !list.contains(handle))
268
+ return;
269
+ const item = handle.closest(options.itemSelector);
270
+ if (!(item instanceof HTMLElement) || item.parentElement !== list)
271
+ return;
272
+ press = {
273
+ pointerId: event.pointerId,
274
+ startX: event.clientX,
275
+ startY: event.clientY,
276
+ lastX: event.clientX,
277
+ lastY: event.clientY,
278
+ item,
279
+ handle,
280
+ dragging: false,
281
+ sourceIndex: -1,
282
+ dropIndex: -1,
283
+ grabOffsetX: 0,
284
+ mirror: null,
285
+ mirrorLayer: null,
286
+ scroller: null,
287
+ scrollFrame: null
288
+ };
289
+ // Keep getting this pointer wherever it goes: over the preview frame, outside the editor, outside the window.
290
+ try {
291
+ handle.setPointerCapture(event.pointerId);
292
+ }
293
+ catch (captureError) {
294
+ console.warn('[itemReorder] Could not capture the pointer; the drag stops at the edge of the page:', captureError);
295
+ }
296
+ view.addEventListener('pointermove', handlePointerMove);
297
+ view.addEventListener('pointerup', handlePointerUp);
298
+ view.addEventListener('pointercancel', handlePointerCancel);
299
+ view.addEventListener('keydown', handleKeyDown, true);
300
+ }
301
+ catch (error) {
302
+ console.error('[itemReorder] Could not start the press:', error);
303
+ endPress('cancelled');
304
+ }
305
+ }
306
+ function handlePointerMove(event) {
307
+ const current = press;
308
+ if (!current || event.pointerId !== current.pointerId)
309
+ return;
310
+ try {
311
+ current.lastX = event.clientX;
312
+ current.lastY = event.clientY;
313
+ if (!current.dragging) {
314
+ const distance = Math.hypot(event.clientX - current.startX, event.clientY - current.startY);
315
+ if (distance < DRAG_START_DISTANCE_PX)
316
+ return;
317
+ beginDragging(current);
318
+ }
319
+ positionMirror(current);
320
+ updateDropTarget(current);
321
+ }
322
+ catch (error) {
323
+ console.error('[itemReorder] Could not follow the pointer:', error);
324
+ endPress('cancelled');
325
+ }
326
+ }
327
+ function handlePointerUp(event) {
328
+ if (!press || event.pointerId !== press.pointerId)
329
+ return;
330
+ endPress('dropped');
331
+ }
332
+ function handlePointerCancel(event) {
333
+ if (!press || event.pointerId !== press.pointerId)
334
+ return;
335
+ endPress('cancelled');
336
+ }
337
+ function handleKeyDown(event) {
338
+ if (event.key !== 'Escape' || !press)
339
+ return;
340
+ event.preventDefault();
341
+ endPress('cancelled');
342
+ }
343
+ list.addEventListener('pointerdown', handlePointerDown);
344
+ return {
345
+ /** Svelte calls this when the options change; the callbacks of the new options are the ones used from then on. */
346
+ update(nextOptions) {
347
+ options = nextOptions;
348
+ },
349
+ destroy() {
350
+ list.removeEventListener('pointerdown', handlePointerDown);
351
+ endPress('cancelled');
352
+ }
353
+ };
354
+ }
@@ -1,9 +1,11 @@
1
+ import { readEditorStorageValue, writeEditorStorageValue } from './editorStorage';
2
+ // Stored in the editor's own corner of localStorage (see editorStorage.ts), not under a key the customer's page may use.
1
3
  const STORAGE_KEY = 'recentColors';
2
4
  const MAX_COLORS = 12;
3
5
  export const recentColorsUtil = {
4
6
  getRecentColors() {
5
7
  try {
6
- const stored = localStorage.getItem(STORAGE_KEY);
8
+ const stored = readEditorStorageValue(STORAGE_KEY);
7
9
  return stored ? JSON.parse(stored) : [];
8
10
  }
9
11
  catch (error) {
@@ -51,7 +53,7 @@ export const recentColorsUtil = {
51
53
  // Trim to max size
52
54
  colors = colors.slice(0, MAX_COLORS);
53
55
  // Save to localStorage
54
- localStorage.setItem(STORAGE_KEY, JSON.stringify(colors));
56
+ writeEditorStorageValue(STORAGE_KEY, JSON.stringify(colors));
55
57
  // console.log('Final colors list:', colors);
56
58
  return colors;
57
59
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The compact preview of a rich text (what the text area control shows before it is opened): the text with its bold,
3
+ * italic, underline, colours and so on, in a few lines.
4
+ *
5
+ * The rich text is HTML that somebody typed or pasted, so it is not trusted. The preview used to be made by copying
6
+ * the HTML it was given, tag by tag, into a string for `{@html}`: a text that said `&lt;img src=x onerror=...&gt;` came
7
+ * out as a real `<img>` that ran its script in the page the editor is on. It is now made from a description of the text
8
+ * (the `PreviewNode`s below), and everything that goes into the string is either escaped or comes from a short list of
9
+ * what is allowed: some tags, a few style properties with plain values, one class per marker.
10
+ *
11
+ * html text --parseRichText (the browser's own parser, which runs nothing)--> PreviewNode[]
12
+ * --renderPreviewNodes (plain string work, no browser needed)--> HTML that is safe for {@html}
13
+ */
14
+ /** A piece of rich text: a plain text, or an element with the few things the preview keeps of it. */
15
+ export type PreviewNode = string | PreviewElement;
16
+ export interface PreviewElement {
17
+ /** The tag name in lower case, as the HTML parser reports it. */
18
+ tag: string;
19
+ /** The element's style, as the browser reads it: `[property, value]`, property in lower case (`font-size`). */
20
+ declarations: Array<[property: string, value: string]>;
21
+ /** The element's `data-text-style` attribute, or null when it has none. */
22
+ textStyle: string | null;
23
+ children: PreviewNode[];
24
+ }
25
+ /** Whether one style declaration may go into the preview. */
26
+ export declare function isAllowedDeclaration(property: string, value: string): boolean;
27
+ /** HTML for `{@html}` from the description of a rich text. Everything in it is escaped or on the lists above. */
28
+ export declare function renderPreviewNodes(nodes: PreviewNode[]): string;
29
+ /**
30
+ * Reads rich text with the browser's HTML parser. A document made by `DOMParser` has no page behind it: scripts do
31
+ * not run, pictures are not fetched, event handlers never fire.
32
+ */
33
+ export declare function parseRichText(html: string): PreviewNode[];
34
+ /**
35
+ * The preview of a rich text, as HTML that is safe for `{@html}`. A text that arrives written out as entities
36
+ * (`&lt;strong&gt;...`) is read as the markup it spells, as it always has been.
37
+ */
38
+ export declare function createPreviewHtml(text: string): string;