@fc3/mmcadi 0.1.48 → 0.1.50

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 (52) hide show
  1. package/dist/.last-compile-time +1 -1
  2. package/dist/.last-publish-time +1 -1
  3. package/dist/client.js +1925 -172
  4. package/dist/src/client/helper/drag.d.ts +29 -0
  5. package/dist/src/client/helper/drag.js +350 -0
  6. package/dist/src/client/helper/drag.js.map +1 -0
  7. package/dist/src/client/helper/interaction.d.ts +47 -0
  8. package/dist/src/client/helper/interaction.js +739 -0
  9. package/dist/src/client/helper/interaction.js.map +1 -0
  10. package/dist/src/client/page/cursor.d.ts +9 -1
  11. package/dist/src/client/page/cursor.js +206 -47
  12. package/dist/src/client/page/cursor.js.map +1 -1
  13. package/dist/src/client/page/history.js.map +1 -1
  14. package/dist/src/client/page/view-custom.js +3 -0
  15. package/dist/src/client/page/view-custom.js.map +1 -1
  16. package/dist/src/client/page.d.ts +1 -1
  17. package/dist/src/client/page.js +24 -13
  18. package/dist/src/client/page.js.map +1 -1
  19. package/dist/src/client/utility/get-index-path-for-element.d.ts +2 -0
  20. package/dist/src/client/utility/get-index-path-for-element.js +12 -0
  21. package/dist/src/client/utility/get-index-path-for-element.js.map +1 -0
  22. package/dist/src/common/utility/role-is-public.d.ts +3 -0
  23. package/dist/src/common/utility/role-is-public.js +13 -0
  24. package/dist/src/common/utility/role-is-public.js.map +1 -0
  25. package/dist/src/enum/action-type.d.ts +1 -0
  26. package/dist/src/enum/action-type.js +1 -0
  27. package/dist/src/enum/action-type.js.map +1 -1
  28. package/dist/src/enum/emoji.d.ts +3 -1
  29. package/dist/src/enum/emoji.js +2 -0
  30. package/dist/src/enum/emoji.js.map +1 -1
  31. package/dist/src/index.d.ts +1 -0
  32. package/dist/src/index.js +3 -1
  33. package/dist/src/index.js.map +1 -1
  34. package/dist/src/server/endpoint/action/create.d.ts +1 -0
  35. package/dist/src/server/endpoint/action/create.js +20 -1
  36. package/dist/src/server/endpoint/action/create.js.map +1 -1
  37. package/dist/src/server/operation/reposition-block.d.ts +15 -0
  38. package/dist/src/server/operation/reposition-block.js +108 -0
  39. package/dist/src/server/operation/reposition-block.js.map +1 -0
  40. package/dist/src/server/serializer/base.d.ts +1 -0
  41. package/dist/src/server/serializer/base.js +46 -2
  42. package/dist/src/server/serializer/base.js.map +1 -1
  43. package/dist/src/server/serializer/block/audio.js +31 -5
  44. package/dist/src/server/serializer/block/audio.js.map +1 -1
  45. package/dist/src/server/serializer/block/video.js +1 -1
  46. package/dist/src/server/serializer/page.js +5 -3
  47. package/dist/src/server/serializer/page.js.map +1 -1
  48. package/dist/src/type/action/reposition-block.d.ts +8 -0
  49. package/dist/src/type/action/reposition-block.js +3 -0
  50. package/dist/src/type/action/reposition-block.js.map +1 -0
  51. package/dist/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +2 -2
@@ -0,0 +1,739 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const errors_1 = require("@fc3/errors");
7
+ const action_type_1 = __importDefault(require("./../../enum/action-type.js"));
8
+ const emoji_1 = __importDefault(require("./../../enum/emoji.js"));
9
+ const get_index_path_for_element_1 = __importDefault(require("./../utility/get-index-path-for-element.js"));
10
+ class InteractionHelper {
11
+ constructor() {
12
+ this.drag_active = false;
13
+ this.drag_origin_block = null;
14
+ this.drag_placeholder = null;
15
+ this.drag_ghost = null;
16
+ this.drag_start_y = null;
17
+ this.drag_pointer_offset_y = null;
18
+ this.drag_press_timer = null;
19
+ this.swipe_active = false;
20
+ this.swipe_block = null;
21
+ this.swipe_start_x = null;
22
+ this.swipe_start_y = null;
23
+ this.swipe_content = null;
24
+ this.swipe_menu_width = 0;
25
+ this.open_swipe_block = null;
26
+ this.open_swipe_content = null;
27
+ }
28
+ attach() {
29
+ document.addEventListener('mousedown', (event) => {
30
+ this.onPressStart(event);
31
+ }, true);
32
+ document.addEventListener('touchstart', (event) => {
33
+ this.onPressStart(event);
34
+ }, { capture: true, passive: false });
35
+ document.addEventListener('mousemove', (event) => {
36
+ this.onPointerMove(event);
37
+ }, true);
38
+ document.addEventListener('touchmove', (event) => {
39
+ this.onPointerMove(event);
40
+ }, { capture: true, passive: false });
41
+ document.addEventListener('mouseup', (event) => {
42
+ this.onPressEnd(event);
43
+ }, true);
44
+ document.addEventListener('touchend', (event) => {
45
+ this.onPressEnd(event);
46
+ }, true);
47
+ // Close any open swipe when touching outside
48
+ document.addEventListener('touchstart', (event) => {
49
+ this.onGlobalTouchStart(event);
50
+ }, { capture: true, passive: true });
51
+ // Prevent native browser drag for anchors/images inside blocks so our
52
+ // custom drag logic receives mousemove events instead of dragover.
53
+ document.addEventListener('dragstart', (event) => {
54
+ this.onNativeDragStart(event);
55
+ }, true);
56
+ }
57
+ isDragging() {
58
+ return this.drag_active;
59
+ }
60
+ getBlockElements() {
61
+ const elements = document.querySelectorAll('section.block');
62
+ return Array.from(elements);
63
+ }
64
+ getSiblingBlocks(container, exclude) {
65
+ const children = Array.from(container.children);
66
+ return children.filter((element) => {
67
+ if (element === exclude) {
68
+ return false;
69
+ }
70
+ return element.classList.contains('block');
71
+ });
72
+ }
73
+ onPressStart(event) {
74
+ const target = event.target;
75
+ if (target === null) {
76
+ return;
77
+ }
78
+ // Allow starting drag from anchors (e.g., Folder blocks wrap content in <a>).
79
+ // Still block on true form controls to avoid interfering with inputs/buttons.
80
+ if (target.closest('button, input, textarea, select, label, form')) {
81
+ return;
82
+ }
83
+ const block = target.closest('section.block');
84
+ if (block === null) {
85
+ return;
86
+ }
87
+ // Avoid native link-drag by preventing default on mousedown for anchors
88
+ if (event instanceof MouseEvent) {
89
+ const anchor = target.closest('a[href]');
90
+ if (anchor) {
91
+ event.preventDefault();
92
+ }
93
+ }
94
+ const touch_event = event;
95
+ const is_touch = touch_event.type === 'touchstart';
96
+ const point = this.getEventPoint(event);
97
+ if (point === null) {
98
+ return;
99
+ }
100
+ this.drag_origin_block = block;
101
+ this.drag_start_y = point.clientY;
102
+ if (is_touch) {
103
+ this.swipe_block = block;
104
+ this.swipe_start_x = point.clientX;
105
+ this.swipe_start_y = point.clientY;
106
+ }
107
+ const threshold_ms = is_touch ? 350 : 200;
108
+ this.clearPressTimer();
109
+ this.drag_press_timer = window.setTimeout(() => {
110
+ this.beginDrag(point.clientY);
111
+ }, threshold_ms);
112
+ }
113
+ onNativeDragStart(event) {
114
+ const target = event.target;
115
+ if (!target) {
116
+ return;
117
+ }
118
+ if (target.closest('section.block')) {
119
+ event.preventDefault();
120
+ }
121
+ }
122
+ onPointerMove(event) {
123
+ const point = this.getEventPoint(event);
124
+ if (point === null) {
125
+ return;
126
+ }
127
+ const touch_event = event;
128
+ const { touches } = touch_event;
129
+ const is_touch = touches !== undefined;
130
+ // Touch-only swipe detection (left swipe reveals actions)
131
+ if (is_touch && this.swipe_block) {
132
+ if (this.swipe_start_x === null || this.swipe_start_y === null) {
133
+ throw new errors_1.InvariantViolation('Swipe start coordinates were not initialized');
134
+ }
135
+ const start_x = this.swipe_start_x;
136
+ const start_y = this.swipe_start_y;
137
+ const delta_x = point.clientX - start_x;
138
+ const delta_y = point.clientY - start_y;
139
+ if (!this.swipe_active) {
140
+ const abs_x = Math.abs(delta_x);
141
+ const abs_y = Math.abs(delta_y);
142
+ // Initiate swipe if horizontal dominates and exceeds threshold
143
+ if (abs_x > 12 && abs_x > abs_y) {
144
+ // Cancel pending drag long-press
145
+ this.clearPressTimer();
146
+ // Close any previously open swipe on another block
147
+ this.closeOpenSwipeMenu(this.swipe_block);
148
+ this.swipe_active = true;
149
+ this.beginSwipe(this.swipe_block);
150
+ }
151
+ }
152
+ if (this.swipe_active) {
153
+ event.preventDefault();
154
+ const content_element = this.swipe_content;
155
+ const menu_width = this.swipe_menu_width;
156
+ // Only allow left swipe (negative delta_x)
157
+ const translate_x = Math.max(-menu_width, Math.min(0, delta_x));
158
+ content_element.style.transition = '';
159
+ content_element.style.transform = `translateX(${translate_x}px)`;
160
+ // Don't fall through to drag while swiping:
161
+ return;
162
+ }
163
+ }
164
+ if (!this.drag_active && this.drag_origin_block && this.drag_start_y !== null) {
165
+ const delta_y = Math.abs(point.clientY - this.drag_start_y);
166
+ if (delta_y > 8 && this.drag_press_timer !== null) {
167
+ this.beginDrag(point.clientY);
168
+ }
169
+ }
170
+ if (!this.drag_active) {
171
+ return;
172
+ }
173
+ event.preventDefault();
174
+ // FLIP: capture positions before DOM change (within current container)
175
+ const origin = this.drag_origin_block;
176
+ let container = null;
177
+ if (this.drag_placeholder !== null) {
178
+ container = this.drag_placeholder.parentElement;
179
+ }
180
+ if (container === null && origin !== null) {
181
+ container = origin.parentElement;
182
+ }
183
+ if (container === null) {
184
+ return;
185
+ }
186
+ const blocks_before = this.getSiblingBlocks(container, origin);
187
+ const preceding_rects = new Map();
188
+ blocks_before.forEach((element) => {
189
+ const rect = element.getBoundingClientRect();
190
+ preceding_rects.set(element, rect);
191
+ });
192
+ // Move placeholder to its new spot
193
+ this.updatePlaceholderPosition(point.clientY);
194
+ // Update ghost to follow pointer (glued to finger/mouse)
195
+ if (this.drag_ghost && this.drag_pointer_offset_y !== null) {
196
+ const top_position = point.clientY - this.drag_pointer_offset_y;
197
+ this.drag_ghost.style.top = `${top_position}px`;
198
+ }
199
+ // Capture last positions and animate
200
+ const blocks_after = this.getSiblingBlocks(container, origin);
201
+ this.animateWithFlip(preceding_rects, blocks_after);
202
+ }
203
+ onPressEnd(event) {
204
+ this.clearPressTimer();
205
+ // Finalize swipe if active
206
+ if (this.swipe_active) {
207
+ const content = this.swipe_content;
208
+ const style = window.getComputedStyle(content);
209
+ let current_x = 0;
210
+ try {
211
+ const css_matrix = new WebKitCSSMatrix(style.transform);
212
+ current_x = css_matrix.m41;
213
+ }
214
+ catch {
215
+ const translate_match = style.transform.match(/translateX\(([-0-9.]+)px\)/);
216
+ if (translate_match) {
217
+ current_x = parseFloat(translate_match[1]);
218
+ }
219
+ }
220
+ const width = this.swipe_menu_width;
221
+ const open = Math.abs(current_x) > width / 2;
222
+ content.style.transition = 'transform 150ms ease';
223
+ content.style.transform = open ? `translateX(${-width}px)` : 'translateX(0)';
224
+ if (!open) {
225
+ this.teardownSwipe();
226
+ }
227
+ if (open) {
228
+ this.open_swipe_block = this.swipe_block;
229
+ this.open_swipe_content = content;
230
+ }
231
+ this.swipe_active = false;
232
+ return;
233
+ }
234
+ if (!this.drag_active) {
235
+ this.resetDragState();
236
+ return;
237
+ }
238
+ event.preventDefault();
239
+ const origin = this.drag_origin_block;
240
+ const placeholder = this.drag_placeholder;
241
+ const source_index_path = (0, get_index_path_for_element_1.default)(origin);
242
+ const target_index_path = this.computeTargetIndexPath(placeholder);
243
+ this.cleanupDragElements();
244
+ if (target_index_path === null || target_index_path === source_index_path) {
245
+ this.resetDragState();
246
+ return;
247
+ }
248
+ const editing = new URL(window.location.href).searchParams.get('editing') === 'true';
249
+ this.postReposition(source_index_path, target_index_path, editing)
250
+ .then((new_index_path) => {
251
+ if (new_index_path) {
252
+ const url = new URL(window.location.href);
253
+ url.searchParams.set('index_path', new_index_path);
254
+ history.replaceState({}, '', url.toString());
255
+ }
256
+ const blocks = this.getBlockElements();
257
+ blocks.forEach((element) => {
258
+ element.classList.remove('selected');
259
+ });
260
+ origin.classList.add('selected');
261
+ // Rewrite data-index-path for all top-level blocks to reflect new order
262
+ this.updateTopLevelIndexPaths();
263
+ })
264
+ .finally(() => {
265
+ this.resetDragState();
266
+ });
267
+ }
268
+ beginSwipe(block) {
269
+ // Ensure wrapper and actions exist
270
+ const result = this.ensureSwipeUI(block);
271
+ const target = result.target;
272
+ const menu = result.menu;
273
+ const menu_width = result.menu_width;
274
+ this.swipe_content = target;
275
+ this.swipe_menu_width = menu_width;
276
+ if (menu) {
277
+ menu.style.display = 'flex';
278
+ menu.style.opacity = '1';
279
+ }
280
+ }
281
+ closeOpenSwipeMenu(except) {
282
+ if (!this.open_swipe_content) {
283
+ return;
284
+ }
285
+ if (except && this.open_swipe_block === except) {
286
+ return;
287
+ }
288
+ const content = this.open_swipe_content;
289
+ content.style.transition = 'transform 150ms ease';
290
+ content.style.transform = 'translateX(0)';
291
+ const clear_callback = () => {
292
+ content.removeEventListener('transitionend', clear_callback);
293
+ if (this.open_swipe_block) {
294
+ const menu_element = this.getSwipeMenuFor(this.open_swipe_block);
295
+ if (menu_element && menu_element.parentElement) {
296
+ menu_element.parentElement.removeChild(menu_element);
297
+ }
298
+ }
299
+ this.open_swipe_block = null;
300
+ this.open_swipe_content = null;
301
+ };
302
+ content.addEventListener('transitionend', clear_callback);
303
+ }
304
+ teardownSwipe() {
305
+ if (!this.swipe_block || !this.swipe_content) {
306
+ this.resetSwipeState();
307
+ return;
308
+ }
309
+ this.swipe_content.style.transition = '';
310
+ this.swipe_content.style.transform = 'translateX(0)';
311
+ const menu = this.getSwipeMenuFor(this.swipe_block);
312
+ if (menu && menu.parentElement) {
313
+ menu.parentElement.removeChild(menu);
314
+ }
315
+ this.resetSwipeState();
316
+ }
317
+ onGlobalTouchStart(event) {
318
+ if (!this.open_swipe_block || !this.open_swipe_content) {
319
+ return;
320
+ }
321
+ const target = event.target;
322
+ if (!target) {
323
+ return;
324
+ }
325
+ // Ignore touches inside the open block or its action menu
326
+ const menu = this.getSwipeMenuFor(this.open_swipe_block);
327
+ const closest_block = target.closest('section.block');
328
+ const swipe_actions = target.closest('.swipe-actions');
329
+ if (closest_block === this.open_swipe_block || swipe_actions === menu) {
330
+ return;
331
+ }
332
+ this.closeOpenSwipeMenu();
333
+ }
334
+ getSwipeMenuFor(block) {
335
+ const wrapper = block.closest('main .section-wrapper');
336
+ if (!wrapper || !block.id) {
337
+ return null;
338
+ }
339
+ return wrapper.querySelector(`.swipe-actions[data-for="${block.id}"]`);
340
+ }
341
+ resetSwipeState() {
342
+ this.swipe_block = null;
343
+ this.swipe_start_x = null;
344
+ this.swipe_start_y = null;
345
+ this.swipe_content = null;
346
+ this.swipe_menu_width = 0;
347
+ }
348
+ ensureSwipeUI(block) {
349
+ const target = block;
350
+ target.style.willChange = 'transform';
351
+ target.style.transform = 'translateX(0)';
352
+ target.style.transition = '';
353
+ target.style.position = target.style.position || 'relative';
354
+ target.style.zIndex = '1';
355
+ const wrapper = block.closest('main .section-wrapper');
356
+ let menu = null;
357
+ if (wrapper) {
358
+ if (getComputedStyle(wrapper).position === 'static') {
359
+ wrapper.style.position = 'relative';
360
+ }
361
+ let existing = wrapper.querySelector(`.swipe-actions[data-for="${block.id}"]`);
362
+ if (!existing) {
363
+ existing = document.createElement('div');
364
+ existing.className = 'swipe-actions';
365
+ existing.setAttribute('data-for', block.id);
366
+ existing.style.position = 'absolute';
367
+ existing.style.right = '0';
368
+ existing.style.display = 'none';
369
+ existing.style.opacity = '0';
370
+ existing.style.gap = '8px';
371
+ existing.style.alignItems = 'stretch';
372
+ existing.style.padding = '0 8px';
373
+ existing.style.background = 'transparent';
374
+ existing.style.zIndex = '0';
375
+ const edit_button = document.createElement('button');
376
+ const delete_button = document.createElement('button');
377
+ const add_button = document.createElement('button');
378
+ edit_button.textContent = emoji_1.default.GEAR;
379
+ edit_button.setAttribute('aria-label', 'Edit');
380
+ edit_button.style.background = '#f0ad4e';
381
+ edit_button.style.color = '#000';
382
+ edit_button.style.border = 'none';
383
+ edit_button.style.padding = '0';
384
+ edit_button.style.fontSize = '20px';
385
+ edit_button.style.width = '48px';
386
+ edit_button.style.minWidth = '48px';
387
+ edit_button.style.display = 'flex';
388
+ edit_button.style.alignItems = 'center';
389
+ edit_button.style.justifyContent = 'center';
390
+ edit_button.style.cursor = 'pointer';
391
+ delete_button.textContent = emoji_1.default.RED_X;
392
+ delete_button.setAttribute('aria-label', 'Delete');
393
+ delete_button.style.background = '#e74c3c';
394
+ delete_button.style.color = '#fff';
395
+ delete_button.style.border = 'none';
396
+ delete_button.style.padding = '0';
397
+ delete_button.style.fontSize = '20px';
398
+ delete_button.style.width = '48px';
399
+ delete_button.style.minWidth = '48px';
400
+ delete_button.style.display = 'flex';
401
+ delete_button.style.alignItems = 'center';
402
+ delete_button.style.justifyContent = 'center';
403
+ delete_button.style.cursor = 'pointer';
404
+ add_button.textContent = emoji_1.default.PLUS_SIGN;
405
+ add_button.setAttribute('aria-label', 'Add After');
406
+ add_button.style.background = '#27ae60';
407
+ add_button.style.color = '#fff';
408
+ add_button.style.border = 'none';
409
+ add_button.style.padding = '0';
410
+ add_button.style.fontSize = '20px';
411
+ add_button.style.width = '48px';
412
+ add_button.style.minWidth = '48px';
413
+ add_button.style.display = 'flex';
414
+ add_button.style.alignItems = 'center';
415
+ add_button.style.justifyContent = 'center';
416
+ add_button.style.cursor = 'pointer';
417
+ existing.appendChild(edit_button);
418
+ existing.appendChild(delete_button);
419
+ existing.appendChild(add_button);
420
+ wrapper.appendChild(existing);
421
+ const edit_handler = (event) => {
422
+ event.preventDefault();
423
+ event.stopPropagation();
424
+ this.navigateToBlockLink(block, 'edit');
425
+ };
426
+ const delete_handler = (event) => {
427
+ event.preventDefault();
428
+ event.stopPropagation();
429
+ this.navigateToBlockLink(block, 'delete');
430
+ };
431
+ const add_handler = (event) => {
432
+ event.preventDefault();
433
+ event.stopPropagation();
434
+ this.navigateToAddAfter(block);
435
+ };
436
+ // Prevent global touchstart from closing when interacting with menu
437
+ existing.addEventListener('touchstart', (event) => {
438
+ event.stopPropagation();
439
+ });
440
+ existing.addEventListener('mousedown', (event) => {
441
+ event.stopPropagation();
442
+ });
443
+ edit_button.addEventListener('click', edit_handler);
444
+ edit_button.addEventListener('touchend', edit_handler);
445
+ delete_button.addEventListener('click', delete_handler);
446
+ delete_button.addEventListener('touchend', delete_handler);
447
+ add_button.addEventListener('click', add_handler);
448
+ add_button.addEventListener('touchend', add_handler);
449
+ }
450
+ menu = existing;
451
+ // align to block
452
+ const top = block.offsetTop;
453
+ const height = block.offsetHeight;
454
+ menu.style.top = `${top}px`;
455
+ menu.style.height = `${height}px`;
456
+ }
457
+ let menu_width = 160;
458
+ if (menu) {
459
+ const prevDisplay = menu.style.display;
460
+ const prevOpacity = menu.style.opacity;
461
+ menu.style.display = 'flex';
462
+ menu.style.opacity = '0';
463
+ menu_width = menu.offsetWidth || 160;
464
+ menu.style.display = prevDisplay;
465
+ menu.style.opacity = prevOpacity;
466
+ }
467
+ this.enforceTimeVisibility(block);
468
+ return {
469
+ target,
470
+ menu,
471
+ menu_width
472
+ };
473
+ }
474
+ navigateToAddAfter(block) {
475
+ const current = (0, get_index_path_for_element_1.default)(block);
476
+ // Compute after index by incrementing the last segment
477
+ const parts = current.split('.').map((part) => {
478
+ return parseInt(part, 10);
479
+ });
480
+ const has_invalid_number = parts.some((value) => {
481
+ return isNaN(value);
482
+ });
483
+ if (parts.length === 0 || has_invalid_number) {
484
+ throw new errors_1.InvariantViolation('Invalid index_path for Add After');
485
+ }
486
+ const last = parts.pop();
487
+ if (last === undefined) {
488
+ throw new errors_1.InvariantViolation('Unable to compute next index for Add After');
489
+ }
490
+ parts.push(last + 1);
491
+ const index_path = parts.join('.');
492
+ const url = new URL(window.location.href);
493
+ const editing = url.searchParams.get('editing') === 'true';
494
+ const path = window.location.pathname;
495
+ const actionUrl = new URL('/actions', window.location.origin);
496
+ actionUrl.searchParams.set('path', path);
497
+ actionUrl.searchParams.set('editing', String(editing));
498
+ actionUrl.searchParams.set('action_type', 'add_block');
499
+ actionUrl.searchParams.set('index_path', index_path);
500
+ window.location.href = actionUrl.toString();
501
+ }
502
+ enforceTimeVisibility(block) {
503
+ const shows_time = block.classList.contains('with-time');
504
+ const time_element = block.querySelector(':scope time');
505
+ if (!time_element) {
506
+ return;
507
+ }
508
+ if (!shows_time) {
509
+ time_element.style.display = 'none';
510
+ }
511
+ else {
512
+ time_element.style.display = '';
513
+ }
514
+ }
515
+ navigateToBlockLink(block, type) {
516
+ const block_id = block.id;
517
+ const link_id = `${block_id}-${type}`;
518
+ const link_element = document.getElementById(link_id);
519
+ if (!link_element) {
520
+ return;
521
+ }
522
+ const href = link_element.getAttribute('href');
523
+ if (href) {
524
+ window.location.href = href;
525
+ }
526
+ }
527
+ beginDrag(current_y) {
528
+ if (this.drag_active) {
529
+ return;
530
+ }
531
+ const block = this.drag_origin_block;
532
+ if (!block) {
533
+ return;
534
+ }
535
+ this.drag_active = true;
536
+ // Reduce text selection and improve UX while dragging
537
+ document.body.style.userSelect = 'none';
538
+ document.body.style.cursor = 'grabbing';
539
+ const rect = block.getBoundingClientRect();
540
+ const ghost = block.cloneNode(true);
541
+ ghost.style.position = 'fixed';
542
+ ghost.style.top = `${rect.top}px`;
543
+ ghost.style.left = `${rect.left}px`;
544
+ ghost.style.width = `${rect.width}px`;
545
+ ghost.style.pointerEvents = 'none';
546
+ ghost.style.opacity = '0.9';
547
+ ghost.style.boxShadow = '0 8px 16px rgba(0,0,0,0.2)';
548
+ ghost.style.zIndex = '9999';
549
+ // No transition on top; keep glued to pointer
550
+ document.body.appendChild(ghost);
551
+ this.drag_ghost = ghost;
552
+ // Record pointer offset within the element so the ghost aligns under finger
553
+ this.drag_pointer_offset_y = current_y - rect.top;
554
+ // Replace the original block's layout with a placeholder in the same spot
555
+ const placeholder = document.createElement('div');
556
+ placeholder.style.height = `${rect.height}px`;
557
+ placeholder.style.margin = getComputedStyle(block).margin;
558
+ placeholder.style.border = '2px dashed #888';
559
+ placeholder.style.borderRadius = '4px';
560
+ placeholder.style.boxSizing = 'border-box';
561
+ if (block.parentElement !== null) {
562
+ block.parentElement.insertBefore(placeholder, block);
563
+ }
564
+ this.drag_placeholder = placeholder;
565
+ // Remove original from layout so we don't keep extra blank space
566
+ block.style.display = 'none';
567
+ this.updatePlaceholderPosition(current_y);
568
+ }
569
+ updatePlaceholderPosition(cursor_y) {
570
+ const placeholder = this.drag_placeholder;
571
+ const origin = this.drag_origin_block;
572
+ if (!placeholder || !origin) {
573
+ return;
574
+ }
575
+ const container = placeholder.parentElement;
576
+ const blocks = this.getSiblingBlocks(container, origin);
577
+ let inserted = false;
578
+ for (let i = 0; i < blocks.length; i++) {
579
+ const element = blocks[i];
580
+ const rect = element.getBoundingClientRect();
581
+ const midpoint = rect.top + rect.height / 2;
582
+ if (cursor_y < midpoint) {
583
+ const parent = element.parentElement;
584
+ if (parent) {
585
+ parent.insertBefore(placeholder, element);
586
+ }
587
+ inserted = true;
588
+ break;
589
+ }
590
+ }
591
+ if (!inserted) {
592
+ const last_element = blocks[blocks.length - 1];
593
+ if (last_element) {
594
+ const parent = last_element.parentElement;
595
+ if (parent) {
596
+ parent.appendChild(placeholder);
597
+ }
598
+ }
599
+ }
600
+ }
601
+ animateWithFlip(preceding_rects, blocks_after) {
602
+ blocks_after.forEach((element) => {
603
+ const first = preceding_rects.get(element);
604
+ if (!first) {
605
+ return;
606
+ }
607
+ const last_rect = element.getBoundingClientRect();
608
+ const delta_y = first.top - last_rect.top;
609
+ if (delta_y === 0) {
610
+ return;
611
+ }
612
+ // Invert
613
+ element.style.transition = '';
614
+ element.style.transform = `translateY(${delta_y}px)`;
615
+ // Play
616
+ void element.getBoundingClientRect();
617
+ element.style.transition = 'transform 120ms ease';
618
+ element.style.transform = 'translateY(0)';
619
+ const cleanup = () => {
620
+ element.style.transition = '';
621
+ element.style.transform = '';
622
+ element.removeEventListener('transitionend', cleanup);
623
+ };
624
+ element.addEventListener('transitionend', cleanup);
625
+ });
626
+ }
627
+ async postReposition(source_index_path, target_index_path, editing) {
628
+ const form = new FormData();
629
+ form.set('path', window.location.pathname);
630
+ form.set('action_type', action_type_1.default.REPOSITION_BLOCK);
631
+ form.set('source_index_path', source_index_path);
632
+ form.set('target_index_path', target_index_path);
633
+ const response = await fetch(`/actions?editing=${editing}`, {
634
+ method: 'POST',
635
+ body: form,
636
+ credentials: 'same-origin',
637
+ redirect: 'follow'
638
+ });
639
+ try {
640
+ const url_object = new URL(response.url, window.location.origin);
641
+ return url_object.searchParams.get('index_path');
642
+ }
643
+ catch (_) {
644
+ return null;
645
+ }
646
+ }
647
+ computeTargetIndexPath(placeholder) {
648
+ if (!placeholder) {
649
+ return null;
650
+ }
651
+ const parent = placeholder.parentElement;
652
+ if (!parent) {
653
+ return null;
654
+ }
655
+ let index = 0;
656
+ const children_elements = Array.from(parent.children);
657
+ for (let i = 0; i < children_elements.length; i++) {
658
+ const child_element = children_elements[i];
659
+ if (child_element === placeholder) {
660
+ break;
661
+ }
662
+ if (child_element.classList.contains('block')) {
663
+ index += 1;
664
+ }
665
+ }
666
+ return index.toString();
667
+ }
668
+ updateTopLevelIndexPaths() {
669
+ // Top-level blocks are rendered inside main > .section-wrapper
670
+ const wrapper = document.querySelector('main .section-wrapper');
671
+ let blocks;
672
+ if (wrapper) {
673
+ const node_list = wrapper.querySelectorAll(':scope > section.block');
674
+ blocks = Array.from(node_list);
675
+ }
676
+ else {
677
+ // Fallback: direct children of main if wrapper is not present
678
+ const node_list = document.querySelectorAll('main > section.block');
679
+ blocks = Array.from(node_list);
680
+ }
681
+ for (let i = 0; i < blocks.length; i++) {
682
+ blocks[i].setAttribute('data-index-path', i.toString());
683
+ }
684
+ }
685
+ cleanupDragElements() {
686
+ if (this.drag_ghost && this.drag_ghost.parentElement) {
687
+ this.drag_ghost.parentElement.removeChild(this.drag_ghost);
688
+ }
689
+ if (this.drag_placeholder && this.drag_placeholder.parentElement) {
690
+ // Move the real element into the placeholder's final position
691
+ if (this.drag_origin_block) {
692
+ this.drag_placeholder.parentElement.insertBefore(this.drag_origin_block, this.drag_placeholder);
693
+ }
694
+ this.drag_placeholder.parentElement.removeChild(this.drag_placeholder);
695
+ }
696
+ if (this.drag_origin_block) {
697
+ this.drag_origin_block.style.display = '';
698
+ }
699
+ this.drag_ghost = null;
700
+ this.drag_placeholder = null;
701
+ // Restore global styles
702
+ document.body.style.userSelect = '';
703
+ document.body.style.cursor = '';
704
+ }
705
+ resetDragState() {
706
+ this.cleanupDragElements();
707
+ this.drag_active = false;
708
+ this.drag_origin_block = null;
709
+ this.drag_start_y = null;
710
+ this.drag_pointer_offset_y = null;
711
+ }
712
+ clearPressTimer() {
713
+ if (this.drag_press_timer !== null) {
714
+ window.clearTimeout(this.drag_press_timer);
715
+ this.drag_press_timer = null;
716
+ }
717
+ }
718
+ getEventPoint(event) {
719
+ const touch_event = event;
720
+ const has_touches = touch_event.touches !== undefined;
721
+ if (has_touches) {
722
+ const primary_touch = touch_event.touches[0] || touch_event.changedTouches[0];
723
+ if (!primary_touch) {
724
+ return null;
725
+ }
726
+ return {
727
+ clientX: primary_touch.clientX,
728
+ clientY: primary_touch.clientY
729
+ };
730
+ }
731
+ const mouse_event = event;
732
+ return {
733
+ clientX: mouse_event.clientX,
734
+ clientY: mouse_event.clientY
735
+ };
736
+ }
737
+ }
738
+ exports.default = InteractionHelper;
739
+ //# sourceMappingURL=interaction.js.map