js-kompo-utils 1.0.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 (67) hide show
  1. package/README.md +1 -0
  2. package/app.js +22 -0
  3. package/components/VlApexChart.vue +89 -0
  4. package/components/VlCKEditorTemplate.vue +52 -0
  5. package/components/VlCKEditorWithVars.vue +80 -0
  6. package/components/VlCollapsible.vue +91 -0
  7. package/components/VlCopyBox.vue +61 -0
  8. package/components/VlCreditCardInput.vue +104 -0
  9. package/components/VlDateMonthDay.vue +327 -0
  10. package/components/VlDateTextInput.vue +151 -0
  11. package/components/VlInternationalPhoneInput.vue +902 -0
  12. package/components/VlPartialDate.vue +283 -0
  13. package/components/VlPasswordInput.vue +358 -0
  14. package/components/VlQrCodeScanner.vue +91 -0
  15. package/components/VlSwipeableTab.vue +35 -0
  16. package/components/VlSwipeableTabs.vue +325 -0
  17. package/components/VlText.vue +72 -0
  18. package/components/VlTutorialStepBuilder.vue +671 -0
  19. package/components/VlValidatedInput.vue +262 -0
  20. package/components/data/countries.js +253 -0
  21. package/components/getAllDefaultComponents.js +12 -0
  22. package/docs/architecture.md +71 -0
  23. package/docs/tutorials.md +247 -0
  24. package/docs/usage.md +128 -0
  25. package/functions/index.js +7 -0
  26. package/functions/inputs.js +64 -0
  27. package/functions/loading.js +20 -0
  28. package/kompo-ckeditor-1.0.17/index.js +5 -0
  29. package/kompo-ckeditor-1.0.17/komponents/CKEditor.vue +36 -0
  30. package/kompo-ckeditor-1.0.17/komponents/CKEditorExtended.vue +66 -0
  31. package/kompo-ckeditor-1.0.17/komponents/EditableCKEditor.vue +42 -0
  32. package/kompo-ckeditor-1.0.17/komponents/TranslatableEditor.vue +28 -0
  33. package/kompo-ckeditor-1.0.17/mixins/CKEditor.js +291 -0
  34. package/kompo-ckeditor-1.0.17/mixins/CKEditorUtilities.js +52 -0
  35. package/kompo-ckeditor-1.0.17/mixins/CkUploadAdapter.js +95 -0
  36. package/kompo-ckeditor-1.0.17/mixins/translations/en.js +1 -0
  37. package/kompo-ckeditor-1.0.17/mixins/translations/fr.js +1 -0
  38. package/kompo-ckeditor-1.0.17/plugins/JsonBlockCommand.js +100 -0
  39. package/kompo-ckeditor-1.0.17/plugins/JsonBlockEditing.js +225 -0
  40. package/kompo-ckeditor-1.0.17/plugins/JsonBlockUi.js +21 -0
  41. package/kompo-ckeditor-1.0.17/plugins/MentionPlugin.js +118 -0
  42. package/orchestrator/KompoJsBuilder.js +333 -0
  43. package/package.json +25 -0
  44. package/raw_animations/count-up-animation.js +220 -0
  45. package/raw_animations/magic-poof.js +210 -0
  46. package/tutorials/TutorialConfig.js +34 -0
  47. package/tutorials/TutorialEngine.js +448 -0
  48. package/tutorials/index.js +2 -0
  49. package/tutorials/modules/AutoNext.js +86 -0
  50. package/tutorials/modules/Avatar.js +94 -0
  51. package/tutorials/modules/Bubble.js +617 -0
  52. package/tutorials/modules/Cursor.js +315 -0
  53. package/tutorials/modules/Highlight.js +282 -0
  54. package/tutorials/modules/HoverForcer.js +250 -0
  55. package/tutorials/modules/Mobile.js +148 -0
  56. package/tutorials/modules/Options.js +98 -0
  57. package/tutorials/modules/Overlay.js +142 -0
  58. package/tutorials/modules/Scroll.js +37 -0
  59. package/tutorials/modules/Typewriter.js +132 -0
  60. package/tutorials/step-builder/step-builder-bridge.js +436 -0
  61. package/tutorials/step-builder/utils/bezier-editor.js +379 -0
  62. package/tutorials/step-builder/utils/code-generator.js +127 -0
  63. package/tutorials/step-builder/utils/element-picker.js +125 -0
  64. package/tutorials/tutorial.scss +816 -0
  65. package/tutorials/utils/selectors.js +225 -0
  66. package/tutorials/utils/stability.js +76 -0
  67. package/tutorials/utils/svg.js +46 -0
@@ -0,0 +1,617 @@
1
+ import { computePosition, flip, shift, offset, arrow as arrowMiddleware, autoUpdate } from '@floating-ui/dom';
2
+
3
+ const BUBBLE_PADDING = 'clamp(1rem, 3vw, 2rem) clamp(1.2rem, 4vw, 2.5rem)';
4
+
5
+ export class Bubble {
6
+ constructor(engine) {
7
+ this.engine = engine;
8
+ this.el = null;
9
+ this.arrowEl = null;
10
+ this.textEl = null;
11
+ this.optionsRow = null;
12
+ this.btnRow = null;
13
+ this.actionBtn = null;
14
+ this.backBtn = null;
15
+ this.pauseBtn = null;
16
+ this._cleanupAutoUpdate = null;
17
+ }
18
+
19
+ create(config) {
20
+ if (this.el) this.destroy();
21
+
22
+ this.el = document.createElement('div');
23
+ this.el.id = 'tutorial-bubble';
24
+ this.el.className = 'tutorial-bubble';
25
+ this.el.setAttribute('role', 'status');
26
+ this.el.setAttribute('aria-live', 'polite');
27
+ this.el.style.maxWidth = config.bubbleMaxWidth;
28
+ this.el.style.minWidth = config.bubbleMinWidth;
29
+ this.el.style.fontSize = config.bubbleFontSize;
30
+ this.el.style.lineHeight = config.bubbleLineHeight;
31
+
32
+ this.arrowEl = document.createElement('div');
33
+ this.arrowEl.className = 'tutorial-arrow';
34
+ this.el.appendChild(this.arrowEl);
35
+
36
+ this.textEl = document.createElement('div');
37
+ this.textEl.id = 'tutorial-text';
38
+ this.textEl.className = 'tutorial-text';
39
+ this.el.appendChild(this.textEl);
40
+
41
+ this.optionsRow = document.createElement('div');
42
+ this.optionsRow.id = 'tutorial-options';
43
+ this.optionsRow.className = 'tutorial-options';
44
+ this.el.appendChild(this.optionsRow);
45
+
46
+ this.btnRow = document.createElement('div');
47
+ this.btnRow.className = 'tutorial-btn-row';
48
+
49
+ this.actionBtn = document.createElement('button');
50
+ this.actionBtn.className = 'tutorial-action-btn';
51
+ this.actionBtn.setAttribute('aria-label', 'Next step');
52
+
53
+ this.pauseBtn = document.createElement('button');
54
+ this.pauseBtn.textContent = '\u275A\u275A';
55
+ this.pauseBtn.className = 'tutorial-pause-btn';
56
+ this.pauseBtn.setAttribute('aria-label', 'Pause');
57
+
58
+ this.backBtn = document.createElement('button');
59
+ this.backBtn.textContent = '\u2190';
60
+ this.backBtn.className = 'tutorial-back-btn';
61
+ this.backBtn.setAttribute('aria-label', 'Previous step');
62
+
63
+ this.btnRow.appendChild(this.backBtn);
64
+ this.btnRow.appendChild(this.pauseBtn);
65
+ this.btnRow.appendChild(this.actionBtn);
66
+ this.el.appendChild(this.btnRow);
67
+
68
+ return {
69
+ el: this.el,
70
+ arrowEl: this.arrowEl,
71
+ textEl: this.textEl,
72
+ optionsRow: this.optionsRow,
73
+ btnRow: this.btnRow,
74
+ actionBtn: this.actionBtn,
75
+ backBtn: this.backBtn,
76
+ pauseBtn: this.pauseBtn,
77
+ };
78
+ }
79
+
80
+ getElement() { return this.el; }
81
+ getTextElement() { return this.textEl; }
82
+ getOptionsContainer() { return this.optionsRow; }
83
+
84
+ async positionForStep(step, refs, highlightBottom = false) {
85
+ const { overlay, container, img, imgWrapper, videoEl, isMobile, config } = refs;
86
+
87
+ this._stopAutoUpdate();
88
+
89
+ if (isMobile) {
90
+ this._positionMobile(step, highlightBottom, { overlay, container, img, imgWrapper, videoEl });
91
+ return;
92
+ }
93
+
94
+ this._resetDesktopOverrides({ container, img, imgWrapper, videoEl, config });
95
+
96
+ if (step.chatMode) {
97
+ this._positionDesktopChat(step, { overlay, container, config });
98
+ return;
99
+ }
100
+
101
+ await this._positionDesktopStandard(step, { overlay, container, img, imgWrapper, videoEl, config });
102
+ }
103
+
104
+ setChatMode(enabled, refs) {
105
+ if (!this.el) return;
106
+
107
+ let chatAvatarEl = this.el.querySelector('.tutorial-chat-avatar');
108
+
109
+ if (enabled) {
110
+ if (refs.img) refs.img.style.display = 'none';
111
+ if (refs.videoEl) refs.videoEl.style.display = 'none';
112
+ if (this.arrowEl) this.arrowEl.style.display = 'none';
113
+ if (this.textEl) this.textEl.style.minHeight = '0';
114
+
115
+ if (!chatAvatarEl && refs.img?.src) {
116
+ chatAvatarEl = document.createElement('div');
117
+ chatAvatarEl.className = 'tutorial-chat-avatar';
118
+ const chatImg = document.createElement('img');
119
+ chatImg.src = refs.img.src;
120
+ chatAvatarEl.appendChild(chatImg);
121
+ this.el.insertBefore(chatAvatarEl, this.arrowEl);
122
+ }
123
+ if (chatAvatarEl) chatAvatarEl.style.display = 'block';
124
+
125
+ this.el.className = 'tutorial-bubble tutorial-bubble--chat';
126
+ if (chatAvatarEl) {
127
+ chatAvatarEl.style.gridRow = '1 / -1';
128
+ chatAvatarEl.style.gridColumn = '1';
129
+ }
130
+ if (this.textEl) this.textEl.style.gridColumn = chatAvatarEl ? '2' : '';
131
+ if (this.optionsRow) this.optionsRow.style.gridColumn = chatAvatarEl ? '2' : '';
132
+ if (this.btnRow) this.btnRow.style.gridColumn = chatAvatarEl ? '2' : '';
133
+ if (this.arrowEl) this.arrowEl.style.gridColumn = '1 / -1';
134
+ } else {
135
+ if (refs.img) refs.img.style.display = 'block';
136
+ if (chatAvatarEl) chatAvatarEl.style.display = 'none';
137
+ this.el.className = 'tutorial-bubble';
138
+ if (this.textEl) this.textEl.style.gridColumn = '';
139
+ if (this.optionsRow) this.optionsRow.style.gridColumn = '';
140
+ if (this.btnRow) this.btnRow.style.gridColumn = '';
141
+ if (this.arrowEl) this.arrowEl.style.gridColumn = '';
142
+ }
143
+ }
144
+
145
+ clearStepContent() {
146
+ if (!this.el) return;
147
+ if (this.textEl) this.textEl.innerHTML = '';
148
+ if (this.optionsRow) {
149
+ this.optionsRow.innerHTML = '';
150
+ this.optionsRow.style.display = 'none';
151
+ }
152
+ if (this.btnRow) {
153
+ this.btnRow.style.opacity = '0';
154
+ this.btnRow.style.pointerEvents = 'none';
155
+ }
156
+ }
157
+
158
+ configureButtons(opts) {
159
+ if (this.actionBtn) {
160
+ this.actionBtn.textContent = opts.isLast ? opts.doneLabel : opts.nextLabel;
161
+ this.actionBtn.style.display = opts.hasOptions ? 'none' : '';
162
+ }
163
+ if (this.backBtn) {
164
+ this.backBtn.style.display =
165
+ (opts.showBack === true || typeof opts.showBack === 'number') ? '' : 'none';
166
+ }
167
+ }
168
+
169
+ revealButtons() {
170
+ if (!this.btnRow) return;
171
+ this.btnRow.style.opacity = '1';
172
+ this.btnRow.style.pointerEvents = 'auto';
173
+ }
174
+
175
+ show() {
176
+ if (!this.el) return;
177
+ this.el.style.opacity = '1';
178
+ // Preserve translate(-50%,-50%) if centered, just remove scale
179
+ const current = this.el.style.transform;
180
+ if (current.includes('translate')) {
181
+ this.el.style.transform = current.replace(/scale\([^)]*\)/, '').trim() || 'none';
182
+ } else {
183
+ this.el.style.transform = 'scale(1)';
184
+ }
185
+ }
186
+
187
+ hide() {
188
+ if (!this.el) return Promise.resolve();
189
+ this._stopAutoUpdate();
190
+ this.el.style.opacity = '0';
191
+ this.el.style.transform = 'scale(0.8)';
192
+ return new Promise(resolve => setTimeout(resolve, 300));
193
+ }
194
+
195
+ setContent(html) {
196
+ if (this.textEl) {
197
+ this.textEl.innerHTML = html;
198
+ }
199
+ }
200
+
201
+ destroy() {
202
+ this._stopAutoUpdate();
203
+ this.el?.remove();
204
+ this.el = null;
205
+ this.arrowEl = null;
206
+ this.textEl = null;
207
+ this.optionsRow = null;
208
+ this.btnRow = null;
209
+ this.actionBtn = null;
210
+ this.backBtn = null;
211
+ this.pauseBtn = null;
212
+ }
213
+
214
+ // --- Private: mobile positioning (CSS flexbox, no @floating-ui) ---
215
+
216
+ _positionMobile(step, highlightBottom, refs) {
217
+ const { overlay, container, img, imgWrapper, videoEl } = refs;
218
+
219
+ if (container) {
220
+ container.style.alignItems = 'center';
221
+ container.style.flexDirection = 'row';
222
+ }
223
+
224
+ if (imgWrapper) {
225
+ Object.assign(imgWrapper.style, {
226
+ display: 'flex',
227
+ flexDirection: 'column',
228
+ alignItems: 'center',
229
+ width: '100%',
230
+ });
231
+ }
232
+
233
+ if (img) {
234
+ Object.assign(img.style, {
235
+ height: 'clamp(120px, 30vh, 240px)',
236
+ maxHeight: '30vh',
237
+ alignSelf: 'center',
238
+ });
239
+ }
240
+ if (videoEl) {
241
+ videoEl.style.height = 'clamp(120px, 30vh, 240px)';
242
+ videoEl.style.maxHeight = '30vh';
243
+ }
244
+
245
+ if (!this.el) return;
246
+
247
+ this.el.className = 'tutorial-bubble tutorial-bubble--mobile';
248
+ if (this.textEl) this.textEl.style.minHeight = '80px';
249
+ if (this.arrowEl) this.arrowEl.className = 'tutorial-arrow tutorial-arrow--hidden';
250
+
251
+ if (overlay) {
252
+ overlay.style.justifyContent = 'center';
253
+ overlay.style.padding = '8px';
254
+ }
255
+
256
+ if (highlightBottom) {
257
+ if (overlay) {
258
+ overlay.style.alignItems = 'flex-start';
259
+ overlay.style.paddingTop = 'max(8px, env(safe-area-inset-top))';
260
+ overlay.style.paddingBottom = '8px';
261
+ }
262
+ if (this.el) {
263
+ this.el.style.order = '2';
264
+ this.el.style.marginTop = '8px';
265
+ this.el.style.marginBottom = '0';
266
+ this.el.style.transformOrigin = 'top center';
267
+ }
268
+
269
+ if (img) { img.style.transform = 'scaleY(-1)'; img.style.order = '1'; }
270
+ if (videoEl) { videoEl.style.transform = 'scaleY(-1)'; videoEl.style.order = '1'; }
271
+ } else {
272
+ if (overlay) {
273
+ overlay.style.alignItems = 'flex-end';
274
+ overlay.style.paddingTop = '8px';
275
+ overlay.style.paddingBottom = 'max(8px, env(safe-area-inset-bottom))';
276
+ }
277
+ if (this.el) {
278
+ this.el.style.order = '1';
279
+ this.el.style.marginBottom = '8px';
280
+ this.el.style.marginTop = '0';
281
+ this.el.style.transformOrigin = 'bottom center';
282
+ }
283
+
284
+ if (img) { img.style.transform = 'none'; img.style.order = '3'; }
285
+ if (videoEl) { videoEl.style.transform = 'none'; videoEl.style.order = '3'; }
286
+ }
287
+ }
288
+
289
+ // --- Private: desktop resets ---
290
+
291
+ _resetDesktopOverrides(refs) {
292
+ const { container, img, imgWrapper, videoEl, config } = refs;
293
+
294
+ // Reset container flex from previous step's align/chat positioning
295
+ if (container) {
296
+ container.style.justifyContent = '';
297
+ container.style.alignItems = '';
298
+ container.style.flexDirection = '';
299
+ container.style.padding = '';
300
+ container.style.overflow = '';
301
+ container.style.position = '';
302
+ }
303
+
304
+ if (imgWrapper) {
305
+ Object.assign(imgWrapper.style, {
306
+ display: '',
307
+ flexDirection: '',
308
+ alignItems: '',
309
+ width: '',
310
+ });
311
+ }
312
+
313
+ if (img) {
314
+ img.style.transform = 'none';
315
+ img.style.order = '';
316
+ img.style.alignSelf = '';
317
+ img.style.height = config.avatarHeight || '';
318
+ img.style.maxHeight = config.avatarMaxHeight || '';
319
+ }
320
+
321
+ if (this.el) {
322
+ this.el.style.order = '';
323
+ this.el.style.width = '';
324
+ this.el.style.alignSelf = '';
325
+ }
326
+
327
+ if (videoEl) {
328
+ videoEl.style.transform = 'none';
329
+ videoEl.style.order = '';
330
+ videoEl.style.height = config.avatarHeight || '';
331
+ videoEl.style.maxHeight = config.avatarMaxHeight || '';
332
+ }
333
+ }
334
+
335
+ // --- Private: desktop chat positioning (relative, no @floating-ui) ---
336
+
337
+ _positionDesktopChat(step, refs) {
338
+ const { overlay, container, config } = refs;
339
+ const side = step.position || 'left';
340
+ const align = step.align || 'center';
341
+
342
+ this.positionChat({
343
+ chatMaxWidth: step.chatMaxWidth,
344
+ positionTarget: step.positionTarget,
345
+ side,
346
+ align,
347
+ config,
348
+ refs: { overlay, container },
349
+ });
350
+
351
+ if (step.positionTarget) {
352
+ this._startChatAutoUpdate(container, step.positionTarget, side);
353
+ }
354
+ }
355
+
356
+ positionChat(options = {}) {
357
+ if (!this.el) return;
358
+
359
+ const config = options.config || {};
360
+ const overlay = options.refs?.overlay;
361
+ const container = options.refs?.container;
362
+ const side = options.side;
363
+ const align = options.align;
364
+
365
+ this.el.className = 'tutorial-bubble tutorial-bubble--chat';
366
+ // Reset standard mode positioning — chat uses its own layout
367
+ this.el.style.position = 'fixed';
368
+ this.el.style.left = '50%';
369
+ this.el.style.top = '50%';
370
+ this.el.style.transform = 'translate(-50%, -50%)';
371
+ this.el.style.right = 'auto';
372
+ this.el.style.bottom = 'auto';
373
+ this.el.style.marginRight = '0';
374
+ this.el.style.marginLeft = '0';
375
+ this.el.style.padding = BUBBLE_PADDING;
376
+ this.el.style.fontSize = config.bubbleFontSize || '';
377
+ this.el.style.maxWidth = options.chatMaxWidth || config.bubbleMaxWidth || 'clamp(260px, 85vw, 550px)';
378
+ this.el.style.minWidth = config.bubbleMinWidth || 'clamp(200px, 70vw, 350px)';
379
+
380
+ if (this.textEl) this.textEl.style.minHeight = '0';
381
+ if (this.arrowEl) this.arrowEl.className = 'tutorial-arrow tutorial-arrow--hidden';
382
+
383
+ if (container) {
384
+ container.style.alignItems = 'stretch';
385
+ container.style.overflow = '';
386
+ }
387
+
388
+ if (overlay) {
389
+ overlay.style.padding = '0';
390
+ }
391
+
392
+ if (options.positionTarget) {
393
+ if (container) {
394
+ this._positionAtTarget(container, options.positionTarget, side);
395
+
396
+ if (options.chatMaxWidth) {
397
+ this.el.style.maxWidth = options.chatMaxWidth;
398
+ this.el.style.minWidth = 'auto';
399
+ }
400
+
401
+ this.el.style.overflowWrap = 'break-word';
402
+ this.el.style.wordBreak = 'break-word';
403
+ this.el.style.boxSizing = 'border-box';
404
+
405
+ if (overlay) {
406
+ overlay.style.justifyContent = '';
407
+ overlay.style.alignItems = '';
408
+ }
409
+
410
+ container.style.maxWidth = '';
411
+ }
412
+ } else {
413
+ if (container) {
414
+ this._resetContainerPosition(container);
415
+
416
+ const vMap = { top: 'flex-start', bottom: 'flex-end' };
417
+ const hMap = { left: 'flex-start', center: 'center', right: 'flex-end' };
418
+
419
+ container.style.alignItems = vMap[side] || 'center';
420
+ container.style.justifyContent = hMap[align] || 'center';
421
+ container.style.padding = '16px';
422
+ }
423
+ }
424
+ }
425
+
426
+ // --- Private: standard desktop positioning via @floating-ui ---
427
+
428
+ async _positionDesktopStandard(step, refs) {
429
+ const { overlay, container, img, videoEl, config } = refs;
430
+ const side = step.position || 'left';
431
+ const align = step.align || 'center';
432
+
433
+ if (!this.el) return;
434
+
435
+ // Move avatar to the align position (left/center/right of screen)
436
+ if (container) {
437
+ const alignMap = { left: 'flex-start', center: 'center', right: 'flex-end' };
438
+ container.style.justifyContent = alignMap[align] || 'center';
439
+ }
440
+
441
+ // Apply per-step avatar size override
442
+ if (step.avatarSize && img) {
443
+ img.style.height = step.avatarSize;
444
+ img.style.maxHeight = step.avatarSize;
445
+ }
446
+
447
+ // Custom position mode: manual left/top values, no floating-ui
448
+ if (side === 'custom') {
449
+ this._stopAutoUpdate();
450
+ this.el.style.position = 'fixed';
451
+ this.el.style.left = step.bubbleLeft || '50%';
452
+ this.el.style.top = step.bubbleTop || '50%';
453
+ this.el.style.transform = (!step.bubbleLeft && !step.bubbleTop) ? 'translate(-50%, -50%)' : '';
454
+ this.el.style.right = 'auto';
455
+ this.el.style.bottom = 'auto';
456
+ this.el.style.padding = BUBBLE_PADDING;
457
+ this.el.style.fontSize = config.bubbleFontSize;
458
+ this.el.style.maxWidth = config.bubbleMaxWidth;
459
+ this.el.style.minWidth = config.bubbleMinWidth;
460
+ if (this.arrowEl) this.arrowEl.style.display = 'none';
461
+ return;
462
+ }
463
+
464
+ const referenceEl = step.positionTarget
465
+ ? document.querySelector(step.positionTarget)
466
+ : (videoEl && videoEl.style.display !== 'none' ? videoEl : img);
467
+
468
+ // No reference element — center the bubble on screen
469
+ if (!referenceEl) {
470
+ this.el.style.position = 'fixed';
471
+ this.el.style.left = '50%';
472
+ this.el.style.top = '50%';
473
+ this.el.style.transform = 'translate(-50%, -50%)';
474
+ this.el.style.padding = BUBBLE_PADDING;
475
+ this.el.style.fontSize = config.bubbleFontSize;
476
+ this.el.style.maxWidth = config.bubbleMaxWidth;
477
+ this.el.style.minWidth = config.bubbleMinWidth;
478
+ if (this.arrowEl) this.arrowEl.style.display = 'none';
479
+ return;
480
+ }
481
+
482
+ // Bubble is fixed on body — floating-ui viewport coordinates map directly
483
+ this.el.style.position = 'fixed';
484
+ this.el.style.left = 'auto';
485
+ this.el.style.top = 'auto';
486
+ this.el.style.transform = '';
487
+ this.el.style.padding = BUBBLE_PADDING;
488
+ this.el.style.fontSize = config.bubbleFontSize;
489
+ this.el.style.maxWidth = config.bubbleMaxWidth;
490
+ this.el.style.minWidth = config.bubbleMinWidth;
491
+
492
+ if (this.textEl) this.textEl.style.minHeight = '0';
493
+ if (this.arrowEl) this.arrowEl.style.display = '';
494
+
495
+ const runPosition = async () => {
496
+ if (!this.el) return;
497
+
498
+ const { x, y, placement, middlewareData } = await computePosition(
499
+ referenceEl, this.el, {
500
+ placement: side,
501
+ middleware: [
502
+ offset(15),
503
+ flip(),
504
+ shift({ padding: 8 }),
505
+ arrowMiddleware({ element: this.arrowEl, padding: 14 }),
506
+ ],
507
+ }
508
+ );
509
+
510
+ Object.assign(this.el.style, {
511
+ left: `${x}px`,
512
+ top: `${y}px`,
513
+ right: 'auto',
514
+ bottom: 'auto',
515
+ marginLeft: '0',
516
+ marginRight: '0',
517
+ });
518
+
519
+ const basePlacement = placement.split('-')[0];
520
+ if (this.arrowEl) {
521
+ this.arrowEl.className = `tutorial-arrow tutorial-arrow--${basePlacement}`;
522
+
523
+ // Reset inline arrow position before applying middleware values
524
+ this.arrowEl.style.left = '';
525
+ this.arrowEl.style.top = '';
526
+ this.arrowEl.style.right = '';
527
+ this.arrowEl.style.bottom = '';
528
+
529
+ if (middlewareData.arrow) {
530
+ const { x: ax, y: ay } = middlewareData.arrow;
531
+ if (ax != null) this.arrowEl.style.left = `${ax}px`;
532
+ if (ay != null) this.arrowEl.style.top = `${ay}px`;
533
+ }
534
+ }
535
+
536
+ // Set transform-origin based on resolved placement
537
+ const originMap = {
538
+ left: 'bottom right',
539
+ right: 'bottom left',
540
+ top: 'bottom center',
541
+ bottom: 'top center',
542
+ };
543
+ this.el.style.transformOrigin = originMap[basePlacement] || 'bottom right';
544
+ };
545
+
546
+ await runPosition();
547
+
548
+ this._stopAutoUpdate();
549
+ this._cleanupAutoUpdate = autoUpdate(referenceEl, this.el, runPosition);
550
+ }
551
+
552
+ // --- Private: position container at target via @floating-ui ---
553
+
554
+ async _positionAtTarget(container, targetSelector, side) {
555
+ if (!container) return;
556
+ const target = document.querySelector(targetSelector);
557
+ if (!target) {
558
+ this._resetContainerPosition(container);
559
+ return;
560
+ }
561
+
562
+ container.style.position = 'fixed';
563
+
564
+ const { x, y } = await computePosition(target, container, {
565
+ placement: side || 'left',
566
+ middleware: [
567
+ offset(10),
568
+ flip(),
569
+ shift({ padding: 8 }),
570
+ ],
571
+ });
572
+
573
+ Object.assign(container.style, {
574
+ left: `${x}px`,
575
+ top: `${y}px`,
576
+ bottom: 'auto',
577
+ right: 'auto',
578
+ transform: 'none',
579
+ maxHeight: `${window.innerHeight - 16}px`,
580
+ overflow: 'auto',
581
+ });
582
+ }
583
+
584
+ // --- Private: auto-update for chat mode with positionTarget ---
585
+
586
+ _startChatAutoUpdate(container, targetSelector, side) {
587
+ if (!container) return;
588
+ const target = document.querySelector(targetSelector);
589
+ if (!target) return;
590
+
591
+ this._stopAutoUpdate();
592
+ this._cleanupAutoUpdate = autoUpdate(target, container, () => {
593
+ this._positionAtTarget(container, targetSelector, side);
594
+ });
595
+ }
596
+
597
+ _resetContainerPosition(container) {
598
+ if (!container) return;
599
+ Object.assign(container.style, {
600
+ position: '',
601
+ left: '',
602
+ right: '',
603
+ top: '',
604
+ bottom: '',
605
+ transform: '',
606
+ maxHeight: '',
607
+ overflow: '',
608
+ });
609
+ }
610
+
611
+ _stopAutoUpdate() {
612
+ if (this._cleanupAutoUpdate) {
613
+ this._cleanupAutoUpdate();
614
+ this._cleanupAutoUpdate = null;
615
+ }
616
+ }
617
+ }