@mushi-mushi/web 0.5.1 → 0.7.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.
- package/CONTRIBUTING.md +4 -0
- package/README.md +41 -0
- package/SECURITY.md +74 -0
- package/dist/index.cjs +256 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +256 -69
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -44,6 +44,12 @@ declare class MushiWidget {
|
|
|
44
44
|
private screenshotAttached;
|
|
45
45
|
private elementSelected;
|
|
46
46
|
private submitting;
|
|
47
|
+
private triggerVisible;
|
|
48
|
+
private triggerShrunk;
|
|
49
|
+
private triggerHiddenByScroll;
|
|
50
|
+
private attachedLaunchers;
|
|
51
|
+
private smartHideCleanup;
|
|
52
|
+
private smartHideTimer;
|
|
47
53
|
/** Captured at the moment of submit so the success ledger metadata
|
|
48
54
|
* ("REPORT · 14:23:07 JST") doesn't drift while the success step
|
|
49
55
|
* is on screen. */
|
|
@@ -62,11 +68,23 @@ declare class MushiWidget {
|
|
|
62
68
|
}): void;
|
|
63
69
|
close(): void;
|
|
64
70
|
getIsOpen(): boolean;
|
|
71
|
+
showTrigger(): void;
|
|
72
|
+
hideTrigger(): void;
|
|
73
|
+
setTrigger(trigger: NonNullable<MushiWidgetConfig['trigger']>): void;
|
|
74
|
+
attachTo(selectorOrElement: string | Element, options?: MushiWidgetConfig): () => void;
|
|
65
75
|
setScreenshotAttached(attached: boolean): void;
|
|
66
76
|
setElementSelected(selected: boolean): void;
|
|
67
77
|
destroy(): void;
|
|
78
|
+
private syncAttachedLaunchers;
|
|
79
|
+
private syncSmartHide;
|
|
80
|
+
private shouldRenderTrigger;
|
|
81
|
+
private effectiveTrigger;
|
|
82
|
+
private isMobileSmartHidden;
|
|
83
|
+
private detectEnvironment;
|
|
84
|
+
private isRouteHidden;
|
|
68
85
|
private getTheme;
|
|
69
86
|
private render;
|
|
87
|
+
private applyInsetVars;
|
|
70
88
|
private renderStep;
|
|
71
89
|
/**
|
|
72
90
|
* Editorial masthead. Always carries:
|
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,12 @@ declare class MushiWidget {
|
|
|
44
44
|
private screenshotAttached;
|
|
45
45
|
private elementSelected;
|
|
46
46
|
private submitting;
|
|
47
|
+
private triggerVisible;
|
|
48
|
+
private triggerShrunk;
|
|
49
|
+
private triggerHiddenByScroll;
|
|
50
|
+
private attachedLaunchers;
|
|
51
|
+
private smartHideCleanup;
|
|
52
|
+
private smartHideTimer;
|
|
47
53
|
/** Captured at the moment of submit so the success ledger metadata
|
|
48
54
|
* ("REPORT · 14:23:07 JST") doesn't drift while the success step
|
|
49
55
|
* is on screen. */
|
|
@@ -62,11 +68,23 @@ declare class MushiWidget {
|
|
|
62
68
|
}): void;
|
|
63
69
|
close(): void;
|
|
64
70
|
getIsOpen(): boolean;
|
|
71
|
+
showTrigger(): void;
|
|
72
|
+
hideTrigger(): void;
|
|
73
|
+
setTrigger(trigger: NonNullable<MushiWidgetConfig['trigger']>): void;
|
|
74
|
+
attachTo(selectorOrElement: string | Element, options?: MushiWidgetConfig): () => void;
|
|
65
75
|
setScreenshotAttached(attached: boolean): void;
|
|
66
76
|
setElementSelected(selected: boolean): void;
|
|
67
77
|
destroy(): void;
|
|
78
|
+
private syncAttachedLaunchers;
|
|
79
|
+
private syncSmartHide;
|
|
80
|
+
private shouldRenderTrigger;
|
|
81
|
+
private effectiveTrigger;
|
|
82
|
+
private isMobileSmartHidden;
|
|
83
|
+
private detectEnvironment;
|
|
84
|
+
private isRouteHidden;
|
|
68
85
|
private getTheme;
|
|
69
86
|
private render;
|
|
87
|
+
private applyInsetVars;
|
|
70
88
|
private renderStep;
|
|
71
89
|
/**
|
|
72
90
|
* Editorial masthead. Always carries:
|
package/dist/index.js
CHANGED
|
@@ -243,11 +243,6 @@ function getWidgetStyles(theme) {
|
|
|
243
243
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
244
244
|
button { font-family: inherit; }
|
|
245
245
|
|
|
246
|
-
/* \u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
247
|
-
A small "stamp card" \u2014 soft rounded square (4px radius), paper
|
|
248
|
-
background, vermillion bottom edge that reads as the inked face
|
|
249
|
-
of a real \u5370\u9451. A pulsing dot in the top-right hints there's a
|
|
250
|
-
channel here without needing a notification badge. */
|
|
251
246
|
.mushi-trigger {
|
|
252
247
|
position: fixed;
|
|
253
248
|
width: 52px;
|
|
@@ -301,10 +296,53 @@ function getWidgetStyles(theme) {
|
|
|
301
296
|
outline: 2px solid ${vermillion};
|
|
302
297
|
outline-offset: 3px;
|
|
303
298
|
}
|
|
304
|
-
.mushi-trigger.bottom-right {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
299
|
+
.mushi-trigger.bottom-right {
|
|
300
|
+
bottom: var(--mushi-bottom, calc(24px + env(safe-area-inset-bottom, 0px)));
|
|
301
|
+
right: var(--mushi-right, calc(24px + env(safe-area-inset-right, 0px)));
|
|
302
|
+
}
|
|
303
|
+
.mushi-trigger.bottom-left {
|
|
304
|
+
bottom: var(--mushi-bottom, calc(24px + env(safe-area-inset-bottom, 0px)));
|
|
305
|
+
left: var(--mushi-left, calc(24px + env(safe-area-inset-left, 0px)));
|
|
306
|
+
}
|
|
307
|
+
.mushi-trigger.top-right {
|
|
308
|
+
top: var(--mushi-top, calc(24px + env(safe-area-inset-top, 0px)));
|
|
309
|
+
right: var(--mushi-right, calc(24px + env(safe-area-inset-right, 0px)));
|
|
310
|
+
}
|
|
311
|
+
.mushi-trigger.top-left {
|
|
312
|
+
top: var(--mushi-top, calc(24px + env(safe-area-inset-top, 0px)));
|
|
313
|
+
left: var(--mushi-left, calc(24px + env(safe-area-inset-left, 0px)));
|
|
314
|
+
}
|
|
315
|
+
.mushi-trigger.edge-tab {
|
|
316
|
+
width: 32px;
|
|
317
|
+
height: 88px;
|
|
318
|
+
border-radius: 4px 0 0 4px;
|
|
319
|
+
writing-mode: vertical-rl;
|
|
320
|
+
text-orientation: upright;
|
|
321
|
+
font-size: 16px;
|
|
322
|
+
box-shadow:
|
|
323
|
+
0 1px 0 ${rule},
|
|
324
|
+
0 10px 24px -14px rgba(14,13,11,0.45),
|
|
325
|
+
inset -3px 0 0 ${vermillion};
|
|
326
|
+
}
|
|
327
|
+
.mushi-trigger.edge-tab.bottom-right,
|
|
328
|
+
.mushi-trigger.edge-tab.top-right {
|
|
329
|
+
right: var(--mushi-right, 0);
|
|
330
|
+
}
|
|
331
|
+
.mushi-trigger.edge-tab.bottom-left,
|
|
332
|
+
.mushi-trigger.edge-tab.top-left {
|
|
333
|
+
left: var(--mushi-left, 0);
|
|
334
|
+
border-radius: 0 4px 4px 0;
|
|
335
|
+
box-shadow:
|
|
336
|
+
0 1px 0 ${rule},
|
|
337
|
+
0 10px 24px -14px rgba(14,13,11,0.45),
|
|
338
|
+
inset 3px 0 0 ${vermillion};
|
|
339
|
+
}
|
|
340
|
+
.mushi-trigger.shrunk {
|
|
341
|
+
width: 36px;
|
|
342
|
+
height: 36px;
|
|
343
|
+
opacity: 0.82;
|
|
344
|
+
transform: scale(0.92);
|
|
345
|
+
}
|
|
308
346
|
|
|
309
347
|
@keyframes mushi-pulse {
|
|
310
348
|
0% { box-shadow: 0 0 0 0 ${vermillion}; opacity: 1; }
|
|
@@ -312,12 +350,6 @@ function getWidgetStyles(theme) {
|
|
|
312
350
|
100% { box-shadow: 0 0 0 0 rgba(224,60,44,0); opacity: 1; }
|
|
313
351
|
}
|
|
314
352
|
|
|
315
|
-
/* \u2500\u2500 Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
316
|
-
Paper-card. Sharper corners (6px) than typical SaaS modals
|
|
317
|
-
(which default to 12-16px and read as plastic). Two-layer shadow:
|
|
318
|
-
one hairline that sells the paper edge, one diffuse that lifts
|
|
319
|
-
the panel off the underlying app. No backdrop-filter \u2014 we want
|
|
320
|
-
the widget to feel like it sits ON the page, not blur INTO it. */
|
|
321
353
|
.mushi-panel {
|
|
322
354
|
position: fixed;
|
|
323
355
|
width: 384px;
|
|
@@ -337,10 +369,26 @@ function getWidgetStyles(theme) {
|
|
|
337
369
|
}
|
|
338
370
|
.mushi-panel.open { animation: mushi-stamp-in 320ms ${easeStamp} both; }
|
|
339
371
|
.mushi-panel.closed { display: none; }
|
|
340
|
-
.mushi-panel.bottom-right {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
372
|
+
.mushi-panel.bottom-right {
|
|
373
|
+
bottom: var(--mushi-panel-bottom, calc(var(--mushi-bottom, 24px) + 64px));
|
|
374
|
+
right: var(--mushi-right, calc(24px + env(safe-area-inset-right, 0px)));
|
|
375
|
+
--mushi-origin: bottom right;
|
|
376
|
+
}
|
|
377
|
+
.mushi-panel.bottom-left {
|
|
378
|
+
bottom: var(--mushi-panel-bottom, calc(var(--mushi-bottom, 24px) + 64px));
|
|
379
|
+
left: var(--mushi-left, calc(24px + env(safe-area-inset-left, 0px)));
|
|
380
|
+
--mushi-origin: bottom left;
|
|
381
|
+
}
|
|
382
|
+
.mushi-panel.top-right {
|
|
383
|
+
top: var(--mushi-panel-top, calc(var(--mushi-top, 24px) + 64px));
|
|
384
|
+
right: var(--mushi-right, calc(24px + env(safe-area-inset-right, 0px)));
|
|
385
|
+
--mushi-origin: top right;
|
|
386
|
+
}
|
|
387
|
+
.mushi-panel.top-left {
|
|
388
|
+
top: var(--mushi-panel-top, calc(var(--mushi-top, 24px) + 64px));
|
|
389
|
+
left: var(--mushi-left, calc(24px + env(safe-area-inset-left, 0px)));
|
|
390
|
+
--mushi-origin: top left;
|
|
391
|
+
}
|
|
344
392
|
|
|
345
393
|
@keyframes mushi-stamp-in {
|
|
346
394
|
0% { opacity: 0; transform: scale(0.94) translateY(6px); }
|
|
@@ -348,10 +396,6 @@ function getWidgetStyles(theme) {
|
|
|
348
396
|
100% { opacity: 1; transform: scale(1) translateY(0); }
|
|
349
397
|
}
|
|
350
398
|
|
|
351
|
-
/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
352
|
-
Editorial masthead: small mono eyebrow ("MUSHI / REPORT") on top,
|
|
353
|
-
serif display headline below, mono step counter on the far right.
|
|
354
|
-
A single hairline separates header from body \u2014 no card stacking. */
|
|
355
399
|
.mushi-header {
|
|
356
400
|
padding: 18px 20px 14px;
|
|
357
401
|
border-bottom: 1px solid ${rule};
|
|
@@ -448,10 +492,6 @@ function getWidgetStyles(theme) {
|
|
|
448
492
|
.mushi-body::-webkit-scrollbar { width: 6px; }
|
|
449
493
|
.mushi-body::-webkit-scrollbar-thumb { background: ${inkFaint}; border-radius: 3px; }
|
|
450
494
|
|
|
451
|
-
/* \u2500\u2500 Step 1: Categories as a contents-page list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
452
|
-
No boxes. Hairline rules between rows. Hovering a row pulls a
|
|
453
|
-
vermillion arrow in from the right and tints the row label \u2014
|
|
454
|
-
reads like flipping through an index card. */
|
|
455
495
|
.mushi-option-btn {
|
|
456
496
|
display: grid;
|
|
457
497
|
grid-template-columns: auto 1fr auto;
|
|
@@ -504,11 +544,6 @@ function getWidgetStyles(theme) {
|
|
|
504
544
|
transition: opacity 220ms ${easeStamp}, transform 220ms ${easeStamp}, color 220ms ${easeStamp};
|
|
505
545
|
}
|
|
506
546
|
|
|
507
|
-
/* \u2500\u2500 Step 2: Selected-category breadcrumb + intent text-buttons \u2500
|
|
508
|
-
Breadcrumb is a thin chip with the kanji-stamp aesthetic carried
|
|
509
|
-
over (vermillion left rule). Intents are inline TEXT buttons
|
|
510
|
-
with vermillion underlines on hover \u2014 not pill-shaped chips,
|
|
511
|
-
which is the SaaS default and not what we are. */
|
|
512
547
|
.mushi-selected-category {
|
|
513
548
|
display: inline-flex;
|
|
514
549
|
align-items: center;
|
|
@@ -564,10 +599,6 @@ function getWidgetStyles(theme) {
|
|
|
564
599
|
box-shadow: inset 2px 0 0 ${vermillion};
|
|
565
600
|
}
|
|
566
601
|
|
|
567
|
-
/* \u2500\u2500 Step 3: Borderless textarea + minimal attach pills \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
568
|
-
The textarea has no box around it \u2014 just a hairline underline
|
|
569
|
-
that turns vermillion on focus. Encourages writing rather than
|
|
570
|
-
form-filling. */
|
|
571
602
|
.mushi-textarea {
|
|
572
603
|
width: 100%;
|
|
573
604
|
min-height: 96px;
|
|
@@ -625,10 +656,6 @@ function getWidgetStyles(theme) {
|
|
|
625
656
|
outline-offset: 2px;
|
|
626
657
|
}
|
|
627
658
|
|
|
628
|
-
/* \u2500\u2500 Footer + submit (vermillion stamp) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
629
|
-
Submit button is the heaviest visual moment in the widget \u2014
|
|
630
|
-
vermillion fill, mono-caps label, send arrow. Holds an ink-
|
|
631
|
-
bloom pseudo-element that animates outward when pressed. */
|
|
632
659
|
.mushi-footer {
|
|
633
660
|
padding: 14px 22px 16px;
|
|
634
661
|
border-top: 1px solid ${rule};
|
|
@@ -693,10 +720,6 @@ function getWidgetStyles(theme) {
|
|
|
693
720
|
}
|
|
694
721
|
.mushi-submit:hover .mushi-submit-arrow { transform: translateX(3px); }
|
|
695
722
|
|
|
696
|
-
/* \u2500\u2500 Step indicator (numeral ledger) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
697
|
-
Replaces the generic three-dots with a typographic series:
|
|
698
|
-
"01 \u2014 02 \u2014 03". The active step uses serif numerals, the
|
|
699
|
-
others use mono so the active one literally reads heavier. */
|
|
700
723
|
.mushi-step-indicator {
|
|
701
724
|
display: flex;
|
|
702
725
|
align-items: center;
|
|
@@ -724,10 +747,6 @@ function getWidgetStyles(theme) {
|
|
|
724
747
|
}
|
|
725
748
|
.mushi-step-sep { width: 14px; height: 1px; background: ${rule}; }
|
|
726
749
|
|
|
727
|
-
/* \u2500\u2500 Success: \u6731\u5370 stamp animation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
728
|
-
The success state is the signature moment. A vermillion ring
|
|
729
|
-
scribes itself, then a "RECEIVED" mono-caps label fades in at
|
|
730
|
-
the centre, evoking a hanko being pressed onto the form. */
|
|
731
750
|
.mushi-success {
|
|
732
751
|
text-align: center;
|
|
733
752
|
padding: 28px 16px 20px;
|
|
@@ -791,9 +810,6 @@ function getWidgetStyles(theme) {
|
|
|
791
810
|
100% { opacity: 1; transform: rotate(-6deg) scale(1); }
|
|
792
811
|
}
|
|
793
812
|
|
|
794
|
-
/* \u2500\u2500 Error \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
795
|
-
Inline editorial note rather than a red box. Vermillion left
|
|
796
|
-
rule keeps the same accent language. */
|
|
797
813
|
.mushi-error {
|
|
798
814
|
margin-top: 10px;
|
|
799
815
|
padding: 8px 0 8px 10px;
|
|
@@ -804,9 +820,6 @@ function getWidgetStyles(theme) {
|
|
|
804
820
|
letter-spacing: 0.02em;
|
|
805
821
|
}
|
|
806
822
|
|
|
807
|
-
/* \u2500\u2500 Reduced motion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
808
|
-
Honour the OS preference: kill every transition + animation
|
|
809
|
-
except the focus underline (which is critical feedback). */
|
|
810
823
|
@media (prefers-reduced-motion: reduce) {
|
|
811
824
|
*,
|
|
812
825
|
*::before,
|
|
@@ -854,6 +867,12 @@ var MushiWidget = class {
|
|
|
854
867
|
screenshotAttached = false;
|
|
855
868
|
elementSelected = false;
|
|
856
869
|
submitting = false;
|
|
870
|
+
triggerVisible = true;
|
|
871
|
+
triggerShrunk = false;
|
|
872
|
+
triggerHiddenByScroll = false;
|
|
873
|
+
attachedLaunchers = [];
|
|
874
|
+
smartHideCleanup = null;
|
|
875
|
+
smartHideTimer = null;
|
|
857
876
|
/** Captured at the moment of submit so the success ledger metadata
|
|
858
877
|
* ("REPORT · 14:23:07 JST") doesn't drift while the success step
|
|
859
878
|
* is on screen. */
|
|
@@ -879,7 +898,16 @@ var MushiWidget = class {
|
|
|
879
898
|
expandedTitle: config.expandedTitle ?? "",
|
|
880
899
|
mode: config.mode ?? "conversational",
|
|
881
900
|
locale: config.locale ?? "auto",
|
|
882
|
-
zIndex: config.zIndex ?? 99999
|
|
901
|
+
zIndex: config.zIndex ?? 99999,
|
|
902
|
+
trigger: config.trigger ?? "auto",
|
|
903
|
+
attachToSelector: config.attachToSelector ?? "",
|
|
904
|
+
inset: config.inset ?? {},
|
|
905
|
+
respectSafeArea: config.respectSafeArea ?? true,
|
|
906
|
+
hideOnSelector: config.hideOnSelector ?? "",
|
|
907
|
+
hideOnRoutes: config.hideOnRoutes ?? [],
|
|
908
|
+
environments: config.environments ?? {},
|
|
909
|
+
smartHide: config.smartHide ?? false,
|
|
910
|
+
draggable: config.draggable ?? false
|
|
883
911
|
};
|
|
884
912
|
this.callbacks = callbacks;
|
|
885
913
|
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
@@ -889,6 +917,8 @@ var MushiWidget = class {
|
|
|
889
917
|
}
|
|
890
918
|
mount() {
|
|
891
919
|
document.body.appendChild(this.host);
|
|
920
|
+
this.syncAttachedLaunchers();
|
|
921
|
+
this.syncSmartHide();
|
|
892
922
|
this.render();
|
|
893
923
|
}
|
|
894
924
|
updateConfig(config = {}) {
|
|
@@ -900,9 +930,20 @@ var MushiWidget = class {
|
|
|
900
930
|
...config.expandedTitle !== void 0 ? { expandedTitle: config.expandedTitle } : {},
|
|
901
931
|
...config.mode ? { mode: config.mode } : {},
|
|
902
932
|
...config.locale ? { locale: config.locale } : {},
|
|
903
|
-
...config.zIndex !== void 0 ? { zIndex: config.zIndex } : {}
|
|
933
|
+
...config.zIndex !== void 0 ? { zIndex: config.zIndex } : {},
|
|
934
|
+
...config.trigger ? { trigger: config.trigger } : {},
|
|
935
|
+
...config.attachToSelector !== void 0 ? { attachToSelector: config.attachToSelector } : {},
|
|
936
|
+
...config.inset !== void 0 ? { inset: config.inset } : {},
|
|
937
|
+
...config.respectSafeArea !== void 0 ? { respectSafeArea: config.respectSafeArea } : {},
|
|
938
|
+
...config.hideOnSelector !== void 0 ? { hideOnSelector: config.hideOnSelector } : {},
|
|
939
|
+
...config.hideOnRoutes !== void 0 ? { hideOnRoutes: config.hideOnRoutes } : {},
|
|
940
|
+
...config.environments !== void 0 ? { environments: config.environments } : {},
|
|
941
|
+
...config.smartHide !== void 0 ? { smartHide: config.smartHide } : {},
|
|
942
|
+
...config.draggable !== void 0 ? { draggable: config.draggable } : {}
|
|
904
943
|
};
|
|
905
944
|
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
945
|
+
this.syncAttachedLaunchers();
|
|
946
|
+
this.syncSmartHide();
|
|
906
947
|
this.render();
|
|
907
948
|
}
|
|
908
949
|
open(options) {
|
|
@@ -933,6 +974,30 @@ var MushiWidget = class {
|
|
|
933
974
|
getIsOpen() {
|
|
934
975
|
return this.isOpen;
|
|
935
976
|
}
|
|
977
|
+
showTrigger() {
|
|
978
|
+
this.triggerVisible = true;
|
|
979
|
+
this.render();
|
|
980
|
+
}
|
|
981
|
+
hideTrigger() {
|
|
982
|
+
this.triggerVisible = false;
|
|
983
|
+
this.render();
|
|
984
|
+
}
|
|
985
|
+
setTrigger(trigger) {
|
|
986
|
+
this.updateConfig({ trigger });
|
|
987
|
+
}
|
|
988
|
+
attachTo(selectorOrElement, options = {}) {
|
|
989
|
+
const elements = typeof selectorOrElement === "string" ? Array.from(document.querySelectorAll(selectorOrElement)) : [selectorOrElement];
|
|
990
|
+
const cleanups = elements.map((el) => {
|
|
991
|
+
const onClick = (event) => {
|
|
992
|
+
event.preventDefault();
|
|
993
|
+
this.updateConfig(options);
|
|
994
|
+
this.open();
|
|
995
|
+
};
|
|
996
|
+
el.addEventListener("click", onClick);
|
|
997
|
+
return () => el.removeEventListener("click", onClick);
|
|
998
|
+
});
|
|
999
|
+
return () => cleanups.forEach((cleanup) => cleanup());
|
|
1000
|
+
}
|
|
936
1001
|
setScreenshotAttached(attached) {
|
|
937
1002
|
this.screenshotAttached = attached;
|
|
938
1003
|
if (this.isOpen) this.render();
|
|
@@ -950,8 +1015,83 @@ var MushiWidget = class {
|
|
|
950
1015
|
clearTimeout(this.autoCloseTimer);
|
|
951
1016
|
this.autoCloseTimer = null;
|
|
952
1017
|
}
|
|
1018
|
+
if (this.smartHideTimer !== null) {
|
|
1019
|
+
clearTimeout(this.smartHideTimer);
|
|
1020
|
+
this.smartHideTimer = null;
|
|
1021
|
+
}
|
|
1022
|
+
this.smartHideCleanup?.();
|
|
1023
|
+
this.smartHideCleanup = null;
|
|
1024
|
+
this.attachedLaunchers.forEach((cleanup) => cleanup());
|
|
1025
|
+
this.attachedLaunchers = [];
|
|
953
1026
|
this.host.remove();
|
|
954
1027
|
}
|
|
1028
|
+
syncAttachedLaunchers() {
|
|
1029
|
+
this.attachedLaunchers.forEach((cleanup) => cleanup());
|
|
1030
|
+
this.attachedLaunchers = [];
|
|
1031
|
+
if (this.config.trigger !== "attach" || !this.config.attachToSelector) return;
|
|
1032
|
+
if (typeof document === "undefined") return;
|
|
1033
|
+
this.attachedLaunchers.push(this.attachTo(this.config.attachToSelector));
|
|
1034
|
+
}
|
|
1035
|
+
syncSmartHide() {
|
|
1036
|
+
this.smartHideCleanup?.();
|
|
1037
|
+
this.smartHideCleanup = null;
|
|
1038
|
+
this.triggerShrunk = false;
|
|
1039
|
+
this.triggerHiddenByScroll = false;
|
|
1040
|
+
if (!this.config.smartHide || typeof window === "undefined") return;
|
|
1041
|
+
const smart = this.config.smartHide === true ? { onScroll: "shrink", onIdleMs: 900 } : this.config.smartHide;
|
|
1042
|
+
if (!smart.onScroll) return;
|
|
1043
|
+
const onScroll = () => {
|
|
1044
|
+
if (smart.onScroll === "hide") {
|
|
1045
|
+
this.triggerHiddenByScroll = true;
|
|
1046
|
+
} else {
|
|
1047
|
+
this.triggerShrunk = true;
|
|
1048
|
+
}
|
|
1049
|
+
this.render();
|
|
1050
|
+
if (this.smartHideTimer !== null) clearTimeout(this.smartHideTimer);
|
|
1051
|
+
this.smartHideTimer = setTimeout(() => {
|
|
1052
|
+
this.triggerHiddenByScroll = false;
|
|
1053
|
+
this.triggerShrunk = false;
|
|
1054
|
+
this.render();
|
|
1055
|
+
}, smart.onIdleMs ?? 900);
|
|
1056
|
+
};
|
|
1057
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
1058
|
+
this.smartHideCleanup = () => window.removeEventListener("scroll", onScroll);
|
|
1059
|
+
}
|
|
1060
|
+
shouldRenderTrigger() {
|
|
1061
|
+
if (!this.triggerVisible) return false;
|
|
1062
|
+
if (this.triggerHiddenByScroll) return false;
|
|
1063
|
+
if (this.config.trigger === "manual" || this.config.trigger === "hidden" || this.config.trigger === "attach") {
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
if (this.isMobileSmartHidden()) return false;
|
|
1067
|
+
if (this.isRouteHidden()) return false;
|
|
1068
|
+
if (this.config.hideOnSelector && document.querySelector(this.config.hideOnSelector)) return false;
|
|
1069
|
+
const action = this.config.environments[this.detectEnvironment()];
|
|
1070
|
+
return action !== "never" && action !== "manual";
|
|
1071
|
+
}
|
|
1072
|
+
effectiveTrigger() {
|
|
1073
|
+
if (!this.config.smartHide || typeof window === "undefined") return this.config.trigger;
|
|
1074
|
+
const smart = this.config.smartHide === true ? { onMobile: "edge-tab" } : this.config.smartHide;
|
|
1075
|
+
if (window.matchMedia("(max-width: 768px)").matches && smart.onMobile === "edge-tab") {
|
|
1076
|
+
return "edge-tab";
|
|
1077
|
+
}
|
|
1078
|
+
return this.config.trigger;
|
|
1079
|
+
}
|
|
1080
|
+
isMobileSmartHidden() {
|
|
1081
|
+
if (!this.config.smartHide || typeof window === "undefined") return false;
|
|
1082
|
+
const smart = this.config.smartHide === true ? { onMobile: "edge-tab" } : this.config.smartHide;
|
|
1083
|
+
return window.matchMedia("(max-width: 768px)").matches && smart.onMobile === "hide";
|
|
1084
|
+
}
|
|
1085
|
+
detectEnvironment() {
|
|
1086
|
+
const host = typeof location !== "undefined" ? location.hostname : "";
|
|
1087
|
+
if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".local")) return "development";
|
|
1088
|
+
if (/\b(staging|stage|preview|dev)\b/i.test(host)) return "staging";
|
|
1089
|
+
return "production";
|
|
1090
|
+
}
|
|
1091
|
+
isRouteHidden() {
|
|
1092
|
+
if (!this.config.hideOnRoutes.length || typeof location === "undefined") return false;
|
|
1093
|
+
return this.config.hideOnRoutes.some((route) => location.pathname.includes(route));
|
|
1094
|
+
}
|
|
955
1095
|
getTheme() {
|
|
956
1096
|
if (this.config.theme !== "auto") return this.config.theme;
|
|
957
1097
|
if (typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
@@ -967,24 +1107,29 @@ var MushiWidget = class {
|
|
|
967
1107
|
const style = document.createElement("style");
|
|
968
1108
|
style.textContent = getWidgetStyles(theme);
|
|
969
1109
|
this.shadow.appendChild(style);
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1110
|
+
if (this.shouldRenderTrigger()) {
|
|
1111
|
+
const effectiveTrigger = this.effectiveTrigger();
|
|
1112
|
+
const trigger = document.createElement("button");
|
|
1113
|
+
trigger.className = `mushi-trigger ${pos}${effectiveTrigger === "edge-tab" ? " edge-tab" : ""}${this.triggerShrunk ? " shrunk" : ""}`;
|
|
1114
|
+
trigger.textContent = this.config.triggerText;
|
|
1115
|
+
trigger.setAttribute("aria-label", t.widget.trigger);
|
|
1116
|
+
trigger.setAttribute("aria-haspopup", "dialog");
|
|
1117
|
+
trigger.setAttribute("aria-expanded", String(this.isOpen));
|
|
1118
|
+
trigger.style.zIndex = String(this.config.zIndex);
|
|
1119
|
+
this.applyInsetVars(trigger);
|
|
1120
|
+
trigger.addEventListener("click", () => {
|
|
1121
|
+
if (this.isOpen) this.close();
|
|
1122
|
+
else this.open();
|
|
1123
|
+
});
|
|
1124
|
+
this.shadow.appendChild(trigger);
|
|
1125
|
+
}
|
|
982
1126
|
const panel = document.createElement("div");
|
|
983
1127
|
panel.className = `mushi-panel ${pos}${this.isOpen ? " open" : " closed"}`;
|
|
984
1128
|
panel.setAttribute("role", "dialog");
|
|
985
1129
|
panel.setAttribute("aria-modal", "true");
|
|
986
1130
|
panel.setAttribute("aria-label", t.widget.title);
|
|
987
1131
|
panel.style.zIndex = String(this.config.zIndex + 1);
|
|
1132
|
+
this.applyInsetVars(panel);
|
|
988
1133
|
if (this.isOpen) {
|
|
989
1134
|
panel.innerHTML = this.renderStep();
|
|
990
1135
|
this.shadow.appendChild(panel);
|
|
@@ -992,6 +1137,20 @@ var MushiWidget = class {
|
|
|
992
1137
|
this.trapFocus(panel);
|
|
993
1138
|
}
|
|
994
1139
|
}
|
|
1140
|
+
applyInsetVars(el) {
|
|
1141
|
+
const { inset } = this.config;
|
|
1142
|
+
if (!this.config.respectSafeArea) {
|
|
1143
|
+
["top", "right", "bottom", "left"].forEach((edge) => {
|
|
1144
|
+
if (inset[edge] === void 0) el.style.setProperty(`--mushi-${edge}`, "24px");
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
["top", "right", "bottom", "left"].forEach((edge) => {
|
|
1148
|
+
const value = inset[edge];
|
|
1149
|
+
if (value === void 0) return;
|
|
1150
|
+
el.style.setProperty(`--mushi-${edge}`, value === "auto" ? "auto" : `${value}px`);
|
|
1151
|
+
});
|
|
1152
|
+
el.style.setProperty("--mushi-safe-area", this.config.respectSafeArea ? "1" : "0");
|
|
1153
|
+
}
|
|
995
1154
|
renderStep() {
|
|
996
1155
|
switch (this.step) {
|
|
997
1156
|
case "category":
|
|
@@ -2107,6 +2266,21 @@ function createInstance(config) {
|
|
|
2107
2266
|
open() {
|
|
2108
2267
|
widget.open();
|
|
2109
2268
|
},
|
|
2269
|
+
openWith(category) {
|
|
2270
|
+
widget.open({ category });
|
|
2271
|
+
},
|
|
2272
|
+
show() {
|
|
2273
|
+
widget.showTrigger();
|
|
2274
|
+
},
|
|
2275
|
+
hide() {
|
|
2276
|
+
widget.hideTrigger();
|
|
2277
|
+
},
|
|
2278
|
+
attachTo(selectorOrElement, options) {
|
|
2279
|
+
return widget.attachTo(selectorOrElement, options);
|
|
2280
|
+
},
|
|
2281
|
+
setTrigger(trigger) {
|
|
2282
|
+
widget.setTrigger(trigger);
|
|
2283
|
+
},
|
|
2110
2284
|
close() {
|
|
2111
2285
|
widget.close();
|
|
2112
2286
|
},
|
|
@@ -2182,11 +2356,14 @@ function createInstance(config) {
|
|
|
2182
2356
|
return sdk;
|
|
2183
2357
|
}
|
|
2184
2358
|
function mergeRuntimeConfig(config, runtime) {
|
|
2359
|
+
const nativeTrigger = runtime.native?.triggerMode;
|
|
2360
|
+
const widgetTrigger = runtime.widget?.trigger ?? (nativeTrigger === "none" || nativeTrigger === "shake" ? "manual" : void 0);
|
|
2185
2361
|
return {
|
|
2186
2362
|
...config,
|
|
2187
2363
|
widget: {
|
|
2188
2364
|
...config.widget,
|
|
2189
|
-
...runtime.widget
|
|
2365
|
+
...runtime.widget,
|
|
2366
|
+
...widgetTrigger ? { trigger: widgetTrigger } : {}
|
|
2190
2367
|
},
|
|
2191
2368
|
capture: {
|
|
2192
2369
|
...config.capture,
|
|
@@ -2242,6 +2419,16 @@ function createNoopInstance() {
|
|
|
2242
2419
|
},
|
|
2243
2420
|
updateConfig: () => {
|
|
2244
2421
|
},
|
|
2422
|
+
openWith: () => {
|
|
2423
|
+
},
|
|
2424
|
+
show: () => {
|
|
2425
|
+
},
|
|
2426
|
+
hide: () => {
|
|
2427
|
+
},
|
|
2428
|
+
attachTo: () => () => {
|
|
2429
|
+
},
|
|
2430
|
+
setTrigger: () => {
|
|
2431
|
+
},
|
|
2245
2432
|
destroy: () => {
|
|
2246
2433
|
instance = null;
|
|
2247
2434
|
},
|